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