1 #include "first.h" 2 3 #include "stat_cache.h" 4 #include "log.h" 5 #include "fdevent.h" 6 #include "etag.h" 7 #include "splaytree.h" 8 9 #include <sys/types.h> 10 #include <sys/stat.h> 11 12 #include <stdlib.h> 13 #include <string.h> 14 #include <errno.h> 15 #include <unistd.h> 16 #include <fcntl.h> 17 18 #if defined(HAVE_SYS_XATTR_H) 19 # include <sys/xattr.h> 20 #elif defined(HAVE_ATTR_ATTRIBUTES_H) 21 # include <attr/attributes.h> 22 #endif 23 24 #ifdef HAVE_SYS_EXTATTR_H 25 # include <sys/extattr.h> 26 #endif 27 28 #ifndef HAVE_LSTAT 29 #define lstat stat 30 #ifndef S_ISLNK 31 #define S_ISLNK(mode) (0) 32 #endif 33 #endif 34 35 /* 36 * stat-cache 37 * 38 * - a splay-tree is used as we can use the caching effect of it 39 */ 40 41 enum { 42 STAT_CACHE_ENGINE_SIMPLE, /*(default)*/ 43 STAT_CACHE_ENGINE_NONE, 44 STAT_CACHE_ENGINE_FAM 45 }; 46 47 struct stat_cache_fam; /* declaration */ 48 49 typedef struct stat_cache { 50 int stat_cache_engine; 51 splay_tree *files; /* nodes of tree are (stat_cache_entry *) */ 52 struct stat_cache_fam *scf; 53 } stat_cache; 54 55 static stat_cache sc; 56 57 58 static void * stat_cache_sptree_find(splay_tree ** const sptree, 59 const char * const name, 60 uint32_t len) 61 { 62 const int ndx = splaytree_djbhash(name, len); 63 *sptree = splaytree_splay(*sptree, ndx); 64 return (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL; 65 } 66 67 68 #ifdef HAVE_FAM_H 69 70 /* monitor changes in directories using FAM 71 * 72 * This implementation employing FAM monitors directories as they are used, 73 * and maintains a reference count for cache use within stat_cache.c. 74 * A periodic job runs in lighttpd every 32 seconds, expiring entries unused 75 * in last 64 seconds out of the cache and cancelling FAM monitoring. Items 76 * within the cache are checked against the filesystem upon use if last stat() 77 * was greater than or equal to 16 seconds ago. 78 * 79 * This implementation does not monitor every directory in a tree, and therefore 80 * the cache may get out-of-sync with the filesystem. Delays in receiving and 81 * processing events from FAM might also lead to stale cache entries. 82 * 83 * For many websites, a large number of files are seldom, if ever, modified, 84 * and a common practice with images is to create a new file with a new name 85 * when a new version is needed, in order for client browsers and CDNs to better 86 * cache the content. Given this, most use will see little difference in 87 * performance between server.stat-cache-engine = "fam" and "simple" (default). 88 * The default server.stat-cache-engine = "simple" calls stat() on a target once 89 * per second, and reuses that information until the next second. For use where 90 * changes must be immediately visible, server.stat-cache-engine = "disable" 91 * should be used. 92 * 93 * When considering use of server.stat-cache-engine = "fam", there are a few 94 * additional limitations for this cache implementation using FAM. 95 * - symlinks to files located outside of the current directory do not result 96 * in changes to that file being monitored (unless that file is in a directory 97 * which is monitored as a result of a different request). symlinks can be 98 * chained and can be circular. This implementation *does not* readlink() or 99 * realpath() to resolve the chains to find and monitor the ultimate target 100 * directory. While symlinks to files located outside the current directory 101 * are not monitored, symlinks to directories *are* monitored, though chains 102 * of symlinks to directories do not result in monitoring of the directories 103 * containing intermediate symlinks to the target directory. 104 * - directory rename of a directory which is not currently being monitored will 105 * result in stale information in the cache if there is a subdirectory that is 106 * being monitored. 107 * Even though lighttpd will not receive FAM events in the above cases, lighttpd 108 * does re-validate the information in the cache upon use if the cache entry has 109 * not been checked in 16 seconds, so that is the upper limit for use of stale 110 * data. 111 * 112 * Use of server.stat-cache-engine = "fam" is discouraged for extremely volatile 113 * directories such as temporary directories (e.g. /tmp and maybe /var/tmp) due 114 * to the overhead of processing the additional noise generated from changes. 115 * Related, server.stat-cache-engine = "fam" is not recommended on trees of 116 * untrusted files where a malicious user could generate an excess of change 117 * events. 118 * 119 * Internal note: lighttpd walks the caches to prune trees in stat_cache when an 120 * event is received for a directory (or symlink to a directory) which has been 121 * deleted or renamed. The splaytree data structure is suboptimal for frequent 122 * changes of large directories trees where there have been a large number of 123 * different files recently accessed and part of the stat_cache. 124 */ 125 126 #include <fam.h> 127 128 typedef struct fam_dir_entry { 129 buffer *name; 130 int refcnt; 131 FAMRequest req; 132 time_t stat_ts; 133 dev_t st_dev; 134 ino_t st_ino; 135 struct fam_dir_entry *fam_parent; 136 } fam_dir_entry; 137 138 typedef struct stat_cache_fam { 139 splay_tree *dirs; /* the nodes of the tree are fam_dir_entry */ 140 FAMConnection fam; 141 log_error_st *errh; 142 fdevents *ev; 143 fdnode *fdn; 144 int fd; 145 } stat_cache_fam; 146 147 static fam_dir_entry * fam_dir_entry_init(const char *name, size_t len) 148 { 149 fam_dir_entry * const fam_dir = calloc(1, sizeof(*fam_dir)); 150 force_assert(NULL != fam_dir); 151 152 fam_dir->name = buffer_init(); 153 buffer_copy_string_len(fam_dir->name, name, len); 154 fam_dir->refcnt = 0; 155 156 return fam_dir; 157 } 158 159 static void fam_dir_entry_free(fam_dir_entry *fam_dir) 160 { 161 if (!fam_dir) return; 162 /*(fam_dir->parent might be invalid pointer here; ignore)*/ 163 buffer_free(fam_dir->name); 164 free(fam_dir); 165 } 166 167 static void fam_dir_invalidate_node(fam_dir_entry *fam_dir) 168 { 169 fam_dir->stat_ts = 0; 170 if (fam_dir->fam_parent) { 171 --fam_dir->fam_parent->refcnt; 172 fam_dir->fam_parent = NULL; 173 } 174 } 175 176 /* 177 * walk though splay_tree and collect contents of dir tree. 178 * remove tagged entries in a second loop 179 */ 180 181 static void fam_dir_tag_refcnt(splay_tree *t, int *keys, int *ndx) 182 { 183 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 184 if (t->left) fam_dir_tag_refcnt(t->left, keys, ndx); 185 if (t->right) fam_dir_tag_refcnt(t->right, keys, ndx); 186 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 187 188 fam_dir_entry * const fam_dir = t->data; 189 if (0 == fam_dir->refcnt) { 190 fam_dir_invalidate_node(fam_dir); 191 keys[(*ndx)++] = t->key; 192 } 193 } 194 195 __attribute_noinline__ 196 static void fam_dir_periodic_cleanup() { 197 stat_cache_fam * const scf = sc.scf; 198 int max_ndx, i; 199 int keys[8192]; /* 32k size on stack */ 200 do { 201 if (!scf->dirs) break; 202 max_ndx = 0; 203 fam_dir_tag_refcnt(scf->dirs, keys, &max_ndx); 204 for (i = 0; i < max_ndx; ++i) { 205 const int ndx = keys[i]; 206 splay_tree *node = scf->dirs = splaytree_splay(scf->dirs, ndx); 207 if (node && node->key == ndx) { 208 fam_dir_entry *fam_dir = node->data; 209 scf->dirs = splaytree_delete(scf->dirs, ndx); 210 FAMCancelMonitor(&scf->fam, &fam_dir->req); 211 fam_dir_entry_free(fam_dir); 212 } 213 } 214 } while (max_ndx == sizeof(keys)/sizeof(int)); 215 } 216 217 static void fam_dir_invalidate_tree(splay_tree *t, const char *name, size_t len) 218 { 219 #ifdef __clang_analyzer__ 220 force_assert(name); 221 #endif 222 /*force_assert(t);*/ 223 if (t->left) fam_dir_invalidate_tree(t->left, name, len); 224 if (t->right) fam_dir_invalidate_tree(t->right, name, len); 225 226 fam_dir_entry * const fam_dir = t->data; 227 #ifdef __clang_analyzer__ 228 force_assert(fam_dir); 229 #endif 230 buffer *b = fam_dir->name; 231 size_t blen = buffer_string_length(b); 232 if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len)) 233 fam_dir_invalidate_node(fam_dir); 234 } 235 236 /* declarations */ 237 static void stat_cache_delete_tree(const char *name, uint32_t len); 238 static void stat_cache_invalidate_dir_tree(const char *name, size_t len); 239 240 static void stat_cache_handle_fdevent_in(stat_cache_fam *scf) 241 { 242 for (int i = 0, ndx; i || (i = FAMPending(&scf->fam)) > 0; --i) { 243 FAMEvent fe; 244 if (FAMNextEvent(&scf->fam, &fe) < 0) break; 245 246 /* ignore events which may have been pending for 247 * paths recently cancelled via FAMCancelMonitor() */ 248 ndx = (int)(intptr_t)fe.userdata; 249 scf->dirs = splaytree_splay(scf->dirs, ndx); 250 if (!scf->dirs || scf->dirs->key != ndx) { 251 continue; 252 } 253 fam_dir_entry *fam_dir = scf->dirs->data; 254 if (FAMREQUEST_GETREQNUM(&fam_dir->req) 255 != FAMREQUEST_GETREQNUM(&fe.fr)) { 256 continue; 257 } 258 259 if (fe.filename[0] != '/') { 260 buffer * const n = fam_dir->name; 261 fam_dir_entry *fam_link; 262 size_t len; 263 switch(fe.code) { 264 case FAMCreated: 265 /* file created in monitored dir modifies dir and 266 * we should get a separate FAMChanged event for dir. 267 * Therefore, ignore file FAMCreated event here. 268 * Also, if FAMNoExists() is used, might get spurious 269 * FAMCreated events as changes are made e.g. in monitored 270 * sub-sub-sub dirs and the library discovers new (already 271 * existing) dir entries */ 272 continue; 273 case FAMChanged: 274 /* file changed in monitored dir does not modify dir */ 275 case FAMDeleted: 276 case FAMMoved: 277 /* file deleted or moved in monitored dir modifies dir, 278 * but FAM provides separate notification for that */ 279 280 /* temporarily append filename to dir in fam_dir->name to 281 * construct path, then delete stat_cache entry (if any)*/ 282 len = buffer_string_length(n); 283 buffer_append_string_len(n, CONST_STR_LEN("/")); 284 buffer_append_string_len(n,fe.filename,strlen(fe.filename)); 285 /* (alternatively, could chose to stat() and update)*/ 286 stat_cache_invalidate_entry(CONST_BUF_LEN(n)); 287 288 fam_link = /*(check if might be symlink to monitored dir)*/ 289 stat_cache_sptree_find(&scf->dirs, CONST_BUF_LEN(n)); 290 if (fam_link && !buffer_is_equal(fam_link->name, n)) 291 fam_link = NULL; 292 293 buffer_string_set_length(n, len); 294 295 if (fam_link) { 296 /* replaced symlink changes containing dir */ 297 stat_cache_invalidate_entry(CONST_BUF_LEN(n)); 298 /* handle symlink to dir as deleted dir below */ 299 fe.code = FAMDeleted; 300 fam_dir = fam_link; 301 break; 302 } 303 continue; 304 default: 305 continue; 306 } 307 } 308 309 switch(fe.code) { 310 case FAMChanged: 311 stat_cache_invalidate_entry(CONST_BUF_LEN(fam_dir->name)); 312 break; 313 case FAMDeleted: 314 case FAMMoved: 315 stat_cache_delete_tree(CONST_BUF_LEN(fam_dir->name)); 316 fam_dir_invalidate_node(fam_dir); 317 if (scf->dirs) 318 fam_dir_invalidate_tree(scf->dirs,CONST_BUF_LEN(fam_dir->name)); 319 fam_dir_periodic_cleanup(); 320 break; 321 default: 322 break; 323 } 324 } 325 } 326 327 static handler_t stat_cache_handle_fdevent(void *ctx, int revent) 328 { 329 stat_cache_fam * const scf = ctx; /* sc.scf */ 330 331 if (revent & FDEVENT_IN) { 332 stat_cache_handle_fdevent_in(scf); 333 } 334 335 if (revent & (FDEVENT_HUP|FDEVENT_RDHUP)) { 336 /* fam closed the connection */ 337 log_error(scf->errh, __FILE__, __LINE__, 338 "FAM connection closed; disabling stat_cache."); 339 /* (although effectively STAT_CACHE_ENGINE_NONE, 340 * do not change here so that periodic jobs clean up memory)*/ 341 /*sc.stat_cache_engine = STAT_CACHE_ENGINE_NONE; */ 342 fdevent_fdnode_event_del(scf->ev, scf->fdn); 343 fdevent_unregister(scf->ev, scf->fd); 344 scf->fdn = NULL; 345 346 FAMClose(&scf->fam); 347 scf->fd = -1; 348 } 349 350 return HANDLER_GO_ON; 351 } 352 353 static stat_cache_fam * stat_cache_init_fam(fdevents *ev, log_error_st *errh) { 354 stat_cache_fam *scf = calloc(1, sizeof(*scf)); 355 force_assert(scf); 356 scf->fd = -1; 357 scf->ev = ev; 358 scf->errh = errh; 359 360 /* setup FAM */ 361 if (0 != FAMOpen2(&scf->fam, "lighttpd")) { 362 log_error(errh, __FILE__, __LINE__, 363 "could not open a fam connection, dying."); 364 return NULL; 365 } 366 #ifdef HAVE_FAMNOEXISTS 367 FAMNoExists(&scf->fam); 368 #endif 369 370 scf->fd = FAMCONNECTION_GETFD(&scf->fam); 371 fdevent_setfd_cloexec(scf->fd); 372 scf->fdn = fdevent_register(scf->ev, scf->fd, stat_cache_handle_fdevent, scf); 373 fdevent_fdnode_event_set(scf->ev, scf->fdn, FDEVENT_IN | FDEVENT_RDHUP); 374 375 return scf; 376 } 377 378 static void stat_cache_free_fam(stat_cache_fam *scf) { 379 if (NULL == scf) return; 380 381 while (scf->dirs) { 382 /*(skip entry invalidation and FAMCancelMonitor())*/ 383 splay_tree *node = scf->dirs; 384 fam_dir_entry_free((fam_dir_entry *)node->data); 385 scf->dirs = splaytree_delete(scf->dirs, node->key); 386 } 387 388 if (-1 != scf->fd) { 389 /*scf->fdn already cleaned up in fdevent_free()*/ 390 FAMClose(&scf->fam); 391 /*scf->fd = -1;*/ 392 } 393 394 free(scf); 395 } 396 397 static fam_dir_entry * fam_dir_monitor(stat_cache_fam *scf, char *fn, uint32_t dirlen, struct stat *st) 398 { 399 if (NULL == scf->fdn) return NULL; /* FAM connection closed; do nothing */ 400 const int fn_is_dir = S_ISDIR(st->st_mode); 401 /*force_assert(0 != dirlen);*/ 402 /*force_assert(fn[0] == '/');*/ 403 /* consistency: ensure fn does not end in '/' unless root "/" 404 * FAM events will not end in '/', so easier to match this way */ 405 if (fn[dirlen-1] == '/') --dirlen; 406 if (0 == dirlen) dirlen = 1; /* root dir ("/") */ 407 /* Note: paths are expected to be normalized before calling stat_cache, 408 * e.g. without repeated '/' */ 409 if (!fn_is_dir) { 410 while (fn[--dirlen] != '/') ; 411 if (0 == dirlen) dirlen = 1; /*(should not happen for file)*/ 412 } 413 int dir_ndx = splaytree_djbhash(fn, dirlen); 414 fam_dir_entry *fam_dir = NULL; 415 416 scf->dirs = splaytree_splay(scf->dirs, dir_ndx); 417 if (NULL != scf->dirs && scf->dirs->key == dir_ndx) { 418 fam_dir = scf->dirs->data; 419 if (!buffer_is_equal_string(fam_dir->name, fn, dirlen)) { 420 /* hash collision; preserve existing 421 * do not monitor new to avoid cache thrashing */ 422 return NULL; 423 } 424 /* directory already registered */ 425 } 426 427 const time_t cur_ts = log_epoch_secs; 428 struct stat lst; 429 int ck_dir = fn_is_dir; 430 if (!fn_is_dir && (NULL==fam_dir || cur_ts - fam_dir->stat_ts >= 16)) { 431 ck_dir = 1; 432 /*(temporarily modify fn)*/ 433 fn[dirlen] = '\0'; 434 if (0 != lstat(fn, &lst)) { 435 fn[dirlen] = '/'; 436 return NULL; 437 } 438 if (!S_ISLNK(lst.st_mode)) { 439 st = &lst; 440 } 441 else if (0 != stat(fn, st)) { /*st passed in now is stat() of dir*/ 442 fn[dirlen] = '/'; 443 return NULL; 444 } 445 fn[dirlen] = '/'; 446 } 447 448 int ck_lnk = (NULL == fam_dir); 449 if (ck_dir && NULL != fam_dir) { 450 /* check stat() matches device and inode, just in case an external event 451 * not being monitored occurs (e.g. rename of unmonitored parent dir)*/ 452 if (st->st_dev != fam_dir->st_dev || st->st_ino != fam_dir->st_ino) { 453 ck_lnk = 1; 454 /*(modifies scf->dirs but no need to re-splay for dir_ndx since 455 * fam_dir is not NULL and so splaytree_insert not called below)*/ 456 if (scf->dirs) fam_dir_invalidate_tree(scf->dirs, fn, dirlen); 457 if (!fn_is_dir) /*(if dir, caller is updating stat_cache_entry)*/ 458 stat_cache_update_entry(fn, dirlen, st, NULL); 459 /*(must not delete tree since caller is holding a valid node)*/ 460 stat_cache_invalidate_dir_tree(fn, dirlen); 461 if (0 != FAMCancelMonitor(&scf->fam, &fam_dir->req) 462 || 0 != FAMMonitorDirectory(&scf->fam, fam_dir->name->ptr, 463 &fam_dir->req, 464 (void *)(intptr_t)dir_ndx)) { 465 fam_dir->stat_ts = 0; /* invalidate */ 466 return NULL; 467 } 468 fam_dir->st_dev = st->st_dev; 469 fam_dir->st_ino = st->st_ino; 470 } 471 fam_dir->stat_ts = cur_ts; 472 } 473 474 if (NULL == fam_dir) { 475 fam_dir = fam_dir_entry_init(fn, dirlen); 476 477 if (0 != FAMMonitorDirectory(&scf->fam,fam_dir->name->ptr,&fam_dir->req, 478 (void *)(intptr_t)dir_ndx)) { 479 log_error(scf->errh, __FILE__, __LINE__, 480 "monitoring dir failed: %s file: %s %s", 481 fam_dir->name->ptr, fn, FamErrlist[FAMErrno]); 482 fam_dir_entry_free(fam_dir); 483 return NULL; 484 } 485 486 scf->dirs = splaytree_insert(scf->dirs, dir_ndx, fam_dir); 487 fam_dir->stat_ts= cur_ts; 488 fam_dir->st_dev = st->st_dev; 489 fam_dir->st_ino = st->st_ino; 490 } 491 492 if (ck_lnk) { 493 if (fn_is_dir) { 494 /*(temporarily modify fn)*/ 495 char e = fn[dirlen]; 496 fn[dirlen] = '\0'; 497 if (0 != lstat(fn, &lst)) { 498 fn[dirlen] = e; 499 return NULL; 500 } 501 fn[dirlen] = e; 502 } 503 if (fam_dir->fam_parent) { 504 --fam_dir->fam_parent->refcnt; 505 fam_dir->fam_parent = NULL; 506 } 507 if (S_ISLNK(lst.st_mode)) { 508 fam_dir->fam_parent = fam_dir_monitor(scf, fn, dirlen, &lst); 509 } 510 } 511 512 ++fam_dir->refcnt; 513 return fam_dir; 514 } 515 516 #endif 517 518 519 static stat_cache_entry * stat_cache_entry_init(void) { 520 stat_cache_entry *sce = calloc(1, sizeof(*sce)); 521 force_assert(NULL != sce); 522 return sce; 523 } 524 525 static void stat_cache_entry_free(void *data) { 526 stat_cache_entry *sce = data; 527 if (!sce) return; 528 529 #ifdef HAVE_FAM_H 530 /*(decrement refcnt only; 531 * defer cancelling FAM monitor on dir even if refcnt reaches zero)*/ 532 if (sce->fam_dir) --((fam_dir_entry *)sce->fam_dir)->refcnt; 533 #endif 534 535 free(sce->name.ptr); 536 free(sce->etag.ptr); 537 if (sce->content_type.size) free(sce->content_type.ptr); 538 539 free(sce); 540 } 541 542 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 543 544 static const char *attrname = "Content-Type"; 545 static char attrval[128]; 546 static buffer attrb = { attrval, 0, 0 }; 547 548 static int stat_cache_attr_get(const char *name) { 549 #if defined(HAVE_XATTR) 550 #if defined(HAVE_SYS_XATTR_H) 551 ssize_t attrlen; 552 if (0 < (attrlen = getxattr(name, attrname, 553 attrval, sizeof(attrval)-1))) 554 #else 555 int attrlen = sizeof(attrval)-1; 556 if (0 == attr_get(name, attrname, attrval, &attrlen, 0)) 557 #endif 558 #elif defined(HAVE_EXTATTR) 559 ssize_t attrlen; 560 if (0 < (attrlen = extattr_get_file(name, EXTATTR_NAMESPACE_USER, attrname, 561 attrval, sizeof(attrval)-1))) 562 #endif 563 { 564 attrval[attrlen] = '\0'; 565 attrb.used = (uint32_t)(attrlen + 1); 566 return 1; 567 } 568 return 0; 569 } 570 571 #endif 572 573 int stat_cache_init(fdevents *ev, log_error_st *errh) { 574 #ifdef HAVE_FAM_H 575 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { 576 sc.scf = stat_cache_init_fam(ev, errh); 577 if (NULL == sc.scf) return 0; 578 } 579 #else 580 UNUSED(ev); 581 UNUSED(errh); 582 #endif 583 584 return 1; 585 } 586 587 void stat_cache_free(void) { 588 splay_tree *sptree = sc.files; 589 while (sptree) { 590 stat_cache_entry_free(sptree->data); 591 sptree = splaytree_delete(sptree, sptree->key); 592 } 593 sc.files = NULL; 594 595 #ifdef HAVE_FAM_H 596 stat_cache_free_fam(sc.scf); 597 sc.scf = NULL; 598 #endif 599 600 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 601 attrname = "Content-Type"; 602 #endif 603 604 sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; /*(default)*/ 605 } 606 607 void stat_cache_xattrname (const char *name) { 608 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 609 attrname = name; 610 #else 611 UNUSED(name); 612 #endif 613 } 614 615 int stat_cache_choose_engine (const buffer *stat_cache_string, log_error_st *errh) { 616 if (buffer_string_is_empty(stat_cache_string)) 617 sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; 618 else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("simple"))) 619 sc.stat_cache_engine = STAT_CACHE_ENGINE_SIMPLE; 620 #ifdef HAVE_FAM_H 621 else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("fam"))) 622 sc.stat_cache_engine = STAT_CACHE_ENGINE_FAM; 623 #endif 624 else if (buffer_eq_slen(stat_cache_string, CONST_STR_LEN("disable"))) 625 sc.stat_cache_engine = STAT_CACHE_ENGINE_NONE; 626 else { 627 log_error(errh, __FILE__, __LINE__, 628 "server.stat-cache-engine can be one of \"disable\", \"simple\"," 629 #ifdef HAVE_FAM_H 630 " \"fam\"," 631 #endif 632 " but not: %s", stat_cache_string->ptr); 633 return -1; 634 } 635 return 0; 636 } 637 638 const buffer * stat_cache_mimetype_by_ext(const array * const mimetypes, const char * const name, const uint32_t nlen) 639 { 640 const char * const end = name + nlen; /*(end of string)*/ 641 const uint32_t used = mimetypes->used; 642 if (used < 16) { 643 for (uint32_t i = 0; i < used; ++i) { 644 /* suffix match */ 645 const data_string *ds = (data_string *)mimetypes->data[i]; 646 const size_t klen = buffer_string_length(&ds->key); 647 if (klen <= nlen && buffer_eq_icase_ssn(end-klen, ds->key.ptr, klen)) 648 return &ds->value; 649 } 650 } 651 else { 652 const char *s; 653 const data_string *ds; 654 if (nlen) { 655 for (s = end-1; s != name && *s != '/'; --s) ; /*(like memrchr())*/ 656 if (*s == '/') ++s; 657 } 658 else { 659 s = name; 660 } 661 /* search for basename, then longest .ext2.ext1, then .ext1, then "" */ 662 ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s); 663 if (NULL != ds) return &ds->value; 664 while (++s < end) { 665 while (*s != '.' && ++s != end) ; 666 if (s == end) break; 667 /* search ".ext" then "ext" */ 668 ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s); 669 if (NULL != ds) return &ds->value; 670 /* repeat search without leading '.' to handle situation where 671 * admin configured mimetype.assign keys without leading '.' */ 672 if (++s < end) { 673 if (*s == '.') { --s; continue; } 674 ds = (const data_string *)array_get_element_klen(mimetypes, s, end - s); 675 if (NULL != ds) return &ds->value; 676 } 677 } 678 /* search for ""; catchall */ 679 ds = (const data_string *)array_get_element_klen(mimetypes, CONST_STR_LEN("")); 680 if (NULL != ds) return &ds->value; 681 } 682 683 return NULL; 684 } 685 686 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 687 688 const buffer * stat_cache_mimetype_by_xattr(const char * const name) 689 { 690 return stat_cache_attr_get(name) ? &attrb : NULL; 691 } 692 693 const buffer * stat_cache_content_type_get_by_xattr(stat_cache_entry *sce, const array *mimetypes, int use_xattr) 694 { 695 /*(invalid caching if user config has multiple, different 696 * r->conf.mimetypes for same extension (not expected))*/ 697 if (!buffer_string_is_empty(&sce->content_type)) return &sce->content_type; 698 699 if (!S_ISREG(sce->st.st_mode)) return NULL; 700 701 /* cache mimetype */ 702 const buffer *mtype = 703 (use_xattr) ? stat_cache_mimetype_by_xattr(sce->name.ptr) : NULL; 704 if (NULL == mtype) 705 mtype = stat_cache_mimetype_by_ext(mimetypes,CONST_BUF_LEN(&sce->name)); 706 if (NULL != mtype) { 707 if (sce->content_type.size) { 708 buffer_copy_buffer(&sce->content_type, mtype); 709 } 710 else if (mtype == &attrb) { 711 sce->content_type.ptr = NULL; 712 buffer_copy_buffer(&sce->content_type, mtype); 713 } 714 else { 715 /*(copy pointers from mimetypes array; avoid allocation)*/ 716 sce->content_type.ptr = mtype->ptr; 717 sce->content_type.used = mtype->used; 718 /*(leave sce->content_type.size = 0 to flag not-allocated)*/ 719 } 720 } 721 else 722 buffer_clear(&sce->content_type); 723 724 return &sce->content_type; 725 } 726 727 #else 728 729 const buffer * stat_cache_content_type_get_by_ext(stat_cache_entry *sce, const array *mimetypes) 730 { 731 /*(invalid caching if user config has multiple, different 732 * r->conf.mimetypes for same extension (not expected))*/ 733 if (!buffer_string_is_empty(&sce->content_type)) return &sce->content_type; 734 735 if (!S_ISREG(sce->st.st_mode)) return NULL; 736 737 /* cache mimetype */ 738 const buffer * const mtype = 739 stat_cache_mimetype_by_ext(mimetypes, CONST_BUF_LEN(&sce->name)); 740 if (NULL != mtype) { 741 /*(copy pointers from mimetypes array; avoid allocation)*/ 742 sce->content_type.ptr = mtype->ptr; 743 sce->content_type.used = mtype->used; 744 /*(leave sce->content_type.size = 0 to flag not-allocated)*/ 745 } 746 else 747 buffer_clear(&sce->content_type); 748 749 return &sce->content_type; 750 } 751 752 #endif 753 754 const buffer * stat_cache_etag_get(stat_cache_entry *sce, int flags) { 755 /*(invalid caching if user cfg has multiple, different r->conf.etag_flags 756 * for same path (not expected, since etag flags should be by filesystem))*/ 757 if (!buffer_string_is_empty(&sce->etag)) return &sce->etag; 758 759 if (S_ISREG(sce->st.st_mode) || S_ISDIR(sce->st.st_mode)) { 760 if (0 == flags) return NULL; 761 etag_create(&sce->etag, &sce->st, flags); 762 return &sce->etag; 763 } 764 765 return NULL; 766 } 767 768 void stat_cache_update_entry(const char *name, uint32_t len, 769 struct stat *st, buffer *etagb) 770 { 771 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_NONE) return; 772 force_assert(0 != len); 773 if (name[len-1] == '/') { if (0 == --len) len = 1; } 774 splay_tree **sptree = &sc.files; 775 stat_cache_entry *sce = 776 stat_cache_sptree_find(sptree, name, len); 777 if (sce && buffer_is_equal_string(&sce->name, name, len)) { 778 sce->stat_ts = log_epoch_secs; 779 sce->st = *st; /* etagb might be NULL to clear etag (invalidate) */ 780 buffer_copy_string_len(&sce->etag, CONST_BUF_LEN(etagb)); 781 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 782 buffer_clear(&sce->content_type); 783 #endif 784 } 785 } 786 787 void stat_cache_delete_entry(const char *name, uint32_t len) 788 { 789 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_NONE) return; 790 force_assert(0 != len); 791 if (name[len-1] == '/') { if (0 == --len) len = 1; } 792 splay_tree **sptree = &sc.files; 793 stat_cache_entry *sce = stat_cache_sptree_find(sptree, name, len); 794 if (sce && buffer_is_equal_string(&sce->name, name, len)) { 795 stat_cache_entry_free(sce); 796 *sptree = splaytree_delete(*sptree, (*sptree)->key); 797 } 798 } 799 800 void stat_cache_invalidate_entry(const char *name, uint32_t len) 801 { 802 splay_tree **sptree = &sc.files; 803 stat_cache_entry *sce = stat_cache_sptree_find(sptree, name, len); 804 if (sce && buffer_is_equal_string(&sce->name, name, len)) { 805 sce->stat_ts = 0; 806 #ifdef HAVE_FAM_H 807 if (sce->fam_dir != NULL) { 808 --((fam_dir_entry *)sce->fam_dir)->refcnt; 809 sce->fam_dir = NULL; 810 } 811 #endif 812 } 813 } 814 815 #ifdef HAVE_FAM_H 816 817 static void stat_cache_invalidate_dir_tree_walk(splay_tree *t, 818 const char *name, size_t len) 819 { 820 if (t->left) stat_cache_invalidate_dir_tree_walk(t->left, name, len); 821 if (t->right) stat_cache_invalidate_dir_tree_walk(t->right, name, len); 822 823 buffer *b = &((stat_cache_entry *)t->data)->name; 824 size_t blen = buffer_string_length(b); 825 if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len)) { 826 stat_cache_entry *sce = t->data; 827 sce->stat_ts = 0; 828 if (sce->fam_dir != NULL) { 829 --((fam_dir_entry *)sce->fam_dir)->refcnt; 830 sce->fam_dir = NULL; 831 } 832 } 833 } 834 835 static void stat_cache_invalidate_dir_tree(const char *name, size_t len) 836 { 837 splay_tree * const sptree = sc.files; 838 if (sptree) stat_cache_invalidate_dir_tree_walk(sptree, name, len); 839 } 840 841 #endif 842 843 /* 844 * walk though splay_tree and collect contents of dir tree. 845 * remove tagged entries in a second loop 846 */ 847 848 static void stat_cache_tag_dir_tree(splay_tree *t, const char *name, size_t len, 849 int *keys, int *ndx) 850 { 851 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 852 if (t->left) stat_cache_tag_dir_tree(t->left, name, len, keys, ndx); 853 if (t->right) stat_cache_tag_dir_tree(t->right, name, len, keys, ndx); 854 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 855 856 buffer *b = &((stat_cache_entry *)t->data)->name; 857 size_t blen = buffer_string_length(b); 858 if (blen > len && b->ptr[len] == '/' && 0 == memcmp(b->ptr, name, len)) 859 keys[(*ndx)++] = t->key; 860 } 861 862 __attribute_noinline__ 863 static void stat_cache_prune_dir_tree(const char *name, size_t len) 864 { 865 splay_tree *sptree = sc.files; 866 int max_ndx, i; 867 int keys[8192]; /* 32k size on stack */ 868 do { 869 if (!sptree) break; 870 max_ndx = 0; 871 stat_cache_tag_dir_tree(sptree, name, len, keys, &max_ndx); 872 for (i = 0; i < max_ndx; ++i) { 873 const int ndx = keys[i]; 874 splay_tree *node = sptree = splaytree_splay(sptree, ndx); 875 if (node && node->key == ndx) { 876 stat_cache_entry_free(node->data); 877 sptree = splaytree_delete(sptree, ndx); 878 } 879 } 880 } while (max_ndx == sizeof(keys)/sizeof(int)); 881 sc.files = sptree; 882 } 883 884 static void stat_cache_delete_tree(const char *name, uint32_t len) 885 { 886 stat_cache_delete_entry(name, len); 887 stat_cache_prune_dir_tree(name, len); 888 } 889 890 void stat_cache_delete_dir(const char *name, uint32_t len) 891 { 892 force_assert(0 != len); 893 if (name[len-1] == '/') { if (0 == --len) len = 1; } 894 stat_cache_delete_tree(name, len); 895 #ifdef HAVE_FAM_H 896 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { 897 splay_tree **sptree = &sc.scf->dirs; 898 fam_dir_entry *fam_dir = stat_cache_sptree_find(sptree, name, len); 899 if (fam_dir && buffer_is_equal_string(fam_dir->name, name, len)) 900 fam_dir_invalidate_node(fam_dir); 901 if (*sptree) fam_dir_invalidate_tree(*sptree, name, len); 902 fam_dir_periodic_cleanup(); 903 } 904 #endif 905 } 906 907 /*** 908 * 909 * 910 * 911 * returns: 912 * - HANDLER_FINISHED on cache-miss (don't forget to reopen the file) 913 * - HANDLER_ERROR on stat() failed -> see errno for problem 914 */ 915 916 stat_cache_entry * stat_cache_get_entry(const buffer *name) { 917 stat_cache_entry *sce = NULL; 918 struct stat st; 919 int file_ndx; 920 921 /* consistency: ensure lookup name does not end in '/' unless root "/" 922 * (but use full path given with stat(), even with trailing '/') */ 923 int final_slash = 0; 924 size_t len = buffer_string_length(name); 925 force_assert(0 != len); 926 if (name->ptr[len-1] == '/') { final_slash = 1; if (0 == --len) len = 1; } 927 /* Note: paths are expected to be normalized before calling stat_cache, 928 * e.g. without repeated '/' */ 929 930 if (name->ptr[0] != '/') { 931 errno = EINVAL; 932 return NULL; 933 } 934 935 /* 936 * check if the directory for this file has changed 937 */ 938 939 const time_t cur_ts = log_epoch_secs; 940 941 file_ndx = splaytree_djbhash(name->ptr, len); 942 splay_tree * const sptree = sc.files = splaytree_splay(sc.files, file_ndx); 943 944 if (sptree && (sptree->key == file_ndx)) { 945 /* we have seen this file already and 946 * don't stat() it again in the same second */ 947 948 sce = sptree->data; 949 950 /* check if the name is the same, we might have a collision */ 951 952 if (buffer_is_equal_string(&sce->name, name->ptr, len)) { 953 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_SIMPLE) { 954 if (sce->stat_ts == cur_ts) { 955 if (final_slash && !S_ISDIR(sce->st.st_mode)) { 956 errno = ENOTDIR; 957 return NULL; 958 } 959 return sce; 960 } 961 } 962 #ifdef HAVE_FAM_H 963 else if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM 964 && sce->fam_dir) { /* entry is in monitored dir */ 965 /* re-stat() periodically, even if monitoring for changes 966 * (due to limitations in stat_cache.c use of FAM) 967 * (gaps due to not continually monitoring an entire tree) */ 968 if (cur_ts - sce->stat_ts < 16) { 969 if (final_slash && !S_ISDIR(sce->st.st_mode)) { 970 errno = ENOTDIR; 971 return NULL; 972 } 973 return sce; 974 } 975 } 976 #endif 977 } else { 978 /* collision, forget about the entry */ 979 sce = NULL; 980 } 981 } 982 983 if (-1 == stat(name->ptr, &st)) { 984 return NULL; 985 } 986 987 if (S_ISREG(st.st_mode)) { 988 /* fix broken stat/open for symlinks to reg files with appended slash on freebsd,osx */ 989 if (name->ptr[buffer_string_length(name) - 1] == '/') { 990 errno = ENOTDIR; 991 return NULL; 992 } 993 } 994 995 if (NULL == sce) { 996 997 sce = stat_cache_entry_init(); 998 buffer_copy_string_len(&sce->name, name->ptr, len); 999 1000 /* already splayed file_ndx */ 1001 if (NULL != sptree && sptree->key == file_ndx) { 1002 /* hash collision: replace old entry */ 1003 stat_cache_entry_free(sptree->data); 1004 sptree->data = sce; 1005 } else { 1006 sc.files = splaytree_insert(sptree, file_ndx, sce); 1007 } 1008 1009 } else { 1010 1011 buffer_clear(&sce->etag); 1012 #if defined(HAVE_XATTR) || defined(HAVE_EXTATTR) 1013 buffer_clear(&sce->content_type); 1014 #endif 1015 1016 } 1017 1018 sce->st = st; /*(copy prior to calling fam_dir_monitor())*/ 1019 1020 #ifdef HAVE_FAM_H 1021 if (sc.stat_cache_engine == STAT_CACHE_ENGINE_FAM) { 1022 if (sce->fam_dir) --((fam_dir_entry *)sce->fam_dir)->refcnt; 1023 sce->fam_dir = 1024 fam_dir_monitor(sc.scf, CONST_BUF_LEN(name), &st); 1025 #if 0 /*(performed below)*/ 1026 if (NULL != sce->fam_dir) { 1027 /*(may have been invalidated by dir change)*/ 1028 sce->stat_ts = cur_ts; 1029 } 1030 #endif 1031 } 1032 #endif 1033 1034 sce->stat_ts = cur_ts; 1035 return sce; 1036 } 1037 1038 int stat_cache_path_contains_symlink(const buffer *name, log_error_st *errh) { 1039 /* caller should check for symlinks only if we should block symlinks. */ 1040 1041 /* catch the obvious symlinks 1042 * 1043 * this is not a secure check as we still have a race-condition between 1044 * the stat() and the open. We can only solve this by 1045 * 1. open() the file 1046 * 2. fstat() the fd 1047 * 1048 * and keeping the file open for the rest of the time. But this can 1049 * only be done at network level. 1050 * */ 1051 1052 #ifdef HAVE_LSTAT 1053 /* we assume "/" can not be symlink, 1054 * so skip the symlink stuff if path is "/" */ 1055 size_t len = buffer_string_length(name); 1056 force_assert(0 != len); 1057 force_assert(name->ptr[0] == '/'); 1058 if (1 == len) return 0; 1059 #ifndef PATH_MAX 1060 #define PATH_MAX 4096 1061 #endif 1062 if (len >= PATH_MAX) return -1; 1063 1064 char buf[PATH_MAX]; 1065 memcpy(buf, name->ptr, len); 1066 char *s_cur = buf+len; 1067 do { 1068 *s_cur = '\0'; 1069 struct stat st; 1070 if (0 == lstat(buf, &st)) { 1071 if (S_ISLNK(st.st_mode)) return 1; 1072 } 1073 else { 1074 log_perror(errh, __FILE__, __LINE__, "lstat failed for: %s", buf); 1075 return -1; 1076 } 1077 } while ((s_cur = strrchr(buf, '/')) > buf); /*(&buf[0]==buf; NULL < buf)*/ 1078 #endif 1079 1080 return 0; 1081 } 1082 1083 int stat_cache_open_rdonly_fstat (const buffer *name, struct stat *st, int symlinks) { 1084 /*(Note: O_NOFOLLOW affects only the final path segment, the target file, 1085 * not any intermediate symlinks along the path)*/ 1086 const int fd = fdevent_open_cloexec(name->ptr, symlinks, O_RDONLY, 0); 1087 if (fd >= 0) { 1088 if (0 == fstat(fd, st)) { 1089 return fd; 1090 } else { 1091 close(fd); 1092 } 1093 } 1094 return -1; 1095 } 1096 1097 /** 1098 * remove stat() from cache which haven't been stat()ed for 1099 * more than 2 seconds 1100 * 1101 * 1102 * walk though the stat-cache, collect the ids which are too old 1103 * and remove them in a second loop 1104 */ 1105 1106 static void stat_cache_tag_old_entries(splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts) { 1107 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 1108 if (t->left) 1109 stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts); 1110 if (t->right) 1111 stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts); 1112 if (*ndx == 8192) return; /*(must match num array entries in keys[])*/ 1113 1114 const stat_cache_entry * const sce = t->data; 1115 if (cur_ts - sce->stat_ts > max_age) 1116 keys[(*ndx)++] = t->key; 1117 } 1118 1119 static void stat_cache_periodic_cleanup(const time_t max_age, const time_t cur_ts) { 1120 splay_tree *sptree = sc.files; 1121 int max_ndx, i; 1122 int keys[8192]; /* 32k size on stack */ 1123 do { 1124 if (!sptree) break; 1125 max_ndx = 0; 1126 stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts); 1127 for (i = 0; i < max_ndx; ++i) { 1128 int ndx = keys[i]; 1129 sptree = splaytree_splay(sptree, ndx); 1130 if (sptree && sptree->key == ndx) { 1131 stat_cache_entry_free(sptree->data); 1132 sptree = splaytree_delete(sptree, ndx); 1133 } 1134 } 1135 } while (max_ndx == sizeof(keys)/sizeof(int)); 1136 sc.files = sptree; 1137 } 1138 1139 void stat_cache_trigger_cleanup(void) { 1140 time_t max_age = 2; 1141 1142 #ifdef HAVE_FAM_H 1143 if (STAT_CACHE_ENGINE_FAM == sc.stat_cache_engine) { 1144 if (log_epoch_secs & 0x1F) return; 1145 /* once every 32 seconds (0x1F == 31) */ 1146 max_age = 32; 1147 fam_dir_periodic_cleanup(); 1148 /* By doing this before stat_cache_periodic_cleanup(), 1149 * entries used within the next max_age secs will remain 1150 * monitored, instead of effectively flushing and 1151 * rebuilding the FAM monitoring every max_age seconds */ 1152 } 1153 #endif 1154 1155 stat_cache_periodic_cleanup(max_age, log_epoch_secs); 1156 } 1157