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 djbhash(const char *str
, const size_t len
)
91 const unsigned char * const s
= (const unsigned char *)str
;
93 for (size_t i
= 0; i
< len
; ++i
) hash
= ((hash
<< 5) + hash
) ^ s
[i
];
98 static uint32_t hashme(const char *str
, const size_t len
)
100 /* strip highest bit of hash value for splaytree */
101 return djbhash(str
,len
) & ~(((uint32_t)1) << 31);
115 typedef struct stat_cache_fam
{
116 splay_tree
*dirs
; /* the nodes of the tree are fam_dir_entry */
121 fam_dir_entry
*fam_dir
;
122 buffer
*dir_name
; /* for building the dirname from the filename */
127 static fam_dir_entry
* fam_dir_entry_init(void) {
128 fam_dir_entry
*fam_dir
= NULL
;
130 fam_dir
= calloc(1, sizeof(*fam_dir
));
131 force_assert(NULL
!= fam_dir
);
133 fam_dir
->name
= buffer_init();
138 static void fam_dir_entry_free(FAMConnection
*fc
, void *data
) {
139 fam_dir_entry
*fam_dir
= data
;
141 if (!fam_dir
) return;
143 FAMCancelMonitor(fc
, fam_dir
->req
);
145 buffer_free(fam_dir
->name
);
151 static handler_t
stat_cache_handle_fdevent(server
*srv
, void *_fce
, int revent
) {
153 stat_cache_fam
*scf
= srv
->stat_cache
->scf
;
159 if (revent
& FDEVENT_IN
) {
160 events
= FAMPending(&scf
->fam
);
162 for (i
= 0; i
< events
; i
++) {
164 fam_dir_entry
*fam_dir
;
168 FAMNextEvent(&scf
->fam
, &fe
);
176 /* if the filename is a directory remove the entry */
178 fam_dir
= fe
.userdata
;
181 /* file/dir is still here */
182 if (fe
.code
== FAMChanged
) break;
184 ndx
= hashme(fe
.filename
, strlen(fe
.filename
));
186 scf
->dirs
= splaytree_splay(scf
->dirs
, ndx
);
189 if (node
&& (node
->key
== ndx
)) {
190 int osize
= splaytree_size(scf
->dirs
);
192 fam_dir_entry_free(&scf
->fam
, node
->data
);
193 scf
->dirs
= splaytree_delete(scf
->dirs
, ndx
);
195 force_assert(osize
- 1 == splaytree_size(scf
->dirs
));
204 if (revent
& (FDEVENT_HUP
|FDEVENT_RDHUP
)) {
205 /* fam closed the connection */
206 fdevent_fdnode_event_del(srv
->ev
, scf
->fdn
);
207 fdevent_unregister(srv
->ev
, scf
->fd
);
214 return HANDLER_GO_ON
;
217 static stat_cache_fam
* stat_cache_init_fam(server
*srv
) {
218 stat_cache_fam
*scf
= calloc(1, sizeof(*scf
));
219 scf
->dir_name
= buffer_init();
223 if (0 != FAMOpen2(&scf
->fam
, "lighttpd")) {
224 log_error_write(srv
, __FILE__
, __LINE__
, "s",
225 "could not open a fam connection, dieing.");
228 #ifdef HAVE_FAMNOEXISTS
229 FAMNoExists(&scf
->fam
);
232 scf
->fd
= FAMCONNECTION_GETFD(&scf
->fam
);
233 fdevent_setfd_cloexec(scf
->fd
);
234 scf
->fdn
= fdevent_register(srv
->ev
, scf
->fd
, stat_cache_handle_fdevent
, NULL
);
235 fdevent_fdnode_event_set(srv
->ev
, scf
->fdn
, FDEVENT_IN
| FDEVENT_RDHUP
);
240 static void stat_cache_free_fam(stat_cache_fam
*scf
) {
241 if (NULL
== scf
) return;
242 buffer_free(scf
->dir_name
);
246 splay_tree
*node
= scf
->dirs
;
248 osize
= scf
->dirs
->size
;
250 fam_dir_entry_free(&scf
->fam
, node
->data
);
251 scf
->dirs
= splaytree_delete(scf
->dirs
, node
->key
);
254 force_assert(NULL
== scf
->dirs
);
256 force_assert(osize
== (scf
->dirs
->size
+ 1));
261 /*scf->fdn already cleaned up in fdevent_free()*/
269 static int buffer_copy_dirname(buffer
*dst
, const buffer
*file
) {
272 if (buffer_string_is_empty(file
)) return -1;
274 for (i
= buffer_string_length(file
); i
> 0; i
--) {
275 if (file
->ptr
[i
] == '/') {
276 buffer_copy_string_len(dst
, file
->ptr
, i
);
284 static handler_t
stat_cache_fam_dir_check(server
*srv
, stat_cache_fam
*scf
, stat_cache_entry
*sce
, const buffer
*name
) {
285 if (0 != buffer_copy_dirname(scf
->dir_name
, name
)) {
286 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
287 "no '/' found in filename:", name
);
288 return HANDLER_ERROR
;
291 scf
->dir_ndx
= hashme(CONST_BUF_LEN(scf
->dir_name
));
293 scf
->dirs
= splaytree_splay(scf
->dirs
, scf
->dir_ndx
);
295 if ((NULL
!= scf
->dirs
) && (scf
->dirs
->key
== scf
->dir_ndx
)) {
296 scf
->fam_dir
= scf
->dirs
->data
;
298 /* check whether we got a collision */
299 if (buffer_is_equal(scf
->dir_name
, scf
->fam_dir
->name
)) {
300 /* test whether a found file cache entry is still ok */
301 if ((NULL
!= sce
) && (scf
->fam_dir
->version
== sce
->dir_version
)) {
302 /* the stat()-cache entry is still ok */
303 return HANDLER_FINISHED
;
306 /* hash collision, forget about the entry */
313 return HANDLER_GO_ON
;
316 static void stat_cache_fam_dir_monitor(server
*srv
, stat_cache_fam
*scf
, stat_cache_entry
*sce
, const buffer
*name
) {
317 /* is this directory already registered ? */
318 fam_dir_entry
*fam_dir
= scf
->fam_dir
;
319 if (NULL
== fam_dir
) {
320 fam_dir
= fam_dir_entry_init();
322 buffer_copy_buffer(fam_dir
->name
, scf
->dir_name
);
324 fam_dir
->version
= 1;
326 fam_dir
->req
= calloc(1, sizeof(FAMRequest
));
327 force_assert(NULL
!= fam_dir
);
329 if (0 != FAMMonitorDirectory(&scf
->fam
, fam_dir
->name
->ptr
,
330 fam_dir
->req
, fam_dir
)) {
332 log_error_write(srv
, __FILE__
, __LINE__
, "sbsbs",
333 "monitoring dir failed:",
336 FamErrlist
[FAMErrno
]);
338 fam_dir_entry_free(&scf
->fam
, fam_dir
);
341 int osize
= splaytree_size(scf
->dirs
);
343 /* already splayed scf->dir_ndx */
344 if ((NULL
!= scf
->dirs
) && (scf
->dirs
->key
== scf
->dir_ndx
)) {
345 /* hash collision: replace old entry */
346 fam_dir_entry_free(&scf
->fam
, scf
->dirs
->data
);
347 scf
->dirs
->data
= fam_dir
;
349 scf
->dirs
= splaytree_insert(scf
->dirs
, scf
->dir_ndx
, fam_dir
);
350 force_assert(osize
== (splaytree_size(scf
->dirs
) - 1));
353 force_assert(scf
->dirs
);
354 force_assert(scf
->dirs
->data
== fam_dir
);
355 scf
->fam_dir
= fam_dir
;
359 /* bind the fam_fc to the stat() cache entry */
362 sce
->dir_version
= fam_dir
->version
;
369 stat_cache
*stat_cache_init(server
*srv
) {
370 stat_cache
*sc
= NULL
;
373 sc
= calloc(1, sizeof(*sc
));
374 force_assert(NULL
!= sc
);
377 if (STAT_CACHE_ENGINE_FAM
== srv
->srvconf
.stat_cache_engine
) {
378 sc
->scf
= stat_cache_init_fam(srv
);
379 if (NULL
== sc
->scf
) {
389 static stat_cache_entry
* stat_cache_entry_init(void) {
390 stat_cache_entry
*sce
= NULL
;
392 sce
= calloc(1, sizeof(*sce
));
393 force_assert(NULL
!= sce
);
395 sce
->name
= buffer_init();
396 sce
->etag
= buffer_init();
397 sce
->content_type
= buffer_init();
402 static void stat_cache_entry_free(void *data
) {
403 stat_cache_entry
*sce
= data
;
406 buffer_free(sce
->etag
);
407 buffer_free(sce
->name
);
408 buffer_free(sce
->content_type
);
413 void stat_cache_free(stat_cache
*sc
) {
416 splay_tree
*node
= sc
->files
;
418 osize
= sc
->files
->size
;
420 stat_cache_entry_free(node
->data
);
421 sc
->files
= splaytree_delete(sc
->files
, node
->key
);
423 force_assert(osize
- 1 == splaytree_size(sc
->files
));
427 stat_cache_free_fam(sc
->scf
);
432 int stat_cache_choose_engine (server
*srv
, const buffer
*stat_cache_string
) {
433 if (buffer_string_is_empty(stat_cache_string
)) {
434 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
435 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("simple"))) {
436 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
438 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("fam"))) {
439 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_FAM
;
441 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("disable"))) {
442 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_NONE
;
444 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
445 "server.stat-cache-engine can be one of \"disable\", \"simple\","
449 " but not:", stat_cache_string
);
455 #if defined(HAVE_XATTR)
456 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
460 buffer_string_prepare_copy(buf
, 1023);
461 attrlen
= buf
->size
- 1;
462 if(0 == (ret
= attr_get(name
, xattrname
, buf
->ptr
, &attrlen
, 0))) {
463 buffer_commit(buf
, attrlen
);
467 #elif defined(HAVE_EXTATTR)
468 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
471 buffer_string_prepare_copy(buf
, 1023);
473 if (-1 != (attrlen
= extattr_get_file(name
, EXTATTR_NAMESPACE_USER
, xattrname
, buf
->ptr
, buf
->size
- 1))) {
474 buf
->used
= attrlen
+ 1;
475 buf
->ptr
[attrlen
] = '\0';
482 const buffer
* stat_cache_mimetype_by_ext(const connection
*con
, const char *name
, size_t nlen
)
484 const char *end
= name
+ nlen
; /*(end of string)*/
485 const size_t used
= con
->conf
.mimetypes
->used
;
487 for (size_t i
= 0; i
< used
; ++i
) {
489 const data_string
*ds
= (data_string
*)con
->conf
.mimetypes
->data
[i
];
490 const size_t klen
= buffer_string_length(ds
->key
);
491 if (klen
<= nlen
&& 0 == strncasecmp(end
-klen
, ds
->key
->ptr
, klen
))
497 const data_string
*ds
;
499 for (s
= end
-1; s
!= name
&& *s
!= '/'; --s
) ; /*(like memrchr())*/
505 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
506 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
507 if (NULL
!= ds
) return ds
->value
;
509 while (*s
!= '.' && ++s
!= end
) ;
511 /* search ".ext" then "ext" */
512 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
513 if (NULL
!= ds
) return ds
->value
;
514 /* repeat search without leading '.' to handle situation where
515 * admin configured mimetype.assign keys without leading '.' */
517 if (*s
== '.') { --s
; continue; }
518 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
519 if (NULL
!= ds
) return ds
->value
;
522 /* search for ""; catchall */
523 ds
= (data_string
*)array_get_element(con
->conf
.mimetypes
, "");
524 if (NULL
!= ds
) return ds
->value
;
530 const buffer
* stat_cache_content_type_get(server
*srv
, connection
*con
, const buffer
*name
, stat_cache_entry
*sce
)
532 /*(invalid caching if user config has multiple, different
533 * con->conf.mimetypes for same extension (not expected))*/
534 if (!buffer_string_is_empty(sce
->content_type
)) return sce
->content_type
;
536 if (S_ISREG(sce
->st
.st_mode
)) {
537 /* determine mimetype */
538 buffer_clear(sce
->content_type
);
539 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
540 if (con
->conf
.use_xattr
) {
541 stat_cache_attr_get(sce
->content_type
, name
->ptr
, srv
->srvconf
.xattr_name
->ptr
);
546 /* xattr did not set a content-type. ask the config */
547 if (buffer_string_is_empty(sce
->content_type
)) {
548 const buffer
*type
= stat_cache_mimetype_by_ext(con
, CONST_BUF_LEN(name
));
550 buffer_copy_buffer(sce
->content_type
, type
);
553 return sce
->content_type
;
559 const buffer
* stat_cache_etag_get(stat_cache_entry
*sce
, etag_flags_t flags
) {
560 /*(invalid caching if user config has multiple, different con->etag_flags
561 * for same path (not expected, since etag flags should be by filesystem))*/
562 if (!buffer_string_is_empty(sce
->etag
)) return sce
->etag
;
564 if (S_ISREG(sce
->st
.st_mode
) || S_ISDIR(sce
->st
.st_mode
)) {
565 etag_create(sce
->etag
, &sce
->st
, flags
);
578 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
579 * - HANDLER_ERROR on stat() failed -> see errno for problem
582 handler_t
stat_cache_get_entry(server
*srv
, connection
*con
, buffer
*name
, stat_cache_entry
**ret_sce
) {
583 stat_cache_entry
*sce
= NULL
;
593 * check if the directory for this file has changed
596 sc
= srv
->stat_cache
;
598 file_ndx
= hashme(CONST_BUF_LEN(name
));
599 sc
->files
= splaytree_splay(sc
->files
, file_ndx
);
601 if (sc
->files
&& (sc
->files
->key
== file_ndx
)) {
602 /* we have seen this file already and
603 * don't stat() it again in the same second */
605 sce
= sc
->files
->data
;
607 /* check if the name is the same, we might have a collision */
609 if (buffer_is_equal(name
, sce
->name
)) {
610 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_SIMPLE
) {
611 if (sce
->stat_ts
== srv
->cur_ts
) {
613 return HANDLER_GO_ON
;
617 /* collision, forget about the entry */
624 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
625 switch (stat_cache_fam_dir_check(srv
, sc
->scf
, sce
, name
)) {
628 case HANDLER_FINISHED
:
630 return HANDLER_GO_ON
;
633 return HANDLER_ERROR
;
640 * - open() + fstat() on a named-pipe results in a (intended) hang.
641 * - stat() if regular file + open() to see if we can read from it is better
644 if (-1 == stat(name
->ptr
, &st
)) {
645 return HANDLER_ERROR
;
649 if (S_ISREG(st
.st_mode
)) {
650 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
651 if (name
->ptr
[buffer_string_length(name
) - 1] == '/') {
653 return HANDLER_ERROR
;
656 /* try to open the file to check if we can read it */
658 fd
= open(name
->ptr
, O_RDONLY
| O_NONBLOCK
, 0);
660 fd
= open(name
->ptr
, O_RDONLY
, 0);
663 return HANDLER_ERROR
;
670 sce
= stat_cache_entry_init();
671 buffer_copy_buffer(sce
->name
, name
);
673 /* already splayed file_ndx */
674 if ((NULL
!= sc
->files
) && (sc
->files
->key
== file_ndx
)) {
675 /* hash collision: replace old entry */
676 stat_cache_entry_free(sc
->files
->data
);
677 sc
->files
->data
= sce
;
679 int osize
= splaytree_size(sc
->files
);
681 sc
->files
= splaytree_insert(sc
->files
, file_ndx
, sce
);
682 force_assert(osize
+ 1 == splaytree_size(sc
->files
));
684 force_assert(sc
->files
);
685 force_assert(sc
->files
->data
== sce
);
689 buffer_clear(sce
->etag
);
690 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
691 buffer_clear(sce
->content_type
);
697 sce
->stat_ts
= srv
->cur_ts
;
700 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
701 stat_cache_fam_dir_monitor(srv
, sc
->scf
, sce
, name
);
707 return HANDLER_GO_ON
;
710 int stat_cache_path_contains_symlink(server
*srv
, buffer
*name
) {
711 /* caller should check for symlinks only if we should block symlinks. */
713 /* catch the obvious symlinks
715 * this is not a secure check as we still have a race-condition between
716 * the stat() and the open. We can only solve this by
720 * and keeping the file open for the rest of the time. But this can
721 * only be done at network level.
725 /* we assume "/" can not be symlink,
726 * so skip the symlink stuff if path is "/" */
727 size_t len
= buffer_string_length(name
);
728 force_assert(0 != len
);
729 force_assert(name
->ptr
[0] == '/');
730 if (1 == len
) return 0;
732 #define PATH_MAX 4096
734 if (len
>= PATH_MAX
) return -1;
737 memcpy(buf
, name
->ptr
, len
);
738 char *s_cur
= buf
+len
;
742 if (0 == lstat(buf
, &st
)) {
743 if (S_ISLNK(st
.st_mode
)) return 1;
746 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
747 "lstat failed for:", buf
, strerror(errno
));
750 } while ((s_cur
= strrchr(buf
, '/')) != buf
);
756 int stat_cache_open_rdonly_fstat (buffer
*name
, struct stat
*st
, int symlinks
) {
757 /*(Note: O_NOFOLLOW affects only the final path segment, the target file,
758 * not any intermediate symlinks along the path)*/
759 const int fd
= fdevent_open_cloexec(name
->ptr
, symlinks
, O_RDONLY
, 0);
761 if (0 == fstat(fd
, st
)) {
771 * remove stat() from cache which havn't been stat()ed for
772 * more than 10 seconds
775 * walk though the stat-cache, collect the ids which are too old
776 * and remove them in a second loop
779 static int stat_cache_tag_old_entries(server
*srv
, splay_tree
*t
, int *keys
, size_t *ndx
) {
780 stat_cache_entry
*sce
;
784 stat_cache_tag_old_entries(srv
, t
->left
, keys
, ndx
);
785 stat_cache_tag_old_entries(srv
, t
->right
, keys
, ndx
);
789 if (srv
->cur_ts
- sce
->stat_ts
> 2) {
790 keys
[(*ndx
)++] = t
->key
;
796 int stat_cache_trigger_cleanup(server
*srv
) {
798 size_t max_ndx
= 0, i
;
801 sc
= srv
->stat_cache
;
803 if (!sc
->files
) return 0;
805 keys
= calloc(1, sizeof(int) * sc
->files
->size
);
806 force_assert(NULL
!= keys
);
808 stat_cache_tag_old_entries(srv
, sc
->files
, keys
, &max_ndx
);
810 for (i
= 0; i
< max_ndx
; i
++) {
814 sc
->files
= splaytree_splay(sc
->files
, ndx
);
818 if (node
&& (node
->key
== ndx
)) {
819 stat_cache_entry_free(node
->data
);
820 sc
->files
= splaytree_delete(sc
->files
, ndx
);