]> granicus.if.org Git - apache/blob - modules/cache/mod_file_cache.c
Add lots of unique tags to error log messages
[apache] / modules / cache / mod_file_cache.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Author: mod_file_cache by Bill Stoddard <stoddard apache.org>
19  *         Based on mod_mmap_static by Dean Gaudet <dgaudet arctic.org>
20  *
21  * v0.01: initial implementation
22  */
23
24 /*
25     Documentation:
26
27     Some sites have a set of static files that are really busy, and
28     change infrequently (or even on a regular schedule). Save time
29     by caching open handles to these files. This module, unlike
30     mod_mmap_static, caches open file handles, not file content.
31     On systems (like Windows) with heavy system call overhead and
32     that have an efficient sendfile implementation, caching file handles
33     offers several advantages over caching content. First, the file system
34     can manage the memory, allowing infrequently hit cached files to
35     be paged out. Second, since caching open handles does not consume
36     significant resources, it will be possible to enable an AutoLoadCache
37     feature where static files are dynamically loaded in the cache
38     as the server runs. On systems that have file change notification,
39     this module can be enhanced to automatically garbage collect
40     cached files that change on disk.
41
42     This module should work on Unix systems that have sendfile. Place
43     cachefile directives into your configuration to direct files to
44     be cached.
45
46         cachefile /path/to/file1
47         cachefile /path/to/file2
48         ...
49
50     These files are only cached when the server is restarted, so if you
51     change the list, or if the files are changed, then you'll need to
52     restart the server.
53
54     To reiterate that point:  if the files are modified *in place*
55     without restarting the server you may end up serving requests that
56     are completely bogus.  You should update files by unlinking the old
57     copy and putting a new copy in place.
58
59     There's no such thing as inheriting these files across vhosts or
60     whatever... place the directives in the main server only.
61
62     Known problems:
63
64     Don't use Alias or RewriteRule to move these files around...  unless
65     you feel like paying for an extra stat() on each request.  This is
66     a deficiency in the Apache API that will hopefully be solved some day.
67     The file will be served out of the file handle cache, but there will be
68     an extra stat() that's a waste.
69 */
70
71 #include "apr.h"
72
73 #if !(APR_HAS_SENDFILE || APR_HAS_MMAP)
74 #error mod_file_cache only works on systems with APR_HAS_SENDFILE or APR_HAS_MMAP
75 #endif
76
77 #include "apr_mmap.h"
78 #include "apr_strings.h"
79 #include "apr_hash.h"
80 #include "apr_buckets.h"
81
82 #define APR_WANT_STRFUNC
83 #include "apr_want.h"
84
85 #if APR_HAVE_SYS_TYPES_H
86 #include <sys/types.h>
87 #endif
88
89 #include "httpd.h"
90 #include "http_config.h"
91 #include "http_log.h"
92 #include "http_protocol.h"
93 #include "http_request.h"
94 #include "http_core.h"
95
96 module AP_MODULE_DECLARE_DATA file_cache_module;
97
98 typedef struct {
99 #if APR_HAS_SENDFILE
100     apr_file_t *file;
101 #endif
102     const char *filename;
103     apr_finfo_t finfo;
104     int is_mmapped;
105 #if APR_HAS_MMAP
106     apr_mmap_t *mm;
107 #endif
108     char mtimestr[APR_RFC822_DATE_LEN];
109     char sizestr[21];   /* big enough to hold any 64-bit file size + null */
110 } a_file;
111
112 typedef struct {
113     apr_hash_t *fileht;
114 } a_server_config;
115
116
117 static void *create_server_config(apr_pool_t *p, server_rec *s)
118 {
119     a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
120
121     sconf->fileht = apr_hash_make(p);
122     return sconf;
123 }
124
125 static void cache_the_file(cmd_parms *cmd, const char *filename, int mmap)
126 {
127     a_server_config *sconf;
128     a_file *new_file;
129     a_file tmp;
130     apr_file_t *fd = NULL;
131     apr_status_t rc;
132     const char *fspec;
133
134     fspec = ap_server_root_relative(cmd->pool, filename);
135     if (!fspec) {
136         ap_log_error(APLOG_MARK, APLOG_WARNING, APR_EBADPATH, cmd->server, APLOGNO(00794)
137                      "invalid file path "
138                      "%s, skipping", filename);
139         return;
140     }
141     if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
142                                  cmd->temp_pool)) != APR_SUCCESS) {
143         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00795)
144                      "unable to stat(%s), skipping", fspec);
145         return;
146     }
147     if (tmp.finfo.filetype != APR_REG) {
148         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00796)
149                      "%s isn't a regular file, skipping", fspec);
150         return;
151     }
152     if (tmp.finfo.size > AP_MAX_SENDFILE) {
153         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00797)
154                      "%s is too large to cache, skipping", fspec);
155         return;
156     }
157
158     rc = apr_file_open(&fd, fspec, APR_READ | APR_BINARY | APR_XTHREAD,
159                        APR_OS_DEFAULT, cmd->pool);
160     if (rc != APR_SUCCESS) {
161         ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00798)
162                      "unable to open(%s, O_RDONLY), skipping", fspec);
163         return;
164     }
165     apr_file_inherit_set(fd);
166
167     /* WooHoo, we have a file to put in the cache */
168     new_file = apr_pcalloc(cmd->pool, sizeof(a_file));
169     new_file->finfo = tmp.finfo;
170
171 #if APR_HAS_MMAP
172     if (mmap) {
173         /* MMAPFile directive. MMAP'ing the file
174          * XXX: APR_HAS_LARGE_FILES issue; need to reject this request if
175          * size is greater than MAX(apr_size_t) (perhaps greater than 1M?).
176          */
177         if ((rc = apr_mmap_create(&new_file->mm, fd, 0,
178                                   (apr_size_t)new_file->finfo.size,
179                                   APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
180             apr_file_close(fd);
181             ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server, APLOGNO(00799)
182                          "unable to mmap %s, skipping", filename);
183             return;
184         }
185         apr_file_close(fd);
186         new_file->is_mmapped = TRUE;
187     }
188 #endif
189 #if APR_HAS_SENDFILE
190     if (!mmap) {
191         /* CacheFile directive. Caching the file handle */
192         new_file->is_mmapped = FALSE;
193         new_file->file = fd;
194     }
195 #endif
196
197     new_file->filename = fspec;
198     apr_rfc822_date(new_file->mtimestr, new_file->finfo.mtime);
199     apr_snprintf(new_file->sizestr, sizeof new_file->sizestr, "%" APR_OFF_T_FMT, new_file->finfo.size);
200
201     sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
202     apr_hash_set(sconf->fileht, new_file->filename, strlen(new_file->filename), new_file);
203
204 }
205
206 static const char *cachefilehandle(cmd_parms *cmd, void *dummy, const char *filename)
207 {
208 #if APR_HAS_SENDFILE
209     cache_the_file(cmd, filename, 0);
210 #else
211     /* Sendfile not supported by this OS */
212     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00800)
213                  "unable to cache file: %s. Sendfile is not supported on this OS", filename);
214 #endif
215     return NULL;
216 }
217 static const char *cachefilemmap(cmd_parms *cmd, void *dummy, const char *filename)
218 {
219 #if APR_HAS_MMAP
220     cache_the_file(cmd, filename, 1);
221 #else
222     /* MMAP not supported by this OS */
223     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00801)
224                  "unable to cache file: %s. MMAP is not supported by this OS", filename);
225 #endif
226     return NULL;
227 }
228
229 static int file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
230                                    apr_pool_t *ptemp, server_rec *s)
231 {
232     /* Hummm, anything to do here? */
233     return OK;
234 }
235
236 /* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
237  * bit of a kludge, because we really want to run after core_translate runs.
238  */
239 static int file_cache_xlat(request_rec *r)
240 {
241     a_server_config *sconf;
242     a_file *match;
243     int res;
244
245     sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
246
247     /* we only operate when at least one cachefile directive was used */
248     if (!apr_hash_count(sconf->fileht)) {
249         return DECLINED;
250     }
251
252     res = ap_core_translate(r);
253     if (res != OK || !r->filename) {
254         return res;
255     }
256
257     /* search the cache */
258     match = (a_file *) apr_hash_get(sconf->fileht, r->filename, APR_HASH_KEY_STRING);
259     if (match == NULL)
260         return DECLINED;
261
262     /* pass search results to handler */
263     ap_set_module_config(r->request_config, &file_cache_module, match);
264
265     /* shortcircuit the get_path_info() stat() calls and stuff */
266     r->finfo = match->finfo;
267     return OK;
268 }
269
270 static int mmap_handler(request_rec *r, a_file *file)
271 {
272 #if APR_HAS_MMAP
273     conn_rec *c = r->connection;
274     apr_bucket *b;
275     apr_mmap_t *mm;
276     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
277
278     apr_mmap_dup(&mm, file->mm, r->pool);
279     b = apr_bucket_mmap_create(mm, 0, (apr_size_t)file->finfo.size,
280                                c->bucket_alloc);
281     APR_BRIGADE_INSERT_TAIL(bb, b);
282     b = apr_bucket_eos_create(c->bucket_alloc);
283     APR_BRIGADE_INSERT_TAIL(bb, b);
284
285     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
286         return HTTP_INTERNAL_SERVER_ERROR;
287 #endif
288     return OK;
289 }
290
291 static int sendfile_handler(request_rec *r, a_file *file)
292 {
293 #if APR_HAS_SENDFILE
294     conn_rec *c = r->connection;
295     apr_bucket *b;
296     apr_bucket_brigade *bb = apr_brigade_create(r->pool, c->bucket_alloc);
297
298     apr_brigade_insert_file(bb, file->file, 0, file->finfo.size, r->pool);
299
300     b = apr_bucket_eos_create(c->bucket_alloc);
301     APR_BRIGADE_INSERT_TAIL(bb, b);
302
303     if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
304         return HTTP_INTERNAL_SERVER_ERROR;
305 #endif
306     return OK;
307 }
308
309 static int file_cache_handler(request_rec *r)
310 {
311     a_file *match;
312     int errstatus;
313     int rc = OK;
314
315     /* XXX: not sure if this is right yet
316      * see comment in http_core.c:default_handler
317      */
318     if (ap_strcmp_match(r->handler, "*/*")) {
319         return DECLINED;
320     }
321
322     /* we don't handle anything but GET */
323     if (r->method_number != M_GET) return DECLINED;
324
325     /* did xlat phase find the file? */
326     match = ap_get_module_config(r->request_config, &file_cache_module);
327
328     if (match == NULL) {
329         return DECLINED;
330     }
331
332     /* note that we would handle GET on this resource */
333     r->allowed |= (AP_METHOD_BIT << M_GET);
334
335     /* This handler has no use for a request body (yet), but we still
336      * need to read and discard it if the client sent one.
337      */
338     if ((errstatus = ap_discard_request_body(r)) != OK)
339         return errstatus;
340
341     ap_update_mtime(r, match->finfo.mtime);
342
343     /* ap_set_last_modified() always converts the file mtime to a string
344      * which is slow.  Accelerate the common case.
345      * ap_set_last_modified(r);
346      */
347     {
348         apr_time_t mod_time;
349         char *datestr;
350
351         mod_time = ap_rationalize_mtime(r, r->mtime);
352         if (mod_time == match->finfo.mtime)
353             datestr = match->mtimestr;
354         else {
355             datestr = apr_palloc(r->pool, APR_RFC822_DATE_LEN);
356             apr_rfc822_date(datestr, mod_time);
357         }
358         apr_table_setn(r->headers_out, "Last-Modified", datestr);
359     }
360
361     /* ap_set_content_length() always converts the same number and never
362      * returns an error.  Accelerate it.
363      */
364     r->clength = match->finfo.size;
365     apr_table_setn(r->headers_out, "Content-Length", match->sizestr);
366
367     ap_set_etag(r);
368     if ((errstatus = ap_meets_conditions(r)) != OK) {
369        return errstatus;
370     }
371
372     /* Call appropriate handler */
373     if (!r->header_only) {
374         if (match->is_mmapped == TRUE)
375             rc = mmap_handler(r, match);
376         else
377             rc = sendfile_handler(r, match);
378     }
379
380     return rc;
381 }
382
383 static command_rec file_cache_cmds[] =
384 {
385 AP_INIT_ITERATE("cachefile", cachefilehandle, NULL, RSRC_CONF,
386      "A space separated list of files to add to the file handle cache at config time"),
387 AP_INIT_ITERATE("mmapfile", cachefilemmap, NULL, RSRC_CONF,
388      "A space separated list of files to mmap at config time"),
389     {NULL}
390 };
391
392 static void register_hooks(apr_pool_t *p)
393 {
394     ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
395     ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
396     ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
397     /* This trick doesn't work apparently because the translate hooks
398        are single shot. If the core_hook returns OK, then our hook is
399        not called.
400     ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
401     */
402
403 }
404
405 AP_DECLARE_MODULE(file_cache) =
406 {
407     STANDARD20_MODULE_STUFF,
408     NULL,                     /* create per-directory config structure */
409     NULL,                     /* merge per-directory config structures */
410     create_server_config,     /* create per-server config structure */
411     NULL,                     /* merge per-server config structures */
412     file_cache_cmds,          /* command handlers */
413     register_hooks            /* register hooks */
414 };