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_http.h>
11#include <ngx_md5.h>
12
13
14static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r,
15    ngx_http_cache_t *c);
16static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev);
17static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r,
18    ngx_http_cache_t *c);
19static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r,
20    ngx_http_cache_t *c);
21static ssize_t ngx_http_file_cache_aio_read(ngx_http_request_t *r,
22    ngx_http_cache_t *c);
23#if (NGX_HAVE_FILE_AIO)
24static void ngx_http_cache_aio_event_handler(ngx_event_t *ev);
25#endif
26#if (NGX_THREADS)
27static ngx_int_t ngx_http_cache_thread_handler(ngx_thread_task_t *task,
28    ngx_file_t *file);
29static void ngx_http_cache_thread_event_handler(ngx_event_t *ev);
30#endif
31static ngx_int_t ngx_http_file_cache_exists(ngx_http_file_cache_t *cache,
32    ngx_http_cache_t *c);
33static ngx_int_t ngx_http_file_cache_name(ngx_http_request_t *r,
34    ngx_path_t *path);
35static ngx_http_file_cache_node_t *
36    ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key);
37static void ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
38    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
39static void ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary,
40    size_t len, u_char *hash);
41static void ngx_http_file_cache_vary_header(ngx_http_request_t *r,
42    ngx_md5_t *md5, ngx_str_t *name);
43static ngx_int_t ngx_http_file_cache_reopen(ngx_http_request_t *r,
44    ngx_http_cache_t *c);
45static ngx_int_t ngx_http_file_cache_update_variant(ngx_http_request_t *r,
46    ngx_http_cache_t *c);
47static void ngx_http_file_cache_cleanup(void *data);
48static time_t ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache);
49static time_t ngx_http_file_cache_expire(ngx_http_file_cache_t *cache);
50static void ngx_http_file_cache_delete(ngx_http_file_cache_t *cache,
51    ngx_queue_t *q, u_char *name);
52static void ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache);
53static ngx_int_t ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx,
54    ngx_str_t *path);
55static ngx_int_t ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx,
56    ngx_str_t *path);
57static ngx_int_t ngx_http_file_cache_manage_directory(ngx_tree_ctx_t *ctx,
58    ngx_str_t *path);
59static ngx_int_t ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx,
60    ngx_str_t *path);
61static ngx_int_t ngx_http_file_cache_add(ngx_http_file_cache_t *cache,
62    ngx_http_cache_t *c);
63static ngx_int_t ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx,
64    ngx_str_t *path);
65static void ngx_http_file_cache_set_watermark(ngx_http_file_cache_t *cache);
66
67
68ngx_str_t  ngx_http_cache_status[] = {
69    ngx_string("MISS"),
70    ngx_string("BYPASS"),
71    ngx_string("EXPIRED"),
72    ngx_string("STALE"),
73    ngx_string("UPDATING"),
74    ngx_string("REVALIDATED"),
75    ngx_string("HIT")
76};
77
78
79static u_char  ngx_http_file_cache_key[] = { LF, 'K', 'E', 'Y', ':', ' ' };
80
81
82static ngx_int_t
83ngx_http_file_cache_init(ngx_shm_zone_t *shm_zone, void *data)
84{
85    ngx_http_file_cache_t  *ocache = data;
86
87    size_t                  len;
88    ngx_uint_t              n;
89    ngx_http_file_cache_t  *cache;
90
91    cache = shm_zone->data;
92
93    if (ocache) {
94        if (ngx_strcmp(cache->path->name.data, ocache->path->name.data) != 0) {
95            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
96                          "cache \"%V\" uses the \"%V\" cache path "
97                          "while previously it used the \"%V\" cache path",
98                          &shm_zone->shm.name, &cache->path->name,
99                          &ocache->path->name);
100
101            return NGX_ERROR;
102        }
103
104        for (n = 0; n < NGX_MAX_PATH_LEVEL; n++) {
105            if (cache->path->level[n] != ocache->path->level[n]) {
106                ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
107                              "cache \"%V\" had previously different levels",
108                              &shm_zone->shm.name);
109                return NGX_ERROR;
110            }
111        }
112
113        cache->sh = ocache->sh;
114
115        cache->shpool = ocache->shpool;
116        cache->bsize = ocache->bsize;
117
118        cache->max_size /= cache->bsize;
119
120        if (!cache->sh->cold || cache->sh->loading) {
121            cache->path->loader = NULL;
122        }
123
124        return NGX_OK;
125    }
126
127    cache->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
128
129    if (shm_zone->shm.exists) {
130        cache->sh = cache->shpool->data;
131        cache->bsize = ngx_fs_bsize(cache->path->name.data);
132
133        return NGX_OK;
134    }
135
136    cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t));
137    if (cache->sh == NULL) {
138        return NGX_ERROR;
139    }
140
141    cache->shpool->data = cache->sh;
142
143    ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel,
144                    ngx_http_file_cache_rbtree_insert_value);
145
146    ngx_queue_init(&cache->sh->queue);
147
148    cache->sh->cold = 1;
149    cache->sh->loading = 0;
150    cache->sh->size = 0;
151    cache->sh->count = 0;
152    cache->sh->watermark = (ngx_uint_t) -1;
153
154    cache->bsize = ngx_fs_bsize(cache->path->name.data);
155
156    cache->max_size /= cache->bsize;
157
158    len = sizeof(" in cache keys zone \"\"") + shm_zone->shm.name.len;
159
160    cache->shpool->log_ctx = ngx_slab_alloc(cache->shpool, len);
161    if (cache->shpool->log_ctx == NULL) {
162        return NGX_ERROR;
163    }
164
165    ngx_sprintf(cache->shpool->log_ctx, " in cache keys zone \"%V\"%Z",
166                &shm_zone->shm.name);
167
168    cache->shpool->log_nomem = 0;
169
170    return NGX_OK;
171}
172
173
174ngx_int_t
175ngx_http_file_cache_new(ngx_http_request_t *r)
176{
177    ngx_http_cache_t  *c;
178
179    c = ngx_pcalloc(r->pool, sizeof(ngx_http_cache_t));
180    if (c == NULL) {
181        return NGX_ERROR;
182    }
183
184    if (ngx_array_init(&c->keys, r->pool, 4, sizeof(ngx_str_t)) != NGX_OK) {
185        return NGX_ERROR;
186    }
187
188    r->cache = c;
189    c->file.log = r->connection->log;
190    c->file.fd = NGX_INVALID_FILE;
191
192    return NGX_OK;
193}
194
195
196ngx_int_t
197ngx_http_file_cache_create(ngx_http_request_t *r)
198{
199    ngx_http_cache_t       *c;
200    ngx_pool_cleanup_t     *cln;
201    ngx_http_file_cache_t  *cache;
202
203    c = r->cache;
204    cache = c->file_cache;
205
206    cln = ngx_pool_cleanup_add(r->pool, 0);
207    if (cln == NULL) {
208        return NGX_ERROR;
209    }
210
211    cln->handler = ngx_http_file_cache_cleanup;
212    cln->data = c;
213
214    if (ngx_http_file_cache_exists(cache, c) == NGX_ERROR) {
215        return NGX_ERROR;
216    }
217
218    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
219        return NGX_ERROR;
220    }
221
222    return NGX_OK;
223}
224
225
226void
227ngx_http_file_cache_create_key(ngx_http_request_t *r)
228{
229    size_t             len;
230    ngx_str_t         *key;
231    ngx_uint_t         i;
232    ngx_md5_t          md5;
233    ngx_http_cache_t  *c;
234
235    c = r->cache;
236
237    len = 0;
238
239    ngx_crc32_init(c->crc32);
240    ngx_md5_init(&md5);
241
242    key = c->keys.elts;
243    for (i = 0; i < c->keys.nelts; i++) {
244        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
245                       "http cache key: \"%V\"", &key[i]);
246
247        len += key[i].len;
248
249        ngx_crc32_update(&c->crc32, key[i].data, key[i].len);
250        ngx_md5_update(&md5, key[i].data, key[i].len);
251    }
252
253    c->header_start = sizeof(ngx_http_file_cache_header_t)
254                      + sizeof(ngx_http_file_cache_key) + len + 1;
255
256    ngx_crc32_final(c->crc32);
257    ngx_md5_final(c->key, &md5);
258
259    ngx_memcpy(c->main, c->key, NGX_HTTP_CACHE_KEY_LEN);
260}
261
262
263ngx_int_t
264ngx_http_file_cache_open(ngx_http_request_t *r)
265{
266    ngx_int_t                  rc, rv;
267    ngx_uint_t                 test;
268    ngx_http_cache_t          *c;
269    ngx_pool_cleanup_t        *cln;
270    ngx_open_file_info_t       of;
271    ngx_http_file_cache_t     *cache;
272    ngx_http_core_loc_conf_t  *clcf;
273
274    c = r->cache;
275
276    if (c->waiting) {
277        return NGX_AGAIN;
278    }
279
280    if (c->reading) {
281        return ngx_http_file_cache_read(r, c);
282    }
283
284    cache = c->file_cache;
285
286    if (c->node == NULL) {
287        cln = ngx_pool_cleanup_add(r->pool, 0);
288        if (cln == NULL) {
289            return NGX_ERROR;
290        }
291
292        cln->handler = ngx_http_file_cache_cleanup;
293        cln->data = c;
294    }
295
296    rc = ngx_http_file_cache_exists(cache, c);
297
298    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
299                   "http file cache exists: %i e:%d", rc, c->exists);
300
301    if (rc == NGX_ERROR) {
302        return rc;
303    }
304
305    if (rc == NGX_AGAIN) {
306        return NGX_HTTP_CACHE_SCARCE;
307    }
308
309    if (rc == NGX_OK) {
310
311        if (c->error) {
312            return c->error;
313        }
314
315        c->temp_file = 1;
316        test = c->exists ? 1 : 0;
317        rv = NGX_DECLINED;
318
319    } else { /* rc == NGX_DECLINED */
320
321        test = cache->sh->cold ? 1 : 0;
322
323        if (c->min_uses > 1) {
324
325            if (!test) {
326                return NGX_HTTP_CACHE_SCARCE;
327            }
328
329            rv = NGX_HTTP_CACHE_SCARCE;
330
331        } else {
332            c->temp_file = 1;
333            rv = NGX_DECLINED;
334        }
335    }
336
337    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
338        return NGX_ERROR;
339    }
340
341    if (!test) {
342        goto done;
343    }
344
345    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
346
347    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
348
349    of.uniq = c->uniq;
350    of.valid = clcf->open_file_cache_valid;
351    of.min_uses = clcf->open_file_cache_min_uses;
352    of.events = clcf->open_file_cache_events;
353    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
354    of.read_ahead = clcf->read_ahead;
355
356    if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool)
357        != NGX_OK)
358    {
359        switch (of.err) {
360
361        case 0:
362            return NGX_ERROR;
363
364        case NGX_ENOENT:
365        case NGX_ENOTDIR:
366            goto done;
367
368        default:
369            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
370                          ngx_open_file_n " \"%s\" failed", c->file.name.data);
371            return NGX_ERROR;
372        }
373    }
374
375    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
376                   "http file cache fd: %d", of.fd);
377
378    c->file.fd = of.fd;
379    c->file.log = r->connection->log;
380    c->uniq = of.uniq;
381    c->length = of.size;
382    c->fs_size = (of.fs_size + cache->bsize - 1) / cache->bsize;
383
384    c->buf = ngx_create_temp_buf(r->pool, c->body_start);
385    if (c->buf == NULL) {
386        return NGX_ERROR;
387    }
388
389    return ngx_http_file_cache_read(r, c);
390
391done:
392
393    if (rv == NGX_DECLINED) {
394        return ngx_http_file_cache_lock(r, c);
395    }
396
397    return rv;
398}
399
400
401static ngx_int_t
402ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c)
403{
404    ngx_msec_t                 now, timer;
405    ngx_http_file_cache_t     *cache;
406
407    if (!c->lock) {
408        return NGX_DECLINED;
409    }
410
411    now = ngx_current_msec;
412
413    cache = c->file_cache;
414
415    ngx_shmtx_lock(&cache->shpool->mutex);
416
417    timer = c->node->lock_time - now;
418
419    if (!c->node->updating || (ngx_msec_int_t) timer <= 0) {
420        c->node->updating = 1;
421        c->node->lock_time = now + c->lock_age;
422        c->updating = 1;
423        c->lock_time = c->node->lock_time;
424    }
425
426    ngx_shmtx_unlock(&cache->shpool->mutex);
427
428    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
429                   "http file cache lock u:%d wt:%M",
430                   c->updating, c->wait_time);
431
432    if (c->updating) {
433        return NGX_DECLINED;
434    }
435
436    if (c->lock_timeout == 0) {
437        return NGX_HTTP_CACHE_SCARCE;
438    }
439
440    c->waiting = 1;
441
442    if (c->wait_time == 0) {
443        c->wait_time = now + c->lock_timeout;
444
445        c->wait_event.handler = ngx_http_file_cache_lock_wait_handler;
446        c->wait_event.data = r;
447        c->wait_event.log = r->connection->log;
448    }
449
450    timer = c->wait_time - now;
451
452    ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer);
453
454    r->main->blocked++;
455
456    return NGX_AGAIN;
457}
458
459
460static void
461ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev)
462{
463    ngx_connection_t    *c;
464    ngx_http_request_t  *r;
465
466    r = ev->data;
467    c = r->connection;
468
469    ngx_http_set_log_request(c->log, r);
470
471    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
472                   "http file cache wait: \"%V?%V\"", &r->uri, &r->args);
473
474    ngx_http_file_cache_lock_wait(r, r->cache);
475
476    ngx_http_run_posted_requests(c);
477}
478
479
480static void
481ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c)
482{
483    ngx_uint_t              wait;
484    ngx_msec_t              now, timer;
485    ngx_http_file_cache_t  *cache;
486
487    now = ngx_current_msec;
488
489    timer = c->wait_time - now;
490
491    if ((ngx_msec_int_t) timer <= 0) {
492        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
493                      "cache lock timeout");
494        c->lock_timeout = 0;
495        goto wakeup;
496    }
497
498    cache = c->file_cache;
499    wait = 0;
500
501    ngx_shmtx_lock(&cache->shpool->mutex);
502
503    timer = c->node->lock_time - now;
504
505    if (c->node->updating && (ngx_msec_int_t) timer > 0) {
506        wait = 1;
507    }
508
509    ngx_shmtx_unlock(&cache->shpool->mutex);
510
511    if (wait) {
512        ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer);
513        return;
514    }
515
516wakeup:
517
518    c->waiting = 0;
519    r->main->blocked--;
520    r->write_event_handler(r);
521}
522
523
524static ngx_int_t
525ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c)
526{
527    u_char                        *p;
528    time_t                         now;
529    ssize_t                        n;
530    ngx_str_t                     *key;
531    ngx_int_t                      rc;
532    ngx_uint_t                     i;
533    ngx_http_file_cache_t         *cache;
534    ngx_http_file_cache_header_t  *h;
535
536    n = ngx_http_file_cache_aio_read(r, c);
537
538    if (n < 0) {
539        return n;
540    }
541
542    if ((size_t) n < c->header_start) {
543        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
544                      "cache file \"%s\" is too small", c->file.name.data);
545        return NGX_DECLINED;
546    }
547
548    h = (ngx_http_file_cache_header_t *) c->buf->pos;
549
550    if (h->version != NGX_HTTP_CACHE_VERSION) {
551        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
552                      "cache file \"%s\" version mismatch", c->file.name.data);
553        return NGX_DECLINED;
554    }
555
556    if (h->crc32 != c->crc32 || (size_t) h->header_start != c->header_start) {
557        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
558                      "cache file \"%s\" has md5 collision", c->file.name.data);
559        return NGX_DECLINED;
560    }
561
562    p = c->buf->pos + sizeof(ngx_http_file_cache_header_t)
563        + sizeof(ngx_http_file_cache_key);
564
565    key = c->keys.elts;
566    for (i = 0; i < c->keys.nelts; i++) {
567        if (ngx_memcmp(p, key[i].data, key[i].len) != 0) {
568            ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
569                          "cache file \"%s\" has md5 collision",
570                          c->file.name.data);
571            return NGX_DECLINED;
572        }
573
574        p += key[i].len;
575    }
576
577    if ((size_t) h->body_start > c->body_start) {
578        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
579                      "cache file \"%s\" has too long header",
580                      c->file.name.data);
581        return NGX_DECLINED;
582    }
583
584    if (h->vary_len > NGX_HTTP_CACHE_VARY_LEN) {
585        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
586                      "cache file \"%s\" has incorrect vary length",
587                      c->file.name.data);
588        return NGX_DECLINED;
589    }
590
591    if (h->vary_len) {
592        ngx_http_file_cache_vary(r, h->vary, h->vary_len, c->variant);
593
594        if (ngx_memcmp(c->variant, h->variant, NGX_HTTP_CACHE_KEY_LEN) != 0) {
595            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
596                           "http file cache vary mismatch");
597            return ngx_http_file_cache_reopen(r, c);
598        }
599    }
600
601    c->buf->last += n;
602
603    c->valid_sec = h->valid_sec;
604    c->updating_sec = h->updating_sec;
605    c->error_sec = h->error_sec;
606    c->last_modified = h->last_modified;
607    c->date = h->date;
608    c->valid_msec = h->valid_msec;
609    c->body_start = h->body_start;
610    c->etag.len = h->etag_len;
611    c->etag.data = h->etag;
612
613    r->cached = 1;
614
615    cache = c->file_cache;
616
617    if (cache->sh->cold) {
618
619        ngx_shmtx_lock(&cache->shpool->mutex);
620
621        if (!c->node->exists) {
622            c->node->uses = 1;
623            c->node->body_start = c->body_start;
624            c->node->exists = 1;
625            c->node->uniq = c->uniq;
626            c->node->fs_size = c->fs_size;
627
628            cache->sh->size += c->fs_size;
629        }
630
631        ngx_shmtx_unlock(&cache->shpool->mutex);
632    }
633
634    now = ngx_time();
635
636    if (c->valid_sec < now) {
637        c->stale_updating = c->valid_sec + c->updating_sec >= now;
638        c->stale_error = c->valid_sec + c->error_sec >= now;
639
640        ngx_shmtx_lock(&cache->shpool->mutex);
641
642        if (c->node->updating) {
643            rc = NGX_HTTP_CACHE_UPDATING;
644
645        } else {
646            c->node->updating = 1;
647            c->updating = 1;
648            c->lock_time = c->node->lock_time;
649            rc = NGX_HTTP_CACHE_STALE;
650        }
651
652        ngx_shmtx_unlock(&cache->shpool->mutex);
653
654        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
655                       "http file cache expired: %i %T %T",
656                       rc, c->valid_sec, now);
657
658        return rc;
659    }
660
661    return NGX_OK;
662}
663
664
665static ssize_t
666ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c)
667{
668#if (NGX_HAVE_FILE_AIO || NGX_THREADS)
669    ssize_t                    n;
670    ngx_http_core_loc_conf_t  *clcf;
671
672    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
673#endif
674
675#if (NGX_HAVE_FILE_AIO)
676
677    if (clcf->aio == NGX_HTTP_AIO_ON && ngx_file_aio) {
678        n = ngx_file_aio_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
679
680        if (n != NGX_AGAIN) {
681            c->reading = 0;
682            return n;
683        }
684
685        c->reading = 1;
686
687        c->file.aio->data = r;
688        c->file.aio->handler = ngx_http_cache_aio_event_handler;
689
690        r->main->blocked++;
691        r->aio = 1;
692
693        return NGX_AGAIN;
694    }
695
696#endif
697
698#if (NGX_THREADS)
699
700    if (clcf->aio == NGX_HTTP_AIO_THREADS) {
701        c->file.thread_task = c->thread_task;
702        c->file.thread_handler = ngx_http_cache_thread_handler;
703        c->file.thread_ctx = r;
704
705        n = ngx_thread_read(&c->file, c->buf->pos, c->body_start, 0, r->pool);
706
707        c->thread_task = c->file.thread_task;
708        c->reading = (n == NGX_AGAIN);
709
710        return n;
711    }
712
713#endif
714
715    return ngx_read_file(&c->file, c->buf->pos, c->body_start, 0);
716}
717
718
719#if (NGX_HAVE_FILE_AIO)
720
721static void
722ngx_http_cache_aio_event_handler(ngx_event_t *ev)
723{
724    ngx_event_aio_t     *aio;
725    ngx_connection_t    *c;
726    ngx_http_request_t  *r;
727
728    aio = ev->data;
729    r = aio->data;
730    c = r->connection;
731
732    ngx_http_set_log_request(c->log, r);
733
734    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
735                   "http file cache aio: \"%V?%V\"", &r->uri, &r->args);
736
737    r->main->blocked--;
738    r->aio = 0;
739
740    r->write_event_handler(r);
741
742    ngx_http_run_posted_requests(c);
743}
744
745#endif
746
747
748#if (NGX_THREADS)
749
750static ngx_int_t
751ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file)
752{
753    ngx_str_t                  name;
754    ngx_thread_pool_t         *tp;
755    ngx_http_request_t        *r;
756    ngx_http_core_loc_conf_t  *clcf;
757
758    r = file->thread_ctx;
759
760    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
761    tp = clcf->thread_pool;
762
763    if (tp == NULL) {
764        if (ngx_http_complex_value(r, clcf->thread_pool_value, &name)
765            != NGX_OK)
766        {
767            return NGX_ERROR;
768        }
769
770        tp = ngx_thread_pool_get((ngx_cycle_t *) ngx_cycle, &name);
771
772        if (tp == NULL) {
773            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
774                          "thread pool \"%V\" not found", &name);
775            return NGX_ERROR;
776        }
777    }
778
779    task->event.data = r;
780    task->event.handler = ngx_http_cache_thread_event_handler;
781
782    if (ngx_thread_task_post(tp, task) != NGX_OK) {
783        return NGX_ERROR;
784    }
785
786    r->main->blocked++;
787    r->aio = 1;
788
789    return NGX_OK;
790}
791
792
793static void
794ngx_http_cache_thread_event_handler(ngx_event_t *ev)
795{
796    ngx_connection_t    *c;
797    ngx_http_request_t  *r;
798
799    r = ev->data;
800    c = r->connection;
801
802    ngx_http_set_log_request(c->log, r);
803
804    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
805                   "http file cache thread: \"%V?%V\"", &r->uri, &r->args);
806
807    r->main->blocked--;
808    r->aio = 0;
809
810    r->write_event_handler(r);
811
812    ngx_http_run_posted_requests(c);
813}
814
815#endif
816
817
818static ngx_int_t
819ngx_http_file_cache_exists(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
820{
821    ngx_int_t                    rc;
822    ngx_http_file_cache_node_t  *fcn;
823
824    ngx_shmtx_lock(&cache->shpool->mutex);
825
826    fcn = c->node;
827
828    if (fcn == NULL) {
829        fcn = ngx_http_file_cache_lookup(cache, c->key);
830    }
831
832    if (fcn) {
833        ngx_queue_remove(&fcn->queue);
834
835        if (c->node == NULL) {
836            fcn->uses++;
837            fcn->count++;
838        }
839
840        if (fcn->error) {
841
842            if (fcn->valid_sec < ngx_time()) {
843                goto renew;
844            }
845
846            rc = NGX_OK;
847
848            goto done;
849        }
850
851        if (fcn->exists || fcn->uses >= c->min_uses) {
852
853            c->exists = fcn->exists;
854            if (fcn->body_start) {
855                c->body_start = fcn->body_start;
856            }
857
858            rc = NGX_OK;
859
860            goto done;
861        }
862
863        rc = NGX_AGAIN;
864
865        goto done;
866    }
867
868    fcn = ngx_slab_calloc_locked(cache->shpool,
869                                 sizeof(ngx_http_file_cache_node_t));
870    if (fcn == NULL) {
871        ngx_http_file_cache_set_watermark(cache);
872
873        ngx_shmtx_unlock(&cache->shpool->mutex);
874
875        (void) ngx_http_file_cache_forced_expire(cache);
876
877        ngx_shmtx_lock(&cache->shpool->mutex);
878
879        fcn = ngx_slab_calloc_locked(cache->shpool,
880                                     sizeof(ngx_http_file_cache_node_t));
881        if (fcn == NULL) {
882            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
883                          "could not allocate node%s", cache->shpool->log_ctx);
884            rc = NGX_ERROR;
885            goto failed;
886        }
887    }
888
889    cache->sh->count++;
890
891    ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
892
893    ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
894               NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
895
896    ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
897
898    fcn->uses = 1;
899    fcn->count = 1;
900
901renew:
902
903    rc = NGX_DECLINED;
904
905    fcn->valid_msec = 0;
906    fcn->error = 0;
907    fcn->exists = 0;
908    fcn->valid_sec = 0;
909    fcn->uniq = 0;
910    fcn->body_start = 0;
911    fcn->fs_size = 0;
912
913done:
914
915    fcn->expire = ngx_time() + cache->inactive;
916
917    ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
918
919    c->uniq = fcn->uniq;
920    c->error = fcn->error;
921    c->node = fcn;
922
923failed:
924
925    ngx_shmtx_unlock(&cache->shpool->mutex);
926
927    return rc;
928}
929
930
931static ngx_int_t
932ngx_http_file_cache_name(ngx_http_request_t *r, ngx_path_t *path)
933{
934    u_char            *p;
935    ngx_http_cache_t  *c;
936
937    c = r->cache;
938
939    if (c->file.name.len) {
940        return NGX_OK;
941    }
942
943    c->file.name.len = path->name.len + 1 + path->len
944                       + 2 * NGX_HTTP_CACHE_KEY_LEN;
945
946    c->file.name.data = ngx_pnalloc(r->pool, c->file.name.len + 1);
947    if (c->file.name.data == NULL) {
948        return NGX_ERROR;
949    }
950
951    ngx_memcpy(c->file.name.data, path->name.data, path->name.len);
952
953    p = c->file.name.data + path->name.len + 1 + path->len;
954    p = ngx_hex_dump(p, c->key, NGX_HTTP_CACHE_KEY_LEN);
955    *p = '\0';
956
957    ngx_create_hashed_filename(path, c->file.name.data, c->file.name.len);
958
959    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
960                   "cache file: \"%s\"", c->file.name.data);
961
962    return NGX_OK;
963}
964
965
966static ngx_http_file_cache_node_t *
967ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key)
968{
969    ngx_int_t                    rc;
970    ngx_rbtree_key_t             node_key;
971    ngx_rbtree_node_t           *node, *sentinel;
972    ngx_http_file_cache_node_t  *fcn;
973
974    ngx_memcpy((u_char *) &node_key, key, sizeof(ngx_rbtree_key_t));
975
976    node = cache->sh->rbtree.root;
977    sentinel = cache->sh->rbtree.sentinel;
978
979    while (node != sentinel) {
980
981        if (node_key < node->key) {
982            node = node->left;
983            continue;
984        }
985
986        if (node_key > node->key) {
987            node = node->right;
988            continue;
989        }
990
991        /* node_key == node->key */
992
993        fcn = (ngx_http_file_cache_node_t *) node;
994
995        rc = ngx_memcmp(&key[sizeof(ngx_rbtree_key_t)], fcn->key,
996                        NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
997
998        if (rc == 0) {
999            return fcn;
1000        }
1001
1002        node = (rc < 0) ? node->left : node->right;
1003    }
1004
1005    /* not found */
1006
1007    return NULL;
1008}
1009
1010
1011static void
1012ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
1013    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
1014{
1015    ngx_rbtree_node_t           **p;
1016    ngx_http_file_cache_node_t   *cn, *cnt;
1017
1018    for ( ;; ) {
1019
1020        if (node->key < temp->key) {
1021
1022            p = &temp->left;
1023
1024        } else if (node->key > temp->key) {
1025
1026            p = &temp->right;
1027
1028        } else { /* node->key == temp->key */
1029
1030            cn = (ngx_http_file_cache_node_t *) node;
1031            cnt = (ngx_http_file_cache_node_t *) temp;
1032
1033            p = (ngx_memcmp(cn->key, cnt->key,
1034                            NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t))
1035                 < 0)
1036                    ? &temp->left : &temp->right;
1037        }
1038
1039        if (*p == sentinel) {
1040            break;
1041        }
1042
1043        temp = *p;
1044    }
1045
1046    *p = node;
1047    node->parent = temp;
1048    node->left = sentinel;
1049    node->right = sentinel;
1050    ngx_rbt_red(node);
1051}
1052
1053
1054static void
1055ngx_http_file_cache_vary(ngx_http_request_t *r, u_char *vary, size_t len,
1056    u_char *hash)
1057{
1058    u_char     *p, *last;
1059    ngx_str_t   name;
1060    ngx_md5_t   md5;
1061    u_char      buf[NGX_HTTP_CACHE_VARY_LEN];
1062
1063    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1064                   "http file cache vary: \"%*s\"", len, vary);
1065
1066    ngx_md5_init(&md5);
1067    ngx_md5_update(&md5, r->cache->main, NGX_HTTP_CACHE_KEY_LEN);
1068
1069    ngx_strlow(buf, vary, len);
1070
1071    p = buf;
1072    last = buf + len;
1073
1074    while (p < last) {
1075
1076        while (p < last && (*p == ' ' || *p == ',')) { p++; }
1077
1078        name.data = p;
1079
1080        while (p < last && *p != ',' && *p != ' ') { p++; }
1081
1082        name.len = p - name.data;
1083
1084        if (name.len == 0) {
1085            break;
1086        }
1087
1088        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1089                       "http file cache vary: %V", &name);
1090
1091        ngx_md5_update(&md5, name.data, name.len);
1092        ngx_md5_update(&md5, (u_char *) ":", sizeof(":") - 1);
1093
1094        ngx_http_file_cache_vary_header(r, &md5, &name);
1095
1096        ngx_md5_update(&md5, (u_char *) CRLF, sizeof(CRLF) - 1);
1097    }
1098
1099    ngx_md5_final(hash, &md5);
1100}
1101
1102
1103static void
1104ngx_http_file_cache_vary_header(ngx_http_request_t *r, ngx_md5_t *md5,
1105    ngx_str_t *name)
1106{
1107    size_t            len;
1108    u_char           *p, *start, *last;
1109    ngx_uint_t        i, multiple, normalize;
1110    ngx_list_part_t  *part;
1111    ngx_table_elt_t  *header;
1112
1113    multiple = 0;
1114    normalize = 0;
1115
1116    if (name->len == sizeof("Accept-Charset") - 1
1117        && ngx_strncasecmp(name->data, (u_char *) "Accept-Charset",
1118                           sizeof("Accept-Charset") - 1) == 0)
1119    {
1120        normalize = 1;
1121
1122    } else if (name->len == sizeof("Accept-Encoding") - 1
1123        && ngx_strncasecmp(name->data, (u_char *) "Accept-Encoding",
1124                           sizeof("Accept-Encoding") - 1) == 0)
1125    {
1126        normalize = 1;
1127
1128    } else if (name->len == sizeof("Accept-Language") - 1
1129        && ngx_strncasecmp(name->data, (u_char *) "Accept-Language",
1130                           sizeof("Accept-Language") - 1) == 0)
1131    {
1132        normalize = 1;
1133    }
1134
1135    part = &r->headers_in.headers.part;
1136    header = part->elts;
1137
1138    for (i = 0; /* void */ ; i++) {
1139
1140        if (i >= part->nelts) {
1141            if (part->next == NULL) {
1142                break;
1143            }
1144
1145            part = part->next;
1146            header = part->elts;
1147            i = 0;
1148        }
1149
1150        if (header[i].hash == 0) {
1151            continue;
1152        }
1153
1154        if (header[i].key.len != name->len) {
1155            continue;
1156        }
1157
1158        if (ngx_strncasecmp(header[i].key.data, name->data, name->len) != 0) {
1159            continue;
1160        }
1161
1162        if (!normalize) {
1163
1164            if (multiple) {
1165                ngx_md5_update(md5, (u_char *) ",", sizeof(",") - 1);
1166            }
1167
1168            ngx_md5_update(md5, header[i].value.data, header[i].value.len);
1169
1170            multiple = 1;
1171
1172            continue;
1173        }
1174
1175        /* normalize spaces */
1176
1177        p = header[i].value.data;
1178        last = p + header[i].value.len;
1179
1180        while (p < last) {
1181
1182            while (p < last && (*p == ' ' || *p == ',')) { p++; }
1183
1184            start = p;
1185
1186            while (p < last && *p != ',' && *p != ' ') { p++; }
1187
1188            len = p - start;
1189
1190            if (len == 0) {
1191                break;
1192            }
1193
1194            if (multiple) {
1195                ngx_md5_update(md5, (u_char *) ",", sizeof(",") - 1);
1196            }
1197
1198            ngx_md5_update(md5, start, len);
1199
1200            multiple = 1;
1201        }
1202    }
1203}
1204
1205
1206static ngx_int_t
1207ngx_http_file_cache_reopen(ngx_http_request_t *r, ngx_http_cache_t *c)
1208{
1209    ngx_http_file_cache_t  *cache;
1210
1211    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
1212                   "http file cache reopen");
1213
1214    if (c->secondary) {
1215        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
1216                      "cache file \"%s\" has incorrect vary hash",
1217                      c->file.name.data);
1218        return NGX_DECLINED;
1219    }
1220
1221    cache = c->file_cache;
1222
1223    ngx_shmtx_lock(&cache->shpool->mutex);
1224
1225    c->node->count--;
1226    c->node = NULL;
1227
1228    ngx_shmtx_unlock(&cache->shpool->mutex);
1229
1230    c->secondary = 1;
1231    c->file.name.len = 0;
1232    c->body_start = c->buf->end - c->buf->start;
1233
1234    ngx_memcpy(c->key, c->variant, NGX_HTTP_CACHE_KEY_LEN);
1235
1236    return ngx_http_file_cache_open(r);
1237}
1238
1239
1240ngx_int_t
1241ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
1242{
1243    ngx_http_file_cache_header_t  *h = (ngx_http_file_cache_header_t *) buf;
1244
1245    u_char            *p;
1246    ngx_str_t         *key;
1247    ngx_uint_t         i;
1248    ngx_http_cache_t  *c;
1249
1250    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1251                   "http file cache set header");
1252
1253    c = r->cache;
1254
1255    ngx_memzero(h, sizeof(ngx_http_file_cache_header_t));
1256
1257    h->version = NGX_HTTP_CACHE_VERSION;
1258    h->valid_sec = c->valid_sec;
1259    h->updating_sec = c->updating_sec;
1260    h->error_sec = c->error_sec;
1261    h->last_modified = c->last_modified;
1262    h->date = c->date;
1263    h->crc32 = c->crc32;
1264    h->valid_msec = (u_short) c->valid_msec;
1265    h->header_start = (u_short) c->header_start;
1266    h->body_start = (u_short) c->body_start;
1267
1268    if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
1269        h->etag_len = (u_char) c->etag.len;
1270        ngx_memcpy(h->etag, c->etag.data, c->etag.len);
1271    }
1272
1273    if (c->vary.len) {
1274        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
1275            /* should not happen */
1276            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
1277        }
1278
1279        h->vary_len = (u_char) c->vary.len;
1280        ngx_memcpy(h->vary, c->vary.data, c->vary.len);
1281
1282        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
1283        ngx_memcpy(h->variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
1284    }
1285
1286    if (ngx_http_file_cache_update_variant(r, c) != NGX_OK) {
1287        return NGX_ERROR;
1288    }
1289
1290    p = buf + sizeof(ngx_http_file_cache_header_t);
1291
1292    p = ngx_cpymem(p, ngx_http_file_cache_key, sizeof(ngx_http_file_cache_key));
1293
1294    key = c->keys.elts;
1295    for (i = 0; i < c->keys.nelts; i++) {
1296        p = ngx_copy(p, key[i].data, key[i].len);
1297    }
1298
1299    *p = LF;
1300
1301    return NGX_OK;
1302}
1303
1304
1305static ngx_int_t
1306ngx_http_file_cache_update_variant(ngx_http_request_t *r, ngx_http_cache_t *c)
1307{
1308    ngx_http_file_cache_t  *cache;
1309
1310    if (!c->secondary) {
1311        return NGX_OK;
1312    }
1313
1314    if (c->vary.len
1315        && ngx_memcmp(c->variant, c->key, NGX_HTTP_CACHE_KEY_LEN) == 0)
1316    {
1317        return NGX_OK;
1318    }
1319
1320    /*
1321     * if the variant hash doesn't match one we used as a secondary
1322     * cache key, switch back to the original key
1323     */
1324
1325    cache = c->file_cache;
1326
1327    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1328                   "http file cache main key");
1329
1330    ngx_shmtx_lock(&cache->shpool->mutex);
1331
1332    c->node->count--;
1333    c->node->updating = 0;
1334    c->node = NULL;
1335
1336    ngx_shmtx_unlock(&cache->shpool->mutex);
1337
1338    c->file.name.len = 0;
1339
1340    ngx_memcpy(c->key, c->main, NGX_HTTP_CACHE_KEY_LEN);
1341
1342    if (ngx_http_file_cache_exists(cache, c) == NGX_ERROR) {
1343        return NGX_ERROR;
1344    }
1345
1346    if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) {
1347        return NGX_ERROR;
1348    }
1349
1350    return NGX_OK;
1351}
1352
1353
1354void
1355ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf)
1356{
1357    off_t                   fs_size;
1358    ngx_int_t               rc;
1359    ngx_file_uniq_t         uniq;
1360    ngx_file_info_t         fi;
1361    ngx_http_cache_t        *c;
1362    ngx_ext_rename_file_t   ext;
1363    ngx_http_file_cache_t  *cache;
1364
1365    c = r->cache;
1366
1367    if (c->updated) {
1368        return;
1369    }
1370
1371    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1372                   "http file cache update");
1373
1374    cache = c->file_cache;
1375
1376    c->updated = 1;
1377    c->updating = 0;
1378
1379    uniq = 0;
1380    fs_size = 0;
1381
1382    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1383                   "http file cache rename: \"%s\" to \"%s\"",
1384                   tf->file.name.data, c->file.name.data);
1385
1386    ext.access = NGX_FILE_OWNER_ACCESS;
1387    ext.path_access = NGX_FILE_OWNER_ACCESS;
1388    ext.time = -1;
1389    ext.create_path = 1;
1390    ext.delete_file = 1;
1391    ext.log = r->connection->log;
1392
1393    rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext);
1394
1395    if (rc == NGX_OK) {
1396
1397        if (ngx_fd_info(tf->file.fd, &fi) == NGX_FILE_ERROR) {
1398            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
1399                          ngx_fd_info_n " \"%s\" failed", tf->file.name.data);
1400
1401            rc = NGX_ERROR;
1402
1403        } else {
1404            uniq = ngx_file_uniq(&fi);
1405            fs_size = (ngx_file_fs_size(&fi) + cache->bsize - 1) / cache->bsize;
1406        }
1407    }
1408
1409    ngx_shmtx_lock(&cache->shpool->mutex);
1410
1411    c->node->count--;
1412    c->node->error = 0;
1413    c->node->uniq = uniq;
1414    c->node->body_start = c->body_start;
1415
1416    cache->sh->size += fs_size - c->node->fs_size;
1417    c->node->fs_size = fs_size;
1418
1419    if (rc == NGX_OK) {
1420        c->node->exists = 1;
1421    }
1422
1423    c->node->updating = 0;
1424
1425    ngx_shmtx_unlock(&cache->shpool->mutex);
1426}
1427
1428
1429void
1430ngx_http_file_cache_update_header(ngx_http_request_t *r)
1431{
1432    ssize_t                        n;
1433    ngx_err_t                      err;
1434    ngx_file_t                     file;
1435    ngx_file_info_t                fi;
1436    ngx_http_cache_t              *c;
1437    ngx_http_file_cache_header_t   h;
1438
1439    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1440                   "http file cache update header");
1441
1442    c = r->cache;
1443
1444    ngx_memzero(&file, sizeof(ngx_file_t));
1445
1446    file.name = c->file.name;
1447    file.log = r->connection->log;
1448    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDWR, NGX_FILE_OPEN, 0);
1449
1450    if (file.fd == NGX_INVALID_FILE) {
1451        err = ngx_errno;
1452
1453        /* cache file may have been deleted */
1454
1455        if (err == NGX_ENOENT) {
1456            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1457                           "http file cache \"%s\" not found",
1458                           file.name.data);
1459            return;
1460        }
1461
1462        ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
1463                      ngx_open_file_n " \"%s\" failed", file.name.data);
1464        return;
1465    }
1466
1467    /*
1468     * make sure cache file wasn't replaced;
1469     * if it was, do nothing
1470     */
1471
1472    if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
1473        ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
1474                      ngx_fd_info_n " \"%s\" failed", file.name.data);
1475        goto done;
1476    }
1477
1478    if (c->uniq != ngx_file_uniq(&fi)
1479        || c->length != ngx_file_size(&fi))
1480    {
1481        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1482                       "http file cache \"%s\" changed",
1483                       file.name.data);
1484        goto done;
1485    }
1486
1487    n = ngx_read_file(&file, (u_char *) &h,
1488                      sizeof(ngx_http_file_cache_header_t), 0);
1489
1490    if (n == NGX_ERROR) {
1491        goto done;
1492    }
1493
1494    if ((size_t) n != sizeof(ngx_http_file_cache_header_t)) {
1495        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
1496                      ngx_read_file_n " read only %z of %z from \"%s\"",
1497                      n, sizeof(ngx_http_file_cache_header_t), file.name.data);
1498        goto done;
1499    }
1500
1501    if (h.version != NGX_HTTP_CACHE_VERSION
1502        || h.last_modified != c->last_modified
1503        || h.crc32 != c->crc32
1504        || (size_t) h.header_start != c->header_start
1505        || (size_t) h.body_start != c->body_start)
1506    {
1507        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1508                       "http file cache \"%s\" content changed",
1509                       file.name.data);
1510        goto done;
1511    }
1512
1513    /*
1514     * update cache file header with new data,
1515     * notably h.valid_sec and h.date
1516     */
1517
1518    ngx_memzero(&h, sizeof(ngx_http_file_cache_header_t));
1519
1520    h.version = NGX_HTTP_CACHE_VERSION;
1521    h.valid_sec = c->valid_sec;
1522    h.updating_sec = c->updating_sec;
1523    h.error_sec = c->error_sec;
1524    h.last_modified = c->last_modified;
1525    h.date = c->date;
1526    h.crc32 = c->crc32;
1527    h.valid_msec = (u_short) c->valid_msec;
1528    h.header_start = (u_short) c->header_start;
1529    h.body_start = (u_short) c->body_start;
1530
1531    if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
1532        h.etag_len = (u_char) c->etag.len;
1533        ngx_memcpy(h.etag, c->etag.data, c->etag.len);
1534    }
1535
1536    if (c->vary.len) {
1537        if (c->vary.len > NGX_HTTP_CACHE_VARY_LEN) {
1538            /* should not happen */
1539            c->vary.len = NGX_HTTP_CACHE_VARY_LEN;
1540        }
1541
1542        h.vary_len = (u_char) c->vary.len;
1543        ngx_memcpy(h.vary, c->vary.data, c->vary.len);
1544
1545        ngx_http_file_cache_vary(r, c->vary.data, c->vary.len, c->variant);
1546        ngx_memcpy(h.variant, c->variant, NGX_HTTP_CACHE_KEY_LEN);
1547    }
1548
1549    (void) ngx_write_file(&file, (u_char *) &h,
1550                          sizeof(ngx_http_file_cache_header_t), 0);
1551
1552done:
1553
1554    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
1555        ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
1556                      ngx_close_file_n " \"%s\" failed", file.name.data);
1557    }
1558}
1559
1560
1561ngx_int_t
1562ngx_http_cache_send(ngx_http_request_t *r)
1563{
1564    ngx_int_t          rc;
1565    ngx_buf_t         *b;
1566    ngx_chain_t        out;
1567    ngx_http_cache_t  *c;
1568
1569    c = r->cache;
1570
1571    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
1572                   "http file cache send: %s", c->file.name.data);
1573
1574    if (r != r->main && c->length - c->body_start == 0) {
1575        return ngx_http_send_header(r);
1576    }
1577
1578    /* we need to allocate all before the header would be sent */
1579
1580    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
1581    if (b == NULL) {
1582        return NGX_HTTP_INTERNAL_SERVER_ERROR;
1583    }
1584
1585    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
1586    if (b->file == NULL) {
1587        return NGX_HTTP_INTERNAL_SERVER_ERROR;
1588    }
1589
1590    rc = ngx_http_send_header(r);
1591
1592    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
1593        return rc;
1594    }
1595
1596    b->file_pos = c->body_start;
1597    b->file_last = c->length;
1598
1599    b->in_file = (c->length - c->body_start) ? 1: 0;
1600    b->last_buf = (r == r->main) ? 1: 0;
1601    b->last_in_chain = 1;
1602
1603    b->file->fd = c->file.fd;
1604    b->file->name = c->file.name;
1605    b->file->log = r->connection->log;
1606
1607    out.buf = b;
1608    out.next = NULL;
1609
1610    return ngx_http_output_filter(r, &out);
1611}
1612
1613
1614void
1615ngx_http_file_cache_free(ngx_http_cache_t *c, ngx_temp_file_t *tf)
1616{
1617    ngx_http_file_cache_t       *cache;
1618    ngx_http_file_cache_node_t  *fcn;
1619
1620    if (c->updated || c->node == NULL) {
1621        return;
1622    }
1623
1624    cache = c->file_cache;
1625
1626    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
1627                   "http file cache free, fd: %d", c->file.fd);
1628
1629    ngx_shmtx_lock(&cache->shpool->mutex);
1630
1631    fcn = c->node;
1632    fcn->count--;
1633
1634    if (c->updating && fcn->lock_time == c->lock_time) {
1635        fcn->updating = 0;
1636    }
1637
1638    if (c->error) {
1639        fcn->error = c->error;
1640
1641        if (c->valid_sec) {
1642            fcn->valid_sec = c->valid_sec;
1643            fcn->valid_msec = c->valid_msec;
1644        }
1645
1646    } else if (!fcn->exists && fcn->count == 0 && c->min_uses == 1) {
1647        ngx_queue_remove(&fcn->queue);
1648        ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
1649        ngx_slab_free_locked(cache->shpool, fcn);
1650        cache->sh->count--;
1651        c->node = NULL;
1652    }
1653
1654    ngx_shmtx_unlock(&cache->shpool->mutex);
1655
1656    c->updated = 1;
1657    c->updating = 0;
1658
1659    if (c->temp_file) {
1660        if (tf && tf->file.fd != NGX_INVALID_FILE) {
1661            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
1662                           "http file cache incomplete: \"%s\"",
1663                           tf->file.name.data);
1664
1665            if (ngx_delete_file(tf->file.name.data) == NGX_FILE_ERROR) {
1666                ngx_log_error(NGX_LOG_CRIT, c->file.log, ngx_errno,
1667                              ngx_delete_file_n " \"%s\" failed",
1668                              tf->file.name.data);
1669            }
1670        }
1671    }
1672
1673    if (c->wait_event.timer_set) {
1674        ngx_del_timer(&c->wait_event);
1675    }
1676}
1677
1678
1679static void
1680ngx_http_file_cache_cleanup(void *data)
1681{
1682    ngx_http_cache_t  *c = data;
1683
1684    if (c->updated) {
1685        return;
1686    }
1687
1688    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
1689                   "http file cache cleanup");
1690
1691    if (c->updating && !c->background) {
1692        ngx_log_error(NGX_LOG_ALERT, c->file.log, 0,
1693                      "stalled cache updating, error:%ui", c->error);
1694    }
1695
1696    ngx_http_file_cache_free(c, NULL);
1697}
1698
1699
1700static time_t
1701ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache)
1702{
1703    u_char                      *name;
1704    size_t                       len;
1705    time_t                       wait;
1706    ngx_uint_t                   tries;
1707    ngx_path_t                  *path;
1708    ngx_queue_t                 *q;
1709    ngx_http_file_cache_node_t  *fcn;
1710
1711    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1712                   "http file cache forced expire");
1713
1714    path = cache->path;
1715    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
1716
1717    name = ngx_alloc(len + 1, ngx_cycle->log);
1718    if (name == NULL) {
1719        return 10;
1720    }
1721
1722    ngx_memcpy(name, path->name.data, path->name.len);
1723
1724    wait = 10;
1725    tries = 20;
1726
1727    ngx_shmtx_lock(&cache->shpool->mutex);
1728
1729    for (q = ngx_queue_last(&cache->sh->queue);
1730         q != ngx_queue_sentinel(&cache->sh->queue);
1731         q = ngx_queue_prev(q))
1732    {
1733        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
1734
1735        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1736                  "http file cache forced expire: #%d %d %02xd%02xd%02xd%02xd",
1737                  fcn->count, fcn->exists,
1738                  fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
1739
1740        if (fcn->count == 0) {
1741            ngx_http_file_cache_delete(cache, q, name);
1742            wait = 0;
1743
1744        } else {
1745            if (--tries) {
1746                continue;
1747            }
1748
1749            wait = 1;
1750        }
1751
1752        break;
1753    }
1754
1755    ngx_shmtx_unlock(&cache->shpool->mutex);
1756
1757    ngx_free(name);
1758
1759    return wait;
1760}
1761
1762
1763static time_t
1764ngx_http_file_cache_expire(ngx_http_file_cache_t *cache)
1765{
1766    u_char                      *name, *p;
1767    size_t                       len;
1768    time_t                       now, wait;
1769    ngx_path_t                  *path;
1770    ngx_msec_t                   elapsed;
1771    ngx_queue_t                 *q;
1772    ngx_http_file_cache_node_t  *fcn;
1773    u_char                       key[2 * NGX_HTTP_CACHE_KEY_LEN];
1774
1775    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1776                   "http file cache expire");
1777
1778    path = cache->path;
1779    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
1780
1781    name = ngx_alloc(len + 1, ngx_cycle->log);
1782    if (name == NULL) {
1783        return 10;
1784    }
1785
1786    ngx_memcpy(name, path->name.data, path->name.len);
1787
1788    now = ngx_time();
1789
1790    ngx_shmtx_lock(&cache->shpool->mutex);
1791
1792    for ( ;; ) {
1793
1794        if (ngx_quit || ngx_terminate) {
1795            wait = 1;
1796            break;
1797        }
1798
1799        if (ngx_queue_empty(&cache->sh->queue)) {
1800            wait = 10;
1801            break;
1802        }
1803
1804        q = ngx_queue_last(&cache->sh->queue);
1805
1806        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
1807
1808        wait = fcn->expire - now;
1809
1810        if (wait > 0) {
1811            wait = wait > 10 ? 10 : wait;
1812            break;
1813        }
1814
1815        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1816                       "http file cache expire: #%d %d %02xd%02xd%02xd%02xd",
1817                       fcn->count, fcn->exists,
1818                       fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
1819
1820        if (fcn->count == 0) {
1821            ngx_http_file_cache_delete(cache, q, name);
1822            goto next;
1823        }
1824
1825        if (fcn->deleting) {
1826            wait = 1;
1827            break;
1828        }
1829
1830        p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
1831                         sizeof(ngx_rbtree_key_t));
1832        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
1833        (void) ngx_hex_dump(p, fcn->key, len);
1834
1835        /*
1836         * abnormally exited workers may leave locked cache entries,
1837         * and although it may be safe to remove them completely,
1838         * we prefer to just move them to the top of the inactive queue
1839         */
1840
1841        ngx_queue_remove(q);
1842        fcn->expire = ngx_time() + cache->inactive;
1843        ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
1844
1845        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
1846                      "ignore long locked inactive cache entry %*s, count:%d",
1847                      (size_t) 2 * NGX_HTTP_CACHE_KEY_LEN, key, fcn->count);
1848
1849next:
1850
1851        if (++cache->files >= cache->manager_files) {
1852            wait = 0;
1853            break;
1854        }
1855
1856        ngx_time_update();
1857
1858        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
1859
1860        if (elapsed >= cache->manager_threshold) {
1861            wait = 0;
1862            break;
1863        }
1864    }
1865
1866    ngx_shmtx_unlock(&cache->shpool->mutex);
1867
1868    ngx_free(name);
1869
1870    return wait;
1871}
1872
1873
1874static void
1875ngx_http_file_cache_delete(ngx_http_file_cache_t *cache, ngx_queue_t *q,
1876    u_char *name)
1877{
1878    u_char                      *p;
1879    size_t                       len;
1880    ngx_path_t                  *path;
1881    ngx_http_file_cache_node_t  *fcn;
1882
1883    fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
1884
1885    if (fcn->exists) {
1886        cache->sh->size -= fcn->fs_size;
1887
1888        path = cache->path;
1889        p = name + path->name.len + 1 + path->len;
1890        p = ngx_hex_dump(p, (u_char *) &fcn->node.key,
1891                         sizeof(ngx_rbtree_key_t));
1892        len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
1893        p = ngx_hex_dump(p, fcn->key, len);
1894        *p = '\0';
1895
1896        fcn->count++;
1897        fcn->deleting = 1;
1898        ngx_shmtx_unlock(&cache->shpool->mutex);
1899
1900        len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
1901        ngx_create_hashed_filename(path, name, len);
1902
1903        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1904                       "http file cache expire: \"%s\"", name);
1905
1906        if (ngx_delete_file(name) == NGX_FILE_ERROR) {
1907            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
1908                          ngx_delete_file_n " \"%s\" failed", name);
1909        }
1910
1911        ngx_shmtx_lock(&cache->shpool->mutex);
1912        fcn->count--;
1913        fcn->deleting = 0;
1914    }
1915
1916    if (fcn->count == 0) {
1917        ngx_queue_remove(q);
1918        ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
1919        ngx_slab_free_locked(cache->shpool, fcn);
1920        cache->sh->count--;
1921    }
1922}
1923
1924
1925static ngx_msec_t
1926ngx_http_file_cache_manager(void *data)
1927{
1928    ngx_http_file_cache_t  *cache = data;
1929
1930    off_t       size;
1931    time_t      wait;
1932    ngx_msec_t  elapsed, next;
1933    ngx_uint_t  count, watermark;
1934
1935    cache->last = ngx_current_msec;
1936    cache->files = 0;
1937
1938    next = (ngx_msec_t) ngx_http_file_cache_expire(cache) * 1000;
1939
1940    if (next == 0) {
1941        next = cache->manager_sleep;
1942        goto done;
1943    }
1944
1945    for ( ;; ) {
1946        ngx_shmtx_lock(&cache->shpool->mutex);
1947
1948        size = cache->sh->size;
1949        count = cache->sh->count;
1950        watermark = cache->sh->watermark;
1951
1952        ngx_shmtx_unlock(&cache->shpool->mutex);
1953
1954        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1955                       "http file cache size: %O c:%ui w:%i",
1956                       size, count, (ngx_int_t) watermark);
1957
1958        if (size < cache->max_size && count < watermark) {
1959            break;
1960        }
1961
1962        wait = ngx_http_file_cache_forced_expire(cache);
1963
1964        if (wait > 0) {
1965            next = (ngx_msec_t) wait * 1000;
1966            break;
1967        }
1968
1969        if (ngx_quit || ngx_terminate) {
1970            break;
1971        }
1972
1973        if (++cache->files >= cache->manager_files) {
1974            next = cache->manager_sleep;
1975            break;
1976        }
1977
1978        ngx_time_update();
1979
1980        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
1981
1982        if (elapsed >= cache->manager_threshold) {
1983            next = cache->manager_sleep;
1984            break;
1985        }
1986    }
1987
1988done:
1989
1990    elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
1991
1992    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
1993                   "http file cache manager: %ui e:%M n:%M",
1994                   cache->files, elapsed, next);
1995
1996    return next;
1997}
1998
1999
2000static void
2001ngx_http_file_cache_loader(void *data)
2002{
2003    ngx_http_file_cache_t  *cache = data;
2004
2005    ngx_tree_ctx_t  tree;
2006
2007    if (!cache->sh->cold || cache->sh->loading) {
2008        return;
2009    }
2010
2011    if (!ngx_atomic_cmp_set(&cache->sh->loading, 0, ngx_pid)) {
2012        return;
2013    }
2014
2015    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
2016                   "http file cache loader");
2017
2018    tree.init_handler = NULL;
2019    tree.file_handler = ngx_http_file_cache_manage_file;
2020    tree.pre_tree_handler = ngx_http_file_cache_manage_directory;
2021    tree.post_tree_handler = ngx_http_file_cache_noop;
2022    tree.spec_handler = ngx_http_file_cache_delete_file;
2023    tree.data = cache;
2024    tree.alloc = 0;
2025    tree.log = ngx_cycle->log;
2026
2027    cache->last = ngx_current_msec;
2028    cache->files = 0;
2029
2030    if (ngx_walk_tree(&tree, &cache->path->name) == NGX_ABORT) {
2031        cache->sh->loading = 0;
2032        return;
2033    }
2034
2035    cache->sh->cold = 0;
2036    cache->sh->loading = 0;
2037
2038    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
2039                  "http file cache: %V %.3fM, bsize: %uz",
2040                  &cache->path->name,
2041                  ((double) cache->sh->size * cache->bsize) / (1024 * 1024),
2042                  cache->bsize);
2043}
2044
2045
2046static ngx_int_t
2047ngx_http_file_cache_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
2048{
2049    return NGX_OK;
2050}
2051
2052
2053static ngx_int_t
2054ngx_http_file_cache_manage_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
2055{
2056    ngx_msec_t              elapsed;
2057    ngx_http_file_cache_t  *cache;
2058
2059    cache = ctx->data;
2060
2061    if (ngx_http_file_cache_add_file(ctx, path) != NGX_OK) {
2062        (void) ngx_http_file_cache_delete_file(ctx, path);
2063    }
2064
2065    if (++cache->files >= cache->loader_files) {
2066        ngx_http_file_cache_loader_sleep(cache);
2067
2068    } else {
2069        ngx_time_update();
2070
2071        elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));
2072
2073        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
2074                       "http file cache loader time elapsed: %M", elapsed);
2075
2076        if (elapsed >= cache->loader_threshold) {
2077            ngx_http_file_cache_loader_sleep(cache);
2078        }
2079    }
2080
2081    return (ngx_quit || ngx_terminate) ? NGX_ABORT : NGX_OK;
2082}
2083
2084
2085static ngx_int_t
2086ngx_http_file_cache_manage_directory(ngx_tree_ctx_t *ctx, ngx_str_t *path)
2087{
2088    if (path->len >= 5
2089        && ngx_strncmp(path->data + path->len - 5, "/temp", 5) == 0)
2090    {
2091        return NGX_DECLINED;
2092    }
2093
2094    return NGX_OK;
2095}
2096
2097
2098static void
2099ngx_http_file_cache_loader_sleep(ngx_http_file_cache_t *cache)
2100{
2101    ngx_msleep(cache->loader_sleep);
2102
2103    ngx_time_update();
2104
2105    cache->last = ngx_current_msec;
2106    cache->files = 0;
2107}
2108
2109
2110static ngx_int_t
2111ngx_http_file_cache_add_file(ngx_tree_ctx_t *ctx, ngx_str_t *name)
2112{
2113    u_char                 *p;
2114    ngx_int_t               n;
2115    ngx_uint_t              i;
2116    ngx_http_cache_t        c;
2117    ngx_http_file_cache_t  *cache;
2118
2119    if (name->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
2120        return NGX_ERROR;
2121    }
2122
2123    /*
2124     * Temporary files in cache have a suffix consisting of a dot
2125     * followed by 10 digits.
2126     */
2127
2128    if (name->len >= 2 * NGX_HTTP_CACHE_KEY_LEN + 1 + 10
2129        && name->data[name->len - 10 - 1] == '.')
2130    {
2131        return NGX_OK;
2132    }
2133
2134    if (ctx->size < (off_t) sizeof(ngx_http_file_cache_header_t)) {
2135        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
2136                      "cache file \"%s\" is too small", name->data);
2137        return NGX_ERROR;
2138    }
2139
2140    ngx_memzero(&c, sizeof(ngx_http_cache_t));
2141    cache = ctx->data;
2142
2143    c.length = ctx->size;
2144    c.fs_size = (ctx->fs_size + cache->bsize - 1) / cache->bsize;
2145
2146    p = &name->data[name->len - 2 * NGX_HTTP_CACHE_KEY_LEN];
2147
2148    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
2149        n = ngx_hextoi(p, 2);
2150
2151        if (n == NGX_ERROR) {
2152            return NGX_ERROR;
2153        }
2154
2155        p += 2;
2156
2157        c.key[i] = (u_char) n;
2158    }
2159
2160    return ngx_http_file_cache_add(cache, &c);
2161}
2162
2163
2164static ngx_int_t
2165ngx_http_file_cache_add(ngx_http_file_cache_t *cache, ngx_http_cache_t *c)
2166{
2167    ngx_http_file_cache_node_t  *fcn;
2168
2169    ngx_shmtx_lock(&cache->shpool->mutex);
2170
2171    fcn = ngx_http_file_cache_lookup(cache, c->key);
2172
2173    if (fcn == NULL) {
2174
2175        fcn = ngx_slab_calloc_locked(cache->shpool,
2176                                     sizeof(ngx_http_file_cache_node_t));
2177        if (fcn == NULL) {
2178            ngx_http_file_cache_set_watermark(cache);
2179
2180            if (cache->fail_time != ngx_time()) {
2181                cache->fail_time = ngx_time();
2182                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
2183                           "could not allocate node%s", cache->shpool->log_ctx);
2184            }
2185
2186            ngx_shmtx_unlock(&cache->shpool->mutex);
2187            return NGX_ERROR;
2188        }
2189
2190        cache->sh->count++;
2191
2192        ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t));
2193
2194        ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)],
2195                   NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
2196
2197        ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node);
2198
2199        fcn->uses = 1;
2200        fcn->exists = 1;
2201        fcn->fs_size = c->fs_size;
2202
2203        cache->sh->size += c->fs_size;
2204
2205    } else {
2206        ngx_queue_remove(&fcn->queue);
2207    }
2208
2209    fcn->expire = ngx_time() + cache->inactive;
2210
2211    ngx_queue_insert_head(&cache->sh->queue, &fcn->queue);
2212
2213    ngx_shmtx_unlock(&cache->shpool->mutex);
2214
2215    return NGX_OK;
2216}
2217
2218
2219static ngx_int_t
2220ngx_http_file_cache_delete_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
2221{
2222    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
2223                   "http file cache delete: \"%s\"", path->data);
2224
2225    if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
2226        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
2227                      ngx_delete_file_n " \"%s\" failed", path->data);
2228    }
2229
2230    return NGX_OK;
2231}
2232
2233
2234static void
2235ngx_http_file_cache_set_watermark(ngx_http_file_cache_t *cache)
2236{
2237    cache->sh->watermark = cache->sh->count - cache->sh->count / 8;
2238
2239    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
2240                   "http file cache watermark: %ui", cache->sh->watermark);
2241}
2242
2243
2244time_t
2245ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status)
2246{
2247    ngx_uint_t               i;
2248    ngx_http_cache_valid_t  *valid;
2249
2250    if (cache_valid == NULL) {
2251        return 0;
2252    }
2253
2254    valid = cache_valid->elts;
2255    for (i = 0; i < cache_valid->nelts; i++) {
2256
2257        if (valid[i].status == 0) {
2258            return valid[i].valid;
2259        }
2260
2261        if (valid[i].status == status) {
2262            return valid[i].valid;
2263        }
2264    }
2265
2266    return 0;
2267}
2268
2269
2270char *
2271ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
2272{
2273    char  *confp = conf;
2274
2275    off_t                   max_size;
2276    u_char                 *last, *p;
2277    time_t                  inactive;
2278    ssize_t                 size;
2279    ngx_str_t               s, name, *value;
2280    ngx_int_t               loader_files, manager_files;
2281    ngx_msec_t              loader_sleep, manager_sleep, loader_threshold,
2282                            manager_threshold;
2283    ngx_uint_t              i, n, use_temp_path;
2284    ngx_array_t            *caches;
2285    ngx_http_file_cache_t  *cache, **ce;
2286
2287    cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
2288    if (cache == NULL) {
2289        return NGX_CONF_ERROR;
2290    }
2291
2292    cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
2293    if (cache->path == NULL) {
2294        return NGX_CONF_ERROR;
2295    }
2296
2297    use_temp_path = 1;
2298
2299    inactive = 600;
2300
2301    loader_files = 100;
2302    loader_sleep = 50;
2303    loader_threshold = 200;
2304
2305    manager_files = 100;
2306    manager_sleep = 50;
2307    manager_threshold = 200;
2308
2309    name.len = 0;
2310    size = 0;
2311    max_size = NGX_MAX_OFF_T_VALUE;
2312
2313    value = cf->args->elts;
2314
2315    cache->path->name = value[1];
2316
2317    if (cache->path->name.data[cache->path->name.len - 1] == '/') {
2318        cache->path->name.len--;
2319    }
2320
2321    if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
2322        return NGX_CONF_ERROR;
2323    }
2324
2325    for (i = 2; i < cf->args->nelts; i++) {
2326
2327        if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {
2328
2329            p = value[i].data + 7;
2330            last = value[i].data + value[i].len;
2331
2332            for (n = 0; n < NGX_MAX_PATH_LEVEL && p < last; n++) {
2333
2334                if (*p > '0' && *p < '3') {
2335
2336                    cache->path->level[n] = *p++ - '0';
2337                    cache->path->len += cache->path->level[n] + 1;
2338
2339                    if (p == last) {
2340                        break;
2341                    }
2342
2343                    if (*p++ == ':' && n < NGX_MAX_PATH_LEVEL - 1 && p < last) {
2344                        continue;
2345                    }
2346
2347                    goto invalid_levels;
2348                }
2349
2350                goto invalid_levels;
2351            }
2352
2353            if (cache->path->len < 10 + NGX_MAX_PATH_LEVEL) {
2354                continue;
2355            }
2356
2357        invalid_levels:
2358
2359            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2360                               "invalid \"levels\" \"%V\"", &value[i]);
2361            return NGX_CONF_ERROR;
2362        }
2363
2364        if (ngx_strncmp(value[i].data, "use_temp_path=", 14) == 0) {
2365
2366            if (ngx_strcmp(&value[i].data[14], "on") == 0) {
2367                use_temp_path = 1;
2368
2369            } else if (ngx_strcmp(&value[i].data[14], "off") == 0) {
2370                use_temp_path = 0;
2371
2372            } else {
2373                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2374                                   "invalid use_temp_path value \"%V\", "
2375                                   "it must be \"on\" or \"off\"",
2376                                   &value[i]);
2377                return NGX_CONF_ERROR;
2378            }
2379
2380            continue;
2381        }
2382
2383        if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {
2384
2385            name.data = value[i].data + 10;
2386
2387            p = (u_char *) ngx_strchr(name.data, ':');
2388
2389            if (p) {
2390                name.len = p - name.data;
2391
2392                p++;
2393
2394                s.len = value[i].data + value[i].len - p;
2395                s.data = p;
2396
2397                size = ngx_parse_size(&s);
2398                if (size > 8191) {
2399                    continue;
2400                }
2401            }
2402
2403            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2404                               "invalid keys zone size \"%V\"", &value[i]);
2405            return NGX_CONF_ERROR;
2406        }
2407
2408        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
2409
2410            s.len = value[i].len - 9;
2411            s.data = value[i].data + 9;
2412
2413            inactive = ngx_parse_time(&s, 1);
2414            if (inactive == (time_t) NGX_ERROR) {
2415                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2416                                   "invalid inactive value \"%V\"", &value[i]);
2417                return NGX_CONF_ERROR;
2418            }
2419
2420            continue;
2421        }
2422
2423        if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) {
2424
2425            s.len = value[i].len - 9;
2426            s.data = value[i].data + 9;
2427
2428            max_size = ngx_parse_offset(&s);
2429            if (max_size < 0) {
2430                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2431                                   "invalid max_size value \"%V\"", &value[i]);
2432                return NGX_CONF_ERROR;
2433            }
2434
2435            continue;
2436        }
2437
2438        if (ngx_strncmp(value[i].data, "loader_files=", 13) == 0) {
2439
2440            loader_files = ngx_atoi(value[i].data + 13, value[i].len - 13);
2441            if (loader_files == NGX_ERROR) {
2442                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2443                           "invalid loader_files value \"%V\"", &value[i]);
2444                return NGX_CONF_ERROR;
2445            }
2446
2447            continue;
2448        }
2449
2450        if (ngx_strncmp(value[i].data, "loader_sleep=", 13) == 0) {
2451
2452            s.len = value[i].len - 13;
2453            s.data = value[i].data + 13;
2454
2455            loader_sleep = ngx_parse_time(&s, 0);
2456            if (loader_sleep == (ngx_msec_t) NGX_ERROR) {
2457                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2458                           "invalid loader_sleep value \"%V\"", &value[i]);
2459                return NGX_CONF_ERROR;
2460            }
2461
2462            continue;
2463        }
2464
2465        if (ngx_strncmp(value[i].data, "loader_threshold=", 17) == 0) {
2466
2467            s.len = value[i].len - 17;
2468            s.data = value[i].data + 17;
2469
2470            loader_threshold = ngx_parse_time(&s, 0);
2471            if (loader_threshold == (ngx_msec_t) NGX_ERROR) {
2472                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2473                           "invalid loader_threshold value \"%V\"", &value[i]);
2474                return NGX_CONF_ERROR;
2475            }
2476
2477            continue;
2478        }
2479
2480        if (ngx_strncmp(value[i].data, "manager_files=", 14) == 0) {
2481
2482            manager_files = ngx_atoi(value[i].data + 14, value[i].len - 14);
2483            if (manager_files == NGX_ERROR) {
2484                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2485                           "invalid manager_files value \"%V\"", &value[i]);
2486                return NGX_CONF_ERROR;
2487            }
2488
2489            continue;
2490        }
2491
2492        if (ngx_strncmp(value[i].data, "manager_sleep=", 14) == 0) {
2493
2494            s.len = value[i].len - 14;
2495            s.data = value[i].data + 14;
2496
2497            manager_sleep = ngx_parse_time(&s, 0);
2498            if (manager_sleep == (ngx_msec_t) NGX_ERROR) {
2499                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2500                           "invalid manager_sleep value \"%V\"", &value[i]);
2501                return NGX_CONF_ERROR;
2502            }
2503
2504            continue;
2505        }
2506
2507        if (ngx_strncmp(value[i].data, "manager_threshold=", 18) == 0) {
2508
2509            s.len = value[i].len - 18;
2510            s.data = value[i].data + 18;
2511
2512            manager_threshold = ngx_parse_time(&s, 0);
2513            if (manager_threshold == (ngx_msec_t) NGX_ERROR) {
2514                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2515                           "invalid manager_threshold value \"%V\"", &value[i]);
2516                return NGX_CONF_ERROR;
2517            }
2518
2519            continue;
2520        }
2521
2522        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2523                           "invalid parameter \"%V\"", &value[i]);
2524        return NGX_CONF_ERROR;
2525    }
2526
2527    if (name.len == 0 || size == 0) {
2528        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2529                           "\"%V\" must have \"keys_zone\" parameter",
2530                           &cmd->name);
2531        return NGX_CONF_ERROR;
2532    }
2533
2534    cache->path->manager = ngx_http_file_cache_manager;
2535    cache->path->loader = ngx_http_file_cache_loader;
2536    cache->path->data = cache;
2537    cache->path->conf_file = cf->conf_file->file.name.data;
2538    cache->path->line = cf->conf_file->line;
2539    cache->loader_files = loader_files;
2540    cache->loader_sleep = loader_sleep;
2541    cache->loader_threshold = loader_threshold;
2542    cache->manager_files = manager_files;
2543    cache->manager_sleep = manager_sleep;
2544    cache->manager_threshold = manager_threshold;
2545
2546    if (ngx_add_path(cf, &cache->path) != NGX_OK) {
2547        return NGX_CONF_ERROR;
2548    }
2549
2550    cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
2551    if (cache->shm_zone == NULL) {
2552        return NGX_CONF_ERROR;
2553    }
2554
2555    if (cache->shm_zone->data) {
2556        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2557                           "duplicate zone \"%V\"", &name);
2558        return NGX_CONF_ERROR;
2559    }
2560
2561
2562    cache->shm_zone->init = ngx_http_file_cache_init;
2563    cache->shm_zone->data = cache;
2564
2565    cache->use_temp_path = use_temp_path;
2566
2567    cache->inactive = inactive;
2568    cache->max_size = max_size;
2569
2570    caches = (ngx_array_t *) (confp + cmd->offset);
2571
2572    ce = ngx_array_push(caches);
2573    if (ce == NULL) {
2574        return NGX_CONF_ERROR;
2575    }
2576
2577    *ce = cache;
2578
2579    return NGX_CONF_OK;
2580}
2581
2582
2583char *
2584ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
2585    void *conf)
2586{
2587    char  *p = conf;
2588
2589    time_t                    valid;
2590    ngx_str_t                *value;
2591    ngx_uint_t                i, n, status;
2592    ngx_array_t             **a;
2593    ngx_http_cache_valid_t   *v;
2594    static ngx_uint_t         statuses[] = { 200, 301, 302 };
2595
2596    a = (ngx_array_t **) (p + cmd->offset);
2597
2598    if (*a == NGX_CONF_UNSET_PTR) {
2599        *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_cache_valid_t));
2600        if (*a == NULL) {
2601            return NGX_CONF_ERROR;
2602        }
2603    }
2604
2605    value = cf->args->elts;
2606    n = cf->args->nelts - 1;
2607
2608    valid = ngx_parse_time(&value[n], 1);
2609    if (valid == (time_t) NGX_ERROR) {
2610        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2611                           "invalid time value \"%V\"", &value[n]);
2612        return NGX_CONF_ERROR;
2613    }
2614
2615    if (n == 1) {
2616
2617        for (i = 0; i < 3; i++) {
2618            v = ngx_array_push(*a);
2619            if (v == NULL) {
2620                return NGX_CONF_ERROR;
2621            }
2622
2623            v->status = statuses[i];
2624            v->valid = valid;
2625        }
2626
2627        return NGX_CONF_OK;
2628    }
2629
2630    for (i = 1; i < n; i++) {
2631
2632        if (ngx_strcmp(value[i].data, "any") == 0) {
2633
2634            status = 0;
2635
2636        } else {
2637
2638            status = ngx_atoi(value[i].data, value[i].len);
2639            if (status < 100) {
2640                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
2641                                   "invalid status \"%V\"", &value[i]);
2642                return NGX_CONF_ERROR;
2643            }
2644        }
2645
2646        v = ngx_array_push(*a);
2647        if (v == NULL) {
2648            return NGX_CONF_ERROR;
2649        }
2650
2651        v->status = status;
2652        v->valid = valid;
2653    }
2654
2655    return NGX_CONF_OK;
2656}
2657