]> granicus.if.org Git - apache/blob - modules/cache/mod_file_cache.c
BUCKET FREELISTS
[apache] / modules / cache / mod_file_cache.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  */
54
55 /*
56  * Author: mod_file_cache by Bill Stoddard <stoddard@apache.org> 
57  *         Based on mod_mmap_static by Dean Gaudet <dgaudet@arctic.org>
58  *
59  * v0.01: initial implementation
60  */
61
62 /*
63     Documentation:
64
65     Some sites have a set of static files that are really busy, and 
66     change infrequently (or even on a regular schedule). Save time 
67     by caching open handles to these files. This module, unlike 
68     mod_mmap_static, caches open file handles, not file content. 
69     On systems (like Windows) with heavy system call overhead and
70     that have an efficient sendfile implementation, caching file handles
71     offers several advantages over caching content. First, the file system
72     can manage the memory, allowing infrequently hit cached files to
73     be paged out. Second, since caching open handles does not consume
74     significant resources, it will be possible to enable an AutoLoadCache
75     feature where static files are dynamically loaded in the cache 
76     as the server runs. On systems that have file change notification,
77     this module can be enhanced to automatically garbage collect 
78     cached files that change on disk.
79
80     This module should work on Unix systems that have sendfile. Place 
81     cachefile directives into your configuration to direct files to
82     be cached.
83
84         cachefile /path/to/file1
85         cachefile /path/to/file2
86         ...
87
88     These files are only cached when the server is restarted, so if you 
89     change the list, or if the files are changed, then you'll need to 
90     restart the server.
91
92     To reiterate that point:  if the files are modified *in place*
93     without restarting the server you may end up serving requests that
94     are completely bogus.  You should update files by unlinking the old
95     copy and putting a new copy in place. 
96
97     There's no such thing as inheriting these files across vhosts or
98     whatever... place the directives in the main server only.
99
100     Known problems:
101
102     Don't use Alias or RewriteRule to move these files around...  unless
103     you feel like paying for an extra stat() on each request.  This is
104     a deficiency in the Apache API that will hopefully be solved some day.
105     The file will be served out of the file handle cache, but there will be
106     an extra stat() that's a waste.
107 */
108
109 #include "apr.h"
110
111 #if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
112 #error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
113 #endif
114
115 #include "apr_mmap.h"
116 #include "apr_strings.h"
117 #include "apr_hash.h"
118 #include "apr_buckets.h"
119
120 #define APR_WANT_STRFUNC
121 #include "apr_want.h"
122
123 #if APR_HAVE_SYS_TYPES_H
124 #include <sys/types.h>
125 #endif
126
127 #define CORE_PRIVATE
128
129 #include "httpd.h"
130 #include "http_config.h"
131 #include "http_log.h"
132 #include "http_protocol.h"
133 #include "http_request.h"
134 #include "http_core.h"
135
136 module AP_MODULE_DECLARE_DATA file_cache_module;
137
138 typedef struct {
139 #if APR_HAS_SENDFILE
140     apr_file_t *file;
141 #endif
142     const char *filename;
143     apr_finfo_t finfo;
144     int is_mmapped;
145 #if APR_HAS_MMAP
146     apr_mmap_t *mm;
147 #endif
148     char mtimestr[APR_RFC822_DATE_LEN];
149     char sizestr[21];   /* big enough to hold any 64-bit file size + null */ 
150 } a_file;
151
152 typedef struct {
153     apr_hash_t *fileht;
154 } a_server_config;
155
156
157 static void *create_server_config(apr_pool_t *p, server_rec *s)
158 {
159     a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
160
161     sconf->fileht = apr_hash_make(p);
162     return sconf;
163 }
164
165 static apr_status_t cleanup_file_cache(void *sconfv)
166 {
167     a_server_config *sconf = sconfv;
168     apr_pool_t *p = apr_hash_pool_get(sconf->fileht);
169     a_file *file;
170     apr_hash_index_t *hi;
171
172     /* Iterate over the file hash table and clean up each entry */
173     for (hi = apr_hash_first(p, sconf->fileht); hi; hi=apr_hash_next(hi)) {
174         apr_hash_this(hi, NULL, NULL, (void **)&file);
175 #if APR_HAS_MMAP
176         if (file->is_mmapped) { 
177             apr_mmap_delete(file->mm);
178         } 
179 #endif 
180 #if APR_HAS_SENDFILE
181         if (!file->is_mmapped) {
182             apr_file_close(file->file); 
183         }
184 #endif
185     }
186     return APR_SUCCESS;
187 }
188
189 static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
190 {
191     a_server_config *sconf;
192     a_file *new_file;
193     a_file tmp;
194     apr_file_t *fd = NULL;
195     apr_status_t rc;
196     const char *fspec;
197
198     fspec = ap_server_root_relative(cmd->pool, filename);
199     if (!fspec) {
200         ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
201                      "mod_file_cache: invalid file path "
202                      "%s, skipping", filename);
203         return;
204     }
205     if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN, 
206                                  cmd->temp_pool)) != APR_SUCCESS) {
207         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
208             "mod_file_cache: unable to stat(%s), skipping", fspec);
209         return;
210     }
211     if (tmp.finfo.filetype != APR_REG) {
212         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
213             "mod_file_cache: %s isn't a regular file, skipping", fspec);
214         return;
215     }
216     if (tmp.finfo.size > AP_MAX_SENDFILE) {
217         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
218             "mod_file_cache: %s is too large to cache, skipping", fspec);
219         return;
220     }
221
222     rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
223                        APR_OS_DEFAULT, cmd->pool);
224     if (rc != APR_SUCCESS) {
225         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
226                      "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
227         return;
228     }
229     apr_file_set_inherit(fd);
230
231     /* WooHoo, we have a file to put in the cache */
232     new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
233     new_file->finfo = tmp.finfo;
234
235 #if APR_HAS_MMAP
236     if (mmap) {
237         apr_mmap_t *mm;
238
239         /* MMAPFile directive. MMAP'ing the file
240          * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
241          * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
242          */
243         if ((rc = apr_mmap_create(&mm, fd, 0, 
244                                   (apr_size_t)new_file->finfo.size,
245                                   APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) { 
246             apr_file_close(fd);
247             /* We want to cache an apr_mmap_t that's marked as "non-owner"
248              * to pass to each request so that mmap_setaside()'s call to
249              * apr_mmap_dup() will never try to move the apr_mmap_t to a
250              * different pool.  This apr_mmap_t is already going to live
251              * longer than any request, but mmap_setaside() has no way to
252              * know that because it's allocated out of cmd->pool,
253              * which is disjoint from r->pool.
254              */
255             apr_mmap_dup(&new_file->mm, mm, cmd->pool, 0);
256             ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
257                          "mod_file_cache: unable to mmap %s, skipping", filename);
258             return;
259         }
260         apr_file_close(fd);
261         new_file->is_mmapped = TRUE;
262     }
263 #endif
264 #if APR_HAS_SENDFILE
265     if (!mmap) {
266         /* CacheFile directive. Caching the file handle */
267         new_file->is_mmapped = FALSE;
268         new_file->file = fd;
269     }
270 #endif
271
272     new_file->filename = fspec;
273     apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
274     apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
275
276     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
277     apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
278
279     if (apr_hash_count(sconf->fileht) == 1) {
280         /* first one, register the cleanup */
281         apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null);
282     }
283 }
284
285 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
286 {
287 #if APR_HAS_SENDFILE
288     cache_the_file(cmd, filename, 0);
289 #else
290     /* Sendfile not supported by this OS */
291     ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
292                  "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
293 #endif
294     return NULL;
295 }
296 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename) 
297 {
298 #if APR_HAS_MMAP
299     cache_the_file(cmd, filename, 1);
300 #else
301     /* MMAP not supported by this OS */
302     ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
303                  "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
304 #endif
305     return NULL;
306 }
307
308 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
309                                    apr_pool_t *ptemp, server_rec *s)
310 {
311     /* Hummm, anything to do here? */
312     return OK;
313 }
314
315 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
316  * bit of a kludge, because we really want to run after core_translate runs.
317  */
318 static int file_cache_xlat(request_rec *r)
319 {
320     a_server_config *sconf;
321     a_file *match;
322     int res;
323
324     sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
325
326     /* we only operate when at least one cachefile directive was used */
327     if (!apr_hash_count(sconf->fileht)) {
328         return DECLINED;
329     }
330
331     res = ap_core_translate(r);
332     if (res != OK || !r->filename) {
333         return res;
334     }
335
336     /* search the cache */
337     match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
338     if (match == NULL)
339         return DECLINED;
340
341     /* pass search results to handler */
342     ap_set_module_config(r->request_config, &file_cache_module, match);
343
344     /* shortcircuit the get_path_info() stat() calls and stuff */
345     r->finfo = match->finfo;
346     return OK;
347 }
348
349 static int mmap_handler(request_rec *r, a_file *file)
350 {
351 #if APR_HAS_MMAP
352     conn_rec *c = r->connection;
353     apr_bucket *b;
354     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
355
356     b = apr_bucket_mmap_create(file->mm, 0, (apr_size_t)file->finfo.size,
357                                c->bucket_alloc);
358     APR_BRIGADE_INSERT_TAIL(bb, b);
359     b = apr_bucket_eos_create(c->bucket_alloc);
360     APR_BRIGADE_INSERT_TAIL(bb, b);
361
362     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
363         return HTTP_INTERNAL_SERVER_ERROR;
364 #endif
365     return OK;
366 }
367
368 static int sendfile_handler(request_rec *r, a_file *file)
369 {
370 #if APR_HAS_SENDFILE
371     conn_rec *c = r->connection;
372     apr_bucket *b;
373     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
374
375     b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
376                                r->pool, c->bucket_alloc);
377     APR_BRIGADE_INSERT_TAIL(bb, b);
378     b = apr_bucket_eos_create(c->bucket_alloc);
379     APR_BRIGADE_INSERT_TAIL(bb, b);
380
381     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
382         return HTTP_INTERNAL_SERVER_ERROR;
383 #endif
384     return OK;
385 }
386
387 static int file_cache_handler(request_rec *r) 
388 {
389     a_file *match;
390     int errstatus;
391     int rc = OK;
392
393     /* XXX: not sure if this is right yet
394      * see comment in http_core.c:default_handler
395      */
396     if (ap_strcmp_match(r->handler, "*/*")) {
397         return DECLINED;
398     }
399
400     /* we don't handle anything but GET */
401     if (r->method_number != M_GET) return DECLINED;
402
403     /* did xlat phase find the file? */
404     match = ap_get_module_config(r->request_config, &file_cache_module);
405
406     if (match == NULL) {
407         return DECLINED;
408     }
409
410     /* note that we would handle GET on this resource */
411     r->allowed |= (AP_METHOD_BIT << M_GET);
412
413     /* This handler has no use for a request body (yet), but we still
414      * need to read and discard it if the client sent one.
415      */
416     if ((errstatus = ap_discard_request_body(r)) != OK)
417         return errstatus;
418
419     ap_update_mtime(r, match->finfo.mtime);
420
421     /* ap_set_last_modified() always converts the file mtime to a string
422      * which is slow.  Accelerate the common case.
423      * ap_set_last_modified(r);
424      */
425     {
426         apr_time_t mod_time;
427         char *datestr;
428
429         mod_time = ap_rationalize_mtime(r, r->mtime);
430         if (mod_time == match->finfo.mtime)
431             datestr = match->mtimestr;
432         else {
433             datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
434             apr_rfc822_date(datestr, mod_time);
435         }
436         apr_table_setn(r->headers_out, "Last-Modified", datestr);
437     }
438
439     ap_set_etag(r);
440     if ((errstatus = ap_meets_conditions(r)) != OK) {
441        return errstatus;
442     }
443
444     /* ap_set_content_length() always converts the same number and never
445      * returns an error.  Accelerate it.
446      */
447     r->clength = match->finfo.size;
448     apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
449
450     /* Call appropriate handler */
451     if (!r->header_only) {    
452         if (match->is_mmapped == TRUE)
453             rc = mmap_handler(r, match);
454         else
455             rc = sendfile_handler(r, match);
456     }
457
458     return rc;
459 }
460
461 static command_rec file_cache_cmds[] =
462 {
463 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
464      "A space separated list of files to add to the file handle cache at config time"),
465 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
466      "A space separated list of files to mmap at config time"),
467     {NULL}
468 };
469
470 static void register_hooks(apr_pool_t *p)
471 {
472     ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
473     ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
474     ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
475     /* This trick doesn't work apparently because the translate hooks
476        are single shot. If the core_hook returns OK, then our hook is 
477        not called.
478     ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE); 
479     */
480
481 }
482
483 module AP_MODULE_DECLARE_DATA file_cache_module =
484 {
485     STANDARD20_MODULE_STUFF,
486     NULL,                     /* create per-directory config structure */
487     NULL,                     /* merge per-directory config structures */
488     create_server_config,     /* create per-server config structure */
489     NULL,                     /* merge per-server config structures */
490     file_cache_cmds,          /* command handlers */
491     register_hooks            /* register hooks */
492 };