3 #include "stat_cache.h"
10 #include <sys/types.h>
19 #ifdef HAVE_ATTR_ATTRIBUTES_H
20 # include <attr/attributes.h>
23 #ifdef HAVE_SYS_EXTATTR_H
24 # include <sys/extattr.h>
34 * we cache the stat() calls in our own storage
35 * the directories are cached in FAM
37 * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
39 * if the stat()-cache is queried we check if the version id for the directory is the
40 * same and return immediatly.
45 * - for each stat-cache entry we need a fast indirect lookup on the directory name
46 * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
48 * stat <<-> directory <-> FAMRequest
50 * if file is deleted, directory is dirty, file is rechecked ...
51 * if directory is deleted, directory mapping is removed
55 /* the directory name is too long to always compare on it
57 * - the hash-key is used as sorting criteria for a tree
58 * - a splay-tree is used as we can use the caching effect of it
61 /* we want to cleanup the stat-cache every few seconds, let's say 10
63 * - remove entries which are outdated since 30s
64 * - remove entries which are fresh but havn't been used since 60s
65 * - if we don't have a stat-cache entry for a directory, release it from the monitor
70 STAT_CACHE_ENGINE_UNSET
,
71 STAT_CACHE_ENGINE_NONE
,
72 STAT_CACHE_ENGINE_SIMPLE
,
77 struct stat_cache_fam
;
80 typedef struct stat_cache
{
81 splay_tree
*files
; /* the nodes of the tree are stat_cache_entry's */
83 struct stat_cache_fam
*scf
;
88 /* the famous DJB hash function for strings */
89 static uint32_t hashme(const char *str
, int z
) {
91 for (const unsigned char *s
= (const unsigned char *)str
; *s
; ++s
) {
92 hash
= ((hash
<< 5) + hash
) ^ *s
;
97 /* (differentiate hash values with and w/o con->conf.follow_symlink) */
98 hash
= ((hash
<< 5) + hash
) ^ (z
? '1' : '0');
100 hash
&= ~(((uint32_t)1) << 31); /* strip the highest bit */
116 typedef struct stat_cache_fam
{
117 splay_tree
*dirs
; /* the nodes of the tree are fam_dir_entry */
122 fam_dir_entry
*fam_dir
;
123 buffer
*dir_name
; /* for building the dirname from the filename */
128 static fam_dir_entry
* fam_dir_entry_init(void) {
129 fam_dir_entry
*fam_dir
= NULL
;
131 fam_dir
= calloc(1, sizeof(*fam_dir
));
132 force_assert(NULL
!= fam_dir
);
134 fam_dir
->name
= buffer_init();
139 static void fam_dir_entry_free(FAMConnection
*fc
, void *data
) {
140 fam_dir_entry
*fam_dir
= data
;
142 if (!fam_dir
) return;
144 FAMCancelMonitor(fc
, fam_dir
->req
);
146 buffer_free(fam_dir
->name
);
152 static handler_t
stat_cache_handle_fdevent(server
*srv
, void *_fce
, int revent
) {
154 stat_cache_fam
*scf
= srv
->stat_cache
->scf
;
160 if (revent
& FDEVENT_IN
) {
161 events
= FAMPending(&scf
->fam
);
163 for (i
= 0; i
< events
; i
++) {
165 fam_dir_entry
*fam_dir
;
169 FAMNextEvent(&scf
->fam
, &fe
);
177 /* if the filename is a directory remove the entry */
179 fam_dir
= fe
.userdata
;
182 /* file/dir is still here */
183 if (fe
.code
== FAMChanged
) break;
185 /* we have 2 versions, follow and no-follow-symlink */
187 for (j
= 0; j
< 2; j
++) {
189 ndx
= hashme(fe
.filename
, j
);
191 scf
->dirs
= splaytree_splay(scf
->dirs
, ndx
);
194 if (node
&& (node
->key
== ndx
)) {
195 int osize
= splaytree_size(scf
->dirs
);
197 fam_dir_entry_free(&scf
->fam
, node
->data
);
198 scf
->dirs
= splaytree_delete(scf
->dirs
, ndx
);
200 force_assert(osize
- 1 == splaytree_size(scf
->dirs
));
210 if (revent
& (FDEVENT_HUP
|FDEVENT_RDHUP
)) {
211 /* fam closed the connection */
212 fdevent_fdnode_event_del(srv
->ev
, scf
->fdn
);
213 fdevent_unregister(srv
->ev
, scf
->fd
);
220 return HANDLER_GO_ON
;
223 static stat_cache_fam
* stat_cache_init_fam(server
*srv
) {
224 stat_cache_fam
*scf
= calloc(1, sizeof(*scf
));
225 scf
->dir_name
= buffer_init();
229 if (0 != FAMOpen2(&scf
->fam
, "lighttpd")) {
230 log_error_write(srv
, __FILE__
, __LINE__
, "s",
231 "could not open a fam connection, dieing.");
234 #ifdef HAVE_FAMNOEXISTS
235 FAMNoExists(&scf
->fam
);
238 scf
->fd
= FAMCONNECTION_GETFD(&scf
->fam
);
239 fdevent_setfd_cloexec(scf
->fd
);
240 scf
->fdn
= fdevent_register(srv
->ev
, scf
->fd
, stat_cache_handle_fdevent
, NULL
);
241 fdevent_fdnode_event_set(srv
->ev
, scf
->fdn
, FDEVENT_IN
| FDEVENT_RDHUP
);
246 static void stat_cache_free_fam(stat_cache_fam
*scf
) {
247 if (NULL
== scf
) return;
248 buffer_free(scf
->dir_name
);
252 splay_tree
*node
= scf
->dirs
;
254 osize
= scf
->dirs
->size
;
256 fam_dir_entry_free(&scf
->fam
, node
->data
);
257 scf
->dirs
= splaytree_delete(scf
->dirs
, node
->key
);
260 force_assert(NULL
== scf
->dirs
);
262 force_assert(osize
== (scf
->dirs
->size
+ 1));
267 /*scf->fdn already cleaned up in fdevent_free()*/
275 static int buffer_copy_dirname(buffer
*dst
, const buffer
*file
) {
278 if (buffer_string_is_empty(file
)) return -1;
280 for (i
= buffer_string_length(file
); i
> 0; i
--) {
281 if (file
->ptr
[i
] == '/') {
282 buffer_copy_string_len(dst
, file
->ptr
, i
);
290 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
) {
291 if (0 != buffer_copy_dirname(scf
->dir_name
, name
)) {
292 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
293 "no '/' found in filename:", name
);
294 return HANDLER_ERROR
;
297 scf
->dir_ndx
= hashme(scf
->dir_name
->ptr
, follow_symlink
);
299 scf
->dirs
= splaytree_splay(scf
->dirs
, scf
->dir_ndx
);
301 if ((NULL
!= scf
->dirs
) && (scf
->dirs
->key
== scf
->dir_ndx
)) {
302 scf
->fam_dir
= scf
->dirs
->data
;
304 /* check whether we got a collision */
305 if (buffer_is_equal(scf
->dir_name
, scf
->fam_dir
->name
)) {
306 /* test whether a found file cache entry is still ok */
307 if ((NULL
!= sce
) && (scf
->fam_dir
->version
== sce
->dir_version
)) {
308 /* the stat()-cache entry is still ok */
309 return HANDLER_FINISHED
;
312 /* hash collision, forget about the entry */
319 return HANDLER_GO_ON
;
322 static void stat_cache_fam_dir_monitor(server
*srv
, stat_cache_fam
*scf
, stat_cache_entry
*sce
, const buffer
*name
) {
323 /* is this directory already registered ? */
324 fam_dir_entry
*fam_dir
= scf
->fam_dir
;
325 if (NULL
== fam_dir
) {
326 fam_dir
= fam_dir_entry_init();
328 buffer_copy_buffer(fam_dir
->name
, scf
->dir_name
);
330 fam_dir
->version
= 1;
332 fam_dir
->req
= calloc(1, sizeof(FAMRequest
));
333 force_assert(NULL
!= fam_dir
);
335 if (0 != FAMMonitorDirectory(&scf
->fam
, fam_dir
->name
->ptr
,
336 fam_dir
->req
, fam_dir
)) {
338 log_error_write(srv
, __FILE__
, __LINE__
, "sbsbs",
339 "monitoring dir failed:",
342 FamErrlist
[FAMErrno
]);
344 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
);
383 if (STAT_CACHE_ENGINE_FAM
== srv
->srvconf
.stat_cache_engine
) {
384 sc
->scf
= stat_cache_init_fam(srv
);
385 if (NULL
== sc
->scf
) {
395 static stat_cache_entry
* stat_cache_entry_init(void) {
396 stat_cache_entry
*sce
= NULL
;
398 sce
= calloc(1, sizeof(*sce
));
399 force_assert(NULL
!= sce
);
401 sce
->name
= buffer_init();
402 sce
->etag
= buffer_init();
403 sce
->content_type
= buffer_init();
408 static void stat_cache_entry_free(void *data
) {
409 stat_cache_entry
*sce
= data
;
412 buffer_free(sce
->etag
);
413 buffer_free(sce
->name
);
414 buffer_free(sce
->content_type
);
419 void stat_cache_free(stat_cache
*sc
) {
422 splay_tree
*node
= sc
->files
;
424 osize
= sc
->files
->size
;
426 stat_cache_entry_free(node
->data
);
427 sc
->files
= splaytree_delete(sc
->files
, node
->key
);
429 force_assert(osize
- 1 == splaytree_size(sc
->files
));
433 stat_cache_free_fam(sc
->scf
);
438 int stat_cache_choose_engine (server
*srv
, const buffer
*stat_cache_string
) {
439 if (buffer_string_is_empty(stat_cache_string
)) {
440 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
441 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("simple"))) {
442 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
444 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("fam"))) {
445 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_FAM
;
447 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("disable"))) {
448 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_NONE
;
450 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
451 "server.stat-cache-engine can be one of \"disable\", \"simple\","
455 " but not:", stat_cache_string
);
461 #if defined(HAVE_XATTR)
462 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
466 buffer_string_prepare_copy(buf
, 1023);
467 attrlen
= buf
->size
- 1;
468 if(0 == (ret
= attr_get(name
, xattrname
, buf
->ptr
, &attrlen
, 0))) {
469 buffer_commit(buf
, attrlen
);
473 #elif defined(HAVE_EXTATTR)
474 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
477 buffer_string_prepare_copy(buf
, 1023);
479 if (-1 != (attrlen
= extattr_get_file(name
, EXTATTR_NAMESPACE_USER
, xattrname
, buf
->ptr
, buf
->size
- 1))) {
480 buf
->used
= attrlen
+ 1;
481 buf
->ptr
[attrlen
] = '\0';
488 const buffer
* stat_cache_mimetype_by_ext(const connection
*con
, const char *name
, size_t nlen
)
490 const char *end
= name
+ nlen
; /*(end of string)*/
491 const size_t used
= con
->conf
.mimetypes
->used
;
493 for (size_t i
= 0; i
< used
; ++i
) {
495 const data_string
*ds
= (data_string
*)con
->conf
.mimetypes
->data
[i
];
496 const size_t klen
= buffer_string_length(ds
->key
);
497 if (klen
<= nlen
&& 0 == strncasecmp(end
-klen
, ds
->key
->ptr
, klen
))
503 const data_string
*ds
;
505 for (s
= end
-1; s
!= name
&& *s
!= '/'; --s
) ; /*(like memrchr())*/
511 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
512 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
513 if (NULL
!= ds
) return ds
->value
;
515 while (*s
!= '.' && ++s
!= end
) ;
517 /* search ".ext" then "ext" */
518 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
519 if (NULL
!= ds
) return ds
->value
;
520 /* repeat search without leading '.' to handle situation where
521 * admin configured mimetype.assign keys without leading '.' */
523 if (*s
== '.') { --s
; continue; }
524 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
525 if (NULL
!= ds
) return ds
->value
;
528 /* search for ""; catchall */
529 ds
= (data_string
*)array_get_element(con
->conf
.mimetypes
, "");
530 if (NULL
!= ds
) return ds
->value
;
536 const buffer
* stat_cache_content_type_get(server
*srv
, connection
*con
, const buffer
*name
, stat_cache_entry
*sce
)
538 /*(invalid caching if user config has multiple, different
539 * con->conf.mimetypes for same extension (not expected))*/
540 if (!buffer_string_is_empty(sce
->content_type
)) return sce
->content_type
;
542 if (S_ISREG(sce
->st
.st_mode
)) {
543 /* determine mimetype */
544 buffer_clear(sce
->content_type
);
545 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
546 if (con
->conf
.use_xattr
) {
547 stat_cache_attr_get(sce
->content_type
, name
->ptr
, srv
->srvconf
.xattr_name
->ptr
);
552 /* xattr did not set a content-type. ask the config */
553 if (buffer_string_is_empty(sce
->content_type
)) {
554 const buffer
*type
= stat_cache_mimetype_by_ext(con
, CONST_BUF_LEN(name
));
556 buffer_copy_buffer(sce
->content_type
, type
);
559 return sce
->content_type
;
565 const buffer
* stat_cache_etag_get(stat_cache_entry
*sce
, etag_flags_t flags
) {
566 /*(invalid caching if user config has multiple, different con->etag_flags
567 * for same path (not expected, since etag flags should be by filesystem))*/
568 if (!buffer_string_is_empty(sce
->etag
)) return sce
->etag
;
570 if (S_ISREG(sce
->st
.st_mode
) || S_ISDIR(sce
->st
.st_mode
)) {
571 etag_create(sce
->etag
, &sce
->st
, flags
);
579 static int stat_cache_lstat(server
*srv
, buffer
*dname
, struct stat
*lst
) {
580 if (lstat(dname
->ptr
, lst
) == 0) {
581 return S_ISLNK(lst
->st_mode
) ? 0 : 1;
584 log_error_write(srv
, __FILE__
, __LINE__
, "sbs",
586 dname
, strerror(errno
));
597 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
598 * - HANDLER_ERROR on stat() failed -> see errno for problem
601 handler_t
stat_cache_get_entry(server
*srv
, connection
*con
, buffer
*name
, stat_cache_entry
**ret_sce
) {
602 stat_cache_entry
*sce
= NULL
;
606 const int follow_symlink
= con
->conf
.follow_symlink
;
613 * check if the directory for this file has changed
616 sc
= srv
->stat_cache
;
618 file_ndx
= hashme(name
->ptr
, follow_symlink
);
619 sc
->files
= splaytree_splay(sc
->files
, file_ndx
);
621 if (sc
->files
&& (sc
->files
->key
== file_ndx
)) {
622 /* we have seen this file already and
623 * don't stat() it again in the same second */
625 sce
= sc
->files
->data
;
627 /* check if the name is the same, we might have a collision */
629 if (buffer_is_equal(name
, sce
->name
)) {
630 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_SIMPLE
) {
631 if (sce
->stat_ts
== srv
->cur_ts
&& follow_symlink
) {
633 return HANDLER_GO_ON
;
637 /* collision, forget about the entry */
644 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
645 switch (stat_cache_fam_dir_check(srv
, sc
->scf
, sce
, name
, follow_symlink
)) {
648 case HANDLER_FINISHED
:
650 return HANDLER_GO_ON
;
653 return HANDLER_ERROR
;
660 * - open() + fstat() on a named-pipe results in a (intended) hang.
661 * - stat() if regular file + open() to see if we can read from it is better
664 if (-1 == stat(name
->ptr
, &st
)) {
665 return HANDLER_ERROR
;
669 if (S_ISREG(st
.st_mode
)) {
670 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
671 if (name
->ptr
[buffer_string_length(name
) - 1] == '/') {
673 return HANDLER_ERROR
;
676 /* try to open the file to check if we can read it */
677 if (-1 == (fd
= open(name
->ptr
, O_RDONLY
))) {
678 return HANDLER_ERROR
;
685 sce
= stat_cache_entry_init();
686 buffer_copy_buffer(sce
->name
, name
);
688 /* already splayed file_ndx */
689 if ((NULL
!= sc
->files
) && (sc
->files
->key
== file_ndx
)) {
690 /* hash collision: replace old entry */
691 stat_cache_entry_free(sc
->files
->data
);
692 sc
->files
->data
= sce
;
694 int osize
= splaytree_size(sc
->files
);
696 sc
->files
= splaytree_insert(sc
->files
, file_ndx
, sce
);
697 force_assert(osize
+ 1 == splaytree_size(sc
->files
));
699 force_assert(sc
->files
);
700 force_assert(sc
->files
->data
== sce
);
704 buffer_clear(sce
->etag
);
705 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
706 buffer_clear(sce
->content_type
);
712 sce
->stat_ts
= srv
->cur_ts
;
714 /* catch the obvious symlinks
716 * this is not a secure check as we still have a race-condition between
717 * the stat() and the open. We can only solve this by
721 * and keeping the file open for the rest of the time. But this can
722 * only be done at network level.
724 * per default it is not a symlink
729 /* we want to only check for symlinks if we should block symlinks.
731 if (!follow_symlink
) {
732 if (stat_cache_lstat(srv
, name
, &lst
) == 0) {
737 * we assume "/" can not be symlink, so
738 * skip the symlink stuff if our path is /
740 else if (buffer_string_length(name
) > 1) {
744 dname
= buffer_init();
745 buffer_copy_buffer(dname
, name
);
747 while ((s_cur
= strrchr(dname
->ptr
, '/'))) {
748 buffer_string_set_length(dname
, s_cur
- dname
->ptr
);
749 if (dname
->ptr
== s_cur
) {
752 if (stat_cache_lstat(srv
, dname
, &lst
) == 0) {
763 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
764 stat_cache_fam_dir_monitor(srv
, sc
->scf
, sce
, name
);
770 return HANDLER_GO_ON
;
773 int stat_cache_open_rdonly_fstat (buffer
*name
, struct stat
*st
, int symlinks
) {
774 /*(Note: O_NOFOLLOW affects only the final path segment, the target file,
775 * not any intermediate symlinks along the path)*/
776 const int fd
= fdevent_open_cloexec(name
->ptr
, symlinks
, O_RDONLY
, 0);
778 if (0 == fstat(fd
, st
)) {
788 * remove stat() from cache which havn't been stat()ed for
789 * more than 10 seconds
792 * walk though the stat-cache, collect the ids which are too old
793 * and remove them in a second loop
796 static int stat_cache_tag_old_entries(server
*srv
, splay_tree
*t
, int *keys
, size_t *ndx
) {
797 stat_cache_entry
*sce
;
801 stat_cache_tag_old_entries(srv
, t
->left
, keys
, ndx
);
802 stat_cache_tag_old_entries(srv
, t
->right
, keys
, ndx
);
806 if (srv
->cur_ts
- sce
->stat_ts
> 2) {
807 keys
[(*ndx
)++] = t
->key
;
813 int stat_cache_trigger_cleanup(server
*srv
) {
815 size_t max_ndx
= 0, i
;
818 sc
= srv
->stat_cache
;
820 if (!sc
->files
) return 0;
822 keys
= calloc(1, sizeof(int) * sc
->files
->size
);
823 force_assert(NULL
!= keys
);
825 stat_cache_tag_old_entries(srv
, sc
->files
, keys
, &max_ndx
);
827 for (i
= 0; i
< max_ndx
; i
++) {
831 sc
->files
= splaytree_splay(sc
->files
, ndx
);
835 if (node
&& (node
->key
== ndx
)) {
836 stat_cache_entry_free(node
->data
);
837 sc
->files
= splaytree_delete(sc
->files
, ndx
);