Blame modules/cache/mod_file_cache.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
 * Author: mod_file_cache by Bill Stoddard <stoddard apache.org>
Packit 90a5c9
 *         Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org>
Packit 90a5c9
 *
Packit 90a5c9
 * v0.01: initial implementation
Packit 90a5c9
 */
Packit 90a5c9
Packit 90a5c9
/*
Packit 90a5c9
    Documentation:
Packit 90a5c9
Packit 90a5c9
    Some sites have a set of static files that are really busy, and
Packit 90a5c9
    change infrequently (or even on a regular schedule). Save time
Packit 90a5c9
    by caching open handles to these files. This module, unlike
Packit 90a5c9
    mod_mmap_static, caches open file handles, not file content.
Packit 90a5c9
    On systems (like Windows) with heavy system call overhead and
Packit 90a5c9
    that have an efficient sendfile implementation, caching file handles
Packit 90a5c9
    offers several advantages over caching content. First, the file system
Packit 90a5c9
    can manage the memory, allowing infrequently hit cached files to
Packit 90a5c9
    be paged out. Second, since caching open handles does not consume
Packit 90a5c9
    significant resources, it will be possible to enable an AutoLoadCache
Packit 90a5c9
    feature where static files are dynamically loaded in the cache
Packit 90a5c9
    as the server runs. On systems that have file change notification,
Packit 90a5c9
    this module can be enhanced to automatically garbage collect
Packit 90a5c9
    cached files that change on disk.
Packit 90a5c9
Packit 90a5c9
    This module should work on Unix systems that have sendfile. Place
Packit 90a5c9
    cachefile directives into your configuration to direct files to
Packit 90a5c9
    be cached.
Packit 90a5c9
Packit 90a5c9
        cachefile /path/to/file1
Packit 90a5c9
        cachefile /path/to/file2
Packit 90a5c9
        ...
Packit 90a5c9
Packit 90a5c9
    These files are only cached when the server is restarted, so if you
Packit 90a5c9
    change the list, or if the files are changed, then you'll need to
Packit 90a5c9
    restart the server.
Packit 90a5c9
Packit 90a5c9
    To reiterate that point:  if the files are modified *in place*
Packit 90a5c9
    without restarting the server you may end up serving requests that
Packit 90a5c9
    are completely bogus.  You should update files by unlinking the old
Packit 90a5c9
    copy and putting a new copy in place.
Packit 90a5c9
Packit 90a5c9
    There's no such thing as inheriting these files across vhosts or
Packit 90a5c9
    whatever... place the directives in the main server only.
Packit 90a5c9
Packit 90a5c9
    Known problems:
Packit 90a5c9
Packit 90a5c9
    Don't use Alias or RewriteRule to move these files around...  unless
Packit 90a5c9
    you feel like paying for an extra stat() on each request.  This is
Packit 90a5c9
    a deficiency in the Apache API that will hopefully be solved some day.
Packit 90a5c9
    The file will be served out of the file handle cache, but there will be
Packit 90a5c9
    an extra stat() that's a waste.
Packit 90a5c9
*/
Packit 90a5c9
Packit 90a5c9
#include "apr.h"
Packit 90a5c9
Packit 90a5c9
#if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
Packit 90a5c9
#error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
#include "apr_mmap.h"
Packit 90a5c9
#include "apr_strings.h"
Packit 90a5c9
#include "apr_hash.h"
Packit 90a5c9
#include "apr_buckets.h"
Packit 90a5c9
Packit 90a5c9
#define APR_WANT_STRFUNC
Packit 90a5c9
#include "apr_want.h"
Packit 90a5c9
Packit 90a5c9
#if APR_HAVE_SYS_TYPES_H
Packit 90a5c9
#include <sys/types.h>
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
#include "httpd.h"
Packit 90a5c9
#include "http_config.h"
Packit 90a5c9
#include "http_log.h"
Packit 90a5c9
#include "http_protocol.h"
Packit 90a5c9
#include "http_request.h"
Packit 90a5c9
#include "http_core.h"
Packit 90a5c9
Packit 90a5c9
module AP_MODULE_DECLARE_DATA file_cache_module;
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
#if APR_HAS_SENDFILE
Packit 90a5c9
    apr_file_t *file;
Packit 90a5c9
#endif
Packit 90a5c9
    const char *filename;
Packit 90a5c9
    apr_finfo_t finfo;
Packit 90a5c9
    int is_mmapped;
Packit 90a5c9
#if APR_HAS_MMAP
Packit 90a5c9
    apr_mmap_t *mm;
Packit 90a5c9
#endif
Packit 90a5c9
    char mtimestr[APR_RFC822_DATE_LEN];
Packit 90a5c9
    char sizestr[21];   /* big enough to hold any 64-bit file size + null */
Packit 90a5c9
} a_file;
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    apr_hash_t *fileht;
Packit 90a5c9
} a_server_config;
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
static void *create_server_config(apr_pool_t *p, server_rec *s)
Packit 90a5c9
{
Packit 90a5c9
    a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
Packit 90a5c9
Packit 90a5c9
    sconf->fileht = apr_hash_make(p);
Packit 90a5c9
    return sconf;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
Packit 90a5c9
{
Packit 90a5c9
    a_server_config *sconf;
Packit 90a5c9
    a_file *new_file;
Packit 90a5c9
    a_file tmp;
Packit 90a5c9
    apr_file_t *fd = NULL;
Packit 90a5c9
    apr_status_t rc;
Packit 90a5c9
    const char *fspec;
Packit 90a5c9
Packit 90a5c9
    fspec = ap_server_root_relative(cmd->pool, filename);
Packit 90a5c9
    if (!fspec) {
Packit 90a5c9
        ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(00794)
Packit 90a5c9
                     "invalid file path "
Packit 90a5c9
                     "%s, skipping", filename);
Packit 90a5c9
        return;
Packit 90a5c9
    }
Packit 90a5c9
    if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
Packit 90a5c9
                                 cmd->temp_pool)) != APR_SUCCESS) {
Packit 90a5c9
        ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00795)
Packit 90a5c9
                     "unable to stat(%s), skipping", fspec);
Packit 90a5c9
        return;
Packit 90a5c9
    }
Packit 90a5c9
    if (tmp.finfo.filetype != APR_REG) {
Packit 90a5c9
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00796)
Packit 90a5c9
                     "%s isn't a regular file, skipping", fspec);
Packit 90a5c9
        return;
Packit 90a5c9
    }
Packit 90a5c9
    if (tmp.finfo.size > AP_MAX_SENDFILE) {
Packit 90a5c9
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00797)
Packit 90a5c9
                     "%s is too large to cache, skipping", fspec);
Packit 90a5c9
        return;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
Packit 90a5c9
                       APR_OS_DEFAULT, cmd->pool);
Packit 90a5c9
    if (rc != APR_SUCCESS) {
Packit 90a5c9
        ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00798)
Packit 90a5c9
                     "unable to open(%s, O_RDONLY), skipping", fspec);
Packit 90a5c9
        return;
Packit 90a5c9
    }
Packit 90a5c9
    apr_file_inherit_set(fd);
Packit 90a5c9
Packit 90a5c9
    /* WooHoo, we have a file to put in the cache */
Packit 90a5c9
    new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
Packit 90a5c9
    new_file->finfo = tmp.finfo;
Packit 90a5c9
Packit 90a5c9
#if APR_HAS_MMAP
Packit 90a5c9
    if (mmap) {
Packit 90a5c9
        /* MMAPFile directive. MMAP'ing the file
Packit 90a5c9
         * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
Packit 90a5c9
         * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
Packit 90a5c9
         */
Packit 90a5c9
        if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
Packit 90a5c9
                                  (apr_size_t)new_file->finfo.size,
Packit 90a5c9
                                  APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
Packit 90a5c9
            apr_file_close(fd);
Packit 90a5c9
            ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00799)
Packit 90a5c9
                         "unable to mmap %s, skipping", filename);
Packit 90a5c9
            return;
Packit 90a5c9
        }
Packit 90a5c9
        apr_file_close(fd);
Packit 90a5c9
        new_file->is_mmapped = TRUE;
Packit 90a5c9
    }
Packit 90a5c9
#endif
Packit 90a5c9
#if APR_HAS_SENDFILE
Packit 90a5c9
    if (!mmap) {
Packit 90a5c9
        /* CacheFile directive. Caching the file handle */
Packit 90a5c9
        new_file->is_mmapped = FALSE;
Packit 90a5c9
        new_file->file = fd;
Packit 90a5c9
    }
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
    new_file->filename = fspec;
Packit 90a5c9
    apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
Packit 90a5c9
    apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
Packit 90a5c9
Packit 90a5c9
    sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
Packit 90a5c9
    apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
Packit 90a5c9
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
Packit 90a5c9
{
Packit 90a5c9
#if APR_HAS_SENDFILE
Packit 90a5c9
    cache_the_file(cmd, filename, 0);
Packit 90a5c9
#else
Packit 90a5c9
    /* Sendfile not supported by this OS */
Packit 90a5c9
    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00800)
Packit 90a5c9
                 "unable to cache file: %s. Sendfile is not supported on this OS", filename);
Packit 90a5c9
#endif
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
Packit 90a5c9
{
Packit 90a5c9
#if APR_HAS_MMAP
Packit 90a5c9
    cache_the_file(cmd, filename, 1);
Packit 90a5c9
#else
Packit 90a5c9
    /* MMAP not supported by this OS */
Packit 90a5c9
    ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00801)
Packit 90a5c9
                 "unable to cache file: %s. MMAP is not supported by this OS", filename);
Packit 90a5c9
#endif
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
Packit 90a5c9
                                   apr_pool_t *ptemp, server_rec *s)
Packit 90a5c9
{
Packit 90a5c9
    /* Hummm, anything to do here? */
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
Packit 90a5c9
 * bit of a kludge, because we really want to run after core_translate runs.
Packit 90a5c9
 */
Packit 90a5c9
static int file_cache_xlat(request_rec *r)
Packit 90a5c9
{
Packit 90a5c9
    a_server_config *sconf;
Packit 90a5c9
    a_file *match;
Packit 90a5c9
    int res;
Packit 90a5c9
Packit 90a5c9
    sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
Packit 90a5c9
Packit 90a5c9
    /* we only operate when at least one cachefile directive was used */
Packit 90a5c9
    if (!apr_hash_count(sconf->fileht)) {
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    res = ap_core_translate(r);
Packit 90a5c9
    if (res != OK || !r->filename) {
Packit 90a5c9
        return res;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* search the cache */
Packit 90a5c9
    match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
Packit 90a5c9
    if (match == NULL)
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
Packit 90a5c9
    /* pass search results to handler */
Packit 90a5c9
    ap_set_module_config(r->request_config, &file_cache_module, match);
Packit 90a5c9
Packit 90a5c9
    /* shortcircuit the get_path_info() stat() calls and stuff */
Packit 90a5c9
    r->finfo = match->finfo;
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int mmap_handler(request_rec *r, a_file *file)
Packit 90a5c9
{
Packit 90a5c9
#if APR_HAS_MMAP
Packit 90a5c9
    conn_rec *c = r->connection;
Packit 90a5c9
    apr_bucket *b;
Packit 90a5c9
    apr_mmap_t *mm;
Packit 90a5c9
    apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
Packit 90a5c9
Packit 90a5c9
    apr_mmap_dup(&mm, file->mm, r->pool);
Packit 90a5c9
    b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
Packit 90a5c9
                               c->bucket_alloc);
Packit 90a5c9
    APR_BRIGADE_INSERT_TAIL(bb, b);
Packit 90a5c9
    b = apr_bucket_eos_create(c->bucket_alloc);
Packit 90a5c9
    APR_BRIGADE_INSERT_TAIL(bb, b);
Packit 90a5c9
Packit 90a5c9
    if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
Packit 90a5c9
        return AP_FILTER_ERROR;
Packit 90a5c9
#endif
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int sendfile_handler(request_rec *r, a_file *file)
Packit 90a5c9
{
Packit 90a5c9
#if APR_HAS_SENDFILE
Packit 90a5c9
    conn_rec *c = r->connection;
Packit 90a5c9
    apr_bucket *b;
Packit 90a5c9
    apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
Packit 90a5c9
Packit 90a5c9
    apr_brigade_insert_file(bb, file->file, 0, file->finfo.size, r->pool);
Packit 90a5c9
Packit 90a5c9
    b = apr_bucket_eos_create(c->bucket_alloc);
Packit 90a5c9
    APR_BRIGADE_INSERT_TAIL(bb, b);
Packit 90a5c9
Packit 90a5c9
    if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
Packit 90a5c9
        return AP_FILTER_ERROR;
Packit 90a5c9
#endif
Packit 90a5c9
    return OK;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int file_cache_handler(request_rec *r)
Packit 90a5c9
{
Packit 90a5c9
    a_file *match;
Packit 90a5c9
    int errstatus;
Packit 90a5c9
    int rc = OK;
Packit 90a5c9
Packit 90a5c9
    /* Bail out if r->handler isn't the default value, and doesn't look like a Content-Type
Packit 90a5c9
     * XXX: Even though we made the user explicitly list each path to cache?
Packit 90a5c9
     */
Packit 90a5c9
    if (ap_strcmp_match(r->handler, "*/*") && !AP_IS_DEFAULT_HANDLER_NAME(r->handler)) {
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* we don't handle anything but GET */
Packit 90a5c9
    if (r->method_number != M_GET) return DECLINED;
Packit 90a5c9
Packit 90a5c9
    /* did xlat phase find the file? */
Packit 90a5c9
    match = ap_get_module_config(r->request_config, &file_cache_module);
Packit 90a5c9
Packit 90a5c9
    if (match == NULL) {
Packit 90a5c9
        return DECLINED;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* note that we would handle GET on this resource */
Packit 90a5c9
    r->allowed |= (AP_METHOD_BIT << M_GET);
Packit 90a5c9
Packit 90a5c9
    /* This handler has no use for a request body (yet), but we still
Packit 90a5c9
     * need to read and discard it if the client sent one.
Packit 90a5c9
     */
Packit 90a5c9
    if ((errstatus = ap_discard_request_body(r)) != OK)
Packit 90a5c9
        return errstatus;
Packit 90a5c9
Packit 90a5c9
    ap_update_mtime(r, match->finfo.mtime);
Packit 90a5c9
Packit 90a5c9
    /* ap_set_last_modified() always converts the file mtime to a string
Packit 90a5c9
     * which is slow.  Accelerate the common case.
Packit 90a5c9
     * ap_set_last_modified(r);
Packit 90a5c9
     */
Packit 90a5c9
    {
Packit 90a5c9
        apr_time_t mod_time;
Packit 90a5c9
        char *datestr;
Packit 90a5c9
Packit 90a5c9
        mod_time = ap_rationalize_mtime(r, r->mtime);
Packit 90a5c9
        if (mod_time == match->finfo.mtime)
Packit 90a5c9
            datestr = match->mtimestr;
Packit 90a5c9
        else {
Packit 90a5c9
            datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
Packit 90a5c9
            apr_rfc822_date(datestr, mod_time);
Packit 90a5c9
        }
Packit 90a5c9
        apr_table_setn(r->headers_out, "Last-Modified", datestr);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* ap_set_content_length() always converts the same number and never
Packit 90a5c9
     * returns an error.  Accelerate it.
Packit 90a5c9
     */
Packit 90a5c9
    r->clength = match->finfo.size;
Packit 90a5c9
    apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
Packit 90a5c9
Packit 90a5c9
    ap_set_etag(r);
Packit 90a5c9
    if ((errstatus = ap_meets_conditions(r)) != OK) {
Packit 90a5c9
       return errstatus;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    /* Call appropriate handler */
Packit 90a5c9
    if (!r->header_only) {
Packit 90a5c9
        if (match->is_mmapped == TRUE)
Packit 90a5c9
            rc = mmap_handler(r, match);
Packit 90a5c9
        else
Packit 90a5c9
            rc = sendfile_handler(r, match);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return rc;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static command_rec file_cache_cmds[] =
Packit 90a5c9
{
Packit 90a5c9
AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
Packit 90a5c9
     "A space separated list of files to add to the file handle cache at config time"),
Packit 90a5c9
AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
Packit 90a5c9
     "A space separated list of files to mmap at config time"),
Packit 90a5c9
    {NULL}
Packit 90a5c9
};
Packit 90a5c9
Packit 90a5c9
static void register_hooks(apr_pool_t *p)
Packit 90a5c9
{
Packit 90a5c9
    ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
Packit 90a5c9
    ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
Packit 90a5c9
    ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
Packit 90a5c9
    /* This trick doesn't work apparently because the translate hooks
Packit 90a5c9
       are single shot. If the core_hook returns OK, then our hook is
Packit 90a5c9
       not called.
Packit 90a5c9
    ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
Packit 90a5c9
    */
Packit 90a5c9
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
AP_DECLARE_MODULE(file_cache) =
Packit 90a5c9
{
Packit 90a5c9
    STANDARD20_MODULE_STUFF,
Packit 90a5c9
    NULL,                     /* create per-directory config structure */
Packit 90a5c9
    NULL,                     /* merge per-directory config structures */
Packit 90a5c9
    create_server_config,     /* create per-server config structure */
Packit 90a5c9
    NULL,                     /* merge per-server config structures */
Packit 90a5c9
    file_cache_cmds,          /* command handlers */
Packit 90a5c9
    register_hooks            /* register hooks */
Packit 90a5c9
};