Return to ngx_open_file_cache.c CVS log | Up to [ELWIX - Embedded LightWeight unIX -] / embedaddon / nginx / src / core |
1.1 ! misho 1: ! 2: /* ! 3: * Copyright (C) Igor Sysoev ! 4: * Copyright (C) Nginx, Inc. ! 5: */ ! 6: ! 7: ! 8: #include <ngx_config.h> ! 9: #include <ngx_core.h> ! 10: #include <ngx_event.h> ! 11: ! 12: ! 13: /* ! 14: * open file cache caches ! 15: * open file handles with stat() info; ! 16: * directories stat() info; ! 17: * files and directories errors: not found, access denied, etc. ! 18: */ ! 19: ! 20: ! 21: #define NGX_MIN_READ_AHEAD (128 * 1024) ! 22: ! 23: ! 24: static void ngx_open_file_cache_cleanup(void *data); ! 25: #if (NGX_HAVE_OPENAT) ! 26: static ngx_fd_t ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name, ! 27: ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log); ! 28: #endif ! 29: static ngx_fd_t ngx_open_file_wrapper(ngx_str_t *name, ! 30: ngx_open_file_info_t *of, ngx_int_t mode, ngx_int_t create, ! 31: ngx_int_t access, ngx_log_t *log); ! 32: static ngx_int_t ngx_file_info_wrapper(ngx_str_t *name, ! 33: ngx_open_file_info_t *of, ngx_file_info_t *fi, ngx_log_t *log); ! 34: static ngx_int_t ngx_open_and_stat_file(ngx_str_t *name, ! 35: ngx_open_file_info_t *of, ngx_log_t *log); ! 36: static void ngx_open_file_add_event(ngx_open_file_cache_t *cache, ! 37: ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log); ! 38: static void ngx_open_file_cleanup(void *data); ! 39: static void ngx_close_cached_file(ngx_open_file_cache_t *cache, ! 40: ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log); ! 41: static void ngx_open_file_del_event(ngx_cached_open_file_t *file); ! 42: static void ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ! 43: ngx_uint_t n, ngx_log_t *log); ! 44: static void ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp, ! 45: ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); ! 46: static ngx_cached_open_file_t * ! 47: ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name, ! 48: uint32_t hash); ! 49: static void ngx_open_file_cache_remove(ngx_event_t *ev); ! 50: ! 51: ! 52: ngx_open_file_cache_t * ! 53: ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive) ! 54: { ! 55: ngx_pool_cleanup_t *cln; ! 56: ngx_open_file_cache_t *cache; ! 57: ! 58: cache = ngx_palloc(pool, sizeof(ngx_open_file_cache_t)); ! 59: if (cache == NULL) { ! 60: return NULL; ! 61: } ! 62: ! 63: ngx_rbtree_init(&cache->rbtree, &cache->sentinel, ! 64: ngx_open_file_cache_rbtree_insert_value); ! 65: ! 66: ngx_queue_init(&cache->expire_queue); ! 67: ! 68: cache->current = 0; ! 69: cache->max = max; ! 70: cache->inactive = inactive; ! 71: ! 72: cln = ngx_pool_cleanup_add(pool, 0); ! 73: if (cln == NULL) { ! 74: return NULL; ! 75: } ! 76: ! 77: cln->handler = ngx_open_file_cache_cleanup; ! 78: cln->data = cache; ! 79: ! 80: return cache; ! 81: } ! 82: ! 83: ! 84: static void ! 85: ngx_open_file_cache_cleanup(void *data) ! 86: { ! 87: ngx_open_file_cache_t *cache = data; ! 88: ! 89: ngx_queue_t *q; ! 90: ngx_cached_open_file_t *file; ! 91: ! 92: ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, ! 93: "open file cache cleanup"); ! 94: ! 95: for ( ;; ) { ! 96: ! 97: if (ngx_queue_empty(&cache->expire_queue)) { ! 98: break; ! 99: } ! 100: ! 101: q = ngx_queue_last(&cache->expire_queue); ! 102: ! 103: file = ngx_queue_data(q, ngx_cached_open_file_t, queue); ! 104: ! 105: ngx_queue_remove(q); ! 106: ! 107: ngx_rbtree_delete(&cache->rbtree, &file->node); ! 108: ! 109: cache->current--; ! 110: ! 111: ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, ! 112: "delete cached open file: %s", file->name); ! 113: ! 114: if (!file->err && !file->is_dir) { ! 115: file->close = 1; ! 116: file->count = 0; ! 117: ngx_close_cached_file(cache, file, 0, ngx_cycle->log); ! 118: ! 119: } else { ! 120: ngx_free(file->name); ! 121: ngx_free(file); ! 122: } ! 123: } ! 124: ! 125: if (cache->current) { ! 126: ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, ! 127: "%d items still leave in open file cache", ! 128: cache->current); ! 129: } ! 130: ! 131: if (cache->rbtree.root != cache->rbtree.sentinel) { ! 132: ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, ! 133: "rbtree still is not empty in open file cache"); ! 134: ! 135: } ! 136: } ! 137: ! 138: ! 139: ngx_int_t ! 140: ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ! 141: ngx_open_file_info_t *of, ngx_pool_t *pool) ! 142: { ! 143: time_t now; ! 144: uint32_t hash; ! 145: ngx_int_t rc; ! 146: ngx_file_info_t fi; ! 147: ngx_pool_cleanup_t *cln; ! 148: ngx_cached_open_file_t *file; ! 149: ngx_pool_cleanup_file_t *clnf; ! 150: ngx_open_file_cache_cleanup_t *ofcln; ! 151: ! 152: of->fd = NGX_INVALID_FILE; ! 153: of->err = 0; ! 154: ! 155: if (cache == NULL) { ! 156: ! 157: if (of->test_only) { ! 158: ! 159: if (ngx_file_info_wrapper(name, of, &fi, pool->log) ! 160: == NGX_FILE_ERROR) ! 161: { ! 162: return NGX_ERROR; ! 163: } ! 164: ! 165: of->uniq = ngx_file_uniq(&fi); ! 166: of->mtime = ngx_file_mtime(&fi); ! 167: of->size = ngx_file_size(&fi); ! 168: of->fs_size = ngx_file_fs_size(&fi); ! 169: of->is_dir = ngx_is_dir(&fi); ! 170: of->is_file = ngx_is_file(&fi); ! 171: of->is_link = ngx_is_link(&fi); ! 172: of->is_exec = ngx_is_exec(&fi); ! 173: ! 174: return NGX_OK; ! 175: } ! 176: ! 177: cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t)); ! 178: if (cln == NULL) { ! 179: return NGX_ERROR; ! 180: } ! 181: ! 182: rc = ngx_open_and_stat_file(name, of, pool->log); ! 183: ! 184: if (rc == NGX_OK && !of->is_dir) { ! 185: cln->handler = ngx_pool_cleanup_file; ! 186: clnf = cln->data; ! 187: ! 188: clnf->fd = of->fd; ! 189: clnf->name = name->data; ! 190: clnf->log = pool->log; ! 191: } ! 192: ! 193: return rc; ! 194: } ! 195: ! 196: cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t)); ! 197: if (cln == NULL) { ! 198: return NGX_ERROR; ! 199: } ! 200: ! 201: now = ngx_time(); ! 202: ! 203: hash = ngx_crc32_long(name->data, name->len); ! 204: ! 205: file = ngx_open_file_lookup(cache, name, hash); ! 206: ! 207: if (file) { ! 208: ! 209: file->uses++; ! 210: ! 211: ngx_queue_remove(&file->queue); ! 212: ! 213: if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) { ! 214: ! 215: /* file was not used often enough to keep open */ ! 216: ! 217: rc = ngx_open_and_stat_file(name, of, pool->log); ! 218: ! 219: if (rc != NGX_OK && (of->err == 0 || !of->errors)) { ! 220: goto failed; ! 221: } ! 222: ! 223: goto add_event; ! 224: } ! 225: ! 226: if (file->use_event ! 227: || (file->event == NULL ! 228: && (of->uniq == 0 || of->uniq == file->uniq) ! 229: && now - file->created < of->valid ! 230: #if (NGX_HAVE_OPENAT) ! 231: && of->disable_symlinks == file->disable_symlinks ! 232: && of->disable_symlinks_from == file->disable_symlinks_from ! 233: #endif ! 234: )) ! 235: { ! 236: if (file->err == 0) { ! 237: ! 238: of->fd = file->fd; ! 239: of->uniq = file->uniq; ! 240: of->mtime = file->mtime; ! 241: of->size = file->size; ! 242: ! 243: of->is_dir = file->is_dir; ! 244: of->is_file = file->is_file; ! 245: of->is_link = file->is_link; ! 246: of->is_exec = file->is_exec; ! 247: of->is_directio = file->is_directio; ! 248: ! 249: if (!file->is_dir) { ! 250: file->count++; ! 251: ngx_open_file_add_event(cache, file, of, pool->log); ! 252: } ! 253: ! 254: } else { ! 255: of->err = file->err; ! 256: #if (NGX_HAVE_OPENAT) ! 257: of->failed = file->disable_symlinks ? ngx_openat_file_n ! 258: : ngx_open_file_n; ! 259: #else ! 260: of->failed = ngx_open_file_n; ! 261: #endif ! 262: } ! 263: ! 264: goto found; ! 265: } ! 266: ! 267: ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0, ! 268: "retest open file: %s, fd:%d, c:%d, e:%d", ! 269: file->name, file->fd, file->count, file->err); ! 270: ! 271: if (file->is_dir) { ! 272: ! 273: /* ! 274: * chances that directory became file are very small ! 275: * so test_dir flag allows to use a single syscall ! 276: * in ngx_file_info() instead of three syscalls ! 277: */ ! 278: ! 279: of->test_dir = 1; ! 280: } ! 281: ! 282: of->fd = file->fd; ! 283: of->uniq = file->uniq; ! 284: ! 285: rc = ngx_open_and_stat_file(name, of, pool->log); ! 286: ! 287: if (rc != NGX_OK && (of->err == 0 || !of->errors)) { ! 288: goto failed; ! 289: } ! 290: ! 291: if (of->is_dir) { ! 292: ! 293: if (file->is_dir || file->err) { ! 294: goto update; ! 295: } ! 296: ! 297: /* file became directory */ ! 298: ! 299: } else if (of->err == 0) { /* file */ ! 300: ! 301: if (file->is_dir || file->err) { ! 302: goto add_event; ! 303: } ! 304: ! 305: if (of->uniq == file->uniq) { ! 306: ! 307: if (file->event) { ! 308: file->use_event = 1; ! 309: } ! 310: ! 311: of->is_directio = file->is_directio; ! 312: ! 313: goto update; ! 314: } ! 315: ! 316: /* file was changed */ ! 317: ! 318: } else { /* error to cache */ ! 319: ! 320: if (file->err || file->is_dir) { ! 321: goto update; ! 322: } ! 323: ! 324: /* file was removed, etc. */ ! 325: } ! 326: ! 327: if (file->count == 0) { ! 328: ! 329: ngx_open_file_del_event(file); ! 330: ! 331: if (ngx_close_file(file->fd) == NGX_FILE_ERROR) { ! 332: ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno, ! 333: ngx_close_file_n " \"%V\" failed", name); ! 334: } ! 335: ! 336: goto add_event; ! 337: } ! 338: ! 339: ngx_rbtree_delete(&cache->rbtree, &file->node); ! 340: ! 341: cache->current--; ! 342: ! 343: file->close = 1; ! 344: ! 345: goto create; ! 346: } ! 347: ! 348: /* not found */ ! 349: ! 350: rc = ngx_open_and_stat_file(name, of, pool->log); ! 351: ! 352: if (rc != NGX_OK && (of->err == 0 || !of->errors)) { ! 353: goto failed; ! 354: } ! 355: ! 356: create: ! 357: ! 358: if (cache->current >= cache->max) { ! 359: ngx_expire_old_cached_files(cache, 0, pool->log); ! 360: } ! 361: ! 362: file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log); ! 363: ! 364: if (file == NULL) { ! 365: goto failed; ! 366: } ! 367: ! 368: file->name = ngx_alloc(name->len + 1, pool->log); ! 369: ! 370: if (file->name == NULL) { ! 371: ngx_free(file); ! 372: file = NULL; ! 373: goto failed; ! 374: } ! 375: ! 376: ngx_cpystrn(file->name, name->data, name->len + 1); ! 377: ! 378: file->node.key = hash; ! 379: ! 380: ngx_rbtree_insert(&cache->rbtree, &file->node); ! 381: ! 382: cache->current++; ! 383: ! 384: file->uses = 1; ! 385: file->count = 0; ! 386: file->use_event = 0; ! 387: file->event = NULL; ! 388: ! 389: add_event: ! 390: ! 391: ngx_open_file_add_event(cache, file, of, pool->log); ! 392: ! 393: update: ! 394: ! 395: file->fd = of->fd; ! 396: file->err = of->err; ! 397: #if (NGX_HAVE_OPENAT) ! 398: file->disable_symlinks = of->disable_symlinks; ! 399: file->disable_symlinks_from = of->disable_symlinks_from; ! 400: #endif ! 401: ! 402: if (of->err == 0) { ! 403: file->uniq = of->uniq; ! 404: file->mtime = of->mtime; ! 405: file->size = of->size; ! 406: ! 407: file->close = 0; ! 408: ! 409: file->is_dir = of->is_dir; ! 410: file->is_file = of->is_file; ! 411: file->is_link = of->is_link; ! 412: file->is_exec = of->is_exec; ! 413: file->is_directio = of->is_directio; ! 414: ! 415: if (!of->is_dir) { ! 416: file->count++; ! 417: } ! 418: } ! 419: ! 420: file->created = now; ! 421: ! 422: found: ! 423: ! 424: file->accessed = now; ! 425: ! 426: ngx_queue_insert_head(&cache->expire_queue, &file->queue); ! 427: ! 428: ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0, ! 429: "cached open file: %s, fd:%d, c:%d, e:%d, u:%d", ! 430: file->name, file->fd, file->count, file->err, file->uses); ! 431: ! 432: if (of->err == 0) { ! 433: ! 434: if (!of->is_dir) { ! 435: cln->handler = ngx_open_file_cleanup; ! 436: ofcln = cln->data; ! 437: ! 438: ofcln->cache = cache; ! 439: ofcln->file = file; ! 440: ofcln->min_uses = of->min_uses; ! 441: ofcln->log = pool->log; ! 442: } ! 443: ! 444: return NGX_OK; ! 445: } ! 446: ! 447: return NGX_ERROR; ! 448: ! 449: failed: ! 450: ! 451: if (file) { ! 452: ngx_rbtree_delete(&cache->rbtree, &file->node); ! 453: ! 454: cache->current--; ! 455: ! 456: if (file->count == 0) { ! 457: ! 458: if (file->fd != NGX_INVALID_FILE) { ! 459: if (ngx_close_file(file->fd) == NGX_FILE_ERROR) { ! 460: ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno, ! 461: ngx_close_file_n " \"%s\" failed", ! 462: file->name); ! 463: } ! 464: } ! 465: ! 466: ngx_free(file->name); ! 467: ngx_free(file); ! 468: ! 469: } else { ! 470: file->close = 1; ! 471: } ! 472: } ! 473: ! 474: if (of->fd != NGX_INVALID_FILE) { ! 475: if (ngx_close_file(of->fd) == NGX_FILE_ERROR) { ! 476: ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno, ! 477: ngx_close_file_n " \"%V\" failed", name); ! 478: } ! 479: } ! 480: ! 481: return NGX_ERROR; ! 482: } ! 483: ! 484: ! 485: #if (NGX_HAVE_OPENAT) ! 486: ! 487: static ngx_fd_t ! 488: ngx_openat_file_owner(ngx_fd_t at_fd, const u_char *name, ! 489: ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log) ! 490: { ! 491: ngx_fd_t fd; ! 492: ngx_err_t err; ! 493: ngx_file_info_t fi, atfi; ! 494: ! 495: /* ! 496: * To allow symlinks with the same owner, use openat() (followed ! 497: * by fstat()) and fstatat(AT_SYMLINK_NOFOLLOW), and then compare ! 498: * uids between fstat() and fstatat(). ! 499: * ! 500: * As there is a race between openat() and fstatat() we don't ! 501: * know if openat() in fact opened symlink or not. Therefore, ! 502: * we have to compare uids even if fstatat() reports the opened ! 503: * component isn't a symlink (as we don't know whether it was ! 504: * symlink during openat() or not). ! 505: */ ! 506: ! 507: fd = ngx_openat_file(at_fd, name, mode, create, access); ! 508: ! 509: if (fd == NGX_INVALID_FILE) { ! 510: return NGX_INVALID_FILE; ! 511: } ! 512: ! 513: if (ngx_file_at_info(at_fd, name, &atfi, AT_SYMLINK_NOFOLLOW) ! 514: == NGX_FILE_ERROR) ! 515: { ! 516: err = ngx_errno; ! 517: goto failed; ! 518: } ! 519: ! 520: if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { ! 521: err = ngx_errno; ! 522: goto failed; ! 523: } ! 524: ! 525: if (fi.st_uid != atfi.st_uid) { ! 526: err = NGX_ELOOP; ! 527: goto failed; ! 528: } ! 529: ! 530: return fd; ! 531: ! 532: failed: ! 533: ! 534: if (ngx_close_file(fd) == NGX_FILE_ERROR) { ! 535: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 536: ngx_close_file_n " \"%V\" failed", name); ! 537: } ! 538: ! 539: ngx_set_errno(err); ! 540: ! 541: return NGX_INVALID_FILE; ! 542: } ! 543: ! 544: #endif ! 545: ! 546: ! 547: static ngx_fd_t ! 548: ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of, ! 549: ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log) ! 550: { ! 551: ngx_fd_t fd; ! 552: ! 553: #if !(NGX_HAVE_OPENAT) ! 554: ! 555: fd = ngx_open_file(name->data, mode, create, access); ! 556: ! 557: if (fd == NGX_INVALID_FILE) { ! 558: of->err = ngx_errno; ! 559: of->failed = ngx_open_file_n; ! 560: return NGX_INVALID_FILE; ! 561: } ! 562: ! 563: return fd; ! 564: ! 565: #else ! 566: ! 567: u_char *p, *cp, *end; ! 568: ngx_fd_t at_fd; ! 569: ngx_str_t at_name; ! 570: ! 571: if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) { ! 572: fd = ngx_open_file(name->data, mode, create, access); ! 573: ! 574: if (fd == NGX_INVALID_FILE) { ! 575: of->err = ngx_errno; ! 576: of->failed = ngx_open_file_n; ! 577: return NGX_INVALID_FILE; ! 578: } ! 579: ! 580: return fd; ! 581: } ! 582: ! 583: p = name->data; ! 584: end = p + name->len; ! 585: ! 586: at_name = *name; ! 587: ! 588: if (of->disable_symlinks_from) { ! 589: ! 590: cp = p + of->disable_symlinks_from; ! 591: ! 592: *cp = '\0'; ! 593: ! 594: at_fd = ngx_open_file(p, NGX_FILE_SEARCH|NGX_FILE_NONBLOCK, ! 595: NGX_FILE_OPEN, 0); ! 596: ! 597: *cp = '/'; ! 598: ! 599: if (at_fd == NGX_INVALID_FILE) { ! 600: of->err = ngx_errno; ! 601: of->failed = ngx_open_file_n; ! 602: return NGX_INVALID_FILE; ! 603: } ! 604: ! 605: at_name.len = of->disable_symlinks_from; ! 606: p = cp + 1; ! 607: ! 608: } else if (*p == '/') { ! 609: ! 610: at_fd = ngx_open_file("/", ! 611: NGX_FILE_SEARCH|NGX_FILE_NONBLOCK, ! 612: NGX_FILE_OPEN, 0); ! 613: ! 614: if (at_fd == NGX_INVALID_FILE) { ! 615: of->err = ngx_errno; ! 616: of->failed = ngx_openat_file_n; ! 617: return NGX_INVALID_FILE; ! 618: } ! 619: ! 620: at_name.len = 1; ! 621: p++; ! 622: ! 623: } else { ! 624: at_fd = NGX_AT_FDCWD; ! 625: } ! 626: ! 627: for ( ;; ) { ! 628: cp = ngx_strlchr(p, end, '/'); ! 629: if (cp == NULL) { ! 630: break; ! 631: } ! 632: ! 633: if (cp == p) { ! 634: p++; ! 635: continue; ! 636: } ! 637: ! 638: *cp = '\0'; ! 639: ! 640: if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) { ! 641: fd = ngx_openat_file_owner(at_fd, p, ! 642: NGX_FILE_SEARCH|NGX_FILE_NONBLOCK, ! 643: NGX_FILE_OPEN, 0, log); ! 644: ! 645: } else { ! 646: fd = ngx_openat_file(at_fd, p, ! 647: NGX_FILE_SEARCH|NGX_FILE_NONBLOCK|NGX_FILE_NOFOLLOW, ! 648: NGX_FILE_OPEN, 0); ! 649: } ! 650: ! 651: *cp = '/'; ! 652: ! 653: if (fd == NGX_INVALID_FILE) { ! 654: of->err = ngx_errno; ! 655: of->failed = ngx_openat_file_n; ! 656: goto failed; ! 657: } ! 658: ! 659: if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) { ! 660: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 661: ngx_close_file_n " \"%V\" failed", &at_name); ! 662: } ! 663: ! 664: p = cp + 1; ! 665: at_fd = fd; ! 666: at_name.len = cp - at_name.data; ! 667: } ! 668: ! 669: if (p == end) { ! 670: ! 671: /* ! 672: * If pathname ends with a trailing slash, assume the last path ! 673: * component is a directory and reopen it with requested flags; ! 674: * if not, fail with ENOTDIR as per POSIX. ! 675: * ! 676: * We cannot rely on O_DIRECTORY in the loop above to check ! 677: * that the last path component is a directory because ! 678: * O_DIRECTORY doesn't work on FreeBSD 8. Fortunately, by ! 679: * reopening a directory, we don't depend on it at all. ! 680: */ ! 681: ! 682: fd = ngx_openat_file(at_fd, ".", mode, create, access); ! 683: goto done; ! 684: } ! 685: ! 686: if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER ! 687: && !(create & (NGX_FILE_CREATE_OR_OPEN|NGX_FILE_TRUNCATE))) ! 688: { ! 689: fd = ngx_openat_file_owner(at_fd, p, mode, create, access, log); ! 690: ! 691: } else { ! 692: fd = ngx_openat_file(at_fd, p, mode|NGX_FILE_NOFOLLOW, create, access); ! 693: } ! 694: ! 695: done: ! 696: ! 697: if (fd == NGX_INVALID_FILE) { ! 698: of->err = ngx_errno; ! 699: of->failed = ngx_openat_file_n; ! 700: } ! 701: ! 702: failed: ! 703: ! 704: if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) { ! 705: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 706: ngx_close_file_n " \"%V\" failed", &at_name); ! 707: } ! 708: ! 709: return fd; ! 710: #endif ! 711: } ! 712: ! 713: ! 714: static ngx_int_t ! 715: ngx_file_info_wrapper(ngx_str_t *name, ngx_open_file_info_t *of, ! 716: ngx_file_info_t *fi, ngx_log_t *log) ! 717: { ! 718: ngx_int_t rc; ! 719: ! 720: #if !(NGX_HAVE_OPENAT) ! 721: ! 722: rc = ngx_file_info(name->data, fi); ! 723: ! 724: if (rc == NGX_FILE_ERROR) { ! 725: of->err = ngx_errno; ! 726: of->failed = ngx_file_info_n; ! 727: return NGX_FILE_ERROR; ! 728: } ! 729: ! 730: return rc; ! 731: ! 732: #else ! 733: ! 734: ngx_fd_t fd; ! 735: ! 736: if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) { ! 737: ! 738: rc = ngx_file_info(name->data, fi); ! 739: ! 740: if (rc == NGX_FILE_ERROR) { ! 741: of->err = ngx_errno; ! 742: of->failed = ngx_file_info_n; ! 743: return NGX_FILE_ERROR; ! 744: } ! 745: ! 746: return rc; ! 747: } ! 748: ! 749: fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, ! 750: NGX_FILE_OPEN, 0, log); ! 751: ! 752: if (fd == NGX_INVALID_FILE) { ! 753: return NGX_FILE_ERROR; ! 754: } ! 755: ! 756: rc = ngx_fd_info(fd, fi); ! 757: ! 758: if (rc == NGX_FILE_ERROR) { ! 759: of->err = ngx_errno; ! 760: of->failed = ngx_fd_info_n; ! 761: } ! 762: ! 763: if (ngx_close_file(fd) == NGX_FILE_ERROR) { ! 764: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 765: ngx_close_file_n " \"%V\" failed", name); ! 766: } ! 767: ! 768: return rc; ! 769: #endif ! 770: } ! 771: ! 772: ! 773: static ngx_int_t ! 774: ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of, ! 775: ngx_log_t *log) ! 776: { ! 777: ngx_fd_t fd; ! 778: ngx_file_info_t fi; ! 779: ! 780: if (of->fd != NGX_INVALID_FILE) { ! 781: ! 782: if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) { ! 783: of->fd = NGX_INVALID_FILE; ! 784: return NGX_ERROR; ! 785: } ! 786: ! 787: if (of->uniq == ngx_file_uniq(&fi)) { ! 788: goto done; ! 789: } ! 790: ! 791: } else if (of->test_dir) { ! 792: ! 793: if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) { ! 794: of->fd = NGX_INVALID_FILE; ! 795: return NGX_ERROR; ! 796: } ! 797: ! 798: if (ngx_is_dir(&fi)) { ! 799: goto done; ! 800: } ! 801: } ! 802: ! 803: if (!of->log) { ! 804: ! 805: /* ! 806: * Use non-blocking open() not to hang on FIFO files, etc. ! 807: * This flag has no effect on a regular files. ! 808: */ ! 809: ! 810: fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK, ! 811: NGX_FILE_OPEN, 0, log); ! 812: ! 813: } else { ! 814: fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND, ! 815: NGX_FILE_CREATE_OR_OPEN, ! 816: NGX_FILE_DEFAULT_ACCESS, log); ! 817: } ! 818: ! 819: if (fd == NGX_INVALID_FILE) { ! 820: of->fd = NGX_INVALID_FILE; ! 821: return NGX_ERROR; ! 822: } ! 823: ! 824: if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) { ! 825: ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, ! 826: ngx_fd_info_n " \"%V\" failed", name); ! 827: ! 828: if (ngx_close_file(fd) == NGX_FILE_ERROR) { ! 829: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 830: ngx_close_file_n " \"%V\" failed", name); ! 831: } ! 832: ! 833: of->fd = NGX_INVALID_FILE; ! 834: ! 835: return NGX_ERROR; ! 836: } ! 837: ! 838: if (ngx_is_dir(&fi)) { ! 839: if (ngx_close_file(fd) == NGX_FILE_ERROR) { ! 840: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 841: ngx_close_file_n " \"%V\" failed", name); ! 842: } ! 843: ! 844: of->fd = NGX_INVALID_FILE; ! 845: ! 846: } else { ! 847: of->fd = fd; ! 848: ! 849: if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) { ! 850: if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) { ! 851: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 852: ngx_read_ahead_n " \"%V\" failed", name); ! 853: } ! 854: } ! 855: ! 856: if (of->directio <= ngx_file_size(&fi)) { ! 857: if (ngx_directio_on(fd) == NGX_FILE_ERROR) { ! 858: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 859: ngx_directio_on_n " \"%V\" failed", name); ! 860: ! 861: } else { ! 862: of->is_directio = 1; ! 863: } ! 864: } ! 865: } ! 866: ! 867: done: ! 868: ! 869: of->uniq = ngx_file_uniq(&fi); ! 870: of->mtime = ngx_file_mtime(&fi); ! 871: of->size = ngx_file_size(&fi); ! 872: of->fs_size = ngx_file_fs_size(&fi); ! 873: of->is_dir = ngx_is_dir(&fi); ! 874: of->is_file = ngx_is_file(&fi); ! 875: of->is_link = ngx_is_link(&fi); ! 876: of->is_exec = ngx_is_exec(&fi); ! 877: ! 878: return NGX_OK; ! 879: } ! 880: ! 881: ! 882: /* ! 883: * we ignore any possible event setting error and ! 884: * fallback to usual periodic file retests ! 885: */ ! 886: ! 887: static void ! 888: ngx_open_file_add_event(ngx_open_file_cache_t *cache, ! 889: ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log) ! 890: { ! 891: ngx_open_file_cache_event_t *fev; ! 892: ! 893: if (!(ngx_event_flags & NGX_USE_VNODE_EVENT) ! 894: || !of->events ! 895: || file->event ! 896: || of->fd == NGX_INVALID_FILE ! 897: || file->uses < of->min_uses) ! 898: { ! 899: return; ! 900: } ! 901: ! 902: file->use_event = 0; ! 903: ! 904: file->event = ngx_calloc(sizeof(ngx_event_t), log); ! 905: if (file->event== NULL) { ! 906: return; ! 907: } ! 908: ! 909: fev = ngx_alloc(sizeof(ngx_open_file_cache_event_t), log); ! 910: if (fev == NULL) { ! 911: ngx_free(file->event); ! 912: file->event = NULL; ! 913: return; ! 914: } ! 915: ! 916: fev->fd = of->fd; ! 917: fev->file = file; ! 918: fev->cache = cache; ! 919: ! 920: file->event->handler = ngx_open_file_cache_remove; ! 921: file->event->data = fev; ! 922: ! 923: /* ! 924: * although vnode event may be called while ngx_cycle->poll ! 925: * destruction, however, cleanup procedures are run before any ! 926: * memory freeing and events will be canceled. ! 927: */ ! 928: ! 929: file->event->log = ngx_cycle->log; ! 930: ! 931: if (ngx_add_event(file->event, NGX_VNODE_EVENT, NGX_ONESHOT_EVENT) ! 932: != NGX_OK) ! 933: { ! 934: ngx_free(file->event->data); ! 935: ngx_free(file->event); ! 936: file->event = NULL; ! 937: return; ! 938: } ! 939: ! 940: /* ! 941: * we do not set file->use_event here because there may be a race ! 942: * condition: a file may be deleted between opening the file and ! 943: * adding event, so we rely upon event notification only after ! 944: * one file revalidation on next file access ! 945: */ ! 946: ! 947: return; ! 948: } ! 949: ! 950: ! 951: static void ! 952: ngx_open_file_cleanup(void *data) ! 953: { ! 954: ngx_open_file_cache_cleanup_t *c = data; ! 955: ! 956: c->file->count--; ! 957: ! 958: ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log); ! 959: ! 960: /* drop one or two expired open files */ ! 961: ngx_expire_old_cached_files(c->cache, 1, c->log); ! 962: } ! 963: ! 964: ! 965: static void ! 966: ngx_close_cached_file(ngx_open_file_cache_t *cache, ! 967: ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log) ! 968: { ! 969: ngx_log_debug5(NGX_LOG_DEBUG_CORE, log, 0, ! 970: "close cached open file: %s, fd:%d, c:%d, u:%d, %d", ! 971: file->name, file->fd, file->count, file->uses, file->close); ! 972: ! 973: if (!file->close) { ! 974: ! 975: file->accessed = ngx_time(); ! 976: ! 977: ngx_queue_remove(&file->queue); ! 978: ! 979: ngx_queue_insert_head(&cache->expire_queue, &file->queue); ! 980: ! 981: if (file->uses >= min_uses || file->count) { ! 982: return; ! 983: } ! 984: } ! 985: ! 986: ngx_open_file_del_event(file); ! 987: ! 988: if (file->count) { ! 989: return; ! 990: } ! 991: ! 992: if (file->fd != NGX_INVALID_FILE) { ! 993: ! 994: if (ngx_close_file(file->fd) == NGX_FILE_ERROR) { ! 995: ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ! 996: ngx_close_file_n " \"%s\" failed", file->name); ! 997: } ! 998: ! 999: file->fd = NGX_INVALID_FILE; ! 1000: } ! 1001: ! 1002: if (!file->close) { ! 1003: return; ! 1004: } ! 1005: ! 1006: ngx_free(file->name); ! 1007: ngx_free(file); ! 1008: } ! 1009: ! 1010: ! 1011: static void ! 1012: ngx_open_file_del_event(ngx_cached_open_file_t *file) ! 1013: { ! 1014: if (file->event == NULL) { ! 1015: return; ! 1016: } ! 1017: ! 1018: (void) ngx_del_event(file->event, NGX_VNODE_EVENT, ! 1019: file->count ? NGX_FLUSH_EVENT : NGX_CLOSE_EVENT); ! 1020: ! 1021: ngx_free(file->event->data); ! 1022: ngx_free(file->event); ! 1023: file->event = NULL; ! 1024: file->use_event = 0; ! 1025: } ! 1026: ! 1027: ! 1028: static void ! 1029: ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ngx_uint_t n, ! 1030: ngx_log_t *log) ! 1031: { ! 1032: time_t now; ! 1033: ngx_queue_t *q; ! 1034: ngx_cached_open_file_t *file; ! 1035: ! 1036: now = ngx_time(); ! 1037: ! 1038: /* ! 1039: * n == 1 deletes one or two inactive files ! 1040: * n == 0 deletes least recently used file by force ! 1041: * and one or two inactive files ! 1042: */ ! 1043: ! 1044: while (n < 3) { ! 1045: ! 1046: if (ngx_queue_empty(&cache->expire_queue)) { ! 1047: return; ! 1048: } ! 1049: ! 1050: q = ngx_queue_last(&cache->expire_queue); ! 1051: ! 1052: file = ngx_queue_data(q, ngx_cached_open_file_t, queue); ! 1053: ! 1054: if (n++ != 0 && now - file->accessed <= cache->inactive) { ! 1055: return; ! 1056: } ! 1057: ! 1058: ngx_queue_remove(q); ! 1059: ! 1060: ngx_rbtree_delete(&cache->rbtree, &file->node); ! 1061: ! 1062: cache->current--; ! 1063: ! 1064: ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, ! 1065: "expire cached open file: %s", file->name); ! 1066: ! 1067: if (!file->err && !file->is_dir) { ! 1068: file->close = 1; ! 1069: ngx_close_cached_file(cache, file, 0, log); ! 1070: ! 1071: } else { ! 1072: ngx_free(file->name); ! 1073: ngx_free(file); ! 1074: } ! 1075: } ! 1076: } ! 1077: ! 1078: ! 1079: static void ! 1080: ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp, ! 1081: ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) ! 1082: { ! 1083: ngx_rbtree_node_t **p; ! 1084: ngx_cached_open_file_t *file, *file_temp; ! 1085: ! 1086: for ( ;; ) { ! 1087: ! 1088: if (node->key < temp->key) { ! 1089: ! 1090: p = &temp->left; ! 1091: ! 1092: } else if (node->key > temp->key) { ! 1093: ! 1094: p = &temp->right; ! 1095: ! 1096: } else { /* node->key == temp->key */ ! 1097: ! 1098: file = (ngx_cached_open_file_t *) node; ! 1099: file_temp = (ngx_cached_open_file_t *) temp; ! 1100: ! 1101: p = (ngx_strcmp(file->name, file_temp->name) < 0) ! 1102: ? &temp->left : &temp->right; ! 1103: } ! 1104: ! 1105: if (*p == sentinel) { ! 1106: break; ! 1107: } ! 1108: ! 1109: temp = *p; ! 1110: } ! 1111: ! 1112: *p = node; ! 1113: node->parent = temp; ! 1114: node->left = sentinel; ! 1115: node->right = sentinel; ! 1116: ngx_rbt_red(node); ! 1117: } ! 1118: ! 1119: ! 1120: static ngx_cached_open_file_t * ! 1121: ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name, ! 1122: uint32_t hash) ! 1123: { ! 1124: ngx_int_t rc; ! 1125: ngx_rbtree_node_t *node, *sentinel; ! 1126: ngx_cached_open_file_t *file; ! 1127: ! 1128: node = cache->rbtree.root; ! 1129: sentinel = cache->rbtree.sentinel; ! 1130: ! 1131: while (node != sentinel) { ! 1132: ! 1133: if (hash < node->key) { ! 1134: node = node->left; ! 1135: continue; ! 1136: } ! 1137: ! 1138: if (hash > node->key) { ! 1139: node = node->right; ! 1140: continue; ! 1141: } ! 1142: ! 1143: /* hash == node->key */ ! 1144: ! 1145: file = (ngx_cached_open_file_t *) node; ! 1146: ! 1147: rc = ngx_strcmp(name->data, file->name); ! 1148: ! 1149: if (rc == 0) { ! 1150: return file; ! 1151: } ! 1152: ! 1153: node = (rc < 0) ? node->left : node->right; ! 1154: } ! 1155: ! 1156: return NULL; ! 1157: } ! 1158: ! 1159: ! 1160: static void ! 1161: ngx_open_file_cache_remove(ngx_event_t *ev) ! 1162: { ! 1163: ngx_cached_open_file_t *file; ! 1164: ngx_open_file_cache_event_t *fev; ! 1165: ! 1166: fev = ev->data; ! 1167: file = fev->file; ! 1168: ! 1169: ngx_queue_remove(&file->queue); ! 1170: ! 1171: ngx_rbtree_delete(&fev->cache->rbtree, &file->node); ! 1172: ! 1173: fev->cache->current--; ! 1174: ! 1175: /* NGX_ONESHOT_EVENT was already deleted */ ! 1176: file->event = NULL; ! 1177: file->use_event = 0; ! 1178: ! 1179: file->close = 1; ! 1180: ! 1181: ngx_close_cached_file(fev->cache, file, 0, ev->log); ! 1182: ! 1183: /* free memory only when fev->cache and fev->file are already not needed */ ! 1184: ! 1185: ngx_free(ev->data); ! 1186: ngx_free(ev); ! 1187: }