diff src/http/ngx_http_file_cache.c @ 464:c8cfb6c462ef NGINX_0_7_44

nginx 0.7.44 *) Feature: the ngx_http_proxy_module preliminary cache support. *) Feature: the --with-pcre option in the configure. *) Feature: the "try_files" directive is now allowed on the server block level. *) Bugfix: the "try_files" directive handled incorrectly a query string in a fallback parameter. *) Bugfix: the "try_files" directive might test incorrectly directories. *) Bugfix: if there is the single server for given address:port pair, then captures in regular expressions in a "server_name" directive did not work.
author Igor Sysoev <http://sysoev.ru>
date Mon, 23 Mar 2009 00:00:00 +0300
parents 13710a1813ad
children 9eda3153223b
line wrap: on
line diff
--- a/src/http/ngx_http_file_cache.c
+++ b/src/http/ngx_http_file_cache.c
@@ -7,253 +7,1277 @@
 #include <ngx_config.h>
 #include <ngx_core.h>
 #include <ngx_http.h>
+#include <ngx_md5.h>
 
 
-#if (NGX_HAVE_OPENSSL_MD5_H)
-#include <openssl/md5.h>
-#else
-#include <md5.h>
-#endif
-
-#if (NGX_OPENSSL_MD5)
-#define  MD5Init    MD5_Init
-#define  MD5Update  MD5_Update
-#define  MD5Final   MD5_Final
-#endif
+static ngx_int_t ngx_http_file_cache_exists(ngx_http_request_t *r,
+    ngx_http_file_cache_t *cache);
+static ngx_http_file_cache_node_t *
+    ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key);
+static void ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+static void ngx_http_file_cache_cleanup(void *data);
+static time_t ngx_http_file_cache_expire(ngx_http_file_cache_t *cache,
+    ngx_uint_t force);
+static ngx_int_t ngx_http_file_cache_clean_noop(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
+static ngx_int_t ngx_http_file_cache_clean_file(ngx_tree_ctx_t *ctx,
+    ngx_str_t *path);
 
 
-ngx_int_t ngx_http_file_cache_get(ngx_http_request_t *r,
-                                  ngx_http_cache_ctx_t *ctx)
-{
-    ngx_uint_t         i;
-    ngx_str_t         *key;
-    ngx_http_cache_t  *c;
-    MD5_CTX            md5;
-
-    c = r->cache;
-
-    c->file.name.len = ctx->path->name.len + 1 + ctx->path->len + 32;
-    if (!(c->file.name.data = ngx_palloc(r->pool, c->file.name.len + 1))) {
-        return NGX_ABORT;
-    }
-
-    MD5Init(&md5);
-
-    key = c->key.elts;
-    for (i = 0; i < c->key.nelts; i++) {
-        MD5Update(&md5, key[i].data, key[i].len);
-    }
-
-    MD5Update(&md5, ctx->key.data, ctx->key.len);
-
-    MD5Final(c->md5, &md5);
-
-    ngx_memcpy(c->file.name.data, ctx->path->name.data, ctx->path->name.len);
-
-    ngx_md5_text(c->file.name.data + ctx->path->name.len + 1 + ctx->path->len,
-                 c->md5);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                  "file cache key: %V, md5: %s", &ctx->key,
-                  c->file.name.data + ctx->path->name.len + 1 + ctx->path->len);
-
-    ngx_create_hashed_filename(&c->file, ctx->path);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                   "file cache name: %s", c->file.name.data);
-
-    return ngx_http_file_cache_open(r->cache);
-}
+static u_char  ngx_http_file_cache_key[] = { LF, 'K', 'E', 'Y', ':', ' ' };
 
 
-ngx_int_t ngx_http_file_cache_open(ngx_http_cache_t *c)
+static ngx_int_t
+ngx_http_file_cache_init(ngx_shm_zone_t *shm_zone, void *data)
 {
-    ssize_t                   n;
-    ngx_err_t                 err;
-    ngx_http_cache_header_t  *h;
+    ngx_http_file_cache_t  *ocache = data;
 
-    c->file.fd = ngx_open_file(c->file.name.data,
-                               NGX_FILE_RDONLY, NGX_FILE_OPEN);
-
-    if (c->file.fd == NGX_INVALID_FILE) {
-        err = ngx_errno;
+    ngx_rbtree_node_t      *sentinel;
+    ngx_http_file_cache_t  *cache;
 
-        if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
-            return NGX_DECLINED;
-        }
+    cache = shm_zone->data;
 
-        ngx_log_error(NGX_LOG_CRIT, c->log, ngx_errno,
-                      ngx_open_file_n " \"%s\" failed", c->file.name.data);
-        return NGX_ERROR;
-    }
-
-    if (c->uniq) {
-        if (ngx_fd_info(c->file.fd, &c->file.info) == NGX_FILE_ERROR) {
-            ngx_log_error(NGX_LOG_CRIT, c->log, ngx_errno,
-                          ngx_fd_info_n " \"%s\" failed", c->file.name.data);
+    if (ocache) {
+        if (ngx_strcmp(cache->path->name.data, ocache->path->name.data) != 0) {
+            ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
+                          "cache \"%V\" uses the \"%V\" cache path "
+                          "while previously it used the \"%V\" cache path",
+                          &shm_zone->name, &cache->path->name,
+                          &ocache->path->name);
 
             return NGX_ERROR;
         }
 
-        if (ngx_file_uniq(&c->file.info) == c->uniq) {
-            if (ngx_close_file(c->file.fd) == NGX_FILE_ERROR) {
-                ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
-                              ngx_close_file_n " \"%s\" failed",
-                              c->file.name.data);
+        cache->rbtree = ocache->rbtree;
+        cache->queue = ocache->queue;
+        cache->shpool = ocache->shpool;
+        cache->created = ocache->created;
+
+        return NGX_OK;
+    }
+
+    cache->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    cache->rbtree = ngx_slab_alloc(cache->shpool, sizeof(ngx_rbtree_t));
+    if (cache->rbtree == NULL) {
+        return NGX_ERROR;
+    }
+
+    sentinel = ngx_slab_alloc(cache->shpool, sizeof(ngx_rbtree_node_t));
+    if (sentinel == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_rbtree_init(cache->rbtree, sentinel,
+                    ngx_http_file_cache_rbtree_insert_value);
+
+    cache->queue = ngx_slab_alloc(cache->shpool, sizeof(ngx_queue_t));
+    if (cache->queue == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_queue_init(cache->queue);
+
+    cache->created = ngx_time();
+
+    return NGX_OK;
+}
+
+
+void
+ngx_http_file_cache_create_key(ngx_http_request_t *r)
+{
+    size_t             len;
+    ngx_str_t         *key;
+    ngx_uint_t         i;
+    ngx_md5_t          md5;
+    ngx_http_cache_t  *c;
+
+    c = r->cache;
+
+    len = 0;
+
+    ngx_crc32_init(c->crc32);
+    ngx_md5_init(&md5);
+
+    key = c->keys.elts;
+    for (i = 0; i < c->keys.nelts; i++) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http cache key: \"%V\"", &key[i]);
+
+        len += key[i].len;
+
+        ngx_crc32_update(&c->crc32, key[i].data, key[i].len);
+        ngx_md5_update(&md5, key[i].data, key[i].len);
+    }
+
+    c->header_start = sizeof(ngx_http_file_cache_header_t)
+                      + sizeof(ngx_http_file_cache_key) + len + 1;
+
+    ngx_crc32_final(c->crc32);
+    ngx_md5_final(c->key, &md5);
+}
+
+
+ngx_int_t
+ngx_http_file_cache_open(ngx_http_request_t *r)
+{
+    u_char                        *p;
+    time_t                         now;
+    ssize_t                        n;
+    ngx_int_t                      rc, rv;
+    ngx_uint_t                     cold, test;
+    ngx_path_t                    *path;
+    ngx_http_cache_t              *c;
+    ngx_pool_cleanup_t            *cln;
+    ngx_open_file_info_t           of;
+    ngx_http_file_cache_t         *cache;
+    ngx_http_core_loc_conf_t      *clcf;
+    ngx_http_file_cache_header_t  *h;
+
+    c = r->cache;
+    cache = c->file_cache;
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_http_file_cache_exists(r, cache);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache exists: %i u:%ui e:%d",
+                   rc, c->uses, c->exists);
+
+    if (rc == NGX_ERROR) {
+        return rc;
+    }
+
+    cln->handler = ngx_http_file_cache_cleanup;
+    cln->data = c;
+
+    if (rc == NGX_AGAIN) {
+        return rc;
+    }
+
+    now = ngx_time();
+
+    cold = (now - cache->created < cache->inactive) ? 1 : 0;
+
+    if (rc == NGX_OK) {
+
+        if (c->error) {
+            return c->error;
+        }
+
+        c->temp_file = 1;
+        test = c->exists ? 1 : 0;
+        rv = NGX_DECLINED;
+
+    } else { /* rc == NGX_DECLINED */
+
+        if (c->min_uses > 1) {
+
+            if (!cold) {
+                return NGX_AGAIN;
             }
 
-            return NGX_HTTP_CACHE_THE_SAME;
+            test = 1;
+            rv = NGX_AGAIN;
+
+        } else {
+            c->temp_file = 1;
+            test = cold ? 1 : 0;
+            rv = NGX_DECLINED;
         }
     }
 
-    n = ngx_read_file(&c->file, c->buf->pos, c->buf->end - c->buf->last, 0);
+    path = cache->path;
+
+    c->file.name.len = path->name.len + 1 + path->len
+                       + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+    c->file.name.data = ngx_pnalloc(r->pool, c->file.name.len + 1);
+    if (c->file.name.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(c->file.name.data, path->name.data, path->name.len);
+
+    p = c->file.name.data + path->name.len + 1 + path->len;
+    p = ngx_hex_dump(p, c->key, NGX_HTTP_CACHE_KEY_LEN);
+    *p = '\0';
+
+    ngx_create_hashed_filename(path, c->file.name.data, c->file.name.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "cache file: \"%s\"", c->file.name.data);
+
+    if (!test) {
+        return NGX_DECLINED;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
 
-    if (n == NGX_ERROR || n == NGX_AGAIN) {
+    of.uniq = c->uniq;
+    of.valid = clcf->open_file_cache_valid;
+    of.min_uses = clcf->open_file_cache_min_uses;
+    of.events = clcf->open_file_cache_events;
+    of.directio = NGX_OPEN_FILE_DIRECTIO_OFF;
+
+    if (ngx_open_cached_file(clcf->open_file_cache, &c->file.name, &of, r->pool)
+        != NGX_OK)
+    {
+        switch (of.err) {
+
+        case 0:
+            return NGX_ERROR;
+
+        case NGX_ENOENT:
+        case NGX_ENOTDIR:
+            return rv;
+
+        default:
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
+                          ngx_open_file_n " \"%s\" failed", c->file.name.data);
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache fd: %d", of.fd);
+
+    c->file.fd = of.fd;
+
+    c->buf = ngx_create_temp_buf(r->pool, c->body_start);
+    if (c->buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    n = ngx_read_file(&c->file, c->buf->pos, c->body_start, 0);
+
+    if (n == NGX_ERROR) {
         return n;
     }
 
-    if (n <= c->header_size) {
-        ngx_log_error(NGX_LOG_CRIT, c->log, 0,
+    if ((size_t) n <= c->header_start) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
                       "cache file \"%s\" is too small", c->file.name.data);
         return NGX_ERROR;
     }
 
-    h = (ngx_http_cache_header_t *) c->buf->pos;
-    c->expires = h->expires;
-    c->last_modified= h->last_modified;
-    c->date = h->date;
-    c->length = h->length;
+    h = (ngx_http_file_cache_header_t *) c->buf->pos;
 
-    if (h->key_len > (size_t) (c->buf->end - c->buf->pos)) {
-        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
-                      "cache file \"%s\" is probably invalid",
-                      c->file.name.data);
+    if (h->crc32 != c->crc32 || (size_t) h->header_start != c->header_start) {
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, 0,
+                      "cache file \"%s\" has md5 collision", c->file.name.data);
         return NGX_DECLINED;
     }
 
-#if 0
+    c->buf->last += n;
 
-    /* TODO */
-
-    if (c->key_len && h->key_len != c->key_len)  {
+    c->valid_sec = h->valid_sec;
+    c->last_modified = h->last_modified;
+    c->date = h->date;
+    c->valid_msec = h->valid_msec;
+    c->length = of.size;
+    c->body_start = h->body_start;
 
-        ngx_strncmp(h->key, c->key_data, h->key_len) != 0))
+    r->cached = 1;
+
+    if (cold) {
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
 
-        h->key[h->key_len] = '\0';
-        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
-                          "md5 collision: \"%s\" and \"%s\"",
-                          h->key, c->key.data);
-        return NGX_DECLINED;
+        c->node->uses = c->min_uses;
+        c->node->body_start = c->body_start;
+        c->node->exists = 1;
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
     }
 
-#endif
+    if (c->valid_sec < now) {
 
-    c->buf->last += n;
+        c->uses = c->min_uses;
 
-    if (c->expires < ngx_time()) {
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
-                       "http file cache expired");
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http file cache expired: %T %T", c->valid_sec, now);
+
         return NGX_HTTP_CACHE_STALE;
     }
 
     /* TODO: NGX_HTTP_CACHE_AGED */
 
-    /* STUB */ return NGX_DECLINED;
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_exists(ngx_http_request_t *r, ngx_http_file_cache_t *cache)
+{
+    ngx_int_t                    rc;
+    ngx_http_file_cache_node_t  *fcn;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    fcn = ngx_http_file_cache_lookup(cache, r->cache->key);
+
+    if (fcn) {
+        ngx_queue_remove(&fcn->queue);
+
+        if (fcn->error) {
+
+            if (fcn->valid_sec < ngx_time()) {
+                goto renew;
+            }
+
+            rc = NGX_OK;
+
+            goto done;
+        }
+
+        fcn->uses++;
+        fcn->count++;
+
+        if (fcn->exists) {
+
+            r->cache->exists = fcn->exists;
+            r->cache->body_start = fcn->body_start;
+
+            rc = NGX_OK;
+
+            goto done;
+        }
+
+        if (fcn->uses >= r->cache->min_uses) {
+
+            r->cache->exists = fcn->exists;
+
+            if (fcn->body_start) {
+                r->cache->body_start = fcn->body_start;
+            }
+
+            rc = NGX_OK;
+
+        } else {
+            rc = NGX_AGAIN;
+        }
+
+        goto done;
+    }
+
+    fcn = ngx_slab_alloc_locked(cache->shpool,
+                                sizeof(ngx_http_file_cache_node_t));
+    if (fcn == NULL) {
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        (void) ngx_http_file_cache_expire(cache, 1);
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+
+        fcn = ngx_slab_alloc_locked(cache->shpool,
+                                    sizeof(ngx_http_file_cache_node_t));
+        if (fcn == NULL) {
+            rc = NGX_ERROR;
+            goto failed;
+        }
+    }
+
+    ngx_memcpy((u_char *) &fcn->node.key, r->cache->key,
+               sizeof(ngx_rbtree_key_t));
+
+    ngx_memcpy(fcn->key, &r->cache->key[sizeof(ngx_rbtree_key_t)],
+               NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+    ngx_rbtree_insert(cache->rbtree, &fcn->node);
+
+renew:
+
+    rc = NGX_DECLINED;
+
+    fcn->uses = 1;
+    fcn->count = 1;
+    fcn->valid_msec = 0;
+    fcn->error = 0;
+    fcn->exists = 0;
+    fcn->valid_sec = 0;
+    fcn->uniq = 0;
+    fcn->body_start = 0;
+
+done:
+
+    fcn->expire = ngx_time() + cache->inactive;
+
+    ngx_queue_insert_head(cache->queue, &fcn->queue);
+
+    r->cache->uniq = fcn->uniq;
+    r->cache->uses = fcn->uses;
+    r->cache->error = fcn->error;
+    r->cache->node = fcn;
+
+failed:
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    return rc;
+}
+
+
+static ngx_http_file_cache_node_t *
+ngx_http_file_cache_lookup(ngx_http_file_cache_t *cache, u_char *key)
+{
+    ngx_int_t                    rc;
+    ngx_rbtree_key_t             node_key;
+    ngx_rbtree_node_t           *node, *sentinel;
+    ngx_http_file_cache_node_t  *fcn;
+
+    ngx_memcpy((u_char *) &node_key, key, sizeof(ngx_rbtree_key_t));
+
+    node = cache->rbtree->root;
+    sentinel = cache->rbtree->sentinel;
+
+    while (node != sentinel) {
+
+        if (node_key < node->key) {
+            node = node->left;
+            continue;
+        }
+
+        if (node_key > node->key) {
+            node = node->right;
+            continue;
+        }
+
+        /* node_key == node->key */
+
+        do {
+            fcn = (ngx_http_file_cache_node_t *) node;
+
+            rc = ngx_memcmp(&key[sizeof(ngx_rbtree_key_t)], fcn->key,
+                            NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+            if (rc == 0) {
+                return fcn;
+            }
+
+            node = (rc < 0) ? node->left : node->right;
+
+        } while (node != sentinel && node_key == node->key);
+
+        break;
+    }
+
+    /* not found */
+
+    return NULL;
+}
+
+
+static void
+ngx_http_file_cache_rbtree_insert_value(ngx_rbtree_node_t *temp,
+    ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
+{
+    ngx_rbtree_node_t           **p;
+    ngx_http_file_cache_node_t   *cn, *cnt;
+
+    for ( ;; ) {
+
+        if (node->key < temp->key) {
+
+            p = &temp->left;
+
+        } else if (node->key > temp->key) {
+
+            p = &temp->right;
+
+        } else { /* node->key == temp->key */
+
+            cn = (ngx_http_file_cache_node_t *) node;
+            cnt = (ngx_http_file_cache_node_t *) temp;
+
+            p = (ngx_memcmp(cn->key, cnt->key,
+                            NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t))
+                 < 0)
+                    ? &temp->left : &temp->right;
+        }
+
+        if (*p == sentinel) {
+            break;
+        }
+
+        temp = *p;
+    }
+
+    *p = node;
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+    ngx_rbt_red(node);
+}
+
+
+void
+ngx_http_file_cache_set_header(ngx_http_request_t *r, u_char *buf)
+{
+    ngx_http_file_cache_header_t  *h = (ngx_http_file_cache_header_t *) buf;
+
+    u_char            *p;
+    ngx_str_t         *key;
+    ngx_uint_t         i;
+    ngx_http_cache_t  *c;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache set header");
+
+    c = r->cache;
+
+    h->valid_sec = c->valid_sec;
+    h->last_modified = c->last_modified;
+    h->date = c->date;
+    h->crc32 = c->crc32;
+    h->valid_msec = (u_short) c->valid_msec;
+    h->header_start = (u_short) c->header_start;
+    h->body_start = (u_short) c->body_start;
+
+    p = buf + sizeof(ngx_http_file_cache_header_t);
+
+    p = ngx_cpymem(p, ngx_http_file_cache_key, sizeof(ngx_http_file_cache_key));
+
+    key = c->keys.elts;
+    for (i = 0; i < c->keys.nelts; i++) {
+        p = ngx_copy(p, key[i].data, key[i].len);
+    }
+
+    *p = LF;
+}
+
+
+void
+ngx_http_file_cache_update(ngx_http_request_t *r, ngx_temp_file_t *tf)
+{
+    ngx_int_t               rc;
+    ngx_file_uniq_t         uniq;
+    ngx_file_info_t         fi;
+    ngx_http_cache_t        *c;
+    ngx_ext_rename_file_t   ext;
+    ngx_http_file_cache_t  *cache;
+
+    c = r->cache;
+
+    if (c->updated) {
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache update");
+
+    c->updated = 1;
+
+    cache = c->file_cache;
+
+    uniq = 0;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache rename: \"%s\" to \"%s\"",
+                   tf->file.name.data, c->file.name.data);
+
+    ext.access = NGX_FILE_OWNER_ACCESS;
+    ext.path_access = NGX_FILE_OWNER_ACCESS;
+    ext.time = -1;
+    ext.create_path = 1;
+    ext.delete_file = 1;
+    ext.log_rename_error = 1;
+    ext.log = r->connection->log;
+
+    rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext);
+
+    if (rc == NGX_OK) {
+
+        if (ngx_fd_info(tf->file.fd, &fi) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", tf->file.name.data);
+
+            rc = NGX_ERROR;
+
+        } else {
+            uniq = ngx_file_uniq(&fi);
+        }
+    }
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+    c->node->uniq = uniq;
+    c->node->body_start = c->body_start;
+
+    if (rc == NGX_OK) {
+        c->node->exists = 1;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+}
+
+
+ngx_int_t
+ngx_http_cache_send(ngx_http_request_t *r)
+{
+    ngx_int_t          rc;
+    ngx_buf_t         *b;
+    ngx_chain_t        out;
+    ngx_http_cache_t  *c;
+
+    c = r->cache;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                  "http file cache send: %s", c->file.name.data);
+
+    /* we need to allocate all before the header would be sent */
+
+    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
+    if (b == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
+    if (b->file == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    b->file_pos = c->body_start;
+    b->file_last = c->length;
+
+    b->in_file = (c->length - c->body_start) ? 1: 0;
+    b->last_buf = (r == r->main) ? 1: 0;
+    b->last_in_chain = 1;
+
+    b->file->fd = c->file.fd;
+    b->file->name = c->file.name;
+    b->file->log = r->connection->log;
+
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+void
+ngx_http_file_cache_free(ngx_http_request_t *r, ngx_temp_file_t *tf)
+{
+    ngx_http_cache_t       *c;
+    ngx_http_file_cache_t  *cache;
+
+    c = r->cache;
+
+    if (c->updated) {
+        return;
+    }
+
+    c->updated = 1;
+
+    cache = c->file_cache;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http file cache free");
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+
+    if (c->error) {
+        c->node->valid_sec = c->valid_sec;
+        c->node->valid_msec = c->valid_msec;
+        c->node->error = c->error;
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    if (c->temp_file) {
+        if (tf && tf->file.fd != NGX_INVALID_FILE) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http file cache incomplete: \"%s\"",
+                           tf->file.name.data);
+
+            if (ngx_delete_file(tf->file.name.data) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                              ngx_delete_file_n " \"%s\" failed",
+                              tf->file.name.data);
+            }
+        }
+    }
+}
+
+
+static void
+ngx_http_file_cache_cleanup(void *data)
+{
+    ngx_http_cache_t  *c = data;
+
+    ngx_http_file_cache_t  *cache;
+
+    if (c->updated) {
+        return;
+    }
+
+    c->updated = 1;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->file.log, 0,
+                   "http file cache cleanup");
+
+    if (c->error) {
+        return;
+    }
+
+    cache = c->file_cache;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    c->node->count--;
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+}
+
+
+static time_t
+ngx_http_file_cache_expire(ngx_http_file_cache_t *cache, ngx_uint_t forced)
+{
+    u_char                      *name, *p;
+    size_t                       len;
+    time_t                       now, wait;
+    ngx_uint_t                   tries;
+    ngx_path_t                  *path;
+    ngx_queue_t                 *q;
+    ngx_http_file_cache_node_t  *fcn;
+    u_char                       key[2 * NGX_HTTP_CACHE_KEY_LEN];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                   "http file cache expire");
+
+    path = cache->path;
+    len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
+
+    now = ngx_time();
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    tries = 0;
+
+    for ( ;; ) {
+
+        if (ngx_queue_empty(cache->queue)) {
+            wait = cache->inactive;
+            break;
+        }
+
+        q = ngx_queue_last(cache->queue);
+
+        fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
+
+        if (!forced) {
+            wait = fcn->expire - now;
+
+            if (wait > 0) {
+                break;
+            }
+        }
+
+        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache expire: #%d %d %02xd%02xd%02xd%02xd",
+                       fcn->count, fcn->exists,
+                       fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
+
+        if (fcn->count) {
+
+            if (!forced) {
+
+                p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
+                                 sizeof(ngx_rbtree_key_t));
+                (void) ngx_hex_dump(p, fcn->key,
+                            NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+
+                /*
+                 * abnormally exited workers may leave locked cache entries,
+                 * and although it may be safe to remove them completely,
+                 * we prefer to remove them from inactive queue and rbtree
+                 * only, and to allow other leaks
+                 */
+
+                ngx_queue_remove(q);
+
+                ngx_rbtree_delete(cache->rbtree, &fcn->node);
+
+                ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                              "ignore long locked inactive cache entry %*s",
+                              NGX_HTTP_CACHE_KEY_LEN, key);
+            }
+
+            if (tries++ < 10) {
+                continue;
+            }
+
+            wait = 1;
+            break;
+        }
+
+        forced = 0;
+
+        if (!fcn->exists) {
+
+            ngx_queue_remove(q);
+
+            ngx_rbtree_delete(cache->rbtree, &fcn->node);
+
+            ngx_slab_free_locked(cache->shpool, fcn);
+
+            continue;
+        }
+
+        name = ngx_alloc(len + 1, ngx_cycle->log);
+        if (name == NULL) {
+            wait = 60;
+            break;
+        }
+
+        ngx_memcpy(name, path->name.data, path->name.len);
+
+        p = name + path->name.len + 1 + path->len;
+        p = ngx_hex_dump(p, (u_char *) &fcn->node.key,
+                         sizeof(ngx_rbtree_key_t));
+        p = ngx_hex_dump(p, fcn->key,
+                         NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t));
+        *p = '\0';
+
+        ngx_queue_remove(q);
+
+        ngx_rbtree_delete(cache->rbtree, &fcn->node);
+
+        ngx_slab_free_locked(cache->shpool, fcn);
+
+        ngx_shmtx_unlock(&cache->shpool->mutex);
+
+        ngx_create_hashed_filename(path, name, len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
+                       "http file cache expire: \"%s\"", name);
+
+        if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
+                          ngx_delete_file_n " \"%s\" failed", name);
+        }
+
+        ngx_free(name);
+
+        ngx_shmtx_lock(&cache->shpool->mutex);
+    }
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    return wait;
+}
+
+
+time_t
+ngx_http_file_cache_cleaner(void *data)
+{
+    ngx_http_file_cache_t  *cache = data;
+
+    time_t          now, next;
+    ngx_tree_ctx_t  tree;
+
+    now = ngx_time();
+
+    if (now >= cache->next_clean_time) {
+
+        ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
+                      "clean unused cache files");
+
+        tree.init_handler = NULL;
+        tree.file_handler = ngx_http_file_cache_clean_file;
+        tree.pre_tree_handler = ngx_http_file_cache_clean_noop;
+        tree.post_tree_handler = ngx_http_file_cache_clean_noop;
+        tree.spec_handler = ngx_http_file_cache_clean_file;
+        tree.data = cache;
+        tree.alloc = 0;
+        tree.log = ngx_cycle->log;
+
+        (void) ngx_walk_tree(&tree, &cache->path->name);
+
+        ngx_time_update(0, 0);
+
+        next = ngx_next_time(cache->clean_time);
+
+        if (next == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
+                          ngx_next_time_n " failed");
+            return 60;
+        }
+
+        cache->next_clean_time = next;
+    }
+
+    next = ngx_http_file_cache_expire(cache, 0);
+
+    now = ngx_time();
+
+    return (now + next < cache->next_clean_time) ? next:
+                                                   cache->next_clean_time - now;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_clean_noop(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_file_cache_clean_file(ngx_tree_ctx_t *ctx, ngx_str_t *path)
+{
+    u_char                      *p, key[2 * NGX_HTTP_CACHE_KEY_LEN];
+    ngx_int_t                    n;
+    ngx_uint_t                   i;
+    ngx_http_file_cache_t       *cache;
+    ngx_http_file_cache_node_t  *node;
+
+    if (path->len < 2 * NGX_HTTP_CACHE_KEY_LEN) {
+        goto clean;
+    }
+
+    p = &path->data[path->len - 2 * NGX_HTTP_CACHE_KEY_LEN];
+
+    for (i = 0; i < NGX_HTTP_CACHE_KEY_LEN; i++) {
+        n = ngx_hextoi(p, 2);
+
+        if (n == NGX_ERROR) {
+            goto clean;
+        }
+
+        p += 2;
+
+        key[i] = (u_char) n;
+    }
+
+    cache = ctx->data;
+
+    ngx_shmtx_lock(&cache->shpool->mutex);
+
+    node = ngx_http_file_cache_lookup(cache, key);
+
+    ngx_shmtx_unlock(&cache->shpool->mutex);
+
+    if (node) {
+        return NGX_OK;
+    }
+
+clean:
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                   "http file cache clean: \"%s\"", path->data);
+
+    if (ngx_delete_file(path->data) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", path->data);
+        return NGX_ERROR;
+    }
 
     return NGX_OK;
 }
 
 
-#if 0
-
-
-int ngx_http_cache_update_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx,
-                               ngx_str_t *temp_file)
+time_t
+ngx_http_file_cache_valid(ngx_array_t *cache_valid, ngx_uint_t status)
 {
-    int        retry;
-    ngx_err_t  err;
+    ngx_uint_t               i;
+    ngx_http_cache_valid_t  *valid;
 
-    retry = 0;
+    valid = cache_valid->elts;
+    for (i = 0; i < cache_valid->nelts; i++) {
 
-    for ( ;; ) {
-        if (ngx_rename_file(temp_file->data, ctx->file.name.data) == NGX_OK) {
-            return NGX_OK;
+        if (valid[i].status == 0) {
+            return valid[i].valid;
         }
 
-        err = ngx_errno;
-
-#if (NGX_WIN32)
-        if (err == NGX_EEXIST) {
-            if (ngx_win32_rename_file(temp_file, &ctx->file.name, r->pool)
-                                                                  == NGX_ERROR)
-            {
-                return NGX_ERROR;
-            }
+        if (valid[i].status == status) {
+            return valid[i].valid;
         }
-#endif
+    }
 
-        if (retry || (err != NGX_ENOENT && err != NGX_ENOTDIR)) {
-            ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
-                          ngx_rename_file_n "(\"%s\", \"%s\") failed",
-                          temp_file->data, ctx->file.name.data);
-
-            return NGX_ERROR;
-        }
-
-        if (ngx_create_path(&ctx->file, ctx->path) == NGX_ERROR) {
-            return NGX_ERROR;
-        }
-
-        retry = 1;
-    }
+    return 0;
 }
 
 
-#endif
+char *
+ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    u_char                 *last, *p;
+    time_t                  inactive, clean_time, next;
+    ssize_t                 size;
+    ngx_str_t               s, name, *value;
+    ngx_uint_t              i, n;
+    ngx_http_file_cache_t  *cache;
+
+    cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
+    if (cache == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
+    if (cache->path == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    inactive = 600;
+    clean_time = 5 * 60 * 60;
+
+    name.len = 0;
+    size = 0;
+
+    value = cf->args->elts;
+
+    cache->path->name = value[1];
+
+    if (cache->path->name.data[cache->path->name.len - 1] == '/') {
+        cache->path->name.len--;
+    }
+
+    if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {
+
+            n = 0;
+            p = value[i].data + 7;
+            last = value[i].data + value[i].len;
+
+            while (p < last) {
+
+                if (*p > '0' && *p < '6') {
+
+                    cache->path->level[n] = *p++ - '0';
+                    cache->path->len += cache->path->level[n] + 1;
+
+                    if (p == last) {
+                        break;
+                    }
+
+                    if (*p++ == ':') {
+
+                        if (n > 2) {
+                            goto invalid_levels;
+                        }
+
+                        if (cache->path->level[n] == 0) {
+                            goto invalid_levels;
+                        }
+
+                        n++;
+
+                        continue;
+                    }
+                }
+
+                goto invalid_levels;
+            }
+
+            if (cache->path->len < 10 + 3) {
+                continue;
+            }
+
+        invalid_levels:
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid \"levels\" \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {
+
+            name.data = value[i].data + 10;
+
+            p = (u_char *) ngx_strchr(name.data, ':');
+
+            if (p) {
+                name.len = p - name.data;
+
+                p++;
+
+                s.len = value[i].data + value[i].len - p;
+                s.data = p;
+
+                size = ngx_parse_size(&s);
+                if (size > 8191) {
+                    continue;
+                }
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid keys zone size \"%V\"", &value[i]);
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {
+
+            s.len = value[i].len - 9;
+            s.data = value[i].data + 9;
+
+            inactive = ngx_parse_time(&s, 1);
+            if (inactive < 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid inactive value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        if (ngx_strncmp(value[i].data, "clean_time=", 11) == 0) {
+
+            s.len = value[i].len - 11;
+            s.data = value[i].data + 11;
+
+            clean_time = ngx_parse_time(&s, 1);
+            if (clean_time < 0 || clean_time > 24 * 60 * 60) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                 "invalid clean_time value \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+
+            continue;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%V\"", &value[i]);
+        return NGX_CONF_ERROR;
+    }
+
+    if (name.len == 0 || size == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"%V\" must have \"keys_zone\" parameter",
+                           &cmd->name);
+        return NGX_CONF_ERROR;
+    }
+
+    cache->path->cleaner = ngx_http_file_cache_cleaner;
+    cache->path->data = cache;
+
+    if (ngx_add_path(cf, &cache->path) != NGX_OK) {
+        return NGX_CONF_ERROR;
+    }
+
+    cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
+    if (cache->shm_zone == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cache->shm_zone->data) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate zone \"%V\"", &name);
+        return NGX_CONF_ERROR;
+    }
+
+    next = ngx_next_time(clean_time);
+
+    if (next == -1) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           ngx_next_time_n " failed");
+        return NGX_CONF_ERROR;
+    }
+
+    cache->shm_zone->init = ngx_http_file_cache_init;
+    cache->shm_zone->data = cache;
+
+    cache->inactive = inactive;
+    cache->clean_time = clean_time;
+    cache->next_clean_time = next;
+
+    return NGX_CONF_OK;
+}
 
 
-ngx_int_t ngx_http_cache_cleaner_handler(ngx_gc_t *gc, ngx_str_t *name,
-                                         ngx_dir_t *dir)
+char *
+ngx_http_file_cache_valid_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
 {
-    int               rc;
-    ngx_buf_t         buf;
-    ngx_http_cache_t  c;
-    u_char            data[sizeof(ngx_http_cache_header_t)];
+    char  *p = conf;
 
-    ngx_memzero(&c, sizeof(ngx_http_cache_t));
+    time_t                    valid;
+    ngx_str_t                *value;
+    ngx_uint_t                i, n, status;
+    ngx_array_t             **a;
+    ngx_http_cache_valid_t   *v;
+    static ngx_uint_t         statuses[] = { 200, 301, 302 };
 
-    c.file.fd = NGX_INVALID_FILE;
-    c.file.name = *name;
-    c.file.log = gc->log;
+    a = (ngx_array_t **) (p + cmd->offset);
 
-    c.header_size = sizeof(ngx_http_cache_header_t);
-    c.buf = &buf;
-    c.log = gc->log;
-    c.key_len = 0;
+    if (*a == NGX_CONF_UNSET_PTR) {
+        *a = ngx_array_create(cf->pool, 1, sizeof(ngx_http_cache_valid_t));
+        if (*a == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
 
-    buf.memory = 1;
-    buf.temporary = 1;
-    buf.pos = data;
-    buf.last = data;
-    buf.start = data;
-    buf.end = data + sizeof(ngx_http_cache_header_t);
+    value = cf->args->elts;
+    n = cf->args->nelts - 1;
 
-    rc = ngx_http_file_cache_open(&c);
-
-    /* TODO: NGX_AGAIN */
-
-    if (rc != NGX_ERROR&& rc != NGX_DECLINED && rc != NGX_HTTP_CACHE_STALE) {
-        return NGX_OK;
+    valid = ngx_parse_time(&value[n], 1);
+    if (valid < 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid time value \"%V\"", &value[n]);
+        return NGX_CONF_ERROR;
     }
 
-    if (ngx_delete_file(name->data) == NGX_FILE_ERROR) {
-        ngx_log_error(NGX_LOG_CRIT, c.log, ngx_errno,
-                      ngx_delete_file_n " \"%s\" failed", name->data);
-        return NGX_ERROR;
+    if (n == 1) {
+
+        for (i = 0; i < 3; i++) {
+            v = ngx_array_push(*a);
+            if (v == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            v->status = statuses[i];
+            v->valid = valid;
+        }
+
+        return NGX_CONF_OK;
     }
 
-    gc->deleted++;
-    gc->freed += ngx_de_size(dir);
+    for (i = 1; i < n; i++) {
+
+        if (ngx_strcmp(value[i].data, "any") == 0) {
+
+            status = 0;
+
+        } else {
 
-    return NGX_OK;
+            status = ngx_atoi(value[i].data, value[i].len);
+            if (status < 100) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid status \"%V\"", &value[i]);
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        v = ngx_array_push(*a);
+        if (v == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        v->status = status;
+        v->valid = valid;
+    }
+
+    return NGX_CONF_OK;
 }