1
2 /*
3 * Copyright (C) Igor Sysoev
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include <ngx_event.h>
10
11
12 /*
13 * open file cache caches
14 * open file handles with stat() info;
15 * directories stat() info;
16 * files and directories errors: not found, access denied, etc.
17 */
18
19
20 static void ngx_open_file_cache_cleanup(void *data);
21 static ngx_int_t ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of,
22 ngx_log_t *log);
23 static void ngx_open_file_add_event(ngx_open_file_cache_t *cache,
24 ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log);
25 static void ngx_open_file_cleanup(void *data);
26 static void ngx_close_cached_file(ngx_open_file_cache_t *cache,
27 ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log);
28 static void ngx_open_file_del_event(ngx_cached_open_file_t *file);
29 static void ngx_expire_old_cached_files(ngx_open_file_cache_t *cache,
30 ngx_uint_t n, ngx_log_t *log);
31 static void ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
32 ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
33 static ngx_cached_open_file_t *
34 ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
35 uint32_t hash);
36 static void ngx_open_file_cache_remove(ngx_event_t *ev);
37
38
39 ngx_open_file_cache_t *
40 ngx_open_file_cache_init(ngx_pool_t *pool, ngx_uint_t max, time_t inactive)
41 {
42 ngx_pool_cleanup_t *cln;
43 ngx_open_file_cache_t *cache;
44
45 cache = ngx_palloc(pool, sizeof(ngx_open_file_cache_t));
46 if (cache == NULL) {
47 return NULL;
48 }
49
50 ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
51 ngx_open_file_cache_rbtree_insert_value);
52
53 ngx_queue_init(&cache->expire_queue);
54
55 cache->current = 0;
56 cache->max = max;
57 cache->inactive = inactive;
58
59 cln = ngx_pool_cleanup_add(pool, 0);
60 if (cln == NULL) {
61 return NULL;
62 }
63
64 cln->handler = ngx_open_file_cache_cleanup;
65 cln->data = cache;
66
67 return cache;
68 }
69
70
71 static void
72 ngx_open_file_cache_cleanup(void *data)
73 {
74 ngx_open_file_cache_t *cache = data;
75
76 ngx_queue_t *q;
77 ngx_cached_open_file_t *file;
78
79 ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
80 "open file cache cleanup");
81
82 for ( ;; ) {
83
84 if (ngx_queue_empty(&cache->expire_queue)) {
85 break;
86 }
87
88 q = ngx_queue_last(&cache->expire_queue);
89
90 file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
91
92 ngx_queue_remove(q);
93
94 ngx_rbtree_delete(&cache->rbtree, &file->node);
95
96 cache->current--;
97
98 ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
99 "delete cached open file: %s", file->name);
100
101 if (!file->err && !file->is_dir) {
102 file->close = 1;
103 file->count = 0;
104 ngx_close_cached_file(cache, file, 0, ngx_cycle->log);
105
106 } else {
107 ngx_free(file->name);
108 ngx_free(file);
109 }
110 }
111
112 if (cache->current) {
113 ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
114 "%d items still leave in open file cache",
115 cache->current);
116 }
117
118 if (cache->rbtree.root != cache->rbtree.sentinel) {
119 ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
120 "rbtree still is not empty in open file cache");
121
122 }
123 }
124
125
126 ngx_int_t
127 ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
128 ngx_open_file_info_t *of, ngx_pool_t *pool)
129 {
130 time_t now;
131 uint32_t hash;
132 ngx_int_t rc;
133 ngx_pool_cleanup_t *cln;
134 ngx_cached_open_file_t *file;
135 ngx_pool_cleanup_file_t *clnf;
136 ngx_open_file_cache_cleanup_t *ofcln;
137
138 of->err = 0;
139
140 if (cache == NULL) {
141
142 cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
143 if (cln == NULL) {
144 return NGX_ERROR;
145 }
146
147 rc = ngx_open_and_stat_file(name->data, of, pool->log);
148
149 if (rc == NGX_OK && !of->is_dir) {
150 cln->handler = ngx_pool_cleanup_file;
151 clnf = cln->data;
152
153 clnf->fd = of->fd;
154 clnf->name = name->data;
155 clnf->log = pool->log;
156 }
157
158 return rc;
159 }
160
161 cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t));
162 if (cln == NULL) {
163 return NGX_ERROR;
164 }
165
166 now = ngx_time();
167
168 hash = ngx_crc32_long(name->data, name->len);
169
170 file = ngx_open_file_lookup(cache, name, hash);
171
172 if (file) {
173
174 file->uses++;
175
176 ngx_queue_remove(&file->queue);
177
178 if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) {
179
180 /* file was not used often enough to keep open */
181
182 rc = ngx_open_and_stat_file(name->data, of, pool->log);
183
184 if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
185 goto failed;
186 }
187
188 goto add_event;
189 }
190
191 if ((file->event && file->use_event)
192 || (file->event == NULL && now - file->created < of->valid))
193 {
194 if (file->err == 0) {
195
196 of->fd = file->fd;
197 of->uniq = file->uniq;
198 of->mtime = file->mtime;
199 of->size = file->size;
200
201 of->is_dir = file->is_dir;
202 of->is_file = file->is_file;
203 of->is_link = file->is_link;
204 of->is_exec = file->is_exec;
205
206 if (!file->is_dir) {
207 file->count++;
208 ngx_open_file_add_event(cache, file, of, pool->log);
209 }
210
211 } else {
212 of->err = file->err;
213 }
214
215 goto found;
216 }
217
218 ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
219 "retest open file: %s, fd:%d, c:%d, e:%d",
220 file->name, file->fd, file->count, file->err);
221
222 if (file->is_dir) {
223
224 /*
225 * chances that directory became file are very small
226 * so test_dir flag allows to use a single syscall
227 * in ngx_file_info() instead of three syscalls
228 */
229
230 of->test_dir = 1;
231 }
232
233 rc = ngx_open_and_stat_file(name->data, of, pool->log);
234
235 if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
236 goto failed;
237 }
238
239 if (of->is_dir) {
240
241 if (file->is_dir || file->err) {
242 goto update;
243 }
244
245 /* file became directory */
246
247 } else if (of->err == 0) { /* file */
248
249 if (file->is_dir || file->err) {
250 goto add_event;
251 }
252
253 if (of->uniq == file->uniq
254 && of->mtime == file->mtime
255 && of->size == file->size)
256 {
257 if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
258 ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
259 ngx_close_file_n " \"%s\" failed",
260 name->data);
261 }
262
263 of->fd = file->fd;
264 file->count++;
265
266 if (file->event) {
267 file->use_event = 1;
268 goto renew;
269 }
270
271 ngx_open_file_add_event(cache, file, of, pool->log);
272
273 goto renew;
274 }
275
276 /* file was changed */
277
278 } else { /* error to cache */
279
280 if (file->err || file->is_dir) {
281 goto update;
282 }
283
284 /* file was removed, etc. */
285 }
286
287 if (file->count == 0) {
288
289 ngx_open_file_del_event(file);
290
291 if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
292 ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
293 ngx_close_file_n " \"%s\" failed",
294 name->data);
295 }
296
297 goto add_event;
298 }
299
300 ngx_rbtree_delete(&cache->rbtree, &file->node);
301
302 cache->current--;
303
304 file->close = 1;
305
306 goto create;
307 }
308
309 /* not found */
310
311 file = NULL;
312
313 rc = ngx_open_and_stat_file(name->data, of, pool->log);
314
315 if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
316 goto failed;
317 }
318
319 create:
320
321 if (cache->current >= cache->max) {
322 ngx_expire_old_cached_files(cache, 0, pool->log);
323 }
324
325 file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log);
326
327 if (file == NULL) {
328 goto failed;
329 }
330
331 file->name = ngx_alloc(name->len + 1, pool->log);
332
333 if (file->name == NULL) {
334 ngx_free(file);
335 file = NULL;
336 goto failed;
337 }
338
339 ngx_cpystrn(file->name, name->data, name->len + 1);
340
341 file->node.key = hash;
342
343 ngx_rbtree_insert(&cache->rbtree, &file->node);
344
345 cache->current++;
346
347 file->uses = 1;
348 file->count = 0;
349 file->use_event = 0;
350 file->event = NULL;
351
352 add_event:
353
354 ngx_open_file_add_event(cache, file, of, pool->log);
355
356 update:
357
358 file->fd = of->fd;
359 file->err = of->err;
360
361 if (of->err == 0) {
362 file->uniq = of->uniq;
363 file->mtime = of->mtime;
364 file->size = of->size;
365
366 file->close = 0;
367
368 file->is_dir = of->is_dir;
369 file->is_file = of->is_file;
370 file->is_link = of->is_link;
371 file->is_exec = of->is_exec;
372
373 if (!of->is_dir) {
374 file->count++;
375 }
376 }
377
378 renew:
379
380 file->created = now;
381
382 found:
383
384 file->accessed = now;
385
386 ngx_queue_insert_head(&cache->expire_queue, &file->queue);
387
388 ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
389 "cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
390 file->name, file->fd, file->count, file->err, file->uses);
391
392 if (of->err == 0) {
393
394 if (!of->is_dir) {
395 cln->handler = ngx_open_file_cleanup;
396 ofcln = cln->data;
397
398 ofcln->cache = cache;
399 ofcln->file = file;
400 ofcln->min_uses = of->min_uses;
401 ofcln->log = pool->log;
402 }
403
404 return NGX_OK;
405 }
406
407 return NGX_ERROR;
408
409 failed:
410
411 if (file && file->count == 0) {
412 ngx_rbtree_delete(&cache->rbtree, &file->node);
413
414 cache->current--;
415
416 if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
417 ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
418 ngx_close_file_n " \"%s\" failed", file->name);
419 }
420
421 ngx_free(file->name);
422 ngx_free(file);
423 }
424
425 if (of->fd != NGX_INVALID_FILE) {
426 if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
427 ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
428 ngx_close_file_n " \"%s\" failed", name->data);
429 }
430 }
431
432 return NGX_ERROR;
433 }
434
435
436 static ngx_int_t
437 ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of, ngx_log_t *log)
438 {
439 ngx_fd_t fd;
440 ngx_file_info_t fi;
441
442 of->fd = NGX_INVALID_FILE;
443
444 if (of->test_dir) {
445
446 if (ngx_file_info(name, &fi) == -1) {
447 of->err = ngx_errno;
448
449 return NGX_ERROR;
450 }
451
452 of->uniq = ngx_file_uniq(&fi);
453 of->mtime = ngx_file_mtime(&fi);
454 of->size = ngx_file_size(&fi);
455 of->is_dir = ngx_is_dir(&fi);
456 of->is_file = ngx_is_file(&fi);
457 of->is_link = ngx_is_link(&fi);
458 of->is_exec = ngx_is_exec(&fi);
459
460 if (of->is_dir) {
461 return NGX_OK;
462 }
463 }
464
465 fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
466
467 if (fd == NGX_INVALID_FILE) {
468 of->err = ngx_errno;
469 return NGX_ERROR;
470 }
471
472 if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
473 ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
474 ngx_fd_info_n " \"%s\" failed", name);
475
476 if (ngx_close_file(fd) == NGX_FILE_ERROR) {
477 ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
478 ngx_close_file_n " \"%s\" failed", name);
479 }
480
481 return NGX_ERROR;
482 }
483
484 if (ngx_is_dir(&fi)) {
485 if (ngx_close_file(fd) == NGX_FILE_ERROR) {
486 ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
487 ngx_close_file_n " \"%s\" failed", name);
488 }
489
490 fd = NGX_INVALID_FILE;
491 }
492
493 of->fd = fd;
494 of->uniq = ngx_file_uniq(&fi);
495 of->mtime = ngx_file_mtime(&fi);
496 of->size = ngx_file_size(&fi);
497 of->is_dir = ngx_is_dir(&fi);
498 of->is_file = ngx_is_file(&fi);
499 of->is_link = ngx_is_link(&fi);
500 of->is_exec = ngx_is_exec(&fi);
501
502 return NGX_OK;
503 }
504
505
506 /*
507 * we ignore any possible event setting error and
508 * fallback to usual periodic file retests
509 */
510
511 static void
512 ngx_open_file_add_event(ngx_open_file_cache_t *cache,
513 ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log)
514 {
515 ngx_open_file_cache_event_t *fev;
516
517 if (!(ngx_event_flags & NGX_USE_VNODE_EVENT)
518 || !of->events
519 || file->event
520 || of->fd == NGX_INVALID_FILE
521 || file->uses < of->min_uses)
522 {
523 return;
524 }
525
526 file->event = ngx_calloc(sizeof(ngx_event_t), log);
527 if (file->event== NULL) {
528 return;
529 }
530
531 fev = ngx_alloc(sizeof(ngx_open_file_cache_event_t), log);
532 if (fev == NULL) {
533 ngx_free(file->event);
534 file->event = NULL;
535 return;
536 }
537
538 fev->fd = of->fd;
539 fev->file = file;
540 fev->cache = cache;
541
542 file->event->handler = ngx_open_file_cache_remove;
543 file->event->data = fev;
544
545 /*
546 * although vnode event may be called while ngx_cycle->poll
547 * destruction, however, cleanup procedures are run before any
548 * memory freeing and events will be canceled.
549 */
550
551 file->event->log = ngx_cycle->log;
552
553 if (ngx_add_event(file->event, NGX_VNODE_EVENT, NGX_ONESHOT_EVENT)
554 != NGX_OK)
555 {
556 ngx_free(file->event->data);
557 ngx_free(file->event);
558 file->event = NULL;
559 return;
560 }
561
562 /*
563 * we do not file->use_event here because there may be a race
564 * condition between opening file and adding event, so we rely
565 * upon event notification only after first file revalidation
566 */
567
568 return;
569 }
570
571
572 static void
573 ngx_open_file_cleanup(void *data)
574 {
575 ngx_open_file_cache_cleanup_t *c = data;
576
577 c->file->count--;
578
579 ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log);
580
581 /* drop one or two expired open files */
582 ngx_expire_old_cached_files(c->cache, 1, c->log);
583 }
584
585
586 static void
587 ngx_close_cached_file(ngx_open_file_cache_t *cache,
588 ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log)
589 {
590 ngx_log_debug5(NGX_LOG_DEBUG_CORE, log, 0,
591 "close cached open file: %s, fd:%d, c:%d, u:%d, %d",
592 file->name, file->fd, file->count, file->uses, file->close);
593
594 if (!file->close) {
595
596 file->accessed = ngx_time();
597
598 ngx_queue_remove(&file->queue);
599
600 ngx_queue_insert_head(&cache->expire_queue, &file->queue);
601
602 if (file->uses >= min_uses || file->count) {
603 return;
604 }
605 }
606
607 ngx_open_file_del_event(file);
608
609 if (file->count) {
610 return;
611 }
612
613 if (file->fd != NGX_INVALID_FILE) {
614
615 if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
616 ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
617 ngx_close_file_n " \"%s\" failed", file->name);
618 }
619
620 file->fd = NGX_INVALID_FILE;
621 }
622
623 if (!file->close) {
624 return;
625 }
626
627 ngx_free(file->name);
628 ngx_free(file);
629 }
630
631
632 static void
633 ngx_open_file_del_event(ngx_cached_open_file_t *file)
634 {
635 if (file->event == NULL) {
636 return;
637 }
638
639 (void) ngx_del_event(file->event, NGX_VNODE_EVENT,
640 file->count ? NGX_FLUSH_EVENT : NGX_CLOSE_EVENT);
641
642 ngx_free(file->event->data);
643 ngx_free(file->event);
644 file->event = NULL;
645 file->use_event = 0;
646 }
647
648
649 static void
650 ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ngx_uint_t n,
651 ngx_log_t *log)
652 {
653 time_t now;
654 ngx_queue_t *q;
655 ngx_cached_open_file_t *file;
656
657 now = ngx_time();
658
659 /*
660 * n == 1 deletes one or two inactive files
661 * n == 0 deletes least recently used file by force
662 * and one or two inactive files
663 */
664
665 while (n < 3) {
666
667 if (ngx_queue_empty(&cache->expire_queue)) {
668 return;
669 }
670
671 q = ngx_queue_last(&cache->expire_queue);
672
673 file = ngx_queue_data(q, ngx_cached_open_file_t, queue);
674
675 if (n++ != 0 && now - file->accessed <= cache->inactive) {
676 return;
677 }
678
679 ngx_queue_remove(q);
680
681 ngx_rbtree_delete(&cache->rbtree, &file->node);
682
683 cache->current--;
684
685 ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
686 "expire cached open file: %s", file->name);
687
688 if (!file->err && !file->is_dir) {
689 file->close = 1;
690 ngx_close_cached_file(cache, file, 0, log);
691
692 } else {
693 ngx_free(file->name);
694 ngx_free(file);
695 }
696 }
697 }
698
699
700 static void
701 ngx_open_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
702 ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
703 {
704 ngx_rbtree_node_t **p;
705 ngx_cached_open_file_t *file, *file_temp;
706
707 for ( ;; ) {
708
709 if (node->key < temp->key) {
710
711 p = &temp->left;
712
713 } else if (node->key > temp->key) {
714
715 p = &temp->right;
716
717 } else { /* node->key == temp->key */
718
719 file = (ngx_cached_open_file_t *) node;
720 file_temp = (ngx_cached_open_file_t *) temp;
721
722 p = (ngx_strcmp(file->name, file_temp->name) < 0)
723 ? &temp->left : &temp->right;
724 }
725
726 if (*p == sentinel) {
727 break;
728 }
729
730 temp = *p;
731 }
732
733 *p = node;
734 node->parent = temp;
735 node->left = sentinel;
736 node->right = sentinel;
737 ngx_rbt_red(node);
738 }
739
740
741 static ngx_cached_open_file_t *
742 ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name,
743 uint32_t hash)
744 {
745 ngx_int_t rc;
746 ngx_rbtree_node_t *node, *sentinel;
747 ngx_cached_open_file_t *file;
748
749 node = cache->rbtree.root;
750 sentinel = cache->rbtree.sentinel;
751
752 while (node != sentinel) {
753
754 if (hash < node->key) {
755 node = node->left;
756 continue;
757 }
758
759 if (hash > node->key) {
760 node = node->right;
761 continue;
762 }
763
764 /* hash == node->key */
765
766 do {
767 file = (ngx_cached_open_file_t *) node;
768
769 rc = ngx_strcmp(name->data, file->name);
770
771 if (rc == 0) {
772 return file;
773 }
774
775 node = (rc < 0) ? node->left : node->right;
776
777 } while (node != sentinel && hash == node->key);
778
779 break;
780 }
781
782 return NULL;
783 }
784
785
786 static void
787 ngx_open_file_cache_remove(ngx_event_t *ev)
788 {
789 ngx_cached_open_file_t *file;
790 ngx_open_file_cache_event_t *fev;
791
792 fev = ev->data;
793 file = fev->file;
794
795 ngx_queue_remove(&file->queue);
796
797 ngx_rbtree_delete(&fev->cache->rbtree, &file->node);
798
799 fev->cache->current--;
800
801 /* NGX_ONESHOT_EVENT was already deleted */
802 file->event = NULL;
803
804 file->close = 1;
805
806 ngx_close_cached_file(fev->cache, file, 0, ev->log);
807
808 /* free memory only when fev->cache and fev->file are already not needed */
809
810 ngx_free(ev->data);
811 ngx_free(ev);
812 }
813
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.