1 #include "first.h" 2 3 #include "stat_cache.h" 4 #include "base.h" 5 #include "log.h" 6 #include "fdevent.h" 7 #include "etag.h" 8 #include "splaytree.h" 9 10 #include <sys/types.h> 11 #include <sys/stat.h> 12 13 #include <stdlib.h> 14 #include <string.h> 15 #include <errno.h> 16 #include <unistd.h> 17 #include <fcntl.h> 18 19 #ifdef HAVE_ATTR_ATTRIBUTES_H 20 # include <attr/attributes.h> 21 #endif 22 23 #ifdef HAVE_SYS_EXTATTR_H 24 # include <sys/extattr.h> 25 #endif 26 27 #ifndef HAVE_LSTAT 28 # define lstat stat 29 #endif 30 31 /* 32 * stat-cache 33 * 34 * we cache the stat() calls in our own storage 35 * the directories are cached in FAM 36 * 37 * if we get a change-event from FAM, we increment the version in the FAM->dir mapping 38 * 39 * if the stat()-cache is queried we check if the version id for the directory is the 40 * same and return immediatly. 41 * 42 * 43 * What we need: 44 * 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) 47 * 48 * stat <<-> directory <-> FAMRequest 49 * 50 * if file is deleted, directory is dirty, file is rechecked ... 51 * if directory is deleted, directory mapping is removed 52 * 53 * */ 54 55 /* the directory name is too long to always compare on it 56 * - we need a hash 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 59 */ 60 61 /* we want to cleanup the stat-cache every few seconds, let's say 10 62 * 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 66 */ 67 68 69 enum { 70 STAT_CACHE_ENGINE_UNSET, 71 STAT_CACHE_ENGINE_NONE, 72 STAT_CACHE_ENGINE_SIMPLE, 73 STAT_CACHE_ENGINE_FAM 74 }; 75 76 #ifdef HAVE_FAM_H 77 struct stat_cache_fam; 78 #endif 79 80 typedef struct stat_cache { 81 splay_tree *files; /* the nodes of the tree are stat_cache_entry's */ 82 #ifdef HAVE_FAM_H 83 struct stat_cache_fam *scf; 84 #endif 85 } stat_cache; 86 87 88 /* the famous DJB hash function for strings */ 89 static uint32_t hashme(const char *str, int z) { 90 uint32_t hash = 5381; 91 for (const unsigned char *s = (const unsigned char *)str; *s; ++s) { 92 hash = ((hash << 5) + hash) ^ *s; 93 } 94 95 /* customizations */ 96 97 /* (differentiate hash values with and w/o con->conf.follow_symlink) */ 98 hash = ((hash << 5) + hash) ^ (z ? '1' : '0'); 99 100 hash &= ~(((uint32_t)1) << 31); /* strip the highest bit */ 101 102 return hash; 103 } 104 105 106 #ifdef HAVE_FAM_H 107 108 #include <fam.h> 109 110 typedef struct { 111 FAMRequest *req; 112 buffer *name; 113 int version; 114 } fam_dir_entry; 115 116 typedef struct stat_cache_fam { 117 splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */ 118 119 FAMConnection fam; 120 121 int dir_ndx; 122 fam_dir_entry *fam_dir; 123 buffer *dir_name; /* for building the dirname from the filename */ 124 fdnode *fdn; 125 int fd; 126 } stat_cache_fam; 127 128 static fam_dir_entry * fam_dir_entry_init(void) { 129 fam_dir_entry *fam_dir = NULL; 130 131 fam_dir = calloc(1, sizeof(*fam_dir)); 132 force_assert(NULL != fam_dir); 133 134 fam_dir->name = buffer_init(); 135 136 return fam_dir; 137 } 138 139 static void fam_dir_entry_free(FAMConnection *fc, void *data) { 140 fam_dir_entry *fam_dir = data; 141 142 if (!fam_dir) return; 143 144 FAMCancelMonitor(fc, fam_dir->req); 145 146 buffer_free(fam_dir->name); 147 free(fam_dir->req); 148 149 free(fam_dir); 150 } 151 152 static handler_t stat_cache_handle_fdevent(server *srv, void *_fce, int revent) { 153 size_t i; 154 stat_cache_fam *scf = srv->stat_cache->scf; 155 size_t events; 156 157 UNUSED(_fce); 158 /* */ 159 160 if (revent & FDEVENT_IN) { 161 events = FAMPending(&scf->fam); 162 163 for (i = 0; i < events; i++) { 164 FAMEvent fe; 165 fam_dir_entry *fam_dir; 166 splay_tree *node; 167 int ndx, j; 168 169 FAMNextEvent(&scf->fam, &fe); 170 171 /* handle event */ 172 173 switch(fe.code) { 174 case FAMChanged: 175 case FAMDeleted: 176 case FAMMoved: 177 /* if the filename is a directory remove the entry */ 178 179 fam_dir = fe.userdata; 180 fam_dir->version++; 181 182 /* file/dir is still here */ 183 if (fe.code == FAMChanged) break; 184 185 /* we have 2 versions, follow and no-follow-symlink */ 186 187 for (j = 0; j < 2; j++) { 188 189 ndx = hashme(fe.filename, j); 190 191 scf->dirs = splaytree_splay(scf->dirs, ndx); 192 node = scf->dirs; 193 194 if (node && (node->key == ndx)) { 195 int osize = splaytree_size(scf->dirs); 196 197 fam_dir_entry_free(&scf->fam, node->data); 198 scf->dirs = splaytree_delete(scf->dirs, ndx); 199 200 force_assert(osize - 1 == splaytree_size(scf->dirs)); 201 } 202 } 203 break; 204 default: 205 break; 206 } 207 } 208 } 209 210 if (revent & (FDEVENT_HUP|FDEVENT_RDHUP)) { 211 /* fam closed the connection */ 212 fdevent_fdnode_event_del(srv->ev, scf->fdn); 213 fdevent_unregister(srv->ev, scf->fd); 214 scf->fdn = NULL; 215 216 FAMClose(&scf->fam); 217 scf->fd = -1; 218 } 219 220 return HANDLER_GO_ON; 221 } 222 223 static stat_cache_fam * stat_cache_init_fam(server *srv) { 224 stat_cache_fam *scf = calloc(1, sizeof(*scf)); 225 scf->dir_name = buffer_init(); 226 scf->fd = -1; 227 228 /* setup FAM */ 229 if (0 != FAMOpen2(&scf->fam, "lighttpd")) { 230 log_error_write(srv, __FILE__, __LINE__, "s", 231 "could not open a fam connection, dieing."); 232 return NULL; 233 } 234 #ifdef HAVE_FAMNOEXISTS 235 FAMNoExists(&scf->fam); 236 #endif 237 238 scf->fd = FAMCONNECTION_GETFD(&scf->fam); 239 fdevent_setfd_cloexec(scf->fd); 240 scf->fdn = fdevent_register(srv->ev, scf->fd, stat_cache_handle_fdevent, NULL); 241 fdevent_fdnode_event_set(srv->ev, scf->fdn, FDEVENT_IN | FDEVENT_RDHUP); 242 243 return scf; 244 } 245 246 static void stat_cache_free_fam(stat_cache_fam *scf) { 247 if (NULL == scf) return; 248 buffer_free(scf->dir_name); 249 250 while (scf->dirs) { 251 int osize; 252 splay_tree *node = scf->dirs; 253 254 osize = scf->dirs->size; 255 256 fam_dir_entry_free(&scf->fam, node->data); 257 scf->dirs = splaytree_delete(scf->dirs, node->key); 258 259 if (osize == 1) { 260 force_assert(NULL == scf->dirs); 261 } else { 262 force_assert(osize == (scf->dirs->size + 1)); 263 } 264 } 265 266 if (-1 != scf->fd) { 267 /*scf->fdn already cleaned up in fdevent_free()*/ 268 FAMClose(&scf->fam); 269 /*scf->fd = -1;*/ 270 } 271 272 free(scf); 273 } 274 275 static int buffer_copy_dirname(buffer *dst, const buffer *file) { 276 size_t i; 277 278 if (buffer_string_is_empty(file)) return -1; 279 280 for (i = buffer_string_length(file); i > 0; i--) { 281 if (file->ptr[i] == '/') { 282 buffer_copy_string_len(dst, file->ptr, i); 283 return 0; 284 } 285 } 286 287 return -1; 288 } 289 290 static handler_t stat_cache_fam_dir_check(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name, unsigned int follow_symlink) { 291 if (0 != buffer_copy_dirname(scf->dir_name, name)) { 292 log_error_write(srv, __FILE__, __LINE__, "sb", 293 "no '/' found in filename:", name); 294 return HANDLER_ERROR; 295 } 296 297 scf->dir_ndx = hashme(scf->dir_name->ptr, follow_symlink); 298 299 scf->dirs = splaytree_splay(scf->dirs, scf->dir_ndx); 300 301 if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) { 302 scf->fam_dir = scf->dirs->data; 303 304 /* check whether we got a collision */ 305 if (buffer_is_equal(scf->dir_name, scf->fam_dir->name)) { 306 /* test whether a found file cache entry is still ok */ 307 if ((NULL != sce) && (scf->fam_dir->version == sce->dir_version)) { 308 /* the stat()-cache entry is still ok */ 309 return HANDLER_FINISHED; 310 } 311 } else { 312 /* hash collision, forget about the entry */ 313 scf->fam_dir = NULL; 314 } 315 } else { 316 scf->fam_dir = NULL; 317 } 318 319 return HANDLER_GO_ON; 320 } 321 322 static void stat_cache_fam_dir_monitor(server *srv, stat_cache_fam *scf, stat_cache_entry *sce, const buffer *name) { 323 /* is this directory already registered ? */ 324 fam_dir_entry *fam_dir = scf->fam_dir; 325 if (NULL == fam_dir) { 326 fam_dir = fam_dir_entry_init(); 327 328 buffer_copy_buffer(fam_dir->name, scf->dir_name); 329 330 fam_dir->version = 1; 331 332 fam_dir->req = calloc(1, sizeof(FAMRequest)); 333 force_assert(NULL != fam_dir); 334 335 if (0 != FAMMonitorDirectory(&scf->fam, fam_dir->name->ptr, 336 fam_dir->req, fam_dir)) { 337 338 log_error_write(srv, __FILE__, __LINE__, "sbsbs", 339 "monitoring dir failed:", 340 fam_dir->name, 341 "file:", name, 342 FamErrlist[FAMErrno]); 343 344 fam_dir_entry_free(&scf->fam, fam_dir); 345 fam_dir = NULL; 346 } else { 347 int osize = splaytree_size(scf->dirs); 348 349 /* already splayed scf->dir_ndx */ 350 if ((NULL != scf->dirs) && (scf->dirs->key == scf->dir_ndx)) { 351 /* hash collision: replace old entry */ 352 fam_dir_entry_free(&scf->fam, scf->dirs->data); 353 scf->dirs->data = fam_dir; 354 } else { 355 scf->dirs = splaytree_insert(scf->dirs, scf->dir_ndx, fam_dir); 356 force_assert(osize == (splaytree_size(scf->dirs) - 1)); 357 } 358 359 force_assert(scf->dirs); 360 force_assert(scf->dirs->data == fam_dir); 361 scf->fam_dir = fam_dir; 362 } 363 } 364 365 /* bind the fam_fc to the stat() cache entry */ 366 367 if (fam_dir) { 368 sce->dir_version = fam_dir->version; 369 } 370 } 371 372 #endif 373 374 375 stat_cache *stat_cache_init(server *srv) { 376 stat_cache *sc = NULL; 377 UNUSED(srv); 378 379 sc = calloc(1, sizeof(*sc)); 380 force_assert(NULL != sc); 381 382 #ifdef HAVE_FAM_H 383 if (STAT_CACHE_ENGINE_FAM == srv->srvconf.stat_cache_engine) { 384 sc->scf = stat_cache_init_fam(srv); 385 if (NULL == sc->scf) { 386 free(sc); 387 return NULL; 388 } 389 } 390 #endif 391 392 return sc; 393 } 394 395 static stat_cache_entry * stat_cache_entry_init(void) { 396 stat_cache_entry *sce = NULL; 397 398 sce = calloc(1, sizeof(*sce)); 399 force_assert(NULL != sce); 400 401 sce->name = buffer_init(); 402 sce->etag = buffer_init(); 403 sce->content_type = buffer_init(); 404 405 return sce; 406 } 407 408 static void stat_cache_entry_free(void *data) { 409 stat_cache_entry *sce = data; 410 if (!sce) return; 411 412 buffer_free(sce->etag); 413 buffer_free(sce->name); 414 buffer_free(sce->content_type); 415 416 free(sce); 417 } 418 419 void stat_cache_free(stat_cache *sc) { 420 while (sc->files) { 421 int osize; 422 splay_tree *node = sc->files; 423 424 osize = sc->files->size; 425 426 stat_cache_entry_free(node->data); 427 sc->files = splaytree_delete(sc->files, node->key); 428 429 force_assert(osize - 1 == splaytree_size(sc->files)); 430 } 431 432 #ifdef HAVE_FAM_H 433 stat_cache_free_fam(sc->scf); 434 #endif 435 free(sc); 436 } 437 438 int stat_cache_choose_engine (server *srv, const buffer *stat_cache_string) { 439 if (buffer_string_is_empty(stat_cache_string)) { 440 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; 441 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("simple"))) { 442 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; 443 #ifdef HAVE_FAM_H 444 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("fam"))) { 445 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_FAM; 446 #endif 447 } else if (buffer_is_equal_string(stat_cache_string, CONST_STR_LEN("disable"))) { 448 srv->srvconf.stat_cache_engine = STAT_CACHE_ENGINE_NONE; 449 } else { 450 log_error_write(srv, __FILE__, __LINE__, "sb", 451 "server.stat-cache-engine can be one of \"disable\", \"simple\"," 452 #ifdef HAVE_FAM_H 453 " \"fam\"," 454 #endif 455 " but not:", stat_cache_string); 456 return -1; 457 } 458 return 0; 459 } 460 461 #if defined(HAVE_XATTR) 462 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) { 463 int attrlen; 464 int ret; 465 466 buffer_string_prepare_copy(buf, 1023); 467 attrlen = buf->size - 1; 468 if(0 == (ret = attr_get(name, xattrname, buf->ptr, &attrlen, 0))) { 469 buffer_commit(buf, attrlen); 470 } 471 return ret; 472 } 473 #elif defined(HAVE_EXTATTR) 474 static int stat_cache_attr_get(buffer *buf, char *name, char *xattrname) { 475 ssize_t attrlen; 476 477 buffer_string_prepare_copy(buf, 1023); 478 479 if (-1 != (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, xattrname, buf->ptr, buf->size - 1))) { 480 buf->used = attrlen + 1; 481 buf->ptr[attrlen] = '\0'; 482 return 0; 483 } 484 return -1; 485 } 486 #endif 487 488 const buffer * stat_cache_mimetype_by_ext(const connection *con, const char *name, size_t nlen) 489 { 490 const char *end = name + nlen; /*(end of string)*/ 491 const size_t used = con->conf.mimetypes->used; 492 if (used < 16) { 493 for (size_t i = 0; i < used; ++i) { 494 /* suffix match */ 495 const data_string *ds = (data_string *)con->conf.mimetypes->data[i]; 496 const size_t klen = buffer_string_length(ds->key); 497 if (klen <= nlen && 0 == strncasecmp(end-klen, ds->key->ptr, klen)) 498 return ds->value; 499 } 500 } 501 else { 502 const char *s; 503 const data_string *ds; 504 if (nlen) { 505 for (s = end-1; s != name && *s != '/'; --s) ; /*(like memrchr())*/ 506 if (*s == '/') ++s; 507 } 508 else { 509 s = name; 510 } 511 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */ 512 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s); 513 if (NULL != ds) return ds->value; 514 while (++s < end) { 515 while (*s != '.' && ++s != end) ; 516 if (s == end) break; 517 /* search ".ext" then "ext" */ 518 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s); 519 if (NULL != ds) return ds->value; 520 /* repeat search without leading '.' to handle situation where 521 * admin configured mimetype.assign keys without leading '.' */ 522 if (++s < end) { 523 if (*s == '.') { --s; continue; } 524 ds = (data_string *)array_get_element_klen(con->conf.mimetypes, s, end - s); 525 if (NULL != ds) return ds->value; 526 } 527 } 528 /* search for ""; catchall */ 529 ds = (data_string *)array_get_element(con->conf.mimetypes, ""); 530 if (NULL != ds) return ds->value; 531 } 532 533 return NULL; 534 } 535 536 const buffer * stat_cache_content_type_get(server *srv, connection *con, const buffer *name, stat_cache_entry *sce) 537 { 538 /*(invalid caching if user config has multiple, different 539 * con->conf.mimetypes for same extension (not expected))*/ 540 if (!buffer_string_is_empty(sce->content_type)) return sce->content_type; 541 542 if (S_ISREG(sce->st.st_mode)) { 543 /* determine mimetype */ 544 buffer_clear(sce->content_type); 545 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 546 if (con->conf.use_xattr) { 547 stat_cache_attr_get(sce->content_type, name->ptr, srv->srvconf.xattr_name->ptr); 548 } 549 #else 550 UNUSED(srv); 551 #endif 552 /* xattr did not set a content-type. ask the config */ 553 if (buffer_string_is_empty(sce->content_type)) { 554 const buffer *type = stat_cache_mimetype_by_ext(con, CONST_BUF_LEN(name)); 555 if (NULL != type) { 556 buffer_copy_buffer(sce->content_type, type); 557 } 558 } 559 return sce->content_type; 560 } 561 562 return NULL; 563 } 564 565 const buffer * stat_cache_etag_get(stat_cache_entry *sce, etag_flags_t flags) { 566 /*(invalid caching if user config has multiple, different con->etag_flags 567 * for same path (not expected, since etag flags should be by filesystem))*/ 568 if (!buffer_string_is_empty(sce->etag)) return sce->etag; 569 570 if (S_ISREG(sce->st.st_mode) || S_ISDIR(sce->st.st_mode)) { 571 etag_create(sce->etag, &sce->st, flags); 572 return sce->etag; 573 } 574 575 return NULL; 576 } 577 578 #ifdef HAVE_LSTAT 579 static int stat_cache_lstat(server *srv, buffer *dname, struct stat *lst) { 580 if (lstat(dname->ptr, lst) == 0) { 581 return S_ISLNK(lst->st_mode) ? 0 : 1; 582 } 583 else { 584 log_error_write(srv, __FILE__, __LINE__, "sbs", 585 "lstat failed for:", 586 dname, strerror(errno)); 587 }; 588 return -1; 589 } 590 #endif 591 592 /*** 593 * 594 * 595 * 596 * returns: 597 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file) 598 * - HANDLER_ERROR on stat() failed -> see errno for problem 599 */ 600 601 handler_t stat_cache_get_entry(server *srv, connection *con, buffer *name, stat_cache_entry **ret_sce) { 602 stat_cache_entry *sce = NULL; 603 stat_cache *sc; 604 struct stat st; 605 int fd; 606 const int follow_symlink = con->conf.follow_symlink; 607 struct stat lst; 608 int file_ndx; 609 610 *ret_sce = NULL; 611 612 /* 613 * check if the directory for this file has changed 614 */ 615 616 sc = srv->stat_cache; 617 618 file_ndx = hashme(name->ptr, follow_symlink); 619 sc->files = splaytree_splay(sc->files, file_ndx); 620 621 if (sc->files && (sc->files->key == file_ndx)) { 622 /* we have seen this file already and 623 * don't stat() it again in the same second */ 624 625 sce = sc->files->data; 626 627 /* check if the name is the same, we might have a collision */ 628 629 if (buffer_is_equal(name, sce->name)) { 630 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { 631 if (sce->stat_ts == srv->cur_ts && follow_symlink) { 632 *ret_sce = sce; 633 return HANDLER_GO_ON; 634 } 635 } 636 } else { 637 /* collision, forget about the entry */ 638 sce = NULL; 639 } 640 } 641 642 #ifdef HAVE_FAM_H 643 /* dir-check */ 644 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { 645 switch (stat_cache_fam_dir_check(srv, sc->scf, sce, name, follow_symlink)) { 646 case HANDLER_GO_ON: 647 break; 648 case HANDLER_FINISHED: 649 *ret_sce = sce; 650 return HANDLER_GO_ON; 651 case HANDLER_ERROR: 652 default: 653 return HANDLER_ERROR; 654 } 655 } 656 #endif 657 658 /* 659 * *lol* 660 * - open() + fstat() on a named-pipe results in a (intended) hang. 661 * - stat() if regular file + open() to see if we can read from it is better 662 * 663 * */ 664 if (-1 == stat(name->ptr, &st)) { 665 return HANDLER_ERROR; 666 } 667 668 669 if (S_ISREG(st.st_mode)) { 670 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */ 671 if (name->ptr[buffer_string_length(name) - 1] == '/') { 672 errno = ENOTDIR; 673 return HANDLER_ERROR; 674 } 675 676 /* try to open the file to check if we can read it */ 677 if (-1 == (fd = open(name->ptr, O_RDONLY))) { 678 return HANDLER_ERROR; 679 } 680 close(fd); 681 } 682 683 if (NULL == sce) { 684 685 sce = stat_cache_entry_init(); 686 buffer_copy_buffer(sce->name, name); 687 688 /* already splayed file_ndx */ 689 if ((NULL != sc->files) && (sc->files->key == file_ndx)) { 690 /* hash collision: replace old entry */ 691 stat_cache_entry_free(sc->files->data); 692 sc->files->data = sce; 693 } else { 694 int osize = splaytree_size(sc->files); 695 696 sc->files = splaytree_insert(sc->files, file_ndx, sce); 697 force_assert(osize + 1 == splaytree_size(sc->files)); 698 } 699 force_assert(sc->files); 700 force_assert(sc->files->data == sce); 701 702 } else { 703 704 buffer_clear(sce->etag); 705 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 706 buffer_clear(sce->content_type); 707 #endif 708 709 } 710 711 sce->st = st; 712 sce->stat_ts = srv->cur_ts; 713 714 /* catch the obvious symlinks 715 * 716 * this is not a secure check as we still have a race-condition between 717 * the stat() and the open. We can only solve this by 718 * 1. open() the file 719 * 2. fstat() the fd 720 * 721 * and keeping the file open for the rest of the time. But this can 722 * only be done at network level. 723 * 724 * per default it is not a symlink 725 * */ 726 #ifdef HAVE_LSTAT 727 sce->is_symlink = 0; 728 729 /* we want to only check for symlinks if we should block symlinks. 730 */ 731 if (!follow_symlink) { 732 if (stat_cache_lstat(srv, name, &lst) == 0) { 733 sce->is_symlink = 1; 734 } 735 736 /* 737 * we assume "/" can not be symlink, so 738 * skip the symlink stuff if our path is / 739 **/ 740 else if (buffer_string_length(name) > 1) { 741 buffer *dname; 742 char *s_cur; 743 744 dname = buffer_init(); 745 buffer_copy_buffer(dname, name); 746 747 while ((s_cur = strrchr(dname->ptr, '/'))) { 748 buffer_string_set_length(dname, s_cur - dname->ptr); 749 if (dname->ptr == s_cur) { 750 break; 751 } 752 if (stat_cache_lstat(srv, dname, &lst) == 0) { 753 sce->is_symlink = 1; 754 break; 755 }; 756 }; 757 buffer_free(dname); 758 }; 759 } 760 #endif 761 762 #ifdef HAVE_FAM_H 763 if (srv->srvconf.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { 764 stat_cache_fam_dir_monitor(srv, sc->scf, sce, name); 765 } 766 #endif 767 768 *ret_sce = sce; 769 770 return HANDLER_GO_ON; 771 } 772 773 int stat_cache_open_rdonly_fstat (buffer *name, struct stat *st, int symlinks) { 774 /*(Note: O_NOFOLLOW affects only the final path segment, the target file, 775 * not any intermediate symlinks along the path)*/ 776 const int fd = fdevent_open_cloexec(name->ptr, symlinks, O_RDONLY, 0); 777 if (fd >= 0) { 778 if (0 == fstat(fd, st)) { 779 return fd; 780 } else { 781 close(fd); 782 } 783 } 784 return -1; 785 } 786 787 /** 788 * remove stat() from cache which havn't been stat()ed for 789 * more than 10 seconds 790 * 791 * 792 * walk though the stat-cache, collect the ids which are too old 793 * and remove them in a second loop 794 */ 795 796 static int stat_cache_tag_old_entries(server *srv, splay_tree *t, int *keys, size_t *ndx) { 797 stat_cache_entry *sce; 798 799 if (!t) return 0; 800 801 stat_cache_tag_old_entries(srv, t->left, keys, ndx); 802 stat_cache_tag_old_entries(srv, t->right, keys, ndx); 803 804 sce = t->data; 805 806 if (srv->cur_ts - sce->stat_ts > 2) { 807 keys[(*ndx)++] = t->key; 808 } 809 810 return 0; 811 } 812 813 int stat_cache_trigger_cleanup(server *srv) { 814 stat_cache *sc; 815 size_t max_ndx = 0, i; 816 int *keys; 817 818 sc = srv->stat_cache; 819 820 if (!sc->files) return 0; 821 822 keys = calloc(1, sizeof(int) * sc->files->size); 823 force_assert(NULL != keys); 824 825 stat_cache_tag_old_entries(srv, sc->files, keys, &max_ndx); 826 827 for (i = 0; i < max_ndx; i++) { 828 int ndx = keys[i]; 829 splay_tree *node; 830 831 sc->files = splaytree_splay(sc->files, ndx); 832 833 node = sc->files; 834 835 if (node && (node->key == ndx)) { 836 stat_cache_entry_free(node->data); 837 sc->files = splaytree_delete(sc->files, ndx); 838 } 839 } 840 841 free(keys); 842 843 return 0; 844 } 845