Annotation of embedaddon/lighttpd/src/stat_cache.c, revision 1.1.1.1
1.1 misho 1: #include "log.h"
2: #include "stat_cache.h"
3: #include "fdevent.h"
4: #include "etag.h"
5:
6: #include <sys/types.h>
7: #include <sys/stat.h>
8:
9: #include <stdlib.h>
10: #include <string.h>
11: #include <errno.h>
12: #include <unistd.h>
13: #include <stdio.h>
14: #include <fcntl.h>
15: #include <assert.h>
16:
17: #ifdef HAVE_ATTR_ATTRIBUTES_H
18: # include <attr/attributes.h>
19: #endif
20:
21: #ifdef HAVE_FAM_H
22: # include <fam.h>
23: #endif
24:
25: #include "sys-mmap.h"
26:
27: /* NetBSD 1.3.x needs it */
28: #ifndef MAP_FAILED
29: # define MAP_FAILED -1
30: #endif
31:
32: #ifndef O_LARGEFILE
33: # define O_LARGEFILE 0
34: #endif
35:
36: #ifndef HAVE_LSTAT
37: # define lstat stat
38: #endif
39:
40: #if 0
41: /* enables debug code for testing if all nodes in the stat-cache as accessable */
42: #define DEBUG_STAT_CACHE
43: #endif
44:
45: /*
46: * stat-cache
47: *
48: * we cache the stat() calls in our own storage
49: * the directories are cached in FAM
50: *
51: * if we get a change-event from FAM, we increment the version in the FAM->dir mapping
52: *
53: * if the stat()-cache is queried we check if the version id for the directory is the
54: * same and return immediatly.
55: *
56: *
57: * What we need:
58: *
59: * - for each stat-cache entry we need a fast indirect lookup on the directory name
60: * - for each FAMRequest we have to find the version in the directory cache (index as userdata)
61: *
62: * stat <<-> directory <-> FAMRequest
63: *
64: * if file is deleted, directory is dirty, file is rechecked ...
65: * if directory is deleted, directory mapping is removed
66: *
67: * */
68:
69: #ifdef HAVE_FAM_H
70: typedef struct {
71: FAMRequest *req;
72: FAMConnection *fc;
73:
74: buffer *name;
75:
76: int version;
77: } fam_dir_entry;
78: #endif
79:
80: /* the directory name is too long to always compare on it
81: * - we need a hash
82: * - the hash-key is used as sorting criteria for a tree
83: * - a splay-tree is used as we can use the caching effect of it
84: */
85:
86: /* we want to cleanup the stat-cache every few seconds, let's say 10
87: *
88: * - remove entries which are outdated since 30s
89: * - remove entries which are fresh but havn't been used since 60s
90: * - if we don't have a stat-cache entry for a directory, release it from the monitor
91: */
92:
93: #ifdef DEBUG_STAT_CACHE
94: typedef struct {
95: int *ptr;
96:
97: size_t used;
98: size_t size;
99: } fake_keys;
100:
101: static fake_keys ctrl;
102: #endif
103:
104: stat_cache *stat_cache_init(void) {
105: stat_cache *fc = NULL;
106:
107: fc = calloc(1, sizeof(*fc));
108:
109: fc->dir_name = buffer_init();
110: fc->hash_key = buffer_init();
111: #ifdef HAVE_FAM_H
112: fc->fam = calloc(1, sizeof(*fc->fam));
113: #endif
114:
115: #ifdef DEBUG_STAT_CACHE
116: ctrl.size = 0;
117: #endif
118:
119: return fc;
120: }
121:
122: static stat_cache_entry * stat_cache_entry_init(void) {
123: stat_cache_entry *sce = NULL;
124:
125: sce = calloc(1, sizeof(*sce));
126:
127: sce->name = buffer_init();
128: sce->etag = buffer_init();
129: sce->content_type = buffer_init();
130:
131: return sce;
132: }
133:
134: static void stat_cache_entry_free(void *data) {
135: stat_cache_entry *sce = data;
136: if (!sce) return;
137:
138: buffer_free(sce->etag);
139: buffer_free(sce->name);
140: buffer_free(sce->content_type);
141:
142: free(sce);
143: }
144:
145: #ifdef HAVE_FAM_H
146: static fam_dir_entry * fam_dir_entry_init(void) {
147: fam_dir_entry *fam_dir = NULL;
148:
149: fam_dir = calloc(1, sizeof(*fam_dir));
150:
151: fam_dir->name = buffer_init();
152:
153: return fam_dir;
154: }
155:
156: static void fam_dir_entry_free(void *data) {
157: fam_dir_entry *fam_dir = data;
158:
159: if (!fam_dir) return;
160:
161: FAMCancelMonitor(fam_dir->fc, fam_dir->req);
162:
163: buffer_free(fam_dir->name);
164: free(fam_dir->req);
165:
166: free(fam_dir);
167: }
168: #endif
169:
170: void stat_cache_free(stat_cache *sc) {
171: while (sc->files) {
172: int osize;
173: splay_tree *node = sc->files;
174:
175: osize = sc->files->size;
176:
177: stat_cache_entry_free(node->data);
178: sc->files = splaytree_delete(sc->files, node->key);
179:
180: assert(osize - 1 == splaytree_size(sc->files));
181: }
182:
183: buffer_free(sc->dir_name);
184: buffer_free(sc->hash_key);
185:
186: #ifdef HAVE_FAM_H
187: while (sc->dirs) {
188: int osize;
189: splay_tree *node = sc->dirs;
190:
191: osize = sc->dirs->size;
192:
193: fam_dir_entry_free(node->data);
194: sc->dirs = splaytree_delete(sc->dirs, node->key);
195:
196: if (osize == 1) {
197: assert(NULL == sc->dirs);
198: } else {
199: assert(osize == (sc->dirs->size + 1));
200: }
201: }
202:
203: if (sc->fam) {
204: FAMClose(sc->fam);
205: free(sc->fam);
206: }
207: #endif
208: free(sc);
209: }
210:
211: #ifdef HAVE_XATTR
212: static int stat_cache_attr_get(buffer *buf, char *name) {
213: int attrlen;
214: int ret;
215:
216: attrlen = 1024;
217: buffer_prepare_copy(buf, attrlen);
218: attrlen--;
219: if(0 == (ret = attr_get(name, "Content-Type", buf->ptr, &attrlen, 0))) {
220: buf->used = attrlen + 1;
221: buf->ptr[attrlen] = '\0';
222: }
223: return ret;
224: }
225: #endif
226:
227: /* the famous DJB hash function for strings */
228: static uint32_t hashme(buffer *str) {
229: uint32_t hash = 5381;
230: const char *s;
231: for (s = str->ptr; *s; s++) {
232: hash = ((hash << 5) + hash) + *s;
233: }
234:
235: hash &= ~(1 << 31); /* strip the highest bit */
236:
237: return hash;
238: }
239:
240: #ifdef HAVE_FAM_H
241: handler_t stat_cache_handle_fdevent(server *srv, void *_fce, int revent) {
242: size_t i;
243: stat_cache *sc = srv->stat_cache;
244: size_t events;
245:
246: UNUSED(_fce);
247: /* */
248:
249: if ((revent & FDEVENT_IN) &&
250: sc->fam) {
251:
252: events = FAMPending(sc->fam);
253:
254: for (i = 0; i < events; i++) {
255: FAMEvent fe;
256: fam_dir_entry *fam_dir;
257: splay_tree *node;
258: int ndx, j;
259:
260: FAMNextEvent(sc->fam, &fe);
261:
262: /* handle event */
263:
264: switch(fe.code) {
265: case FAMChanged:
266: case FAMDeleted:
267: case FAMMoved:
268: /* if the filename is a directory remove the entry */
269:
270: fam_dir = fe.userdata;
271: fam_dir->version++;
272:
273: /* file/dir is still here */
274: if (fe.code == FAMChanged) break;
275:
276: /* we have 2 versions, follow and no-follow-symlink */
277:
278: for (j = 0; j < 2; j++) {
279: buffer_copy_string(sc->hash_key, fe.filename);
280: buffer_append_long(sc->hash_key, j);
281:
282: ndx = hashme(sc->hash_key);
283:
284: sc->dirs = splaytree_splay(sc->dirs, ndx);
285: node = sc->dirs;
286:
287: if (node && (node->key == ndx)) {
288: int osize = splaytree_size(sc->dirs);
289:
290: fam_dir_entry_free(node->data);
291: sc->dirs = splaytree_delete(sc->dirs, ndx);
292:
293: assert(osize - 1 == splaytree_size(sc->dirs));
294: }
295: }
296: break;
297: default:
298: break;
299: }
300: }
301: }
302:
303: if (revent & FDEVENT_HUP) {
304: /* fam closed the connection */
305: srv->stat_cache->fam_fcce_ndx = -1;
306:
307: fdevent_event_del(srv->ev, &(sc->fam_fcce_ndx), FAMCONNECTION_GETFD(sc->fam));
308: fdevent_unregister(srv->ev, FAMCONNECTION_GETFD(sc->fam));
309:
310: FAMClose(sc->fam);
311: free(sc->fam);
312:
313: sc->fam = NULL;
314: }
315:
316: return HANDLER_GO_ON;
317: }
318:
319: static int buffer_copy_dirname(buffer *dst, buffer *file) {
320: size_t i;
321:
322: if (buffer_is_empty(file)) return -1;
323:
324: for (i = file->used - 1; i+1 > 0; i--) {
325: if (file->ptr[i] == '/') {
326: buffer_copy_string_len(dst, file->ptr, i);
327: return 0;
328: }
329: }
330:
331: return -1;
332: }
333: #endif
334:
335: #ifdef HAVE_LSTAT
336: static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) {
337: if (lstat(dname->ptr, lst) == 0) {
338: return S_ISLNK(lst->st_mode) ? 0 : 1;
339: }
340: else {
341: log_error_write(srv, __FILE__, __LINE__, "sbs",
342: "lstat failed for:",
343: dname, strerror(errno));
344: };
345: return -1;
346: }
347: #endif
348:
349: /***
350: *
351: *
352: *
353: * returns:
354: * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file)
355: * - HANDLER_ERROR on stat() failed -> see errno for problem
356: */
357:
358: handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) {
359: #ifdef HAVE_FAM_H
360: fam_dir_entry *fam_dir = NULL;
361: int dir_ndx = -1;
362: splay_tree *dir_node = NULL;
363: #endif
364: stat_cache_entry *sce = NULL;
365: stat_cache *sc;
366: struct stat st;
367: size_t k;
368: int fd;
369: struct stat lst;
370: #ifdef DEBUG_STAT_CACHE
371: size_t i;
372: #endif
373:
374: int file_ndx;
375: splay_tree *file_node = NULL;
376:
377: *ret_sce = NULL;
378:
379: /*
380: * check if the directory for this file has changed
381: */
382:
383: sc = srv->stat_cache;
384:
385: buffer_copy_string_buffer(sc->hash_key, name);
386: buffer_append_long(sc->hash_key, con->conf.follow_symlink);
387:
388: file_ndx = hashme(sc->hash_key);
389: sc->files = splaytree_splay(sc->files, file_ndx);
390:
391: #ifdef DEBUG_STAT_CACHE
392: for (i = 0; i < ctrl.used; i++) {
393: if (ctrl.ptr[i] == file_ndx) break;
394: }
395: #endif
396:
397: if (sc->files && (sc->files->key == file_ndx)) {
398: #ifdef DEBUG_STAT_CACHE
399: /* it was in the cache */
400: assert(i < ctrl.used);
401: #endif
402:
403: /* we have seen this file already and
404: * don't stat() it again in the same second */
405:
406: file_node = sc->files;
407:
408: sce = file_node->data;
409:
410: /* check if the name is the same, we might have a collision */
411:
412: if (buffer_is_equal(name, sce->name)) {
413: if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) {
414: if (sce->stat_ts == srv->cur_ts) {
415: *ret_sce = sce;
416: return HANDLER_GO_ON;
417: }
418: }
419: } else {
420: /* oops, a collision,
421: *
422: * file_node is used by the FAM check below to see if we know this file
423: * and if we can save a stat().
424: *
425: * BUT, the sce is not reset here as the entry into the cache is ok, we
426: * it is just not pointing to our requested file.
427: *
428: * */
429:
430: file_node = NULL;
431: }
432: } else {
433: #ifdef DEBUG_STAT_CACHE
434: if (i != ctrl.used) {
435: log_error_write(srv, __FILE__, __LINE__, "xSB",
436: file_ndx, "was already inserted but not found in cache, ", name);
437: }
438: assert(i == ctrl.used);
439: #endif
440: }
441:
442: #ifdef HAVE_FAM_H
443: /* dir-check */
444: if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) {
445: if (0 != buffer_copy_dirname(sc->dir_name, name)) {
446: log_error_write(srv, __FILE__, __LINE__, "sb",
447: "no '/' found in filename:", name);
448: return HANDLER_ERROR;
449: }
450:
451: buffer_copy_string_buffer(sc->hash_key, sc->dir_name);
452: buffer_append_long(sc->hash_key, con->conf.follow_symlink);
453:
454: dir_ndx = hashme(sc->hash_key);
455:
456: sc->dirs = splaytree_splay(sc->dirs, dir_ndx);
457:
458: if (sc->dirs && (sc->dirs->key == dir_ndx)) {
459: dir_node = sc->dirs;
460: }
461:
462: if (dir_node && file_node) {
463: /* we found a file */
464:
465: sce = file_node->data;
466: fam_dir = dir_node->data;
467:
468: if (fam_dir->version == sce->dir_version) {
469: /* the stat()-cache entry is still ok */
470:
471: *ret_sce = sce;
472: return HANDLER_GO_ON;
473: }
474: }
475: }
476: #endif
477:
478: /*
479: * *lol*
480: * - open() + fstat() on a named-pipe results in a (intended) hang.
481: * - stat() if regular file + open() to see if we can read from it is better
482: *
483: * */
484: if (-1 == stat(name->ptr, &st)) {
485: return HANDLER_ERROR;
486: }
487:
488:
489: if (S_ISREG(st.st_mode)) {
490: /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */
491: if (name->ptr[name->used-2] == '/') {
492: errno = ENOTDIR;
493: return HANDLER_ERROR;
494: }
495:
496: /* try to open the file to check if we can read it */
497: if (-1 == (fd = open(name->ptr, O_RDONLY))) {
498: return HANDLER_ERROR;
499: }
500: close(fd);
501: }
502:
503: if (NULL == sce) {
504: #ifdef DEBUG_STAT_CACHE
505: int osize = splaytree_size(sc->files);
506: #endif
507:
508: sce = stat_cache_entry_init();
509: buffer_copy_string_buffer(sce->name, name);
510:
511: sc->files = splaytree_insert(sc->files, file_ndx, sce);
512: #ifdef DEBUG_STAT_CACHE
513: if (ctrl.size == 0) {
514: ctrl.size = 16;
515: ctrl.used = 0;
516: ctrl.ptr = malloc(ctrl.size * sizeof(*ctrl.ptr));
517: } else if (ctrl.size == ctrl.used) {
518: ctrl.size += 16;
519: ctrl.ptr = realloc(ctrl.ptr, ctrl.size * sizeof(*ctrl.ptr));
520: }
521:
522: ctrl.ptr[ctrl.used++] = file_ndx;
523:
524: assert(sc->files);
525: assert(sc->files->data == sce);
526: assert(osize + 1 == splaytree_size(sc->files));
527: #endif
528: }
529:
530: sce->st = st;
531: sce->stat_ts = srv->cur_ts;
532:
533: /* catch the obvious symlinks
534: *
535: * this is not a secure check as we still have a race-condition between
536: * the stat() and the open. We can only solve this by
537: * 1. open() the file
538: * 2. fstat() the fd
539: *
540: * and keeping the file open for the rest of the time. But this can
541: * only be done at network level.
542: *
543: * per default it is not a symlink
544: * */
545: #ifdef HAVE_LSTAT
546: sce->is_symlink = 0;
547:
548: /* we want to only check for symlinks if we should block symlinks.
549: */
550: if (!con->conf.follow_symlink) {
551: if (stat_cache_lstat(srv, name, &lst) == 0) {
552: #ifdef DEBUG_STAT_CACHE
553: log_error_write(srv, __FILE__, __LINE__, "sb",
554: "found symlink", name);
555: #endif
556: sce->is_symlink = 1;
557: }
558:
559: /*
560: * we assume "/" can not be symlink, so
561: * skip the symlink stuff if our path is /
562: **/
563: else if ((name->used > 2)) {
564: buffer *dname;
565: char *s_cur;
566:
567: dname = buffer_init();
568: buffer_copy_string_buffer(dname, name);
569:
570: while ((s_cur = strrchr(dname->ptr,'/'))) {
571: *s_cur = '\0';
572: dname->used = s_cur - dname->ptr + 1;
573: if (dname->ptr == s_cur) {
574: #ifdef DEBUG_STAT_CACHE
575: log_error_write(srv, __FILE__, __LINE__, "s", "reached /");
576: #endif
577: break;
578: }
579: #ifdef DEBUG_STAT_CACHE
580: log_error_write(srv, __FILE__, __LINE__, "sbs",
581: "checking if", dname, "is a symlink");
582: #endif
583: if (stat_cache_lstat(srv, dname, &lst) == 0) {
584: sce->is_symlink = 1;
585: #ifdef DEBUG_STAT_CACHE
586: log_error_write(srv, __FILE__, __LINE__, "sb",
587: "found symlink", dname);
588: #endif
589: break;
590: };
591: };
592: buffer_free(dname);
593: };
594: };
595: #endif
596:
597: if (S_ISREG(st.st_mode)) {
598: /* determine mimetype */
599: buffer_reset(sce->content_type);
600: #ifdef HAVE_XATTR
601: if (con->conf.use_xattr) {
602: stat_cache_attr_get(sce->content_type, name->ptr);
603: }
604: #endif
605: /* xattr did not set a content-type. ask the config */
606: if (buffer_is_empty(sce->content_type)) {
607: for (k = 0; k < con->conf.mimetypes->used; k++) {
608: data_string *ds = (data_string *)con->conf.mimetypes->data[k];
609: buffer *type = ds->key;
610:
611: if (type->used == 0) continue;
612:
613: /* check if the right side is the same */
614: if (type->used > name->used) continue;
615:
616: if (0 == strncasecmp(name->ptr + name->used - type->used, type->ptr, type->used - 1)) {
617: buffer_copy_string_buffer(sce->content_type, ds->value);
618: break;
619: }
620: }
621: }
622: etag_create(sce->etag, &(sce->st), con->etag_flags);
623: } else if (S_ISDIR(st.st_mode)) {
624: etag_create(sce->etag, &(sce->st), con->etag_flags);
625: }
626:
627: #ifdef HAVE_FAM_H
628: if (sc->fam &&
629: (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM)) {
630: /* is this directory already registered ? */
631: if (!dir_node) {
632: fam_dir = fam_dir_entry_init();
633: fam_dir->fc = sc->fam;
634:
635: buffer_copy_string_buffer(fam_dir->name, sc->dir_name);
636:
637: fam_dir->version = 1;
638:
639: fam_dir->req = calloc(1, sizeof(FAMRequest));
640:
641: if (0 != FAMMonitorDirectory(sc->fam, fam_dir->name->ptr,
642: fam_dir->req, fam_dir)) {
643:
644: log_error_write(srv, __FILE__, __LINE__, "sbsbs",
645: "monitoring dir failed:",
646: fam_dir->name,
647: "file:", name,
648: FamErrlist[FAMErrno]);
649:
650: fam_dir_entry_free(fam_dir);
651: } else {
652: int osize = 0;
653:
654: if (sc->dirs) {
655: osize = sc->dirs->size;
656: }
657:
658: sc->dirs = splaytree_insert(sc->dirs, dir_ndx, fam_dir);
659: assert(sc->dirs);
660: assert(sc->dirs->data == fam_dir);
661: assert(osize == (sc->dirs->size - 1));
662: }
663: } else {
664: fam_dir = dir_node->data;
665: }
666:
667: /* bind the fam_fc to the stat() cache entry */
668:
669: if (fam_dir) {
670: sce->dir_version = fam_dir->version;
671: sce->dir_ndx = dir_ndx;
672: }
673: }
674: #endif
675:
676: *ret_sce = sce;
677:
678: return HANDLER_GO_ON;
679: }
680:
681: /**
682: * remove stat() from cache which havn't been stat()ed for
683: * more than 10 seconds
684: *
685: *
686: * walk though the stat-cache, collect the ids which are too old
687: * and remove them in a second loop
688: */
689:
690: static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) {
691: stat_cache_entry *sce;
692:
693: if (!t) return 0;
694:
695: stat_cache_tag_old_entries(srv, t->left, keys, ndx);
696: stat_cache_tag_old_entries(srv, t->right, keys, ndx);
697:
698: sce = t->data;
699:
700: if (srv->cur_ts - sce->stat_ts > 2) {
701: keys[(*ndx)++] = t->key;
702: }
703:
704: return 0;
705: }
706:
707: int stat_cache_trigger_cleanup(server *srv) {
708: stat_cache *sc;
709: size_t max_ndx = 0, i;
710: int *keys;
711:
712: sc = srv->stat_cache;
713:
714: if (!sc->files) return 0;
715:
716: keys = calloc(1, sizeof(size_t) * sc->files->size);
717:
718: stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx);
719:
720: for (i = 0; i < max_ndx; i++) {
721: int ndx = keys[i];
722: splay_tree *node;
723:
724: sc->files = splaytree_splay(sc->files, ndx);
725:
726: node = sc->files;
727:
728: if (node && (node->key == ndx)) {
729: #ifdef DEBUG_STAT_CACHE
730: size_t j;
731: int osize = splaytree_size(sc->files);
732: stat_cache_entry *sce = node->data;
733: #endif
734: stat_cache_entry_free(node->data);
735: sc->files = splaytree_delete(sc->files, ndx);
736:
737: #ifdef DEBUG_STAT_CACHE
738: for (j = 0; j < ctrl.used; j++) {
739: if (ctrl.ptr[j] == ndx) {
740: ctrl.ptr[j] = ctrl.ptr[--ctrl.used];
741: break;
742: }
743: }
744:
745: assert(osize - 1 == splaytree_size(sc->files));
746: #endif
747: }
748: }
749:
750: free(keys);
751:
752: return 0;
753: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>