]> granicus.if.org Git - apache/blob - modules/cache/mod_file_cache.c
Provide apr_pool_t arg to register_hooks, since anything they do in that
[apache] / modules / cache / mod_file_cache.c
1 /* ====================================================================
2  * Copyright (c) 1998-1999 The Apache Group.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer. 
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * 3. All advertising materials mentioning features or use of this
17  *    software must display the following acknowledgment:
18  *    "This product includes software developed by the Apache Group
19  *    for use in the Apache HTTP server project (http://www.apache.org/)."
20  *
21  * 4. The names "Apache Server" and "Apache Group" must not be used to
22  *    endorse or promote products derived from this software without
23  *    prior written permission. For written permission, please contact
24  *    apache@apache.org.
25  *
26  * 5. Products derived from this software may not be called "Apache"
27  *    nor may "Apache" appear in their names without prior written
28  *    permission of the Apache Group.
29  *
30  * 6. Redistributions of any form whatsoever must retain the following
31  *    acknowledgment:
32  *    "This product includes software developed by the Apache Group
33  *    for use in the Apache HTTP server project (http://www.apache.org/)."
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46  * OF THE POSSIBILITY OF SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Group and was originally based
51  * on public domain software written at the National Center for
52  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53  * For more information on the Apache Group and the Apache HTTP server
54  * project, please see <http://www.apache.org/>.
55  *
56  */
57
58 /*
59  * Author: mod_file_cache by Bill Stoddard <stoddard@apache.org> 
60  *         Based on mod_mmap_static by Dean Gaudet <dgaudet@arctic.org>
61  *
62  * v0.01: initial implementation
63  */
64
65 /*
66     Documentation:
67
68     Some sites have a set of static files that are really busy, and 
69     change infrequently (or even on a regular schedule). Save time 
70     by caching open handles to these files. This module, unlike 
71     mod_mmap_static, caches open file handles, not file content. 
72     On systems (like Windows) with heavy system call overhead and
73     that have an efficient sendfile implementation, caching file handles
74     offers several advantages over caching content. First, the file system
75     can manage the memory, allowing infrequently hit cached files to
76     be paged out. Second, since caching open handles does not consume
77     significant resources, it will be possible to enable an AutoLoadCache
78     feature where static files are dynamically loaded in the cache 
79     as the server runs. On systems that have file change notification,
80     this module can be enhanced to automatically garbage collect 
81     cached files that change on disk.
82
83     This module should work on Unix systems that have sendfile. Place 
84     cachefile directives into your configuration to direct files to
85     be cached.
86
87         cachefile /path/to/file1
88         cachefile /path/to/file2
89         ...
90
91     These files are only cached when the server is restarted, so if you 
92     change the list, or if the files are changed, then you'll need to 
93     restart the server.
94
95     To reiterate that point:  if the files are modified *in place*
96     without restarting the server you may end up serving requests that
97     are completely bogus.  You should update files by unlinking the old
98     copy and putting a new copy in place. 
99
100     There's no such thing as inheriting these files across vhosts or
101     whatever... place the directives in the main server only.
102
103     Known problems:
104
105     Don't use Alias or RewriteRule to move these files around...  unless
106     you feel like paying for an extra stat() on each request.  This is
107     a deficiency in the Apache API that will hopefully be solved some day.
108     The file will be served out of the file handle cache, but there will be
109     an extra stat() that's a waste.
110 */
111
112 #ifdef HAVE_STDIOP_H
113 #include <stdio.h>
114 #endif
115 #ifdef HAVE_SYS_TYPES_H
116 #include <sys/types.h>
117 #endif
118 #ifdef HAVE_STRING_H
119 #include <string.h>
120 #endif
121
122 #define CORE_PRIVATE
123
124 #include "httpd.h"
125 #include "http_config.h"
126 #include "http_log.h"
127 #include "http_protocol.h"
128 #include "http_request.h"
129 #include "http_core.h"
130 #include "apr_mmap.h"
131 #include "apr_strings.h"
132
133 module AP_MODULE_DECLARE_DATA file_cache_module;
134
135 typedef struct {
136 #if APR_HAS_SENDFILE
137     apr_file_t *file;
138 #endif
139     const char *filename;
140     apr_finfo_t finfo;
141     int is_mmapped;
142 #if APR_HAS_MMAP
143     apr_mmap_t *mm;
144 #endif
145 } a_file;
146
147 typedef struct {
148     apr_array_header_t *files;
149 } a_server_config;
150
151
152 static void *create_server_config(apr_pool_t *p, server_rec *s)
153 {
154     a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
155
156     sconf->files = apr_make_array(p, 20, sizeof(a_file));
157     return sconf;
158 }
159
160 #if APR_HAS_SENDFILE
161 static apr_status_t open_file(apr_file_t **file, const char *filename, int flg1, int flg2, 
162                              apr_pool_t *p)
163 {
164     apr_status_t rv;
165 #ifdef WIN32
166     /* The Windows file needs to be opened for overlapped i/o, which APR doesn't
167      * support.
168      */
169     HANDLE hFile;
170     /* XXX: This is wrong for unicode FS ... and it doesn't belong in httpd */
171     hFile = CreateFile(filename,          /* pointer to name of the file */
172                        GENERIC_READ,      /* access (read-write) mode */
173                        FILE_SHARE_READ,   /* share mode */
174                        NULL,              /* pointer to security attributes */
175                        OPEN_EXISTING,     /* how to create */
176                        FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, /* file attributes */
177                        NULL);            /* handle to file with attributes to copy */
178     if (hFile != INVALID_HANDLE_VALUE) {
179         rv = apr_put_os_file(file, &hFile, p);
180     }
181     else {
182         rv = GetLastError();
183         *file = NULL;
184     }
185 #else
186     rv = apr_open(file, filename, flg1, flg2, p);
187 #endif
188
189     return rv;
190 }
191 #endif /* APR_HAS_SENDFILE */
192
193 static apr_status_t cleanup_file_cache(void *sconfv)
194 {
195     a_server_config *sconf = sconfv;
196     size_t n;
197     a_file *file;
198
199     n = sconf->files->nelts;
200     file = (a_file *)sconf->files->elts;
201     while(n) {
202 #if APR_HAS_MMAP
203         if (file->is_mmapped) { 
204             apr_mmap_delete(file->mm);
205         } 
206         else 
207 #endif 
208 #if APR_HAS_SENDFILE
209             apr_close(file->file); 
210 #endif
211             ++file;
212             --n;
213     }
214     return APR_SUCCESS;
215 }
216
217 static const char *cachefile(cmd_parms *cmd, void *dummy, const char *filename)
218
219 {
220     /* ToDo:
221      * Disable the file cache on a Windows 9X box. APR_HAS_SENDFILE will be
222      * defined in an Apache for Windows build, but apr_sendfile is not
223      * implemened on Windows 9X because TransmitFile is not available.
224      */
225
226 #if APR_HAS_SENDFILE
227     a_server_config *sconf;
228     a_file *new_file;
229     a_file tmp;
230     apr_file_t *fd = NULL;
231     apr_status_t rc;
232
233     /* canonicalize the file name? */
234     /* os_canonical... */
235     if ((rc = apr_stat(&tmp.finfo, filename, cmd->temp_pool)) != APR_SUCCESS) {
236         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
237             "mod_file_cache: unable to stat(%s), skipping", filename);
238         return NULL;
239     }
240     if (tmp.finfo.filetype != APR_REG) {
241         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
242             "mod_file_cache: %s isn't a regular file, skipping", filename);
243         return NULL;
244     }
245
246     /* Note: open_file should call apr_open for Unix and CreateFile for Windows.
247      * The Windows file needs to be opened for async I/O to allow multiple threads
248      * to serve it up at once.
249      */
250     rc = open_file(&fd, filename, APR_READ, APR_OS_DEFAULT, cmd->pool);
251     if (rc != APR_SUCCESS) {
252         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
253                      "mod_file_cache: unable to open(%s, O_RDONLY), skipping", filename);
254         return NULL;
255     }
256     tmp.file = fd;
257     tmp.filename = apr_pstrdup(cmd->pool, filename);
258     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
259     new_file = apr_push_array(sconf->files);
260     *new_file = tmp;
261     if (sconf->files->nelts == 1) {
262         /* first one, register the cleanup */
263         apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup);
264     }
265
266     new_file->is_mmapped = FALSE;
267
268     return NULL;
269 #else
270     /* Sendfile not supported on this platform */
271     ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
272                  "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
273     return NULL;
274 #endif
275 }
276
277 static const char *mmapfile(cmd_parms *cmd, void *dummy, const char *filename)
278 {
279 #if APR_HAS_MMAP
280     a_server_config *sconf;
281     a_file *new_file;
282     a_file tmp;
283     apr_file_t *fd = NULL;
284     apr_status_t rc;
285     const char *fspec;
286
287     fspec = ap_os_case_canonical_filename(cmd->pool, filename);
288     if ((rc = apr_stat(&tmp.finfo, fspec, cmd->temp_pool)) != APR_SUCCESS) {
289         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
290             "mod_file_cache: unable to stat(%s), skipping", filename);
291         return NULL;
292     }
293     if ((tmp.finfo.filetype) != APR_REG) {
294         ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
295             "mod_file_cache: %s isn't a regular file, skipping", filename);
296         return NULL;
297     }
298     if ((rc = apr_open(&fd, fspec, APR_READ, APR_OS_DEFAULT, 
299                        cmd->temp_pool)) != APR_SUCCESS) { 
300         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
301                      "mod_file_cache: unable to open %s, skipping", 
302                      filename);
303         return NULL;
304     }
305     if ((rc = apr_mmap_create(&tmp.mm, fd, 0, tmp.finfo.size, APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) { 
306         apr_close(fd);
307         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
308             "mod_file_cache: unable to mmap %s, skipping", filename);
309         return NULL;
310     }
311     apr_close(fd);
312     tmp.filename = fspec;
313     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
314     new_file = apr_push_array(sconf->files);
315     *new_file = tmp;
316     if (sconf->files->nelts == 1) {
317         /* first one, register the cleanup */
318        apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup); 
319     }
320
321     new_file->is_mmapped = TRUE;
322
323     return NULL;
324 #else
325     /* MMAP not supported on this platform*/
326     return NULL;
327 #endif
328 }
329
330
331 static int file_compare(const void *av, const void *bv)
332 {
333     const a_file *a = av;
334     const a_file *b = bv;
335
336     return strcmp(a->filename, b->filename);
337 }
338
339 static void file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
340                                    apr_pool_t *ptemp, server_rec *s)
341 {
342     a_server_config *sconf;
343     a_file *elts;
344     int nelts;
345
346     /* sort the elements of the main_server, by filename */
347     sconf = ap_get_module_config(s->module_config, &file_cache_module);
348     elts = (a_file *)sconf->files->elts;
349     nelts = sconf->files->nelts;
350     qsort(elts, nelts, sizeof(a_file), file_compare);
351
352     /* and make the virtualhosts share the same thing */
353     for (s = s->next; s; s = s->next) {
354         ap_set_module_config(s->module_config, &file_cache_module, sconf);
355     }
356 }
357
358 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
359  * bit of a kludge, because we really want to run after core_translate runs.
360  */
361 static int file_cache_xlat(request_rec *r)
362 {
363     a_server_config *sconf;
364     a_file tmp;
365     a_file *match;
366     int res;
367
368     sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
369
370     /* we only operate when at least one cachefile directive was used */
371     if (apr_is_empty_table(sconf->files))
372         return DECLINED;
373
374     res = ap_core_translate(r);
375     if (res != OK || !r->filename) {
376         return res;
377     }
378
379     tmp.filename = r->filename;
380     match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts,
381         sizeof(a_file), file_compare);
382
383     if (match == NULL)
384         return DECLINED;
385
386     /* pass bsearch results to handler */
387     ap_set_module_config(r->request_config, &file_cache_module, match);
388
389     /* shortcircuit the get_path_info() stat() calls and stuff */
390     r->finfo = match->finfo;
391     return OK;
392 }
393
394
395 static int mmap_handler(request_rec *r, a_file *file)
396 {
397 #if APR_HAS_MMAP
398     ap_send_mmap (file->mm, r, 0, file->finfo.size);
399 #endif
400     return OK;
401 }
402
403 static int sendfile_handler(request_rec *r, a_file *file)
404 {
405 #if APR_HAS_SENDFILE
406     apr_size_t nbytes;
407     apr_status_t rv = APR_EINIT;
408
409     /* A cached file handle (more importantly, its file pointer) is 
410      * shared by all threads in the process. The file pointer will 
411      * be corrupted if multiple threads attempt to read from the 
412      * cached file handle. The sendfile API does not rely on the position 
413      * of the file pointer instead taking explicit file offset and 
414      * length arguments. 
415      *
416      * We should call ap_send_fd with a cached file handle IFF 
417      * we are CERTAIN the file will be served with apr_sendfile(). 
418      * The presense of an AP_FTYPE_FILTER in the filter chain nearly
419      * guarantees that apr_sendfile will NOT be used to send the file.
420      * Furthermore, AP_FTYPE_CONTENT filters will be at the beginning
421      * of the chain, so it should suffice to just check the first 
422      * filter in the chain. If the first filter is not a content filter, 
423      * assume apr_sendfile() will be used to send the content.
424      */
425     if (r->output_filters && r->output_filters->frec) {
426         if (r->output_filters->frec->ftype == AP_FTYPE_CONTENT)
427             return DECLINED;
428     }
429
430
431     rv = ap_send_fd(file->file, r, 0, file->finfo.size, &nbytes);
432     if (rv != APR_SUCCESS) {
433         /* ap_send_fd will log the error */
434         return HTTP_INTERNAL_SERVER_ERROR;
435     }
436 #endif
437     return OK;
438 }
439
440 static int file_cache_handler(request_rec *r) 
441 {
442     a_file *match;
443     int errstatus;
444     int rc = OK;
445
446     /* XXX: not sure if this is right yet
447      * see comment in http_core.c:default_handler
448      */
449     if (ap_strcmp_match(r->handler, "*/*")) {
450         return DECLINED;
451     }
452
453     /* we don't handle anything but GET */
454     if (r->method_number != M_GET) return DECLINED;
455
456     /* did xlat phase find the file? */
457     match = ap_get_module_config(r->request_config, &file_cache_module);
458
459     if (match == NULL) {
460         return DECLINED;
461     }
462
463     /* note that we would handle GET on this resource */
464     r->allowed |= (1 << M_GET);
465
466     /* This handler has no use for a request body (yet), but we still
467      * need to read and discard it if the client sent one.
468      */
469     if ((errstatus = ap_discard_request_body(r)) != OK)
470         return errstatus;
471
472     ap_update_mtime(r, match->finfo.mtime);
473     ap_set_last_modified(r);
474     ap_set_etag(r);
475     if ((errstatus = ap_meets_conditions(r)) != OK) {
476        return errstatus;
477     }
478     ap_set_content_length(r, match->finfo.size);
479
480     ap_send_http_header(r);
481
482     /* Call appropriate handler */
483     if (!r->header_only) {    
484         if (match->is_mmapped == TRUE)
485             rc = mmap_handler(r, match);
486         else
487             rc = sendfile_handler(r, match);
488     }
489
490     return rc;
491 }
492
493 static command_rec file_cache_cmds[] =
494 {
495 AP_INIT_ITERATE("cachefile", cachefile, NULL, RSRC_CONF,
496      "A space separated list of files to add to the file handle cache at config time"),
497 AP_INIT_ITERATE("mmapfile", mmapfile, NULL, RSRC_CONF,
498      "A space separated list of files to mmap at config time"),
499     {NULL}
500 };
501
502 static void register_hooks(apr_pool_t *p)
503 {
504     ap_hook_handler(file_cache_handler, NULL, NULL, AP_HOOK_LAST);
505     ap_hook_post_config(file_cache_post_config, NULL, NULL, AP_HOOK_MIDDLE);
506     ap_hook_translate_name(file_cache_xlat, NULL, NULL, AP_HOOK_MIDDLE);
507     /* This trick doesn't work apparently because the translate hooks
508        are single shot. If the core_hook returns OK, then our hook is 
509        not called.
510     ap_hook_translate_name(file_cache_xlat, aszPre, NULL, AP_HOOK_MIDDLE); 
511     */
512
513 }
514
515 module AP_MODULE_DECLARE_DATA file_cache_module =
516 {
517     STANDARD20_MODULE_STUFF,
518     NULL,                     /* create per-directory config structure */
519     NULL,                     /* merge per-directory config structures */
520     create_server_config,     /* create per-server config structure */
521     NULL,                     /* merge per-server config structures */
522     file_cache_cmds,          /* command handlers */
523     register_hooks            /* register hooks */
524 };