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