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 } 754