Statistics
| Revision:

root / branches / lighttpd-1.4.x / src / stat_cache.c @ 1874

History | View | Annotate | Download (15.6 KB)

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