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>
30 #define S_ISLNK(mode) (0)
37 * - a splay-tree is used as we can use the caching effect of it
41 STAT_CACHE_ENGINE_UNSET
,
42 STAT_CACHE_ENGINE_NONE
,
43 STAT_CACHE_ENGINE_SIMPLE
,
47 struct stat_cache_fam
; /* declaration */
49 typedef struct stat_cache
{
50 splay_tree
*files
; /* nodes of tree are (stat_cache_entry *) */
51 struct stat_cache_fam
*scf
;
55 /* the famous DJB hash function for strings */
56 static uint32_t djbhash(const char *str
, const size_t len
)
58 const unsigned char * const s
= (const unsigned char *)str
;
60 for (size_t i
= 0; i
< len
; ++i
) hash
= ((hash
<< 5) + hash
) ^ s
[i
];
65 static uint32_t hashme(const char *str
, const size_t len
)
67 /* strip highest bit of hash value for splaytree */
68 return djbhash(str
,len
) & ~(((uint32_t)1) << 31);
72 static void * stat_cache_sptree_find(splay_tree
** const sptree
,
73 const char * const name
,
76 const int ndx
= hashme(name
, len
);
77 *sptree
= splaytree_splay(*sptree
, ndx
);
78 return (*sptree
&& (*sptree
)->key
== ndx
) ? (*sptree
)->data
: NULL
;
84 /* monitor changes in directories using FAM
86 * This implementation employing FAM monitors directories as they are used,
87 * and maintains a reference count for cache use within stat_cache.c.
88 * A periodic job runs in lighttpd every 32 seconds, expiring entires unused
89 * in last 64 seconds out of the cache and cancelling FAM monitoring. Items
90 * within the cache are checked against the filesystem upon use if last stat()
91 * was greater than or equal to 16 seconds ago.
93 * This implementation does not monitor every directory in a tree, and therefore
94 * the cache may get out-of-sync with the filesystem. Delays in receiving and
95 * processing events from FAM might also lead to stale cache entries.
97 * For many websites, a large number of files are seldom, if ever, modified,
98 * and a common practice with images is to create a new file with a new name
99 * when a new version is needed, in order for client browsers and CDNs to better
100 * cache the content. Given this, most use will see little difference in
101 * performance between server.stat-cache-engine = "fam" and "simple" (default).
102 * The default server.stat-cache-engine = "simple" calls stat() on a target once
103 * per second, and reuses that information until the next second. For use where
104 * changes must be immediately visible, server.stat-cache-engine = "disable"
107 * When considering use of server.stat-cache-engine = "fam", there are a few
108 * additional limitations for this cache implementation using FAM.
109 * - symlinks to files located outside of the current directory do not result
110 * in changes to that file being monitored (unless that file is in a directory
111 * which is monitored as a result of a different request). symlinks can be
112 * chained and can be circular. This implementation *does not* readlink() or
113 * realpath() to resolve the chains to find and monitor the ultimate target
114 * directory. While symlinks to files located outside the current directory
115 * are not monitored, symlinks to directories *are* monitored, though chains
116 * of symlinks to directories do not result in monitoring of the directories
117 * containing intermediate symlinks to the target directory.
118 * - directory rename of a directory which is not currently being monitored will
119 * result in stale information in the cache if there is a subdirectory that is
121 * Even though lighttpd will not receive FAM events in the above cases, lighttpd
122 * does re-validate the information in the cache upon use if the cache entry has
123 * not been checked in 16 seconds, so that is the upper limit for use of stale
126 * Use of server.stat-cache-engine = "fam" is discouraged for extremely volatile
127 * directories such as temporary directories (e.g. /tmp and maybe /var/tmp) due
128 * to the overhead of processing the additional noise generated from changes.
129 * Related, server.stat-cache-engine = "fam" is not recommended on trees of
130 * untrusted files where a malicious user could generate an excess of change
133 * Internal note: lighttpd walks the caches to prune trees in stat_cache when an
134 * event is received for a directory (or symlink to a directory) which has been
135 * deleted or renamed. The splaytree data structure is suboptimal for frequent
136 * changes of large directories trees where there have been a large number of
137 * different files recently accessed and part of the stat_cache.
142 typedef struct fam_dir_entry
{
149 struct fam_dir_entry
*fam_parent
;
152 typedef struct stat_cache_fam
{
153 splay_tree
*dirs
; /* the nodes of the tree are fam_dir_entry */
159 static fam_dir_entry
* fam_dir_entry_init(const char *name
, size_t len
)
161 fam_dir_entry
* const fam_dir
= calloc(1, sizeof(*fam_dir
));
162 force_assert(NULL
!= fam_dir
);
164 fam_dir
->name
= buffer_init();
165 buffer_copy_string_len(fam_dir
->name
, name
, len
);
171 static void fam_dir_entry_free(fam_dir_entry
*fam_dir
)
173 if (!fam_dir
) return;
174 /*(fam_dir->parent might be invalid pointer here; ignore)*/
175 buffer_free(fam_dir
->name
);
179 static void fam_dir_invalidate_node(fam_dir_entry
*fam_dir
)
181 fam_dir
->stat_ts
= 0;
182 if (fam_dir
->fam_parent
) {
183 --fam_dir
->fam_parent
->refcnt
;
184 fam_dir
->fam_parent
= NULL
;
189 * walk though splay_tree and collect contents of dir tree.
190 * remove tagged entries in a second loop
193 static void fam_dir_tag_refcnt(splay_tree
*t
, int *keys
, int *ndx
)
195 if (*ndx
== 8192) return; /*(must match num array entries in keys[])*/
196 if (t
->left
) fam_dir_tag_refcnt(t
->left
, keys
, ndx
);
197 if (t
->right
) fam_dir_tag_refcnt(t
->right
, keys
, ndx
);
198 if (*ndx
== 8192) return; /*(must match num array entries in keys[])*/
200 fam_dir_entry
* const fam_dir
= t
->data
;
201 if (0 == fam_dir
->refcnt
) {
202 fam_dir_invalidate_node(fam_dir
);
203 keys
[(*ndx
)++] = t
->key
;
207 static void fam_dir_periodic_cleanup(server
*srv
) {
209 int keys
[8192]; /* 32k size on stack */
210 stat_cache_fam
* const scf
= srv
->stat_cache
->scf
;
212 if (!scf
->dirs
) return;
214 fam_dir_tag_refcnt(scf
->dirs
, keys
, &max_ndx
);
215 for (i
= 0; i
< max_ndx
; ++i
) {
216 const int ndx
= keys
[i
];
217 splay_tree
*node
= scf
->dirs
= splaytree_splay(scf
->dirs
, ndx
);
218 if (node
&& node
->key
== ndx
) {
219 fam_dir_entry
*fam_dir
= node
->data
;
220 scf
->dirs
= splaytree_delete(scf
->dirs
, ndx
);
221 FAMCancelMonitor(&scf
->fam
, &fam_dir
->req
);
222 fam_dir_entry_free(fam_dir
);
225 } while (max_ndx
== sizeof(keys
)/sizeof(int));
228 static void fam_dir_invalidate_tree(splay_tree
*t
, const char *name
, size_t len
)
231 if (t
->left
) fam_dir_invalidate_tree(t
->left
, name
, len
);
232 if (t
->right
) fam_dir_invalidate_tree(t
->right
, name
, len
);
234 fam_dir_entry
* const fam_dir
= t
->data
;
235 buffer
*b
= fam_dir
->name
;
236 size_t blen
= buffer_string_length(b
);
237 if (blen
> len
&& b
->ptr
[len
] == '/' && 0 == memcmp(b
->ptr
, name
, len
))
238 fam_dir_invalidate_node(fam_dir
);
242 static void stat_cache_delete_tree(server
*srv
, const char *name
, size_t len
);
243 static void stat_cache_invalidate_entry(server
*srv
, const char *name
, size_t len
);
244 static void stat_cache_invalidate_dir_tree(server
*srv
, const char *name
, size_t len
);
246 static void stat_cache_handle_fdevent_in(server
*srv
, stat_cache_fam
*scf
)
248 for (int i
= 0, ndx
; i
|| (i
= FAMPending(&scf
->fam
)) > 0; --i
) {
250 if (FAMNextEvent(&scf
->fam
, &fe
) < 0) break;
252 /* ignore events which may have been pending for
253 * paths recently cancelled via FAMCancelMonitor() */
254 ndx
= (int)(intptr_t)fe
.userdata
;
255 scf
->dirs
= splaytree_splay(scf
->dirs
, ndx
);
256 if (!scf
->dirs
|| scf
->dirs
->key
!= ndx
) {
259 fam_dir_entry
*fam_dir
= scf
->dirs
->data
;
260 if (FAMREQUEST_GETREQNUM(&fam_dir
->req
)
261 != FAMREQUEST_GETREQNUM(&fe
.fr
)) {
265 if (fe
.filename
[0] != '/') {
266 buffer
* const n
= fam_dir
->name
;
267 fam_dir_entry
*fam_link
;
271 /* file created in monitored dir modifies dir and
272 * we should get a separate FAMChanged event for dir.
273 * Therefore, ignore file FAMCreated event here.
274 * Also, if FAMNoExists() is used, might get spurious
275 * FAMCreated events as changes are made e.g. in monitored
276 * sub-sub-sub dirs and the library discovers new (already
277 * existing) dir entries */
280 /* file changed in monitored dir does not modify dir */
283 /* file deleted or moved in monitored dir modifies dir,
284 * but FAM provides separate notification for that */
286 /* temporarily append filename to dir in fam_dir->name to
287 * construct path, then delete stat_cache entry (if any)*/
288 len
= buffer_string_length(n
);
289 buffer_append_string_len(n
, CONST_STR_LEN("/"));
290 buffer_append_string_len(n
,fe
.filename
,strlen(fe
.filename
));
291 /* (alternatively, could chose to stat() and update)*/
292 stat_cache_invalidate_entry(srv
, CONST_BUF_LEN(n
));
294 fam_link
= /*(check if might be symlink to monitored dir)*/
295 stat_cache_sptree_find(&scf
->dirs
, CONST_BUF_LEN(n
));
296 if (fam_link
&& !buffer_is_equal(fam_link
->name
, n
))
299 buffer_string_set_length(n
, len
);
302 /* replaced symlink changes containing dir */
303 stat_cache_invalidate_entry(srv
, CONST_BUF_LEN(n
));
304 /* handle symlink to dir as deleted dir below */
305 fe
.code
= FAMDeleted
;
317 stat_cache_invalidate_entry(srv
, CONST_BUF_LEN(fam_dir
->name
));
321 stat_cache_delete_tree(srv
, CONST_BUF_LEN(fam_dir
->name
));
322 fam_dir_invalidate_node(fam_dir
);
323 fam_dir_invalidate_tree(scf
->dirs
, CONST_BUF_LEN(fam_dir
->name
));
324 fam_dir_periodic_cleanup(srv
);
332 static handler_t
stat_cache_handle_fdevent(server
*srv
, void *_fce
, int revent
)
334 stat_cache_fam
*scf
= srv
->stat_cache
->scf
;
337 if (revent
& FDEVENT_IN
) {
338 stat_cache_handle_fdevent_in(srv
, scf
);
341 if (revent
& (FDEVENT_HUP
|FDEVENT_RDHUP
)) {
342 /* fam closed the connection */
343 fdevent_fdnode_event_del(srv
->ev
, scf
->fdn
);
344 fdevent_unregister(srv
->ev
, scf
->fd
);
351 return HANDLER_GO_ON
;
354 static stat_cache_fam
* stat_cache_init_fam(server
*srv
) {
355 stat_cache_fam
*scf
= calloc(1, sizeof(*scf
));
360 if (0 != FAMOpen2(&scf
->fam
, "lighttpd")) {
361 log_error_write(srv
, __FILE__
, __LINE__
, "s",
362 "could not open a fam connection, dieing.");
365 #ifdef HAVE_FAMNOEXISTS
366 FAMNoExists(&scf
->fam
);
369 scf
->fd
= FAMCONNECTION_GETFD(&scf
->fam
);
370 fdevent_setfd_cloexec(scf
->fd
);
371 scf
->fdn
= fdevent_register(srv
->ev
, scf
->fd
, stat_cache_handle_fdevent
, NULL
);
372 fdevent_fdnode_event_set(srv
->ev
, scf
->fdn
, FDEVENT_IN
| FDEVENT_RDHUP
);
377 static void stat_cache_free_fam(stat_cache_fam
*scf
) {
378 if (NULL
== scf
) return;
381 /*(skip entry invalidation and FAMCancelMonitor())*/
382 splay_tree
*node
= scf
->dirs
;
383 fam_dir_entry_free((fam_dir_entry
*)node
->data
);
384 scf
->dirs
= splaytree_delete(scf
->dirs
, node
->key
);
388 /*scf->fdn already cleaned up in fdevent_free()*/
396 static fam_dir_entry
* fam_dir_monitor(server
*srv
, stat_cache_fam
*scf
, char *fn
, size_t dirlen
, struct stat
*st
)
398 const int fn_is_dir
= S_ISDIR(st
->st_mode
);
399 /*force_assert(0 != dirlen);*/
400 /*force_assert(fn[0] == '/');*/
401 /* consistency: ensure fn does not end in '/' unless root "/"
402 * FAM events will not end in '/', so easier to match this way */
403 if (fn
[dirlen
-1] == '/') --dirlen
;
404 if (0 == dirlen
) dirlen
= 1; /* root dir ("/") */
405 /* Note: paths are expected to be normalized before calling stat_cache,
406 * e.g. without repeated '/' */
408 while (fn
[--dirlen
] != '/') ;
409 if (0 == dirlen
) dirlen
= 1; /*(should not happen for file)*/
411 int dir_ndx
= hashme(fn
, dirlen
);
412 fam_dir_entry
*fam_dir
= NULL
;
414 scf
->dirs
= splaytree_splay(scf
->dirs
, dir_ndx
);
415 if (NULL
!= scf
->dirs
&& scf
->dirs
->key
== dir_ndx
) {
416 fam_dir
= scf
->dirs
->data
;
417 if (!buffer_is_equal_string(fam_dir
->name
, fn
, dirlen
)) {
418 /* hash collision; preserve existing
419 * do not monitor new to avoid cache thrashing */
422 /* directory already registered */
426 int ck_dir
= fn_is_dir
;
427 if (!fn_is_dir
&& (NULL
==fam_dir
|| srv
->cur_ts
- fam_dir
->stat_ts
>= 16)) {
429 /*(temporarily modify fn)*/
431 if (0 != lstat(fn
, &lst
)) {
435 if (!S_ISLNK(lst
.st_mode
)) {
438 else if (0 != stat(fn
, st
)) { /*st passed in now is stat() of dir*/
445 int ck_lnk
= (NULL
== fam_dir
);
446 if (ck_dir
&& NULL
!= fam_dir
) {
447 /* check stat() matches device and inode, just in case an external event
448 * not being monitored occurs (e.g. rename of unmonitored parent dir)*/
449 if (st
->st_dev
!= fam_dir
->st_dev
|| st
->st_ino
!= fam_dir
->st_ino
) {
451 /*(modifies scf->dirs but no need to re-splay for dir_ndx since
452 * fam_dir is not NULL and so splaytree_insert not called below)*/
453 if (scf
->dirs
) fam_dir_invalidate_tree(scf
->dirs
, fn
, dirlen
);
454 if (!fn_is_dir
) /*(if dir, caller is updating stat_cache_entry)*/
455 stat_cache_update_entry(srv
, fn
, dirlen
, st
, NULL
);
456 /*(must not delete tree since caller is holding a valid node)*/
457 stat_cache_invalidate_dir_tree(srv
, fn
, dirlen
);
458 if (0 != FAMCancelMonitor(&scf
->fam
, &fam_dir
->req
)
459 || 0 != FAMMonitorDirectory(&scf
->fam
, fam_dir
->name
->ptr
,
461 (void *)(intptr_t)dir_ndx
)) {
462 fam_dir
->stat_ts
= 0; /* invalidate */
465 fam_dir
->st_dev
= st
->st_dev
;
466 fam_dir
->st_ino
= st
->st_ino
;
468 fam_dir
->stat_ts
= srv
->cur_ts
;
471 if (NULL
== fam_dir
) {
472 fam_dir
= fam_dir_entry_init(fn
, dirlen
);
474 if (0 != FAMMonitorDirectory(&scf
->fam
,fam_dir
->name
->ptr
,&fam_dir
->req
,
475 (void *)(intptr_t)dir_ndx
)) {
476 log_error_write(srv
, __FILE__
, __LINE__
, "sbsss",
477 "monitoring dir failed:",
480 FamErrlist
[FAMErrno
]);
481 fam_dir_entry_free(fam_dir
);
485 scf
->dirs
= splaytree_insert(scf
->dirs
, dir_ndx
, fam_dir
);
486 fam_dir
->stat_ts
= srv
->cur_ts
;
487 fam_dir
->st_dev
= st
->st_dev
;
488 fam_dir
->st_ino
= st
->st_ino
;
493 /*(temporarily modify fn)*/
496 if (0 != lstat(fn
, &lst
)) {
502 if (fam_dir
->fam_parent
) {
503 --fam_dir
->fam_parent
->refcnt
;
504 fam_dir
->fam_parent
= NULL
;
506 if (S_ISLNK(lst
.st_mode
)) {
507 fam_dir
->fam_parent
= fam_dir_monitor(srv
, scf
, fn
, dirlen
, &lst
);
518 stat_cache
*stat_cache_init(server
*srv
) {
519 stat_cache
*sc
= NULL
;
522 sc
= calloc(1, sizeof(*sc
));
523 force_assert(NULL
!= sc
);
526 if (STAT_CACHE_ENGINE_FAM
== srv
->srvconf
.stat_cache_engine
) {
527 sc
->scf
= stat_cache_init_fam(srv
);
528 if (NULL
== sc
->scf
) {
538 static stat_cache_entry
* stat_cache_entry_init(void) {
539 stat_cache_entry
*sce
= NULL
;
541 sce
= calloc(1, sizeof(*sce
));
542 force_assert(NULL
!= sce
);
544 sce
->name
= buffer_init();
545 sce
->etag
= buffer_init();
546 sce
->content_type
= buffer_init();
551 static void stat_cache_entry_free(void *data
) {
552 stat_cache_entry
*sce
= data
;
556 /*(decrement refcnt only;
557 * defer cancelling FAM monitor on dir even if refcnt reaches zero)*/
558 if (sce
->fam_dir
) --((fam_dir_entry
*)sce
->fam_dir
)->refcnt
;
561 buffer_free(sce
->etag
);
562 buffer_free(sce
->name
);
563 buffer_free(sce
->content_type
);
568 void stat_cache_free(stat_cache
*sc
) {
570 splay_tree
*node
= sc
->files
;
571 stat_cache_entry_free(node
->data
);
572 sc
->files
= splaytree_delete(sc
->files
, node
->key
);
576 stat_cache_free_fam(sc
->scf
);
581 int stat_cache_choose_engine (server
*srv
, const buffer
*stat_cache_string
) {
582 if (buffer_string_is_empty(stat_cache_string
)) {
583 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
584 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("simple"))) {
585 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_SIMPLE
;
587 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("fam"))) {
588 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_FAM
;
590 } else if (buffer_is_equal_string(stat_cache_string
, CONST_STR_LEN("disable"))) {
591 srv
->srvconf
.stat_cache_engine
= STAT_CACHE_ENGINE_NONE
;
593 log_error_write(srv
, __FILE__
, __LINE__
, "sb",
594 "server.stat-cache-engine can be one of \"disable\", \"simple\","
598 " but not:", stat_cache_string
);
604 #if defined(HAVE_XATTR)
605 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
609 buffer_string_prepare_copy(buf
, 1023);
610 attrlen
= buf
->size
- 1;
611 if(0 == (ret
= attr_get(name
, xattrname
, buf
->ptr
, &attrlen
, 0))) {
612 buffer_commit(buf
, attrlen
);
616 #elif defined(HAVE_EXTATTR)
617 static int stat_cache_attr_get(buffer
*buf
, char *name
, char *xattrname
) {
620 buffer_string_prepare_copy(buf
, 1023);
622 if (-1 != (attrlen
= extattr_get_file(name
, EXTATTR_NAMESPACE_USER
, xattrname
, buf
->ptr
, buf
->size
- 1))) {
623 buf
->used
= attrlen
+ 1;
624 buf
->ptr
[attrlen
] = '\0';
631 const buffer
* stat_cache_mimetype_by_ext(const connection
*con
, const char *name
, size_t nlen
)
633 const char *end
= name
+ nlen
; /*(end of string)*/
634 const size_t used
= con
->conf
.mimetypes
->used
;
636 for (size_t i
= 0; i
< used
; ++i
) {
638 const data_string
*ds
= (data_string
*)con
->conf
.mimetypes
->data
[i
];
639 const size_t klen
= buffer_string_length(ds
->key
);
640 if (klen
<= nlen
&& 0 == strncasecmp(end
-klen
, ds
->key
->ptr
, klen
))
646 const data_string
*ds
;
648 for (s
= end
-1; s
!= name
&& *s
!= '/'; --s
) ; /*(like memrchr())*/
654 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */
655 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
656 if (NULL
!= ds
) return ds
->value
;
658 while (*s
!= '.' && ++s
!= end
) ;
660 /* search ".ext" then "ext" */
661 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
662 if (NULL
!= ds
) return ds
->value
;
663 /* repeat search without leading '.' to handle situation where
664 * admin configured mimetype.assign keys without leading '.' */
666 if (*s
== '.') { --s
; continue; }
667 ds
= (data_string
*)array_get_element_klen(con
->conf
.mimetypes
, s
, end
- s
);
668 if (NULL
!= ds
) return ds
->value
;
671 /* search for ""; catchall */
672 ds
= (data_string
*)array_get_element(con
->conf
.mimetypes
, "");
673 if (NULL
!= ds
) return ds
->value
;
679 const buffer
* stat_cache_content_type_get(server
*srv
, connection
*con
, const buffer
*name
, stat_cache_entry
*sce
)
681 /*(invalid caching if user config has multiple, different
682 * con->conf.mimetypes for same extension (not expected))*/
683 if (!buffer_string_is_empty(sce
->content_type
)) return sce
->content_type
;
685 if (S_ISREG(sce
->st
.st_mode
)) {
686 /* determine mimetype */
687 buffer_clear(sce
->content_type
);
688 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
689 if (con
->conf
.use_xattr
) {
690 stat_cache_attr_get(sce
->content_type
, name
->ptr
, srv
->srvconf
.xattr_name
->ptr
);
695 /* xattr did not set a content-type. ask the config */
696 if (buffer_string_is_empty(sce
->content_type
)) {
697 const buffer
*type
= stat_cache_mimetype_by_ext(con
, CONST_BUF_LEN(name
));
699 buffer_copy_buffer(sce
->content_type
, type
);
702 return sce
->content_type
;
708 const buffer
* stat_cache_etag_get(stat_cache_entry
*sce
, etag_flags_t flags
) {
709 /*(invalid caching if user config has multiple, different con->etag_flags
710 * for same path (not expected, since etag flags should be by filesystem))*/
711 if (!buffer_string_is_empty(sce
->etag
)) return sce
->etag
;
713 if (S_ISREG(sce
->st
.st_mode
) || S_ISDIR(sce
->st
.st_mode
)) {
714 etag_create(sce
->etag
, &sce
->st
, flags
);
721 void stat_cache_update_entry(server
*srv
, const char *name
, size_t len
,
722 struct stat
*st
, buffer
*etagb
)
724 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_NONE
) return;
725 force_assert(0 != len
);
726 if (name
[len
-1] == '/') { if (0 == --len
) len
= 1; }
727 splay_tree
**sptree
= &srv
->stat_cache
->files
;
728 stat_cache_entry
*sce
=
729 stat_cache_sptree_find(sptree
, name
, len
);
730 if (sce
&& buffer_is_equal_string(sce
->name
, name
, len
)) {
731 sce
->stat_ts
= srv
->cur_ts
;
732 sce
->st
= *st
; /* etagb might be NULL to clear etag (invalidate) */
733 buffer_copy_string_len(sce
->etag
, CONST_BUF_LEN(etagb
));
734 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
735 buffer_clear(sce
->content_type
);
740 void stat_cache_delete_entry(server
*srv
, const char *name
, size_t len
)
742 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_NONE
) return;
743 force_assert(0 != len
);
744 if (name
[len
-1] == '/') { if (0 == --len
) len
= 1; }
745 splay_tree
**sptree
= &srv
->stat_cache
->files
;
746 stat_cache_entry
*sce
= stat_cache_sptree_find(sptree
, name
, len
);
747 if (sce
&& buffer_is_equal_string(sce
->name
, name
, len
)) {
748 stat_cache_entry_free(sce
);
749 *sptree
= splaytree_delete(*sptree
, (*sptree
)->key
);
755 static void stat_cache_invalidate_entry(server
*srv
, const char *name
, size_t len
)
757 splay_tree
**sptree
= &srv
->stat_cache
->files
;
758 stat_cache_entry
*sce
= stat_cache_sptree_find(sptree
, name
, len
);
759 if (sce
&& buffer_is_equal_string(sce
->name
, name
, len
)) {
762 if (sce
->fam_dir
!= NULL
) {
763 --((fam_dir_entry
*)sce
->fam_dir
)->refcnt
;
770 static void stat_cache_invalidate_dir_tree_walk(splay_tree
*t
,
771 const char *name
, size_t len
)
773 if (t
->left
) stat_cache_invalidate_dir_tree_walk(t
->left
, name
, len
);
774 if (t
->right
) stat_cache_invalidate_dir_tree_walk(t
->right
, name
, len
);
776 buffer
*b
= ((stat_cache_entry
*)t
->data
)->name
;
777 size_t blen
= buffer_string_length(b
);
778 if (blen
> len
&& b
->ptr
[len
] == '/' && 0 == memcmp(b
->ptr
, name
, len
)) {
779 stat_cache_entry
*sce
= t
->data
;
782 if (sce
->fam_dir
!= NULL
) {
783 --((fam_dir_entry
*)sce
->fam_dir
)->refcnt
;
790 static void stat_cache_invalidate_dir_tree(server
*srv
,
791 const char *name
, size_t len
)
793 splay_tree
*sptree
= srv
->stat_cache
->files
;
794 if (sptree
) stat_cache_invalidate_dir_tree_walk(sptree
, name
, len
);
800 * walk though splay_tree and collect contents of dir tree.
801 * remove tagged entries in a second loop
804 static void stat_cache_tag_dir_tree(splay_tree
*t
, const char *name
, size_t len
,
807 if (*ndx
== 8192) return; /*(must match num array entries in keys[])*/
808 if (t
->left
) stat_cache_tag_dir_tree(t
->left
, name
, len
, keys
, ndx
);
809 if (t
->right
) stat_cache_tag_dir_tree(t
->right
, name
, len
, keys
, ndx
);
810 if (*ndx
== 8192) return; /*(must match num array entries in keys[])*/
812 buffer
*b
= ((stat_cache_entry
*)t
->data
)->name
;
813 size_t blen
= buffer_string_length(b
);
814 if (blen
> len
&& b
->ptr
[len
] == '/' && 0 == memcmp(b
->ptr
, name
, len
))
815 keys
[(*ndx
)++] = t
->key
;
818 static void stat_cache_prune_dir_tree(stat_cache
* const sc
,
819 const char *name
, size_t len
)
822 int keys
[8192]; /* 32k size on stack */
824 if (!sc
->files
) return;
826 stat_cache_tag_dir_tree(sc
->files
, name
, len
, keys
, &max_ndx
);
827 for (i
= 0; i
< max_ndx
; ++i
) {
828 const int ndx
= keys
[i
];
829 splay_tree
*node
= sc
->files
= splaytree_splay(sc
->files
, ndx
);
830 if (node
&& node
->key
== ndx
) {
831 stat_cache_entry_free(node
->data
);
832 sc
->files
= splaytree_delete(sc
->files
, ndx
);
835 } while (max_ndx
== sizeof(keys
)/sizeof(int));
838 static void stat_cache_delete_tree(server
*srv
, const char *name
, size_t len
)
840 stat_cache_delete_entry(srv
, name
, len
);
841 stat_cache_prune_dir_tree(srv
->stat_cache
, name
, len
);
844 void stat_cache_delete_dir(server
*srv
, const char *name
, size_t len
)
846 force_assert(0 != len
);
847 if (name
[len
-1] == '/') { if (0 == --len
) len
= 1; }
848 stat_cache_delete_tree(srv
, name
, len
);
850 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
851 fam_dir_entry
*fam_dir
=
852 stat_cache_sptree_find(&srv
->stat_cache
->scf
->dirs
, name
, len
);
853 if (fam_dir
&& buffer_is_equal_string(fam_dir
->name
, name
, len
))
854 fam_dir_invalidate_node(fam_dir
);
855 fam_dir_invalidate_tree(srv
->stat_cache
->scf
->dirs
, name
, len
);
856 fam_dir_periodic_cleanup(srv
);
866 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
867 * - HANDLER_ERROR on stat() failed -> see errno for problem
870 handler_t
stat_cache_get_entry(server
*srv
, connection
*con
, buffer
*name
, stat_cache_entry
**ret_sce
) {
871 stat_cache_entry
*sce
= NULL
;
879 /* consistency: ensure lookup name does not end in '/' unless root "/"
880 * (but use full path given with stat(), even with trailing '/') */
882 size_t len
= buffer_string_length(name
);
883 force_assert(0 != len
);
884 if (name
->ptr
[len
-1] == '/') { final_slash
= 1; if (0 == --len
) len
= 1; }
885 /* Note: paths are expected to be normalized before calling stat_cache,
886 * e.g. without repeated '/' */
888 if (name
->ptr
[0] != '/') return HANDLER_ERROR
;
891 * check if the directory for this file has changed
894 sc
= srv
->stat_cache
;
896 file_ndx
= hashme(name
->ptr
, len
);
897 sc
->files
= splaytree_splay(sc
->files
, file_ndx
);
899 if (sc
->files
&& (sc
->files
->key
== file_ndx
)) {
900 /* we have seen this file already and
901 * don't stat() it again in the same second */
903 sce
= sc
->files
->data
;
905 /* check if the name is the same, we might have a collision */
907 if (buffer_is_equal_string(sce
->name
, name
->ptr
, len
)) {
908 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_SIMPLE
) {
909 if (sce
->stat_ts
== srv
->cur_ts
) {
910 if (final_slash
&& !S_ISDIR(sce
->st
.st_mode
)) {
912 return HANDLER_ERROR
;
915 return HANDLER_GO_ON
;
919 else if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
920 && sce
->fam_dir
) { /* entry is in monitored dir */
921 /* re-stat() periodically, even if monitoring for changes
922 * (due to limitations in stat_cache.c use of FAM)
923 * (gaps due to not continually monitoring an entire tree) */
924 if (srv
->cur_ts
- sce
->stat_ts
< 16) {
925 if (final_slash
&& !S_ISDIR(sce
->st
.st_mode
)) {
927 return HANDLER_ERROR
;
930 return HANDLER_GO_ON
;
935 /* collision, forget about the entry */
940 if (-1 == stat(name
->ptr
, &st
)) {
941 return HANDLER_ERROR
;
944 if (S_ISREG(st
.st_mode
)) {
945 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
946 if (name
->ptr
[buffer_string_length(name
) - 1] == '/') {
948 return HANDLER_ERROR
;
954 sce
= stat_cache_entry_init();
955 buffer_copy_string_len(sce
->name
, name
->ptr
, len
);
957 /* already splayed file_ndx */
958 if ((NULL
!= sc
->files
) && (sc
->files
->key
== file_ndx
)) {
959 /* hash collision: replace old entry */
960 stat_cache_entry_free(sc
->files
->data
);
961 sc
->files
->data
= sce
;
963 sc
->files
= splaytree_insert(sc
->files
, file_ndx
, sce
);
968 buffer_clear(sce
->etag
);
969 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR)
970 buffer_clear(sce
->content_type
);
975 sce
->st
= st
; /*(copy prior to calling fam_dir_monitor())*/
978 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
979 if (sce
->fam_dir
) --((fam_dir_entry
*)sce
->fam_dir
)->refcnt
;
981 fam_dir_monitor(srv
, sc
->scf
, CONST_BUF_LEN(name
), &st
);
982 #if 0 /*(performed below)*/
983 if (NULL
!= sce
->fam_dir
) {
984 /*(may have been invalidated by dir change)*/
985 sce
->stat_ts
= srv
->cur_ts
;
991 sce
->stat_ts
= srv
->cur_ts
;
994 return HANDLER_GO_ON
;
997 int stat_cache_path_contains_symlink(server
*srv
, buffer
*name
) {
998 /* caller should check for symlinks only if we should block symlinks. */
1000 /* catch the obvious symlinks
1002 * this is not a secure check as we still have a race-condition between
1003 * the stat() and the open. We can only solve this by
1004 * 1. open() the file
1007 * and keeping the file open for the rest of the time. But this can
1008 * only be done at network level.
1012 /* we assume "/" can not be symlink,
1013 * so skip the symlink stuff if path is "/" */
1014 size_t len
= buffer_string_length(name
);
1015 force_assert(0 != len
);
1016 force_assert(name
->ptr
[0] == '/');
1017 if (1 == len
) return 0;
1019 #define PATH_MAX 4096
1021 if (len
>= PATH_MAX
) return -1;
1024 memcpy(buf
, name
->ptr
, len
);
1025 char *s_cur
= buf
+len
;
1029 if (0 == lstat(buf
, &st
)) {
1030 if (S_ISLNK(st
.st_mode
)) return 1;
1033 log_error_write(srv
, __FILE__
, __LINE__
, "sss",
1034 "lstat failed for:", buf
, strerror(errno
));
1037 } while ((s_cur
= strrchr(buf
, '/')) != buf
);
1043 int stat_cache_open_rdonly_fstat (buffer
*name
, struct stat
*st
, int symlinks
) {
1044 /*(Note: O_NOFOLLOW affects only the final path segment, the target file,
1045 * not any intermediate symlinks along the path)*/
1046 const int fd
= fdevent_open_cloexec(name
->ptr
, symlinks
, O_RDONLY
, 0);
1048 if (0 == fstat(fd
, st
)) {
1058 * remove stat() from cache which haven't been stat()ed for
1059 * more than 2 seconds
1062 * walk though the stat-cache, collect the ids which are too old
1063 * and remove them in a second loop
1066 static int stat_cache_tag_old_entries(server
*srv
, splay_tree
*t
, int *keys
, size_t *ndx
, time_t max_age
) {
1067 stat_cache_entry
*sce
;
1071 stat_cache_tag_old_entries(srv
, t
->left
, keys
, ndx
, max_age
);
1072 stat_cache_tag_old_entries(srv
, t
->right
, keys
, ndx
, max_age
);
1076 if (srv
->cur_ts
- sce
->stat_ts
> max_age
) {
1077 keys
[(*ndx
)++] = t
->key
;
1083 static int stat_cache_periodic_cleanup(server
*srv
, time_t max_age
) {
1085 size_t max_ndx
= 0, i
;
1088 sc
= srv
->stat_cache
;
1090 if (!sc
->files
) return 0;
1092 keys
= calloc(1, sizeof(int) * sc
->files
->size
);
1093 force_assert(NULL
!= keys
);
1095 stat_cache_tag_old_entries(srv
, sc
->files
, keys
, &max_ndx
, max_age
);
1097 for (i
= 0; i
< max_ndx
; i
++) {
1101 sc
->files
= splaytree_splay(sc
->files
, ndx
);
1105 if (node
&& (node
->key
== ndx
)) {
1106 stat_cache_entry_free(node
->data
);
1107 sc
->files
= splaytree_delete(sc
->files
, ndx
);
1116 int stat_cache_trigger_cleanup(server
*srv
) {
1120 if (srv
->srvconf
.stat_cache_engine
== STAT_CACHE_ENGINE_FAM
) {
1121 if (srv
->cur_ts
& 0x1F) return 0;
1122 /* once every 32 seconds (0x1F == 31) */
1124 fam_dir_periodic_cleanup(srv
);
1125 /* By doing this before stat_cache_periodic_cleanup(),
1126 * entries used within the next max_age secs will remain
1127 * monitored, instead of effectively flushing and
1128 * rebuilding the FAM monitoring every max_age seconds */
1132 stat_cache_periodic_cleanup(srv
, max_age
);