static ngx_int_t
ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
    ngx_log_t *log)
{
    ngx_fd_t         fd;
    ngx_file_info_t  fi;

    if (of->fd != NGX_INVALID_FILE) {

        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
            of->fd = NGX_INVALID_FILE;
            return NGX_ERROR;
        }
		// 判断文件是否被改变
        if (of->uniq == ngx_file_uniq(&fi)) {
            goto done;
        }

    } else if (of->test_dir) {

        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
            of->fd = NGX_INVALID_FILE;
            return NGX_ERROR;
        }

        if (ngx_is_dir(&fi)) {
            goto done;
        }
    }
	// 否则重新打开
    if (!of->log) {

        /*
         * Use non-blocking open() not to hang on FIFO files, etc.
         * This flag has no effect on a regular files.
         */

        fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
                                   NGX_FILE_OPEN, 0, log);

    } else {
        fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
                                   NGX_FILE_CREATE_OR_OPEN,
                                   NGX_FILE_DEFAULT_ACCESS, log);
    }

    if (fd == NGX_INVALID_FILE) {
        of->fd = NGX_INVALID_FILE;
        return NGX_ERROR;
    }

    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
                      ngx_fd_info_n " \"%V\" failed", name);

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

        return NGX_ERROR;
    }

    if (ngx_is_dir(&fi)) {
        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

    } else {
        of->fd = fd;

        if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) {
            if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_read_ahead_n " \"%V\" failed", name);
            }
        }

        if (of->directio <= ngx_file_size(&fi)) {
            if (ngx_directio_on(fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_directio_on_n " \"%V\" failed", name);

            } else {
                of->is_directio = 1;
            }
        }
    }

done:

    of->uniq = ngx_file_uniq(&fi);
    of->mtime = ngx_file_mtime(&fi);
    of->size = ngx_file_size(&fi);
    of->fs_size = ngx_file_fs_size(&fi);
    of->is_dir = ngx_is_dir(&fi);
    of->is_file = ngx_is_file(&fi);
    of->is_link = ngx_is_link(&fi);
    of->is_exec = ngx_is_exec(&fi);

    return NGX_OK;
}
ngx_int_t
ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
    ngx_open_file_info_t *of, ngx_pool_t *pool)
{
    time_t                          now;
    uint32_t                        hash;
    ngx_int_t                       rc;
    ngx_file_info_t                 fi;
    ngx_pool_cleanup_t             *cln;
    ngx_cached_open_file_t         *file;
    ngx_pool_cleanup_file_t        *clnf;
    ngx_open_file_cache_cleanup_t  *ofcln;

    of->fd = NGX_INVALID_FILE;
    of->err = 0;

    if (cache == NULL) {

        if (of->test_only) {

            if (ngx_file_info_wrapper(name, of, &fi, pool->log)
                == NGX_FILE_ERROR)
            {
                return NGX_ERROR;
            }

            of->uniq = ngx_file_uniq(&fi);
            of->mtime = ngx_file_mtime(&fi);
            of->size = ngx_file_size(&fi);
            of->fs_size = ngx_file_fs_size(&fi);
            of->is_dir = ngx_is_dir(&fi);
            of->is_file = ngx_is_file(&fi);
            of->is_link = ngx_is_link(&fi);
            of->is_exec = ngx_is_exec(&fi);

            return NGX_OK;
        }

        cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
        if (cln == NULL) {
            return NGX_ERROR;
        }

        rc = ngx_open_and_stat_file(name, of, pool->log);

        if (rc == NGX_OK && !of->is_dir) {
            cln->handler = ngx_pool_cleanup_file;
            clnf = cln->data;

            clnf->fd = of->fd;
            clnf->name = name->data;
            clnf->log = pool->log;
        }

        return rc;
    }

    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t));
    if (cln == NULL) {
        return NGX_ERROR;
    }

    now = ngx_time();
	// 计算hash
    hash = ngx_crc32_long(name->data, name->len);
	// 根据名字查找对应的file对象
    file = ngx_open_file_lookup(cache, name, hash);

    if (file) {

        file->uses++;

        ngx_queue_remove(&file->queue);

        if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) {

            /* file was not used often enough to keep open */

            rc = ngx_open_and_stat_file(name, of, pool->log);

            if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
                goto failed;
            }

            goto add_event;
        }

        if (file->use_event
            || (file->event == NULL
                && (of->uniq == 0 || of->uniq == file->uniq)
                && now - file->created < of->valid
#if (NGX_HAVE_OPENAT)
                && of->disable_symlinks == file->disable_symlinks
                && of->disable_symlinks_from == file->disable_symlinks_from
#endif
            ))
        {
            if (file->err == 0) {

                of->fd = file->fd;
                of->uniq = file->uniq;
                of->mtime = file->mtime;
                of->size = file->size;

                of->is_dir = file->is_dir;
                of->is_file = file->is_file;
                of->is_link = file->is_link;
                of->is_exec = file->is_exec;
                of->is_directio = file->is_directio;

                if (!file->is_dir) {
                    file->count++;
                    ngx_open_file_add_event(cache, file, of, pool->log);
                }

            } else {
                of->err = file->err;
#if (NGX_HAVE_OPENAT)
                of->failed = file->disable_symlinks ? ngx_openat_file_n
                                                    : ngx_open_file_n;
#else
                of->failed = ngx_open_file_n;
#endif
            }

            goto found;
        }

        ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
                       "retest open file: %s, fd:%d, c:%d, e:%d",
                       file->name, file->fd, file->count, file->err);

        if (file->is_dir) {

            /*
             * chances that directory became file are very small
             * so test_dir flag allows to use a single syscall
             * in ngx_file_info() instead of three syscalls
             */

            of->test_dir = 1;
        }

        of->fd = file->fd;
        of->uniq = file->uniq;
		// 打开文件,保存文件信息
        rc = ngx_open_and_stat_file(name, of, pool->log);

        if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
            goto failed;
        }

        if (of->is_dir) {

            if (file->is_dir || file->err) {
                goto update;
            }

            /* file became directory */

        } else if (of->err == 0) {  /* file */

            if (file->is_dir || file->err) {
                goto add_event;
            }

            if (of->uniq == file->uniq) {

                if (file->event) {
                    file->use_event = 1;
                }

                of->is_directio = file->is_directio;

                goto update;
            }

            /* file was changed */

        } else { /* error to cache */

            if (file->err || file->is_dir) {
                goto update;
            }

            /* file was removed, etc. */
        }

        if (file->count == 0) {

            ngx_open_file_del_event(file);

            if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                              ngx_close_file_n " \"%V\" failed", name);
            }

            goto add_event;
        }

        ngx_rbtree_delete(&cache->rbtree, &file->node);

        cache->current--;

        file->close = 1;

        goto create;
    }

    /* not found */

    rc = ngx_open_and_stat_file(name, of, pool->log);

    if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
        goto failed;
    }

create:
	// max为open_file_cache命令中定义的那个max指令,
	// 而current也就是当前cache的文件个数
    if (cache->current >= cache->max) {
		// 如果大于max,则需要强制expire几个元素
        ngx_expire_old_cached_files(cache, 0, pool->log);
    }

    file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log);

    if (file == NULL) {
        goto failed;
    }

    file->name = ngx_alloc(name->len + 1, pool->log);

    if (file->name == NULL) {
        ngx_free(file);
        file = NULL;
        goto failed;
    }

    ngx_cpystrn(file->name, name->data, name->len + 1);

    file->node.key = hash;

    ngx_rbtree_insert(&cache->rbtree, &file->node);

    cache->current++;

    file->uses = 1;
    file->count = 0;
    file->use_event = 0;
    file->event = NULL;

add_event:

    ngx_open_file_add_event(cache, file, of, pool->log);

update:

    file->fd = of->fd;
    file->err = of->err;
#if (NGX_HAVE_OPENAT)
    file->disable_symlinks = of->disable_symlinks;
    file->disable_symlinks_from = of->disable_symlinks_from;
#endif

    if (of->err == 0) {
        file->uniq = of->uniq;
        file->mtime = of->mtime;
        file->size = of->size;

        file->close = 0;

        file->is_dir = of->is_dir;
        file->is_file = of->is_file;
        file->is_link = of->is_link;
        file->is_exec = of->is_exec;
        file->is_directio = of->is_directio;

        if (!of->is_dir) {
            file->count++;
        }
    }

    file->created = now;

found:
	// 更新存取时间
    file->accessed = now;
	// 将文件插入到超时队列中
    ngx_queue_insert_head(&cache->expire_queue, &file->queue);

    ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
                   "cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
                   file->name, file->fd, file->count, file->err, file->uses);

    if (of->err == 0) {

        if (!of->is_dir) {
			// 这里很关键,将cln的handler
            cln->handler = ngx_open_file_cleanup;
            ofcln = cln->data;

            ofcln->cache = cache;
            ofcln->file = file;
            ofcln->min_uses = of->min_uses;
            ofcln->log = pool->log;
        }

        return NGX_OK;
    }

    return NGX_ERROR;

failed:

    if (file) {
        ngx_rbtree_delete(&cache->rbtree, &file->node);

        cache->current--;

        if (file->count == 0) {

            if (file->fd != NGX_INVALID_FILE) {
                if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
                    ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                                  ngx_close_file_n " \"%s\" failed",
                                  file->name);
                }
            }

            ngx_free(file->name);
            ngx_free(file);

        } else {
            file->close = 1;
        }
    }

    if (of->fd != NGX_INVALID_FILE) {
        if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }
    }

    return NGX_ERROR;
}
static ngx_int_t
ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of, ngx_log_t *log)
{
    ngx_fd_t         fd;
    ngx_file_info_t  fi;

    of->fd = NGX_INVALID_FILE;

    if (of->test_dir) {

        if (ngx_file_info(name, &fi) == -1) {
            of->err = ngx_errno;

            return NGX_ERROR;
        }

        of->uniq = ngx_file_uniq(&fi);
        of->mtime = ngx_file_mtime(&fi);
        of->size = ngx_file_size(&fi);
        of->is_dir = ngx_is_dir(&fi);
        of->is_file = ngx_is_file(&fi);
        of->is_link = ngx_is_link(&fi);
        of->is_exec = ngx_is_exec(&fi);

        if (of->is_dir) {
            return NGX_OK;
        }
    }

    fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);

    if (fd == NGX_INVALID_FILE) {
        of->err = ngx_errno;
        return NGX_ERROR;
    }

    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
                      ngx_fd_info_n " \"%s\" failed", name);

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", name);
        }

        return NGX_ERROR;
    }

    if (ngx_is_dir(&fi)) {
        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", name);
        }

        fd = NGX_INVALID_FILE;
    }

    of->fd = fd;
    of->uniq = ngx_file_uniq(&fi);
    of->mtime = ngx_file_mtime(&fi);
    of->size = ngx_file_size(&fi);
    of->is_dir = ngx_is_dir(&fi);
    of->is_file = ngx_is_file(&fi);
    of->is_link = ngx_is_link(&fi);
    of->is_exec = ngx_is_exec(&fi);

    return NGX_OK;
}
Пример #4
0
static ngx_int_t
ngx_open_and_stat_file(u_char *name, ngx_open_file_info_t *of, ngx_log_t *log)
{
    int              mode;
    ngx_fd_t         fd;
    ngx_file_info_t  fi;

    if (of->fd != NGX_INVALID_FILE) {

        if (ngx_file_info(name, &fi) == NGX_FILE_ERROR) {
            of->failed = ngx_file_info_n;
            goto failed;
        }

        if (of->uniq == ngx_file_uniq(&fi)) {
            goto done;
        }

    } else if (of->test_dir) {

        if (ngx_file_info(name, &fi) == NGX_FILE_ERROR) {
            of->failed = ngx_file_info_n;
            goto failed;
        }

        if (ngx_is_dir(&fi)) {
            goto done;
        }
    }

    if (!of->log) {

        /*
         * Use non-blocking open() not to hang on FIFO files, etc.
         * This flag has no effect on a regular files.
         */

        mode = NGX_FILE_RDONLY|NGX_FILE_NONBLOCK;

#if (NGX_WIN32 && NGX_HAVE_FILE_AIO)
        if (ngx_event_flags & NGX_USE_IOCP_EVENT && of->aio) {
            mode |= NGX_FILE_OVERLAPPED;
        }
#endif

        fd = ngx_open_file(name, mode, NGX_FILE_OPEN, 0);

    } else {
        fd = ngx_open_file(name, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN,
                           NGX_FILE_DEFAULT_ACCESS);
    }

    if (fd == NGX_INVALID_FILE) {
        of->failed = ngx_open_file_n;
        goto failed;
    }

    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
                      ngx_fd_info_n " \"%s\" failed", name);

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

        return NGX_ERROR;
    }

    if (ngx_is_dir(&fi)) {
        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%s\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

    } else {
        of->fd = fd;

        if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) {
            if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_read_ahead_n " \"%s\" failed", name);
            }
        }

        if (of->directio <= ngx_file_size(&fi)) {
            if (ngx_directio_on(fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_directio_on_n " \"%s\" failed", name);

            } else {
                of->is_directio = 1;
            }
        }
    }

done:

    of->uniq = ngx_file_uniq(&fi);
    of->mtime = ngx_file_mtime(&fi);
    of->size = ngx_file_size(&fi);
    of->fs_size = ngx_file_fs_size(&fi);
    of->is_dir = ngx_is_dir(&fi);
    of->is_file = ngx_is_file(&fi);
    of->is_link = ngx_is_link(&fi);
    of->is_exec = ngx_is_exec(&fi);

    return NGX_OK;

failed:

    of->fd = NGX_INVALID_FILE;
    of->err = ngx_errno;

    return NGX_ERROR;
}
Пример #5
0
static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r)
{
  u_char                      *last;
  ngx_fd_t                     fd;
  ngx_int_t                    rc;
  ngx_uint_t                   level;
  ngx_str_t                    name, location;
  ngx_err_t                    err;
  ngx_log_t                   *log;
  ngx_buf_t                   *b;
  ngx_chain_t                  out;
  ngx_file_info_t              fi;
  ngx_http_cleanup_t          *file_cleanup, *redirect_cleanup;
  ngx_http_log_ctx_t          *ctx;
  ngx_http_core_loc_conf_t    *clcf;
  ngx_http_static_loc_conf_t  *slcf;
#if (NGX_HTTP_CACHE)
  uint32_t                     file_crc, redirect_crc;
  ngx_http_cache_t            *file, *redirect;
#endif

  if (r->uri.data[r->uri.len - 1] == '/') {
    return NGX_DECLINED;
  }

  if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
    return NGX_HTTP_NOT_ALLOWED;
  }

  rc = ngx_http_discard_body(r);

  if (rc != NGX_OK && rc != NGX_AGAIN) {
    return rc;
  }

#if (NGX_HTTP_CACHE)

  /*
   * there is a valid cached open file, i.e by the index handler,
   * and it should be already registered in r->cleanup
   */

  if (r->cache && !r->cache->expired) {
    return ngx_http_send_cached(r);
  }

#endif

  log = r->connection->log;

  clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

  /*
   * make a file name, reserve 2 bytes for a trailing '/'
   * in a possible redirect and for the last '\0'
   */

  if (clcf->alias) {
    name.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len + 2
                    - clcf->name.len);
    if (name.data == NULL) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    last = ngx_cpymem(name.data, clcf->root.data, clcf->root.len);
    last = ngx_cpystrn(last, r->uri.data + clcf->name.len,
               r->uri.len + 1 - clcf->name.len);

    name.len = last - name.data;

    location.data = ngx_palloc(r->pool, r->uri.len + 2);
    if (location.data == NULL) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    last = ngx_cpystrn(location.data, r->uri.data, r->uri.len + 1);

#if 0
    /*
     * aliases usually have trailling "/",
     * set it in the start of the possible redirect
     */

    if (*location.data != '/') {
      location.data--;
    }
#endif

    location.len = last - location.data + 1;

  } else {
    name.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len + 2);
    if (name.data == NULL) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    location.data = ngx_cpymem(name.data, clcf->root.data, clcf->root.len);
    last = ngx_cpystrn(location.data, r->uri.data, r->uri.len + 1);

    name.len = last - name.data;
    location.len = last - location.data + 1;
  }

  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
           "http filename: \"%s\"", name.data);


  /* allocate cleanups */

  if (!(file_cleanup = ngx_push_array(&r->cleanup))) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
  }
  file_cleanup->valid = 0;

  slcf = ngx_http_get_module_loc_conf(r, ngx_http_static_module);
  if (slcf->redirect_cache) {
    if (!(redirect_cleanup = ngx_push_array(&r->cleanup))) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    redirect_cleanup->valid = 0;

  } else {
    redirect_cleanup = NULL;
  }

#if (NGX_HTTP_CACHE)

  /* look up an open files cache */

  if (clcf->open_files) {
    file = ngx_http_cache_get(clcf->open_files, file_cleanup,
                  &name, &file_crc);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
             "http open file cache get: " PTR_FMT, file);

    if (file && !file->expired) {
      r->cache = file;
      return ngx_http_send_cached(r);
    }

  } else {
    file = NULL;
  }


  /* look up an redirect cache */

  if (slcf->redirect_cache) {
    redirect = ngx_http_cache_get(slcf->redirect_cache, redirect_cleanup,
                    &name, &redirect_crc);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
             "http redirect cache get: " PTR_FMT, redirect);

    if (redirect && !redirect->expired) {

      /*
       * We do not copy a cached value so the cache entry is locked
       * until the end of the request.  In a single threaded model
       * the redirected request should complete before other event
       * will be processed.  In a multithreaded model this locking
       * should keep more popular redirects in cache.
       */

      if (!(r->headers_out.location =
           ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
      {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
      }

      r->headers_out.location->value = redirect->data.value;

      return NGX_HTTP_MOVED_PERMANENTLY;
    }

  } else {
    redirect = NULL;
  }

#endif

  /* open file */

#if (WIN9X)

  /* TODO: redirect cache */

  if (ngx_win32_version < NGX_WIN_NT) {

    /*
     * there is no way to open a file or a directory in Win9X with
     * one syscall because Win9X has no FILE_FLAG_BACKUP_SEMANTICS flag
     * so we need to check its type before the opening
     */

    if (ngx_file_info(name.data, &fi) == NGX_FILE_ERROR) {
      err = ngx_errno;
      ngx_log_error(NGX_LOG_ERR, log, err,
              ngx_file_info_n " \"%s\" failed", name.data);

      if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
        return NGX_HTTP_NOT_FOUND;

      } else if (err == NGX_EACCES) {
        return NGX_HTTP_FORBIDDEN;

      } else {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
      }
    }

    if (ngx_is_dir(&fi)) {
      ngx_log_debug(log, "HTTP DIR: '%s'" _ name.data);

      if (!(r->headers_out.location =
           ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
      {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
      }

      *last++ = '/';
      *last = '\0';
      r->headers_out.location->value.len = last - location;
      r->headers_out.location->value.data = location;

      return NGX_HTTP_MOVED_PERMANENTLY;
    }
  }

#endif


  fd = ngx_open_file(name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN);

  if (fd == NGX_INVALID_FILE) {
    err = ngx_errno;

    if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
      level = NGX_LOG_ERR;
      rc = NGX_HTTP_NOT_FOUND;

    } else if (err == NGX_EACCES) {
      level = NGX_LOG_ERR;
      rc = NGX_HTTP_FORBIDDEN;

    } else {
      level = NGX_LOG_CRIT;
      rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_log_error(level, log, err,
            ngx_open_file_n " \"%s\" failed", name.data);

    return rc;
  }

  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", fd);

  if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
    ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
            ngx_fd_info_n " \"%s\" failed", name.data);

    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
      ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
              ngx_close_file_n " \"%s\" failed", name.data);
    }

    return NGX_HTTP_INTERNAL_SERVER_ERROR;
  }

  if (ngx_is_dir(&fi)) {

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");

    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
      ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
              ngx_close_file_n " \"%s\" failed", name.data);
    }

    *last++ = '/';
    *last = '\0';

    r->headers_out.location = ngx_list_push(&r->headers_out.headers);
    if (r->headers_out.location == NULL) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->headers_out.location->value = location;

#if (NGX_HTTP_CACHE)

    if (slcf->redirect_cache) {
      if (redirect) {
        if (location.len == redirect->data.value.len
          && ngx_memcmp(redirect->data.value.data, location.data,
                              location.len) == 0)
        {
          redirect->accessed = ngx_cached_time;
          redirect->updated = ngx_cached_time;

          /*
           * we can unlock the cache entry because
           * we have the local copy anyway
           */

          ngx_http_cache_unlock(slcf->redirect_cache, redirect, log);
          redirect_cleanup->valid = 0;

          return NGX_HTTP_MOVED_PERMANENTLY;
        }
      }

      location.len++;
      redirect = ngx_http_cache_alloc(slcf->redirect_cache, redirect,
                      redirect_cleanup,
                      &name, redirect_crc,
                      &location, log);
      location.len--;

      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
               "http redirect cache alloc: " PTR_FMT, redirect);

      if (redirect) {
        redirect->fd = NGX_INVALID_FILE;
        redirect->accessed = ngx_cached_time;
        redirect->last_modified = 0;
        redirect->updated = ngx_cached_time;
        redirect->memory = 1;
        ngx_http_cache_unlock(slcf->redirect_cache, redirect, log);
        redirect_cleanup->valid = 0;
      }

    }

#endif

    return NGX_HTTP_MOVED_PERMANENTLY;
  }

#if !(WIN32) /* the not regular files are probably Unix specific */

  if (!ngx_is_file(&fi)) {
    ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
            "%s is not a regular file", name.data);

    if (ngx_close_file(fd) == NGX_FILE_ERROR) {
      ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
              ngx_close_file_n " \"%s\" failed", name.data);
    }

    return NGX_HTTP_NOT_FOUND;
  }

#endif


#if (NGX_HTTP_CACHE)

  if (clcf->open_files) {

#if (NGX_USE_HTTP_FILE_CACHE_UNIQ)

    if (file && file->uniq == ngx_file_uniq(&fi)) {
      if (ngx_close_file(fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                ngx_close_file_n " \"%s\" failed", name.data);
      }
      file->accessed = ngx_cached_time;
      file->updated = ngx_cached_time;
      file->expired = 0;
      r->cache = file;

      return ngx_http_send_cached(r);

    } else {
      if (file) {
        ngx_http_cache_unlock(clcf->open_files, file, log);
        file = NULL;
      }

      file = ngx_http_cache_alloc(clcf->open_files, file,
                    file_cleanup,
                    &name, file_crc, NULL, log);
      if (file) {
        file->uniq = ngx_file_uniq(&fi);
      }
    }

#else
    file = ngx_http_cache_alloc(clcf->open_files, file,
                  file_cleanup,
                  &name, file_crc, NULL, log);
#endif

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
             "http open file cache alloc: " PTR_FMT, file);

    if (file) {
      file->fd = fd;
      file->data.size = ngx_file_size(&fi);
      file->accessed = ngx_cached_time;
      file->last_modified = ngx_file_mtime(&fi);
      file->updated = ngx_cached_time;
      r->cache = file;
    }

    return ngx_http_send_cached(r);
  }

#endif

  ctx = log->data;
  ctx->action = "sending response to client";

  file_cleanup->data.file.fd = fd;
  file_cleanup->data.file.name = name.data;
  file_cleanup->valid = 1;
  file_cleanup->cache = 0;

  r->headers_out.status = NGX_HTTP_OK;
  r->headers_out.content_length_n = ngx_file_size(&fi);
  r->headers_out.last_modified_time = ngx_file_mtime(&fi);

  if (r->headers_out.content_length_n == 0) {
    r->header_only = 1;
  }

  if (ngx_http_set_content_type(r) != NGX_OK) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
  }

#if (NGX_SUPPRESS_WARN)
  b = NULL;
#endif

  if (!r->header_only) {
    /* we need to allocate all before the header would be sent */

    if (!(b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)))) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (!(b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)))) {
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->filter_allow_ranges = 1;
  }

  rc = ngx_http_send_header(r);

  if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
    return rc;
  }

  b->in_file = 1;

  if (!r->main) {
    b->last_buf = 1;
  }

  b->file_pos = 0;
  b->file_last = ngx_file_size(&fi);

  b->file->fd = fd;
  b->file->log = log;

  out.buf = b;
  out.next = NULL;

  return ngx_http_output_filter(r, &out);
}