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