]> granicus.if.org Git - apache/blob - modules/cache/mod_file_cache.c
Fix a problem whereby multiple MMapFile directives would cause a segfault
[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 void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
166 {
167     a_server_config *sconf;
168     a_file *new_file;
169     a_file tmp;
170     apr_file_t *fd = NULL;
171     apr_status_t rc;
172     const char *fspec;
173
174     fspec = ap_server_root_relative(cmd->pool, filename);
175     if (!fspec) {
176         ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server,
177                      "mod_file_cache: invalid file path "
178                      "%s, skipping", filename);
179         return;
180     }
181     if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN, 
182                                  cmd->temp_pool)) != APR_SUCCESS) {
183         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
184             "mod_file_cache: unable to stat(%s), skipping", fspec);
185         return;
186     }
187     if (tmp.finfo.filetype != APR_REG) {
188         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
189             "mod_file_cache: %s isn't a regular file, skipping", fspec);
190         return;
191     }
192     if (tmp.finfo.size > AP_MAX_SENDFILE) {
193         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
194             "mod_file_cache: %s is too large to cache, skipping", fspec);
195         return;
196     }
197
198     rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
199                        APR_OS_DEFAULT, cmd->pool);
200     if (rc != APR_SUCCESS) {
201         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
202                      "mod_file_cache: unable to open(%s, O_RDONLY), skipping", fspec);
203         return;
204     }
205     apr_file_inherit_set(fd);
206
207     /* WooHoo, we have a file to put in the cache */
208     new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
209     new_file->finfo = tmp.finfo;
210
211 #if APR_HAS_MMAP
212     if (mmap) {
213         apr_mmap_t *mm;
214
215         /* MMAPFile directive. MMAP'ing the file
216          * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
217          * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
218          */
219         if ((rc = apr_mmap_create(&mm, fd, 0, 
220                                   (apr_size_t)new_file->finfo.size,
221                                   APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) { 
222             apr_file_close(fd);
223             ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
224                          "mod_file_cache: unable to mmap %s, skipping", filename);
225             return;
226         }
227         apr_file_close(fd);
228         /* We want to cache a duplicate apr_mmap_t to pass to each
229          * request so that nothing in the request will ever think that
230          * it's allowed to delete the mmap, since the "refcount" will
231          * never reach zero. */
232         /* XXX: the transfer_ownership flag on this call
233          * will go away soon.. it's ignored right now. */
234         apr_mmap_dup(&new_file->mm, mm, cmd->pool, 0);
235         new_file->is_mmapped = TRUE;
236     }
237 #endif
238 #if APR_HAS_SENDFILE
239     if (!mmap) {
240         /* CacheFile directive. Caching the file handle */
241         new_file->is_mmapped = FALSE;
242         new_file->file = fd;
243     }
244 #endif
245
246     new_file->filename = fspec;
247     apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
248     apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
249
250     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
251     apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
252
253 }
254
255 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
256 {
257 #if APR_HAS_SENDFILE
258     cache_the_file(cmd, filename, 0);
259 #else
260     /* Sendfile not supported by this OS */
261     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
262                  "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
263 #endif
264     return NULL;
265 }
266 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename) 
267 {
268 #if APR_HAS_MMAP
269     cache_the_file(cmd, filename, 1);
270 #else
271     /* MMAP not supported by this OS */
272     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
273                  "mod_file_cache: unable to cache file: %s. MMAP is not supported by this OS", filename);
274 #endif
275     return NULL;
276 }
277
278 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
279                                    apr_pool_t *ptemp, server_rec *s)
280 {
281     /* Hummm, anything to do here? */
282     return OK;
283 }
284
285 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
286  * bit of a kludge, because we really want to run after core_translate runs.
287  */
288 static int file_cache_xlat(request_rec *r)
289 {
290     a_server_config *sconf;
291     a_file *match;
292     int res;
293
294     sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
295
296     /* we only operate when at least one cachefile directive was used */
297     if (!apr_hash_count(sconf->fileht)) {
298         return DECLINED;
299     }
300
301     res = ap_core_translate(r);
302     if (res != OK || !r->filename) {
303         return res;
304     }
305
306     /* search the cache */
307     match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
308     if (match == NULL)
309         return DECLINED;
310
311     /* pass search results to handler */
312     ap_set_module_config(r->request_config, &file_cache_module, match);
313
314     /* shortcircuit the get_path_info() stat() calls and stuff */
315     r->finfo = match->finfo;
316     return OK;
317 }
318
319 static int mmap_handler(request_rec *r, a_file *file)
320 {
321 #if APR_HAS_MMAP
322     conn_rec *c = r->connection;
323     apr_bucket *b;
324     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
325
326     b = apr_bucket_mmap_create(file->mm, 0, (apr_size_t)file->finfo.size,
327                                c->bucket_alloc);
328     APR_BRIGADE_INSERT_TAIL(bb, b);
329     b = apr_bucket_eos_create(c->bucket_alloc);
330     APR_BRIGADE_INSERT_TAIL(bb, b);
331
332     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
333         return HTTP_INTERNAL_SERVER_ERROR;
334 #endif
335     return OK;
336 }
337
338 static int sendfile_handler(request_rec *r, a_file *file)
339 {
340 #if APR_HAS_SENDFILE
341     conn_rec *c = r->connection;
342     apr_bucket *b;
343     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
344
345     b = apr_bucket_file_create(file->file, 0, (apr_size_t)file->finfo.size,
346                                r->pool, c->bucket_alloc);
347     APR_BRIGADE_INSERT_TAIL(bb, b);
348     b = apr_bucket_eos_create(c->bucket_alloc);
349     APR_BRIGADE_INSERT_TAIL(bb, b);
350
351     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
352         return HTTP_INTERNAL_SERVER_ERROR;
353 #endif
354     return OK;
355 }
356
357 static int file_cache_handler(request_rec *r) 
358 {
359     a_file *match;
360     int errstatus;
361     int rc = OK;
362
363     /* XXX: not sure if this is right yet
364      * see comment in http_core.c:default_handler
365      */
366     if (ap_strcmp_match(r->handler, "*/*")) {
367         return DECLINED;
368     }
369
370     /* we don't handle anything but GET */
371     if (r->method_number != M_GET) return DECLINED;
372
373     /* did xlat phase find the file? */
374     match = ap_get_module_config(r->request_config, &file_cache_module);
375
376     if (match == NULL) {
377         return DECLINED;
378     }
379
380     /* note that we would handle GET on this resource */
381     r->allowed |= (AP_METHOD_BIT << M_GET);
382
383     /* This handler has no use for a request body (yet), but we still
384      * need to read and discard it if the client sent one.
385      */
386     if ((errstatus = ap_discard_request_body(r)) != OK)
387         return errstatus;
388
389     ap_update_mtime(r, match->finfo.mtime);
390
391     /* ap_set_last_modified() always converts the file mtime to a string
392      * which is slow.  Accelerate the common case.
393      * ap_set_last_modified(r);
394      */
395     {
396         apr_time_t mod_time;
397         char *datestr;
398
399         mod_time = ap_rationalize_mtime(r, r->mtime);
400         if (mod_time == match->finfo.mtime)
401             datestr = match->mtimestr;
402         else {
403             datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
404             apr_rfc822_date(datestr, mod_time);
405         }
406         apr_table_setn(r->headers_out, "Last-Modified", datestr);
407     }
408
409     ap_set_etag(r);
410     if ((errstatus = ap_meets_conditions(r)) != OK) {
411        return errstatus;
412     }
413
414     /* ap_set_content_length() always converts the same number and never
415      * returns an error.  Accelerate it.
416      */
417     r->clength = match->finfo.size;
418     apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
419
420     /* Call appropriate handler */
421     if (!r->header_only) {    
422         if (match->is_mmapped == TRUE)
423             rc = mmap_handler(r, match);
424         else
425             rc = sendfile_handler(r, match);
426     }
427
428     return rc;
429 }
430
431 static command_rec file_cache_cmds[] =
432 {
433 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
434      "A space separated list of files to add to the file handle cache at config time"),
435 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
436      "A space separated list of files to mmap at config time"),
437     {NULL}
438 };
439
440 static void register_hooks(apr_pool_t *p)
441 {
442     ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
443     ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
444     ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
445     /* This trick doesn't work apparently because the translate hooks
446        are single shot. If the core_hook returns OK, then our hook is 
447        not called.
448     ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE); 
449     */
450
451 }
452
453 module AP_MODULE_DECLARE_DATA file_cache_module =
454 {
455     STANDARD20_MODULE_STUFF,
456     NULL,                     /* create per-directory config structure */
457     NULL,                     /* merge per-directory config structures */
458     create_server_config,     /* create per-server config structure */
459     NULL,                     /* merge per-server config structures */
460     file_cache_cmds,          /* command handlers */
461     register_hooks            /* register hooks */
462 };