4 #include "stat_cache.h"
18 #ifdef HAVE_ATTR_ATTRIBUTES_H
19 # include <attr/attributes.h>
22 #ifdef HAVE_SYS_EXTATTR_H
23 # include <sys/extattr.h>
33 * we cache the stat() calls in our own storage
34 * the directories are cached in FAM
36 * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
38 * if the stat()-cache is queried we check if the version id for the directory is the
39 * same and return immediatly.
44 * - for each stat-cache entry we need a fast indirect lookup on the directory name
45 * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
47 * stat <<-> directory <-> FAMRequest
49 * if file is deleted, directory is dirty, file is rechecked ...
50 * if directory is deleted, directory mapping is removed
54 /* the directory name is too long to always compare on it
56 * - the hash-key is used as sorting criteria for a tree
57 * - a splay-tree is used as we can use the caching effect of it
60 /* we want to cleanup the stat-cache every few seconds, let's say 10
62 * - remove entries which are outdated since 30s
63 * - remove entries which are fresh but havn't been used since 60s
64 * - if we don't have a stat-cache entry for a directory, release it from the monitor
69 STAT_CACHE_ENGINE_UNSET
,
70 STAT_CACHE_ENGINE_NONE
,
71 STAT_CACHE_ENGINE_SIMPLE
,
76 struct stat_cache_fam
;
79 typedef struct stat_cache
{
80 splay_tree
*files
; /* the nodes of the tree are stat_cache_entry's */
81 buffer
*hash_key
; /* temp-store for the hash-key */
83 struct stat_cache_fam
*scf
;
88 /* the famous DJB hash function for strings */
89 static uint32_t hashme(buffer
*str
) {
92 for (s
= str
->ptr
; *s
; s
++) {
93 hash
= ((hash
<< 5) + hash
) + *s
;
96 hash
&= ~(((uint32_t)1) << 31); /* strip the highest bit */
112 typedef struct stat_cache_fam
{
113 splay_tree
*dirs
; /* the nodes of the tree are fam_dir_entry */
119 fam_dir_entry
*fam_dir
;
120 buffer
*dir_name
; /* for building the dirname from the filename */
121 buffer
*hash_key
; /* temp-store for the hash-key */
124 static fam_dir_entry
* fam_dir_entry_init(void) {
125 fam_dir_entry
*fam_dir
= NULL
;
127 fam_dir
= calloc(1, sizeof(*fam_dir
));
128 force_assert(NULL
!= fam_dir
);
130 fam_dir
->name
= buffer_init();
135 static void fam_dir_entry_free(FAMConnection
*fc
, void *data
) {
136 fam_dir_entry
*fam_dir
= data
;
138 if (!fam_dir
) return;
140 FAMCancelMonitor(fc
, fam_dir
->req
);
142 buffer_free(fam_dir
->name
);
148 static handler_t
stat_cache_handle_fdevent(server
*srv
, void *_fce
, int revent
) {
150 stat_cache_fam
*scf
= srv
->stat_cache
->scf
;
156 if (revent
& FDEVENT_IN
) {
157 events
= FAMPending(&scf
->fam
);
159 for (i
= 0; i
< events
; i
++) {
161 fam_dir_entry
*fam_dir
;
165 FAMNextEvent(&scf
->fam
, &fe
);
173 /* if the filename is a directory remove the entry */
175 fam_dir
= fe
.userdata
;
178 /* file/dir is still here */
179 if (fe
.code
== FAMChanged
) break;
181 /* we have 2 versions, follow and no-follow-symlink */
183 for (j
= 0; j
< 2; j
++) {
184 buffer_copy_string(scf
->hash_key
, fe
.filename
);
185 buffer_append_int(scf
->hash_key
, j
);
187 ndx
= hashme(scf
->hash_key
);
189 scf
->dirs
= splaytree_splay(scf
->dirs
, ndx
);
192 if (node
&& (node
->key
== ndx
)) {
193 int osize
= splaytree_size(scf
->dirs
);
195 fam_dir_entry_free(&scf
->fam
, node
->data
);
196 scf
->dirs
= splaytree_delete(scf
->dirs
, ndx
);
198 force_assert(osize
- 1 == splaytree_size(scf
->dirs
));
208 if (revent
& FDEVENT_HUP
) {
209 /* fam closed the connection */
210 fdevent_event_del(srv
->ev
, &(scf
->fam_fcce_ndx
), FAMCONNECTION_GETFD(&scf
->fam
));
211 fdevent_unregister(srv
->ev
, FAMCONNECTION_GETFD(&scf
->fam
));
216 return HANDLER_GO_ON
;
219 static stat_cache_fam
* stat_cache_init_fam(server
*srv
) {
220 stat_cache_fam
*scf
= calloc(1, sizeof(*scf
));
221 scf
->fam_fcce_ndx
= -1;
222 scf
->dir_name
= buffer_init();
223 scf
->hash_key
= buffer_init();
226 if (0 != FAMOpen2(&scf
->fam
, "lighttpd")) {
227 log_error_write(srv
, __FILE__
, __LINE__
, "s",
228 "could not open a fam connection, dieing.");
231 #ifdef HAVE_FAMNOEXISTS
232 FAMNoExists(&scf
->fam
);
235 fdevent_setfd_cloexec(FAMCONNECTION_GETFD(&scf
->fam
));
236 fdevent_register(srv
->ev
, FAMCONNECTION_GETFD(&scf
->fam
), stat_cache_handle_fdevent
, NULL
);
237 fdevent_event_set(srv
->ev
, &(scf
->fam_fcce_ndx
), FAMCONNECTION_GETFD(&scf
->fam
), FDEVENT_IN
);
242 static void stat_cache_free_fam(stat_cache_fam
*scf
) {
243 if (NULL
== scf
) return;
244 buffer_free(scf
->dir_name
);
245 buffer_free(scf
->hash_key
);
249 splay_tree
*node
= scf
->dirs
;
251 osize
= scf
->dirs
->size
;
253 fam_dir_entry_free(&scf
->fam
, node
->data
);
254 scf
->dirs
= splaytree_delete(scf
->dirs
, node
->key
);
257 force_assert(NULL
== scf
->dirs
);
259 force_assert(osize
== (scf
->dirs
->size
+ 1));
263 if (-1 != scf
->fam_fcce_ndx
) {
264 /* fd events already gone */
265 scf
->fam_fcce_ndx
= -1;
273 static int buffer_copy_dirname(buffer
*dst
, const buffer
*file
) {
276 if (buffer_string_is_empty(file
)) return -1;
278 for (i
= buffer_string_length(file
); i
> 0; i
--) {
279 if (file
->ptr
[i
] == '/') {
280 buffer_copy_string_len(dst
, file
->ptr
, i
);
288 static handler_t
stat_cache_fam_dir_check(server
*srv
, stat_cache_fam
*scf
, stat_cache_entry
*sce
, const buffer
*name
, unsigned int follow_symlink
) {
289 if (0 != buffer_copy_dirname(scf
->dir_name
, name
)) {
290 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
291 "no '/' found in filename:", name
);
292 return HANDLER_ERROR
;
295 buffer_copy_buffer(scf
->hash_key
, scf
->dir_name
);
296 buffer_append_int(scf
->hash_key
, (int)follow_symlink
);
298 scf
->dir_ndx
= hashme(scf
->hash_key
);
300 scf
->dirs
= splaytree_splay(scf
->dirs
, scf
->dir_ndx
);
302 if ((NULL
!= scf
->dirs
) && (scf
->dirs
->key
== scf
->dir_ndx
)) {
303 scf
->fam_dir
= scf
->dirs
->data
;
305 /* check whether we got a collision */
306 if (buffer_is_equal(scf
->dir_name
, scf
->fam_dir
->name
)) {
307 /* test whether a found file cache entry is still ok */
308 if ((NULL
!= sce
) && (scf
->fam_dir
->version
== sce
->dir_version
)) {
309 /* the stat()-cache entry is still ok */
310 return HANDLER_FINISHED
;
313 /* hash collision, forget about the entry */
320 return HANDLER_GO_ON
;
323 static void stat_cache_fam_dir_monitor(server
*srv
, stat_cache_fam
*scf
, stat_cache_entry
*sce
, const buffer
*name
) {
324 /* is this directory already registered ? */
325 fam_dir_entry
*fam_dir
= scf
->fam_dir
;
326 if (NULL
== fam_dir
) {
327 fam_dir
= fam_dir_entry_init();
329 buffer_copy_buffer(fam_dir
->name
, scf
->dir_name
);
331 fam_dir
->version
= 1;
333 fam_dir
->req
= calloc(1, sizeof(FAMRequest
));
334 force_assert(NULL
!= fam_dir
);
336 if (0 != FAMMonitorDirectory(&scf
->fam
, fam_dir
->name
->ptr
,
337 fam_dir
->req
, fam_dir
)) {
339 log_error_write(srv
, __FILE__
, __LINE__
, "sbsbs",
340 "monitoring dir failed:",
343 FamErrlist
[FAMErrno
]);
345 fam_dir_entry_free(&scf
->fam
, fam_dir
);
347 int osize
= splaytree_size(scf
->dirs
);
349 /* already splayed scf->dir_ndx */
350 if ((NULL
!= scf
->dirs
) && (scf
->dirs
->key
== scf
->dir_ndx
)) {
351 /* hash collision: replace old entry */
352 fam_dir_entry_free(&scf
->fam
, scf
->dirs
->data
);
353 scf
->dirs
->data
= fam_dir
;
355 scf
->dirs
= splaytree_insert(scf
->dirs
, scf
->dir_ndx
, fam_dir
);
356 force_assert(osize
== (splaytree_size(scf
->dirs
) - 1));
359 force_assert(scf
->dirs
);
360 force_assert(scf
->dirs
->data
== fam_dir
);
361 scf
->fam_dir
= fam_dir
;
365 /* bind the fam_fc to the stat() cache entry */
368 sce
->dir_version
= fam_dir
->version
;
375 stat_cache
*stat_cache_init(server
*srv
) {
376 stat_cache
*sc
= NULL
;
379 sc
= calloc(1, sizeof(*sc
));
380 force_assert(NULL
!= sc
);
382 sc
->hash_key
= buffer_init();
385 if (STAT_CACHE_ENGINE_FAM
== srv
->srvconf
.stat_cache_engine
) {
386 sc
->scf
= stat_cache_init_fam(srv
);
387 if (NULL
== sc
->scf
) {
397 static stat_cache_entry
* stat_cache_entry_init(void) {
398 stat_cache_entry
*sce
= NULL
;
400 sce
= calloc(1, sizeof(*sce
));
401 force_assert(NULL
!= sce
);
403 sce
->name
= buffer_init();
404 sce
->etag
= buffer_init();
405 sce
->content_type
= buffer_init();
410 static void stat_cache_entry_free(void *data
) {
411 stat_cache_entry
*sce
= data
;
414 buffer_free(sce
->etag
);
415 buffer_free(sce
->name
);
416 buffer_free(sce
->content_type
);
421 void stat_cache_free(stat_cache
*sc
) {
424 splay_tree
*node
= sc
->files
;
426 osize
= sc
->files
->size
;
428 stat_cache_entry_free(node
->data
);
429 sc
->files
= splaytree_delete(sc
->files
, node
->key
);
431 force_assert(osize
- 1 == splaytree_size(sc
->files
));
434 buffer_free(sc
->hash_key
);
437 stat_cache_free_fam(sc
->scf
);
442 int stat_cache_choose_engine (server
*srv
, const buffer
*stat_cache_string
) {
443 if (buffer_string_is_empty(stat_cache_string
)) {
444 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
445 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("simple"))) {
446 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
448 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("fam"))) {
449 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_FAM
;
451 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("disable"))) {
452 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_NONE
;
454 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
455 "server.stat-cache-engine can be one of \"disable\", \"simple\","
459 " but not:", stat_cache_string
);
465 #if defined(HAVE_XATTR)
466 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
470 buffer_string_prepare_copy(buf
, 1023);
471 attrlen
= buf
->size
- 1;
472 if(0 == (ret
= attr_get(name
, xattrname
, buf
->ptr
, &attrlen
, 0))) {
473 buffer_commit(buf
, attrlen
);
477 #elif defined(HAVE_EXTATTR)
478 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
481 buffer_string_prepare_copy(buf
, 1023);
483 if (-1 != (attrlen
= extattr_get_file(name
, EXTATTR_NAMESPACE_USER
, xattrname
, buf
->ptr
, buf
->size
- 1))) {
484 buf
->used
= attrlen
+ 1;
485 buf
->ptr
[attrlen
] = '\0';
492 const buffer
* stat_cache_mimetype_by_ext(const connection
*con
, const char *name
, size_t nlen
)
494 const char *end
= name
+ nlen
; /*(end of string)*/
495 const size_t used
= con
->conf
.mimetypes
->used
;
497 for (size_t i
= 0; i
< used
; ++i
) {
499 const data_string
*ds
= (data_string
*)con
->conf
.mimetypes
->data
[i
];
500 const size_t klen
= buffer_string_length(ds
->key
);
501 if (klen
<= nlen
&& 0 == strncasecmp(end
-klen
, ds
->key
->ptr
, klen
))
507 const data_string
*ds
;
509 for (s
= end
-1; s
!= name
&& *s
!= '/'; --s
) ; /*(like memrchr())*/
515 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
516 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
517 if (NULL
!= ds
) return ds
->value
;
519 while (*s
!= '.' && ++s
!= end
) ;
521 /* search ".ext" then "ext" */
522 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
523 if (NULL
!= ds
) return ds
->value
;
524 /* repeat search without leading '.' to handle situation where
525 * admin configured mimetype.assign keys without leading '.' */
527 if (*s
== '.') { --s
; continue; }
528 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
529 if (NULL
!= ds
) return ds
->value
;
532 /* search for ""; catchall */
533 ds
= (data_string
*)array_get_element(con
->conf
.mimetypes
, "");
534 if (NULL
!= ds
) return ds
->value
;
541 static int stat_cache_lstat(server
*srv
, buffer
*dname
, struct stat
*lst
) {
542 if (lstat(dname
->ptr
, lst
) == 0) {
543 return S_ISLNK(lst
->st_mode
) ? 0 : 1;
546 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
548 dname
, strerror(errno
));
559 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
560 * - HANDLER_ERROR on stat() failed -> see errno for problem
563 handler_t
stat_cache_get_entry(server
*srv
, connection
*con
, buffer
*name
, stat_cache_entry
**ret_sce
) {
564 stat_cache_entry
*sce
= NULL
;
574 * check if the directory for this file has changed
577 sc
= srv
->stat_cache
;
579 buffer_copy_buffer(sc
->hash_key
, name
);
580 buffer_append_int(sc
->hash_key
, con
->conf
.follow_symlink
);
582 file_ndx
= hashme(sc
->hash_key
);
583 sc
->files
= splaytree_splay(sc
->files
, file_ndx
);
585 if (sc
->files
&& (sc
->files
->key
== file_ndx
)) {
586 /* we have seen this file already and
587 * don't stat() it again in the same second */
589 sce
= sc
->files
->data
;
591 /* check if the name is the same, we might have a collision */
593 if (buffer_is_equal(name
, sce
->name
)) {
594 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_SIMPLE
) {
595 if (sce
->stat_ts
== srv
->cur_ts
&& con
->conf
.follow_symlink
) {
597 return HANDLER_GO_ON
;
601 /* collision, forget about the entry */
608 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
609 switch (stat_cache_fam_dir_check(srv
, sc
->scf
, sce
, name
, con
->conf
.follow_symlink
)) {
612 case HANDLER_FINISHED
:
614 return HANDLER_GO_ON
;
617 return HANDLER_ERROR
;
624 * - open() + fstat() on a named-pipe results in a (intended) hang.
625 * - stat() if regular file + open() to see if we can read from it is better
628 if (-1 == stat(name
->ptr
, &st
)) {
629 return HANDLER_ERROR
;
633 if (S_ISREG(st
.st_mode
)) {
634 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
635 if (name
->ptr
[buffer_string_length(name
) - 1] == '/') {
637 return HANDLER_ERROR
;
640 /* try to open the file to check if we can read it */
641 if (-1 == (fd
= open(name
->ptr
, O_RDONLY
))) {
642 return HANDLER_ERROR
;
649 sce
= stat_cache_entry_init();
650 buffer_copy_buffer(sce
->name
, name
);
652 /* already splayed file_ndx */
653 if ((NULL
!= sc
->files
) && (sc
->files
->key
== file_ndx
)) {
654 /* hash collision: replace old entry */
655 stat_cache_entry_free(sc
->files
->data
);
656 sc
->files
->data
= sce
;
658 int osize
= splaytree_size(sc
->files
);
660 sc
->files
= splaytree_insert(sc
->files
, file_ndx
, sce
);
661 force_assert(osize
+ 1 == splaytree_size(sc
->files
));
663 force_assert(sc
->files
);
664 force_assert(sc
->files
->data
== sce
);
668 sce
->stat_ts
= srv
->cur_ts
;
670 /* catch the obvious symlinks
672 * this is not a secure check as we still have a race-condition between
673 * the stat() and the open. We can only solve this by
677 * and keeping the file open for the rest of the time. But this can
678 * only be done at network level.
680 * per default it is not a symlink
685 /* we want to only check for symlinks if we should block symlinks.
687 if (!con
->conf
.follow_symlink
) {
688 if (stat_cache_lstat(srv
, name
, &lst
) == 0) {
693 * we assume "/" can not be symlink, so
694 * skip the symlink stuff if our path is /
696 else if (buffer_string_length(name
) > 1) {
700 dname
= buffer_init();
701 buffer_copy_buffer(dname
, name
);
703 while ((s_cur
= strrchr(dname
->ptr
, '/'))) {
704 buffer_string_set_length(dname
, s_cur
- dname
->ptr
);
705 if (dname
->ptr
== s_cur
) {
708 if (stat_cache_lstat(srv
, dname
, &lst
) == 0) {
718 if (S_ISREG(st
.st_mode
)) {
719 /* determine mimetype */
720 buffer_reset(sce
->content_type
);
721 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
722 if (con
->conf
.use_xattr
) {
723 stat_cache_attr_get(sce
->content_type
, name
->ptr
, srv
->srvconf
.xattr_name
->ptr
);
726 /* xattr did not set a content-type. ask the config */
727 if (buffer_string_is_empty(sce
->content_type
)) {
728 const buffer
*type
= stat_cache_mimetype_by_ext(con
, CONST_BUF_LEN(name
));
730 buffer_copy_buffer(sce
->content_type
, type
);
733 etag_create(sce
->etag
, &(sce
->st
), con
->etag_flags
);
734 } else if (S_ISDIR(st
.st_mode
)) {
735 etag_create(sce
->etag
, &(sce
->st
), con
->etag_flags
);
739 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
740 stat_cache_fam_dir_monitor(srv
, sc
->scf
, sce
, name
);
746 return HANDLER_GO_ON
;
749 int stat_cache_open_rdonly_fstat (server
*srv
, connection
*con
, buffer
*name
, struct stat
*st
) {
750 /*(Note: O_NOFOLLOW affects only the final path segment, the target file,
751 * not any intermediate symlinks along the path)*/
756 #define O_LARGEFILE 0
767 const int oflags
= O_BINARY
| O_LARGEFILE
| O_NOCTTY
| O_NONBLOCK
768 | (con
->conf
.follow_symlink
? 0 : O_NOFOLLOW
);
769 const int fd
= open(name
->ptr
, O_RDONLY
| oflags
);
771 if (0 == fstat(fd
, st
)) {
777 UNUSED(srv
); /*(might log_error_write(srv, ...) in the future)*/
782 * remove stat() from cache which havn't been stat()ed for
783 * more than 10 seconds
786 * walk though the stat-cache, collect the ids which are too old
787 * and remove them in a second loop
790 static int stat_cache_tag_old_entries(server
*srv
, splay_tree
*t
, int *keys
, size_t *ndx
) {
791 stat_cache_entry
*sce
;
795 stat_cache_tag_old_entries(srv
, t
->left
, keys
, ndx
);
796 stat_cache_tag_old_entries(srv
, t
->right
, keys
, ndx
);
800 if (srv
->cur_ts
- sce
->stat_ts
> 2) {
801 keys
[(*ndx
)++] = t
->key
;
807 int stat_cache_trigger_cleanup(server
*srv
) {
809 size_t max_ndx
= 0, i
;
812 sc
= srv
->stat_cache
;
814 if (!sc
->files
) return 0;
816 keys
= calloc(1, sizeof(int) * sc
->files
->size
);
817 force_assert(NULL
!= keys
);
819 stat_cache_tag_old_entries(srv
, sc
->files
, keys
, &max_ndx
);
821 for (i
= 0; i
< max_ndx
; i
++) {
825 sc
->files
= splaytree_splay(sc
->files
, ndx
);
829 if (node
&& (node
->key
== ndx
)) {
830 stat_cache_entry_free(node
->data
);
831 sc
->files
= splaytree_delete(sc
->files
, ndx
);