changeset 0:f0b350454894 NGINX_0_1_0

nginx 0.1.0 *) The first public version.
author Igor Sysoev <http://sysoev.ru>
date Mon, 04 Oct 2004 00:00:00 +0400
parents
children 80bdda0151b0
files LICENSE README auto/cc auto/define auto/endianess auto/feature auto/fmt/fmt auto/fmt/ptrfmt auto/fmt/xfmt auto/func auto/have auto/headers auto/inc auto/init auto/install auto/lib/conf auto/lib/make auto/lib/md5/conf auto/lib/md5/make auto/lib/md5/makefile.bcc auto/lib/md5/makefile.msvc auto/lib/md5/makefile.owc auto/lib/openssl/conf auto/lib/openssl/make auto/lib/pcre/conf auto/lib/pcre/make auto/lib/pcre/makefile.bcc auto/lib/pcre/makefile.msvc auto/lib/pcre/makefile.owc auto/lib/pcre/patch.config.in auto/lib/pcre/patch.pcre.in auto/lib/test auto/lib/zlib/conf auto/lib/zlib/make auto/lib/zlib/makefile.bcc auto/lib/zlib/makefile.msvc auto/lib/zlib/makefile.owc auto/make auto/modules auto/nohave auto/options auto/os/conf auto/os/freebsd auto/os/linux auto/os/solaris auto/sources auto/summary auto/threads auto/types/sizeof auto/types/typedef auto/types/uintptr_t auto/types/value auto/unix conf/koi-win conf/mime.types conf/nginx.conf configure html/index.html src/core/nginx.c src/core/nginx.h src/core/ngx_array.c src/core/ngx_array.h src/core/ngx_buf.c src/core/ngx_buf.h src/core/ngx_conf_file.c src/core/ngx_conf_file.h src/core/ngx_config.h src/core/ngx_connection.c src/core/ngx_connection.h src/core/ngx_core.h src/core/ngx_crc.h src/core/ngx_cycle.c src/core/ngx_cycle.h src/core/ngx_file.c src/core/ngx_file.h src/core/ngx_garbage_collector.c src/core/ngx_garbage_collector.h src/core/ngx_inet.c src/core/ngx_inet.h src/core/ngx_list.c src/core/ngx_list.h src/core/ngx_log.c src/core/ngx_log.h src/core/ngx_output_chain.c src/core/ngx_palloc.c src/core/ngx_palloc.h src/core/ngx_parse.c src/core/ngx_parse.h src/core/ngx_rbtree.c src/core/ngx_rbtree.h src/core/ngx_regex.c src/core/ngx_regex.h src/core/ngx_spinlock.c src/core/ngx_string.c src/core/ngx_string.h src/core/ngx_table.h src/core/ngx_times.c src/core/ngx_times.h src/event/modules/ngx_aio_module.c src/event/modules/ngx_aio_module.h src/event/modules/ngx_devpoll_module.c src/event/modules/ngx_epoll_module.c src/event/modules/ngx_kqueue_module.c src/event/modules/ngx_kqueue_module.h src/event/modules/ngx_poll_module.c src/event/modules/ngx_rtsig_module.c src/event/modules/ngx_select_module.c src/event/ngx_event.c src/event/ngx_event.h src/event/ngx_event_accept.c src/event/ngx_event_busy_lock.c src/event/ngx_event_busy_lock.h src/event/ngx_event_connect.c src/event/ngx_event_connect.h src/event/ngx_event_mutex.c src/event/ngx_event_openssl.c src/event/ngx_event_openssl.h src/event/ngx_event_pipe.c src/event/ngx_event_pipe.h src/event/ngx_event_posted.c src/event/ngx_event_posted.h src/event/ngx_event_timer.c src/event/ngx_event_timer.h src/http/modules/ngx_http_access_handler.c src/http/modules/ngx_http_charset_filter.c src/http/modules/ngx_http_chunked_filter.c src/http/modules/ngx_http_gzip_filter.c src/http/modules/ngx_http_headers_filter.c src/http/modules/ngx_http_index_handler.c src/http/modules/ngx_http_not_modified_filter.c src/http/modules/ngx_http_range_filter.c src/http/modules/ngx_http_rewrite_handler.c src/http/modules/ngx_http_ssl_module.c src/http/modules/ngx_http_ssl_module.h src/http/modules/ngx_http_static_handler.c src/http/modules/ngx_http_userid_filter.c src/http/modules/proxy/ngx_http_proxy_cache.c src/http/modules/proxy/ngx_http_proxy_handler.c src/http/modules/proxy/ngx_http_proxy_handler.h src/http/modules/proxy/ngx_http_proxy_header.c src/http/modules/proxy/ngx_http_proxy_parse.c src/http/modules/proxy/ngx_http_proxy_upstream.c src/http/ngx_http.c src/http/ngx_http.h src/http/ngx_http_busy_lock.c src/http/ngx_http_busy_lock.h src/http/ngx_http_cache.c src/http/ngx_http_cache.h src/http/ngx_http_config.h src/http/ngx_http_copy_filter.c src/http/ngx_http_core_module.c src/http/ngx_http_core_module.h src/http/ngx_http_file_cache.c src/http/ngx_http_header_filter.c src/http/ngx_http_log_handler.c src/http/ngx_http_log_handler.h src/http/ngx_http_parse.c src/http/ngx_http_parse_time.c src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/ngx_http_request_body.c src/http/ngx_http_special_response.c src/http/ngx_http_write_filter.c src/os/unix/ngx_aio.h src/os/unix/ngx_aio_read.c src/os/unix/ngx_aio_read_chain.c src/os/unix/ngx_aio_write.c src/os/unix/ngx_aio_write_chain.c src/os/unix/ngx_alloc.c src/os/unix/ngx_alloc.h src/os/unix/ngx_atomic.h src/os/unix/ngx_channel.c src/os/unix/ngx_channel.h src/os/unix/ngx_daemon.c src/os/unix/ngx_errno.c src/os/unix/ngx_errno.h src/os/unix/ngx_files.c src/os/unix/ngx_files.h src/os/unix/ngx_freebsd.h src/os/unix/ngx_freebsd_config.h src/os/unix/ngx_freebsd_init.c src/os/unix/ngx_freebsd_rfork_thread.c src/os/unix/ngx_freebsd_rfork_thread.h src/os/unix/ngx_freebsd_sendfile_chain.c src/os/unix/ngx_linux.h src/os/unix/ngx_linux_config.h src/os/unix/ngx_linux_init.c src/os/unix/ngx_linux_sendfile_chain.c src/os/unix/ngx_os.h src/os/unix/ngx_posix_config.h src/os/unix/ngx_posix_init.c src/os/unix/ngx_process.c src/os/unix/ngx_process.h src/os/unix/ngx_process_cycle.c src/os/unix/ngx_process_cycle.h src/os/unix/ngx_pthread_thread.c src/os/unix/ngx_readv_chain.c src/os/unix/ngx_recv.c src/os/unix/ngx_send.c src/os/unix/ngx_shared.c src/os/unix/ngx_shared.h src/os/unix/ngx_socket.c src/os/unix/ngx_socket.h src/os/unix/ngx_solaris.h src/os/unix/ngx_solaris_config.h src/os/unix/ngx_solaris_init.c src/os/unix/ngx_solaris_sendfilev_chain.c src/os/unix/ngx_thread.h src/os/unix/ngx_time.c src/os/unix/ngx_time.h src/os/unix/ngx_types.h src/os/unix/ngx_user.h src/os/unix/ngx_writev_chain.c src/os/unix/rfork_thread.S
diffstat 214 files changed, 47329 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+/* 
+ * Copyright (C) 2002-2004 Igor Sysoev
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+
+Documentation is available at http://sysoev.ru/nginx/ only.
+
new file mode 100644
--- /dev/null
+++ b/auto/cc
@@ -0,0 +1,376 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS="$CFLAGS $CC_OPT"
+
+case $CC in
+
+    *gcc*)
+         # gcc 2.7.2.3, 2.8.1, 2.95.4,
+         #     3.0.4, 3.1.1, 3.2.3, 3.3.2, 3.3.3, 3.3.4, 3.4
+
+         # optimizations
+         #CFLAGS="$CFLAGS -O2 -fomit-frame-pointer"
+
+         case $CPU in
+             pentium)
+                 # optimize for Pentium and Athlon
+                 CPU_OPT="-march=pentium"
+             ;;
+
+             pentiumpro)
+                 # optimize for Pentium Pro, Pentium II and Pentium III
+                 CPU_OPT="-march=pentiumpro"
+             ;;
+
+             pentium4)
+                 # optimize for Pentium 4, gcc 3.x
+                 CPU_OPT="-march=pentium4"
+             ;;
+         esac
+
+         # STUB for batch builds
+         if [ $CC = gcc27 ]; then CPU_OPT=; fi
+
+         CFLAGS="$CFLAGS $PIPE $CPU_OPT"
+
+         if [ ".$PCRE_OPT" = "." ]; then
+             PCRE_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT"
+         else
+             PCRE_OPT="$PCRE_OPT $PIPE"
+         fi
+
+         if [ ".$MD5_OPT" = "." ]; then
+             MD5_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT"
+         else
+             MD5_OPT="$MD5_OPT $PIPE"
+         fi
+
+         if [ ".$ZLIB_OPT" = "." ]; then
+             ZLIB_OPT="-O2 -fomit-frame-pointer $PIPE $CPU_OPT"
+         else
+             ZLIB_OPT="$ZLIB_OPT $PIPE"
+         fi
+
+         # warnings
+         CFLAGS="$CFLAGS -O -W"
+         CFLAGS="$CFLAGS -Wall -Wpointer-arith"
+         #CFLAGS="$CFLAGS -Wconversion"
+         #CFLAGS="$CFLAGS -Winline"
+
+         # we have a lot of the unused function arguments
+         CFLAGS="$CFLAGS -Wno-unused"
+
+         # stop on warning
+         CFLAGS="$CFLAGS -Werror"
+
+         # ANSI C warnings
+         #CFLAGS="$CFLAGS -pedantic"
+
+         # debug
+         CFLAGS="$CFLAGS -g"
+
+         # DragonFly's gcc3 generates DWARF
+         #CFLAGS="$CFLAGS -g -gstabs"
+
+         have=HAVE_GCC_VARIADIC_MACROS . auto/have
+
+         if [ ".$CPP" = "." ]; then
+             CPP="$CC -E"
+         fi
+
+         LINK="\$(CC)"
+
+         INCOPT="-I "
+         COMPOPT="-c"
+         OBJOUT="-o "
+         BINOUT="-o "
+         OBJEXT="o"
+
+         CC_STRONG="$CC -Wall -Werror"
+    ;;
+
+
+    *icc)
+         # Intel C++ compiler 7.1, 8.0
+
+         # optimizations
+         CFLAGS="$CFLAGS -O"
+         # inline functions declared with __inline
+         #CFLAGS="$CFLAGS -Ob1"
+         # inline any function, at the compiler's discretion
+         CFLAGS="$CFLAGS -Ob2"
+
+         # single-file IP optimizations
+         #IPO="-ip"
+         # multi-file IP optimizations 
+         IPO="-ipo -ipo_obj"
+         CFLAGS="$CFLAGS $IPO"
+         CORE_LINK="$CORE_LINK $IPO"
+         CORE_LINK="$CORE_LINK -opt_report_file=$OBJS/opt_report_file"
+
+         case $CPU in
+             pentium)
+                 # optimize for Pentium and Athlon
+                 CPU_OPT="-march=pentium"
+             ;;
+
+             pentiumpro)
+                 # optimize for Pentium Pro, Pentium II and Pentium III
+                 CPU_OPT="-mcpu=pentiumpro -march=pentiumpro"
+             ;;
+
+             pentium4)
+                 # optimize for Pentium 4, default
+                 CPU_OPT="-march=pentium4"
+             ;;
+         esac
+
+         CFLAGS="$CFLAGS $CPU_OPT"
+
+         if [ ".$PCRE_OPT" = "." ]; then
+             PCRE_OPT="-O $IPO $CPU_OPT"
+         fi
+
+         if [ ".$MD5_OPT" = "." ]; then
+             MD5_OPT="-O $IPO $CPU_OPT"
+         fi
+
+         if [ ".$ZLIB_OPT" = "." ]; then
+             ZLIB_OPT="-O $IPO $CPU_OPT"
+         fi
+
+         # warnings
+         CFLAGS="$CFLAGS -w1"
+         #CFLAGS="$CFLAGS -w2"
+
+         # stop on warning
+         CFLAGS="$CFLAGS -Werror"
+
+         # debug
+         CFLAGS="$CFLAGS -g"
+
+         have=HAVE_C99_VARIADIC_MACROS . auto/have
+
+         LINK="\$(CC)"
+
+         INCOPT="-I "
+         COMPOPT="-c"
+         OBJOUT="-o "
+         BINOUT="-o "
+         OBJEXT="o"
+
+         CC_STRONG="$CC -w1 -Werror"
+    ;;
+
+
+    cl)
+         # MSVC 6.0 SP2
+
+         # optimizations
+
+         # maximize speed
+         CFLAGS="$CFLAGS -O2"
+         # enable global optimization
+         CFLAGS="$CFLAGS -Og"
+         # enable intrinsic functions
+         CFLAGS="$CFLAGS -Oi"
+         # inline expansion
+         CFLAGS="$CFLAGS -Ob1"
+         # enable frame pointer omission
+         CFLAGS="$CFLAGS -Oy"
+         # disable stack checking calls
+         CFLAGS="$CFLAGS -Gs"
+
+         case $CPU in
+             pentium)
+                 # optimize for Pentium and Athlon
+                 CPU_OPT="-G5"
+             ;;
+
+             pentiumpro)
+                 # optimize for Pentium Pro, Pentium II and Pentium III
+                 CPU_OPT="-G6"
+             ;;
+
+             pentium4)
+                 # optimize for Pentium 4
+                 #CPU_OPT="-G7"
+             ;;
+         esac
+
+         CFLAGS="$CFLAGS $CPU_OPT"
+
+         # warnings
+         #CFLAGS="$CFLAGS -W3"
+         CFLAGS="$CFLAGS -W4"
+
+         # stop on warning
+         CFLAGS="$CFLAGS -WX"
+
+         # link with libcmt.lib, multithreaded
+         #LIBC="-MT"
+         # link with msvcrt.dll
+         LIBC="-MD"
+
+         CFLAGS="$CFLAGS $LIBC"
+
+         # disable logo
+         CFLAGS="$CFLAGS -nologo"
+
+         LINK="\$(CC)"
+
+         # link flags
+         CORE_LINK="$CORE_LINK -link -verbose:lib"
+
+         # debug
+         CFLAGS="$CFLAGS -Yd"
+         CORE_LINK="$CORE_LINK -debug -debugtype:coff"
+
+         # precompiled headers
+         CORE_DEPS="$CORE_DEPS ngx_config.pch"
+         PCH="ngx_config.pch"
+         BUILDPCH="-Ycngx_config.h"
+         USEPCH="-Yungx_config.h"
+
+         INCOPT="-I "
+         COMPOPT="-c"
+         OBJOUT="-Fo"
+         BINOUT="-Fe"
+         OBJEXT="obj"
+         BINEXT=".exe"
+         #DIRSEP='\\'
+    ;;
+
+
+    wcl386)
+         # Open Watcom C 1.0, 1.2
+
+         # optimizations
+
+         # maximize speed
+         CFLAGS="$CFLAGS -ot"
+         # reorder instructions for best pipeline usage
+         CFLAGS="$CFLAGS -op"
+         # inline intrinsic functions
+         CFLAGS="$CFLAGS -oi"
+         # inline expansion
+         CFLAGS="$CFLAGS -oe"
+         # disable stack checking calls
+         CFLAGS="$CFLAGS -s"
+
+         case $CPU in
+             pentium)
+                 # optimize for Pentium and Athlon
+                 # register-based arguments passing conventions
+                 CPU_OPT="-5r"
+                 # stack-based arguments passing conventions
+                 #CPU_OPT="-5s"
+             ;;
+
+             pentiumpro)
+                 # optimize for Pentium Pro, Pentium II and Pentium III
+                 # register-based arguments passing conventions
+                 CPU_OPT="-6r"
+                 # stack-based arguments passing conventions
+                 #CPU_OPT="-6s"
+             ;;
+         esac
+
+         CFLAGS="$CFLAGS $CPU_OPT"
+
+         # warnings
+         #CFLAGS="$CFLAGS -w3"
+         CFLAGS="$CFLAGS -wx"
+
+         # stop on warning
+         CFLAGS="$CFLAGS -we"
+
+         # built target is NT
+         CFLAGS="$CFLAGS -bt=nt"
+
+         # multithreaded
+         CFLAGS="$CFLAGS -bm"
+
+         # debug
+         CFLAGS="$CFLAGS -d2"
+
+         # quiet
+         CFLAGS="$CFLAGS -zq"
+
+         # Open Watcom C 1.2
+         #have=HAVE_C99_VARIADIC_MACROS . auto/have
+
+         # precompiled headers
+         CORE_DEPS="$CORE_DEPS $OBJS\\ngx_config.pch"
+         PCH="$OBJS\\ngx_config.pch"
+         BUILDPCH="-fhq=$OBJS\\ngx_config.pch"
+         USEPCH="-fh=$OBJS\\ngx_config.pch"
+
+         LINK="\$(CC)"
+
+         # link flags
+         CORE_LINK="$CORE_LINK -l=nt"
+
+         INCOPT="-i="
+         COMPOPT="-c"
+         OBJOUT="-fo"
+         BINOUT="-fe="
+         OBJEXT="obj"
+         BINEXT=".exe"
+         DIRSEP='\\'
+
+         MAKE_SL=YES
+    ;;
+
+
+    bcc32)
+         # Borland C++ 5.5
+
+         # optimizations
+
+         # maximize speed
+         CFLAGS="$CFLAGS -O2"
+
+         case $CPU in
+             pentium)
+                 # optimize for Pentium and Athlon
+                 CPU_OPT="-5"
+             ;;
+
+             pentiumpro)
+                 # optimize for Pentium Pro, Pentium II and Pentium III
+                 CPU_OPT="-6"
+             ;;
+         esac
+
+         CFLAGS="$CFLAGS $CPU_OPT"
+
+         # multithreaded
+         CFLAGS="$CFLAGS -tWM"
+
+         # stop on warning
+         CFLAGS="$CFLAGS -w!"
+
+         # disable logo
+         CFLAGS="$CFLAGS -q"
+
+         # precompiled headers
+         CORE_DEPS="$CORE_DEPS $OBJS\\ngx_config.csm"
+         PCH="$OBJS\\ngx_config.csm"
+         BUILDPCH="-H=$OBJS\\ngx_config.csm"
+         USEPCH="-Hu -H=$OBJS\\ngx_config.csm"
+
+         LINK="\$(CC)"
+
+         INCOPT="-I"
+         COMPOPT="-c"
+         OBJOUT="-o" 
+         BINOUT="-e"
+         OBJEXT="obj"
+         BINEXT=".exe"
+         DIRSEP='\\'
+    ;;
+
+esac
new file mode 100644
--- /dev/null
+++ b/auto/define
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  $value
+#endif
+
+END
new file mode 100644
--- /dev/null
+++ b/auto/endianess
@@ -0,0 +1,41 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for system endianess ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for system endianess" >> $NGX_ERR
+
+
+cat << END > $NGX_AUTOTEST.c
+
+int main() {
+    int i = 0x11223344;
+    char *p;
+
+    p = (char *) &i;
+    if (*p == 0x44) return 0;
+    return 1;
+}
+
+END
+
+eval "${CC} -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    if $NGX_AUTOTEST 2>&1 > /dev/null; then
+        echo " little endianess"
+        have=HAVE_LITTLE_ENDIAN . auto/have
+    else
+        echo " big endianess"
+    fi
+
+    rm $NGX_AUTOTEST*
+
+else
+    rm $NGX_AUTOTEST*
+
+    echo
+    echo "$0: error: can not detect system endianess"
+    exit 1
+fi
new file mode 100644
--- /dev/null
+++ b/auto/feature
@@ -0,0 +1,56 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_feature ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_feature" >> $NGX_ERR
+
+ngx_found=no
+
+feature=`echo $ngx_feature_name | tr '[a-z]' '[A-Z]'`
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+$NGX_UNISTD_H
+$ngx_feature_inc
+
+int main() {
+    $ngx_feature_test;
+    return 0;
+}
+
+END
+
+test="$CC_WARN $CC_TEST_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c \
+      $ngx_feature_libs"
+eval "$test >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+
+    if [ $ngx_feature_run = yes ]; then
+       if $NGX_AUTOTEST 2>&1 > /dev/null; then
+           echo " found"
+           have=HAVE_$feature . auto/have
+           ngx_found=yes
+       else
+           echo " found but is not working"
+       fi
+
+    else
+       echo " found"
+       have=HAVE_$feature . auto/have
+       ngx_found=yes
+    fi
+
+else
+    echo " not found"
+    echo "---------" >> $NGX_ERR
+    cat $NGX_AUTOTEST.c >> $NGX_ERR
+    echo "---------" >> $NGX_ERR
+    echo $test >> $NGX_ERR
+    echo "---------" >> $NGX_ERR
+fi
+
+rm $NGX_AUTOTEST*
new file mode 100644
--- /dev/null
+++ b/auto/fmt/fmt
@@ -0,0 +1,80 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_type printf() format ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_type printf() format" >> $NGX_ERR
+
+ngx_fmt=no
+comma=
+
+for fmt in $ngx_formats
+do
+
+    cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <sys/resource.h>
+$NGX_INTTYPES_H
+$NGX_AUTO_CONFIG
+
+int main() {
+    printf("$fmt", ($ngx_type) $ngx_max_value);
+    return 0;
+}
+
+END
+
+    eval "$CC_WARN $CC_TEST_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c \
+          >> $NGX_ERR 2>&1"
+
+    max_value=`echo $ngx_max_value | sed -e "s/L*\$//"`
+
+    if [ -x $NGX_AUTOTEST ]; then
+        if [ "`$NGX_AUTOTEST`" = $max_value ]; then
+            if [ $ngx_fmt_collect = yes ]; then
+                echo $ngx_n "$comma \"${fmt}\" is appropriate" $ngx_c
+            else
+                echo $ngx_n "$comma \"${fmt}\" used" $ngx_c
+            fi
+            ngx_fmt=$fmt
+        fi
+    fi
+
+    rm $NGX_AUTOTEST*
+
+    if [ $ngx_fmt != no ]; then
+        if [ $ngx_fmt_collect = yes ]; then
+            eval "ngx_${ngx_size}_fmt=\"\${ngx_${ngx_size}_fmt} \$ngx_fmt\""
+            comma=","
+            continue
+        else
+            break
+        fi
+    fi
+
+    echo $ngx_n "$comma \"${fmt}\" is not appropriate" $ngx_c
+    comma=","
+done
+
+echo
+
+if [ $ngx_fmt = no ]; then
+    echo "$0: error: printf() $ngx_type format not found"
+    exit 1
+fi
+
+
+if [ $ngx_fmt_collect = no ]; then
+    cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_fmt_name
+#define $ngx_fmt_name  "$ngx_fmt"
+#endif
+
+END
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/fmt/ptrfmt
@@ -0,0 +1,72 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_type printf() format ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_type printf() format" >> $NGX_ERR
+
+ngx_fmt=no
+comma=
+fmtX=
+
+for fmt in $ngx_formats
+do
+
+    cat << END > $NGX_AUTOTEST.c
+
+int main() {
+    printf("$fmt", ($ngx_type) $ngx_max_value);
+    return 0;
+}
+
+END
+
+    eval "$CC_WARN $CC_TEST_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c \
+          >> $NGX_ERR 2>&1"
+
+    max_value=`echo $ngx_max_value | sed -e "s/L*\$//"`
+
+    if [ -x $NGX_AUTOTEST ]; then
+        if [ "`$NGX_AUTOTEST`" = $max_value ]; then
+            ngx_fmt=$fmt
+        fi
+    fi
+
+    rm $NGX_AUTOTEST*
+
+    if [ $ngx_fmt != no ]; then
+        break
+    fi
+
+    fmtX=`echo $fmt | sed -e "s/d/X/"`
+
+    echo $ngx_n "$comma \"${fmtX}\" is not appropriate" $ngx_c
+    comma=","
+done
+
+
+if [ $ngx_fmt = no ]; then
+    echo "$0: error: printf() $ngx_type format not found"
+    exit 1
+fi
+
+
+if [ $ngx_ptr_size = 4 ]; then
+    fmtX="%0`expr 2 \* $ngx_ptr_size`"
+else
+    fmtX="%"
+fi
+
+ngx_fmt=`echo $ngx_fmt | sed -e "s/d/X/" -e "s/^%/$fmtX/"`
+
+echo "$comma \"${ngx_fmt}\" used"
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_fmt_name
+#define $ngx_fmt_name  "$ngx_fmt"
+#endif
+
+END
new file mode 100644
--- /dev/null
+++ b/auto/fmt/xfmt
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END | sed -e 's/d"$/x"/' >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_fmt_name
+#define $ngx_fmt_name  "$ngx_fmt"
+#endif
+
+END
new file mode 100644
--- /dev/null
+++ b/auto/func
@@ -0,0 +1,43 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_func ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_func" >> $NGX_ERR
+
+ngx_found=no
+
+func=`echo $ngx_func | sed -e 's/()$//' | tr '[a-z]' '[A-Z]'`
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+$NGX_UNISTD_H
+$ngx_func_inc
+
+int main() {
+    $ngx_func_test;
+    return 0;
+}
+
+END
+
+test="$CC_WARN $CC_TEST_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c $ngx_func_libs"
+eval "$test >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    echo " found"
+    have=HAVE_$func . auto/have
+    ngx_found=yes
+
+else
+    echo " not found"
+    echo "---------" >> $NGX_ERR
+    cat $NGX_AUTOTEST.c >> $NGX_ERR
+    echo "---------" >> $NGX_ERR
+    echo $test >> $NGX_ERR
+    echo "---------" >> $NGX_ERR
+fi
+
+rm $NGX_AUTOTEST*
new file mode 100644
--- /dev/null
+++ b/auto/have
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  1
+#endif
+
+END
new file mode 100644
--- /dev/null
+++ b/auto/headers
@@ -0,0 +1,6 @@
+
+# Copyright (C) Igor Sysoev
+
+
+ngx_inc="unistd.h"; . auto/inc
+ngx_inc="inttypes.h"; . auto/inc
new file mode 100644
--- /dev/null
+++ b/auto/inc
@@ -0,0 +1,35 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_inc ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_inc" >> $NGX_ERR
+
+ngx_found=no
+
+inc=`echo $ngx_inc | sed -e 's/\./_/' | sed -e 's/\//_/' | tr '[a-z]' '[A-Z]'`
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <$ngx_inc>
+
+int main() {
+    return 0;
+}
+
+END
+
+eval "${CC} -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    echo " found"
+    have=HAVE_$inc . auto/have
+    eval "NGX_$inc='#include <$ngx_inc>'"
+    ngx_found=yes
+
+else
+    echo " not found"
+fi
+
+rm $NGX_AUTOTEST*
new file mode 100644
--- /dev/null
+++ b/auto/init
@@ -0,0 +1,38 @@
+
+# Copyright (C) Igor Sysoev
+
+
+MAKEFILE=$OBJS/Makefile
+
+NGX_AUTO_CONFIG_H=$OBJS/ngx_auto_config.h
+NGX_MODULES_C=$OBJS/ngx_modules.c
+
+NGX_AUTOTEST=$OBJS/autotest
+NGX_ERR=$OBJS/autoconf.err
+
+CC_WARN=$CC
+
+PCH=NO
+USEPCH=
+
+OBJEXT=
+BINEXT=
+DIRSEP='\/'
+MAKE_SL=NO
+
+
+# checking echo's "-n" option and "\c" capabilties
+
+if echo "test\c" | grep c >/dev/null; then
+    if echo -n test | grep n >/dev/null; then
+        ngx_n=
+        ngx_c=
+    else
+        ngx_n=-n
+        ngx_c=
+    fi
+
+else
+    ngx_n=
+    ngx_c='\c'
+fi
new file mode 100644
--- /dev/null
+++ b/auto/install
@@ -0,0 +1,65 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END                                                        >> $MAKEFILE
+install:
+	test -d $PREFIX || mkdir -p $PREFIX
+
+	test -d `dirname $SBIN_PATH` || mkdir -p `dirname $SBIN_PATH`
+	test ! -f $SBIN_PATH || mv $SBIN_PATH $SBIN_PATH.old
+	cp nginx $SBIN_PATH
+
+	test -d `dirname $CONF_PATH` || mkdir -p `dirname $CONF_PATH`
+
+	cp conf/koi-win `dirname $CONF_PATH`
+
+	test -f `dirname $CONF_PATH`/mime.types || \
+		cp conf/mime.types `dirname $CONF_PATH`/mime.types
+	cp conf/mime.types `dirname $CONF_PATH`/mime.types.default
+
+	test -f $CONF_PATH || cp conf/nginx.conf $CONF_PATH
+	cp conf/nginx.conf `dirname $CONF_PATH`/nginx.conf.default
+
+	test -d `dirname $PID_PATH` || mkdir -p `dirname $PID_PATH`
+
+	test -d `dirname $HTTP_LOG_PATH` || mkdir -p `dirname $HTTP_LOG_PATH`
+
+	test -d $PREFIX/html || cp -r html $PREFIX
+
+	#test -d $PREFIX/temp || mkdir -p $PREFIX/temp
+END
+
+
+if [ ".$ERROR_LOG_PATH" != "." ]; then
+    cat << END                                                    >> $MAKEFILE
+
+	test -d `dirname $ERROR_LOG_PATH` || mkdir -p `dirname $ERROR_LOG_PATH`
+END
+
+fi
+
+
+if test ! -f Makefile; then
+
+    cat << END > Makefile
+
+build:
+	\$(MAKE) -f $OBJS/Makefile
+
+install:
+	\$(MAKE) -f $OBJS/Makefile install
+
+clean:
+	rm -rf Makefile $OBJS
+
+upgrade:
+	$SBIN_PATH -t
+	kill -USR2 \`cat $PID_PATH\`
+	sleep 1
+	test -f $PID_PATH.newbin
+	kill -WINCH \`cat $PID_PATH\`
+
+END
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/conf
@@ -0,0 +1,19 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $USE_PCRE = YES ]; then
+    . auto/lib/pcre/conf
+fi
+
+if [ $USE_MD5 = YES ]; then
+    . auto/lib/md5/conf
+fi
+
+if [ $USE_OPENSSL = YES ]; then
+    . auto/lib/openssl/conf
+fi
+
+if [ $USE_ZLIB = YES ]; then
+    . auto/lib/zlib/conf
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/make
@@ -0,0 +1,19 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $PCRE != NONE -a $PCRE != NO -a $PCRE != YES ]; then
+    . auto/lib/pcre/make
+fi
+
+if [ $MD5 != NONE -a $MD5 != NO -a $MD5 != YES ]; then
+    . auto/lib/md5/make
+fi
+
+if [ $OPENSSL != NONE -a $OPENSSL != NO -a $OPENSSL != YES ]; then
+    . auto/lib/openssl/make
+fi
+
+if [ $ZLIB != NONE -a $ZLIB != NO -a $ZLIB != YES ]; then
+    . auto/lib/zlib/make
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/md5/conf
@@ -0,0 +1,105 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $MD5 != NONE ]; then
+
+    if grep MD5_Init $MD5/md5.h >/dev/null; then
+        # OpenSSL md5
+        OPENSSL_MD5=YES
+        have=HAVE_OPENSSL_MD5 . auto/have
+    else
+        # rsaref md5
+        OPENSSL_MD5=NO
+    fi
+
+    CORE_INCS="$CORE_INCS $MD5"
+
+    case "$CC" in
+
+        cl | wcl386 | bcc32)
+            LINK_DEPS="$LINK_DEPS $MD5/md5.lib"
+            CORE_LIBS="$CORE_LIBS $MD5/md5.lib"
+        ;;
+
+        *icc)
+            LINK_DEPS="$LINK_DEPS $MD5/libmd5.a"
+
+            # to allow -ipo optimization we link with the *.o but not library
+            CORE_LIBS="$CORE_LIBS $MD5/md5_dgst.o"
+
+            if [ $MD5_ASM = YES ]; then
+                CORE_LIBS="$CORE_LIBS $MD5/asm/mx86-elf.o"
+            fi
+        ;;
+
+        *)
+            LINK_DEPS="$LINK_DEPS $MD5/libmd5.a"
+            CORE_LIBS="$CORE_LIBS $MD5/libmd5.a"
+            #CORE_LIBS="$CORE_LIBS -L $MD5 -lmd5"
+        ;;
+
+    esac
+
+else
+
+    if [ $PLATFORM != win32 ]; then
+        MD5=NO
+        ngx_lib_cflags=
+
+        # Solaris 8/9
+
+        ngx_lib_inc="#include <sys/types.h>
+#include <md5.h>"
+        ngx_lib="rsaref md5 library"
+        ngx_lib_test="MD5_CTX md5; MD5Init(&md5)"
+        ngx_libs=-lmd5
+        . auto/lib/test
+
+
+        if [ $ngx_found = yes ]; then
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            MD5=YES
+            MD5_LIB=md5
+            ngx_found=no
+
+        else
+            # FreeBSD
+
+            ngx_lib="rsaref md library"
+            ngx_lib_test="MD5_CTX md5; MD5Init(&md5)"
+            ngx_libs=-lmd
+            . auto/lib/test
+        fi
+
+
+        if [ $ngx_found = yes ]; then
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            MD5=YES
+            MD5_LIB=md
+            ngx_found=no
+
+        else
+            if [ $MD5 = NO ]; then
+
+               # OpenSSL crypto library
+
+               ngx_lib_inc="#include <openssl/md5.h>"
+               ngx_lib="OpenSSL md5 crypto library"
+               ngx_lib_test="MD5_CTX md5; MD5_Init(&md5)"
+               ngx_libs=-lcrypto
+               . auto/lib/test
+            fi
+        fi
+
+
+        if [ $ngx_found = yes ]; then
+            have=HAVE_OPENSSL_MD5 . auto/have
+            have=HAVE_OPENSSL_MD5_H . auto/have
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            MD5=YES
+            MD5_LIB=crypto
+        fi
+    fi
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/md5/make
@@ -0,0 +1,98 @@
+
+# Copyright (C) Igor Sysoev
+
+
+case "$CC" in
+
+    cl)
+        makefile=makefile.msvc
+        opt="CPU_OPT=$CPU_OPT LIBC=$LIBC MD5_ASM=$MD5_ASM"
+    ;;
+
+    wcl386)
+        makefile=makefile.owc
+        opt="CPU_OPT=$CPU_OPT"
+    ;;
+
+    bcc32)
+        makefile=makefile.bcc
+        opt="-DCPU_OPT=$CPU_OPT -DMD5_ASM=$MD5_ASM"
+    ;;
+
+esac
+
+
+case $PLATFORM in
+
+    win32)
+        line=`echo $MD5/md5.lib: | sed -e "s/\//$DIRSEP/g"`
+        echo "$line"                                              >> $MAKEFILE
+    ;;
+
+    *)
+        echo "$MD5/libmd5.a:"                                     >> $MAKEFILE
+    ;;
+
+esac
+
+
+done=NO
+
+
+case $PLATFORM in
+
+    win32)
+        md5=`echo $MD5 | sed -e "s/\//$DIRSEP/g"`
+
+        cp auto/lib/md5/$makefile $MD5
+        echo "	cd $md5"                                          >> $MAKEFILE
+        echo "	\$(MAKE) -f $makefile $opt"                       >> $MAKEFILE
+        echo "	cd ..\\..\\.."                                    >> $MAKEFILE
+
+        done=YES
+    ;;
+
+    SunOS:*:i86pc)
+        if [ $MD5_ASM = YES ]; then
+
+            MD5_OPT="CFLAGS=\"$MD5_OPT -DSOL -DMD5_ASM -DL_ENDIAN\""
+
+            echo "	cd $MD5 \\"                               >> $MAKEFILE
+            echo "	&& \$(MAKE) $MD5_OPT \\"                  >> $MAKEFILE
+            echo "		MD5_ASM_OBJ=asm/mx86-sol.o \\"    >> $MAKEFILE
+            echo "		CC=\"\$(CC)\" CPP=\"\$(CPP)\" \\" >> $MAKEFILE
+            echo "		libmd5.a"                         >> $MAKEFILE
+
+            done=YES
+        fi
+    ;;
+
+    # FreeBSD: i386
+    # Linux: i686
+
+    *:i386 | *:i686)
+        if [ $MD5_ASM = YES ]; then
+
+            MD5_OPT="CFLAGS=\"$MD5_OPT -DELF -DMD5_ASM -DL_ENDIAN\""
+
+            echo "	cd $MD5 \\"                               >> $MAKEFILE
+            echo "	&& \$(MAKE) $MD5_OPT \\"                  >> $MAKEFILE
+            echo "		MD5_ASM_OBJ=asm/mx86-elf.o \\"    >> $MAKEFILE
+            echo "		CC=\"\$(CC)\" CPP=\"\$(CPP)\" \\" >> $MAKEFILE
+            echo "		libmd5.a"                         >> $MAKEFILE
+
+            done=YES
+        fi
+    ;;
+
+esac
+
+
+if [ $done = NO ]; then
+    echo "	cd $MD5 \\"                                       >> $MAKEFILE
+    echo "	&& \$(MAKE) CFLAGS=\"$MD5_OPT\" \\"               >> $MAKEFILE
+    echo "		MD5_ASM_OBJ= CC=\"\$(CC)\" libmd5.a"      >> $MAKEFILE
+fi
+
+
+echo                                                              >> $MAKEFILE
new file mode 100644
--- /dev/null
+++ b/auto/lib/md5/makefile.bcc
@@ -0,0 +1,19 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -q -O2 -tWM $(CPU_OPT) -DL_ENDIAN
+
+!if "$(MD5_ASM)" == "YES"
+
+md5.lib:
+	bcc32 -c $(CFLAGS) -DMD5_ASM md5_dgst.c
+	tlib md5.lib +md5_dgst.obj +asm/m-win32.obj
+
+!else
+
+md5.lib:
+	bcc32 -c $(CFLAGS) md5_dgst.c
+	tlib md5.lib +md5_dgst.obj
+
+!endif
new file mode 100644
--- /dev/null
+++ b/auto/lib/md5/makefile.msvc
@@ -0,0 +1,19 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -nologo -MT -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT) -D L_ENDIAN
+
+!if "$(MD5_ASM)" == "YES"
+
+md5.lib:
+	cl -c $(CFLAGS) -D MD5_ASM md5_dgst.c
+	link -lib -out:md5.lib md5_dgst.obj asm/m-win32.obj
+
+!else
+
+md5.lib:
+	cl -c $(CFLAGS) md5_dgst.c
+	link -lib -out:md5.lib md5_dgst.obj
+
+!endif
new file mode 100644
--- /dev/null
+++ b/auto/lib/md5/makefile.owc
@@ -0,0 +1,9 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -zq -bt=nt -bm -ot -op -oi -oe -s $(CPU_OPT)
+
+md5.lib:
+	wcl386 -c $(CFLAGS) -dL_ENDIAN md5_dgst.c
+	wlib -n md5.lib md5_dgst.obj
new file mode 100644
--- /dev/null
+++ b/auto/lib/openssl/conf
@@ -0,0 +1,43 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $OPENSSL != NONE ]; then
+    CORE_INCS="$CORE_INCS $OPENSSL/include"
+    CORE_DEPS="$CORE_DEPS $OPENSSL_DEPS"
+    CORE_SRCS="$CORE_SRCS $OPENSSL_SRCS"
+
+    case "$CC" in
+        *)
+            have=NGX_OPENSSL . auto/have
+            LINK_DEPS="$LINK_DEPS $OPENSSL/libssl.a $OPENSSL/libcrypto.a"
+            CORE_LIBS="$CORE_LIBS $OPENSSL/libssl.a $OPENSSL/libcrypto.a"
+        ;;
+
+    esac
+
+else
+
+    if [ $PLATFORM != win32 ]; then
+            OPENSSL=NO
+            ngx_lib_cflags=
+
+            ngx_lib_inc="#include <openssl/ssl.h>"
+            ngx_lib="OpenSSL library"
+            ngx_lib_test="SSL_library_init()"
+            ngx_libs="-lssl -lcrypto"
+            . auto/lib/test
+
+
+        if [ $ngx_found = yes ]; then
+            have=NGX_OPENSSL . auto/have
+            CORE_DEPS="$CORE_DEPS $OPENSSL_DEPS"
+            CORE_SRCS="$CORE_SRCS $OPENSSL_SRCS"
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            OPENSSL=YES
+            ngx_found=no
+        fi
+
+    fi
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/openssl/make
@@ -0,0 +1,16 @@
+
+# Copyright (C) Igor Sysoev
+
+
+case $PLATFORM in
+    *)
+        echo "$OPENSSL/libssl.a:"                                 >> $MAKEFILE
+        echo "	cd $OPENSSL \\"                                   >> $MAKEFILE
+        echo "	&& CC=\"\$(CC)\" \\"                              >> $MAKEFILE
+        echo "	./config threads no-shared \\"                    >> $MAKEFILE
+        echo "	&& \$(MAKE)"                                      >> $MAKEFILE
+    ;;
+
+esac
+
+echo                                                              >> $MAKEFILE
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/conf
@@ -0,0 +1,86 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $PCRE != NONE ]; then
+    CORE_INCS="$CORE_INCS $PCRE"
+    CORE_DEPS="$CORE_DEPS $REGEX_DEPS"
+    CORE_SRCS="$CORE_SRCS $REGEX_SRCS"
+
+    case "$CC" in
+
+        cl | wcl386 | bcc32)
+            have=HAVE_PCRE . auto/have
+            have=PCRE_STATIC . auto/have
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+            LINK_DEPS="$LINK_DEPS $PCRE/pcre.lib"
+            CORE_LIBS="$CORE_LIBS $PCRE/pcre.lib"
+        ;;
+
+        *icc)
+            have=HAVE_PCRE . auto/have
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+
+            LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a"
+
+            # to allow -ipo optimization we link with the *.o but not library
+            CORE_LIBS="$CORE_LIBS $PCRE/maketables.o"
+            CORE_LIBS="$CORE_LIBS $PCRE/get.o"
+            CORE_LIBS="$CORE_LIBS $PCRE/study.o"
+            CORE_LIBS="$CORE_LIBS $PCRE/pcre.o"
+        ;;
+
+        *)
+            have=HAVE_PCRE . auto/have
+            CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
+            LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a"
+            CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a"
+            #CORE_LIBS="$CORE_LIBS -L $PCRE/.libs -lpcre"
+        ;;
+
+    esac
+
+else
+
+    if [ $PLATFORM != win32 ]; then
+            PCRE=NO
+            ngx_lib_cflags=
+
+            # Linux
+
+            ngx_lib_inc="#include <pcre.h>"
+            ngx_lib="PCRE library"
+            ngx_lib_test="pcre *re; re = pcre_compile(NULL, 0, NULL, 0, NULL)"
+            ngx_libs="-lpcre"
+            . auto/lib/test
+
+
+        if [ $ngx_found = yes ]; then
+            have=HAVE_PCRE . auto/have
+            CORE_DEPS="$CORE_DEPS $REGEX_DEPS"
+            CORE_SRCS="$CORE_SRCS $REGEX_SRCS"
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            PCRE=YES
+            ngx_found=no
+
+        else
+            # FreeBSD PCRE port.
+
+            ngx_lib="PCRE library in /usr/local/"
+            ngx_lib_cflags="-I /usr/local/include"
+            ngx_libs="-L /usr/local/lib -lpcre"
+            . auto/lib/test
+        fi
+
+
+        if [ $ngx_found = yes ]; then
+            have=HAVE_PCRE . auto/have
+            CORE_DEPS="$CORE_DEPS $REGEX_DEPS"
+            CORE_INCS="$CORE_INCS /usr/local/include"
+            CORE_SRCS="$CORE_SRCS $REGEX_SRCS"
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            PCRE=YES
+        fi
+    fi
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/make
@@ -0,0 +1,65 @@
+
+# Copyright (C) Igor Sysoev
+
+
+case "$CC" in
+
+    cl)
+        makefile=makefile.msvc
+        opt="CPU_OPT=$CPU_OPT LIBC=$LIBC"
+    ;;
+
+    wcl386)
+        makefile=makefile.owc
+        opt="CPU_OPT=$CPU_OPT"
+    ;;
+
+    bcc32)
+        makefile=makefile.bcc
+        opt="-DCPU_OPT=$CPU_OPT"
+    ;;
+
+esac
+
+
+case $PLATFORM in
+
+    win32)
+        cp auto/lib/pcre/patch.pcre.in $PCRE
+        cp auto/lib/pcre/patch.config.in $PCRE
+        cp auto/lib/pcre/$makefile $PCRE
+
+        pcre=`echo $PCRE | sed -e "s/\//$DIRSEP/g"`
+        line=`echo $PCRE/pcre.h: | sed -e "s/\//$DIRSEP/g"`
+
+        echo "$line"                                              >> $MAKEFILE
+        echo "	cd $pcre"                                         >> $MAKEFILE
+        echo "	\$(MAKE) -f $makefile pcre.h"                     >> $MAKEFILE
+        echo "	cd ..\\..\\.."                                    >> $MAKEFILE
+        echo                                                      >> $MAKEFILE
+
+        line="$PCRE/pcre.lib:	$PCRE/pcre.h"
+        line=`echo $line | sed -e "s/\//$DIRSEP/g"`
+
+        echo "$line"                                              >> $MAKEFILE
+        echo "	cd $pcre"                                         >> $MAKEFILE
+        echo "	\$(MAKE) -f $makefile $opt"                       >> $MAKEFILE
+        echo "	cd ..\\..\\.."                                    >> $MAKEFILE
+    ;;
+
+    *)
+        PCRE_OPT="CFLAGS=\"$PCRE_OPT\""
+
+        echo "$PCRE/pcre.h:"                                      >> $MAKEFILE
+        echo "	cd $PCRE \\"                                      >> $MAKEFILE
+        echo "	&& CC=\"\$(CC)\" $PCRE_OPT \\"                    >> $MAKEFILE
+        echo "	./configure --disable-shared"                     >> $MAKEFILE
+        echo                                                      >> $MAKEFILE
+        echo "$PCRE/.libs/libpcre.a:	$PCRE/pcre.h"             >> $MAKEFILE
+        echo "	cd $PCRE \\"                                      >> $MAKEFILE
+        echo "	&& \$(MAKE) libpcre.la"                           >> $MAKEFILE
+    ;;
+
+esac
+
+echo                                                              >> $MAKEFILE
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/makefile.bcc
@@ -0,0 +1,20 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS =	-q -O2 -tWM $(CPU_OPT)
+PCREFLAGS =	-DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10
+
+
+pcre.lib:	pcre.h
+	bcc32 -q -edftables dftables.c
+
+	dftables > chartables.c
+
+	bcc32 -c $(CFLAGS) $(PCREFLAGS) maketables.c get.c study.c pcre.c
+
+	tlib pcre.lib +maketables.obj +get.obj +study.obj +pcre.obj
+
+pcre.h:
+	patch -o pcre.h pcre.in patch.pcre.in
+	patch -o config.h config.in patch.config.in
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/makefile.msvc
@@ -0,0 +1,22 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS =	-O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT)
+PCREFLAGS =	-DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10
+
+
+pcre.lib:	pcre.h
+	cl -Fedftables dftables.c
+
+	dftables > chartables.c
+
+	cl -nologo -c $(CFLAGS) $(PCREFLAGS)				\
+		maketables.c get.c study.c pcre.c
+
+	link -lib -out:pcre.lib -verbose:lib				\
+		maketables.obj get.obj study.obj pcre.obj
+
+pcre.h:
+	patch -o pcre.h pcre.in patch.pcre.in
+	patch -o config.h config.in patch.config.in
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/makefile.owc
@@ -0,0 +1,19 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS =	-c -zq -bt=nt -ot -op -oi -oe -s -bm $(CPU_OPT)
+PCREFLAGS =	-DPCRE_STATIC -DPOSIX_MALLOC_THRESHOLD=10
+
+
+pcre.lib:	pcre.h
+	wcl386 -zq -bt=nt -l=nt -fe=dftables dftables.c
+	dftables > chartables.c
+
+	wcl386 $(CFLAGS) $(PCREFLAGS) maketables.c get.c study.c pcre.c
+	wlib -n pcre.lib maketables.obj get.obj study.obj pcre.obj
+
+
+pcre.h:
+	patch -o pcre.h pcre.in patch.pcre.in
+	patch -o config.h config.in patch.config.in
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/patch.config.in
@@ -0,0 +1,11 @@
+--- config.in	Thu Aug 21 14:43:07 2003
++++ config.in	Sun Mar  7 02:37:24 2004
+@@ -28,7 +28,7 @@
+ found. */
+ 
+ #define HAVE_STRERROR 0
+-#define HAVE_MEMMOVE  0
++#define HAVE_MEMMOVE 1
+ 
+ /* There are some non-Unix systems that don't even have bcopy(). If this macro
+ is false, an emulation is used. If HAVE_MEMMOVE is set to 1, the value of
new file mode 100644
--- /dev/null
+++ b/auto/lib/pcre/patch.pcre.in
@@ -0,0 +1,15 @@
+--- pcre.in	Thu Aug 21 14:43:07 2003
++++ pcre.in	Sun Mar  7 02:10:11 2004
+@@ -10,9 +10,9 @@
+ /* The file pcre.h is build by "configure". Do not edit it; instead
+ make changes to pcre.in. */
+ 
+-#define PCRE_MAJOR          @PCRE_MAJOR@
+-#define PCRE_MINOR          @PCRE_MINOR@
+-#define PCRE_DATE           @PCRE_DATE@
++#define PCRE_MAJOR          4
++#define PCRE_MINOR          4
++#define PCRE_DATE           21-August-2003
+ 
+ /* Win32 uses DLL by default */
+ 
new file mode 100644
--- /dev/null
+++ b/auto/lib/test
@@ -0,0 +1,27 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_lib ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_lib library" >> $NGX_ERR
+
+ngx_found=no
+
+echo "$ngx_lib_inc" > $NGX_AUTOTEST.c
+echo "int main() { $ngx_lib_test; return 0; }" >> $NGX_AUTOTEST.c
+
+eval "$CC $cc_test_flags $ngx_lib_cflags \
+     -o $NGX_AUTOTEST $NGX_AUTOTEST.c $ngx_libs \
+     >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    echo " found"
+
+    ngx_found=yes
+
+else
+    echo " not found"
+fi
+
+rm $NGX_AUTOTEST*
new file mode 100644
--- /dev/null
+++ b/auto/lib/zlib/conf
@@ -0,0 +1,60 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $ZLIB != NONE ]; then
+    CORE_INCS="$CORE_INCS $ZLIB"
+
+    case "$CC" in
+
+        cl | wcl386 | bcc32)
+            LINK_DEPS="$LINK_DEPS $ZLIB/zlib.lib"
+            CORE_LIBS="$CORE_LIBS $ZLIB/zlib.lib"
+        ;;
+
+        *icc)
+            LINK_DEPS="$LINK_DEPS $ZLIB/libz.a"
+
+            # to allow -ipo optimization we link with the *.o but not library
+            CORE_LIBS="$CORE_LIBS $ZLIB/adler32.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/crc32.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/deflate.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/trees.o"
+            CORE_LIBS="$CORE_LIBS $ZLIB/zutil.o"
+
+            if [ $ZLIB_ASM != NO ]; then
+                CORE_LIBS="$CORE_LIBS $ZLIB/match.o"
+            fi
+        ;;
+
+        *)
+            LINK_DEPS="$LINK_DEPS $ZLIB/libz.a"
+            CORE_LIBS="$CORE_LIBS $ZLIB/libz.a"
+            #CORE_LIBS="$CORE_LIBS -L $ZLIB -lz"
+        ;;
+
+    esac
+
+else
+
+    if [ $PLATFORM != win32 ]; then
+
+        # FreeBSD, Solaris, Linux
+
+        ngx_lib_cflags=
+        ngx_lib_inc="#include <zlib.h>"
+        ngx_lib="zlib library"
+        ngx_lib_test="z_stream z; deflate(&z, Z_NO_FLUSH)"
+        ngx_libs=-lz
+        . auto/lib/test
+
+
+        if [ $ngx_found = yes ]; then
+            CORE_LIBS="$CORE_LIBS $ngx_libs"
+            ZLIB=YES
+        else
+            ZLIB=NO
+        fi
+    fi
+
+fi
new file mode 100644
--- /dev/null
+++ b/auto/lib/zlib/make
@@ -0,0 +1,107 @@
+
+# Copyright (C) Igor Sysoev
+
+
+case "$CC" in
+
+    cl)
+        makefile=makefile.msvc
+        opt="CPU_OPT=$CPU_OPT LIBC=$LIBC"
+
+    ;;
+
+    wcl386)
+        makefile=makefile.owc
+        opt="CPU_OPT=$CPU_OPT"
+    ;;
+
+    bcc32)
+        makefile=makefile.bcc
+        opt="-DCPU_OPT=$CPU_OPT"
+    ;;
+
+esac
+
+
+case $PLATFORM in
+
+    win32)
+        line=`echo $ZLIB/zlib.lib: | sed -e "s/\//$DIRSEP/g"`
+        echo "$line"                                              >> $MAKEFILE
+    ;;
+
+    *)
+        echo "$ZLIB/libz.a:"                                      >> $MAKEFILE
+    ;;
+
+esac
+
+
+done=NO
+
+
+case $PLATFORM in
+
+    win32)
+        zlib=`echo $ZLIB | sed -e "s/\//$DIRSEP/g"`
+
+        cp auto/lib/zlib/$makefile $ZLIB
+        echo "	cd $zlib"                                         >> $MAKEFILE
+        echo "	\$(MAKE) -f $makefile $opt"                       >> $MAKEFILE
+        echo "	cd ..\\..\\.."                                    >> $MAKEFILE
+
+        done=YES
+    ;;
+
+    # FreeBSD: i386
+    # Linux: i686
+
+    *:i386 | *:i686)
+        case $ZLIB_ASM in
+            pentium)
+                echo "	cd $ZLIB \\"                              >> $MAKEFILE
+                echo "	&& cp contrib/asm586/match.S . \\"        >> $MAKEFILE
+                echo "	&& CFLAGS=\"$ZLIB_OPT -DASMV\" \\"        >> $MAKEFILE
+                echo "		CC=\"\$(CC)\" \\"                 >> $MAKEFILE
+                echo "		./configure \\"                   >> $MAKEFILE
+                echo "	&& \$(MAKE) OBJA=match.o libz.a"          >> $MAKEFILE
+
+                done=YES
+            ;;
+
+            pentiumpro)
+                echo "	cd $ZLIB \\"                              >> $MAKEFILE
+                echo "	&& cp contrib/asm686/match.S . \\"        >> $MAKEFILE
+                echo "	&& CFLAGS=\"$ZLIB_OPT -DASMV\" \\"        >> $MAKEFILE
+                echo "		CC=\"\$(CC)\" \\"                 >> $MAKEFILE
+                echo "		./configure \\"                   >> $MAKEFILE
+                echo "	&& \$(MAKE) OBJA=match.o libz.a"          >> $MAKEFILE
+
+                done=YES
+            ;;
+
+            NO)
+            ;;
+
+            *)
+                echo "$0: error: invalid --with-zlib-asm=$ZLIB_ASM option."
+                echo "The valid values are \"pentium\" and \"pentiumpro\" only".
+                echo
+
+                exit 1;
+            ;;
+        esac
+    ;;
+
+esac
+
+
+if [ $done = NO ]; then
+    echo "	cd $ZLIB \\"                                      >> $MAKEFILE
+    echo "	&& CFLAGS=\"$ZLIB_OPT\" CC=\"\$(CC)\" \\"         >> $MAKEFILE
+    echo "		./configure \\"                           >> $MAKEFILE
+    echo "	&& \$(MAKE) libz.a"                               >> $MAKEFILE
+fi
+
+
+echo                                                              >> $MAKEFILE
new file mode 100644
--- /dev/null
+++ b/auto/lib/zlib/makefile.bcc
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -q -O2 -tWM $(CPU_OPT)
+
+zlib.lib:
+	bcc32 -c $(CFLAGS) adler32.c crc32.c deflate.c trees.c zutil.c
+
+	tlib zlib.lib +adler32.obj +crc32.obj +deflate.obj \
+		+trees.obj +zutil.obj
new file mode 100644
--- /dev/null
+++ b/auto/lib/zlib/makefile.msvc
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -nologo -O2 -Ob1 -Oi -Gs $(LIBC) $(CPU_OPT)
+
+zlib.lib:
+	cl -c $(CFLAGS) adler32.c crc32.c deflate.c trees.c zutil.c
+
+	link -lib -out:zlib.lib adler32.obj crc32.obj deflate.obj \
+		trees.obj zutil.obj
new file mode 100644
--- /dev/null
+++ b/auto/lib/zlib/makefile.owc
@@ -0,0 +1,9 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CFLAGS = -zq -bt=nt -ot -op -oi -oe -s -bm $(CPU_OPT)
+
+zlib.lib:
+	wcl386 -c $(CFLAGS) adler32.c crc32.c deflate.c trees.c zutil.c
+	wlib -n zlib.lib adler32.obj crc32.obj deflate.obj trees.obj zutil.obj
new file mode 100644
--- /dev/null
+++ b/auto/make
@@ -0,0 +1,360 @@
+
+# Copyright (C) Igor Sysoev
+
+
+mkdir -p $OBJS/src/core $OBJS/src/event $OBJS/src/event/modules \
+         $OBJS/src/os/unix $OBJS/src/os/win32 \
+         $OBJS/src/http $OBJS/src/http/modules $OBJS/src/http/modules/proxy \
+         $OBJS/src/imap
+
+
+echo "CC = $CC"                                                   > $MAKEFILE
+echo "CPP = $CPP"                                                 >> $MAKEFILE
+echo "LINK = $LINK"                                               >> $MAKEFILE
+if [ "$CC" = wcl386 ]; then
+    echo MAKE = wmake                                             >> $MAKEFILE
+fi
+echo "CFLAGS = $CFLAGS"                                           >> $MAKEFILE
+echo                                                              >> $MAKEFILE
+
+
+if [ $MAKE_SL = YES ]; then
+    echo                                                          >> $MAKEFILE
+fi
+
+all_inc="$CORE_INCS $OBJS $HTTP_INCS $IMAP_INCS"
+all_inc=`echo " $all_inc" | sed -e "s/ \([^ ]\)/ $INCOPT\1/g"`
+all_inc=`echo $all_inc | sed -e "s/\//$DIRSEP/g"`
+
+echo "ALL_INCS = $all_inc"                                        >> $MAKEFILE
+echo                                                              >> $MAKEFILE
+
+all_srcs="$CORE_SRCS"
+
+
+# CORE_DEPS
+
+if [ $MAKE_SL = YES ]; then
+    echo $ngx_n "CORE_DEPS =" $ngx_c                              >> $MAKEFILE
+else
+    echo "CORE_DEPS = \\"                                         >> $MAKEFILE
+fi
+
+for dep in $CORE_DEPS
+do
+    dep=`echo $dep | sed -e "s/\//$DIRSEP/g"`
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n " $dep" $ngx_c                                >> $MAKEFILE
+    else
+        echo "	$dep \\"                                          >> $MAKEFILE
+    fi
+done
+echo                                                              >> $MAKEFILE
+
+
+# CORE_INCS
+
+if [ $MAKE_SL = YES ]; then
+    echo                                                          >> $MAKEFILE
+fi
+
+inc="$CORE_INCS $OBJS"
+inc=`echo " $inc" | sed -e "s/ \([^ ]\)/ $INCOPT\1/g" -e "s/\//$DIRSEP/g"`
+
+echo "CORE_INCS = $inc"                                           >> $MAKEFILE
+echo                                                              >> $MAKEFILE
+
+
+if [ $HTTP = YES ]; then
+
+    all_srcs="$all_srcs $HTTP_SRCS"
+
+    # HTTP_DEPS
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n "HTTP_DEPS =" $ngx_c                          >> $MAKEFILE
+    else
+        echo "HTTP_DEPS = \\"                                     >> $MAKEFILE
+    fi
+
+    for dep in $HTTP_DEPS
+    do
+        dep=`echo $dep | sed -e "s/\//$DIRSEP/g"`
+
+        if [ $MAKE_SL = YES ]; then
+            echo $ngx_n " $dep" $ngx_c                            >> $MAKEFILE
+        else
+            echo "	$dep \\"                                  >> $MAKEFILE
+        fi
+    done
+    echo                                                          >> $MAKEFILE
+
+
+    # HTTP_INCS
+
+    if [ $MAKE_SL = YES ]; then
+        echo                                                      >> $MAKEFILE
+    fi
+
+    inc="$HTTP_INCS $OBJS"
+    inc=`echo " $inc" | sed -e "s/ \([^ ]\)/ $INCOPT\1/g" -e "s/\//$DIRSEP/g"`
+
+    echo "HTTP_INCS = $inc"                                       >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+
+fi
+
+
+if [ $IMAP = YES ]; then
+
+    all_srcs="$all_srcs $IMAP_SRCS"
+
+    # IMAP_DEPS
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n "IMAP_DEPS =" $ngx_c                          >> $MAKEFILE
+    else
+        echo "IMAP_DEPS = \\"                                     >> $MAKEFILE
+    fi
+
+    for dep in $IMAP_DEPS
+    do
+        dep=`echo $dep | sed -e "s/\//$DIRSEP/g"`
+
+        if [ $MAKE_SL = YES ]; then
+            echo $ngx_n " $dep" $ngx_c                            >> $MAKEFILE
+        else
+            echo "	$dep \\"                                  >> $MAKEFILE
+        fi
+    done
+    echo                                                          >> $MAKEFILE
+
+
+    # IMAP_INCS
+
+    if [ $MAKE_SL = YES ]; then
+        echo                                                      >> $MAKEFILE
+    fi
+
+    inc="$IMAP_INCS $OBJS"
+    inc=`echo " $inc" | sed -e "s/ \([^ ]\)/ $INCOPT\1/g" -e "s/\//$DIRSEP/g"`
+
+    echo "IMAP_INCS = $inc"                                       >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+
+fi
+
+
+# nginx
+
+if [ $MAKE_SL = YES ]; then
+    echo $ngx_n "nginx$BINEXT:	" $ngx_c                          >> $MAKEFILE
+else
+    echo "nginx$BINEXT: \\"                                       >> $MAKEFILE
+fi
+
+
+# nginx deps
+
+for src in $all_srcs
+do
+    obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\.S\$/.$OBJEXT/"`
+    obj=`echo $OBJS/$obj | sed -e "s/\//$DIRSEP/g"`
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n " $obj" $ngx_c                                >> $MAKEFILE
+    else
+        echo "	$obj \\"                                          >> $MAKEFILE
+    fi
+done
+
+for src in $NGX_MODULES_C $LINK_DEPS
+do
+    obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/"`
+    obj=`echo $obj | sed -e "s/\//$DIRSEP/g"`
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n " $obj" $ngx_c                                >> $MAKEFILE
+    else
+        echo "	$obj \\"                                          >> $MAKEFILE
+    fi
+done
+echo                                                              >> $MAKEFILE
+
+
+# nginx build
+
+if [ $MAKE_SL = YES ]; then
+    echo $ngx_n "	\$(LINK) ${BINOUT}nginx" $ngx_c           >> $MAKEFILE
+else
+    echo "	\$(LINK) ${BINOUT}nginx \\"                       >> $MAKEFILE
+fi
+
+
+# nginx build sources
+
+for src in $all_srcs
+do
+    obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\.S\$/.$OBJEXT/"`
+    obj=`echo $OBJS/$obj | sed -e "s/\//$DIRSEP/g"`
+
+    if [ $MAKE_SL = YES ]; then
+        echo $ngx_n " $obj" $ngx_c                                >> $MAKEFILE
+    else
+        echo "	$obj \\"                                          >> $MAKEFILE
+    fi
+done
+
+
+# nginx build ngx_modules.c and libs
+
+obj=`echo $NGX_MODULES_C | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\//$DIRSEP/g"`
+src=`echo $NGX_MODULES_C | sed -e "s/\//$DIRSEP/g"`
+libs=`echo $CORE_LIBS | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\//$DIRSEP/g"`
+link=`echo $CORE_LINK | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\//$DIRSEP/g"`
+
+if [ $MAKE_SL = YES ]; then
+    echo " $obj $libs $CORE_LINK"                                 >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+else
+    echo "	$obj \\"                                          >> $MAKEFILE
+    echo "	$libs \\"                                         >> $MAKEFILE
+    echo "	$link"                                            >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+fi
+
+
+# ngx_modules.c
+
+deps="\$(CORE_DEPS)"
+
+if [ $PCH != NO ]; then
+    args="\$(CFLAGS) $USEPCH \$(ALL_INCS)"
+else
+    args="\$(CFLAGS) $USEPCH \$(CORE_INCS)"
+fi
+
+if [ $MAKE_SL = YES ]; then
+    echo "$obj: $NGX_MODULES_C $deps"                             >> $MAKEFILE
+    echo $ngx_n "	\$(CC) $COMPOPT $args" $ngx_c             >> $MAKEFILE
+    echo " $OBJOUT$obj $src"                                      >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+else
+    echo "$obj: \\"                                               >> $MAKEFILE
+    echo "	$NGX_MODULES_C $deps"                             >> $MAKEFILE
+    echo "	\$(CC) $COMPOPT $args \\"                         >> $MAKEFILE
+    echo "		$OBJOUT$obj \\"                           >> $MAKEFILE
+    echo "		$src"                                     >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+fi
+
+
+# core sources
+
+for src in $CORE_SRCS
+do
+    obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/" -e "s/\.S\$/.$OBJEXT/"`
+    obj=`echo $OBJS/$obj | sed -e "s/\//$DIRSEP/g"`
+    src=`echo $src | sed -e "s/\//$DIRSEP/g"`
+
+    if [ $MAKE_SL = YES ]; then
+        echo "$obj: $src $deps"                                   >> $MAKEFILE
+        echo "	\$(CC) $COMPOPT $args $OBJOUT$obj $src"           >> $MAKEFILE
+        echo                                                      >> $MAKEFILE
+    else
+        echo "$obj: \\"                                           >> $MAKEFILE
+        echo "	$src $deps"                                       >> $MAKEFILE
+        echo "	\$(CC) $COMPOPT $args \\"                         >> $MAKEFILE
+        echo "		$OBJOUT$obj \\"                           >> $MAKEFILE
+        echo "		$src"                                     >> $MAKEFILE
+        echo                                                      >> $MAKEFILE
+    fi
+done
+
+
+# http sources
+
+if [ $HTTP = YES ]; then
+
+    deps="\$(CORE_DEPS) \$(HTTP_DEPS)"
+
+    if [ $PCH != NO ]; then
+        args="\$(CFLAGS) $USEPCH \$(ALL_INCS)"
+    else
+        args="\$(CFLAGS) $USEPCH \$(CORE_INCS) \$(HTTP_INCS)"
+    fi
+
+    for src in $HTTP_SRCS
+    do
+        obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/"`
+        obj=`echo $OBJS/$obj | sed -e "s/\//$DIRSEP/g"`
+        src=`echo $src | sed -e "s/\//$DIRSEP/g"`
+
+        if [ $MAKE_SL = YES ]; then
+            echo "$obj: $src $deps"                               >> $MAKEFILE
+            echo "	\$(CC) $COMPOPT $args $OBJOUT$obj $src"   >> $MAKEFILE
+            echo                                                  >> $MAKEFILE
+        else
+            echo "$obj: \\"                                       >> $MAKEFILE
+            echo "	$src $deps"                               >> $MAKEFILE
+            echo "	\$(CC) $COMPOPT $args \\"                 >> $MAKEFILE
+            echo "		$OBJOUT$obj \\"                   >> $MAKEFILE
+            echo "		$src"                             >> $MAKEFILE
+            echo                                                  >> $MAKEFILE
+        fi
+    done
+
+fi
+
+
+# imap sources
+
+if [ $IMAP = YES ]; then
+
+    deps="\$(CORE_DEPS) \$(IMAP_DEPS)"
+
+    if [ $PCH != NO ]; then
+        args="\$(CFLAGS) $USEPCH \$(ALL_INCS)"
+    else
+        args="\$(CFLAGS) $USEPCH \$(CORE_INCS) \$(IMAP_INCS)"
+    fi
+
+    for src in $IMAP_SRCS
+    do
+        obj=`echo $src | sed -e "s/\.c\$/.$OBJEXT/"`
+        obj=`echo $OBJS/$obj | sed -e "s/\//$DIRSEP/g"`
+        src=`echo $src | sed -e "s/\//$DIRSEP/g"`
+
+        if [ $MAKE_SL = YES ]; then
+            echo "$obj: $src $deps"                               >> $MAKEFILE
+            echo "	\$(CC) $COMPOPT $args $OBJOUT$obj $src"   >> $MAKEFILE
+            echo                                                  >> $MAKEFILE
+        else
+            echo "$obj: \\"                                       >> $MAKEFILE
+            echo "	$src $deps"                               >> $MAKEFILE
+            echo "	\$(CC) $COMPOPT $args \\"                 >> $MAKEFILE
+            echo "		$OBJOUT$obj \\"                   >> $MAKEFILE
+            echo "		$src"                             >> $MAKEFILE
+            echo                                                  >> $MAKEFILE
+        fi
+    done
+
+fi
+
+
+# precompiled headers
+
+if [ $PCH != NO ]; then
+    echo "#include <ngx_config.h>" > $OBJS/pch.c
+
+    pch="$PCH:	src/core/ngx_config.h $OS_CONFIG $OBJS/ngx_auto_config.h"
+    pch=`echo $pch | sed -e "s/\//$DIRSEP/g"`
+    src="\$(CC) \$(CFLAGS) $BUILDPCH $COMPOPT \$(ALL_INCS)"
+    src="$src $OBJOUT$OBJS/pch.obj $OBJS/pch.c"
+    src=`echo $src | sed -e "s/\//$DIRSEP/g"`
+
+    echo "$pch"                                                   >> $MAKEFILE
+    echo "	$src"                                             >> $MAKEFILE
+    echo                                                          >> $MAKEFILE
+fi
new file mode 100644
--- /dev/null
+++ b/auto/modules
@@ -0,0 +1,167 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ $EVENT_SELECT = NO -a $EVENT_FOUND = NO ]; then
+    EVENT_SELECT=YES
+fi
+
+if [ $EVENT_SELECT = YES ]; then
+    CORE_SRCS="$CORE_SRCS $SELECT_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $SELECT_MODULE"
+fi
+
+
+if [ $EVENT_POLL = NO -a $EVENT_FOUND = NO ]; then
+    EVENT_POLL=YES
+fi
+
+if [ $EVENT_POLL = YES ]; then
+    CORE_SRCS="$CORE_SRCS $POLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $POLL_MODULE"
+fi
+
+
+if [ $TEST_BUILD_DEVPOLL = YES ]; then
+    have=HAVE_DEVPOLL . auto/have
+    have=TEST_BUILD_DEVPOLL . auto/have
+    EVENT_MODULES="$EVENT_MODULES $DEVPOLL_MODULE"
+    CORE_SRCS="$CORE_SRCS $DEVPOLL_SRCS"
+fi
+
+if [ $TEST_BUILD_EPOLL = YES ]; then
+    have=HAVE_EPOLL . auto/have
+    have=TEST_BUILD_EPOLL . auto/have
+    EVENT_MODULES="$EVENT_MODULES $EPOLL_MODULE"
+    CORE_SRCS="$CORE_SRCS $EPOLL_SRCS"
+fi
+
+if [ $TEST_BUILD_RTSIG = YES ]; then
+    have=HAVE_RTSIG . auto/have
+    have=TEST_BUILD_RTSIG . auto/have
+    EVENT_MODULES="$EVENT_MODULES $RTSIG_MODULE"
+    CORE_SRCS="$CORE_SRCS $RTSIG_SRCS"
+fi
+
+
+# the filter order is important
+#     ngx_http_write_filter
+#     ngx_http_header_filter
+#     ngx_http_chunked_filter
+#     ngx_http_range_header_filter
+#     ngx_http_ssl_filter
+#     ngx_http_gzip_filter
+#     ngx_http_charset_filter
+#     ngx_http_ssi_filter
+#     ngx_http_headers_filter
+#     ngx_http_copy_filter
+#     ngx_http_range_body_filter
+#     ngx_http_not_modified_filter
+
+HTTP_FILTER_MODULES="$HTTP_WRITE_FILTER_MODULE \
+                     $HTTP_HEADER_FILTER_MODULE \
+                     $HTTP_CHUNKED_FILTER_MODULE \
+                     $HTTP_RANGE_HEADER_FILTER_MODULE"
+
+if [ $HTTP_GZIP = YES ]; then
+    have=NGX_HTTP_GZIP . auto/have
+    USE_ZLIB=YES
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_GZIP_FILTER_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_GZIP_SRCS"
+fi
+
+if [ $HTTP_CHARSET = YES ]; then
+    have=NGX_HTTP_CHARSET . auto/have
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_CHARSET_FILTER_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_CHARSET_SRCS"
+fi
+
+if [ $HTTP_SSI = YES ]; then
+    have=NGX_HTTP_SSI . auto/have
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_SSI_FILTER_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_SSI_SRCS"
+fi
+
+if [ $HTTP_USERID = YES ]; then
+    HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_USERID_FILTER_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_USERID_SRCS"
+fi
+
+HTTP_MODULES="$HTTP_MODULES $HTTP_STATIC_MODULE $HTTP_INDEX_MODULE"
+
+if [ $HTTP_ACCESS = YES ]; then
+    have=NGX_HTTP_ACCESS . auto/have
+    HTTP_MODULES="$HTTP_MODULES $HTTP_ACCESS_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_ACCESS_SRCS"
+fi
+
+if [ $HTTP_STATUS = YES ]; then
+    have=NGX_HTTP_STATUS . auto/have
+    HTTP_MODULES="$HTTP_MODULES $HTTP_STATUS_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_STATUS_SRCS"
+fi
+
+if [ $HTTP_REWRITE = YES -a $USE_PCRE != DISABLED ]; then
+    have=NGX_HTTP_REWRITE . auto/have
+    USE_PCRE=YES
+    HTTP_MODULES="$HTTP_MODULES $HTTP_REWRITE_MODULE"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_REWRITE_SRCS"
+fi
+
+if [ $HTTP_SSL = YES ]; then
+    USE_OPENSSL=YES
+    have=NGX_HTTP_SSL . auto/have
+    HTTP_MODULES="$HTTP_MODULES $HTTP_SSL_MODULE"
+    HTTP_DEPS="$HTTP_DEPS $HTTP_SSL_DEPS"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_SSL_SRCS"
+fi
+
+if [ $HTTP_PROXY = YES ]; then
+    have=NGX_HTTP_PROXY . auto/have
+    #USE_MD5=YES
+    HTTP_MODULES="$HTTP_MODULES $HTTP_PROXY_MODULE"
+    HTTP_INCS="$HTTP_INCS $HTTP_PROXY_INCS"
+    HTTP_DEPS="$HTTP_DEPS $HTTP_PROXY_DEPS"
+    HTTP_SRCS="$HTTP_SRCS $HTTP_PROXY_SRCS"
+fi
+
+if [ -r $OBJS/auto ]; then
+    . $OBJS/auto
+fi
+
+modules="$CORE_MODULES $EVENT_MODULES"
+
+if [ $HTTP = YES ]; then
+    modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
+             $HTTP_HEADERS_FILTER_MODULE \
+             $HTTP_COPY_FILTER_MODULE \
+             $HTTP_RANGE_BODY_FILTER_MODULE \
+             $HTTP_NOT_MODIFIED_FILTER_MODULE"
+fi
+
+IMAP_MODULES=$IMAP_MODULE
+
+if [ $IMAP = YES ]; then
+    modules="$modules $IMAP_MODULES"
+fi
+
+
+echo "#include <ngx_config.h>"                > $NGX_MODULES_C
+echo "#include <ngx_core.h>"                  >> $NGX_MODULES_C
+echo                                          >> $NGX_MODULES_C
+
+for mod in $modules
+do
+    echo "extern ngx_module_t  $mod;"         >> $NGX_MODULES_C
+done
+
+echo                                          >> $NGX_MODULES_C
+echo 'ngx_module_t *ngx_modules[] = {'        >> $NGX_MODULES_C
+
+for mod in $modules
+do
+    echo "    &$mod,"                         >> $NGX_MODULES_C
+done
+
+echo "    NULL"                               >> $NGX_MODULES_C
+echo "};"                                     >> $NGX_MODULES_C
new file mode 100644
--- /dev/null
+++ b/auto/nohave
@@ -0,0 +1,11 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $have
+#define $have  0
+#endif
+
+END
new file mode 100644
--- /dev/null
+++ b/auto/options
@@ -0,0 +1,260 @@
+
+# Copyright (C) Igor Sysoev
+
+help=no
+
+PREFIX=
+SBIN_PATH=
+CONF_PATH=
+HTTP_LOG_PATH=
+ERROR_LOG_PATH=
+PID_PATH=
+
+CC=gcc
+CPP=
+OBJS=objs
+
+DEBUG=NO
+CC_OPT=
+CPU=NO
+
+TEST_BUILD_DEVPOLL=NO
+TEST_BUILD_EPOLL=NO
+TEST_BUILD_RTSIG=NO
+
+EVENT_FOUND=NO
+
+EVENT_RTSIG=NO
+EVENT_SELECT=NO
+EVENT_POLL=NO
+EVENT_AIO=NO
+
+USE_THREADS=NO
+
+HTTP=YES
+HTTP_CHARSET=YES
+HTTP_GZIP=YES
+HTTP_SSL=NO
+HTTP_SSI=NO
+HTTP_ACCESS=YES
+HTTP_USERID=YES
+HTTP_STATUS=NO
+HTTP_REWRITE=YES
+HTTP_PROXY=YES
+
+IMAP=NO
+
+USE_PCRE=NO
+PCRE=NONE
+PCRE_OPT=
+
+USE_OPENSSL=NO
+OPENSSL=NONE
+
+USE_MD5=NO
+MD5=NONE
+MD5_OPT=
+MD5_ASM=NO
+
+USE_ZLIB=NO
+ZLIB=NONE
+ZLIB_OPT=
+ZLIB_ASM=NO
+
+
+for option
+do
+    case "$option" in
+        -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
+           *) value="" ;;
+    esac
+
+    case "$option" in
+        --help)                          help=yes                   ;;
+
+        --prefix=*)                      PREFIX="$value"            ;;
+        --sbin-path=*)                   SBIN_PATH="$value"         ;;
+        --conf-path=*)                   CONF_PATH="$value"         ;;
+        --error-log-path=*)              ERROR_LOG_PATH="$value"    ;;
+        --pid-path=*)                    PID_PATH="$value"          ;;
+
+        --crossbuild=*)                  PLATFORM="$value"          ;;
+
+        --builddir=*)                    OBJS="$value"              ;;
+
+        --with-rtsig_module)             EVENT_RTSIG=YES            ;;
+        --with-select_module)            EVENT_SELECT=YES           ;;
+        --without-select_module)         EVENT_SELECT=NONE          ;;
+        --with-poll_module)              EVENT_POLL=YES             ;;
+        --without-poll_module)           EVENT_POLL=NONE            ;;
+        --with-aio_module)               EVENT_AIO=YES              ;;
+
+        --with-threads=*)                USE_THREADS="$value"       ;;
+        --with-threads)                  USE_THREADS="pthreads"     ;;
+
+        --without-http)                  HTTP=NO                    ;;
+        --http-log-path=*)               HTTP_LOG_PATH="$value"     ;;
+
+        --with-http_ssl_module)          HTTP_SSL=YES               ;;
+        --without-http_charset_module)   HTTP_CHARSET=NO            ;;
+        --without-http_gzip_module)      HTTP_GZIP=NO               ;;
+        --without-http_ssi_module)       HTTP_SSI=NO                ;;
+        --without-http_userid_module)    HTTP_USERID=NO             ;;
+        --without-http_access_module)    HTTP_ACCESS=NO             ;;
+        --without-http_status_module)    HTTP_STATUS=NO             ;;
+        --without-http_rewrite_module)   HTTP_REWRITE=NO            ;;
+        --without-http_proxy_module)     HTTP_PROXY=NO              ;;
+
+        --with-imap)                     IMAP=YES                   ;;
+
+        --with-cc=*)                     CC="$value"                ;;
+        --with-cpp=*)                    CPP="$value"               ;;
+        --with-cc-opt=*)                 CC_OPT="$value"            ;;
+        --with-cpu-opt=*)                CPU="$value"               ;;
+        --with-debug)                    DEBUG=YES                  ;;
+
+        --without-pcre)                  USE_PCRE=DISABLED          ;;
+        --with-pcre=*)                   PCRE="$value"              ;;
+        --with-pcre-opt=*)               PCRE_OPT="$value"          ;;
+
+        --with-openssl=*)                OPENSSL="$value"           ;;
+
+        --with-md5=*)                    MD5="$value"               ;;
+        --with-md5-opt=*)                MD5_OPT="$value"           ;;
+        --with-md5-asm)                  MD5_ASM=YES                ;;
+
+        --with-zlib=*)                   ZLIB="$value"              ;;
+        --with-zlib-opt=*)               ZLIB_OPT="$value"          ;;
+        --with-zlib-asm=*)               ZLIB_ASM="$value"          ;;
+
+        --test-build-devpoll)            TEST_BUILD_DEVPOLL=YES     ;;
+        --test-build-epoll)              TEST_BUILD_EPOLL=YES       ;;
+        --test-build-rtsig)              TEST_BUILD_RTSIG=YES       ;;
+
+        *)
+            echo "$0: error: invalid option \"$option\""
+            exit 1
+        ;;
+    esac
+done
+
+
+if [ $help = yes ]; then
+    echo
+    echo "  --help                         this message"
+    echo
+
+    echo "  --without-select_module        disable select_module"
+    echo "  --without-poll_module          disable poll_module"
+
+    echo "  --without-http_rewrite_module  disable http_rewrite_module"
+    echo "  --without-http_gzip_module     disable http_gzip_module"
+    echo "  --without-http_proxy_module    disable http_proxy_module"
+
+    echo "  --with-cc=NAME                 name of or path to C compiler"
+    echo
+
+    echo "  --with-pcre=DIR                path to PCRE library"
+    echo "  --with-md5=DIR                 path to md5 library"
+    echo "  --with-zlib=DIR                path to zlib library"
+    echo
+
+    exit 1
+fi
+
+
+if [ $HTTP = NO ]; then
+    HTTP_CHARSET=NO
+    HTTP_GZIP=NO
+    HTTP_SSI=NO
+    HTTP_USERID=NO
+    HTTP_ACCESS=NO
+    HTTP_STATUS=NO
+    HTTP_REWRITE=NO
+    HTTP_PROXY=NO
+fi
+
+
+if [ ".$PLATFORM" = ".win32" -a $EVENT_POLL = YES ]; then
+    EVENT_POLL=NO
+    echo "$0: warning: --with-poll_module option is ignored for win32"
+fi
+
+
+if [ ".$PREFIX" = "." ]; then
+    PREFIX=/usr/local/nginx
+fi
+
+
+case ".$SBIN_PATH" in
+    ./*)
+    ;;
+
+    .)
+        SBIN_PATH=$PREFIX/sbin/nginx
+    ;;
+
+    *)
+        SBIN_PATH=$PREFIX/$SBIN_PATH
+    ;;
+esac
+
+
+case ".$CONF_PATH" in
+    ./*)
+    ;;
+
+    .)
+        CONF_PATH=$PREFIX/conf/nginx.conf
+    ;;
+
+    *)
+        CONF_PATH=$PREFIX/$CONF_PATH
+    ;;
+esac
+
+
+case ".$PID_PATH" in
+    ./*)
+    ;;
+
+    .)
+        PID_PATH=$PREFIX/logs/nginx.pid
+    ;;
+
+    *)
+        PID_PATH=$PREFIX/$PID_PATH
+    ;;
+esac
+
+
+case ".$ERROR_LOG_PATH" in
+    ./*)
+    ;;
+
+    .)
+        ERROR_LOG_PATH=$PREFIX/logs/error.log
+    ;;
+
+    .stderr)
+        ERROR_LOG_PATH=
+    ;;
+
+    *)
+        ERROR_LOG_PATH=$PREFIX/$ERROR_LOG_PATH
+    ;;
+esac
+
+
+case ".$HTTP_LOG_PATH" in
+    ./*)
+    ;;
+
+    .)
+        HTTP_LOG_PATH=$PREFIX/logs/access.log
+    ;;
+
+    *)
+        HTTP_LOG_PATH=$PREFIX/$HTTP_LOG_PATH
+    ;;
+esac
new file mode 100644
--- /dev/null
+++ b/auto/os/conf
@@ -0,0 +1,57 @@
+
+# Copyright (C) Igor Sysoev
+
+
+if [ ".$PLATFORM" = "." ]; then
+    echo "checking for OS"
+
+    SYSTEM=`uname -s 2>/dev/null`
+    RELEASE=`uname -r 2>/dev/null`
+    MACHINE=`uname -m 2>/dev/null`
+
+    echo " + $SYSTEM $RELEASE $MACHINE"
+
+    PLATFORM="$SYSTEM:$RELEASE:$MACHINE";
+else
+    echo "building for $PLATFORM"
+fi
+
+case $PLATFORM in
+
+    FreeBSD:* | DragonFly:*)
+        . auto/os/freebsd
+    ;;
+
+    Linux:*)
+        . auto/os/linux
+    ;;
+
+    SunOS:*)
+        . auto/os/solaris
+    ;;
+
+    win32)
+        CORE_INCS="$WIN32_INCS"
+        CORE_DEPS="$WIN32_DEPS"
+        CORE_SRCS="$WIN32_SRCS $IOCP_SRCS"
+        OS_CONFIG="$WIN32_CONFIG"
+        EVENT_MODULES="$EVENT_MODULES $IOCP_MODULE"
+        EVENT_FOUND=YES
+
+        if [ $EVENT_SELECT = NO ]; then
+            CORE_SRCS="$CORE_SRCS $SELECT_SRCS"
+            EVENT_MODULES="$EVENT_MODULES $SELECT_MODULE"
+        fi
+
+        have=HAVE_AIO . auto/have
+        have=HAVE_IOCP . auto/have
+        CORE_LIBS="$CORE_LIBS ws2_32.lib"
+    ;;
+
+    *)
+        CORE_INCS="$UNIX_INCS"
+        CORE_DEPS="$UNIX_DEPS $POSIX_DEPS"
+        CORE_SRCS="$UNIX_SRCS"
+    ;;
+
+esac
new file mode 100644
--- /dev/null
+++ b/auto/os/freebsd
@@ -0,0 +1,89 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $FREEBSD_DEPS"
+CORE_SRCS="$UNIX_SRCS $FREEBSD_SRCS"
+
+PIPE="-pipe"
+
+
+# __FreeBSD_version is the best way to determine whether
+# some capability exists and is safe to use
+
+version=`grep "#define __FreeBSD_version" /usr/include/osreldate.h \
+         | sed -e 's/^.* \(.*\)$/\1/'`
+
+
+# setproctitle() in libutil
+
+if [ \( $version -ge 500000 -a $version -lt 500012 \) \
+     -o $version -lt 410002 ]
+then
+    echo " + setproctitle() in libutil"
+
+    CORE_LIBS="$CORE_LIBS -lutil"
+fi
+
+# sendfile
+
+if [ $version -gt 300007 ]; then
+    echo " + using sendfile()"
+
+    have=HAVE_SENDFILE . auto/have
+    CORE_SRCS="$CORE_SRCS $FREEBSD_SENDFILE_SRCS"
+fi
+
+
+# kqueue
+
+if [ \( $version -lt 500000 -a $version -ge 410000 \) \
+     -o $version -ge 500011 ]
+then
+    echo " + using kqueue"
+
+    have=HAVE_KQUEUE . auto/have
+    have=HAVE_CLEAR_EVENT . auto/have
+    EVENT_MODULES="$EVENT_MODULES $KQUEUE_MODULE"
+    CORE_SRCS="$CORE_SRCS $KQUEUE_SRCS"
+    EVENT_FOUND=YES
+fi
+
+
+# kqueue's NOTE_LAWAT
+
+if [ \( $version -lt 500000 -a $version -ge 430000 \) \
+     -o $version -ge 500018 ]
+then
+    echo " + using kqueue's NOTE_LOWAT"
+    have=HAVE_LOWAT_EVENT . auto/have
+fi
+
+
+if [ $USE_THREADS = "rfork" ]; then
+
+    echo " + using rfork()"
+
+#    # kqueue's EVFILT_SIGNAL is safe
+#
+#    if [ $version -gt 460101 ]; then
+#        echo " + kqueue's EVFILT_SIGNAL is safe"
+#        have=HAVE_SAFE_EVFILT_SIGNAL . auto/have
+#    else
+#        echo "$0: error: the kqueue's EVFILT_SIGNAL is unsafe on this"
+#        echo "FreeBSD version, so --with-threads=rfork could not be used"
+#        echo
+#
+#        exit 1
+#    fi
+fi
+
+
+if [ $EVENT_AIO = YES ]; then
+    have=HAVE_AIO . auto/have
+    EVENT_MODULES="$EVENT_MODULES $AIO_MODULE"
+    CORE_SRCS="$CORE_SRCS $AIO_SRCS"
+else
+    have=HAVE_AIO . auto/nohave
+fi
new file mode 100644
--- /dev/null
+++ b/auto/os/linux
@@ -0,0 +1,82 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $LINUX_DEPS"
+CORE_SRCS="$UNIX_SRCS $LINUX_SRCS"
+EVENT_MODULES="$EVENT_MODULES"
+
+PIPE="-pipe"
+
+
+CC_TEST_FLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE"
+
+# Linux kernel version
+
+version=`grep "#define LINUX_VERSION_CODE" /usr/include/linux/version.h \
+         | sed -e 's/^.* \(.*\)$/\1/'`
+
+
+# enable the rt signals on Linux 2.2.19 and onward
+
+if [ $version -ge 131609 -o $EVENT_RTSIG = YES ]; then
+    echo " + using rt signals"
+    have=HAVE_RTSIG . auto/have
+    EVENT_MODULES="$EVENT_MODULES $RTSIG_MODULE"
+    CORE_SRCS="$CORE_SRCS $RTSIG_SRCS"
+    EVENT_FOUND=YES
+fi
+
+
+# epoll, EPOLLET version
+
+ngx_func="epoll";
+ngx_func_inc="#include <sys/epoll.h>"
+ngx_func_test="int efd = 0, fd = 1, n;
+               struct epoll_event ee;
+               ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
+               ee.data.ptr = NULL;
+               n = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ee)"
+. auto/func
+
+if [ $ngx_found = yes ]; then
+    have=HAVE_EPOLL . auto/have
+    have=HAVE_CLEAR_EVENT . auto/have
+    CORE_SRCS="$CORE_SRCS $EPOLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $EPOLL_MODULE"
+    EVENT_FOUND=YES
+fi
+
+
+# sendfile()
+
+CC_TEST_FLAGS="-D_GNU_SOURCE"
+ngx_func="sendfile()";
+ngx_func_inc="#include <sys/sendfile.h>"
+ngx_func_test="int s = 0, fd = 1;
+               ssize_t n; off_t off = 0;
+               n = sendfile(s, fd, &off, 1)"
+. auto/func
+
+if [ $ngx_found = yes ]; then
+    CORE_SRCS="$CORE_SRCS $LINUX_SENDFILE_SRCS"
+fi
+
+
+# sendfile64()
+
+CC_TEST_FLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE"
+ngx_func="sendfile64()"; . auto/func
+
+
+# prctl(PR_SET_DUMPABLE)
+
+ngx_func="prctl()";
+ngx_func_inc="#include <sys/prctl.h>"
+ngx_func_test="prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)"
+. auto/func
+
+if [ $ngx_found = yes ]; then
+    have=HAVE_PR_SET_DUMPABLE . auto/have
+fi
new file mode 100644
--- /dev/null
+++ b/auto/os/solaris
@@ -0,0 +1,83 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CORE_INCS="$UNIX_INCS"
+CORE_DEPS="$UNIX_DEPS $SOLARIS_DEPS"
+CORE_SRCS="$UNIX_SRCS $SOLARIS_SRCS "
+EVENT_MODULES="$EVENT_MODULES"
+
+CORE_LIBS="$CORE_LIBS -lsocket -lnsl -lrt"
+
+# the Solaris's make support
+MAKE_SL=YES
+
+
+CC_TEST_FLAGS="-D_FILE_OFFSET_BITS=64"
+
+case $PLATFORM in
+
+    SunOS:5.[89]:* | SunOS:5.10:*)
+        PIPE="-pipe"
+    ;;
+
+    *)
+        # Solaris 7's /usr/ccs/bin/as does not support "-pipe"
+    ;;
+
+esac
+
+
+case $PLATFORM in
+
+    *:sun4u)
+        CFLAGS="$CFLAGS -mcpu=v9"
+
+        if [ ".$CPU" = ".sparc64" ]; then
+            CFLAGS="$CFLAGS -m64"
+            CPU_OPT="-m64"
+            CORE_LINK="$CORE_LINK -m64"
+
+            CC_TEST_FLAGS="$CC_TEST_FLAGS -mcpu=v9 -m64"
+        fi
+    ;;
+
+    *)
+    ;;
+
+esac
+
+
+if [ $ZLIB_ASM != NO ]; then
+    echo "$0: error: the --with-zlib-asm=CPU option is not supported"
+    echo "on that platform"
+    echo
+
+    exit 1
+fi
+
+
+ngx_inc="sys/devpoll.h"; . auto/inc
+
+if [ $ngx_found = yes ]; then
+    have=HAVE_DEVPOLL . auto/have
+    CORE_SRCS="$CORE_SRCS $DEVPOLL_SRCS"
+    EVENT_MODULES="$EVENT_MODULES $DEVPOLL_MODULE"
+    EVENT_FOUND=YES
+fi
+
+
+ngx_func="sendfilev()";
+ngx_func_inc="#include <sys/sendfile.h>"
+ngx_func_libs="-lsendfile"
+ngx_func_test="int fd = 1; sendfilevec_t vec[1];
+               size_t sent; ssize_t n;
+               n = sendfilev(fd, vec, 1, &sent)"
+. auto/func
+
+
+if [ $ngx_found = yes ]; then
+    have=HAVE_SENDFILE . auto/have
+    CORE_SRCS="$CORE_SRCS $SOLARIS_SENDFILEV_SRCS"
+    CORE_LIBS="$CORE_LIBS -lsendfile"
+fi
new file mode 100644
--- /dev/null
+++ b/auto/sources
@@ -0,0 +1,307 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CORE_MODULES="ngx_core_module ngx_errlog_module ngx_conf_module"
+
+CORE_INCS="src/core"
+
+CORE_DEPS="src/core/nginx.h \
+           src/core/ngx_config.h \
+           src/core/ngx_core.h \
+           src/core/ngx_log.h \
+           src/core/ngx_palloc.h \
+           src/core/ngx_array.h \
+           src/core/ngx_list.h \
+           src/core/ngx_table.h \
+           src/core/ngx_buf.h \
+           src/core/ngx_string.h \
+           src/core/ngx_parse.h \
+           src/core/ngx_inet.h \
+           src/core/ngx_file.h \
+           src/core/ngx_crc.h \
+           src/core/ngx_rbtree.h \
+           src/core/ngx_times.h \
+           src/core/ngx_connection.h \
+           src/core/ngx_cycle.h \
+           src/core/ngx_conf_file.h \
+           src/core/ngx_garbage_collector.h"
+
+#           src/core/ngx_radix_tree.h \
+#           src/core/ngx_radix_tree.c \
+
+CORE_SRCS="src/core/nginx.c \
+           src/core/ngx_log.c \
+           src/core/ngx_palloc.c \
+           src/core/ngx_array.c \
+           src/core/ngx_list.c \
+           src/core/ngx_buf.c \
+           src/core/ngx_output_chain.c \
+           src/core/ngx_string.c \
+           src/core/ngx_parse.c \
+           src/core/ngx_inet.c \
+           src/core/ngx_file.c \
+           src/core/ngx_rbtree.c \
+           src/core/ngx_times.c \
+           src/core/ngx_connection.c \
+           src/core/ngx_cycle.c \
+           src/core/ngx_spinlock.c \
+           src/core/ngx_conf_file.c \
+           src/core/ngx_garbage_collector.c"
+
+
+REGEX_DEPS=src/core/ngx_regex.h
+REGEX_SRCS=src/core/ngx_regex.c
+
+
+EVENT_MODULES="ngx_events_module ngx_event_core_module"
+
+EVENT_INCS="src/event src/event/modules"
+
+EVENT_DEPS="src/event/ngx_event.h \
+            src/event/ngx_event_timer.h \
+            src/event/ngx_event_posted.h \
+            src/event/ngx_event_busy_lock.h \
+            src/event/ngx_event_connect.h \
+            src/event/ngx_event_pipe.h"
+
+EVENT_SRCS="src/event/ngx_event.c \
+            src/event/ngx_event_timer.c \
+            src/event/ngx_event_posted.c \
+            src/event/ngx_event_busy_lock.c \
+            src/event/ngx_event_accept.c \
+            src/event/ngx_event_connect.c \
+            src/event/ngx_event_pipe.c"
+
+
+SELECT_MODULE=ngx_select_module
+SELECT_SRCS=src/event/modules/ngx_select_module.c
+
+POLL_MODULE=ngx_poll_module
+POLL_SRCS=src/event/modules/ngx_poll_module.c
+
+KQUEUE_MODULE=ngx_kqueue_module
+KQUEUE_SRCS=src/event/modules/ngx_kqueue_module.c
+
+DEVPOLL_MODULE=ngx_devpoll_module
+DEVPOLL_SRCS=src/event/modules/ngx_devpoll_module.c
+
+EPOLL_MODULE=ngx_epoll_module
+EPOLL_SRCS=src/event/modules/ngx_epoll_module.c
+
+RTSIG_MODULE=ngx_rtsig_module
+RTSIG_SRCS=src/event/modules/ngx_rtsig_module.c
+
+IOCP_MODULE=ngx_iocp_module
+IOCP_SRCS=src/event/modules/ngx_iocp_module.c
+
+AIO_MODULE=ngx_aio_module
+AIO_SRCS="src/event/modules/ngx_aio_module.c \
+          src/os/unix/ngx_aio_read.c \
+          src/os/unix/ngx_aio_write.c \
+          src/os/unix/ngx_aio_read_chain.c \
+          src/os/unix/ngx_aio_write_chain.c"
+
+
+OPENSSL_DEPS=src/event/ngx_event_openssl.h
+OPENSSL_SRCS=src/event/ngx_event_openssl.c
+
+
+UNIX_INCS="$CORE_INCS $EVENT_INCS src/os/unix"
+
+UNIX_DEPS="$CORE_DEPS $EVENT_DEPS \
+            src/os/unix/ngx_time.h \
+            src/os/unix/ngx_types.h \
+            src/os/unix/ngx_errno.h \
+            src/os/unix/ngx_alloc.h \
+            src/os/unix/ngx_files.h \
+            src/os/unix/ngx_channel.h \
+            src/os/unix/ngx_shared.h \
+            src/os/unix/ngx_process.h \
+            src/os/unix/ngx_atomic.h \
+            src/os/unix/ngx_thread.h \
+            src/os/unix/ngx_socket.h \
+            src/os/unix/ngx_os.h \
+            src/os/unix/ngx_process_cycle.h"
+
+UNIX_SRCS="$CORE_SRCS $EVENT_SRCS \
+            src/os/unix/ngx_time.c \
+            src/os/unix/ngx_errno.c \
+            src/os/unix/ngx_alloc.c \
+            src/os/unix/ngx_files.c \
+            src/os/unix/ngx_socket.c \
+            src/os/unix/ngx_recv.c \
+            src/os/unix/ngx_readv_chain.c \
+            src/os/unix/ngx_send.c \
+            src/os/unix/ngx_writev_chain.c \
+            src/os/unix/ngx_channel.c \
+            src/os/unix/ngx_shared.c \
+            src/os/unix/ngx_process.c \
+            src/os/unix/ngx_daemon.c \
+            src/os/unix/ngx_posix_init.c \
+            src/os/unix/ngx_process_cycle.c"
+
+POSIX_DEPS=src/os/unix/ngx_posix_config.h
+
+FREEBSD_DEPS=src/os/unix/ngx_freebsd_config.h
+FREEBSD_SRCS=src/os/unix/ngx_freebsd_init.c
+FREEBSD_SENDFILE_SRCS=src/os/unix/ngx_freebsd_sendfile_chain.c
+FREEBSD_RFORK_DEPS="src/os/unix/ngx_freebsd_rfork_thread.h"
+FREEBSD_RFORK_SRCS="src/os/unix/ngx_freebsd_rfork_thread.c"
+FREEBSD_RFORK_THREAD_SRCS="src/os/unix/rfork_thread.S"
+
+PTHREAD_SRCS="src/os/unix/ngx_pthread_thread.c"
+
+LINUX_DEPS=src/os/unix/ngx_linux_config.h
+LINUX_SRCS=src/os/unix/ngx_linux_init.c
+LINUX_SENDFILE_SRCS=src/os/unix/ngx_linux_sendfile_chain.c
+
+
+SOLARIS_DEPS=src/os/unix/ngx_solaris_config.h
+SOLARIS_SRCS=src/os/unix/ngx_solaris_init.c
+SOLARIS_SENDFILEV_SRCS=src/os/unix/ngx_solaris_sendfilev_chain.c
+
+
+WIN32_INCS="$CORE_INCS $EVENT_INCS src/os/win32"
+
+WIN32_DEPS="$CORE_DEPS $EVENT_DEPS \
+            src/os/win32/ngx_win32_config.h \
+            src/os/win32/ngx_time.h \
+            src/os/win32/ngx_types.h \
+            src/os/win32/ngx_errno.h \
+            src/os/win32/ngx_alloc.h \
+            src/os/win32/ngx_files.h \
+            src/os/win32/ngx_shared.h \
+            src/os/win32/ngx_process.h \
+            src/os/win32/ngx_atomic.h \
+            src/os/win32/ngx_socket.h \
+            src/os/win32/ngx_os.h \
+            src/os/win32/ngx_process_cycle.h"
+
+WIN32_CONFIG=src/os/win32/ngx_win32_config.h
+
+WIN32_SRCS="$CORE_SRCS $EVENT_SRCS \
+            src/os/win32/ngx_errno.c \
+            src/os/win32/ngx_alloc.c \
+            src/os/win32/ngx_files.c \
+            src/os/win32/ngx_time.c \
+            src/os/win32/ngx_process.c \
+            src/os/win32/ngx_socket.c \
+            src/os/win32/ngx_wsarecv.c \
+            src/os/win32/ngx_wsarecv_chain.c \
+            src/os/win32/ngx_wsasend_chain.c \
+            src/os/win32/ngx_win32_init.c \
+            src/os/win32/ngx_process_cycle.c \
+            src/event/ngx_event_acceptex.c"
+
+
+HTTP_MODULES="ngx_http_module \
+              ngx_http_core_module \
+              ngx_http_log_module"
+
+HTTP_FILE_CACHE_MODULE=ngx_http_cache_module
+
+HTTP_WRITE_FILTER_MODULE="ngx_http_write_filter_module"
+HTTP_HEADER_FILTER_MODULE="ngx_http_header_filter_module"
+
+HTTP_CHUNKED_FILTER_MODULE=ngx_http_chunked_filter_module
+HTTP_HEADERS_FILTER_MODULE=ngx_http_headers_filter_module
+HTTP_COPY_FILTER_MODULE=ngx_http_copy_filter_module
+
+HTTP_RANGE_HEADER_FILTER_MODULE=ngx_http_range_header_filter_module
+HTTP_RANGE_BODY_FILTER_MODULE=ngx_http_range_body_filter_module
+
+HTTP_NOT_MODIFIED_FILTER_MODULE=ngx_http_not_modified_filter_module
+
+HTTP_STATIC_MODULE=ngx_http_static_module
+HTTP_INDEX_MODULE=ngx_http_index_module
+
+HTTP_INCS="src/http src/http/modules"
+
+HTTP_DEPS="src/http/ngx_http.h \
+           src/http/ngx_http_request.h \
+           src/http/ngx_http_config.h \
+           src/http/ngx_http_core_module.h \
+           src/http/ngx_http_cache.h \
+           src/http/ngx_http_busy_lock.h \
+           src/http/ngx_http_log_handler.h"
+
+HTTP_SRCS="src/http/ngx_http.c \
+           src/http/ngx_http_core_module.c \
+           src/http/ngx_http_special_response.c \
+           src/http/ngx_http_request.c \
+           src/http/ngx_http_parse.c \
+           src/http/ngx_http_header_filter.c \
+           src/http/ngx_http_write_filter.c \
+           src/http/ngx_http_copy_filter.c \
+           src/http/ngx_http_log_handler.c \
+           src/http/ngx_http_request_body.c \
+           src/http/ngx_http_parse_time.c \
+           src/http/modules/ngx_http_static_handler.c \
+           src/http/modules/ngx_http_index_handler.c \
+           src/http/modules/ngx_http_chunked_filter.c \
+           src/http/modules/ngx_http_range_filter.c \
+           src/http/modules/ngx_http_headers_filter.c \
+           src/http/modules/ngx_http_not_modified_filter.c"
+
+# STUB
+HTTP_SRCS="$HTTP_SRCS src/http/ngx_http_busy_lock.c"
+
+HTPP_CACHE_SRCS=src/http/ngx_http_cache.c
+HTPP_FILE_CACHE_SRCS=src/http/ngx_http_file_cache.c
+
+
+HTTP_CHARSET_FILTER_MODULE=ngx_http_charset_filter_module
+HTTP_CHARSET_SRCS=src/http/modules/ngx_http_charset_filter.c
+
+
+HTTP_GZIP_FILTER_MODULE=ngx_http_gzip_filter_module
+HTTP_GZIP_SRCS=src/http/modules/ngx_http_gzip_filter.c
+
+
+HTTP_SSI_FILTER_MODULE=ngx_http_ssi_filter_module
+HTTP_SSI_SRCS=src/http/modules/ngx_http_ssi_filter.c
+
+
+HTTP_USERID_FILTER_MODULE=ngx_http_userid_filter_module
+HTTP_USERID_SRCS=src/http/modules/ngx_http_userid_filter.c
+
+
+HTTP_ACCESS_MODULE=ngx_http_access_module
+HTTP_ACCESS_SRCS=src/http/modules/ngx_http_access_handler.c
+
+
+HTTP_STATUS_MODULE=ngx_http_status_module
+HTTP_STATUS_SRCS=src/http/modules/ngx_http_status_handler.c
+
+
+HTTP_REWRITE_MODULE=ngx_http_rewrite_module
+HTTP_REWRITE_SRCS=src/http/modules/ngx_http_rewrite_handler.c
+
+
+HTTP_SSL_MODULE=ngx_http_ssl_module
+HTTP_SSL_DEPS=src/http/modules/ngx_http_ssl_module.h
+HTTP_SSL_SRCS=src/http/modules/ngx_http_ssl_module.c
+
+
+HTTP_PROXY_MODULE=ngx_http_proxy_module
+HTTP_PROXY_INCS="src/http/modules/proxy"
+HTTP_PROXY_DEPS=src/http/modules/proxy/ngx_http_proxy_handler.h
+HTTP_PROXY_SRCS="src/http/modules/proxy/ngx_http_proxy_handler.c \
+                 src/http/modules/proxy/ngx_http_proxy_upstream.c \
+                 src/http/modules/proxy/ngx_http_proxy_parse.c \
+                 src/http/modules/proxy/ngx_http_proxy_header.c"
+
+# STUB
+#                 src/http/modules/proxy/ngx_http_proxy_cache.c \
+
+
+IMAP_INCS="src/imap"
+
+IMAP_DEPS="src/imap/ngx_imap.h"
+
+IMAP_MODULE=ngx_imap_module
+IMAP_SRCS="src/imap/ngx_imap.c \
+	   src/imap/ngx_imap_handler.c \
+	   src/imap/ngx_imap_parse.c \
+	   src/imap/ngx_imap_proxy.c"
new file mode 100644
--- /dev/null
+++ b/auto/summary
@@ -0,0 +1,98 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo
+echo "Configuration summary"
+
+
+if [ $USE_PCRE = DISABLED ]; then
+    echo " + PCRE library is disabled"
+
+else
+    case $PCRE in
+        YES)   echo "  + using system PCRE library" ;;
+        NONE)  echo "  + PCRE library is not used" ;;
+        NO)    echo "  + PCRE library is not found" ;;
+        *)     echo "  + using PCRE library: $PCRE" ;;
+    esac
+fi
+
+case $MD5 in
+    YES)   echo "  + md5: using system $MD5_LIB library" ;;
+    NONE)  echo "  + md5 library is not used" ;;
+    NO)    echo "  + md5 library is not found" ;;
+    *)     echo "  + using md5 library: $MD5" ;;
+esac
+
+case $OPENSSL in
+    YES)   echo "  + using system OpenSSL library" ;;
+    NONE)  echo "  + OpenSSL library is not used" ;;
+    NO)    echo "  + OpenSSL library is not found" ;;
+    *)     echo "  + using OpenSSL library: $OPENSSL" ;;
+esac
+
+case $ZLIB in
+    YES)   echo "  + using system zlib library" ;;
+    NONE)  echo "  + zlib library is not used" ;;
+    NO)    echo "  + zlib library is not found" ;;
+    *)     echo "  + using zlib library: $ZLIB" ;;
+esac
+
+echo
+
+
+if [ $HTTP_REWRITE = YES ]; then
+    if [ $USE_PCRE = DISABLED ]; then
+
+cat << END
+$0: error: the HTTP rewrite module requires the PCRE library.
+You can either disable the module by using --without-http_rewrite_module
+option or you have to enable the PCRE support.
+
+END
+        exit 1
+    fi
+
+    if [ $PCRE = NONE -o $PCRE = NO ]; then
+
+cat << END
+$0: error: the HTTP rewrite module requires the PCRE library.
+You can either disable the module by using --without-http_rewrite_module
+option, or install the PCRE library into the system, or build the PCRE library
+statically from the source with nginx by using --with-pcre=<path> option.
+
+END
+
+        exit 1
+    fi
+fi
+
+
+if [ $HTTP_GZIP = YES ]; then
+    if [ $ZLIB = NONE -o $ZLIB = NO ]; then
+
+cat << END
+$0: error: the HTTP gzip module requires the zlib library.
+You can either disable the module by using --without-http_gzip_module
+option, or install the zlib library into the system, or build the zlib library
+statically from the source with nginx by using --with-zlib=<path> option.
+
+END
+
+        exit 1
+    fi
+fi
+
+
+echo "  nginx path prefix: $PREFIX"
+echo "  nginx binary file: $SBIN_PATH"
+echo "  nginx configuration file: $CONF_PATH"
+echo "  nginx pid file: $PID_PATH"
+if [ ".$ERROR_LOG_PATH" != "." ]; then
+    echo "  nginx error log file: $ERROR_LOG_PATH"
+else
+    echo "  nginx logs errors to stderr"
+fi
+echo "  nginx http access log file: $HTTP_LOG_PATH"
+echo
new file mode 100644
--- /dev/null
+++ b/auto/threads
@@ -0,0 +1,63 @@
+
+# Copyright (C) Igor Sysoev
+
+
+case $USE_THREADS in
+    rfork)
+        have=NGX_THREADS . auto/have
+        have=NGX_USE_RFORK . auto/have
+        CORE_DEPS="$CORE_DEPS $FREEBSD_RFORK_DEPS"
+        CORE_SRCS="$CORE_SRCS $FREEBSD_RFORK_SRCS"
+
+        case $PLATFORM in
+            *:i386)
+                if [ \( $version -gt 500000 -a $version -lt 501000 \) \
+                     -o $version -lt 491000 ]
+                then
+                    CORE_SRCS="$CORE_SRCS $FREEBSD_RFORK_THREAD_SRCS"
+                fi
+            ;;
+        esac
+    ;;
+
+    pthread)
+        have=NGX_THREADS . auto/have
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -lpthread"
+    ;;
+
+    freebsd4)
+        have=NGX_THREADS . auto/have
+        CFLAGS="$CFLAGS -pthread"
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -pthread"
+    ;;
+
+    linuxthreads)
+        have=NGX_THREADS . auto/have
+        have=NGX_LINUXTHREADS . auto/have
+        CFLAGS="$CFLAGS -D_THREAD_SAFE"
+        CFLAGS="$CFLAGS -I /usr/local/include/pthread/linuxthreads"
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -L /usr/local/lib -llthread -llgcc_r"
+    ;;
+
+    lc_r)
+        have=NGX_THREADS . auto/have
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -lc_r"
+    ;;
+
+    lthr)
+        have=NGX_THREADS . auto/have
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -lthr"
+    ;;
+
+    lkse)
+        have=NGX_THREADS . auto/have
+        CORE_SRCS="$CORE_SRCS $PTHREAD_SRCS"
+        CORE_LIBS="$CORE_LIBS -lkse"
+    ;;
+
+esac
new file mode 100644
--- /dev/null
+++ b/auto/types/sizeof
@@ -0,0 +1,62 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_type size ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_type size" >> $NGX_ERR
+
+ngx_size=
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+#include <sys/time.h>
+$NGX_UNISTD_H
+#include <signal.h>
+#include <sys/resource.h>
+$NGX_INTTYPES_H
+$NGX_AUTO_CONFIG
+
+int main() {
+    printf("%d", sizeof($ngx_type));
+    return 0;
+}
+
+END
+
+eval "$CC $CC_TEST_FLAGS -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    ngx_size=`$NGX_AUTOTEST`
+    echo " $ngx_size bytes"
+fi
+
+rm $NGX_AUTOTEST*
+
+case $ngx_size in
+    4)
+        if [ "$ngx_type"="long" ]; then
+            ngx_max_value=2147483647L
+        else
+            ngx_max_value=2147483647
+        fi
+
+        ngx_max_len='sizeof("-2147483648") - 1'
+    ;;
+
+    8)
+        if [ "$ngx_type"="long long" ]; then
+            ngx_max_value=9223372036854775807LL
+        else
+            ngx_max_value=9223372036854775807L
+        fi
+
+        ngx_max_len='sizeof("-9223372036854775808") - 1'
+    ;;
+
+    *)
+        echo
+        echo "$0: error: can not detect $ngx_type size"
+        exit 1
+esac
new file mode 100644
--- /dev/null
+++ b/auto/types/typedef
@@ -0,0 +1,60 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for $ngx_type ..." $ngx_c
+echo >> $NGX_ERR
+echo "checking for $ngx_type" >> $NGX_ERR
+
+found=no
+
+for type in $ngx_type $ngx_types
+do
+
+    cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+$NGX_INTTYPES_H
+
+int main() {
+    $type i = 0;
+    return 0;
+}
+
+END
+
+    eval "$CC -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+    if [ -x $NGX_AUTOTEST ]; then
+        if [ $type = $ngx_type ]; then
+            echo " found"
+            found=yes
+        else
+            echo ", $type used"
+            found=$type
+        fi
+    fi
+
+    rm $NGX_AUTOTEST*
+
+    if [ $found = no ]; then
+        echo $ngx_n " $type not found" $ngx_c
+    else
+        break
+    fi
+done
+
+if [ $found = no ]; then
+    echo
+    echo "$0: error: can not define $ngx_type"
+    exit 1
+fi
+
+if [ $found != yes ]; then
+    echo "typedef $found  $ngx_type;"   >> $NGX_AUTO_CONFIG_H
+fi
new file mode 100644
--- /dev/null
+++ b/auto/types/uintptr_t
@@ -0,0 +1,40 @@
+
+# Copyright (C) Igor Sysoev
+
+
+echo $ngx_n "checking for uintptr_t ... " $ngx_c
+echo >> $NGX_ERR
+echo "checking for uintptr_t" >> $NGX_ERR
+
+found=no
+
+cat << END > $NGX_AUTOTEST.c
+
+#include <sys/types.h>
+$NGX_INTTYPES_H
+
+int main() {
+    uintptr_t i = 0;
+    return 0;
+}
+
+END
+
+eval "$CC -o $NGX_AUTOTEST $NGX_AUTOTEST.c >> $NGX_ERR 2>&1"
+
+if [ -x $NGX_AUTOTEST ]; then
+    echo " uintptr_t found"
+    found=yes
+else
+    echo $ngx_n " uintptr_t not found" $ngx_c
+fi
+
+rm $NGX_AUTOTEST*
+
+
+if [ $found = no ]; then
+    found="uint`expr 8 \* $ngx_ptr_size`_t"
+    echo ", $found used"
+
+    echo "typedef $found  uintptr_t;"   >> $NGX_AUTO_CONFIG_H
+fi
new file mode 100644
--- /dev/null
+++ b/auto/types/value
@@ -0,0 +1,9 @@
+
+# Copyright (C) Igor Sysoev
+
+
+cat << END >> $NGX_AUTO_CONFIG_H
+
+#ifndef $ngx_param
+#define $ngx_param  $ngx_value
+#endif
new file mode 100755
--- /dev/null
+++ b/auto/unix
@@ -0,0 +1,199 @@
+
+# Copyright (C) Igor Sysoev
+
+
+CC_WARN=$CC
+ngx_fmt_collect=yes
+
+# C types
+
+ngx_type="int"; . auto/types/sizeof
+ngx_formats="%d"; . auto/fmt/fmt
+
+ngx_type="long"; . auto/types/sizeof
+ngx_formats="%ld"; . auto/fmt/fmt
+
+ngx_type="long long"; . auto/types/sizeof
+ngx_formats="%lld %qd"; . auto/fmt/fmt
+
+ngx_type="void *"; . auto/types/sizeof; ngx_ptr_size=$ngx_size
+ngx_fmt_name=PTR_FMT;
+eval ngx_formats=\${ngx_${ngx_ptr_size}_fmt}; . auto/fmt/ptrfmt
+
+
+# POSIX types
+
+NGX_AUTO_CONFIG="#include \"../$NGX_AUTO_CONFIG_H\""
+
+ngx_type="uint64_t"; ngx_types="u_int64_t"; . auto/types/typedef
+
+ngx_type="sig_atomic_t"; ngx_types="int"; . auto/types/typedef
+. auto/types/sizeof
+ngx_param=SIG_ATOMIC_T_SIZE; ngx_value=$ngx_size; . auto/types/value
+
+ngx_type="socklen_t"; ngx_types="uint32_t"; . auto/types/typedef
+
+ngx_type="in_addr_t"; ngx_types="uint32_t"; . auto/types/typedef
+
+ngx_type="in_port_t"; ngx_types="u_short"; . auto/types/typedef
+
+ngx_type="rlim_t"; ngx_types="int"; . auto/types/typedef
+
+. auto/types/uintptr_t
+
+. auto/endianess
+
+
+# printf() formats
+
+CC_WARN=$CC_STRONG
+ngx_fmt_collect=no
+
+ngx_fmt_name=OFF_T_FMT; ngx_type="off_t"; . auto/types/sizeof
+ngx_param=OFF_T_MAX_VALUE; ngx_value=$ngx_max_value; . auto/types/value
+eval ngx_formats=\${ngx_${ngx_size}_fmt}; . auto/fmt/fmt
+
+ngx_fmt_name=TIME_T_FMT; ngx_type="time_t"; . auto/types/sizeof
+ngx_param=TIME_T_SIZE; ngx_value=$ngx_size; . auto/types/value
+ngx_param=TIME_T_LEN; ngx_value=$ngx_max_len; . auto/types/value
+eval ngx_formats=\${ngx_${ngx_size}_fmt}; . auto/fmt/fmt
+
+ngx_fmt_name=SIZE_T_FMT; ngx_type="size_t"; . auto/types/sizeof
+eval ngx_formats=\${ngx_${ngx_size}_fmt}; . auto/fmt/fmt
+
+ngx_fmt_name=SIZE_T_X_FMT; . auto/fmt/xfmt
+
+ngx_fmt_name=PID_T_FMT; ngx_type="pid_t"; . auto/types/sizeof
+eval ngx_formats=\${ngx_${ngx_size}_fmt}; . auto/fmt/fmt
+
+ngx_fmt_name=RLIM_T_FMT; ngx_type="rlim_t"; . auto/types/sizeof
+eval ngx_formats=\${ngx_${ngx_size}_fmt}; . auto/fmt/fmt
+
+
+# syscalls, libc calls and some features
+
+ngx_feature_libs=
+ngx_func_libs=
+
+
+ngx_func="pread()"
+ngx_func_inc=
+ngx_func_test="char buf[1]; ssize_t n; n = pread(0, buf, 1, 0)"
+. auto/func
+
+
+ngx_func="pwrite()"
+ngx_func_inc=
+ngx_func_test="char buf[1]; ssize_t n; n = pwrite(1, buf, 1, 0)"
+. auto/func
+
+
+#ngx_func="strsignal()"
+#ngx_func_inc="#include <string.h>"
+#ngx_func_test="char *s = strsignal(1)"
+#. auto/func
+
+
+ngx_func="strerror_r()"
+ngx_func_inc="#include <string.h>"
+ngx_func_test="char buf[20]; int n; n = strerror_r(1, buf, 20)"
+. auto/func
+
+
+ngx_func="gnu_strerror_r()"
+ngx_func_inc="#include <string.h>"
+ngx_func_test="char buf[20], *str; str = strerror_r(1, buf, 20)"
+. auto/func
+
+
+ngx_func="localtime_r()"
+ngx_func_inc="#include <time.h>"
+ngx_func_test="struct tm t; time_t c=0; localtime_r(&c, &t)"
+. auto/func
+
+
+ngx_func="posix_memalign()"
+ngx_func_inc="#include <stdlib.h>"
+ngx_func_test="void *p; int n; n = posix_memalign(&p, 4096, 4096)"
+. auto/func
+
+
+ngx_func="memalign()"
+ngx_func_inc="#include <stdlib.h>"
+ngx_func_test="void *p; p = memalign(4096, 4096)"
+. auto/func
+
+
+
+ngx_feature="mmap(MAP_ANON|MAP_SHARED)"
+ngx_feature_name="MAP_ANON"
+ngx_feature_inc="#include <sys/mman.h>"
+ngx_feature_test="void *p;
+                  p = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
+                           MAP_ANON|MAP_SHARED, -1, 0);
+                  if (p == MAP_FAILED) return 1;"
+ngx_feature_run=yes
+. auto/feature
+
+
+ngx_feature='mmap("/dev/zero", MAP_SHARED)'
+ngx_feature_name="MAP_DEVZERO"
+ngx_feature_inc="#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>"
+ngx_feature_test='void *p; int  fd;
+                  fd = open("/dev/zero", O_RDWR);
+                  p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+                  if (p == MAP_FAILED) return 1;'
+. auto/feature
+
+
+ngx_feature="System V shared memory"
+ngx_feature_name="SYSVSHM"
+ngx_feature_inc="#include <sys/ipc.h>
+#include <sys/shm.h>"
+ngx_feature_test="int  id;
+                  id = shmget(IPC_PRIVATE, 4096, (SHM_R|SHM_W|IPC_CREAT));
+                  if (id == -1) return 1;
+                  shmctl(id, IPC_RMID, NULL);"
+. auto/feature
+
+
+
+ngx_feature="struct sockaddr_in.sin_len"
+ngx_feature_name="sin_len"
+ngx_feature_inc="#include <sys/socket.h>
+#include <netinet/in.h>"
+ngx_feature_test="struct sockaddr_in sa; sa.sin_len = 5"
+ngx_feature_run=no
+. auto/feature
+
+
+ngx_feature="struct msghdr.msg_control"
+ngx_feature_name="msghdr_msg_control"
+ngx_feature_inc="#include <sys/socket.h>"
+ngx_feature_test="struct msghdr  msg; msg.msg_control = NULL"
+. auto/feature
+
+
+case $PLATFORM in
+    Linux:*)
+        ngx_feature_inc="#include <sys/ioctl.h>"
+    ;;
+
+    *)
+        ngx_feature_inc="#include <sys/filio.h>"
+    ;;
+esac
+
+ngx_feature="ioctl(FIONBIO)"
+ngx_feature_name="FIONBIO"
+ngx_feature_test="int i; i = FIONBIO"
+. auto/feature
+
+
+ngx_feature="struct tm.tm_gmtoff"
+ngx_feature_name="gmtoff"
+ngx_feature_inc="#include <time.h>"
+ngx_feature_test="struct tm  tm; tm.tm_gmtoff = 0"
+. auto/feature
new file mode 100644
--- /dev/null
+++ b/conf/koi-win
@@ -0,0 +1,85 @@
+
+charset_map  koi8-r  windows-1251 {
+
+    95  95 ; # bullet
+
+    9A  A0 ; # &nbsp;
+
+    9C  B0 ; # &deg;
+
+    9E  B7 ; # &middot;
+
+    A3  B8 ; # small yo
+
+    B3  A8 ; # capital YO
+
+    BF  A9 ; # (C)
+
+    C0  FE ; # small yu
+    C1  E0 ; # small a
+    C2  E1 ; # small b
+    C3  F6 ; # small ts
+    C4  E4 ; # small d
+    C5  E5 ; # small ye
+    C6  F4 ; # small f
+    C7  E3 ; # small g
+    C8  F5 ; # small kh
+    C9  E8 ; # small i
+    CA  E9 ; # small j
+    CB  EA ; # small k
+    CC  EB ; # small l
+    CD  EC ; # small m
+    CE  ED ; # small n
+    CF  EE ; # small o
+
+    D0  EF ; # small p
+    D1  FF ; # small ya
+    D2  F0 ; # small r
+    D3  F1 ; # small s
+    D4  F2 ; # small t
+    D5  F3 ; # small u
+    D6  E6 ; # small zh
+    D7  E2 ; # small v
+    D8  FC ; # small soft sign
+    D9  FB ; # small y
+    DA  E7 ; # small z
+    DB  F8 ; # small sh
+    DC  FD ; # small e
+    DD  F9 ; # small shch
+    DE  F7 ; # small ch
+    DF  FA ; # small hard sign
+
+    E0  DE ; # capital YU
+    E1  C0 ; # capital A
+    E2  C1 ; # capital B
+    E3  D6 ; # capital TS
+    E4  C4 ; # capital D
+    E5  C5 ; # capital YE
+    E6  D4 ; # capital F
+    E7  C3 ; # capital G
+    E8  D5 ; # capital KH
+    E9  C8 ; # capital I
+    EA  C9 ; # capital J
+    EB  CA ; # capital K
+    EC  CB ; # capital L
+    ED  CC ; # capital M
+    EE  CD ; # capital N
+    EF  CE ; # capital O
+
+    F0  CF ; # capital P
+    F1  DF ; # capital YA
+    F2  D0 ; # capital R
+    F3  D1 ; # capital S
+    F4  D2 ; # capital T
+    F5  D3 ; # capital U
+    F6  C6 ; # capital ZH
+    F7  C2 ; # capital V
+    F8  DC ; # capital soft sign
+    F9  DB ; # capital Y
+    FA  C7 ; # capital Z
+    FB  D8 ; # capital SH
+    FC  DD ; # capital E
+    FD  D9 ; # capital SHCH
+    FE  D7 ; # capital CH
+    FF  DA ; # capital hard sign
+}
new file mode 100644
--- /dev/null
+++ b/conf/mime.types
@@ -0,0 +1,24 @@
+
+types {
+    text/html                      html htm shtml;
+    text/xml                       xml rss;
+    text/css                       css;
+    text/plain                     txt;
+
+    image/gif                      gif;
+    image/png                      png;
+    image/jpeg                     jpeg jpg;
+    image/x-icon                   ico;
+
+    application/pdf                pdf;
+    application/x-shockwave-flash  swf;
+    application/x-javascript       js;
+
+    audio/mpeg                     mp3;
+    audio/x-realaudio              ra;
+
+    video/mpeg                     mpeg mpg;
+    video/quicktime                mov;
+    video/x-msvideo                avi;
+    video/x-ms-wmv                 wmv;
+}
new file mode 100644
--- /dev/null
+++ b/conf/nginx.conf
@@ -0,0 +1,37 @@
+
+user  nobody;
+worker_processes  3;
+
+#error_log  logs/error.log;
+#pid        logs/nginx.pid;
+
+
+events {
+    connections  1024;
+}
+
+
+http {
+    include       conf/mime.types;
+    default_type  application/octet-stream;
+
+    sendfile  on;
+
+    #gzip  on;
+
+    server {
+        listen  80;
+
+        charset         on;
+        source_charset  koi8-r;
+
+        #access_log  logs/access.log;
+
+        location / {
+            root   html;
+            index  index.html index.htm;
+        }
+
+    }
+
+}
new file mode 100755
--- /dev/null
+++ b/configure
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# Copyright (C) Igor Sysoev
+
+
+. auto/options
+. auto/init
+. auto/sources
+
+test -d $OBJS || mkdir $OBJS
+echo > $NGX_AUTO_CONFIG_H
+
+if [ $DEBUG = YES ]; then
+    have=NGX_DEBUG . auto/have
+fi
+
+have=NGX_USE_HTTP_FILE_CACHE_UNIQ . auto/have
+have=NGX_SUPPRESS_WARN . auto/have
+
+
+if [ "$PLATFORM" != win32 ]; then
+    . auto/headers
+fi
+
+. auto/os/conf
+. auto/modules
+
+. auto/cc
+. auto/lib/conf
+
+if [ "$PLATFORM" != win32 ]; then
+    . auto/threads
+fi
+
+. auto/make
+. auto/lib/make
+. auto/install
+
+if [ "$PLATFORM" != win32 ]; then
+    . auto/unix
+fi
+
+have=NGX_SMP . auto/have
+
+have=NGX_PREFIX value="\"$PREFIX/\"" . auto/define
+have=NGX_SBIN_PATH value="\"$SBIN_PATH\"" . auto/define
+have=NGX_CONF_PATH value="\"$CONF_PATH\"" . auto/define
+have=NGX_PID_PATH value="\"$PID_PATH\"" . auto/define
+if [ ".$ERROR_LOG_PATH" != "." ]; then
+    have=NGX_ERROR_LOG_PATH value="\"$ERROR_LOG_PATH\"" . auto/define
+fi
+have=NGX_HTTP_LOG_PATH value="\"$HTTP_LOG_PATH\"" . auto/define
+
+. auto/summary
new file mode 100644
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Welcome to nginx!</title>
+</head>
+<body bgcolor="white" text="black">
+<center><h1>Welcome to nginx!</h1></center>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/src/core/nginx.c
@@ -0,0 +1,503 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle);
+static ngx_int_t ngx_getopt(ngx_master_ctx_t *ctx, ngx_cycle_t *cycle);
+static void *ngx_core_module_create_conf(ngx_cycle_t *cycle);
+static char *ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf);
+static char *ngx_set_user(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t  ngx_core_commands[] = {
+
+    { ngx_string("daemon"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_core_conf_t, daemon),
+      NULL },
+
+    { ngx_string("master_process"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_core_conf_t, master),
+      NULL },
+
+    { ngx_string("worker_processes"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_core_conf_t, worker_processes),
+      NULL },
+
+#if (NGX_THREADS)
+
+    { ngx_string("worker_threads"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_core_conf_t, worker_threads),
+      NULL },
+
+    { ngx_string("thread_stack_size"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      0,
+      offsetof(ngx_core_conf_t, thread_stack_size),
+      NULL },
+
+#endif
+
+    { ngx_string("user"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE12,
+      ngx_set_user,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("pid"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      0,
+      offsetof(ngx_core_conf_t, pid),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_core_module_ctx = {
+    ngx_string("core"),
+    ngx_core_module_create_conf,
+    ngx_core_module_init_conf
+};
+
+
+ngx_module_t  ngx_core_module = {
+    NGX_MODULE,
+    &ngx_core_module_ctx,                  /* module context */
+    ngx_core_commands,                     /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+ngx_uint_t  ngx_max_module;
+
+
+int main(int argc, char *const *argv)
+{
+    ngx_int_t          i;
+    ngx_log_t         *log;
+    ngx_cycle_t       *cycle, init_cycle;
+    ngx_core_conf_t   *ccf;
+    ngx_master_ctx_t   ctx;
+
+#if defined __FreeBSD__
+    ngx_debug_init();
+#endif
+
+    /* TODO */ ngx_max_sockets = -1;
+
+    ngx_time_init();
+
+#if (HAVE_PCRE)
+    ngx_regex_init();
+#endif
+
+    ngx_pid = ngx_getpid();
+
+    if (!(log = ngx_log_init_stderr())) {
+        return 1;
+    }
+
+#if (NGX_OPENSSL)
+    ngx_ssl_init(log);
+#endif
+
+    /* init_cycle->log is required for signal handlers and ngx_getopt() */
+
+    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
+    init_cycle.log = log;
+    ngx_cycle = &init_cycle;
+
+    ngx_memzero(&ctx, sizeof(ngx_master_ctx_t));
+    ctx.argc = argc;
+    ctx.argv = argv;
+
+    if (!(init_cycle.pool = ngx_create_pool(1024, log))) {
+        return 1;
+    }
+
+    if (ngx_getopt(&ctx, &init_cycle) == NGX_ERROR) {
+        return 1;
+    }
+
+    if (ngx_test_config) {
+        log->log_level = NGX_LOG_INFO;
+    }
+
+    if (ngx_os_init(log) == NGX_ERROR) {
+        return 1;
+    }
+
+    if (ngx_add_inherited_sockets(&init_cycle) == NGX_ERROR) {
+        return 1;
+    }
+
+    ngx_max_module = 0;
+    for (i = 0; ngx_modules[i]; i++) {
+        ngx_modules[i]->index = ngx_max_module++;
+    }
+
+    cycle = ngx_init_cycle(&init_cycle);
+    if (cycle == NULL) {
+        if (ngx_test_config) {
+            ngx_log_error(NGX_LOG_EMERG, log, 0,
+                          "the configuration file %s test failed",
+                          init_cycle.conf_file.data);
+        }
+
+        return 1;
+    }
+
+    if (ngx_test_config) {
+        ngx_log_error(NGX_LOG_INFO, log, 0,
+                      "the configuration file %s was tested successfully",
+                      init_cycle.conf_file.data);
+        return 0;
+    }
+
+    ngx_os_status(cycle->log);
+
+    ngx_cycle = cycle;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    ngx_process = ccf->master ? NGX_PROCESS_MASTER : NGX_PROCESS_SINGLE;
+
+#if (WIN32)
+
+#if 0
+
+    TODO:
+
+    if (ccf->run_as_service) {
+        if (ngx_service(cycle->log) == NGX_ERROR) {
+            return 1;
+        }
+
+        return 0;
+    }
+#endif
+
+#else
+
+    if (!ngx_inherited && ccf->daemon) {
+        if (ngx_daemon(cycle->log) == NGX_ERROR) {
+            return 1;
+        }
+
+        ngx_daemonized = 1;
+    }
+
+    if (ngx_create_pidfile(cycle, NULL) == NGX_ERROR) {
+        return 1;
+    }
+
+#endif
+
+    if (ngx_process == NGX_PROCESS_MASTER) {
+        ngx_master_process_cycle(cycle, &ctx);
+
+    } else {
+        ngx_single_process_cycle(cycle, &ctx);
+    }
+
+    return 0;
+}
+
+
+static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle)
+{
+    u_char              *p, *v, *inherited;
+    ngx_socket_t         s;
+    ngx_listening_t     *ls;
+
+    inherited = (u_char *) getenv(NGINX_VAR);
+
+    if (inherited == NULL) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
+                  "using inherited sockets from \"%s\"", inherited);
+
+    ngx_init_array(cycle->listening, cycle->pool,
+                   10, sizeof(ngx_listening_t), NGX_ERROR);
+
+    for (p = inherited, v = p; *p; p++) {
+        if (*p == ':' || *p == ';') {
+            s = ngx_atoi(v, p - v);
+            if (s == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                              "invalid socket number \"%s\" in "
+                              NGINX_VAR " enviroment variable, "
+                              "ignoring the rest of the variable", v);
+                break;
+            }
+
+            v = p + 1;
+
+            if (!(ls = ngx_push_array(&cycle->listening))) {
+                return NGX_ERROR;
+            }
+
+            ls->fd = s;
+        }
+    }
+
+    ngx_inherited = 1;
+
+    return ngx_set_inherited_sockets(cycle);
+}
+
+
+ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
+{
+    char             *env[2], *var, *p;
+    ngx_uint_t        i;
+    ngx_pid_t         pid;
+    ngx_exec_ctx_t    ctx;
+    ngx_listening_t  *ls;
+
+    ctx.path = argv[0];
+    ctx.name = "new binary process";
+    ctx.argv = argv;
+
+    var = ngx_alloc(sizeof(NGINX_VAR)
+                            + cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2,
+                    cycle->log);
+
+    p = (char *) ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        p += ngx_snprintf(p, NGX_INT32_LEN + 2, "%u;", ls[i].fd);
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, "inherited: %s", var);
+
+    env[0] = var;
+    env[1] = NULL;
+    ctx.envp = (char *const *) &env;
+
+    pid = ngx_execute(cycle, &ctx);
+
+    ngx_free(var);
+
+    return pid;
+}
+
+
+static ngx_int_t ngx_getopt(ngx_master_ctx_t *ctx, ngx_cycle_t *cycle)
+{
+    ngx_int_t  i;
+
+    for (i = 1; i < ctx->argc; i++) {
+        if (ctx->argv[i][0] != '-') {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "invalid option: \"%s\"", ctx->argv[i]);
+            return NGX_ERROR;
+        }
+
+        switch (ctx->argv[i][1]) {
+
+        case 't':
+            ngx_test_config = 1;
+            break;
+
+        case 'c':
+            if (ctx->argv[i + 1] == NULL) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                              "the option: \"%s\" requires file name",
+                              ctx->argv[i]);
+                return NGX_ERROR;
+            }
+
+            cycle->conf_file.data = (u_char *) ctx->argv[++i];
+            cycle->conf_file.len = ngx_strlen(cycle->conf_file.data);
+            break;
+
+        default:
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "invalid option: \"%s\"", ctx->argv[i]);
+            return NGX_ERROR;
+        }
+    }
+
+    if (cycle->conf_file.data == NULL) {
+        cycle->conf_file.len = sizeof(NGX_CONF_PATH) - 1;
+        cycle->conf_file.data = (u_char *) NGX_CONF_PATH;
+    }
+
+    if (ngx_conf_full_name(cycle, &cycle->conf_file) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static void *ngx_core_module_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_core_conf_t  *ccf;
+
+    if (!(ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t)))) {
+        return NULL;
+    }
+    /* set by pcalloc()
+     *
+     * ccf->pid = NULL;
+     * ccf->newpid = NULL;
+     */
+    ccf->daemon = NGX_CONF_UNSET;
+    ccf->master = NGX_CONF_UNSET;
+    ccf->worker_processes = NGX_CONF_UNSET;
+#if (NGX_THREADS)
+    ccf->worker_threads = NGX_CONF_UNSET;
+    ccf->thread_stack_size = NGX_CONF_UNSET;
+#endif
+    ccf->user = (ngx_uid_t) NGX_CONF_UNSET;
+    ccf->group = (ngx_gid_t) NGX_CONF_UNSET;
+
+    return ccf;
+}
+
+
+static char *ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_core_conf_t  *ccf = conf;
+
+#if !(WIN32)
+    struct passwd    *pwd;
+    struct group     *grp;
+#endif
+
+    ngx_conf_init_value(ccf->daemon, 1);
+    ngx_conf_init_value(ccf->master, 1);
+    ngx_conf_init_value(ccf->worker_processes, 1);
+
+#if (NGX_THREADS)
+    ngx_conf_init_value(ccf->worker_threads, 0);
+    ngx_threads_n = ccf->worker_threads;
+    ngx_conf_init_size_value(ccf->thread_stack_size, 2 * 1024 * 1024);
+#endif
+
+#if !(WIN32)
+
+    if (ccf->user == (uid_t) NGX_CONF_UNSET) {
+
+        pwd = getpwnam("nobody");
+        if (pwd == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "getpwnam(\"nobody\") failed");
+            return NGX_CONF_ERROR;
+        }
+
+        ccf->user = pwd->pw_uid;
+
+        grp = getgrnam("nobody");
+        if (grp == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "getgrnam(\"nobody\") failed");
+            return NGX_CONF_ERROR;
+        }
+
+        ccf->group = grp->gr_gid;
+    }
+
+    if (ccf->pid.len == 0) {
+        ccf->pid.len = sizeof(NGX_PID_PATH) - 1;
+        ccf->pid.data = NGX_PID_PATH;
+    }
+
+    if (ngx_conf_full_name(cycle, &ccf->pid) == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->newpid.len = ccf->pid.len + sizeof(NGX_NEWPID_EXT);
+
+    if (!(ccf->newpid.data = ngx_palloc(cycle->pool, ccf->newpid.len))) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memcpy(ngx_cpymem(ccf->newpid.data, ccf->pid.data, ccf->pid.len),
+               NGX_NEWPID_EXT, sizeof(NGX_NEWPID_EXT));
+
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_user(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#if (WIN32)
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"user\" is not supported, ignored");
+
+    return NGX_CONF_OK;
+
+#else
+
+    ngx_core_conf_t  *ccf = conf;
+
+    struct passwd    *pwd;
+    struct group     *grp;
+    ngx_str_t        *value;
+
+    if (ccf->user != (uid_t) NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = (ngx_str_t *) cf->args->elts;
+
+    pwd = getpwnam((const char *) value[1].data);
+    if (pwd == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           "getpwnam(%s) failed", value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->user = pwd->pw_uid;
+
+    if (cf->args->nelts == 2) {
+        return NGX_CONF_OK;
+    }
+
+    grp = getgrnam((const char *) value[2].data);
+    if (grp == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                           "getgrnam(%s) failed", value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    ccf->group = grp->gr_gid;
+
+    return NGX_CONF_OK;
+
+#endif
+}
new file mode 100644
--- /dev/null
+++ b/src/core/nginx.h
@@ -0,0 +1,17 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGINX_H_INCLUDED_
+#define _NGINX_H_INCLUDED_
+
+
+#define NGINX_VER          "nginx/0.1.0"
+
+#define NGINX_VAR          "NGINX"
+#define NGX_NEWPID_EXT     ".newbin"
+
+
+#endif /* _NGINX_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_array.c
@@ -0,0 +1,74 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_array_t *ngx_create_array(ngx_pool_t *p, ngx_uint_t n, size_t size)
+{
+    ngx_array_t *a;
+
+    ngx_test_null(a, ngx_palloc(p, sizeof(ngx_array_t)), NULL);
+
+    ngx_test_null(a->elts, ngx_palloc(p, n * size), NULL);
+
+    a->pool = p;
+    a->nelts = 0;
+    a->nalloc = n;
+    a->size = size;
+
+    return a;
+}
+
+
+void ngx_destroy_array(ngx_array_t *a)
+{
+    ngx_pool_t  *p;
+
+    p = a->pool;
+
+    if ((char *) a->elts + a->size * a->nalloc == p->last) {
+        p->last -= a->size * a->nalloc;
+    }
+
+    if ((char *) a + sizeof(ngx_array_t) == p->last) {
+        p->last = (char *) a;
+    }
+}
+
+
+void *ngx_push_array(ngx_array_t *a)
+{
+    void        *elt, *new;
+    ngx_pool_t  *p;
+
+    /* array is full */
+    if (a->nelts == a->nalloc) {
+        p = a->pool;
+
+        /* array allocation is the last in the pool */
+        if ((char *) a->elts + a->size * a->nelts == p->last
+            && (unsigned) (p->end - p->last) >= a->size)
+        {
+            p->last += a->size;
+            a->nalloc++;
+
+        /* allocate new array */
+        } else {
+            ngx_test_null(new, ngx_palloc(p, 2 * a->nalloc * a->size), NULL);
+
+            ngx_memcpy(new, a->elts, a->nalloc * a->size);
+            a->elts = new;
+            a->nalloc *= 2;
+        }
+    }
+
+    elt = (char *) a->elts + a->size * a->nelts;
+    a->nelts++;
+
+    return elt;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_array.h
@@ -0,0 +1,54 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ARRAY_H_INCLUDED_
+#define _NGX_ARRAY_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+struct ngx_array_s {
+    void        *elts;
+    ngx_uint_t   nelts;
+    size_t       size;
+    ngx_uint_t   nalloc;
+    ngx_pool_t  *pool;
+};
+
+
+ngx_array_t *ngx_create_array(ngx_pool_t *p, ngx_uint_t n, size_t size);
+void ngx_destroy_array(ngx_array_t *a);
+void *ngx_push_array(ngx_array_t *a);
+
+
+ngx_inline static ngx_int_t ngx_array_init(ngx_array_t *array, ngx_pool_t *pool,
+                                           ngx_uint_t n, size_t size)
+{
+    if (!(array->elts = ngx_palloc(pool, n * size))) {
+        return NGX_ERROR;
+    }
+
+    array->nelts = 0;
+    array->size = size;
+    array->nalloc = n;
+    array->pool = pool;
+
+    return NGX_OK;
+}
+
+
+
+#define ngx_init_array(a, p, n, s, rc)                                       \
+    ngx_test_null(a.elts, ngx_palloc(p, n * s), rc);                         \
+    a.nelts = 0; a.size = s; a.nalloc = n; a.pool = p;
+
+#define ngx_array_create  ngx_create_array
+#define ngx_array_push    ngx_push_array
+
+
+#endif /* _NGX_ARRAY_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_buf.c
@@ -0,0 +1,162 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size)
+{
+    ngx_buf_t *b;
+
+    if (!(b = ngx_calloc_buf(pool))) {
+        return NULL;
+    }
+
+    if (!(b->start = ngx_palloc(pool, size))) {
+        return NULL;
+    }
+
+    b->pos = b->start;
+    b->last = b->start;
+    b->end = b->last + size;
+    b->temporary = 1;
+
+    /*
+
+    b->file_pos = 0;
+    b->file_last = 0;
+
+    b->file = NULL;
+    b->shadow = NULL;
+
+    b->tag = 0;
+
+     */
+
+    return b;
+}
+
+
+ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs)
+{
+    u_char       *p;
+    ngx_int_t     i;
+    ngx_buf_t    *b;
+    ngx_chain_t  *chain, *cl, **ll;
+
+    if (!(p = ngx_palloc(pool, bufs->num * bufs->size))) {
+        return NULL;
+    }
+
+    ll = &chain;
+
+    for (i = 0; i < bufs->num; i++) {
+        if (!(b = ngx_calloc_buf(pool))) {
+            return NULL;
+        }
+
+        b->pos = p;
+        b->last = p;
+        b->temporary = 1;
+
+        b->start = p;
+        p += bufs->size;
+        b->end = p;
+
+        /*
+        b->file_pos = 0;
+        b->file_last = 0;
+
+        b->file = NULL;
+        b->shadow = NULL;
+        b->tag = 0;
+        */
+
+        if (!(cl = ngx_alloc_chain_link(pool))) {
+            return NULL;
+        }
+
+        cl->buf = b;
+        *ll = cl;
+        ll = &cl->next;
+    }
+
+    *ll = NULL;
+
+    return chain;
+}
+
+
+ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
+                             ngx_chain_t *in)
+{
+    ngx_chain_t  *cl, **ll;
+
+    ll = chain;
+
+    for (cl = *chain; cl; cl = cl->next) {
+        ll = &cl->next;
+    }
+
+    while (in) {
+        ngx_test_null(cl, ngx_alloc_chain_link(pool), NGX_ERROR);
+
+        cl->buf = in->buf;
+        *ll = cl;
+        ll = &cl->next;
+        in = in->next;
+    }
+
+    *ll = NULL;
+
+    return NGX_OK;
+}
+
+
+void ngx_chain_update_chains(ngx_chain_t **free, ngx_chain_t **busy,
+                             ngx_chain_t **out, ngx_buf_tag_t tag)
+{
+    ngx_chain_t  *tl;
+
+    if (*busy == NULL) {
+        *busy = *out;
+
+    } else {
+        for (tl = *busy; /* void */ ; tl = tl->next) {
+            if (tl->next == NULL) {
+                tl->next = *out;
+                break;
+            }
+        }
+    }
+
+    *out = NULL;
+
+    while (*busy) {
+        if (ngx_buf_size((*busy)->buf) != 0) {
+            break;
+        }
+
+#if (HAVE_WRITE_ZEROCOPY)
+        if ((*busy)->buf->zerocopy_busy) {
+            break;
+        }
+#endif
+
+        if ((*busy)->buf->tag != tag) {
+            *busy = (*busy)->next;
+            continue;
+        }
+
+        (*busy)->buf->pos = (*busy)->buf->last = (*busy)->buf->start;
+
+        tl = *busy;
+        *busy = (*busy)->next;
+        tl->next = *free;
+        *free = tl;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_buf.h
@@ -0,0 +1,153 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_BUF_H_INCLUDED_
+#define _NGX_BUF_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef void *            ngx_buf_tag_t;
+
+typedef struct ngx_buf_s  ngx_buf_t;
+
+struct ngx_buf_s {
+    u_char          *pos;
+    u_char          *last;
+    off_t            file_pos;
+    off_t            file_last;
+
+    int              type;
+    u_char          *start;         /* start of buffer */
+    u_char          *end;           /* end of buffer */
+    ngx_buf_tag_t    tag;
+    ngx_file_t      *file;
+    ngx_buf_t       *shadow;
+
+
+    /* the buf's content could be changed */
+    unsigned         temporary:1;
+
+    /*
+     * the buf's content is in a memory cache or in a read only memory
+     * and must not be changed
+     */
+    unsigned         memory:1;
+
+    /* the buf's content is mmap()ed and must not be changed */
+    unsigned         mmap:1;
+
+    unsigned         recycled:1;
+    unsigned         in_file:1;
+    unsigned         flush:1;
+    unsigned         last_buf:1;
+
+    unsigned         last_shadow:1;
+    unsigned         temp_file:1;
+
+    unsigned         zerocopy_busy:1;
+
+    /* STUB */ int   num;
+};
+
+
+typedef struct ngx_chain_s       ngx_chain_t;
+
+struct ngx_chain_s {
+    ngx_buf_t    *buf;
+    ngx_chain_t  *next;
+};
+
+
+typedef struct {
+    ngx_int_t    num;
+    size_t       size;
+} ngx_bufs_t;
+
+
+typedef int  (*ngx_output_chain_filter_pt)(void *ctx, ngx_chain_t *out);
+
+typedef struct {
+    ngx_buf_t                   *buf;
+    ngx_chain_t                 *in;
+    ngx_chain_t                 *free;
+    ngx_chain_t                 *busy;
+
+    unsigned                     sendfile;
+    unsigned                     need_in_memory;
+    unsigned                     need_in_temp;
+
+    ngx_pool_t                  *pool;
+    ngx_int_t                    allocated;
+    ngx_bufs_t                   bufs;
+    ngx_buf_tag_t                tag;
+
+    ngx_output_chain_filter_pt   output_filter;
+    void                        *filter_ctx;
+} ngx_output_chain_ctx_t;
+
+
+typedef struct {
+    ngx_chain_t                 *out;
+    ngx_chain_t                **last;
+    ngx_connection_t            *connection;
+    ngx_pool_t                  *pool;
+    off_t                        limit;
+} ngx_chain_writer_ctx_t;
+
+
+#define NGX_CHAIN_ERROR     (ngx_chain_t *) NGX_ERROR
+
+
+#define ngx_buf_in_memory(b)        (b->temporary || b->memory || b->mmap)
+#define ngx_buf_in_memory_only(b)   (ngx_buf_in_memory(b) && !b->in_file)
+#define ngx_buf_special(b)                                                   \
+        ((b->flush || b->last_buf) && !ngx_buf_in_memory(b) && !b->in_file)
+
+#define ngx_buf_size(b)                                                      \
+        (ngx_buf_in_memory(b) ? (size_t) (b->last - b->pos):                 \
+                                (size_t) (b->file_last - b->file_pos))
+
+ngx_buf_t *ngx_create_temp_buf(ngx_pool_t *pool, size_t size);
+ngx_chain_t *ngx_create_chain_of_bufs(ngx_pool_t *pool, ngx_bufs_t *bufs);
+
+
+#define ngx_alloc_buf(pool)  ngx_palloc(pool, sizeof(ngx_buf_t))
+#define ngx_calloc_buf(pool) ngx_pcalloc(pool, sizeof(ngx_buf_t))
+
+
+#define ngx_alloc_chain_link(pool) ngx_palloc(pool, sizeof(ngx_chain_t))
+
+
+#define ngx_alloc_link_and_set_buf(chain, b, pool, error)                    \
+            do {                                                             \
+                ngx_test_null(chain, ngx_alloc_chain_link(pool), error);     \
+                chain->buf = b;                                              \
+                chain->next = NULL;                                          \
+            } while (0);
+
+
+#define ngx_chain_add_link(chain, last, cl)                                  \
+            if (chain) {                                                     \
+                *last = cl;                                                  \
+            } else {                                                         \
+                chain = cl;                                                  \
+            }                                                                \
+            last = &cl->next
+
+
+ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in);
+ngx_int_t ngx_chain_writer(void *data, ngx_chain_t *in);
+
+ngx_int_t ngx_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain,
+                             ngx_chain_t *in);
+void ngx_chain_update_chains(ngx_chain_t **free, ngx_chain_t **busy,
+                             ngx_chain_t **out, ngx_buf_tag_t tag);
+
+
+#endif /* _NGX_BUF_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_conf_file.c
@@ -0,0 +1,1012 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static char *ngx_conf_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t  ngx_conf_commands[] = {
+
+    { ngx_string("include"),
+      NGX_ANY_CONF|NGX_CONF_TAKE1,
+      ngx_conf_include,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+ngx_module_t  ngx_conf_module = {
+    NGX_MODULE,
+    NULL,                                  /* module context */
+    ngx_conf_commands,                     /* module directives */
+    NGX_CONF_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+
+/* The ten fixed arguments */
+
+static int argument_number[] = {
+    NGX_CONF_NOARGS,
+    NGX_CONF_TAKE1,
+    NGX_CONF_TAKE2,
+    NGX_CONF_TAKE3,
+    NGX_CONF_TAKE4,
+    NGX_CONF_TAKE5,
+    NGX_CONF_TAKE6,
+    NGX_CONF_TAKE7
+};
+
+static int ngx_conf_read_token(ngx_conf_t *cf);
+
+
+char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename)
+{
+    int               m, rc, found, valid;
+    char             *rv;
+    void             *conf, **confp;
+    ngx_fd_t          fd;
+    ngx_str_t        *name;
+    ngx_conf_file_t  *prev;
+    ngx_command_t    *cmd;
+
+#if (NGX_SUPPRESS_WARN)
+    fd = NGX_INVALID_FILE;
+    prev = NULL;
+#endif
+
+    if (filename) {
+
+        /* open configuration file */
+
+        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN);
+        if (fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_open_file_n " %s failed", filename->data);
+            return NGX_CONF_ERROR;
+        }
+
+        prev = cf->conf_file;
+        if (!(cf->conf_file = ngx_palloc(cf->pool, sizeof(ngx_conf_file_t)))) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (ngx_fd_info(fd, &cf->conf_file->file.info) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          ngx_fd_info_n " %s failed", filename->data);
+        }
+
+        if (!(cf->conf_file->buffer = ngx_create_temp_buf(cf->pool, 1024))) {
+            return NGX_CONF_ERROR;
+        }
+
+        cf->conf_file->file.fd = fd;
+        cf->conf_file->file.name.len = filename->len;
+        cf->conf_file->file.name.data = filename->data;
+        cf->conf_file->file.offset = 0;
+        cf->conf_file->file.log = cf->log;;
+        cf->conf_file->line = 1;
+    }
+
+    for ( ;; ) {
+        rc = ngx_conf_read_token(cf);
+
+        /*
+         * ngx_conf_read_token() returns NGX_OK, NGX_ERROR,
+         * NGX_CONF_FILE_DONE or NGX_CONF_BLOCK_DONE
+         */
+
+#if 0
+ngx_log_debug(cf->log, "token %d" _ rc);
+#endif
+
+        if (rc == NGX_ERROR) {
+            break;
+        }
+
+        if (rc != NGX_OK) {
+            break;
+        }
+
+        if (cf->handler) {
+
+            /* custom handler, i.e. used in http "types { ... }" directive */
+
+            rv = (*cf->handler)(cf, NULL, cf->handler_conf);
+            if (rv == NGX_CONF_OK) {
+                continue;
+
+            } else if (rv == NGX_CONF_ERROR) {
+                rc = NGX_ERROR;
+                break;
+
+            } else {
+                ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                             "%s in %s:%d",
+                             rv,
+                             cf->conf_file->file.name.data,
+                             cf->conf_file->line);
+                rc = NGX_ERROR;
+                break;
+            }
+        }
+
+        name = (ngx_str_t *) cf->args->elts;
+        found = 0;
+
+        for (m = 0; rc != NGX_ERROR && !found && ngx_modules[m]; m++) {
+
+            /* look up the directive in the appropriate modules */
+
+            if (ngx_modules[m]->type != NGX_CONF_MODULE
+                && ngx_modules[m]->type != cf->module_type)
+            {
+                continue;
+            }
+
+            cmd = ngx_modules[m]->commands;
+            if (cmd == NULL) {
+                continue;
+            }
+
+            while (cmd->name.len) {
+                if (name->len == cmd->name.len
+                    && ngx_strcmp(name->data, cmd->name.data) == 0)
+                {
+
+                    found = 1;
+#if 0
+ngx_log_debug(cf->log, "command '%s'" _ cmd->name.data);
+#endif
+                    /* is the directive's location right ? */
+
+                    if ((cmd->type & cf->cmd_type) == 0) {
+                        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                      "directive \"%s\" in %s:%d "
+                                      "is not allowed here",
+                                      name->data,
+                                      cf->conf_file->file.name.data,
+                                      cf->conf_file->line);
+                        rc = NGX_ERROR;
+                        break;
+                    }
+
+                    /* is the directive's argument count right ? */
+
+                    if (cmd->type & NGX_CONF_ANY) {
+                        valid = 1;
+
+                    } else if (cmd->type & NGX_CONF_FLAG) {
+
+                        if (cf->args->nelts == 2) {
+                            valid = 1;
+                        } else {
+                            valid = 0;
+                        }
+
+                    } else if (cmd->type & NGX_CONF_1MORE) {
+
+                        if (cf->args->nelts > 1) {
+                            valid = 1;
+                        } else {
+                            valid = 0;
+                        }
+
+                    } else if (cmd->type & NGX_CONF_2MORE) {
+
+                        if (cf->args->nelts > 2) {
+                            valid = 1;
+                        } else {
+                            valid = 0;
+                        }
+
+                    } else if (cf->args->nelts <= 10
+                               && (cmd->type
+                                   & argument_number[cf->args->nelts - 1]))
+                    {
+                        valid = 1;
+
+                    } else {
+                        valid = 0;
+                    }
+
+                    if (!valid) {
+                        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                      "invalid number arguments in "
+                                      "directive \"%s\" in %s:%d",
+                                      name->data,
+                                      cf->conf_file->file.name.data,
+                                      cf->conf_file->line);
+                        rc = NGX_ERROR;
+                        break;
+                    }
+
+                    /* set up the directive's configuration context */
+
+                    conf = NULL;
+
+                    if (cmd->type & NGX_DIRECT_CONF) {
+                        conf = ((void **) cf->ctx)[ngx_modules[m]->index];
+
+                    } else if (cmd->type & NGX_MAIN_CONF) {
+                        conf = &(((void **) cf->ctx)[ngx_modules[m]->index]);
+
+                    } else if (cf->ctx) {
+                        confp = *(void **) ((char *) cf->ctx + cmd->conf);
+
+                        if (confp) {
+                            conf = confp[ngx_modules[m]->ctx_index];
+                        }
+                    }
+
+                    rv = cmd->set(cf, cmd, conf);
+
+#if 0
+ngx_log_debug(cf->log, "rv: %d" _ rv);
+#endif
+
+                    if (rv == NGX_CONF_OK) {
+                        break;
+
+                    } else if (rv == NGX_CONF_ERROR) {
+                        rc = NGX_ERROR;
+                        break;
+
+                    } else {
+                        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                      "the \"%s\" directive %s in %s:%d",
+                                      name->data, rv,
+                                      cf->conf_file->file.name.data,
+                                      cf->conf_file->line);
+
+                        rc = NGX_ERROR;
+                        break;
+                    }
+                }
+
+                cmd++;
+            }
+        }
+
+        if (!found) {
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "unknown directive \"%s\" in %s:%d",
+                          name->data,
+                          cf->conf_file->file.name.data,
+                          cf->conf_file->line);
+
+            rc = NGX_ERROR;
+            break;
+        }
+
+        if (rc == NGX_ERROR) {
+            break;
+        }
+    }
+
+    if (filename) {
+        cf->conf_file = prev;
+
+        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+                          ngx_close_file_n " %s failed",
+                          cf->conf_file->file.name.data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (rc == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static int ngx_conf_read_token(ngx_conf_t *cf)
+{
+    u_char      *start, ch, *src, *dst;
+    int          len;
+    int          found, need_space, last_space, sharp_comment;
+    int          quoted, s_quoted, d_quoted;
+    ssize_t      n;
+    ngx_str_t   *word;
+    ngx_buf_t   *b;
+
+    found = 0;
+    need_space = 0;
+    last_space = 1;
+    sharp_comment = 0;
+    quoted = s_quoted = d_quoted = 0;
+
+    cf->args->nelts = 0;
+    b = cf->conf_file->buffer;
+    start = b->pos;
+
+#if 0
+ngx_log_debug(cf->log, "TOKEN START");
+#endif
+
+    for ( ;; ) {
+
+        if (b->pos >= b->last) {
+            if (cf->conf_file->file.offset
+                                 >= ngx_file_size(&cf->conf_file->file.info)) {
+                return NGX_CONF_FILE_DONE;
+            }
+
+            if (b->pos - start) {
+                ngx_memcpy(b->start, start, b->pos - start);
+            }
+
+            n = ngx_read_file(&cf->conf_file->file,
+                              b->start + (b->pos - start),
+                              b->end - (b->start + (b->pos - start)),
+                              cf->conf_file->file.offset);
+
+            if (n == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            b->pos = b->start + (b->pos - start);
+            start = b->start;
+            b->last = b->pos + n;
+        }
+
+        ch = *b->pos++;
+
+#if 0
+ngx_log_debug(cf->log, "%d:%d:%d:%d:%d '%c'" _
+              last_space _ need_space _
+              quoted _ s_quoted _ d_quoted _ ch);
+#endif
+
+        if (ch == LF) {
+            cf->conf_file->line++;
+
+            if (sharp_comment) {
+                sharp_comment = 0;
+            }
+        }
+
+        if (sharp_comment) {
+            continue;
+        }
+
+        if (quoted) {
+            quoted = 0;
+            continue;
+        }
+
+        if (need_space) {
+            if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
+                last_space = 1;
+                need_space = 0;
+                continue;
+            }
+
+            if (ch == ';' || ch == '{') {
+                return NGX_OK;
+            }
+
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          "unexpected '%c' in %s:%d",
+                          ch, cf->conf_file->file.name.data,
+                          cf->conf_file->line);
+
+            return NGX_ERROR;
+        }
+
+        if (last_space) {
+            if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) {
+                continue;
+            }
+
+            start = b->pos - 1;
+
+            switch (ch) {
+
+            case ';':
+            case '{':
+                if (cf->args->nelts == 0) {
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "unexpected '%c' in %s:%d",
+                                  ch, cf->conf_file->file.name.data,
+                                  cf->conf_file->line);
+                    return NGX_ERROR;
+                }
+
+                return NGX_OK;
+
+            case '}':
+                if (cf->args->nelts > 0) {
+                    ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                                  "unexpected '}' in %s:%d",
+                                  cf->conf_file->file.name.data,
+                                  cf->conf_file->line);
+                    return NGX_ERROR;
+                }
+
+                return NGX_CONF_BLOCK_DONE;
+
+            case '#':
+                sharp_comment = 1;
+                continue;
+
+            case '\\':
+                quoted = 1;
+                last_space = 0;
+                continue;
+
+            case '"':
+                start++;
+                d_quoted = 1;
+                last_space = 0;
+                continue;
+
+            case '\'':
+                start++;
+                s_quoted = 1;
+                last_space = 0;
+                continue;
+
+            default:
+                last_space = 0;
+            }
+
+        } else {
+            if (ch == '\\') {
+                quoted = 1;
+                continue;
+            }
+
+            if (d_quoted) {
+                if (ch == '"') {
+                    d_quoted = 0;
+                    need_space = 1;
+                    found = 1;
+                }
+
+            } else if (s_quoted) {
+                if (ch == '\'') {
+                    s_quoted = 0;
+                    need_space = 1;
+                    found = 1;
+                }
+
+            } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF
+                       || ch == ';' || ch == '{') {
+                last_space = 1;
+                found = 1;
+            }
+
+            if (found) {
+                if (!(word = ngx_push_array(cf->args))) {
+                    return NGX_ERROR;
+                }
+
+                if (!(word->data = ngx_palloc(cf->pool, b->pos - start + 1))) {
+                    return NGX_ERROR;
+                }
+
+                for (dst = word->data, src = start, len = 0;
+                     src < b->pos - 1;
+                     len++)
+                {
+                    if (*src == '\\') {
+                        switch (src[1]) {
+                        case '"':
+                        case '\'':
+                        case '\\':
+                            src++;
+                            break;
+
+                        case 't':
+                            *dst++ = '\t';
+                            src += 2;
+                            continue;
+
+                        case 'r':
+                            *dst++ = '\r';
+                            src += 2;
+                            continue;
+
+                        case 'n':
+                            *dst++ = '\n';
+                            src += 2;
+                            continue;
+                        }
+
+                    }
+                    *dst++ = *src++;
+                }
+                *dst = '\0';
+                word->len = len;
+
+#if 0
+ngx_log_debug(cf->log, "FOUND %d:'%s'" _ word->len _ word->data);
+#endif
+
+                if (ch == ';' || ch == '{') {
+                    return NGX_OK;
+                }
+
+                found = 0;
+            }
+        }
+    }
+}
+
+
+static char *ngx_conf_include(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_str_t  *value, file;
+
+    value = cf->args->elts;
+    file = value[1];
+
+    if (ngx_conf_full_name(cf->cycle, &file) == NGX_ERROR){
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cf->log, 0, "include %s", file.data);
+
+    return ngx_conf_parse(cf, &file);
+}
+
+
+ngx_int_t ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name)
+{
+    u_char     *p;
+    ngx_str_t   old;
+
+    if (name->data[0] == '/') {
+        return NGX_OK;
+    }
+
+    old = *name;
+
+    name->len = cycle->root.len + old.len;
+
+    if (!(name->data = ngx_palloc(cycle->pool, name->len + 1))) {
+        return  NGX_ERROR;
+    }
+
+    p = ngx_cpymem(name->data, cycle->root.data, cycle->root.len),
+    ngx_cpystrn(p, old.data, old.len + 1);
+
+    return NGX_OK;
+}
+
+
+ngx_open_file_t *ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name)
+{
+    ngx_str_t         full;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_open_file_t  *file;
+
+#if (NGX_SUPPRESS_WARN)
+    full.len = 0;
+    full.data = NULL;
+#endif
+
+    if (name) {
+        full = *name;
+
+        if (ngx_conf_full_name(cycle, &full) == NGX_ERROR) {
+            return NULL;
+        }
+
+        part = &cycle->open_files.part;
+        file = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+                part = part->next;
+                file = part->elts;
+                i = 0;
+            }
+
+            if (full.len != file[i].name.len) {
+                continue;
+            }
+
+            if (ngx_strcmp(full.data, file[i].name.data) == 0) {
+                return &file[i];
+            }
+        }
+    }
+
+    if (!(file = ngx_list_push(&cycle->open_files))) {
+        return NULL;
+    }
+
+    if (name) {
+        file->fd = NGX_INVALID_FILE;
+        file->name = full;
+
+    } else {
+        file->fd = ngx_stderr_fileno;
+        file->name.len = 0;
+        file->name.data = NULL;
+    }
+
+    return file;
+}
+
+
+void ngx_conf_log_error(ngx_uint_t level, ngx_conf_t *cf, ngx_err_t err,
+                        char *fmt, ...)
+{
+    int      len;
+    char     errstr[NGX_MAX_CONF_ERRSTR];
+    va_list  args;
+
+    va_start(args, fmt);
+    len = ngx_vsnprintf(errstr, sizeof(errstr) - 1, fmt, args);
+    va_end(args);
+
+    if (err) {
+        len += ngx_snprintf(errstr + len, sizeof(errstr) - len - 1,
+                            " (%d: ", err);
+        len += ngx_strerror_r(err, errstr + len, sizeof(errstr) - len - 1);
+        errstr[len++] = ')';
+        errstr[len] = '\0';
+    }
+
+    ngx_log_error(level, cf->log, 0, "%s in %s:%d",
+                  errstr, cf->conf_file->file.name.data, cf->conf_file->line);
+}
+
+
+char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t        *value;
+    ngx_flag_t       *fp;
+    ngx_conf_post_t  *post;
+
+    fp = (ngx_flag_t *) (p + cmd->offset);
+
+    if (*fp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcasecmp(value[1].data, "on") == 0) {
+        *fp = 1;
+
+    } else if (ngx_strcasecmp(value[1].data, "off") == 0) {
+        *fp = 0;
+
+    } else {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                     "invalid value \"%s\" in \"%s\" directive, "
+                     "it must be \"on\" or \"off\"",
+                     value[1].data, cmd->name.data);
+        return NGX_CONF_ERROR;
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, fp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t        *field, *value;
+    ngx_conf_post_t  *post;
+
+    field = (ngx_str_t *) (p + cmd->offset);
+
+    if (field->data) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *field = value[1];
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, field);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t        *np;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    np = (ngx_int_t *) (p + cmd->offset);
+
+    if (*np != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    *np = ngx_atoi(value[1].data, value[1].len);
+    if (*np == NGX_ERROR) {
+        return "invalid number";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, np);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    size_t           *sp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    sp = (size_t *) (p + cmd->offset);
+    if (*sp != NGX_CONF_UNSET_SIZE) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *sp = ngx_parse_size(&value[1]);
+    if (*sp == (size_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, sp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_msec_t       *msp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    msp = (ngx_msec_t *) (p + cmd->offset);
+    if (*msp != NGX_CONF_UNSET_MSEC) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *msp = ngx_parse_time(&value[1], 0);
+    if (*msp == (ngx_msec_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (*msp == (ngx_msec_t) NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 597 hours";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, msp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_sec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    time_t           *sp;
+    ngx_str_t        *value;
+    ngx_conf_post_t  *post;
+
+
+    sp = (time_t *) (p + cmd->offset);
+    if (*sp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    *sp = ngx_parse_time(&value[1], 1);
+    if (*sp == NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (*sp == NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 68 years";
+    }
+
+    if (cmd->post) {
+        post = cmd->post;
+        return post->post_handler(cf, post, sp);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_bufs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char *p = conf;
+
+    ngx_str_t   *value;
+    ngx_bufs_t  *bufs;
+
+
+    bufs = (ngx_bufs_t *) (p + cmd->offset);
+    if (bufs->num) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    bufs->num = ngx_atoi(value[1].data, value[1].len);
+    if (bufs->num == NGX_ERROR || bufs->num == 0) {
+        return "invalid value";
+    }
+
+    bufs->size = ngx_parse_size(&value[2]);
+    if (bufs->size == (size_t) NGX_ERROR || bufs->size == 0) {
+        return "invalid value";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_set_enum_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_uint_t       *np, i;
+    ngx_str_t        *value;
+    ngx_conf_enum_t  *e;
+
+    np = (ngx_uint_t *) (p + cmd->offset);
+
+    if (*np != NGX_CONF_UNSET_UINT) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+    e = cmd->post;
+
+    for (i = 0; e[i].name.len != 0; i++) {
+        if (e[i].name.len != value[1].len
+            || ngx_strcasecmp(e[i].name.data, value[1].data) != 0)
+        {
+            continue;
+        }
+
+        *np = e[i].value;
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "invalid value \"%s\"", value[1].data);
+
+    return NGX_CONF_ERROR;
+}
+
+
+char *ngx_conf_set_bitmask_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_uint_t          *np, i, m;
+    ngx_str_t           *value;
+    ngx_conf_bitmask_t  *mask;
+
+
+    np = (ngx_uint_t *) (p + cmd->offset);
+    value = cf->args->elts;
+    mask = cmd->post;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        for (m = 0; mask[m].name.len != 0; m++) {
+
+            if (mask[m].name.len != value[i].len
+                || ngx_strcasecmp(mask[m].name.data, value[i].data) != 0)
+            {
+                continue;
+            }
+
+            if (*np & mask[m].mask) {
+                ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                                   "duplicate value \"%s\"", value[i].data);
+
+            } else {
+                *np |= mask[m].mask;
+            }
+
+            break;
+        }
+
+        if (mask[m].name.len == 0) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_unsupported(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    return "unsupported on this platform";
+}
+
+
+char *ngx_conf_check_num_bounds(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_conf_num_bounds_t  *bounds = post;
+    ngx_int_t  *np = data;
+
+    if (bounds->high == -1) {
+        if (*np >= bounds->low) {
+            return NGX_CONF_OK;
+        }
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "value must be equal or more than %d", bounds->low);
+
+        return NGX_CONF_ERROR;
+    }
+
+    if (*np >= bounds->low && *np <= bounds->high) {
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "value must be between %d and %d",
+                       bounds->low, bounds->high);
+
+    return NGX_CONF_ERROR;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_conf_file.h
@@ -0,0 +1,295 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CONF_FILE_H_INCLUDED_
+#define _NGX_HTTP_CONF_FILE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ *        AAAA  number of agruments
+ *      FF      command flags
+ *    TT        command type, i.e. HTTP "location" or "server" command
+ */
+
+#define NGX_CONF_NOARGS      0x00000001
+#define NGX_CONF_TAKE1       0x00000002
+#define NGX_CONF_TAKE2       0x00000004
+#define NGX_CONF_TAKE3       0x00000008
+#define NGX_CONF_TAKE4       0x00000010
+#define NGX_CONF_TAKE5       0x00000020
+#define NGX_CONF_TAKE6       0x00000040
+#define NGX_CONF_TAKE7       0x00000080
+
+#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2)
+#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3)
+
+#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3)
+
+#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3   \
+                              |NGX_CONF_TAKE4)
+
+#define NGX_CONF_ARGS_NUMBER 0x000000ff
+#define NGX_CONF_BLOCK       0x00000100
+#define NGX_CONF_FLAG        0x00000200
+#define NGX_CONF_ANY         0x00000400
+#define NGX_CONF_1MORE       0x00000800
+#define NGX_CONF_2MORE       0x00001000
+
+#define NGX_DIRECT_CONF      0x00010000
+
+#define NGX_MAIN_CONF        0x01000000
+#define NGX_ANY_CONF         0x0F000000
+
+
+
+#define NGX_CONF_UNSET       -1
+#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
+#define NGX_CONF_UNSET_PTR   (void *) -1
+#define NGX_CONF_UNSET_SIZE  (size_t) -1
+#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1
+
+
+#define NGX_CONF_OK          NULL
+#define NGX_CONF_ERROR       (void *) -1
+
+#define NGX_CONF_BLOCK_DONE  1
+#define NGX_CONF_FILE_DONE   2
+
+#define NGX_MODULE           0, 0
+
+#define NGX_CORE_MODULE      0x45524F43  /* "CORE" */
+#define NGX_CONF_MODULE      0x464E4F43  /* "CONF" */
+
+
+#define NGX_MAX_CONF_ERRSTR  256
+
+
+struct ngx_command_s {
+    ngx_str_t     name;
+    int           type;
+    char       *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+    int           conf;
+    int           offset;
+    void         *post;
+};
+
+#define ngx_null_command   { ngx_null_string, 0, NULL, 0, 0, NULL }
+
+
+struct ngx_open_file_s {
+    ngx_fd_t   fd;
+    ngx_str_t  name;
+#if 0
+    /* e.g. append mode, error_log */
+    int        flags;
+    /* e.g. reopen db file */
+    int      (*handler)(void *data, ngx_open_file_t *file);
+    void      *data;
+#endif
+};
+
+
+struct ngx_module_s {
+    ngx_uint_t       ctx_index;
+    ngx_uint_t       index;
+    void            *ctx;
+    ngx_command_t   *commands;
+    ngx_uint_t       type;
+    ngx_int_t      (*init_module)(ngx_cycle_t *cycle);
+    ngx_int_t      (*init_process)(ngx_cycle_t *cycle);
+#if 0
+    ngx_int_t      (*init_thread)(ngx_cycle_t *cycle);
+#endif
+};
+
+
+typedef struct {
+    ngx_str_t       name;
+    void         *(*create_conf)(ngx_cycle_t *cycle);
+    char         *(*init_conf)(ngx_cycle_t *cycle, void *conf);
+} ngx_core_module_t; 
+
+
+typedef struct {
+    ngx_file_t   file;
+    ngx_buf_t   *buffer;
+    ngx_uint_t   line;
+} ngx_conf_file_t;
+
+
+typedef char *(*ngx_conf_handler_pt)(ngx_conf_t *cf,
+                                     ngx_command_t *dummy, void *conf);
+
+
+struct ngx_conf_s {
+    char                 *name;
+    ngx_array_t          *args;
+
+    ngx_cycle_t          *cycle;
+    ngx_pool_t           *pool;
+    ngx_conf_file_t      *conf_file;
+    ngx_log_t            *log;
+
+    void                 *ctx;
+    ngx_uint_t            module_type;
+    ngx_uint_t            cmd_type;
+
+    ngx_conf_handler_pt   handler;
+    char                 *handler_conf;
+};
+
+
+typedef char *(*ngx_conf_post_handler_pt) (ngx_conf_t *cf,
+                                           void *data, void *conf);
+
+typedef struct {
+    ngx_conf_post_handler_pt  post_handler;
+} ngx_conf_post_t;
+
+
+typedef struct {
+    ngx_conf_post_handler_pt  post_handler;
+    int                       low;
+    int                       high;
+} ngx_conf_num_bounds_t;
+
+
+typedef struct {
+    ngx_str_t   name;
+    ngx_uint_t  value;
+} ngx_conf_enum_t;
+
+
+#define NGX_CONF_BITMASK_SET  1
+
+typedef struct {
+    ngx_str_t   name;
+    ngx_uint_t  mask;
+} ngx_conf_bitmask_t;
+
+
+char *ngx_conf_check_num_bounds(ngx_conf_t *cf, void *post, void *data);
+
+
+#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index]
+
+
+
+#define ngx_conf_init_value(conf, default)                                   \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_ptr_value(conf, default)                               \
+    if (conf == NGX_CONF_UNSET_PTR) {                                        \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_unsigned_value(conf, default)                          \
+    if (conf == (unsigned) NGX_CONF_UNSET) {                                 \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_size_value(conf, default)                              \
+    if (conf == NGX_CONF_UNSET_SIZE) {                                       \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_init_msec_value(conf, default)                              \
+    if (conf == NGX_CONF_UNSET_MSEC) {                                       \
+        conf = default;                                                      \
+    }
+
+#define ngx_conf_merge_value(conf, prev, default)                            \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
+    }
+
+#define ngx_conf_merge_ptr_value(conf, prev, default)                        \
+    if (conf == NULL) {                                                      \
+        conf = (prev == NULL) ? default : prev;                              \
+    }
+
+#define ngx_conf_merge_unsigned_value(conf, prev, default)                   \
+    if (conf == NGX_CONF_UNSET_UINT) {                                       \
+        conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_msec_value(conf, prev, default)                       \
+    if (conf == NGX_CONF_UNSET_MSEC) {                                       \
+        conf = (prev == NGX_CONF_UNSET_MSEC) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_sec_value(conf, prev, default)                        \
+    if (conf == NGX_CONF_UNSET) {                                            \
+        conf = (prev == NGX_CONF_UNSET) ? default : prev;                    \
+    }
+
+#define ngx_conf_merge_size_value(conf, prev, default)                       \
+    if (conf == NGX_CONF_UNSET_SIZE) {                                       \
+        conf = (prev == NGX_CONF_UNSET_SIZE) ? default : prev;               \
+    }
+
+#define ngx_conf_merge_str_value(conf, prev, default)                        \
+    if (conf.len == 0) {                                                     \
+        if (prev.len) {                                                      \
+            conf.len = prev.len;                                             \
+            conf.data = prev.data;                                           \
+        } else {                                                             \
+            conf.len = sizeof(default) - 1;                                  \
+            conf.data = (u_char *) default;                                  \
+        }                                                                    \
+    }
+
+#define ngx_conf_merge_bufs_value(conf, prev, default_num, default_size)     \
+    if (conf.num == 0) {                                                     \
+        if (prev.num) {                                                      \
+            conf.num = prev.num;                                             \
+            conf.size = prev.size;                                           \
+        } else {                                                             \
+            conf.num = default_num;                                          \
+            conf.size = default_size;                                        \
+        }                                                                    \
+    }
+
+#define ngx_conf_merge_bitmask_value(conf, prev, default)                    \
+    if (conf == 0) {                                                         \
+        conf = (prev == 0) ? default : prev;                                 \
+    }
+
+
+#define addressof(addr)  ((int) &addr)
+
+
+char *ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename);
+
+
+ngx_int_t ngx_conf_full_name(ngx_cycle_t *cycle, ngx_str_t *name);
+ngx_open_file_t *ngx_conf_open_file(ngx_cycle_t *cycle, ngx_str_t *name);
+void ngx_conf_log_error(ngx_uint_t level, ngx_conf_t *cf, ngx_err_t err,
+                        char *fmt, ...);
+
+
+char *ngx_conf_set_flag_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_num_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_size_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_sec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_bufs_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_enum_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+char *ngx_conf_set_bitmask_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+extern ngx_uint_t     ngx_max_module;
+extern ngx_module_t  *ngx_modules[];
+
+
+#endif /* _NGX_HTTP_CONF_FILE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_config.h
@@ -0,0 +1,131 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CONFIG_H_INCLUDED_
+#define _NGX_CONFIG_H_INCLUDED_
+
+
+#if defined __DragonFly__ && !defined __FreeBSD__
+#define __FreeBSD__        4
+#define __FreeBSD_version  480101
+#endif
+
+
+#if defined __FreeBSD__
+#include <ngx_freebsd_config.h>
+
+
+#elif defined __linux__
+#include <ngx_linux_config.h>
+
+
+       /* Solaris */
+#elif defined sun && (defined __svr4__ || defined __SVR4)
+#include <ngx_solaris_config.h>
+
+
+#elif defined _WIN32
+#include <ngx_win32_config.h>
+
+
+#else /* posix */
+#include <ngx_posix_config.h>
+
+#endif
+
+
+#if !(WIN32)
+
+#define ngx_signal_helper(n)     SIG##n
+#define ngx_signal_value(n)      ngx_signal_helper(n)
+
+/* TODO: #ifndef */
+#define NGX_SHUTDOWN_SIGNAL      QUIT
+#define NGX_TERMINATE_SIGNAL     TERM
+#define NGX_NOACCEPT_SIGNAL      WINCH
+#define NGX_RECONFIGURE_SIGNAL   HUP
+
+#if (NGX_LINUXTHREADS)
+#define NGX_REOPEN_SIGNAL        INFO
+#define NGX_CHANGEBIN_SIGNAL     XCPU
+#else
+#define NGX_REOPEN_SIGNAL        USR1
+#define NGX_CHANGEBIN_SIGNAL     USR2
+#endif
+
+#endif
+
+
+
+/* TODO: platform specific: array[NGX_INVALID_ARRAY_INDEX] must cause SIGSEGV */
+#define NGX_INVALID_ARRAY_INDEX 0x80000000
+
+
+#if 1
+/* STUB: autoconf */
+typedef int                ngx_int_t;
+typedef u_int              ngx_uint_t;
+typedef int                ngx_flag_t;
+#define NGX_INT_T_LEN      sizeof("-2147483648") - 1
+#define NGX_INT_T_FMT      "d"
+#define NGX_UINT_T_FMT     "u"
+
+#else
+
+typedef long               ngx_int_t;
+typedef u_long             ngx_uint_t;
+typedef long               ngx_flag_t;
+#define NGX_INT_T_LEN      sizeof("-9223372036854775808") - 1
+#define NGX_INT_T_FMT      "lld"
+#define NGX_UINT_T_FMT     "llu"
+
+#endif
+
+/* TODO: auto */
+#define NGX_INT32_LEN      sizeof("-2147483648") - 1
+#define NGX_INT64_LEN      sizeof("-9223372036854775808") - 1
+#define NGX_OFF_T_LEN      sizeof("-9223372036854775808") - 1
+
+
+#if (SOLARIS)
+
+/* TODO: auto_conf */
+#define NGX_ALIGN       (_MAX_ALIGNMENT - 1)         /* platform word */
+#define NGX_ALIGN_CAST  (unsigned long)              /* size of the pointer */
+
+#else
+
+/* TODO: auto_conf */
+#define NGX_ALIGN       (sizeof(unsigned long) - 1)  /* platform word */
+#define NGX_ALIGN_CAST  (unsigned long)              /* size of the pointer */
+
+#endif
+
+#define ngx_align(p)    (char *) ((NGX_ALIGN_CAST p + NGX_ALIGN) & ~NGX_ALIGN)
+
+
+/* TODO: auto_conf: ngx_inline   inline __inline __inline__ */
+#ifndef ngx_inline
+#define ngx_inline   inline
+#endif
+
+#define NGX_ACCEPT_THRESHOLD   100
+
+#ifndef INADDR_NONE  /* Solaris */
+#define INADDR_NONE  ((unsigned int) -1)
+#endif
+
+#ifndef INET_ADDRSTRLEN  /* Win32 */
+#define INET_ADDRSTRLEN  16
+#endif
+
+#define NGX_MAXHOSTNAMELEN 64
+/*
+#define NGX_MAXHOSTNAMELEN MAXHOSTNAMELEN
+*/
+
+
+#endif /* _NGX_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_connection.c
@@ -0,0 +1,453 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_os_io_t  ngx_io;
+
+
+ngx_listening_t *ngx_listening_inet_stream_socket(ngx_conf_t *cf,
+                                                 in_addr_t addr,
+                                                 in_port_t port)
+{
+    size_t               len;
+    ngx_listening_t     *ls;
+    struct sockaddr_in  *addr_in;
+
+    if (!(ls = ngx_array_push(&cf->cycle->listening))) {
+        return NULL;
+    }
+
+    ngx_memzero(ls, sizeof(ngx_listening_t));
+
+    if (!(addr_in = ngx_pcalloc(cf->pool, sizeof(struct sockaddr_in)))) {
+        return NULL;
+    }
+
+#if (HAVE_SIN_LEN)
+    addr_in->sin_len = sizeof(struct sockaddr_in);
+#endif
+    addr_in->sin_family = AF_INET;
+    addr_in->sin_addr.s_addr = addr;
+    addr_in->sin_port = htons(port);
+
+    if (!(ls->addr_text.data = ngx_palloc(cf->pool, INET_ADDRSTRLEN + 6))) {
+        return NULL;
+    }
+
+    len = ngx_inet_ntop(AF_INET, &addr, ls->addr_text.data, INET_ADDRSTRLEN);
+    ls->addr_text.len = ngx_snprintf((char *) ls->addr_text.data + len,
+                                     6, ":%d", port);
+
+    ls->fd = (ngx_socket_t) -1;
+    ls->family = AF_INET;
+    ls->type = SOCK_STREAM;
+    ls->protocol = IPPROTO_IP;
+#if (WIN32)
+    ls->flags = WSA_FLAG_OVERLAPPED;
+#endif
+    ls->sockaddr = (struct sockaddr *) addr_in;
+    ls->socklen = sizeof(struct sockaddr_in);
+    ls->addr = offsetof(struct sockaddr_in, sin_addr);
+    ls->addr_text_max_len = INET_ADDRSTRLEN;
+
+    return ls;
+}
+
+
+ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle)
+{
+    ngx_uint_t           i;
+    ngx_listening_t     *ls;
+    struct sockaddr_in  *addr_in;
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        /* AF_INET only */
+
+        ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(struct sockaddr_in));
+        if (ls[i].sockaddr == NULL) {
+            return NGX_ERROR;
+        }
+
+        ls[i].socklen = sizeof(struct sockaddr_in);
+        if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                          "getsockname() of the inherited "
+                          "socket #%d failed", ls[i].fd);
+            ls[i].ignore = 1;
+            continue;
+        }
+
+        addr_in = (struct sockaddr_in *) ls[i].sockaddr;
+
+        if (addr_in->sin_family != AF_INET) {
+            ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno,
+                          "the inherited socket #%d has "
+                          "unsupported family", ls[i].fd);
+            ls[i].ignore = 1;
+            continue;
+        }
+        ls[i].addr_text_max_len = INET_ADDRSTRLEN;
+
+        ls[i].addr_text.data = ngx_palloc(cycle->pool, ls[i].addr_text_max_len);
+        if (ls[i].addr_text.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ls[i].family = addr_in->sin_family;
+        ls[i].addr_text.len = ngx_sock_ntop(ls[i].family, ls[i].sockaddr,
+                                            ls[i].addr_text.data,
+                                            ls[i].addr_text_max_len);
+        if (ls[i].addr_text.len == 0) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle)
+{
+    ngx_uint_t        tries, failed, reuseaddr, i;
+    ngx_err_t         err;
+    ngx_log_t        *log;
+    ngx_socket_t      s;
+    ngx_listening_t  *ls;
+
+    reuseaddr = 1;
+#if (NGX_SUPPRESS_WARN)
+    failed = 0;
+#endif
+
+    log = cycle->log;
+
+    /* TODO: tries configurable */
+
+    for (tries = /* STUB */ 5; tries; tries--) {
+        failed = 0;
+
+        /* for each listening socket */
+
+        ls = cycle->listening.elts;
+        for (i = 0; i < cycle->listening.nelts; i++) {
+
+            if (ls[i].ignore) {
+                continue;
+            }
+
+            if (ls[i].fd != -1) {
+                continue;
+            }
+
+            if (ls[i].inherited) {
+
+                /* TODO: close on exit */
+                /* TODO: nonblocking */
+                /* TODO: deferred accept */
+
+                continue;
+            }
+
+            s = ngx_socket(ls[i].family, ls[i].type, ls[i].protocol,
+                           ls[i].flags);
+
+            if (s == -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                              ngx_socket_n " %s failed", ls[i].addr_text.data);
+                return NGX_ERROR;
+            }
+
+#if (WIN32)
+            /*
+             * Winsock assignes a socket number divisible by 4
+             * so to find a connection we divide a socket number by 4.
+             */
+
+            if (s % 4) {
+                ngx_log_error(NGX_LOG_EMERG, ls->log, 0,
+                              ngx_socket_n " created socket %d", s);
+                return NGX_ERROR;
+            }
+#endif
+
+            if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                           (const void *) &reuseaddr, sizeof(int)) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                              "setsockopt(SO_REUSEADDR) %s failed",
+                              ls[i].addr_text.data);
+                return NGX_ERROR;
+            }
+
+            /* TODO: close on exit */
+
+            if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {
+                if (ngx_nonblocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  ngx_nonblocking_n " %s failed",
+                                  ls[i].addr_text.data);
+                    return NGX_ERROR;
+                }
+            }
+
+#if 0
+            if (ls[i].nonblocking) {
+                if (ngx_nonblocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  ngx_nonblocking_n " %s failed",
+                                  ls[i].addr_text.data);
+                    return NGX_ERROR;
+                }
+            }
+#endif
+
+            if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) {
+                err = ngx_socket_errno;
+                ngx_log_error(NGX_LOG_EMERG, log, err,
+                              "bind() to %s failed", ls[i].addr_text.data);
+
+                if (err != NGX_EADDRINUSE)
+                    return NGX_ERROR;
+
+                if (ngx_close_socket(s) == -1)
+                    ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                                  ngx_close_socket_n " %s failed",
+                                  ls[i].addr_text.data);
+
+                failed = 1;
+                continue;
+            }
+
+            if (listen(s, ls[i].backlog) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                              "listen() to %s failed", ls[i].addr_text.data);
+                return NGX_ERROR;
+            }
+
+            /* TODO: deferred accept */
+
+            ls[i].fd = s;
+        }
+
+        if (!failed)
+            break;
+
+        /* TODO: delay configurable */
+
+        ngx_log_error(NGX_LOG_NOTICE, log, 0,
+                      "try again to bind() after 500ms");
+        ngx_msleep(500);
+    }
+
+    if (failed) {
+        ngx_log_error(NGX_LOG_EMERG, log, 0, "still can not bind()");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void ngx_close_listening_sockets(ngx_cycle_t *cycle)
+{
+    ngx_uint_t        i;
+    ngx_socket_t      fd;
+    ngx_listening_t  *ls;
+
+    if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+        return;
+    }
+
+    ngx_accept_mutex_held = 0;
+    ngx_accept_mutex = NULL;
+
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        fd = ls[i].fd;
+
+#if (WIN32)
+        /*
+         * Winsock assignes a socket number divisible by 4
+         * so to find a connection we divide a socket number by 4.
+         */
+
+        fd /= 4;
+#endif
+
+        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+            if (cycle->connections[fd].read->active) {
+                ngx_del_conn(&cycle->connections[fd], NGX_CLOSE_EVENT);
+            }
+
+        } else {
+            if (cycle->read_events[fd].active) {
+                ngx_del_event(&cycle->read_events[fd],
+                              NGX_READ_EVENT, NGX_CLOSE_EVENT);
+            }
+        }
+
+        if (ngx_close_socket(ls[i].fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
+                          ngx_close_socket_n " %s failed",
+                          ls[i].addr_text.data);
+        }
+
+        cycle->connections[fd].fd = (ngx_socket_t) -1;
+    }
+}
+
+
+void ngx_close_connection(ngx_connection_t *c)
+{
+    ngx_socket_t  fd;
+
+    if (c->pool == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
+        return;
+    }
+    
+#if (NGX_OPENSSL)
+
+    if (c->ssl) {
+        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+            c->read->event_handler = ngx_ssl_close_handler;
+            c->write->event_handler = ngx_ssl_close_handler;
+            return;
+        }
+    }
+    
+#endif
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+    
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+    
+    if (ngx_del_conn) {
+        ngx_del_conn(c, NGX_CLOSE_EVENT);
+
+    } else {
+        if (c->read->active || c->read->disabled) {
+            ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+        }
+
+        if (c->write->active || c->write->disabled) {
+            ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
+        }
+    }
+
+#if (NGX_THREADS)
+
+    /*
+     * we have to clean the connection information before the closing
+     * because another thread may reopen the same file descriptor
+     * before we clean the connection
+     */
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_OK) {
+
+        if (c->read->prev) {
+            ngx_delete_posted_event(c->read);
+        }
+
+        if (c->write->prev) {
+            ngx_delete_posted_event(c->write);
+        }
+
+        c->read->closed = 1;
+        c->write->closed = 1;
+
+        if (c->single_connection) {
+            ngx_unlock(&c->lock);
+            c->read->locked = 0;
+            c->write->locked = 0;
+        }
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+#else
+
+    if (c->read->prev) {
+        ngx_delete_posted_event(c->read);
+    }
+
+    if (c->write->prev) {
+        ngx_delete_posted_event(c->write);
+    }
+
+    c->read->closed = 1;
+    c->write->closed = 1;
+
+#endif
+
+    fd = c->fd;
+    c->fd = (ngx_socket_t) -1;
+    c->data = NULL;
+
+    ngx_destroy_pool(c->pool);
+
+    if (ngx_close_socket(fd) == -1) {
+
+        /* we use ngx_cycle->log because c->log was in c->pool */
+
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+}
+
+
+
+ngx_int_t ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text)
+{
+    ngx_uint_t  level;
+
+    if (err == NGX_ECONNRESET
+        && c->log_error == NGX_ERROR_IGNORE_ECONNRESET)
+    {
+        return 0;
+    }
+
+    if (err == NGX_ECONNRESET
+#if !(WIN32)
+        || err == NGX_EPIPE
+#endif
+        || err == NGX_ENOTCONN
+        || err == NGX_ECONNREFUSED
+        || err == NGX_EHOSTUNREACH)
+    {
+
+        switch (c->log_error) {
+
+        case NGX_ERROR_IGNORE_ECONNRESET:
+        case NGX_ERROR_INFO:
+            level = NGX_LOG_INFO;
+            break;
+
+        case NGX_ERROR_ERR:
+            level = NGX_LOG_ERR;
+            break;
+
+        default:
+            level = NGX_LOG_CRIT;
+        }
+
+    } else {
+        level = NGX_LOG_CRIT;
+    }
+
+    ngx_log_error(level, c->log, err, text);
+
+    return NGX_ERROR;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_connection.h
@@ -0,0 +1,152 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CONNECTION_H_INCLUDED_
+#define _NGX_CONNECTION_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    ngx_socket_t      fd;
+
+    struct sockaddr  *sockaddr;
+    socklen_t         socklen;    /* size of sockaddr */
+    int               addr;       /* offset to address in sockaddr */
+    int               addr_text_max_len;
+    ngx_str_t         addr_text;
+
+    int               family;
+    int               type;
+    int               protocol;
+    int               flags;      /* Winsock2 flags */
+
+    void            (*handler)(ngx_connection_t *c); /* handler of accepted
+                                                        connection */
+    void             *ctx;        /* ngx_http_conf_ctx_t, for example */
+    void             *servers;    /* array of ngx_http_in_addr_t, for example */
+
+    ngx_log_t        *log;
+    int               backlog;
+
+    size_t            pool_size;
+    size_t            post_accept_buffer_size; /* should be here because
+                                                  of the AcceptEx() preread */
+    time_t            post_accept_timeout;     /* should be here because
+                                                  of the deferred accept */
+
+    unsigned          new:1;
+    unsigned          remain:1;
+    unsigned          ignore:1;
+
+    unsigned          bound:1;       /* already bound */
+    unsigned          inherited:1;   /* inherited from previous process */
+    unsigned          nonblocking_accept:1;
+    unsigned          nonblocking:1;
+#if 0
+    unsigned          overlapped:1;  /* Winsock2 overlapped */
+#endif
+    unsigned          shared:1;    /* shared between threads or processes */
+#if (HAVE_DEFERRED_ACCEPT)
+    unsigned          deferred_accept:1;
+#endif
+
+    unsigned          addr_ntop:1;
+} ngx_listening_t;
+
+
+typedef enum {
+     NGX_ERROR_CRIT = 0,
+     NGX_ERROR_ERR,
+     NGX_ERROR_INFO,
+     NGX_ERROR_IGNORE_ECONNRESET
+} ngx_connection_log_error_e;
+
+
+typedef enum {
+     NGX_TCP_NOPUSH_DISABLED = -1,
+     NGX_TCP_NOPUSH_UNSET = 0,
+     NGX_TCP_NOPUSH_SET
+} ngx_connection_tcp_nopush_e;
+
+
+struct ngx_connection_s {
+    void               *data;
+    ngx_event_t        *read;
+    ngx_event_t        *write;
+
+    ngx_socket_t        fd;
+
+    ngx_recv_pt         recv;
+    ngx_send_chain_pt   send_chain;
+
+    ngx_listening_t    *listening;
+
+    off_t               sent;
+
+    void               *ctx;
+    void               *servers;
+
+
+    ngx_log_t          *log;
+
+    ngx_pool_t         *pool;
+
+    struct sockaddr    *sockaddr;
+    socklen_t           socklen;
+    ngx_str_t           addr_text;
+
+#if (NGX_OPENSSL)
+    ngx_ssl_t          *ssl;
+#endif
+
+#if (HAVE_IOCP)
+    struct sockaddr    *local_sockaddr;
+    socklen_t           local_socklen;
+#endif
+
+    ngx_buf_t          *buffer;
+
+    ngx_uint_t          number;
+
+    unsigned            log_error:2;  /* ngx_connection_log_error_e */
+
+    unsigned            buffered:1;
+    unsigned            single_connection:1;
+    unsigned            unexpected_eof:1;
+    unsigned            timedout:1;
+    signed              tcp_nopush:2;
+#if (HAVE_IOCP)
+    unsigned            accept_context_updated:1;
+#endif
+
+#if (NGX_THREADS)
+    ngx_atomic_t        lock;
+#endif
+};
+
+
+#ifndef ngx_ssl_set_nosendshut
+#define ngx_ssl_set_nosendshut(ssl)
+#endif
+
+
+ngx_listening_t *ngx_listening_inet_stream_socket(ngx_conf_t *cf,
+                                                 in_addr_t addr,
+                                                 in_port_t port);
+ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle);
+ngx_int_t ngx_open_listening_sockets(ngx_cycle_t *cycle);
+void ngx_close_listening_sockets(ngx_cycle_t *cycle);
+void ngx_close_connection(ngx_connection_t *c);
+ngx_int_t ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text);
+
+
+extern ngx_os_io_t  ngx_io;
+
+
+#endif /* _NGX_CONNECTION_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_core.h
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CORE_H_INCLUDED_
+#define _NGX_CORE_H_INCLUDED_
+
+
+typedef struct ngx_module_s      ngx_module_t;
+typedef struct ngx_conf_s        ngx_conf_t;
+typedef struct ngx_cycle_s       ngx_cycle_t;
+typedef struct ngx_pool_s        ngx_pool_t;
+typedef struct ngx_log_s         ngx_log_t;
+typedef struct ngx_array_s       ngx_array_t;
+typedef struct ngx_open_file_s   ngx_open_file_t;
+typedef struct ngx_command_s     ngx_command_t;
+typedef struct ngx_file_s        ngx_file_t;
+typedef struct ngx_event_s       ngx_event_t;
+typedef struct ngx_connection_s  ngx_connection_t;
+
+typedef void (*ngx_event_handler_pt)(ngx_event_t *ev);
+
+
+
+#define  NGX_OK          0
+#define  NGX_ERROR      -1
+#define  NGX_AGAIN      -2
+#define  NGX_BUSY       -3
+#define  NGX_DONE       -4
+#define  NGX_DECLINED   -5
+#define  NGX_ABORT      -6
+
+
+#include <ngx_atomic.h>
+#include <ngx_time.h>
+#include <ngx_socket.h>
+#include <ngx_errno.h>
+#include <ngx_types.h>
+#include <ngx_shared.h>
+#include <ngx_process.h>
+#include <ngx_thread.h>
+#include <ngx_user.h>
+#include <ngx_string.h>
+#include <ngx_parse.h>
+#include <ngx_log.h>
+#include <ngx_alloc.h>
+#include <ngx_palloc.h>
+#include <ngx_buf.h>
+#include <ngx_array.h>
+#include <ngx_list.h>
+#include <ngx_table.h>
+#include <ngx_file.h>
+#include <ngx_files.h>
+#include <ngx_crc.h>
+#if (HAVE_PCRE)
+#include <ngx_regex.h>
+#endif
+#include <ngx_rbtree.h>
+#include <ngx_times.h>
+#include <ngx_inet.h>
+#include <ngx_cycle.h>
+#include <ngx_process_cycle.h>
+#include <ngx_conf_file.h>
+#include <ngx_os.h>
+#if (NGX_OPENSSL)
+#include <ngx_event_openssl.h>
+#endif
+#include <ngx_connection.h>
+
+
+#define LF     (u_char) 10
+#define CR     (u_char) 13
+#define CRLF   "\x0d\x0a"
+
+
+#endif /* _NGX_CORE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_crc.h
@@ -0,0 +1,33 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CRC_H_INCLUDED_
+#define _NGX_CRC_H_INCLUDED_
+
+
+/* 32-bit crc16 */
+
+ngx_inline static uint32_t ngx_crc(char *data, size_t len)
+{
+    uint32_t  sum;
+
+    for (sum = 0; len; len--) {
+
+        /*
+         * gcc 2.95.2 x86 and icc 7.1.006 compile that operator
+         *                                into the single "rol" opcode.
+         * msvc 6.0sp2 compiles it into four opcodes.
+         */
+        sum = sum >> 1 | sum << 31;
+
+        sum += *data++;
+    }
+
+    return sum;
+}
+
+
+#endif /* _NGX_CRC_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_cycle.c
@@ -0,0 +1,767 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static void ngx_clean_old_cycles(ngx_event_t *ev);
+
+
+volatile ngx_cycle_t  *ngx_cycle;
+ngx_array_t            ngx_old_cycles;
+
+static ngx_pool_t     *ngx_temp_pool;
+static ngx_event_t     ngx_cleaner_event;
+
+ngx_uint_t             ngx_test_config;
+
+#if (NGX_THREADS)
+ngx_tls_key_t          ngx_core_tls_key;
+#endif
+
+
+/* STUB NAME */
+static ngx_connection_t  dumb;
+/* STUB */
+
+#ifdef NGX_ERROR_LOG_PATH
+static ngx_str_t  error_log = ngx_string(NGX_ERROR_LOG_PATH);
+#else
+static ngx_str_t  error_log = ngx_null_string;
+#endif
+
+
+ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle)
+{
+    void               *rv;
+    ngx_uint_t          i, n, failed;
+    ngx_log_t          *log;
+    ngx_conf_t          conf;
+    ngx_pool_t         *pool;
+    ngx_cycle_t        *cycle, **old;
+    ngx_socket_t        fd;
+    ngx_list_part_t    *part;
+    ngx_open_file_t    *file;
+    ngx_listening_t    *ls, *nls;
+    ngx_core_module_t  *module;
+
+    log = old_cycle->log;
+
+    if (!(pool = ngx_create_pool(16 * 1024, log))) {
+        return NULL;
+    }
+    pool->log = log;
+
+    if (!(cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)))) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+    cycle->pool = pool;
+    cycle->log = log;
+    cycle->old_cycle = old_cycle;
+    cycle->conf_file = old_cycle->conf_file;
+    cycle->root.len = sizeof(NGX_PREFIX) - 1;
+    cycle->root.data = (u_char *) NGX_PREFIX;
+
+
+    n = old_cycle->pathes.nelts ? old_cycle->pathes.nelts : 10;
+    if (!(cycle->pathes.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *)))) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+    cycle->pathes.nelts = 0;
+    cycle->pathes.size = sizeof(ngx_path_t *);
+    cycle->pathes.nalloc = n;
+    cycle->pathes.pool = pool;
+
+
+    if (old_cycle->open_files.part.nelts) {
+        n = old_cycle->open_files.part.nelts;
+        for (part = old_cycle->open_files.part.next; part; part = part->next) {
+            n += part->nelts;
+        }
+
+    } else {
+        n = 20;
+    }
+
+    if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t))
+                                                                  == NGX_ERROR)
+    {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    if (!(cycle->new_log = ngx_log_create_errlog(cycle, NULL))) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    cycle->new_log->file->name = error_log;
+
+
+    n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;
+    cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));
+    if (cycle->listening.elts == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+    cycle->listening.nelts = 0;
+    cycle->listening.size = sizeof(ngx_listening_t);
+    cycle->listening.nalloc = n;
+    cycle->listening.pool = pool;
+
+
+    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
+    if (cycle->conf_ctx == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[i]->ctx;
+
+        if (module->create_conf) {
+            rv = module->create_conf(cycle);
+            if (rv == NGX_CONF_ERROR) {
+                ngx_destroy_pool(pool);
+                return NULL;
+            }
+            cycle->conf_ctx[ngx_modules[i]->index] = rv;
+        }
+    }
+
+
+    ngx_memzero(&conf, sizeof(ngx_conf_t));
+    /* STUB: init array ? */
+    conf.args = ngx_create_array(pool, 10, sizeof(ngx_str_t));
+    if (conf.args == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    conf.ctx = cycle->conf_ctx;
+    conf.cycle = cycle;
+    conf.pool = pool;
+    conf.log = log;
+    conf.module_type = NGX_CORE_MODULE;
+    conf.cmd_type = NGX_MAIN_CONF;
+
+
+    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    if (ngx_test_config) {
+        ngx_log_error(NGX_LOG_INFO, log, 0,
+                      "the configuration file %s syntax is ok",
+                      cycle->conf_file.data);
+    }
+
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[i]->ctx;
+
+        if (module->init_conf) {
+            if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
+                                                              == NGX_CONF_ERROR)
+            {
+                ngx_destroy_pool(pool);
+                return NULL;
+            }
+        }
+    }
+
+
+    failed = 0;
+
+
+#if !(WIN32)
+    if (ngx_create_pidfile(cycle, old_cycle) == NGX_ERROR) {
+        failed = 1;
+    }
+#endif
+
+
+    if (!failed) {
+
+        part = &cycle->open_files.part;
+        file = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+                part = part->next;
+                file = part->elts;
+                i = 0;
+            }
+
+            if (file[i].name.data == NULL) {
+                continue;
+            }
+
+            file[i].fd = ngx_open_file(file[i].name.data,
+                                       NGX_FILE_RDWR,
+                                       NGX_FILE_CREATE_OR_OPEN|NGX_FILE_APPEND);
+
+#if 0
+            log->log_level = NGX_LOG_DEBUG_ALL;
+#endif
+            ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,
+                           "log: %0X %d \"%s\"",
+                           &file[i], file[i].fd, file[i].name.data);
+
+            if (file[i].fd == NGX_INVALID_FILE) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                              ngx_open_file_n " \"%s\" failed",
+                              file[i].name.data);
+                failed = 1;
+                break;
+            }
+
+#if (WIN32)
+            if (ngx_file_append_mode(file[i].fd) == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                              ngx_file_append_mode_n " \"%s\" failed",
+                              file[i].name.data);
+                failed = 1;
+                break;
+            }
+#else
+            if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                              "fcntl(FD_CLOEXEC) \"%s\" failed",
+                              file[i].name.data);
+                failed = 1;
+                break;
+            }
+#endif
+        }
+    }
+
+    cycle->log = cycle->new_log;
+    pool->log = cycle->new_log;
+
+    if (cycle->log->log_level == 0) {
+        cycle->log->log_level = NGX_LOG_ERR;
+    }
+
+    if (!failed) {
+        if (old_cycle->listening.nelts) {
+            ls = old_cycle->listening.elts;
+            for (i = 0; i < old_cycle->listening.nelts; i++) {
+                ls[i].remain = 0;
+            }
+
+            nls = cycle->listening.elts;
+            for (n = 0; n < cycle->listening.nelts; n++) {
+                for (i = 0; i < old_cycle->listening.nelts; i++) {
+                    if (ls[i].ignore) {
+                        continue;
+                    }
+
+                    if (ngx_memcmp(nls[n].sockaddr,
+                                   ls[i].sockaddr, ls[i].socklen) == 0)
+                    {
+                        fd = ls[i].fd;
+#if (WIN32)
+                        /*
+                         * Winsock assignes a socket number divisible by 4 so
+                         * to find a connection we divide a socket number by 4.
+                         */
+
+                        fd /= 4;
+#endif
+                        if (fd >= (ngx_socket_t) cycle->connection_n) {
+                            ngx_log_error(NGX_LOG_EMERG, log, 0,
+                                        "%d connections is not enough to hold "
+                                        "an open listening socket on %s, "
+                                        "required at least %d connections",
+                                        cycle->connection_n,
+                                        ls[i].addr_text.data, fd);
+                            failed = 1;
+                            break;
+                        }
+
+                        nls[n].fd = ls[i].fd;
+                        nls[i].remain = 1;
+                        ls[i].remain = 1;
+                        break;
+                    }
+                }
+
+                if (nls[n].fd == -1) {
+                    nls[n].new = 1;
+                }
+            }
+
+        } else {
+            ls = cycle->listening.elts;
+            for (i = 0; i < cycle->listening.nelts; i++) {
+                ls[i].new = 1;
+            }
+        }
+
+        if (!ngx_test_config && !failed) {
+            if (ngx_open_listening_sockets(cycle) == NGX_ERROR) {
+                failed = 1;
+            }
+        }
+    }
+
+    if (failed) {
+
+        /* rollback the new cycle configuration */
+
+        part = &cycle->open_files.part;
+        file = part->elts;
+
+        for (i = 0; /* void */ ; i++) {
+
+            if (i >= part->nelts) {
+                if (part->next == NULL) {
+                    break;
+                }
+                part = part->next;
+                file = part->elts;
+                i = 0;
+            }
+
+            if (file[i].fd == NGX_INVALID_FILE
+                || file[i].fd == ngx_stderr_fileno)
+            {
+                continue;
+            }
+
+            if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              file[i].name.data);
+            }
+        }
+
+        if (ngx_test_config) {
+            ngx_destroy_pool(pool);
+            return NULL;
+        }
+
+        ls = cycle->listening.elts;
+        for (i = 0; i < cycle->listening.nelts; i++) {
+            if (ls[i].fd == -1 || !ls[i].new) {
+                continue;
+            }
+
+            if (ngx_close_socket(ls[i].fd) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                              ngx_close_socket_n " %s failed",
+                              ls[i].addr_text.data);
+            }
+        }
+
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+
+    /* commit the new cycle configuration */
+
+#if !(WIN32)
+
+    if (!ngx_test_config && cycle->log->file->fd != STDERR_FILENO) {
+
+        ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,
+                       "dup2: %0X %d \"%s\"",
+                       cycle->log->file,
+                       cycle->log->file->fd, cycle->log->file->name.data);
+
+        if (dup2(cycle->log->file->fd, STDERR_FILENO) == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          "dup2(STDERR) failed");
+            /* fatal */
+            exit(1);
+        }
+    }
+
+#endif
+
+    pool->log = cycle->log;
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->init_module) {
+            if (ngx_modules[i]->init_module(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(1);
+            }
+        }
+    }
+
+    /* close and delete stuff that lefts from an old cycle */
+
+    /* close the unneeded listening sockets */
+
+    ls = old_cycle->listening.elts;
+    for (i = 0; i < old_cycle->listening.nelts; i++) {
+        if (ls[i].remain) {
+            continue;
+        }
+
+        if (ngx_close_socket(ls[i].fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
+                          ngx_close_socket_n " %s failed",
+                          ls[i].addr_text.data);
+        }
+    }
+
+
+    /* close the unneeded open files */
+
+    part = &old_cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            file = part->elts;
+            i = 0;
+        }
+
+        if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr_fileno) {
+            continue;
+        }
+
+        if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          file[i].name.data);
+        }
+    }
+
+    if (old_cycle->connections == NULL) {
+        /* an old cycle is an init cycle */
+        ngx_destroy_pool(old_cycle->pool);
+        return cycle;
+    }
+
+    if (ngx_process == NGX_PROCESS_MASTER) {
+        ngx_destroy_pool(old_cycle->pool);
+        return cycle;
+    }
+
+    if (ngx_temp_pool == NULL) {
+        ngx_temp_pool = ngx_create_pool(128, cycle->log);
+        if (ngx_temp_pool == NULL) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "can not create ngx_temp_pool");
+            exit(1);
+        }
+
+        n = 10;
+        ngx_old_cycles.elts = ngx_pcalloc(ngx_temp_pool,
+                                          n * sizeof(ngx_cycle_t *));
+        if (ngx_old_cycles.elts == NULL) {
+            exit(1);
+        }
+        ngx_old_cycles.nelts = 0;
+        ngx_old_cycles.size = sizeof(ngx_cycle_t *);
+        ngx_old_cycles.nalloc = n;
+        ngx_old_cycles.pool = ngx_temp_pool;
+
+        ngx_cleaner_event.event_handler = ngx_clean_old_cycles;
+        ngx_cleaner_event.log = cycle->log;
+        ngx_cleaner_event.data = &dumb;
+        dumb.fd = (ngx_socket_t) -1;
+    }
+
+    ngx_temp_pool->log = cycle->log;
+
+    old = ngx_push_array(&ngx_old_cycles);
+    if (old == NULL) {
+        exit(1);
+    }
+    *old = old_cycle;
+
+    if (!ngx_cleaner_event.timer_set) {
+        ngx_add_timer(&ngx_cleaner_event, 30000);
+        ngx_cleaner_event.timer_set = 1;
+    }
+
+    return cycle;
+}
+
+
+#if !(WIN32)
+
+ngx_int_t ngx_create_pidfile(ngx_cycle_t *cycle, ngx_cycle_t *old_cycle)
+{
+    ngx_uint_t        trunc;
+    size_t            len;
+    u_char           *name, pid[NGX_INT64_LEN + 1];
+    ngx_file_t        file;
+    ngx_core_conf_t  *ccf, *old_ccf;
+
+    if (!ngx_test_config && old_cycle && old_cycle->conf_ctx == NULL) {
+
+        /*
+         * do not create the pid file in the first ngx_init_cycle() call
+         * because we need to write the demonized process pid 
+         */
+
+        return NGX_OK;
+    }
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (!ngx_test_config && old_cycle) {
+        old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,
+                                                   ngx_core_module);
+
+        if (ccf->pid.len == old_ccf->pid.len
+            && ngx_strcmp(ccf->pid.data, old_ccf->pid.data) == 0)
+        {
+
+            /* pid file name is the same */
+
+            return NGX_OK;
+        }
+    }
+
+    len = ngx_snprintf((char *) pid, NGX_INT64_LEN + 1, PID_T_FMT, ngx_pid);
+
+    ngx_memzero(&file, sizeof(ngx_file_t));
+    file.name = (ngx_inherited && getppid() > 1) ? ccf->newpid : ccf->pid;
+    file.log = cycle->log;
+
+    trunc = ngx_test_config ? 0: NGX_FILE_TRUNCATE;
+
+    file.fd = ngx_open_file(file.name.data, NGX_FILE_RDWR,
+                            NGX_FILE_CREATE_OR_OPEN|trunc);
+
+    if (file.fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (!ngx_test_config) {
+        if (ngx_write_file(&file, pid, len, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      ngx_close_file_n " \"%s\" failed", file.name.data);
+    }
+
+    ngx_delete_pidfile(old_cycle);
+
+    return NGX_OK;
+}
+
+
+void ngx_delete_pidfile(ngx_cycle_t *cycle)
+{   
+    u_char           *name;
+    ngx_core_conf_t  *ccf;
+
+    if (cycle == NULL || cycle->conf_ctx == NULL) {
+        return;
+    }
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ngx_inherited && getppid() > 1) {
+        name = ccf->newpid.data;
+
+    } else { 
+        name = ccf->pid.data;
+    }
+
+    if (ngx_delete_file(name) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", name);
+    }
+}
+
+#endif
+
+
+void ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user)
+{
+    ngx_fd_t          fd;
+    ngx_uint_t        i;
+    ngx_list_part_t  *part;
+    ngx_open_file_t  *file;
+
+    part = &cycle->open_files.part;
+    file = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+            part = part->next;
+            i = 0;
+        }
+
+        if (file[i].name.data == NULL) {
+            continue;
+        }
+
+        fd = ngx_open_file(file[i].name.data, NGX_FILE_RDWR,
+                           NGX_FILE_CREATE_OR_OPEN|NGX_FILE_APPEND);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "reopen file \"%s\", old:%d new:%d",
+                       file[i].name.data, file[i].fd, fd);
+
+        if (fd == NGX_INVALID_FILE) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_open_file_n " \"%s\" failed", file[i].name.data);
+            continue;
+        }
+
+#if (WIN32)
+        if (ngx_file_append_mode(fd) == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_file_append_mode_n " \"%s\" failed",
+                          file[i].name.data);
+
+            if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              file[i].name.data);
+            }
+
+            continue;
+        }
+#else
+        if (user != (ngx_uid_t) -1) {
+            if (chown((const char *) file[i].name.data, user, -1) == -1) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              "chown \"%s\" failed", file[i].name.data);
+
+                if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                                  ngx_close_file_n " \"%s\" failed",
+                                  file[i].name.data);
+                }
+            }
+        }
+
+        if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) \"%s\" failed",
+                          file[i].name.data);
+
+            if (ngx_close_file(fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              file[i].name.data);
+            }
+
+            continue;
+        }
+#endif
+
+        if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          file[i].name.data);
+        }
+
+        file[i].fd = fd;
+    }
+
+#if !(WIN32)
+
+    if (cycle->log->file->fd != STDERR_FILENO) {
+        if (dup2(cycle->log->file->fd, STDERR_FILENO) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "dup2(STDERR) failed");
+        }
+    }
+
+#endif
+}
+
+
+static void ngx_clean_old_cycles(ngx_event_t *ev)
+{
+    ngx_uint_t     i, n, found, live;
+    ngx_log_t     *log;
+    ngx_cycle_t  **cycle;
+
+    log = ngx_cycle->log;
+    ngx_temp_pool->log = log;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "clean old cycles");
+
+    live = 0;
+
+    cycle = ngx_old_cycles.elts;
+    for (i = 0; i < ngx_old_cycles.nelts; i++) {
+
+        if (cycle[i] == NULL) {
+            continue;
+        }
+
+        found = 0;
+
+        for (n = 0; n < cycle[i]->connection_n; n++) {
+            if (cycle[i]->connections[n].fd != (ngx_socket_t) -1) {
+                found = 1;
+
+                ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "live fd:%d", n);
+
+                break;
+            }
+        }
+
+        if (found) {
+            live = 1;
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "clean old cycle: %d", i);
+
+        ngx_destroy_pool(cycle[i]->pool);
+        cycle[i] = NULL;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "old cycles status: %d", live);
+
+    if (live) {
+        ngx_add_timer(ev, 30000);
+
+    } else {
+        ngx_destroy_pool(ngx_temp_pool);
+        ngx_temp_pool = NULL;
+        ngx_old_cycles.nelts = 0;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_cycle.h
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CYCLE_H_INCLUDED_
+#define _NGX_CYCLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+struct ngx_cycle_s {
+    void           ****conf_ctx;
+    ngx_pool_t        *pool;
+
+    ngx_log_t         *log;
+    ngx_log_t         *new_log;
+
+    ngx_array_t        listening;
+    ngx_array_t        pathes;
+    ngx_list_t         open_files;
+
+    ngx_uint_t         connection_n;
+    ngx_connection_t  *connections;
+    ngx_event_t       *read_events;
+    ngx_event_t       *write_events;
+
+    ngx_cycle_t       *old_cycle;
+
+    ngx_str_t          conf_file;
+    ngx_str_t          root;
+};
+
+
+typedef struct {
+     ngx_flag_t  daemon;
+     ngx_flag_t  master;
+
+     ngx_int_t   worker_processes;
+
+     ngx_uid_t   user;
+     ngx_gid_t   group;
+
+     ngx_str_t   pid;
+     ngx_str_t   newpid;
+
+#if (NGX_THREADS)
+     ngx_int_t   worker_threads;
+     size_t      thread_stack_size;
+#endif
+
+} ngx_core_conf_t;
+
+
+typedef struct {
+     ngx_pool_t  *pool;   /* pcre's malloc() pool */
+} ngx_core_tls_t;
+
+
+ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle);
+ngx_int_t ngx_create_pidfile(ngx_cycle_t *cycle, ngx_cycle_t *old_cycle);
+void ngx_delete_pidfile(ngx_cycle_t *cycle);
+void ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user);
+ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv);
+
+
+extern volatile ngx_cycle_t  *ngx_cycle;
+extern ngx_array_t            ngx_old_cycles;
+extern ngx_module_t           ngx_core_module;
+extern ngx_uint_t             ngx_test_config;
+#if (NGX_THREADS)
+extern ngx_tls_key_t          ngx_core_tls_key;
+#endif
+
+
+#endif /* _NGX_CYCLE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_file.c
@@ -0,0 +1,237 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_uint_t ngx_temp_number;
+static ngx_uint_t ngx_random;
+
+
+int ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain)
+{
+    int  rc;
+
+    if (tf->file.fd == NGX_INVALID_FILE) {
+        rc = ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+                                  tf->persistent);
+
+        if (rc == NGX_ERROR || rc == NGX_AGAIN) {
+            return rc;
+        }
+
+        if (!tf->persistent && tf->warn) {
+            ngx_log_error(NGX_LOG_WARN, tf->file.log, 0, tf->warn);
+        }
+    }
+
+    return ngx_write_chain_to_file(&tf->file, chain, tf->offset, tf->pool);
+}
+
+
+int ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path,
+                         ngx_pool_t *pool, int persistent)
+{
+    int        num;
+    ngx_err_t  err;
+
+    file->name.len = path->name.len + 1 + path->len + 10;
+
+    ngx_test_null(file->name.data, ngx_palloc(pool, file->name.len + 1),
+                  NGX_ERROR);
+
+#if 0
+    for (i = 0; i < file->name.len; i++) {
+         file->name.data[i] = 'X';
+    }
+#endif
+
+    ngx_memcpy(file->name.data, path->name.data, path->name.len);
+
+    num = ngx_next_temp_number(0);
+
+    for ( ;; ) {
+        ngx_snprintf((char *)
+                            (file->name.data + path->name.len + 1 + path->len),
+                     11, "%010u", num);
+
+        ngx_create_hashed_filename(file, path);
+
+#if 1
+        file->fd = ngx_open_tempfile(file->name.data, persistent);
+#else
+        file->fd = ngx_open_tempfile(file->name.data, 1);
+#endif
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                       "temp fd:%d", file->fd);
+
+        if (file->fd != NGX_INVALID_FILE) {
+            return NGX_OK;
+        }
+
+        err = ngx_errno;
+
+        if (err == NGX_EEXIST) {
+            num = ngx_next_temp_number(1);
+            continue;
+        }
+
+        if ((path->level[0] == 0)
+            || (err != NGX_ENOENT
+#if (WIN32)
+                && err != NGX_ENOTDIR
+#endif
+        )) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                          ngx_open_tempfile_n " \"%s\" failed",
+                          file->name.data);
+            return NGX_ERROR;
+        }
+
+        if (ngx_create_path(file, path) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+}
+
+
+void ngx_create_hashed_filename(ngx_file_t *file, ngx_path_t *path)
+{
+    int     i, name, pos;
+    size_t  level;
+
+    name = file->name.len;
+    pos = path->name.len + 1;
+
+    file->name.data[path->name.len + path->len]  = '/';
+
+    for (i = 0; i < 3; i++) {
+        level = path->level[i];
+
+        if (level == 0) {
+            break;
+        }
+
+        name -= level;
+        file->name.data[pos - 1] = '/';
+        ngx_memcpy(&file->name.data[pos], &file->name.data[name], level);
+        pos += level + 1;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "hashed path: %s", file->name.data);
+}
+
+
+int ngx_create_path(ngx_file_t *file, ngx_path_t *path)
+{
+    int        i, pos;
+    ngx_err_t  err;
+
+    pos = path->name.len;
+
+    for (i = 0; i < 3; i++) {
+        if (path->level[i] == 0) {
+            break;
+        }
+
+        pos += path->level[i] + 1;
+
+        file->name.data[pos] = '\0';
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
+                       "temp file: \"%s\"", file->name.data);
+
+        if (ngx_create_dir(file->name.data) == NGX_FILE_ERROR) {
+            err = ngx_errno;
+            if (err != NGX_EEXIST) {
+                ngx_log_error(NGX_LOG_CRIT, file->log, err,
+                              ngx_create_dir_n " \"%s\" failed",
+                              file->name.data);
+                return NGX_ERROR;
+            }
+        }
+
+        file->name.data[pos] = '/';
+    }
+
+    return NGX_OK;
+}
+
+
+void ngx_init_temp_number()
+{
+    ngx_random = 0;
+
+    ngx_temp_number = ngx_random;
+
+    while (ngx_random < 10000) {
+        ngx_random = 123456;
+    }
+}
+
+
+ngx_uint_t ngx_next_temp_number(ngx_uint_t collision)
+{
+    if (collision) {
+        ngx_temp_number += ngx_random;
+    }
+
+    return ngx_temp_number++;
+}
+
+
+char *ngx_conf_set_path_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t    level;
+    ngx_uint_t   i, n;
+    ngx_str_t   *value;
+    ngx_path_t  *path, **pp;
+
+    pp = (ngx_path_t **) (p + cmd->offset);
+
+    if (*pp) {
+        return "is duplicate";
+    }
+
+    /* TODO: check duplicate in cf->cycle->pathes */
+
+    ngx_test_null(path, ngx_pcalloc(cf->pool, sizeof(ngx_path_t)),
+                  NGX_CONF_ERROR);
+
+    *pp = path;
+
+    ngx_test_null(pp, ngx_push_array(&cf->cycle->pathes), NGX_CONF_ERROR);
+    *pp = path;
+
+    value = (ngx_str_t *) cf->args->elts;
+
+    path->name = value[1];
+
+    path->len = 0;
+
+    for (i = 0, n = 2; n < cf->args->nelts; i++, n++) {
+        level = ngx_atoi(value[n].data, value[n].len);
+        if (level == NGX_ERROR || level == 0) {
+            return "invalid value";
+        }
+
+        path->level[i] = level;
+        path->len += level + 1;
+    }
+
+    while (i < 3) {
+        path->level[i++] = 0;
+    }
+
+    path->gc_handler = (ngx_gc_handler_pt) cmd->post;
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_file.h
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FILE_H_INCLUDED_
+#define _NGX_FILE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+typedef struct ngx_path_s  ngx_path_t;
+
+#include <ngx_garbage_collector.h>
+
+
+struct ngx_file_s {
+    ngx_fd_t         fd;
+    ngx_str_t        name;
+    ngx_file_info_t  info;
+
+    off_t            offset;
+    off_t            sys_offset;
+
+    ngx_log_t       *log;
+
+    unsigned         info_valid:1;
+};
+
+#define NGX_MAX_PATH_LEVEL  3
+
+struct ngx_path_s {
+    ngx_str_t           name;
+    u_int               len;
+    u_int               level[3];
+    ngx_gc_handler_pt   gc_handler;
+};
+
+
+typedef struct {
+    ngx_file_t   file;
+    off_t        offset;
+    ngx_path_t  *path;
+    ngx_pool_t  *pool;
+    char        *warn;
+
+    unsigned     persistent:1;
+} ngx_temp_file_t;
+
+
+int ngx_write_chain_to_temp_file(ngx_temp_file_t *tf, ngx_chain_t *chain);
+int ngx_create_temp_file(ngx_file_t *file, ngx_path_t *path,
+                         ngx_pool_t *pool, int persistent);
+void ngx_create_hashed_filename(ngx_file_t *file, ngx_path_t *path);
+int ngx_create_path(ngx_file_t *file, ngx_path_t *path);
+
+void ngx_init_temp_number();
+ngx_uint_t ngx_next_temp_number(ngx_uint_t collision);
+
+char *ngx_conf_set_path_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+#define ngx_conf_merge_path_value(conf, prev, path, l1, l2, l3, pool)        \
+    if (conf == NULL) {                                                      \
+        if (prev == NULL) {                                                  \
+            ngx_test_null(conf, ngx_palloc(pool, sizeof(ngx_path_t)), NULL); \
+            conf->name.len = sizeof(path) - 1;                               \
+            conf->name.data = (u_char *) path;                               \
+            conf->level[0] = l1;                                             \
+            conf->level[1] = l2;                                             \
+            conf->level[2] = l3;                                             \
+            conf->len = l1 + l2 + l3 + (l1 ? 1:0) + (l2 ? 1:0) + (l3 ? 1:0); \
+        } else {                                                             \
+            conf = prev;                                                     \
+        }                                                                    \
+    }
+
+
+
+#endif /* _NGX_FILE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_garbage_collector.c
@@ -0,0 +1,279 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_garbage_collector.h>
+
+
+int ngx_garbage_collector_temp_handler(ngx_gc_t *ctx, ngx_str_t *name,
+                                       ngx_dir_t *dir);
+
+
+static int ngx_collect_garbage(ngx_gc_t *ctx, ngx_str_t *dname, int level);
+
+
+
+#if 0
+
+{
+    ngx_test_null(cycle->timer_events,
+                  ngx_alloc(sizeof(ngx_event_t) * TIMERS, cycle->log),
+                  NGX_ERROR);
+
+    ngx_event_timer_init(cycle);
+}
+
+
+void garbage_collector()
+{
+    ngx_msec_t        timer;
+    struct timeval    tv;
+    ngx_epoch_msec_t  delta;
+
+    for ( ;; ) {
+        timer = ngx_event_find_timer();
+
+        ngx_gettimeofday(&tv);
+        delta = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+
+        msleep(timer);
+
+        ngx_gettimeofday(&tv);
+
+        ngx_cached_time = tv.tv_sec;
+        ngx_time_update();
+
+        delta = tv.tv_sec * 1000 + tv.tv_usec / 1000 - delta;
+
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+}
+
+#endif
+
+
+void stub_init(ngx_cycle_t *cycle)
+{
+    ngx_uint_t    i;
+    ngx_gc_t      ctx;
+    ngx_path_t  **path;
+
+    path = cycle->pathes.elts;
+    for (i = 0; i < cycle->pathes.nelts; i++) {
+        ctx.path = path[i];
+        ctx.log = cycle->log;
+        ctx.handler = path[i]->gc_handler;
+
+        ngx_collect_garbage(&ctx, &path[i]->name, 0);
+    }
+}
+
+
+static int ngx_collect_garbage(ngx_gc_t *ctx, ngx_str_t *dname, int level)
+{
+    int         rc;
+    u_char     *last;
+    size_t      len;
+    ngx_err_t   err;
+    ngx_str_t   fname, buf;
+    ngx_dir_t   dir;
+
+    buf.len = 0;
+#if (NGX_SUPPRESS_WARN)
+    buf.data = NULL;
+    fname.data = NULL;
+#endif
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                   "gc dir \"%s\":%d", dname->data, dname->len);
+
+    if (ngx_open_dir(dname, &dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_open_dir_n " \"%s\" failed", dname->data);
+        return NGX_ERROR;
+    }
+
+    for ( ;; ) {
+        ngx_set_errno(0);
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            err = ngx_errno;
+
+            if (err != NGX_ENOMOREFILES) {
+                ngx_log_error(NGX_LOG_CRIT, ctx->log, err,
+                              ngx_read_dir_n " \"%s\" failed", dname->data);
+                rc = NGX_ERROR;
+
+            } else {
+                rc = NGX_OK;
+            }
+
+            break;
+        }
+
+        len = ngx_de_namelen(&dir);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                      "gc name \"%s\":%d", ngx_de_name(&dir), len);
+
+        if (len == 1 && ngx_de_name(&dir)[0] == '.') {
+            continue;
+        }
+
+        if (len == 2
+            && ngx_de_name(&dir)[0] == '.'
+            && ngx_de_name(&dir)[1] == '.')
+        {
+            continue;
+        }
+
+        fname.len = dname->len + 1+ len;
+
+        if (fname.len + NGX_DIR_MASK_LEN > buf.len) {
+
+            if (buf.len) {
+                ngx_free(buf.data);
+            }
+
+            buf.len = dname->len + 1 + len + NGX_DIR_MASK_LEN;
+
+            if (!(buf.data = ngx_alloc(buf.len + 1, ctx->log))) {
+                return NGX_ABORT;
+            }
+        }
+
+        last = ngx_cpymem(buf.data, dname->data, dname->len);
+        *last++ = '/';
+        ngx_memcpy(last, ngx_de_name(&dir), len + 1);
+        fname.data = buf.data;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                       "gc path: \"%s\"", fname.data);
+
+        if (!dir.info_valid) {
+            if (ngx_de_info(fname.data, &dir) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                              ngx_de_info_n " \"%s\" failed", fname.data);
+                continue;
+            }
+        }
+
+        if (ngx_de_is_dir(&dir)) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                           "gc enter dir \"%s\"", fname.data);
+
+            if (level == -1
+                   /* there can not be directory on the last level */
+                || level == NGX_MAX_PATH_LEVEL
+                   /* an directory from the old path hierarchy */
+                || len != ctx->path->level[level])
+            {
+                if (ngx_collect_garbage(ctx, &fname, -1) == NGX_ABORT) {
+                    return NGX_ABORT;
+                }
+
+                fname.data[fname.len] = '\0';
+
+                ngx_log_error(NGX_LOG_NOTICE, ctx->log, 0,
+                              "delete old hierachy directory \"%s\"",
+                              fname.data);
+
+                if (ngx_delete_dir(fname.data) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                                  ngx_delete_dir_n " \"%s\" failed",
+                                  fname.data);
+                } else {
+                    ctx->deleted++;
+                    ctx->freed += ngx_de_size(&dir);
+                }
+
+                continue;
+            }
+
+            if (ngx_collect_garbage(ctx, &fname, level + 1) == NGX_ABORT) {
+                return NGX_ABORT;
+            }
+
+        } else if (ngx_de_is_file(&dir)) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->log, 0,
+                           "gc file \"%s\"", fname.data);
+
+            if (level == -1
+                || (level < NGX_MAX_PATH_LEVEL && ctx->path->level[level] != 0))
+            {
+                if (ngx_delete_file(fname.data) == NGX_FILE_ERROR) {
+                    ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                                  ngx_delete_file_n " \"%s\" failed",
+                                  fname.data);
+                } else {
+                    ctx->deleted++;
+                    ctx->freed += ngx_de_size(&dir);
+                }
+
+                continue;
+            }
+
+            if (ctx->handler(ctx, &fname, &dir) == NGX_ABORT) {
+                return NGX_ABORT;
+            }
+
+        } else {
+            ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                          "\"%s\" has unknown file type, deleting", fname.data);
+
+            if (ngx_delete_file(fname.data) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                              ngx_delete_file_n " \"%s\" failed", fname.data);
+            } else {
+                ctx->deleted++;
+                ctx->freed += ngx_de_size(&dir);
+            }
+        }
+    }
+
+    if (buf.len) {
+        ngx_free(buf.data);
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_close_dir_n " \"%s\" failed", fname.data);
+    }
+
+    return rc;
+}
+
+
+int ngx_garbage_collector_temp_handler(ngx_gc_t *ctx, ngx_str_t *name,
+                                       ngx_dir_t *dir)
+{
+    /*
+     * We use mtime only and do not use atime because:
+     *    on NTFS access time has a resolution of 1 hour,
+     *    on NT FAT access time has a resolution of 1 day,
+     *    Unices have the mount option "noatime".
+     */
+
+    if (ngx_time() - ngx_de_mtime(dir) < 3600) {
+        return NGX_OK;
+    }
+
+    ngx_log_error(NGX_LOG_NOTICE, ctx->log, 0,
+                  "delete stale temporary \"%s\"", name->data);
+
+    if (ngx_delete_file(name->data) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", name->data);
+        return NGX_ERROR;
+    }
+
+    ctx->deleted++;
+    ctx->freed += ngx_de_size(dir);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_garbage_collector.h
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_GARBAGE_COLLECTOR_H_INCLUDED_
+#define _NGX_GARBAGE_COLLECTOR_H_INCLUDED_
+
+
+typedef struct ngx_gc_s  ngx_gc_t;
+
+typedef int (*ngx_gc_handler_pt) (ngx_gc_t *ctx, ngx_str_t *name,
+                                  ngx_dir_t *dir);
+
+
+struct ngx_gc_s {
+    ngx_path_t         *path;
+    u_int               deleted;
+    off_t               freed;
+    ngx_gc_handler_pt   handler;
+    ngx_log_t          *log;
+};
+
+
+int ngx_garbage_collector_temp_handler(ngx_gc_t *ctx, ngx_str_t *name,
+                                       ngx_dir_t *dir);
+
+
+#endif /* _NGX_GARBAGE_COLLECTOR_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_inet.c
@@ -0,0 +1,273 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_inline static size_t ngx_sprint_uchar(u_char *text, u_char c, size_t len)
+{
+    size_t      n;
+    ngx_uint_t  c1, c2;
+
+    n = 0;
+
+    if (len == n) {
+        return n;
+    }
+
+    c1 = c / 100;
+
+    if (c1) {
+        *text++ = (u_char) (c1 + '0');
+        n++;
+
+        if (len == n) {
+            return n;
+        }
+    }
+
+    c2 = (c % 100) / 10;
+
+    if (c1 || c2) {
+        *text++ = (u_char) (c2 + '0');
+        n++;
+
+        if (len == n) {
+            return n;
+        }
+    }
+
+    c2 = c % 10;
+
+    *text++ = (u_char) (c2 + '0');
+    n++;
+
+    return n;
+}
+
+
+/* AF_INET only */
+
+size_t ngx_sock_ntop(int family, struct sockaddr *addr, u_char *text,
+                     size_t len)
+{
+    u_char              *p;
+    size_t               n;
+    ngx_uint_t           i;
+    struct sockaddr_in  *addr_in;
+
+    if (len == 0) {
+        return 0;
+    }
+
+    if (family != AF_INET) {
+        return 0;
+    }
+
+    addr_in = (struct sockaddr_in *) addr;
+    p = (u_char *) &addr_in->sin_addr;
+
+    if (len > INET_ADDRSTRLEN) {
+        len = INET_ADDRSTRLEN;
+    }
+
+    n = ngx_sprint_uchar(text, p[0], len);
+
+    i = 1;
+
+    do {
+        if (len == n) {
+            text[n - 1] = '\0';
+            return n;
+        }
+
+        text[n++] = '.';
+
+        if (len == n) {
+            text[n - 1] = '\0';
+            return n;
+        }
+
+        n += ngx_sprint_uchar(&text[n], p[i++], len - n);
+
+    } while (i < 4);
+
+    if (len == n) {
+        text[n] = '\0';
+        return n;
+    }
+
+    text[n] = '\0';
+
+    return n;
+
+#if 0
+    return ngx_snprintf((char *) text,
+                        len > INET_ADDRSTRLEN ? INET_ADDRSTRLEN : len,
+                        "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
+#endif
+}
+
+
+size_t ngx_inet_ntop(int family, void *addr, u_char *text, size_t len)
+{
+    u_char      *p;
+    size_t       n;
+    ngx_uint_t   i;
+
+    if (len == 0) {
+        return 0;
+    }
+
+    if (family != AF_INET) {
+        return 0;
+    }
+
+    p = (u_char *) addr;
+
+    if (len > INET_ADDRSTRLEN) {
+        len = INET_ADDRSTRLEN;
+    }
+
+    n = ngx_sprint_uchar(text, p[0], len);
+
+    i = 1;
+
+    do {
+        if (len == n) {
+            text[n - 1] = '\0';
+            return n;
+        }
+
+        text[n++] = '.';
+
+        if (len == n) {
+            text[n - 1] = '\0';
+            return n;
+        }
+
+        n += ngx_sprint_uchar(&text[n], p[i++], len - n);
+
+    } while (i < 4);
+
+    if (len == n) {
+        text[n] = '\0';
+        return n;
+    }
+
+    text[n] = '\0';
+
+    return n;
+
+#if 0
+    return ngx_snprintf((char *) text,
+                        len > INET_ADDRSTRLEN ? INET_ADDRSTRLEN : len,
+                        "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
+#endif
+}
+
+
+/* AF_INET only */
+
+ngx_int_t ngx_ptocidr(ngx_str_t *text, void *cidr)
+{
+    ngx_int_t         m;
+    ngx_uint_t        i;
+    ngx_inet_cidr_t  *in_cidr;
+
+    in_cidr = cidr;
+
+    for (i = 0; i < text->len; i++) {
+        if (text->data[i] == '/') {
+            break;
+        }
+    }
+
+    if (i == text->len) {
+        return NGX_ERROR;
+    }
+
+    text->data[i] = '\0';
+    in_cidr->addr = inet_addr((char *) text->data);
+    text->data[i] = '/';
+    if (in_cidr->addr == INADDR_NONE) {
+        return NGX_ERROR;
+    }
+
+    m = ngx_atoi(&text->data[i + 1], text->len - (i + 1));
+    if (m == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (m == 0) {
+
+        /* the x86 compilers use the shl instruction that shifts by modulo 32 */
+
+        in_cidr->mask = 0;
+        return NGX_OK;
+    }
+
+    in_cidr->mask = htonl((ngx_uint_t) (0 - (1 << (32 - m))));
+
+    return NGX_OK;
+}
+
+
+#if 0
+
+ngx_int_t ngx_inet_addr_port(ngx_conf_t *cf, ngx_command_t *cmd,
+                             ngx_str_t *addr_port)
+{
+    u_char          *host;
+    ngx_int_t        port;
+    ngx_uint_t       p;
+    struct hostent  *h;
+
+    for (p = 0; p < addr_port->len; p++) {
+        if (addr_port->data[p] == ':') {
+            break;
+        }
+    }
+
+    in_addr->host.len = p;
+    if (!(in_addr->host.data = ngx_palloc(pool, p + 1))) {
+        return NGX_ERROR;
+    }
+
+    ngx_cpystrn(in_addr->host.data, addr_port->data, p + 1);
+
+    if (p == addr_port->len) {
+        p = 0;
+    }
+
+    port = ngx_atoi(&addr[p], args[1].len - p);
+    if (port == NGX_ERROR && p == 0) {
+
+        /* default port */
+        iap->port = 0;
+
+    } else if ((port == NGX_ERROR && p != 0) /* "listen host:NONNUMBER" */
+               || (port < 1 || port > 65536)) { /* "listen 99999" */
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid port \"%s\" in \"%s\" directive, "
+                           "it must be a number between 1 and 65535",
+                           &addr[p], cmd->name.data);
+
+        return NGX_CONF_ERROR;
+
+    } else if (p == 0) {
+        ls->addr = INADDR_ANY;
+        ls->port = (in_port_t) port;
+        return NGX_CONF_OK;
+    }
+
+    return NGX_OK;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_inet.h
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_INET_H_INCLUDED_
+#define _NGX_INET_H_INCLUDED_
+
+
+typedef struct {
+    in_addr_t  addr;
+    in_addr_t  mask;
+} ngx_inet_cidr_t;
+
+
+size_t ngx_sock_ntop(int family, struct sockaddr *addr, u_char *text,
+                     size_t len);
+size_t ngx_inet_ntop(int family, void *addr, u_char *text, size_t len);
+
+ngx_int_t ngx_ptocidr(ngx_str_t *text, void *cidr);
+
+
+#endif /* _NGX_INET_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_list.c
@@ -0,0 +1,41 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *ngx_list_push(ngx_list_t *l)
+{
+    void             *elt;
+    ngx_list_part_t  *last;
+
+    last = l->last;
+
+    if (last->nelts == l->nalloc) {
+
+        /* the last part is full, allocate a new list part */
+
+        if (!(last = ngx_palloc(l->pool, sizeof(ngx_list_part_t)))) {
+            return NULL;
+        }
+
+        if (!(last->elts = ngx_palloc(l->pool, l->nalloc * l->size))) {
+            return NULL;
+        }
+
+        last->nelts = 0;
+        last->next = NULL;
+
+        l->last->next = last;
+        l->last = last;
+    }
+
+    elt = (char *) last->elts + l->size * last->nelts;
+    last->nelts++;
+
+    return elt;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_list.h
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LIST_H_INCLUDED_
+#define _NGX_LIST_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_list_part_s  ngx_list_part_t;
+
+struct ngx_list_part_s {
+    void             *elts;
+    ngx_uint_t        nelts;
+    ngx_list_part_t  *next;
+};
+
+
+typedef struct {
+    ngx_list_part_t  *last;
+    ngx_list_part_t   part;
+    size_t            size;
+    ngx_uint_t        nalloc;
+    ngx_pool_t       *pool;
+} ngx_list_t;
+
+
+ngx_inline static ngx_int_t ngx_list_init(ngx_list_t *list, ngx_pool_t *pool,
+                                          ngx_uint_t n, size_t size)
+{
+    if (!(list->part.elts = ngx_palloc(pool, n * size))) {
+        return NGX_ERROR;
+    }
+
+    list->part.nelts = 0;
+    list->part.next = NULL;
+    list->last = &list->part;
+    list->size = size;
+    list->nalloc = n;
+    list->pool = pool;
+
+    return NGX_OK;
+}
+
+
+/*
+ *
+ *  the iteration through the list:
+ *
+ *  part = &list.part;
+ *  data = part->elts;
+ *
+ *  for (i = 0 ;; i++) {
+ *
+ *      if (i >= part->nelts) {
+ *          if (part->next == NULL) {
+ *              break;
+ *          }
+ *
+ *          part = part->next;
+ *          data = part->elts;
+ *          i = 0;
+ *      }
+ *
+ *      ...  data[i] ...
+ *
+ *  }
+ */
+
+
+void *ngx_list_push(ngx_list_t *list);
+
+
+#endif /* _NGX_LIST_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_log.c
@@ -0,0 +1,410 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static void ngx_log_write(ngx_log_t *log, char *errstr, size_t len);
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t  ngx_errlog_commands[] = {
+
+    {ngx_string("error_log"),
+     NGX_MAIN_CONF|NGX_CONF_1MORE,
+     ngx_set_error_log,
+     0,
+     0,
+     NULL},
+
+    ngx_null_command
+};
+
+
+static ngx_core_module_t  ngx_errlog_module_ctx = {
+    ngx_string("errlog"),
+    NULL,                           
+    NULL
+};
+
+
+ngx_module_t  ngx_errlog_module = {
+    NGX_MODULE,
+    &ngx_errlog_module_ctx,                /* module context */
+    ngx_errlog_commands,                   /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_log_t        ngx_log;
+static ngx_open_file_t  ngx_stderr;
+
+
+static const char *err_levels[] = {
+    "stderr", "emerg", "alert", "crit", "error",
+    "warn", "notice", "info", "debug"
+};
+
+static const char *debug_levels[] = {
+    "debug_core", "debug_alloc", "debug_mutex", "debug_event",
+    "debug_http", "debug_imap"
+};
+
+
+#if (HAVE_VARIADIC_MACROS)
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                        const char *fmt, ...)
+#else
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                        const char *fmt, va_list args)
+#endif
+{
+    char      errstr[MAX_ERROR_STR];
+    size_t    len, max;
+#if (HAVE_VARIADIC_MACROS)
+    va_list   args;
+#endif
+
+    if (log->file->fd == NGX_INVALID_FILE) {
+        return;
+    }
+
+    ngx_memcpy(errstr, ngx_cached_err_log_time.data,
+               ngx_cached_err_log_time.len);
+
+#if (WIN32)
+    max = MAX_ERROR_STR - 2;
+#else
+    max = MAX_ERROR_STR - 1;
+#endif
+
+    len = ngx_cached_err_log_time.len;
+
+    len += ngx_snprintf(errstr + len, max - len, " [%s] ", err_levels[level]);
+
+    /* pid#tid */
+    len += ngx_snprintf(errstr + len, max - len,
+                        PID_T_FMT "#" TID_T_FMT ": ", ngx_log_pid, ngx_log_tid);
+
+    if (log->data && *(int *) log->data != -1) {
+        len += ngx_snprintf(errstr + len, max - len,
+                            "*%u ", *(u_int *) log->data);
+    }
+
+#if (HAVE_VARIADIC_MACROS)
+
+    va_start(args, fmt);
+    len += ngx_vsnprintf(errstr + len, max - len, fmt, args);
+    va_end(args);
+
+#else
+
+    len += ngx_vsnprintf(errstr + len, max - len, fmt, args);
+
+#endif
+
+    if (err) {
+
+        if (len > max - 50) {
+
+            /* leave a space for an error code */
+
+            len = max - 50;
+            errstr[len++] = '.';
+            errstr[len++] = '.';
+            errstr[len++] = '.';
+        }
+
+#if (WIN32)
+        if ((unsigned) err >= 0x80000000) {
+            len += ngx_snprintf(errstr + len, max - len, " (%X: ", err);
+        } else {
+            len += ngx_snprintf(errstr + len, max - len, " (%d: ", err);
+        }
+#else
+        len += ngx_snprintf(errstr + len, max - len, " (%d: ", err);
+#endif
+
+        if (len >= max) {
+            ngx_log_write(log, errstr, max);
+            return;
+        }
+
+        len += ngx_strerror_r(err, errstr + len, max - len);
+
+        if (len >= max) {
+            ngx_log_write(log, errstr, max);
+            return;
+        }
+
+        errstr[len++] = ')';
+
+        if (len >= max) {
+            ngx_log_write(log, errstr, max);
+            return;
+        }
+
+    } else {
+        if (len >= max) {
+            ngx_log_write(log, errstr, max);
+            return;
+        }
+    }
+
+    if (level != NGX_LOG_DEBUG && log->handler) {
+        len += log->handler(log->data, errstr + len, max - len);
+
+        if (len >= max) {
+            len = max;
+        }
+    }
+
+    ngx_log_write(log, errstr, len);
+}
+
+
+static void ngx_log_write(ngx_log_t *log, char *errstr, size_t len)
+{
+#if (WIN32)
+    u_long  written;
+
+    errstr[len++] = CR;
+    errstr[len++] = LF;
+    WriteFile(log->file->fd, errstr, len, &written, NULL);
+
+#else
+
+    errstr[len++] = LF;
+    write(log->file->fd, errstr, len);
+
+#endif
+}
+
+
+#if !(HAVE_VARIADIC_MACROS)
+
+void ngx_log_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                   const char *fmt, ...)
+{
+    va_list    args;
+
+    if (log->log_level >= level) {
+        va_start(args, fmt);
+        ngx_log_error_core(level, log, err, fmt, args);
+        va_end(args);
+    }
+}
+
+
+void ngx_log_debug_core(ngx_log_t *log, ngx_err_t err, const char *fmt, ...)
+{
+    va_list    args;
+
+    va_start(args, fmt);
+    ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, args);
+    va_end(args);
+}
+
+
+void ngx_assert_core(ngx_log_t *log, const char *fmt, ...)
+{
+    va_list    args;
+
+    va_start(args, fmt);
+    ngx_log_error_core(NGX_LOG_ALERT, log, 0, fmt, args);
+    va_end(args);
+}
+
+#endif
+
+
+ngx_log_t *ngx_log_init_stderr()
+{
+#if (WIN32)
+
+    ngx_stderr_fileno = GetStdHandle(STD_ERROR_HANDLE);
+    ngx_stderr.fd = ngx_stderr_fileno;
+
+    if (ngx_stderr_fileno == NGX_INVALID_FILE) {
+
+        /* TODO: where can we log error ? */
+
+        return NULL;
+
+    } else if (ngx_stderr_fileno == NULL) {
+
+        /* there are no associated standard handles */
+
+        /* TODO: where can we can log possible errors ? */
+
+        ngx_stderr.fd = NGX_INVALID_FILE;
+    }
+
+#else
+
+    ngx_stderr.fd = STDERR_FILENO;
+
+#endif
+
+    ngx_log.file = &ngx_stderr;
+    ngx_log.log_level = NGX_LOG_ERR;
+
+    return &ngx_log;
+}
+
+
+#if 0
+
+ngx_int_t ngx_log_init_error_log()
+{
+    ngx_fd_t  fd;
+
+#ifdef NGX_ERROR_LOG_PATH
+
+    fd = ngx_open_file(NGX_ERROR_LOG_PATH, NGX_FILE_RDWR,
+                       NGX_FILE_CREATE_OR_OPEN|NGX_FILE_APPEND);
+
+    if (fd == NGX_INVALID_FILE) {
+        ngx_log_error(NGX_LOG_EMERG, (&ngx_log), ngx_errno,
+                      ngx_open_file_n " \"" NGX_ERROR_LOG_PATH "\" failed");
+        return NGX_ERROR;
+    }
+
+#if (WIN32)
+
+    if (ngx_file_append_mode(fd) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_EMERG, (&ngx_log), ngx_errno,
+                      ngx_file_append_mode_n " \"" NGX_ERROR_LOG_PATH
+                      "\" failed");
+        return NGX_ERROR;
+    }
+
+#else
+
+    if (dup2(fd, STDERR_FILENO) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_EMERG, (&ngx_log), ngx_errno,
+                      "dup2(STDERR) failed");
+        return NGX_ERROR;
+    }
+
+#endif
+
+#else  /* no NGX_ERROR_LOG_PATH */
+
+    ngx_log.log_level = NGX_LOG_INFO;
+
+#endif
+
+    return NGX_OK;
+}
+
+#endif
+
+
+ngx_log_t *ngx_log_create_errlog(ngx_cycle_t *cycle, ngx_array_t *args)
+{
+    ngx_log_t  *log;
+    ngx_str_t  *value, *name;
+
+    if (args) {
+        value = args->elts;
+        name = &value[1];
+
+    } else {
+        name = NULL;
+    }
+
+    if (!(log = ngx_pcalloc(cycle->pool, sizeof(ngx_log_t)))) {
+        return NULL;
+    }
+
+    if (!(log->file = ngx_conf_open_file(cycle, name))) {
+        return NULL;
+    }
+
+    return log;
+}
+
+
+char *ngx_set_error_log_levels(ngx_conf_t *cf, ngx_log_t *log)
+{
+    ngx_uint_t   i, n, d;
+    ngx_str_t   *value;
+
+    value = cf->args->elts;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        for (n = 1; n <= NGX_LOG_DEBUG; n++) {
+            if (ngx_strcmp(value[i].data, err_levels[n]) == 0) {
+
+                if (log->log_level != 0) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid log level \"%s\"",
+                                       value[i].data);
+                    return NGX_CONF_ERROR;
+                }
+
+                log->log_level = n;
+                continue;
+            }
+        }
+
+        for (n = 0, d = NGX_LOG_DEBUG_FIRST; d <= NGX_LOG_DEBUG_LAST; d <<= 1) {
+            if (ngx_strcmp(value[i].data, debug_levels[n++]) == 0) {
+                if (log->log_level & ~NGX_LOG_DEBUG_ALL) {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                       "invalid log level \"%s\"",
+                                       value[i].data);
+                    return NGX_CONF_ERROR;
+                }
+
+                log->log_level |= d;
+            }
+        }
+
+
+        if (log->log_level == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid log level \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (log->log_level == NGX_LOG_DEBUG) {
+        log->log_level = NGX_LOG_DEBUG_ALL;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_str_t  *value;
+
+    value = cf->args->elts;
+
+    if (value[1].len == 6 && ngx_strcmp(value[1].data, "stderr") == 0) {
+        cf->cycle->new_log->file->fd = ngx_stderr.fd;
+        cf->cycle->new_log->file->name.len = 0;
+        cf->cycle->new_log->file->name.data = NULL;
+
+    } else {
+        cf->cycle->new_log->file->name = value[1];
+
+        if (ngx_conf_full_name(cf->cycle, &cf->cycle->new_log->file->name)
+                                                                  == NGX_ERROR)
+        {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return ngx_set_error_log_levels(cf, cf->cycle->new_log);
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_log.h
@@ -0,0 +1,210 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LOG_H_INCLUDED_
+#define _NGX_LOG_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_LOG_STDERR            0
+#define NGX_LOG_EMERG             1
+#define NGX_LOG_ALERT             2
+#define NGX_LOG_CRIT              3
+#define NGX_LOG_ERR               4
+#define NGX_LOG_WARN              5
+#define NGX_LOG_NOTICE            6
+#define NGX_LOG_INFO              7
+#define NGX_LOG_DEBUG             8
+
+#define NGX_LOG_DEBUG_CORE        0x010
+#define NGX_LOG_DEBUG_ALLOC       0x020
+#define NGX_LOG_DEBUG_MUTEX       0x040
+#define NGX_LOG_DEBUG_EVENT       0x080
+#define NGX_LOG_DEBUG_HTTP        0x100
+#define NGX_LOG_DEBUG_IMAP        0x200
+
+/*
+ * do not forget to update debug_levels[] in src/core/ngx_log.c
+ * after the adding a new debug level
+ */
+
+#define NGX_LOG_DEBUG_FIRST       NGX_LOG_DEBUG_CORE
+#define NGX_LOG_DEBUG_LAST        NGX_LOG_DEBUG_IMAP
+#define NGX_LOG_DEBUG_CONNECTION  0x80000000
+#define NGX_LOG_DEBUG_ALL         0x7ffffff0
+
+
+typedef size_t  (*ngx_log_handler_pt) (void *ctx, char *buf, size_t len);
+
+
+struct ngx_log_s {
+    ngx_uint_t           log_level;
+    ngx_open_file_t     *file;
+    void                *data;
+    ngx_log_handler_pt   handler;
+};
+
+#define MAX_ERROR_STR	 2048
+
+
+/*********************************/
+
+#if (HAVE_GCC_VARIADIC_MACROS)
+
+#define HAVE_VARIADIC_MACROS  1
+
+#define ngx_log_error(level, log, args...) \
+        if (log->log_level >= level) ngx_log_error_core(level, log, args)
+
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                        const char *fmt, ...);
+
+/*********************************/
+
+#elif (HAVE_C99_VARIADIC_MACROS)
+
+#define HAVE_VARIADIC_MACROS  1
+
+#define ngx_log_error(level, log, ...) \
+        if (log->log_level >= level) ngx_log_error_core(level, log, __VA_ARGS__)
+
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                        const char *fmt, ...);
+
+/*********************************/
+
+#else /* NO VARIADIC MACROS */
+
+#define HAVE_VARIADIC_MACROS  0
+
+void ngx_log_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                   const char *fmt, ...);
+void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                        const char *fmt, va_list args);
+void ngx_log_debug_core(ngx_log_t *log, ngx_err_t err, const char *fmt, ...);
+void ngx_assert_core(ngx_log_t *log, const char *fmt, ...);
+
+
+#endif /* VARIADIC MACROS */
+
+
+/*********************************/
+
+#if (NGX_DEBUG)
+
+#if (HAVE_VARIADIC_MACROS)
+
+#define ngx_log_debug0(level, log, err, fmt) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt)
+
+#define ngx_log_debug1(level, log, err, fmt, arg1) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, arg1)
+
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, arg1, arg2)
+
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, arg1, arg2, arg3)
+
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, arg1, arg2, arg3, arg4)
+
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, \
+                           arg1, arg2, arg3, arg4, arg5)
+
+#define ngx_log_debug6(level, log, err, fmt, \
+                       arg1, arg2, arg3, arg4, arg5, arg6) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, \
+                           arg1, arg2, arg3, arg4, arg5, arg6)
+
+#define ngx_log_debug7(level, log, err, fmt, \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
+    if (log->log_level & level) \
+        ngx_log_error_core(NGX_LOG_DEBUG, log, err, fmt, \
+                           arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+
+#else /* NO VARIADIC MACROS */
+
+#define ngx_log_debug0(level, log, err, fmt) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt)
+
+#define ngx_log_debug1(level, log, err, fmt, arg1) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1)
+
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2)
+
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3)
+
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4)
+
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5)
+
+#define ngx_log_debug6(level, log, err, fmt, \
+                       arg1, arg2, arg3, arg4, arg5, arg6) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)
+
+#define ngx_log_debug7(level, log, err, fmt, \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
+    if (log->log_level & level) \
+        ngx_log_debug_core(log, err, fmt, \
+                       arg1, arg2, arg3, arg4, arg5, arg6, arg7)
+
+#endif
+
+#else /* NO NGX_DEBUG */
+
+#define ngx_log_debug0(level, log, err, fmt)
+#define ngx_log_debug1(level, log, err, fmt, arg1)
+#define ngx_log_debug2(level, log, err, fmt, arg1, arg2)
+#define ngx_log_debug3(level, log, err, fmt, arg1, arg2, arg3)
+#define ngx_log_debug4(level, log, err, fmt, arg1, arg2, arg3, arg4)
+#define ngx_log_debug5(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5)
+#define ngx_log_debug6(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5, arg6)
+#define ngx_log_debug7(level, log, err, fmt, arg1, arg2, arg3, arg4, arg5,    \
+                       arg6, arg7)
+
+#endif
+
+/*********************************/
+
+#define ngx_log_alloc_log(pool, log)  ngx_palloc(pool, log, sizeof(ngx_log_t))
+#define ngx_log_copy_log(new, old)    ngx_memcpy(new, old, sizeof(ngx_log_t))
+
+ngx_log_t *ngx_log_init_stderr();
+#if 0
+ngx_int_t ngx_log_init_error_log();
+#endif
+ngx_log_t *ngx_log_create_errlog(ngx_cycle_t *cycle, ngx_array_t *args);
+char *ngx_set_error_log_levels(ngx_conf_t *cf, ngx_log_t *log);
+
+
+
+extern ngx_module_t  ngx_errlog_module;
+
+
+#endif /* _NGX_LOG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_output_chain.c
@@ -0,0 +1,312 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_NONE      1
+
+
+ngx_inline static ngx_int_t
+    ngx_output_chain_need_to_copy(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf);
+static ngx_int_t ngx_output_chain_copy_buf(ngx_buf_t *dst, ngx_buf_t *src,
+                                           ngx_uint_t sendfile);
+
+
+ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
+{
+    int           rc, last;
+    size_t        size, bsize;
+    ngx_chain_t  *cl, *out, **last_out;
+
+    /*
+     * the short path for the case when the ctx->in chain is empty
+     * and the incoming chain is empty too or it has the single buf
+     * that does not require the copy
+     */
+
+    if (ctx->in == NULL) {
+
+        if (in == NULL) {
+            return ctx->output_filter(ctx->filter_ctx, in);
+        }
+
+        if (in->next == NULL
+            && (!ngx_output_chain_need_to_copy(ctx, in->buf)))
+        {
+            return ctx->output_filter(ctx->filter_ctx, in);
+        }
+    }
+
+    /* add the incoming buf to the chain ctx->in */
+
+    if (in) {
+        if (ngx_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    last = NGX_NONE;
+    out = NULL;
+    last_out = &out;
+
+    for ( ;; ) {
+
+        while (ctx->in) {
+
+            /*
+             * cycle while there are the ctx->in bufs
+             * or there are the free output bufs to copy in
+             */
+
+            bsize = ngx_buf_size(ctx->in->buf);
+
+            if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) {
+
+                ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0,
+                              "zero size buf");
+
+                ctx->in = ctx->in->next;
+
+                continue;
+            }
+
+            if (!ngx_output_chain_need_to_copy(ctx, ctx->in->buf)) {
+
+                /* move the chain link to the output chain */
+
+                cl = ctx->in;
+                ctx->in = cl->next;
+
+                *last_out = cl;
+                last_out = &cl->next;
+                cl->next = NULL;
+
+                continue;
+            }
+
+            if (ctx->buf == NULL) {
+
+                /* get the free buf */
+
+                if (ctx->free) {
+                    ctx->buf = ctx->free->buf;
+                    ctx->free = ctx->free->next;
+
+                } else if (out || ctx->allocated == ctx->bufs.num) {
+
+                    break;
+
+                } else {
+
+                    size = ctx->bufs.size;
+
+                    if (ctx->in->buf->last_buf) {
+
+                        if (bsize < ctx->bufs.size) {
+
+                           /*
+                            * allocate small temp buf for the small last buf
+                            * or its small last part
+                            */
+
+                            size = bsize;
+
+                        } else if (ctx->bufs.num == 1
+                                   && (bsize < ctx->bufs.size
+                                                     + (ctx->bufs.size >> 2)))
+                        {
+                            /*
+                             * allocate a temp buf that equals
+                             * to the last buf if the last buf size is lesser
+                             * than 1.25 of bufs.size and a temp buf is single
+                             */
+
+                            size = bsize;
+                        }
+                    }
+
+                    if (!(ctx->buf = ngx_create_temp_buf(ctx->pool, size))) {
+                        return NGX_ERROR;
+                    }
+
+                    ctx->buf->tag = ctx->tag;
+                    ctx->buf->recycled = 1;
+                    ctx->allocated++;
+                }
+            }
+
+            rc = ngx_output_chain_copy_buf(ctx->buf, ctx->in->buf,
+                                           ctx->sendfile);
+
+            if (rc == NGX_ERROR) {
+                return rc;
+            }
+
+            if (rc == NGX_AGAIN) {
+                if (out) {
+                    break;
+                }
+
+                return rc;
+            }
+
+            /* delete the completed buf from the ctx->in chain */
+
+            if (ngx_buf_size(ctx->in->buf) == 0) {
+                ctx->in = ctx->in->next;
+            }
+
+            ngx_alloc_link_and_set_buf(cl, ctx->buf, ctx->pool, NGX_ERROR);
+            *last_out = cl;
+            last_out = &cl->next;
+            ctx->buf = NULL;
+        }
+
+        if (out == NULL && last != NGX_NONE) {
+            return last;
+        }
+
+        last = ctx->output_filter(ctx->filter_ctx, out);
+
+        ngx_chain_update_chains(&ctx->free, &ctx->busy, &out, ctx->tag);
+        last_out = &out;
+
+        if (last == NGX_ERROR) {
+            return last;
+        }
+    }
+}
+
+
+ngx_inline static ngx_int_t
+    ngx_output_chain_need_to_copy(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf)
+{
+    if (ngx_buf_special(buf)) {
+        return 0;
+    }
+
+    if (!ctx->sendfile) {
+        if (!ngx_buf_in_memory(buf)) {
+            return 1;
+        }
+
+        buf->in_file = 0;
+    }
+
+    if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) {
+        return 1;
+    }
+
+    if (ctx->need_in_temp && (buf->memory || buf->mmap)) {
+        return 1;
+    }
+
+    return 0;
+}
+
+
+static ngx_int_t ngx_output_chain_copy_buf(ngx_buf_t *dst, ngx_buf_t *src,
+                                           ngx_uint_t sendfile)
+{
+    size_t   size;
+    ssize_t  n;
+
+    size = ngx_buf_size(src);
+
+    if (size > (size_t) (dst->end - dst->pos)) {
+        size = dst->end - dst->pos;
+    }
+
+    if (ngx_buf_in_memory(src)) {
+        ngx_memcpy(dst->pos, src->pos, size);
+        src->pos += size;
+        dst->last += size;
+
+        if (src->in_file) {
+            src->file_pos += size;
+        }
+
+        if (src->last_buf && src->pos == src->last) {
+            dst->last_buf = 1;
+        }
+
+    } else {
+        n = ngx_read_file(src->file, dst->pos, size, src->file_pos);
+
+        if (n == NGX_ERROR) {
+            return n;
+        }
+
+#if (NGX_FILE_AIO_READ)
+        if (n == NGX_AGAIN) {
+            return n;
+        }
+#endif
+
+        if ((size_t) n != size) {
+            ngx_log_error(NGX_LOG_ALERT, src->file->log, 0,
+                          ngx_read_file_n " reads only %d of %d from file",
+                          n, size);
+            if (n == 0) {
+                return NGX_ERROR;
+            }
+        }
+
+        src->file_pos += n;
+        dst->last += n;
+
+        if (!sendfile) {
+            dst->in_file = 0;
+        }
+
+        if (src->last_buf && src->file_pos == src->file_last) {
+            dst->last_buf = 1;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_chain_writer(void *data, ngx_chain_t *in)
+{
+    ngx_chain_writer_ctx_t *ctx = data;
+
+    ngx_chain_t  *cl;
+
+
+    for (/* void */; in; in = in->next) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->connection->log, 0,
+                       "WRITER buf: %d", ngx_buf_size(in->buf));
+
+        ngx_alloc_link_and_set_buf(cl, in->buf, ctx->pool, NGX_ERROR);
+        *ctx->last = cl;
+        ctx->last = &cl->next;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->connection->log, 0,
+                   "WRITER0: %X", ctx->out);
+
+    ctx->out = ngx_send_chain(ctx->connection, ctx->out, ctx->limit);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ctx->connection->log, 0,
+                   "WRITER1: %X", ctx->out);
+
+    if (ctx->out == NGX_CHAIN_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (ctx->out == NULL) {
+        ctx->last = &ctx->out;
+        return NGX_OK;
+    }
+
+    return NGX_AGAIN;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_palloc.c
@@ -0,0 +1,215 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log)
+{
+    ngx_pool_t  *p;
+
+    if (!(p = ngx_alloc(size, log))) {
+       return NULL;
+    }
+
+    p->last = (char *) p + sizeof(ngx_pool_t);
+    p->end = (char *) p + size;
+    p->next = NULL;
+    p->large = NULL;
+    p->log = log;
+
+    return p;
+}
+
+
+void ngx_destroy_pool(ngx_pool_t *pool)
+{
+    ngx_pool_t        *p, *n;
+    ngx_pool_large_t  *l;
+
+    for (l = pool->large; l; l = l->next) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                       "free: " PTR_FMT, l->alloc);
+
+        if (l->alloc) {
+            free(l->alloc);
+        }
+    }
+
+#if (NGX_DEBUG)
+
+    /*
+     * we could allocate the pool->log from this pool
+     * so we can not use this log while the free()ing the pool
+     */
+
+    for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
+        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                       "free: " PTR_FMT ", unused: " SIZE_T_FMT,
+                       p, p->end - p->last);
+
+        if (n == NULL) {
+            break;
+        }
+    }
+
+#endif
+
+    for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
+        free(p);
+
+        if (n == NULL) {
+            break;
+        }
+    }
+}
+
+
+void *ngx_palloc(ngx_pool_t *pool, size_t size)
+{
+    char              *m;
+    ngx_pool_t        *p, *n;
+    ngx_pool_large_t  *large, *last;
+
+    if (size <= (size_t) NGX_MAX_ALLOC_FROM_POOL
+        && size <= (size_t) (pool->end - (char *) pool) - sizeof(ngx_pool_t))
+    {
+        for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {
+            m = ngx_align(p->last);
+
+            if ((size_t) (p->end - m) >= size) {
+                p->last = m + size ;
+
+                return m;
+            }
+
+            if (n == NULL) {
+                break;
+            }
+        }
+
+        /* allocate a new pool block */
+
+        if (!(n = ngx_create_pool((size_t) (p->end - (char *) p), p->log))) {
+            return NULL;
+        }
+
+        p->next = n;
+        m = n->last;
+        n->last += size;
+
+        return m;
+    }
+
+    /* allocate a large block */
+
+    large = NULL;
+    last = NULL;
+
+    if (pool->large) {
+        for (last = pool->large; /* void */ ; last = last->next) {
+            if (last->alloc == NULL) {
+                large = last;
+                last = NULL;
+                break;
+            }
+
+            if (last->next == NULL) {
+                break;
+            }
+        }
+    }
+
+    if (large == NULL) {
+        if (!(large = ngx_palloc(pool, sizeof(ngx_pool_large_t)))) {
+            return NULL;
+        }
+
+        large->next = NULL;
+    }
+
+#if 0
+    if (!(p = ngx_memalign(ngx_pagesize, size, pool->log))) {
+        return NULL;
+    }
+#else
+    if (!(p = ngx_alloc(size, pool->log))) {
+        return NULL;
+    }
+#endif
+
+    if (pool->large == NULL) {
+        pool->large = large;
+
+    } else if (last) {
+        last->next = large;
+    }
+
+    large->alloc = p;
+
+    return p;
+}
+
+
+ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p)
+{
+    ngx_pool_large_t  *l;
+
+    for (l = pool->large; l; l = l->next) {
+        if (p == l->alloc) {
+            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
+                           "free: " PTR_FMT, l->alloc);
+            free(l->alloc);
+            l->alloc = NULL;
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+void *ngx_pcalloc(ngx_pool_t *pool, size_t size)
+{
+    void *p;
+
+    p = ngx_palloc(pool, size);
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+#if 0
+
+static void *ngx_get_cached_block(size_t size)
+{
+    void                     *p;
+    ngx_cached_block_slot_t  *slot;
+
+    if (ngx_cycle->cache == NULL) {
+        return NULL;
+    }
+
+    slot = &ngx_cycle->cache[(size + ngx_pagesize - 1) / ngx_pagesize];
+
+    slot->tries++;
+
+    if (slot->number) {
+        p = slot->block;
+        slot->block = slot->block->next;
+        slot->number--;
+        return p;
+    }
+
+    return NULL;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_palloc.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PALLOC_H_INCLUDED_
+#define _NGX_PALLOC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * NGX_MAX_ALLOC_FROM_POOL should be (ngx_page_size - 1), i.e. 4095 on x86.
+ * On FreeBSD 5.x it allows to use the zero copy sending.
+ * On Windows NT it decreases a number of locked pages in a kernel.
+ */
+#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1)
+
+#define NGX_DEFAULT_POOL_SIZE   (16 * 1024)
+
+#define ngx_test_null(p, alloc, rc)  if ((p = alloc) == NULL) { return rc; }
+
+
+typedef struct ngx_pool_large_s  ngx_pool_large_t;
+
+struct ngx_pool_large_s {
+    ngx_pool_large_t  *next;
+    void              *alloc;
+};
+
+
+struct ngx_pool_s {
+    char              *last;
+    char              *end;
+    ngx_pool_t        *next;
+    ngx_pool_large_t  *large;
+    ngx_log_t         *log;
+};
+
+
+void *ngx_alloc(size_t size, ngx_log_t *log);
+void *ngx_calloc(size_t size, ngx_log_t *log);
+
+ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
+void ngx_destroy_pool(ngx_pool_t *pool);
+
+void *ngx_palloc(ngx_pool_t *pool, size_t size);
+void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
+ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
+
+
+#endif /* _NGX_PALLOC_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_parse.c
@@ -0,0 +1,212 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t ngx_parse_size(ngx_str_t *line)
+{
+    u_char     last;
+    size_t     len;
+    ngx_int_t  scale, size;
+
+    len = line->len;
+    last = line->data[len - 1];
+
+    switch (last) {
+    case 'K':
+    case 'k':
+        len--;
+        scale = 1024;
+        break;
+
+    case 'M':
+    case 'm':
+        len--;
+        scale = 1024 * 1024;
+        break;
+
+    default:
+        scale = 1;
+    }
+
+    size = ngx_atoi(line->data, len);
+    if (size == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    size *= scale;
+
+    return size;
+}
+
+
+ngx_int_t ngx_parse_time(ngx_str_t *line, ngx_int_t sec)
+{
+    size_t      len;
+    u_char     *start, last;
+    ngx_int_t   value, total, scale;
+    ngx_uint_t  max, i;
+    enum {
+        st_start = 0,
+        st_year,
+        st_month,
+        st_week,
+        st_day,
+        st_hour,
+        st_min,
+        st_sec,
+        st_msec,
+        st_last
+    } step;
+
+
+    start = line->data;
+    len = 0;
+    total = 0;
+    step = sec ? st_start : st_month;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i < line->len) {
+            if (line->data[i] != ' ') {
+                len++;
+                continue;
+            }
+
+            if (line->data[i] == ' ' && len == 0) {
+                start = &line->data[i + 1];
+                continue;
+            }
+        }
+
+        if (len == 0) {
+            break;
+        }
+
+        last = line->data[i - 1];
+
+        switch (last) {
+        case 'y':
+            if (step > st_start) {
+                return NGX_ERROR;
+            }
+            step = st_year;
+            len--;
+            max = 68;
+            scale = 60 * 60 * 24 * 365;
+            break;
+
+        case 'M':
+            if (step > st_year) {
+                return NGX_ERROR;
+            }
+            step = st_month;
+            len--;
+            max = 828;
+            scale = 60 * 60 * 24 * 30;
+            break;
+
+        case 'w':
+            if (step > st_month) {
+                return NGX_ERROR;
+            }
+            step = st_week;
+            len--;
+            max = 3550;
+            scale = 60 * 60 * 24 * 7;
+            break;
+
+        case 'd':
+            if (step > st_week) {
+                return NGX_ERROR;
+            }
+            step = st_day;
+            len--;
+            max = 24855;
+            scale = 60 * 60 * 24;
+            break;
+
+        case 'h':
+            if (step > st_day) {
+                return NGX_ERROR;
+            }
+            step = st_hour;
+            len--;
+            max = 596523;
+            scale = 60 * 60;
+            break;
+
+        case 'm':
+            if (step > st_hour) {
+                return NGX_ERROR;
+            }
+            step = st_min;
+            len--;
+            max = 35791394;
+            scale = 60;
+            break;
+
+        case 's':
+            len--;
+
+            if (line->data[i - 2] == 'm') {
+                if (sec || step > st_sec) {
+                    return NGX_ERROR;
+                }
+                step = st_msec;
+                len--;
+                max = 2147483647;
+                scale = 1;
+                break;
+            }
+
+            if (step > st_min) {
+                return NGX_ERROR;
+            }
+
+            step = st_sec;
+            max = 2147483647;
+            scale = 1;
+            break;
+
+        default:
+            step = st_last;
+            max = 2147483647;
+            scale = 1;
+        }
+
+        value = ngx_atoi(start, len);
+        if (value == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        if (step != st_msec && !sec) {
+            scale *= 1000;
+            max /= 1000;
+        }
+
+        if ((u_int) value > max) {
+            return NGX_PARSE_LARGE_TIME;
+        }
+
+        total += value * scale;
+
+        if ((u_int) total > 2147483647) {
+            return NGX_PARSE_LARGE_TIME;
+        }
+
+        if (i >= line->len) {
+            break;
+        }
+
+        len = 0;
+        start = &line->data[i + 1];
+    }
+
+    return total;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_parse.h
@@ -0,0 +1,22 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PARSE_H_INCLUDED_
+#define _NGX_PARSE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_PARSE_LARGE_TIME  -2
+
+
+ngx_int_t ngx_parse_size(ngx_str_t *line);
+ngx_int_t ngx_parse_time(ngx_str_t *line, ngx_int_t sec);
+
+
+#endif /* _NGX_PARSE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_rbtree.c
@@ -0,0 +1,349 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * The code is based on the algorithm described in the "Introduction
+ * to Algorithms" by Cormen, Leiserson and Rivest.
+ */
+
+#define ngx_rbt_red(node)           ((node)->color = 1)
+#define ngx_rbt_black(node)         ((node)->color = 0)
+#define ngx_rbt_is_red(node)        ((node)->color)
+#define ngx_rbt_is_black(node)      (!ngx_rbt_is_red(node))
+#define ngx_rbt_copy_color(n1, n2)  (n1->color = n2->color)
+
+
+ngx_inline void ngx_rbtree_left_rotate(ngx_rbtree_t **root,
+                                       ngx_rbtree_t *sentinel,
+                                       ngx_rbtree_t *node);
+ngx_inline void ngx_rbtree_right_rotate(ngx_rbtree_t **root,
+                                        ngx_rbtree_t *sentinel,
+                                        ngx_rbtree_t *node);
+
+
+void ngx_rbtree_insert(ngx_rbtree_t **root, ngx_rbtree_t *sentinel,
+                       ngx_rbtree_t *node)
+{
+    ngx_rbtree_t  *temp;
+
+    /* a binary tree insert */
+
+    if (*root == sentinel) {
+        node->parent = NULL;
+        node->left = sentinel;
+        node->right = sentinel;
+        ngx_rbt_black(node);
+        *root = node;
+
+        return;
+    }
+
+    temp = *root;
+
+    for ( ;; ) {
+        if (node->key < temp->key) {
+            if (temp->left == sentinel) {
+                temp->left = node;
+                break;
+            }
+
+            temp = temp->left;
+            continue;
+        }
+
+        if (temp->right == sentinel) {
+            temp->right = node;
+            break;
+        }
+
+        temp = temp->right;
+        continue;
+    }
+
+    node->parent = temp;
+    node->left = sentinel;
+    node->right = sentinel;
+
+
+    /* re-balance tree */
+
+    ngx_rbt_red(node);
+
+    while (node != *root && ngx_rbt_is_red(node->parent)) {
+
+        if (node->parent == node->parent->parent->left) {
+            temp = node->parent->parent->right;
+
+            if (ngx_rbt_is_red(temp)) {
+                ngx_rbt_black(node->parent);
+                ngx_rbt_black(temp);
+                ngx_rbt_red(node->parent->parent);
+                node = node->parent->parent;
+
+            } else {
+                if (node == node->parent->right) {
+                    node = node->parent;
+                    ngx_rbtree_left_rotate(root, sentinel, node);
+                }
+
+                ngx_rbt_black(node->parent);
+                ngx_rbt_red(node->parent->parent);
+                ngx_rbtree_right_rotate(root, sentinel, node->parent->parent);
+            }
+
+        } else {
+            temp = node->parent->parent->left;
+
+            if (ngx_rbt_is_red(temp)) {
+                ngx_rbt_black(node->parent);
+                ngx_rbt_black(temp);
+                ngx_rbt_red(node->parent->parent);
+                node = node->parent->parent;
+
+            } else {
+                if (node == node->parent->left) {
+                    node = node->parent;
+                    ngx_rbtree_right_rotate(root, sentinel, node);
+                }
+
+                ngx_rbt_black(node->parent);
+                ngx_rbt_red(node->parent->parent);
+                ngx_rbtree_left_rotate(root, sentinel, node->parent->parent);
+            }
+        }
+
+    }
+
+    ngx_rbt_black(*root);
+}
+
+
+void ngx_rbtree_delete(ngx_rbtree_t **root, ngx_rbtree_t *sentinel,
+                       ngx_rbtree_t *node)
+{
+    ngx_int_t      is_red;
+    ngx_rbtree_t  *subst, *temp, *w;
+
+    /* a binary tree delete */
+
+    if (node->left == sentinel) {
+        temp = node->right;
+        subst = node;
+
+    } else if (node->right == sentinel) {
+        temp = node->left;
+        subst = node;
+
+    } else {
+        subst = ngx_rbtree_min(node->right, sentinel);
+
+        if (subst->left != sentinel) {
+            temp = subst->left;
+        } else {
+            temp = subst->right;
+        }
+    }
+
+    if (subst == *root) {
+        *root = temp;
+        ngx_rbt_black(temp);
+
+        /* DEBUG stuff */
+        node->left = NULL;
+        node->right = NULL;
+        node->parent = NULL;
+        node->key = 0;
+
+        return;
+    }
+
+    is_red = ngx_rbt_is_red(subst);
+
+    if (subst == subst->parent->left) {
+        subst->parent->left = temp;
+
+    } else {
+        subst->parent->right = temp;
+    }
+
+    if (subst == node) {
+
+        temp->parent = subst->parent;
+
+    } else {
+
+        if (subst->parent == node) {
+            temp->parent = subst;
+
+        } else {
+            temp->parent = subst->parent;
+        }
+
+        subst->left = node->left;
+        subst->right = node->right;
+        subst->parent = node->parent;
+        ngx_rbt_copy_color(subst, node);
+
+        if (node == *root) {
+            *root = subst;
+
+        } else {
+            if (node == node->parent->left) {
+                node->parent->left = subst;
+            } else {
+                node->parent->right = subst;
+            }
+        }
+
+        if (subst->left != sentinel) {
+            subst->left->parent = subst;
+        }
+
+        if (subst->right != sentinel) {
+            subst->right->parent = subst;
+        }
+
+        /* DEBUG stuff */
+        node->left = NULL;
+        node->right = NULL;
+        node->parent = NULL;
+        node->key = 0;
+    }
+
+    if (is_red) {
+        return;
+    }
+
+    /* a delete fixup */
+
+    while (temp != *root && ngx_rbt_is_black(temp)) {
+
+        if (temp == temp->parent->left) {
+            w = temp->parent->right;
+
+            if (ngx_rbt_is_red(w)) {
+                ngx_rbt_black(w);
+                ngx_rbt_red(temp->parent);
+                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
+                w = temp->parent->right;
+            }
+
+            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
+                ngx_rbt_red(w);
+                temp = temp->parent;
+
+            } else {
+                if (ngx_rbt_is_black(w->right)) {
+                    ngx_rbt_black(w->left);
+                    ngx_rbt_red(w);
+                    ngx_rbtree_right_rotate(root, sentinel, w);
+                    w = temp->parent->right;
+                }
+
+                ngx_rbt_copy_color(w, temp->parent);
+                ngx_rbt_black(temp->parent);
+                ngx_rbt_black(w->right);
+                ngx_rbtree_left_rotate(root, sentinel, temp->parent);
+                temp = *root;
+            }
+
+        } else {
+            w = temp->parent->left;
+
+            if (ngx_rbt_is_red(w)) {
+                ngx_rbt_black(w);
+                ngx_rbt_red(temp->parent);
+                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
+                w = temp->parent->left;
+            }
+
+            if (ngx_rbt_is_black(w->left) && ngx_rbt_is_black(w->right)) {
+                ngx_rbt_red(w);
+                temp = temp->parent;
+
+            } else {
+                if (ngx_rbt_is_black(w->left)) {
+                    ngx_rbt_black(w->right);
+                    ngx_rbt_red(w);
+                    ngx_rbtree_left_rotate(root, sentinel, w);
+                    w = temp->parent->left;
+                }
+
+                ngx_rbt_copy_color(w, temp->parent);
+                ngx_rbt_black(temp->parent);
+                ngx_rbt_black(w->left);
+                ngx_rbtree_right_rotate(root, sentinel, temp->parent);
+                temp = *root;
+            }
+        }
+    }
+
+    ngx_rbt_black(temp);
+}
+
+
+ngx_inline void ngx_rbtree_left_rotate(ngx_rbtree_t **root,
+                                       ngx_rbtree_t *sentinel,
+                                       ngx_rbtree_t *node)
+{
+    ngx_rbtree_t  *temp;
+
+    temp = node->right;
+    node->right = temp->left;
+
+    if (temp->left != sentinel) {
+        temp->left->parent = node;
+    }
+
+    temp->parent = node->parent;
+
+    if (node == *root) {
+        *root = temp;
+
+    } else if (node == node->parent->left) {
+        node->parent->left = temp;
+
+    } else {
+        node->parent->right = temp;
+    }
+
+    temp->left = node;
+    node->parent = temp;
+}
+
+
+ngx_inline void ngx_rbtree_right_rotate(ngx_rbtree_t **root,
+                                        ngx_rbtree_t *sentinel,
+                                        ngx_rbtree_t *node)
+{
+    ngx_rbtree_t  *temp;
+
+    temp = node->left;
+    node->left = temp->right;
+
+    if (temp->right != sentinel) {
+        temp->right->parent = node;
+    }
+
+    temp->parent = node->parent;
+
+    if (node == *root) {
+        *root = temp;
+
+    } else if (node == node->parent->right) {
+        node->parent->right = temp;
+
+    } else {
+        node->parent->left = temp;
+    }
+
+    temp->right = node;
+    node->parent = temp;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_rbtree.h
@@ -0,0 +1,43 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_RBTREE_H_INCLUDED_
+#define _NGX_RBTREE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct ngx_rbtree_s  ngx_rbtree_t;
+
+struct ngx_rbtree_s {
+   ngx_int_t       key;
+   ngx_rbtree_t   *left;
+   ngx_rbtree_t   *right;
+   ngx_rbtree_t   *parent;
+   char            color;
+};
+
+
+void ngx_rbtree_insert(ngx_rbtree_t **root, ngx_rbtree_t *sentinel,
+                       ngx_rbtree_t *node);
+void ngx_rbtree_delete(ngx_rbtree_t **root, ngx_rbtree_t *sentinel,
+                       ngx_rbtree_t *node);
+
+
+ngx_inline static ngx_rbtree_t *ngx_rbtree_min(ngx_rbtree_t *node,
+                                               ngx_rbtree_t *sentinel)
+{
+   while (node->left != sentinel) {
+       node = node->left;
+   }
+
+   return node;
+}
+
+
+#endif /* _NGX_RBTREE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_regex.c
@@ -0,0 +1,125 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static void *ngx_regex_malloc(size_t size);
+static void ngx_regex_free(void *p);
+
+
+static ngx_pool_t  *ngx_pcre_pool;
+
+
+void ngx_regex_init()
+{
+    pcre_malloc = ngx_regex_malloc;
+    pcre_free = ngx_regex_free;
+}
+
+
+ngx_regex_t *ngx_regex_compile(ngx_str_t *pattern, ngx_int_t options,
+                               ngx_pool_t *pool, ngx_str_t *err)
+{
+    int              erroff;
+    const char      *errstr;
+    ngx_regex_t     *re;
+#if (NGX_THREADS)
+    ngx_core_tls_t  *tls;
+
+#if (NGX_SUPPRESS_WARN)
+    tls = NULL;
+#endif
+
+    if (ngx_threaded) {
+        tls = ngx_thread_get_tls(ngx_core_tls_key);
+        tls->pool = pool;
+    } else {
+        ngx_pcre_pool = pool;
+    }
+
+#else
+
+    ngx_pcre_pool = pool;
+
+#endif
+
+    re = pcre_compile((const char *) pattern->data, (int) options,
+                      &errstr, &erroff, NULL);
+
+    if (re == NULL) {
+       if ((size_t) erroff == pattern->len) {
+           ngx_snprintf((char *) err->data, err->len - 1,
+                        "pcre_compile() failed: %s in \"%s\"",
+                        errstr, pattern->data);
+        } else {
+           ngx_snprintf((char *) err->data, err->len - 1,
+                        "pcre_compile() failed: %s in \"%s\" at \"%s\"",
+                        errstr, pattern->data, pattern->data + erroff);
+        }
+    }
+
+    /* ensure that there is no current pool */
+
+#if (NGX_THREADS)
+    if (ngx_threaded) {
+        tls->pool = NULL;
+    } else {
+        ngx_pcre_pool = NULL;
+    }
+#else
+    ngx_pcre_pool = NULL;
+#endif
+
+    return re;
+}
+
+
+ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s,
+                         int *matches, ngx_int_t size)
+{
+    int  rc;
+
+    rc = pcre_exec(re, NULL, (const char *) s->data, s->len, 0, 0,
+                   matches, size);
+
+    if (rc == -1) {
+        return NGX_DECLINED;
+    }
+
+    return rc;
+}
+
+
+static void *ngx_regex_malloc(size_t size)
+{
+    ngx_pool_t      *pool;
+#if (NGX_THREADS)
+    ngx_core_tls_t  *tls;
+
+    if (ngx_threaded) {
+        tls = ngx_thread_get_tls(ngx_core_tls_key);
+        pool = tls->pool;
+    } else {
+        pool = ngx_pcre_pool;
+    }
+#else
+    pool = ngx_pcre_pool;
+#endif
+
+    if (pool) {
+        return ngx_palloc(pool, size);
+    }
+
+    return NULL;
+}
+
+
+static void ngx_regex_free(void *p)
+{
+    return;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_regex.h
@@ -0,0 +1,30 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_REGEX_H_INCLUDED_
+#define _NGX_REGEX_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <pcre.h>
+
+
+#define NGX_REGEX_CASELESS  PCRE_CASELESS
+
+typedef pcre  ngx_regex_t;
+
+void ngx_regex_init();
+ngx_regex_t *ngx_regex_compile(ngx_str_t *pattern, ngx_int_t options,
+                               ngx_pool_t *pool, ngx_str_t *err);
+ngx_int_t ngx_regex_exec(ngx_regex_t *re, ngx_str_t *s,
+                         int *matches, ngx_int_t size);
+
+#define ngx_regex_exec_n  "pcre_exec()"
+
+
+#endif /* _NGX_REGEX_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_spinlock.c
@@ -0,0 +1,48 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void ngx_spinlock(ngx_atomic_t *lock, ngx_uint_t spin)
+{
+
+#if (NGX_HAVE_ATOMIC_OPS)
+
+    ngx_uint_t  tries;
+
+    tries = 0;
+
+    for ( ;; ) {
+
+        if (*lock) {
+            if (ngx_ncpu > 1 && tries++ < spin) {
+                continue;
+            }
+
+            ngx_sched_yield();
+
+            tries = 0;
+
+        } else {
+            if (ngx_atomic_cmp_set(lock, 0, 1)) {
+                return;
+            }
+        }
+    }
+
+#else
+
+#if (NGX_THREADS)
+
+#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
+
+#endif
+
+#endif
+
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_string.c
@@ -0,0 +1,284 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n)
+{
+    if (n == 0) {
+        return dst;
+    }
+
+    for (/* void */; --n; dst++, src++) {
+        *dst = *src;
+
+        if (*dst == '\0') {
+            return dst;
+        }
+    }
+
+    *dst = '\0';
+
+    return dst;
+}
+
+
+ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n)
+{
+    if (n == 0) {
+        return 0;
+    }
+
+    n--;
+
+    for ( ;; ) {
+        if (s1[n] != s2[n]) {
+            return s1[n] - s2[n];
+        }
+
+        if (n == 0) {
+            return 0;
+        }
+
+        n--;
+    }
+}
+
+
+ngx_int_t ngx_atoi(u_char *line, size_t n)
+{
+    ngx_int_t  value;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    for (value = 0; n--; line++) {
+        if (*line < '0' || *line > '9') {
+            return NGX_ERROR;
+        }
+
+        value = value * 10 + (*line - '0');
+    }
+
+    if (value < 0) {
+        return NGX_ERROR;
+
+    } else {
+        return value;
+    }
+}
+
+
+ngx_int_t ngx_hextoi(u_char *line, size_t n)
+{
+    u_char     ch;
+    ngx_int_t  value;
+
+    if (n == 0) {
+        return NGX_ERROR;
+    }
+
+    for (value = 0; n--; line++) {
+        ch = *line;
+
+        if (ch >= '0' && ch <= '9') {
+            value = value * 16 + (ch - '0');
+            continue;
+        }
+
+        if (ch >= 'A' && ch <= 'F') {
+            value = value * 16 + (ch - 'A' + 10);
+            continue;
+        }
+
+        if (ch >= 'a' && ch <= 'f') {
+            value = value * 16 + (ch - 'a' + 10);
+            continue;
+        }
+
+        return NGX_ERROR;
+    }
+
+    if (value < 0) {
+        return NGX_ERROR;
+
+    } else {
+        return value;
+    }
+}
+
+
+void ngx_md5_text(u_char *text, u_char *md5)
+{
+    int            i;
+    static u_char  hex[] = "0123456789abcdef";
+
+    for (i = 0; i < 16; i++) {
+        *text++ = hex[md5[i] >> 4];
+        *text++ = hex[md5[i] & 0xf];
+    }
+
+    *text = '\0';
+}
+
+
+void ngx_encode_base64(ngx_str_t *src, ngx_str_t *dst)
+{
+    u_char         *d, *s;
+    size_t          len;
+    static u_char   basis64[] =
+            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    len = src->len;
+    s = src->data;
+    d = dst->data;
+
+    while (len > 2) {
+        *d++ = basis64[(s[0] >> 2) & 0x3f];
+        *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)];
+        *d++ = basis64[((s[1] & 0x0f) << 2) | (s[2] >> 6)];
+        *d++ = basis64[s[2] & 0x3f];
+
+        s += 3;
+        len -= 3;
+    }
+
+    if (len) {
+        *d++ = basis64[(s[0] >> 2) & 0x3f];
+
+        if (len == 1) {
+            *d++ = basis64[(s[0] & 3) << 4];
+            *d++ = '=';
+
+        } else {
+            *d++ = basis64[((s[0] & 3) << 4) | (s[1] >> 4)];
+            *d++ = basis64[(s[1] & 0x0f) << 2];
+        }
+
+        *d++ = '=';
+    }
+
+    dst->len = d - dst->data;
+}
+
+
+ngx_int_t ngx_decode_base64(ngx_str_t *src, ngx_str_t *dst)
+{
+    size_t          len;
+    u_char         *d, *s;
+    static u_char   basis64[] =
+        { 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
+          52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+          77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+          15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
+          77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+          41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+          77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 };
+
+    for (len = 0; len < src->len; len++) {
+        if (src->data[len] == '=') {
+            break;
+        }
+
+        if (basis64[src->data[len]] == 77) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (len % 4 == 1) {
+        return NGX_ERROR;
+    }
+
+    s = src->data;
+    d = dst->data;
+
+    while (len > 3) {
+        *d++ = (u_char) (basis64[s[0]] << 2 | basis64[s[1]] >> 4);
+        *d++ = (u_char) (basis64[s[1]] << 4 | basis64[s[2]] >> 2);
+        *d++ = (u_char) (basis64[s[2]] << 6 | basis64[s[3]]);
+
+        s += 4;
+        len -= 4;
+    }
+
+    if (len > 1) {
+        *d++ = (u_char) (basis64[s[0]] << 2 | basis64[s[1]] >> 4);
+    }
+
+    if (len > 2) {
+        *d++ = (u_char) (basis64[s[1]] << 4 | basis64[s[2]] >> 2);
+    }
+
+    dst->len = d - dst->data;
+
+    return NGX_OK;
+}
+
+
+#if 0
+char *ngx_psprintf(ngx_pool_t *p, const char *fmt, ...)
+{
+    va_list    args;
+
+    va_start(args, fmt);
+
+    while (*fmt) {
+         switch(*fmt++) {
+         case '%':
+             switch(*fmt++) {
+             case 's':
+                 s = va_arg(args, char *);
+                 n += ngx_strlen(s);
+                 break;
+
+             default:
+                 n++;
+         }
+         default:
+             n++;
+         }
+    }
+
+    str = ngx_palloc(p, n);
+
+    va_start(args, fmt);
+
+    for (i = 0; i < n; i++) {
+         switch(*fmt++) {
+         case '%':
+             switch(*fmt++) {
+             case 's':
+                 s = va_arg(args, char *);
+                 while (str[i++] = s);
+                 break;
+
+             default:
+                 n++;
+         }
+         default:
+             str[i] = *fmt;
+         }
+    }
+
+    len += ngx_vsnprintf(errstr + len, sizeof(errstr) - len - 1, fmt, args);
+
+    va_end(args);
+
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_string.h
@@ -0,0 +1,94 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_STRING_H_INCLUDED_
+#define _NGX_STRING_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    size_t    len;
+    u_char   *data;
+} ngx_str_t;
+
+
+#define ngx_string(str)  { sizeof(str) - 1, (u_char *) str }
+#define ngx_null_string  { 0, NULL }
+
+
+#if (WIN32)
+
+#define ngx_strncasecmp(s1, s2, n)                                           \
+                           strnicmp((const char *) s1, (const char *) s2, n)
+#define ngx_strcasecmp(s1, s2)                                               \
+                           stricmp((const char *) s1, (const char *) s2)
+
+#define ngx_snprintf       _snprintf
+#define ngx_vsnprintf      _vsnprintf
+
+#else
+
+#define ngx_strncasecmp(s1, s2, n)                                           \
+                           strncasecmp((const char *) s1, (const char *) s2, n)
+#define ngx_strcasecmp(s1, s2)                                               \
+                           strcasecmp((const char *) s1, (const char *) s2)
+
+#define ngx_snprintf       snprintf
+#define ngx_vsnprintf      vsnprintf
+
+#endif
+
+
+#define ngx_strncmp(s1, s2, n)                                               \
+                            strncmp((const char *) s1, (const char *) s2, n)
+
+/* msvc and icc compile strcmp() to inline loop */
+#define ngx_strcmp(s1, s2)  strcmp((const char *) s1, (const char *) s2)
+
+#define ngx_strstr(s1, s2)  strstr((const char *) s1, (const char *) s2)
+#define ngx_strlen(s)       strlen((const char *) s)
+
+/*
+ * msvc and icc compile memset() to the inline "rep stos"
+ * while ZeroMemory() and bzero() are the calls.
+ * icc may also inline several mov's of a zeroed register for small blocks.
+ */
+#define ngx_memzero(buf, n)       memset(buf, 0, n)
+
+/* msvc and icc compile memcpy() to the inline "rep movs" */
+#define ngx_memcpy(dst, src, n)   memcpy(dst, src, n)
+#define ngx_cpymem(dst, src, n)   ((u_char *) memcpy(dst, src, n)) + n
+
+/* msvc and icc compile memcmp() to the inline loop */
+#define ngx_memcmp                memcmp
+
+u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n);
+ngx_int_t ngx_rstrncmp(u_char *s1, u_char *s2, size_t n);
+
+ngx_int_t ngx_atoi(u_char *line, size_t n);
+ngx_int_t ngx_hextoi(u_char *line, size_t n);
+
+void ngx_md5_text(u_char *text, u_char *md5);
+
+
+#define ngx_base64_encoded_length(len)  (((len + 2) / 3) * 4)
+#define ngx_base64_decoded_length(len)  (((len + 3) / 4) * 3)
+
+void ngx_encode_base64(ngx_str_t *src, ngx_str_t *dst);
+ngx_int_t ngx_decode_base64(ngx_str_t *src, ngx_str_t *dst);
+
+
+#define  ngx_qsort                qsort
+
+
+#define  ngx_value_helper(n)      #n
+#define  ngx_value(n)             ngx_value_helper(n)
+
+
+#endif /* _NGX_STRING_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_table.h
@@ -0,0 +1,27 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_TABLE_H_INCLUDED_
+#define _NGX_TABLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef ngx_array_t  ngx_table_t;
+
+typedef struct {
+    ngx_str_t  key;
+    ngx_str_t  value;
+} ngx_table_elt_t;
+
+
+#define ngx_create_table(p, n)  ngx_create_array(p, n, 2 * sizeof(ngx_str_t))
+#define ngx_push_table(t)       ngx_push_array(t)
+
+
+#endif /* _NGX_TABLE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_times.c
@@ -0,0 +1,346 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_epoch_msec_t  ngx_elapsed_msec;
+ngx_epoch_msec_t  ngx_old_elapsed_msec;
+ngx_epoch_msec_t  ngx_start_msec;
+
+static ngx_tm_t   ngx_cached_gmtime;
+static ngx_int_t  ngx_gmtoff;
+
+
+/*
+ * In the threaded mode only one thread updates the cached time and strings
+ * and these operations are protected by the mutex.  The reading of the cached
+ * time and strings is not protected by the mutex.  To avoid the race
+ * conditions for non-atomic values we use the NGX_TIME_SLOTS slots to store
+ * time value and strings.  Thus thread may get the corrupted values only
+ * if it is preempted while copying and then it is not scheduled to run
+ * more than NGX_TIME_SLOTS seconds.
+ */
+
+#if (NGX_THREADS)
+
+#define NGX_TIME_SLOTS  60
+static ngx_uint_t       slot = NGX_TIME_SLOTS;
+
+static ngx_mutex_t     *ngx_time_mutex;
+
+#else
+
+#define NGX_TIME_SLOTS  1
+#define slot            0
+
+#endif
+
+
+#if (NGX_THREADS && (TIME_T_SIZE > SIG_ATOMIC_T_SIZE))
+
+volatile time_t  *ngx_cached_time;
+static time_t     cached_time[NGX_TIME_SLOTS];
+
+#else
+
+volatile time_t   ngx_cached_time;
+
+#endif
+
+
+ngx_thread_volatile ngx_str_t  ngx_cached_err_log_time;
+ngx_thread_volatile ngx_str_t  ngx_cached_http_time;
+ngx_thread_volatile ngx_str_t  ngx_cached_http_log_time;
+
+
+static u_char  cached_err_log_time[NGX_TIME_SLOTS]
+                                               [sizeof("1970/09/28 12:00:00")];
+static u_char  cached_http_time[NGX_TIME_SLOTS]
+                                     [sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
+static u_char  cached_http_log_time[NGX_TIME_SLOTS]
+                                        [sizeof("28/Sep/1970:12:00:00 +0600")];
+
+
+static char  *week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static char  *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                           "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+
+void ngx_time_init()
+{
+    struct timeval  tv;
+
+    ngx_memzero(&ngx_cached_gmtime, sizeof(ngx_tm_t));
+#ifdef ngx_tm_zone
+    ngx_cached_gmtime.ngx_tm_zone = "GMT";
+#endif
+
+    ngx_cached_err_log_time.len = sizeof("1970/09/28 12:00:00") - 1;
+    ngx_cached_http_time.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
+    ngx_cached_http_log_time.len = sizeof("28/Sep/1970:12:00:00 +0600") - 1;
+
+#if (NGX_THREADS && (TIME_T_SIZE > SIG_ATOMIC_T_SIZE))
+    ngx_cached_time = &cached_time[0];
+#endif
+
+    ngx_gettimeofday(&tv);
+
+    ngx_start_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
+    ngx_old_elapsed_msec = 0;
+    ngx_elapsed_msec = 0;
+
+#if !(WIN32)
+    tzset();
+#endif
+
+    ngx_time_update(tv.tv_sec);
+}
+
+
+#if (NGX_THREADS)
+
+ngx_int_t ngx_time_mutex_init(ngx_log_t *log)
+{
+    if (!(ngx_time_mutex = ngx_mutex_init(log, NGX_MUTEX_LIGHT))) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+void ngx_time_update(time_t s)
+{
+    u_char    *p;
+    ngx_tm_t   tm;
+
+    if (ngx_time() == s) {
+        return;
+    }
+
+#if (NGX_THREADS)
+
+    if (ngx_mutex_trylock(ngx_time_mutex) != NGX_OK) {
+        return;
+    }
+
+    if (slot == NGX_TIME_SLOTS) {
+        slot = 0;
+    } else {
+        slot++;
+    }
+
+#if (NGX_THREADS && (TIME_T_SIZE > SIG_ATOMIC_T_SIZE))
+    ngx_cached_time = &cached_time[slot];
+#endif
+
+#endif
+
+    ngx_time() = s;
+
+    ngx_gmtime(s, &ngx_cached_gmtime);
+
+
+    p = cached_http_time[slot];
+
+    ngx_snprintf((char *) p, sizeof("Mon, 28 Sep 1970 06:00:00 GMT"),
+                 "%s, %02d %s %4d %02d:%02d:%02d GMT",
+                 week[ngx_cached_gmtime.ngx_tm_wday],
+                 ngx_cached_gmtime.ngx_tm_mday,
+                 months[ngx_cached_gmtime.ngx_tm_mon - 1],
+                 ngx_cached_gmtime.ngx_tm_year,
+                 ngx_cached_gmtime.ngx_tm_hour,
+                 ngx_cached_gmtime.ngx_tm_min,
+                 ngx_cached_gmtime.ngx_tm_sec);
+
+    ngx_cached_http_time.data = p;
+
+
+#if (HAVE_GETTIMEZONE)
+
+    ngx_gmtoff = ngx_gettimezone();
+    ngx_gmtime(s + ngx_gmtoff * 60, &tm);
+
+#elif (HAVE_GMTOFF)
+
+    ngx_localtime(&tm);
+    ngx_gmtoff = tm.ngx_tm_gmtoff / 60;
+
+#else
+
+    ngx_localtime(&tm);
+    ngx_gmtoff = ngx_timezone(tm.ngx_tm_isdst);
+
+#endif
+
+
+    p = cached_err_log_time[slot];
+
+    ngx_snprintf((char *) p, sizeof("1970/09/28 12:00:00"),
+                 "%4d/%02d/%02d %02d:%02d:%02d",
+                 tm.ngx_tm_year, tm.ngx_tm_mon,
+                 tm.ngx_tm_mday, tm.ngx_tm_hour,
+                 tm.ngx_tm_min, tm.ngx_tm_sec);
+
+    ngx_cached_err_log_time.data = p;
+
+
+    p = cached_http_log_time[slot];
+
+    ngx_snprintf((char *) p, sizeof("28/Sep/1970:12:00:00 +0600"),
+                 "%02d/%s/%d:%02d:%02d:%02d %c%02d%02d",
+                 tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
+                 tm.ngx_tm_year, tm.ngx_tm_hour,
+                 tm.ngx_tm_min, tm.ngx_tm_sec,
+                 ngx_gmtoff < 0 ? '-' : '+',
+                 abs(ngx_gmtoff / 60), abs(ngx_gmtoff % 60));
+
+    ngx_cached_http_log_time.data = p;
+
+
+#if (NGX_THREADS)
+    ngx_mutex_unlock(ngx_time_mutex);
+#endif
+
+}
+
+
+size_t ngx_http_time(u_char *buf, time_t t)
+{
+    ngx_tm_t  tm;
+
+    ngx_gmtime(t, &tm);
+
+    return ngx_snprintf((char *) buf, sizeof("Mon, 28 Sep 1970 06:00:00 GMT"),
+                                      "%s, %02d %s %4d %02d:%02d:%02d GMT",
+                                      week[tm.ngx_tm_wday],
+                                      tm.ngx_tm_mday,
+                                      months[tm.ngx_tm_mon - 1],
+                                      tm.ngx_tm_year,
+                                      tm.ngx_tm_hour,
+                                      tm.ngx_tm_min,
+                                      tm.ngx_tm_sec);
+}
+
+
+size_t ngx_http_cookie_time(u_char *buf, time_t t)
+{
+    ngx_tm_t  tm;
+
+    ngx_gmtime(t, &tm);
+
+    /*
+     * Netscape 3.x does not understand 4-digit years at all and
+     * 2-digit years more than "37"
+     */
+
+    if (tm.ngx_tm_year > 2037) {
+        return ngx_snprintf((char *) buf,
+                                      sizeof("Mon, 28-Sep-1970 06:00:00 GMT"),
+                                      "%s, %02d-%s-%d %02d:%02d:%02d GMT",
+                                      week[tm.ngx_tm_wday],
+                                      tm.ngx_tm_mday,
+                                      months[tm.ngx_tm_mon - 1],
+                                      tm.ngx_tm_year,
+                                      tm.ngx_tm_hour,
+                                      tm.ngx_tm_min,
+                                      tm.ngx_tm_sec);
+    } else {
+        return ngx_snprintf((char *) buf,
+                                      sizeof("Mon, 28-Sep-70 06:00:00 GMT"),
+                                      "%s, %02d-%s-%02d %02d:%02d:%02d GMT",
+                                      week[tm.ngx_tm_wday],
+                                      tm.ngx_tm_mday,
+                                      months[tm.ngx_tm_mon - 1],
+                                      tm.ngx_tm_year % 100,
+                                      tm.ngx_tm_hour,
+                                      tm.ngx_tm_min,
+                                      tm.ngx_tm_sec);
+    }
+}
+
+
+void ngx_gmtime(time_t t, ngx_tm_t *tp)
+{
+    ngx_int_t  sec, min, hour, mday, mon, year, wday, yday, days;
+
+    days = t / 86400;
+
+    /* Jaunary 1, 1970 was Thursday */
+    wday = (4 + days) % 7;
+
+    t %= 86400;
+    hour = t / 3600;
+    t %= 3600;
+    min = t / 60;
+    sec = t % 60;
+
+    /* the algorithm based on Gauss's formula */
+
+    days = days - (31 + 28) + 719527;
+
+    year = days * 400 / (365 * 400 + 100 - 4 + 1);
+    yday = days - (365 * year + year / 4 - year / 100 + year / 400);
+
+    mon = (yday + 31) * 12 / 367;
+    mday = yday - (mon * 367 / 12 - 31);
+
+    mon += 2;
+
+    if (yday >= 306) {
+
+        /*
+         * there is no "yday" in Win32 SYSTEMTIME
+         *
+         * yday -= 306;
+         */
+
+        year++;
+        mon -= 12;
+
+        if (mday == 0) {
+            /* Jaunary 31 */
+            mon = 1;
+            mday = 31;
+
+        } else if (mon == 2) {
+
+            if ((year % 4 == 0) && (year % 100 || (year % 400 == 0))) {
+                if (mday > 29) {
+                    mon = 3;
+                    mday -= 29;
+                }
+
+            } else if (mday > 28) {
+                mon = 3;
+                mday -= 28;
+            }
+        }
+
+/*
+ *  there is no "yday" in Win32 SYSTEMTIME
+ *
+ *  } else {
+ *      yday += 31 + 28;
+ *
+ *      if ((year % 4 == 0) && (year % 100 || (year % 400 == 0))) {
+ *           yday++;
+ *      }
+ */
+    }
+
+    tp->ngx_tm_sec = (ngx_tm_sec_t) sec;
+    tp->ngx_tm_min = (ngx_tm_min_t) min;
+    tp->ngx_tm_hour = (ngx_tm_hour_t) hour;
+    tp->ngx_tm_mday = (ngx_tm_mday_t) mday;
+    tp->ngx_tm_mon = (ngx_tm_mon_t) mon;
+    tp->ngx_tm_year = (ngx_tm_year_t) year;
+    tp->ngx_tm_wday = (ngx_tm_wday_t) wday;
+}
new file mode 100644
--- /dev/null
+++ b/src/core/ngx_times.h
@@ -0,0 +1,58 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_TIMES_H_INCLUDED_
+#define _NGX_TIMES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void ngx_time_init();
+void ngx_time_update(time_t s);
+size_t ngx_http_time(u_char *buf, time_t t);
+size_t ngx_http_cookie_time(u_char *buf, time_t t);
+void ngx_gmtime(time_t t, ngx_tm_t *tp);
+
+#if (NGX_THREADS)
+ngx_int_t ngx_time_mutex_init(ngx_log_t *log);
+#endif
+
+#if (NGX_THREADS && (TIME_T_SIZE > SIG_ATOMIC_T_SIZE))
+
+#define ngx_time()        *ngx_cached_time
+extern volatile time_t    *ngx_cached_time;
+
+#else
+
+#define ngx_time()         ngx_cached_time
+extern volatile time_t     ngx_cached_time;
+
+#endif
+
+
+extern ngx_thread_volatile ngx_str_t  ngx_cached_err_log_time;
+extern ngx_thread_volatile ngx_str_t  ngx_cached_http_time;
+extern ngx_thread_volatile ngx_str_t  ngx_cached_http_log_time;
+
+extern ngx_epoch_msec_t    ngx_start_msec;
+
+/*
+ * msecs elapsed since ngx_start_msec in the current event cycle,
+ * used in ngx_event_add_timer() and ngx_event_find_timer()
+ */
+extern ngx_epoch_msec_t  ngx_elapsed_msec;
+
+/*
+ * msecs elapsed since ngx_start_msec in the previous event cycle,
+ * used in ngx_event_expire_timers()
+ */
+extern ngx_epoch_msec_t  ngx_old_elapsed_msec;
+
+
+
+#endif /* _NGX_TIMES_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_aio_module.c
@@ -0,0 +1,209 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_aio.h>
+
+#if (HAVE_KQUEUE)
+#include <ngx_kqueue_module.h>
+#endif
+
+
+static int ngx_aio_init(ngx_cycle_t *cycle);
+static void ngx_aio_done(ngx_cycle_t *cycle);
+static int ngx_aio_add_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_aio_del_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_aio_del_connection(ngx_connection_t *c, u_int flags);
+static int ngx_aio_process_events(ngx_cycle_t *cycle);
+
+
+ngx_os_io_t ngx_os_aio = {
+    ngx_aio_read,
+    ngx_aio_read_chain,
+    ngx_aio_write,
+    ngx_aio_write_chain,
+    NGX_HAVE_ZEROCOPY
+};
+
+
+static ngx_str_t      aio_name = ngx_string("aio");
+
+ngx_event_module_t  ngx_aio_module_ctx = {
+    &aio_name,
+    NULL,                                  /* create configuration */
+    NULL,                                  /* init configuration */
+
+    {
+        ngx_aio_add_event,                 /* add an event */
+        ngx_aio_del_event,                 /* delete an event */
+        NULL,                              /* enable an event */
+        NULL,                              /* disable an event */
+        NULL,                              /* add an connection */
+        ngx_aio_del_connection,            /* delete an connection */
+        NULL,                              /* process the changes */
+        ngx_aio_process_events,            /* process the events */
+        ngx_aio_init,                      /* init the events */
+        ngx_aio_done                       /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_aio_module = {
+    NGX_MODULE,
+    &ngx_aio_module_ctx,                   /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+
+#if (HAVE_KQUEUE)
+
+static int ngx_aio_init(ngx_cycle_t *cycle)
+{
+    if (ngx_kqueue_module_ctx.actions.init(cycle) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ngx_io = ngx_os_aio;
+
+    ngx_event_flags = NGX_USE_AIO_EVENT;
+    ngx_event_actions = ngx_aio_module_ctx.actions;
+
+
+    return NGX_OK;
+}
+
+
+static void ngx_aio_done(ngx_cycle_t *cycle)
+{
+    ngx_kqueue_module_ctx.actions.done(cycle);
+}
+
+
+/* The event adding and deleting are needed for the listening sockets */
+
+static int ngx_aio_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+    return ngx_kqueue_module_ctx.actions.add(ev, event, flags);
+}
+
+
+static int ngx_aio_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    return ngx_kqueue_module_ctx.actions.del(ev, event, flags);
+}
+
+
+static int ngx_aio_del_connection(ngx_connection_t *c, u_int flags)
+{
+    int  rc;
+
+    if (c->read->active == 0 && c->write->active == 0) {
+        return NGX_OK;
+    }
+
+    rc = aio_cancel(c->fd, NULL);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_cancel: %d", rc);
+
+    if (rc == AIO_CANCELED) {
+        c->read->active = c->write->active = 0;
+        return NGX_OK;
+    }
+
+    if (rc == AIO_ALLDONE) {
+        c->read->active = c->write->active = 0;
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "aio_cancel() returned AIO_ALLDONE");
+        return NGX_OK;
+    }
+
+    if (rc == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "aio_cancel() failed");
+        return NGX_ERROR;
+    }
+
+    if (rc == AIO_NOTCANCELED) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+                      "aio_cancel() returned AIO_NOTCANCELED");
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static int ngx_aio_process_events(ngx_cycle_t *cycle)
+{
+    return ngx_kqueue_module_ctx.actions.process_events(cycle);
+}
+
+#endif /* HAVE_KQUEUE */
+
+
+#if 0
+
+/* 1 */
+int ngx_posix_aio_process_events(ngx_log_t *log)
+{
+    listen via SIGIO;
+    aio_* via SIGxxx;
+
+    sigsuspend()/sigwaitinfo()/sigtimedwait();
+}
+
+/* 2 */
+int ngx_posix_aio_process_events(ngx_log_t *log)
+{
+    unmask signal
+
+    listen via SIGIO;
+
+    /* BUG: SIGIO can be delivered before aio_*() */
+
+    aio_suspend()/aiowait()/aio_waitcomplete() with timeout
+
+    mask signal
+
+    if (ngx_socket_errno == NGX_EINTR)
+        look listen
+        select()/accept() nb listen sockets
+    else
+        aio
+}
+
+/* 3 */
+int ngx_posix_aio_process_events(ngx_log_t *log)
+{
+#if 0
+    unmask signal
+
+    /* BUG: AIO signal can be delivered before select() */
+
+    select(listen);
+
+    mask signal
+#endif
+
+    pselect(listen, mask);
+
+    if (ngx_socket_errno == NGX_EINTR)
+        look ready array
+}
+
+void aio_sig_handler(int signo, siginfo_t *siginfo, void *context)
+{
+    push siginfo->si_value.sival_ptr
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_aio_module.h
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_AIO_MODULE_H_INCLUDED_
+#define _NGX_AIO_MODULE_H_INCLUDED_
+
+
+#include <ngx_types.h>
+#include <ngx_log.h>
+#include <ngx_event.h>
+
+
+int ngx_aio_init(int max_connections, ngx_log_t *log);
+int ngx_aio_process_events(ngx_log_t *log);
+
+
+#endif /* _NGX_AIO_MODULE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_devpoll_module.c
@@ -0,0 +1,578 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (TEST_BUILD_DEVPOLL)
+
+/* Solaris declarations */
+
+#define POLLREMOVE   0x0800
+#define DP_POLL      0xD001
+
+struct dvpoll {
+    struct pollfd  *dp_fds;
+    int             dp_nfds;
+    int             dp_timeout;
+};
+
+#endif
+
+
+typedef struct {
+    u_int  changes;
+    u_int  events;
+} ngx_devpoll_conf_t;
+
+
+static int ngx_devpoll_init(ngx_cycle_t *cycle);
+static void ngx_devpoll_done(ngx_cycle_t *cycle);
+static int ngx_devpoll_add_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_devpoll_del_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_devpoll_set_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_devpoll_process_events(ngx_cycle_t *cycle);
+
+static void *ngx_devpoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int              dp = -1;
+static struct pollfd   *change_list, *event_list;
+static u_int            nchanges, max_changes, nevents;
+
+static ngx_event_t    **change_index;
+
+
+static ngx_str_t      devpoll_name = ngx_string("/dev/poll");
+
+static ngx_command_t  ngx_devpoll_commands[] = {
+
+    {ngx_string("devpoll_changes"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_devpoll_conf_t, changes),
+     NULL},
+
+    {ngx_string("devpoll_events"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_devpoll_conf_t, events),
+     NULL},
+
+    ngx_null_command
+};
+
+
+ngx_event_module_t  ngx_devpoll_module_ctx = {
+    &devpoll_name,
+    ngx_devpoll_create_conf,               /* create configuration */
+    ngx_devpoll_init_conf,                 /* init configuration */
+
+    {
+        ngx_devpoll_add_event,             /* add an event */
+        ngx_devpoll_del_event,             /* delete an event */
+        ngx_devpoll_add_event,             /* enable an event */
+        ngx_devpoll_del_event,             /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* process the changes */
+        ngx_devpoll_process_events,        /* process the events */
+        ngx_devpoll_init,                  /* init the events */
+        ngx_devpoll_done,                  /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_devpoll_module = {
+    NGX_MODULE,
+    &ngx_devpoll_module_ctx,               /* module context */
+    ngx_devpoll_commands,                  /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static int ngx_devpoll_init(ngx_cycle_t *cycle)
+{
+    size_t               n;
+    ngx_devpoll_conf_t  *dpcf;
+
+    dpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_devpoll_module);
+
+    if (dp == -1) {
+        dp = open("/dev/poll", O_RDWR);
+
+        if (dp == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "open(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+    }
+
+    if (max_changes < dpcf->changes) {
+        if (nchanges) {
+            n = nchanges * sizeof(struct pollfd);
+            if (write(dp, change_list, n) != (ssize_t) n) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "write(/dev/poll) failed");
+                return NGX_ERROR;
+            }
+
+            nchanges = 0;
+        }
+
+        if (change_list) {
+            ngx_free(change_list);
+        }
+
+        ngx_test_null(change_list,
+                      ngx_alloc(sizeof(struct pollfd) * dpcf->changes,
+                                cycle->log),
+                      NGX_ERROR);
+
+        if (change_index) {
+            ngx_free(change_index);
+        }
+
+        ngx_test_null(change_index,
+                      ngx_alloc(sizeof(ngx_event_t *) * dpcf->changes,
+                                cycle->log),
+                      NGX_ERROR);
+    }
+
+    max_changes = dpcf->changes;
+
+    if (nevents < dpcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        ngx_test_null(event_list,
+                      ngx_alloc(sizeof(struct pollfd) * dpcf->events,
+                                cycle->log),
+                      NGX_ERROR);
+    }
+
+    nevents = dpcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_devpoll_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void ngx_devpoll_done(ngx_cycle_t *cycle)
+{
+    if (close(dp) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close(/dev/poll) failed");
+    }
+
+    dp = -1;
+
+    ngx_free(change_list);
+    ngx_free(event_list);
+    ngx_free(change_index);
+
+    change_list = NULL;
+    event_list = NULL;
+    change_index = NULL;
+    max_changes = 0;
+    nchanges = 0;
+    nevents = 0;
+}
+
+
+static int ngx_devpoll_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+#if (NGX_DEBUG)
+    ngx_connection_t *c;
+#endif
+
+#if (NGX_READ_EVENT != POLLIN)
+    if (event == NGX_READ_EVENT) {
+        event = POLLOUT;
+#if (NGX_WRITE_EVENT != POLLOUT)
+    } else {
+        event = POLLIN;
+#endif
+    }
+#endif
+
+#if (NGX_DEBUG)
+    c = ev->data;
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll add event: fd:%d ev:%04X", c->fd, event);
+#endif
+
+    ev->active = 1;
+    return ngx_devpoll_set_event(ev, event, 0);
+}
+
+
+static int ngx_devpoll_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll del event: fd:%d ev:%04X", c->fd, event);
+
+    if (ngx_devpoll_set_event(ev, POLLREMOVE, flags) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ev->active = 0;
+
+    if (flags & NGX_CLOSE_EVENT) {
+        return NGX_OK;
+    }
+
+    /* we need to restore the second event if it exists */
+
+    if (event == NGX_READ_EVENT) {
+        if (ev->accept) {
+            return NGX_OK;
+        }
+
+        e = c->write;
+        event = POLLOUT;
+
+    } else {
+        e = c->read;
+        event = POLLIN;
+    }
+
+    if (e) {
+        return ngx_devpoll_set_event(e, event, 0);
+    }
+
+    return NGX_OK;
+}
+
+
+static int ngx_devpoll_set_event(ngx_event_t *ev, int event, u_int flags)
+{
+    size_t             n;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "devpoll fd:%d ev:%d fl:%d", c->fd, event, flags);
+
+    if (nchanges >= max_changes) {
+        ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+                      "/dev/pool change list is filled up");
+
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    change_list[nchanges].fd = c->fd;
+    change_list[nchanges].events = event;
+    change_list[nchanges].revents = 0;
+
+    change_index[nchanges] = ev;
+    ev->index = nchanges;
+
+    nchanges++;
+
+    if (flags & NGX_CLOSE_EVENT) {
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+int ngx_devpoll_process_events(ngx_cycle_t *cycle)
+{
+    int                 events;
+    ngx_int_t           i;
+    ngx_uint_t          j, lock, accept_lock, expire;
+    size_t              n;
+    ngx_msec_t          timer;
+    ngx_err_t           err;
+    ngx_cycle_t       **old_cycle;
+    ngx_event_t        *rev, *wev;
+    ngx_connection_t   *c;
+    ngx_epoch_msec_t    delta;
+    struct dvpoll       dvp;
+    struct timeval      tv;
+
+    for ( ;; ) {
+        timer = ngx_event_find_timer();
+
+        if (timer != 0) {
+            break;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "devpoll expired timer");
+
+        ngx_event_expire_timers((ngx_msec_t)
+                                    (ngx_elapsed_msec - ngx_old_elapsed_msec));
+    }
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+    if (timer == NGX_TIMER_INFINITE) {
+        expire = 0;
+
+    } else {
+        expire = 1;
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec;
+    accept_lock = 0;
+
+    if (ngx_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return NGX_ERROR;
+            } 
+
+            if (ngx_accept_mutex_held) {
+                accept_lock = 1;
+
+            } else if (timer == NGX_TIMER_INFINITE
+                       || timer > ngx_accept_mutex_delay)
+            {
+                timer = ngx_accept_mutex_delay;
+                expire = 0;
+            }
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "devpoll timer: %d", timer);
+
+    if (nchanges) {
+        n = nchanges * sizeof(struct pollfd);
+        if (write(dp, change_list, n) != (ssize_t) n) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "write(/dev/poll) failed");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    dvp.dp_fds = event_list;
+    dvp.dp_nfds = nevents;
+    dvp.dp_timeout = timer;
+    events = ioctl(dp, DP_POLL, &dvp);
+
+    if (events == -1) {
+        err = ngx_errno;
+    } else {
+        err = 0;
+    }
+
+    nchanges = 0;
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    if (err) {
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "ioctl(DP_POLL) failed");
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "devpoll timer: %d, delta: %d", timer, (int) delta);
+    } else {
+        if (events == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "ioctl(DP_POLL) returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    lock = 1;
+
+    for (i = 0; i < events; i++) {
+        c = &ngx_cycle->connections[event_list[i].fd];
+
+        if (c->fd == -1) {
+            old_cycle = ngx_old_cycles.elts;
+            for (j = 0; j < ngx_old_cycles.nelts; j++) {
+                if (old_cycle[j] == NULL) {
+                    continue;
+                }
+                c = &old_cycle[j]->connections[event_list[i].fd];
+                if (c->fd != -1) {
+                    break;
+                }
+            }
+        }
+
+        if (c->fd == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "unknown cycle");
+            exit(1);
+        }
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "devpoll: fd:%d, ev:%04X, rev:%04X",
+                       event_list[i].fd,
+                       event_list[i].events, event_list[i].revents);
+
+        if (event_list[i].revents & (POLLERR|POLLHUP|POLLNVAL)) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "ioctl(DP_POLL) error fd:%d ev:%04X rev:%04X",
+                          event_list[i].fd,
+                          event_list[i].events, event_list[i].revents);
+        }
+
+        if (event_list[i].revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL))
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "strange ioctl(DP_POLL) events "
+                          "fd:%d ev:%04X rev:%04X",
+                          event_list[i].fd,
+                          event_list[i].events, event_list[i].revents);
+        }
+
+        wev = c->write;
+
+        if ((event_list[i].events & (POLLOUT|POLLERR|POLLHUP)) && wev->active) {
+            wev->ready = 1;
+
+            if (!ngx_threaded && !ngx_accept_mutex_held) {
+                wev->event_handler(wev);
+
+            } else {
+                ngx_post_event(wev);
+            }
+        }
+
+        /*
+         * POLLIN must be handled after POLLOUT because we use
+         * the optimization to avoid the unnecessary mutex locking/unlocking
+         * if the accept event is the last one.
+         */
+
+        rev = c->read;
+
+        if ((event_list[i].events & (POLLIN|POLLERR|POLLHUP)) && rev->active) {
+            rev->ready = 1;
+
+            if (!ngx_threaded && !ngx_accept_mutex_held) {
+                rev->event_handler(rev);
+
+            } else if (!rev->accept) {
+                ngx_post_event(rev);
+
+            } else if (ngx_accept_disabled <= 0) {
+                ngx_mutex_unlock(ngx_posted_events_mutex);
+
+                c->read->event_handler(rev);
+
+                if (ngx_accept_disabled > 0) { 
+                    ngx_accept_mutex_unlock();
+                    accept_lock = 0;
+                }
+
+                if (i + 1 == events) {
+                    lock = 0;
+                    break;
+                }
+
+                if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                    if (accept_lock) {
+                        ngx_accept_mutex_unlock();
+                    }
+                    return NGX_ERROR;
+                }
+            }
+        }
+    }
+
+    if (accept_lock) {
+        ngx_accept_mutex_unlock();
+    }
+
+    if (lock) {
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (!ngx_threaded) {
+        ngx_event_process_posted(cycle);
+    }
+
+    return NGX_OK;
+}
+
+
+static void *ngx_devpoll_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_devpoll_conf_t  *dpcf;
+
+    ngx_test_null(dpcf, ngx_palloc(cycle->pool, sizeof(ngx_devpoll_conf_t)),
+                  NGX_CONF_ERROR);
+
+    dpcf->changes = NGX_CONF_UNSET;
+    dpcf->events = NGX_CONF_UNSET;
+
+    return dpcf;
+}
+
+
+static char *ngx_devpoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_devpoll_conf_t *dpcf = conf;
+
+    ngx_conf_init_unsigned_value(dpcf->changes, 512);
+    ngx_conf_init_unsigned_value(dpcf->events, 512);
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_epoll_module.c
@@ -0,0 +1,657 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (TEST_BUILD_EPOLL)
+
+/* epoll declarations */
+
+#define EPOLLIN        0x001
+#define EPOLLPRI       0x002
+#define EPOLLOUT       0x004
+#define EPOLLRDNORM    0x040
+#define EPOLLRDBAND    0x080
+#define EPOLLWRNORM    0x100
+#define EPOLLWRBAND    0x200
+#define EPOLLMSG       0x400
+#define EPOLLERR       0x008
+#define EPOLLHUP       0x010
+
+#define EPOLLET        0x80000000
+#define EPOLLONESHOT   0x40000000
+
+#define EPOLL_CTL_ADD  1
+#define EPOLL_CTL_DEL  2
+#define EPOLL_CTL_MOD  3
+
+typedef union epoll_data {
+    void         *ptr;
+    int           fd;
+    uint32_t      u32;
+    uint64_t      u64;
+} epoll_data_t;
+
+struct epoll_event {
+    uint32_t      events;
+    epoll_data_t  data;
+};
+
+int epoll_create(int size);
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
+int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout);
+
+int epoll_create(int size)
+{
+    return -1;
+}
+
+int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
+{
+    return -1;
+}
+
+int epoll_wait(int epfd, struct epoll_event *events, int nevents, int timeout)
+{
+    return -1;
+}
+
+#endif
+
+
+typedef struct {
+    u_int  events;
+} ngx_epoll_conf_t;
+
+
+static int ngx_epoll_init(ngx_cycle_t *cycle);
+static void ngx_epoll_done(ngx_cycle_t *cycle);
+static int ngx_epoll_add_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_epoll_del_event(ngx_event_t *ev, int event, u_int flags);
+static int ngx_epoll_add_connection(ngx_connection_t *c);
+static int ngx_epoll_del_connection(ngx_connection_t *c, u_int flags);
+static int ngx_epoll_process_events(ngx_cycle_t *cycle);
+
+static void *ngx_epoll_create_conf(ngx_cycle_t *cycle);
+static char *ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+static int                  ep = -1;
+static struct epoll_event  *event_list;
+static u_int                nevents;
+
+
+static ngx_str_t      epoll_name = ngx_string("epoll");
+
+static ngx_command_t  ngx_epoll_commands[] = {
+
+    {ngx_string("epoll_events"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_epoll_conf_t, events),
+     NULL},
+
+    ngx_null_command
+};
+
+
+ngx_event_module_t  ngx_epoll_module_ctx = {
+    &epoll_name,
+    ngx_epoll_create_conf,               /* create configuration */
+    ngx_epoll_init_conf,                 /* init configuration */
+
+    {
+        ngx_epoll_add_event,             /* add an event */
+        ngx_epoll_del_event,             /* delete an event */
+        ngx_epoll_add_event,             /* enable an event */
+        ngx_epoll_del_event,             /* disable an event */
+        ngx_epoll_add_connection,        /* add an connection */
+        ngx_epoll_del_connection,        /* delete an connection */
+        NULL,                            /* process the changes */
+        ngx_epoll_process_events,        /* process the events */
+        ngx_epoll_init,                  /* init the events */
+        ngx_epoll_done,                  /* done the events */
+    }
+};
+
+ngx_module_t  ngx_epoll_module = {
+    NGX_MODULE,
+    &ngx_epoll_module_ctx,               /* module context */
+    ngx_epoll_commands,                  /* module directives */
+    NGX_EVENT_MODULE,                    /* module type */
+    NULL,                                /* init module */
+    NULL                                 /* init process */
+};
+
+
+static int ngx_epoll_init(ngx_cycle_t *cycle)
+{
+    size_t             n;
+    ngx_event_conf_t  *ecf;
+    ngx_epoll_conf_t  *epcf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
+
+    if (ep == -1) {
+        ep = epoll_create(ecf->connections / 2);
+
+        if (ep == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "epoll_create() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    if (nevents < epcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
+                               cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    nevents = epcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_epoll_module_ctx.actions;
+
+#if (HAVE_CLEAR_EVENT)
+    ngx_event_flags = NGX_USE_CLEAR_EVENT
+#else
+    ngx_event_flags = NGX_USE_LEVEL_EVENT
+#endif
+                      |NGX_HAVE_GREEDY_EVENT
+                      |NGX_USE_EPOLL_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void ngx_epoll_done(ngx_cycle_t *cycle)
+{
+    if (close(ep) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "epoll close() failed");
+    }
+
+    ep = -1;
+
+    ngx_free(event_list);
+
+    event_list = NULL;
+    nevents = 0;
+}
+
+
+static int ngx_epoll_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+    int                  op, prev;
+    ngx_event_t         *e;
+    ngx_connection_t    *c;
+    struct epoll_event   ee;
+
+    c = ev->data;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        prev = EPOLLOUT;
+#if (NGX_READ_EVENT != EPOLLIN)
+        event = EPOLLIN;
+#endif
+
+    } else {
+        e = c->read;
+        prev = EPOLLIN;
+#if (NGX_WRITE_EVENT != EPOLLOUT)
+        event = EPOLLOUT;
+#endif
+    }
+
+    if (e->active) {
+        op = EPOLL_CTL_MOD;
+        event |= prev;
+
+    } else {
+        op = EPOLL_CTL_ADD;
+    }
+
+    ee.events = event | flags;
+    ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "epoll add event: fd:%d op:%d ev:%08X",
+                   c->fd, op, ee.events);
+
+    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    ev->active = 1;
+#if 0
+    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+#endif
+
+    return NGX_OK;
+}
+
+
+static int ngx_epoll_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    int                  op, prev;
+    ngx_event_t         *e;
+    ngx_connection_t    *c;
+    struct epoll_event   ee;
+
+    /*
+     * when the file descriptor is closed the epoll automatically deletes
+     * it from its queue so we do not need to delete explicity the event
+     * before the closing the file descriptor
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        ev->active = 0;
+        return NGX_OK;
+    }
+
+    c = ev->data;
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+        prev = EPOLLOUT;
+
+    } else {
+        e = c->read;
+        prev = EPOLLIN;
+    }
+
+    if (e->active) {
+        op = EPOLL_CTL_MOD;
+        ee.events = prev | flags;
+        ee.data.ptr = (void *) ((uintptr_t) c | ev->instance);
+
+    } else {
+        op = EPOLL_CTL_DEL;
+        ee.events = 0;
+        ee.data.ptr = NULL;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "epoll del event: fd:%d op:%d ev:%08X",
+                   c->fd, op, ee.events);
+
+    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    ev->active = 0;
+
+    return NGX_OK;
+}
+
+
+static int ngx_epoll_add_connection(ngx_connection_t *c)
+{
+    struct epoll_event  ee;
+
+    ee.events = EPOLLIN|EPOLLOUT|EPOLLET;
+    ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "epoll add connection: fd:%d ev:%08X", c->fd, ee.events);
+
+    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
+        return NGX_ERROR;
+    }
+
+    c->read->active = 1;
+    c->write->active = 1;
+
+    return NGX_OK;
+}
+
+
+static int ngx_epoll_del_connection(ngx_connection_t *c, u_int flags)
+{
+    int                  op;
+    struct epoll_event   ee;
+
+    /*
+     * when the file descriptor is closed the epoll automatically deletes
+     * it from its queue so we do not need to delete explicity the event
+     * before the closing the file descriptor
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        c->read->active = 0;
+        c->write->active = 0;
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "epoll del connection: fd:%d", c->fd);
+
+    op = EPOLL_CTL_DEL;
+    ee.events = 0;
+    ee.data.ptr = NULL;
+
+    if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "epoll_ctl(%d, %d) failed", op, c->fd);
+        return NGX_ERROR;
+    }
+
+    c->read->active = 0;
+    c->write->active = 0;
+
+    return NGX_OK;
+}
+
+
+int ngx_epoll_process_events(ngx_cycle_t *cycle)
+{
+    int                events;
+    size_t             n;
+    ngx_int_t          instance, i;
+    ngx_uint_t         lock, accept_lock, expire;
+    ngx_err_t          err;
+    ngx_log_t         *log;
+    ngx_msec_t         timer;
+    ngx_event_t       *rev, *wev;
+    struct timeval     tv;
+    ngx_connection_t  *c;
+    ngx_epoch_msec_t   delta;
+
+    for ( ;; ) { 
+        timer = ngx_event_find_timer();
+
+#if (NGX_THREADS)
+
+        if (timer == NGX_TIMER_ERROR) {
+            return NGX_ERROR;
+        }
+
+        if (timer == NGX_TIMER_INFINITE || timer > 500) {
+            timer = 500;
+            break;
+        }
+
+#endif
+
+        if (timer != 0) {
+            break;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "epoll expired timer");
+
+        ngx_event_expire_timers((ngx_msec_t)
+                                    (ngx_elapsed_msec - ngx_old_elapsed_msec));
+
+        if (ngx_posted_events && ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+        }
+    }
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+    if (timer == NGX_TIMER_INFINITE) {
+        expire = 0;
+
+    } else {
+        expire = 1;
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec;
+    accept_lock = 0;
+
+    if (ngx_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ngx_accept_mutex_held) {
+                accept_lock = 1;
+
+            } else if (timer == NGX_TIMER_INFINITE
+                       || timer > ngx_accept_mutex_delay)
+            {
+                timer = ngx_accept_mutex_delay;
+                expire = 0;
+            }
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "epoll timer: %d", timer);
+
+    events = epoll_wait(ep, event_list, nevents, timer);
+
+    if (events == -1) {
+        err = ngx_errno;
+    } else {
+        err = 0;
+    }
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "epoll timer: %d, delta: %d", timer, (int) delta);
+    } else {
+        if (events == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "epoll_wait() returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    if (err) {
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "epoll_wait() failed");
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    if (events > 0) {
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        } 
+
+        lock = 1;
+
+    } else {
+        lock =0;
+    }
+
+    log = cycle->log;
+
+    for (i = 0; i < events; i++) {
+        c = event_list[i].data.ptr;
+
+        instance = (uintptr_t) c & 1;
+        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
+
+        rev = c->read;
+
+        if (c->fd == -1 || rev->instance != instance) {
+
+            /*
+             * the stale event from a file descriptor
+             * that was just closed in this iteration
+             */
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "epoll: stale event " PTR_FMT, c);
+            continue;
+        }
+
+#if (NGX_DEBUG0)
+        log = c->log ? c->log : cycle->log;
+#endif
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "epoll: fd:%d ev:%04X d:" PTR_FMT,
+                       c->fd, event_list[i].events, event_list[i].data);
+
+        if (event_list[i].events & (EPOLLERR|EPOLLHUP)) {
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                           "epoll_wait() error on fd:%d ev:%04X",
+                           c->fd, event_list[i].events);
+        }
+
+        if (event_list[i].events & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "strange epoll_wait() events fd:%d ev:%04X",
+                          c->fd, event_list[i].events);
+        }
+
+        wev = c->write;
+
+        if ((event_list[i].events & (EPOLLOUT|EPOLLERR|EPOLLHUP))
+            && wev->active)
+        {
+            if (ngx_threaded) {
+                wev->posted_ready = 1;
+                ngx_post_event(wev);
+
+            } else {
+                wev->ready = 1;
+
+                if (!ngx_accept_mutex_held) {
+                    wev->event_handler(wev);
+
+                } else {
+                    ngx_post_event(wev);
+                }
+            }
+        }
+
+        /*
+         * EPOLLIN must be handled after EPOLLOUT because we use
+         * the optimization to avoid the unnecessary mutex locking/unlocking
+         * if the accept event is the last one.
+         */
+
+        if ((event_list[i].events & (EPOLLIN|EPOLLERR|EPOLLHUP))
+            && rev->active)
+        {
+            if (ngx_threaded && !rev->accept) {
+                rev->posted_ready = 1;
+
+                ngx_post_event(rev);
+
+                continue;
+            }
+
+            rev->ready = 1;
+
+            if (!ngx_threaded && !ngx_accept_mutex_held) {
+                rev->event_handler(rev);
+
+            } else if (!rev->accept) {
+                ngx_post_event(rev);
+
+            } else if (ngx_accept_disabled <= 0) {
+
+                ngx_mutex_unlock(ngx_posted_events_mutex);
+
+                rev->event_handler(rev);
+
+                if (ngx_accept_disabled > 0) {
+                    ngx_accept_mutex_unlock();
+                    accept_lock = 0;
+                }
+
+                if (i + 1 == events) {
+                    lock = 0;
+                    break;
+                }
+
+                if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                    if (accept_lock) {
+                        ngx_accept_mutex_unlock();
+                    }
+                    return NGX_ERROR;
+                }
+            }
+        }
+    }
+
+    if (accept_lock) {
+        ngx_accept_mutex_unlock();
+    }
+
+    if (lock) {
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (ngx_posted_events) {
+        if (ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+
+        } else {
+            ngx_event_process_posted(cycle);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void *ngx_epoll_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_epoll_conf_t  *epcf;
+
+    ngx_test_null(epcf, ngx_palloc(cycle->pool, sizeof(ngx_epoll_conf_t)),
+                  NGX_CONF_ERROR);
+
+    epcf->events = NGX_CONF_UNSET;
+
+    return epcf;
+}
+
+
+static char *ngx_epoll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_epoll_conf_t *epcf = conf;
+
+    ngx_conf_init_unsigned_value(epcf->events, 512);
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_kqueue_module.c
@@ -0,0 +1,800 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_kqueue_module.h>
+
+
+typedef struct {
+    int  changes;
+    int  events;
+} ngx_kqueue_conf_t;
+
+
+static ngx_int_t ngx_kqueue_init(ngx_cycle_t *cycle);
+static void ngx_kqueue_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_kqueue_add_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_kqueue_del_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_kqueue_set_event(ngx_event_t *ev, int filter, u_int flags);
+static ngx_int_t ngx_kqueue_process_changes(ngx_cycle_t *cycle, ngx_uint_t try);
+static ngx_int_t ngx_kqueue_process_events(ngx_cycle_t *cycle);
+static ngx_inline void ngx_kqueue_dump_event(ngx_log_t *log,
+                                             struct kevent *kev);
+
+static void *ngx_kqueue_create_conf(ngx_cycle_t *cycle);
+static char *ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+int                    ngx_kqueue = -1;
+
+/*
+ * The "change_list" should be declared as ngx_thread_volatile.
+ * However, the use of the change_list is localized in kqueue functions and
+ * is protected by the mutex so even the "icc -ipo" should not build the code
+ * with the race condition.  Thus we avoid the declaration to make a more
+ * readable code.
+ */
+
+static struct kevent  *change_list, *change_list0, *change_list1;
+static struct kevent  *event_list;
+static int             max_changes, nchanges, nevents;
+
+#if (NGX_THREADS)
+static ngx_mutex_t    *list_mutex;
+static ngx_mutex_t    *kevent_mutex;
+#endif
+
+
+
+static ngx_str_t      kqueue_name = ngx_string("kqueue");
+
+static ngx_command_t  ngx_kqueue_commands[] = {
+
+    {ngx_string("kqueue_changes"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_kqueue_conf_t, changes),
+     NULL},
+
+    {ngx_string("kqueue_events"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_kqueue_conf_t, events),
+     NULL},
+
+    ngx_null_command
+};
+
+
+ngx_event_module_t  ngx_kqueue_module_ctx = {
+    &kqueue_name,
+    ngx_kqueue_create_conf,                /* create configuration */
+    ngx_kqueue_init_conf,                  /* init configuration */
+
+    {
+        ngx_kqueue_add_event,              /* add an event */
+        ngx_kqueue_del_event,              /* delete an event */
+        ngx_kqueue_add_event,              /* enable an event */
+        ngx_kqueue_del_event,              /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        ngx_kqueue_process_changes,        /* process the changes */
+        ngx_kqueue_process_events,         /* process the events */
+        ngx_kqueue_init,                   /* init the events */
+        ngx_kqueue_done                    /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_kqueue_module = {
+    NGX_MODULE,
+    &ngx_kqueue_module_ctx,                /* module context */
+    ngx_kqueue_commands,                   /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+
+static ngx_int_t ngx_kqueue_init(ngx_cycle_t *cycle)
+{
+    struct timespec     ts;
+    ngx_kqueue_conf_t  *kcf;
+
+    kcf = ngx_event_get_conf(cycle->conf_ctx, ngx_kqueue_module);
+
+    if (ngx_kqueue == -1) {
+        ngx_kqueue = kqueue();
+
+        if (ngx_kqueue == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "kqueue() failed");
+            return NGX_ERROR;
+        }
+
+#if (NGX_THREADS)
+
+        if (!(list_mutex = ngx_mutex_init(cycle->log, 0))) {
+            return NGX_ERROR;
+        }
+
+        if (!(kevent_mutex = ngx_mutex_init(cycle->log, 0))) {
+            return NGX_ERROR;
+        }
+
+#endif
+    }
+
+    if (max_changes < kcf->changes) {
+        if (nchanges) {
+            ts.tv_sec = 0;
+            ts.tv_nsec = 0;
+
+            if (kevent(ngx_kqueue, change_list, nchanges, NULL, 0, &ts) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "kevent() failed");
+                return NGX_ERROR;
+            }
+            nchanges = 0;
+        }
+
+        if (change_list0) {
+            ngx_free(change_list0);
+        }
+
+        change_list0 = ngx_alloc(kcf->changes * sizeof(struct kevent),
+                                 cycle->log);
+        if (change_list0 == NULL) {
+            return NGX_ERROR;
+        }
+
+        if (change_list1) {
+            ngx_free(change_list1);
+        }
+
+        change_list1 = ngx_alloc(kcf->changes * sizeof(struct kevent),
+                                 cycle->log);
+        if (change_list1 == NULL) {
+            return NGX_ERROR;
+        }
+
+        change_list = change_list0;
+    }
+
+    max_changes = kcf->changes;
+
+    if (nevents < kcf->events) {
+        if (event_list) {
+            ngx_free(event_list);
+        }
+
+        event_list = ngx_alloc(kcf->events * sizeof(struct kevent), cycle->log);
+        if (event_list == NULL) {
+            return NGX_ERROR;
+        }
+    }
+
+    nevents = kcf->events;
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_kqueue_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_ONESHOT_EVENT
+#if (HAVE_CLEAR_EVENT)
+                     |NGX_USE_CLEAR_EVENT
+#else
+                     |NGX_USE_LEVEL_EVENT
+#endif
+#if (HAVE_LOWAT_EVENT)
+                     |NGX_HAVE_LOWAT_EVENT
+#endif
+                     |NGX_HAVE_KQUEUE_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void ngx_kqueue_done(ngx_cycle_t *cycle)
+{
+    if (close(ngx_kqueue) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "kqueue close() failed");
+    }
+
+    ngx_kqueue = -1;
+
+#if (NGX_THREADS)
+    ngx_mutex_destroy(kevent_mutex);
+    ngx_mutex_destroy(list_mutex);
+#endif
+
+    ngx_free(change_list1);
+    ngx_free(change_list0);
+    ngx_free(event_list);
+
+    change_list1 = NULL;
+    change_list0 = NULL;
+    change_list = NULL;
+    event_list = NULL;
+    max_changes = 0;
+    nchanges = 0;
+    nevents = 0;
+}
+
+
+static ngx_int_t ngx_kqueue_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_int_t          rc;
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    ev->active = 1;
+    ev->disabled = 0;
+    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+
+    if (ngx_mutex_lock(list_mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (nchanges > 0
+        && ev->index < (u_int) nchanges
+        && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+                                                             == (uintptr_t) ev)
+    {
+        if (change_list[ev->index].flags == EV_DISABLE) {
+
+            /*
+             * if the EV_DISABLE is still not passed to a kernel
+             * we will not pass it
+             */
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "kevent activated: %d: ft:%d",
+                           ngx_event_ident(ev->data), event);
+
+            if (ev->index < (u_int) --nchanges) {
+                e = (ngx_event_t *) change_list[nchanges].udata;
+                change_list[ev->index] = change_list[nchanges];
+                e->index = ev->index;
+            }
+
+            ngx_mutex_unlock(list_mutex);
+
+            return NGX_OK;
+        }
+
+        c = ev->data;
+
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "previous event on #%d were not passed in kernel", c->fd);
+
+        ngx_mutex_unlock(list_mutex);
+
+        return NGX_ERROR;
+    }
+
+    rc = ngx_kqueue_set_event(ev, event, EV_ADD|EV_ENABLE|flags);
+
+    ngx_mutex_unlock(list_mutex);
+
+    return rc;
+}
+
+
+static ngx_int_t ngx_kqueue_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_int_t     rc;
+    ngx_event_t  *e;
+
+    ev->active = 0;
+    ev->disabled = 0;
+
+    if (ngx_mutex_lock(list_mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (nchanges > 0
+        && ev->index < (u_int) nchanges
+        && ((uintptr_t) change_list[ev->index].udata & (uintptr_t) ~1)
+                                                             == (uintptr_t) ev)
+    {
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "kevent deleted: %d: ft:%d",
+                       ngx_event_ident(ev->data), event);
+
+        /* if the event is still not passed to a kernel we will not pass it */
+
+        if (ev->index < (u_int) --nchanges) {
+            e = (ngx_event_t *) change_list[nchanges].udata;
+            change_list[ev->index] = change_list[nchanges];
+            e->index = ev->index;
+        }
+
+        ngx_mutex_unlock(list_mutex);
+
+        return NGX_OK;
+    }
+
+    /*
+     * when the file descriptor is closed the kqueue automatically deletes
+     * its filters so we do not need to delete explicity the event
+     * before the closing the file descriptor.
+     */
+
+    if (flags & NGX_CLOSE_EVENT) {
+        ngx_mutex_unlock(list_mutex);
+        return NGX_OK;
+    }
+
+    if (flags & NGX_DISABLE_EVENT) {
+        ev->disabled = 1;
+    }
+
+    rc = ngx_kqueue_set_event(ev, event,
+                           flags & NGX_DISABLE_EVENT ? EV_DISABLE : EV_DELETE);
+
+    ngx_mutex_unlock(list_mutex);
+
+    return rc;
+}
+
+
+static ngx_int_t ngx_kqueue_set_event(ngx_event_t *ev, int filter, u_int flags)
+{
+    struct kevent     *kev;
+    struct timespec    ts;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "kevent set event: %d: ft:%d fl:%04X",
+                   c->fd, filter, flags);
+
+    if (nchanges >= max_changes) {
+        ngx_log_error(NGX_LOG_WARN, ev->log, 0,
+                      "kqueue change list is filled up");
+
+        ts.tv_sec = 0;
+        ts.tv_nsec = 0;
+
+        if (kevent(ngx_kqueue, change_list, nchanges, NULL, 0, &ts) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "kevent() failed");
+            return NGX_ERROR;
+        }
+
+        nchanges = 0;
+    }
+
+    kev = &change_list[nchanges];
+
+    kev->ident = c->fd;
+    kev->filter = filter;
+    kev->flags = flags;
+    kev->udata = (void *) ((uintptr_t) ev | ev->instance);
+
+    if (filter == EVFILT_VNODE) {
+        kev->fflags = NOTE_DELETE|NOTE_WRITE|NOTE_EXTEND
+                                 |NOTE_ATTRIB|NOTE_RENAME
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 430000) \
+    || __FreeBSD_version >= 500018
+                                 |NOTE_REVOKE
+#endif
+                                       ;
+        kev->data = 0;
+
+    } else {
+#if (HAVE_LOWAT_EVENT)
+        if (flags & NGX_LOWAT_EVENT) {
+            kev->fflags = NOTE_LOWAT;
+            kev->data = ev->available;
+
+        } else {
+            kev->fflags = 0;
+            kev->data = 0;
+        }
+#else
+        kev->fflags = 0;
+        kev->data = 0;
+#endif
+    }
+
+    ev->index = nchanges;
+    nchanges++;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_kqueue_process_events(ngx_cycle_t *cycle)
+{
+    int                events, n;
+    ngx_int_t          i, instance;
+    ngx_uint_t         lock, accept_lock, expire;
+    ngx_err_t          err;
+    ngx_msec_t         timer;
+    ngx_event_t       *ev;
+    ngx_epoch_msec_t   delta;
+    struct timeval     tv;
+    struct timespec    ts, *tp;
+
+    for ( ;; ) {
+        timer = ngx_event_find_timer();
+
+#if (NGX_THREADS)
+
+        if (timer == NGX_TIMER_ERROR) {
+            return NGX_ERROR;
+        }
+
+        if (timer == NGX_TIMER_INFINITE || timer > 500) {
+            timer = 500;
+            break;
+        }
+
+#endif
+
+        if (timer != 0) {
+            break;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "kevent expired timer");
+
+        ngx_event_expire_timers((ngx_msec_t)
+                                    (ngx_elapsed_msec - ngx_old_elapsed_msec));
+
+        if (ngx_posted_events && ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+        }
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec;
+    expire = 1;
+    accept_lock = 0;
+
+    if (ngx_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ngx_accept_mutex_held) {
+                accept_lock = 1;
+
+            } else if (timer == NGX_TIMER_INFINITE
+                       || timer > ngx_accept_mutex_delay)
+            {
+                timer = ngx_accept_mutex_delay;
+                expire = 0;
+            }
+        }
+    }
+
+    if (ngx_threaded) {
+        if (ngx_kqueue_process_changes(cycle, 0) == NGX_ERROR) {
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+
+        n = 0;
+
+    } else {
+        n = nchanges;
+        nchanges = 0;
+    }
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+        expire = 0;
+
+    } else {
+        ts.tv_sec = timer / 1000;
+        ts.tv_nsec = (timer % 1000) * 1000000;
+        tp = &ts;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "kevent timer: %d, changes: %d", timer, n);
+
+    events = kevent(ngx_kqueue, change_list, n, event_list, nevents, tp);
+
+    if (events == -1) {
+        err = ngx_errno;
+    } else {
+        err = 0;
+    }
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "kevent events: %d", events);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    if (err) {
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "kevent() failed");
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "kevent timer: %d, delta: %d", timer, (int) delta);
+
+    } else {
+        if (events == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "kevent() returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    if (events > 0) {
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+
+        lock = 1;
+
+    } else {
+        lock =0;
+    }
+
+    for (i = 0; i < events; i++) {
+
+        ngx_kqueue_dump_event(cycle->log, &event_list[i]);
+
+        if (event_list[i].flags & EV_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, event_list[i].data,
+                          "kevent() error on %d", event_list[i].ident);
+            continue;
+        }
+
+        ev = (ngx_event_t *) event_list[i].udata;
+
+        switch (event_list[i].filter) {
+
+        case EVFILT_READ:
+        case EVFILT_WRITE:
+
+            instance = (uintptr_t) ev & 1;
+            ev = (ngx_event_t *) ((uintptr_t) ev & (uintptr_t) ~1);
+
+            if (ev->closed || ev->instance != instance) {
+
+                /*
+                 * the stale event from a file descriptor
+                 * that was just closed in this iteration
+                 */
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "kevent: stale event " PTR_FMT, ev);
+                continue;
+            }
+
+            if (ev->log && (ev->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+                ngx_kqueue_dump_event(ev->log, &event_list[i]);
+            }
+
+#if (NGX_THREADS)
+
+            if (ngx_threaded && !ev->accept) {
+                ev->posted_ready = 1;
+                ev->posted_available = event_list[i].data;
+
+                if (event_list[i].flags & EV_EOF) {
+                    ev->posted_eof = 1;
+                    ev->posted_errno = event_list[i].fflags;
+                }
+
+                ngx_post_event(ev);
+
+                continue;
+            }
+
+#endif
+
+            ev->available = event_list[i].data;
+
+            if (event_list[i].flags & EV_EOF) {
+                ev->pending_eof = 1;
+                ev->kq_errno = event_list[i].fflags;
+            }
+
+            ev->ready = 1;
+
+            break;
+
+        case EVFILT_VNODE:
+            ev->kq_vnode = 1;
+
+            break;
+
+        case EVFILT_AIO:
+            ev->complete = 1;
+            ev->ready = 1;
+
+            break;
+
+        default:
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "unexpected kevent() filter %d",
+                          event_list[i].filter);
+            continue;
+        }
+
+        if (!ngx_threaded && !ngx_accept_mutex_held) {
+            ev->event_handler(ev);
+            continue;
+        }
+
+        if (!ev->accept) {
+            ngx_post_event(ev);
+            continue;
+        }
+
+        if (ngx_accept_disabled > 0) {
+            continue;
+        }
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+
+        ev->event_handler(ev);
+
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_mutex_unlock();
+            accept_lock = 0;
+        }
+
+        if (i + 1 == events) {
+            lock = 0;
+            break;
+        }
+
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            if (accept_lock) {
+                ngx_accept_mutex_unlock();
+            }
+            return NGX_ERROR;
+        }
+    }
+
+    if (accept_lock) {
+        ngx_accept_mutex_unlock();
+    }
+
+    if (lock) {
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (ngx_posted_events) {
+        if (ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+
+        } else {
+            ngx_event_process_posted(cycle);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_kqueue_process_changes(ngx_cycle_t *cycle, ngx_uint_t try)
+{
+    int               n;
+    ngx_int_t         rc;
+    ngx_err_t         err;
+    struct timespec   ts;
+    struct kevent    *changes;
+
+    if (ngx_mutex_lock(kevent_mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_mutex_lock(list_mutex) == NGX_ERROR) {
+        ngx_mutex_unlock(kevent_mutex);
+        return NGX_ERROR;
+    }
+
+    if (nchanges == 0) {
+        ngx_mutex_unlock(list_mutex);
+        ngx_mutex_unlock(kevent_mutex);
+        return NGX_OK;
+    }
+
+    changes = change_list;
+    if (change_list == change_list0) {
+        change_list = change_list1;
+    } else {
+        change_list = change_list0;
+    }
+
+    n = nchanges;
+    nchanges = 0;
+
+    ngx_mutex_unlock(list_mutex);
+
+    ts.tv_sec = 0;
+    ts.tv_nsec = 0;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "kevent changes: %d", n);
+
+    if (kevent(ngx_kqueue, changes, n, NULL, 0, &ts) == -1) {
+        err = ngx_errno;
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "kevent() failed");
+        rc = NGX_ERROR;
+
+    } else {
+        rc = NGX_OK;
+    }
+
+    ngx_mutex_unlock(kevent_mutex);
+
+    return rc;
+}
+
+
+static ngx_inline void ngx_kqueue_dump_event(ngx_log_t *log, struct kevent *kev)
+{
+    ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0,
+                   (kev->ident > 0x8000000 && kev->ident != (unsigned) -1) ?
+                    "kevent: " PTR_FMT ": ft:%d fl:%04X ff:%08X d:%d ud:"
+                                                                       PTR_FMT:
+                    "kevent: %d: ft:%d fl:%04X ff:%08X d:%d ud:" PTR_FMT,
+                    kev->ident, kev->filter,
+                    kev->flags, kev->fflags,
+                    kev->data, kev->udata);
+}
+
+
+static void *ngx_kqueue_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_kqueue_conf_t  *kcf;
+
+    ngx_test_null(kcf, ngx_palloc(cycle->pool, sizeof(ngx_kqueue_conf_t)),
+                  NGX_CONF_ERROR);
+
+    kcf->changes = NGX_CONF_UNSET;
+    kcf->events = NGX_CONF_UNSET;
+
+    return kcf;
+}
+
+
+static char *ngx_kqueue_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_kqueue_conf_t *kcf = conf;
+
+    ngx_conf_init_value(kcf->changes, 512);
+    ngx_conf_init_value(kcf->events, 512);
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_kqueue_module.h
@@ -0,0 +1,17 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_KQUEUE_MODULE_H_INCLUDED_
+#define _NGX_KQUEUE_MODULE_H_INCLUDED_
+
+
+extern int                 ngx_kqueue;
+extern ngx_module_t        ngx_kqueue_module;
+extern ngx_event_module_t  ngx_kqueue_module_ctx;
+
+
+
+#endif /* _NGX_KQUEUE_MODULE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_poll_module.c
@@ -0,0 +1,605 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle);
+static void ngx_poll_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle);
+static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static struct pollfd  *event_list;
+static int             nevents;
+
+#if 0
+static ngx_event_t   **ready_index;
+#endif
+
+static ngx_event_t    *accept_events;
+
+
+static ngx_str_t    poll_name = ngx_string("poll");
+
+ngx_event_module_t  ngx_poll_module_ctx = {
+    &poll_name,
+    NULL,                                  /* create configuration */
+    ngx_poll_init_conf,                    /* init configuration */
+
+    {
+        ngx_poll_add_event,                /* add an event */
+        ngx_poll_del_event,                /* delete an event */
+        ngx_poll_add_event,                /* enable an event */
+        ngx_poll_del_event,                /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* process the changes */
+        ngx_poll_process_events,           /* process the events */
+        ngx_poll_init,                     /* init the events */
+        ngx_poll_done                      /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_poll_module = {
+    NGX_MODULE,
+    &ngx_poll_module_ctx,                  /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+
+static ngx_int_t ngx_poll_init(ngx_cycle_t *cycle)
+{
+    struct pollfd   *list;
+
+    if (event_list == NULL) {
+        nevents = 0;
+    }
+
+    if (ngx_process == NGX_PROCESS_WORKER
+        || cycle->old_cycle == NULL
+        || cycle->old_cycle->connection_n < cycle->connection_n)
+    {
+        ngx_test_null(list,
+                      ngx_alloc(sizeof(struct pollfd) * cycle->connection_n,
+                                cycle->log),
+                      NGX_ERROR);
+
+        if (event_list) {
+            ngx_memcpy(list, event_list, sizeof(ngx_event_t *) * nevents);
+            ngx_free(event_list);
+        }
+
+        event_list = list;
+
+#if 0
+        if (ready_index) {
+            ngx_free(ready_index);
+        }
+
+        ngx_test_null(ready_index,
+                      ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+                                cycle->log),
+                      NGX_ERROR);
+#endif
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_poll_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_ONESHOT_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void ngx_poll_done(ngx_cycle_t *cycle)
+{
+    ngx_free(event_list);
+#if 0
+    ngx_free(ready_index);
+#endif
+
+    event_list = NULL;
+#if 0
+    ready_index = NULL;
+#endif
+}
+
+
+static ngx_int_t ngx_poll_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_event_t       *e;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 1;
+
+    if (ev->index != NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "poll event fd:%d ev:%d is already set", c->fd, event);
+        return NGX_OK;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+        event = POLLIN;
+#endif
+
+    } else {
+        e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+        event = POLLOUT;
+#endif
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "poll add event: fd:%d ev:%d", c->fd, event);
+
+    if (e == NULL || e->index == NGX_INVALID_INDEX) {
+        event_list[nevents].fd = c->fd;
+        event_list[nevents].events = event;
+        event_list[nevents].revents = 0;
+
+        ev->index = nevents;
+        nevents++;
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "poll add index: %d", e->index);
+
+        event_list[e->index].events |= event;
+        ev->index = e->index;
+    }
+
+    ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_poll_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_uint_t          i;
+    ngx_cycle_t       **cycle;
+    ngx_event_t        *e;
+    ngx_connection_t   *c;
+
+    c = ev->data;
+
+    ev->active = 0;
+
+    if (ev->index == NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "poll event fd:%d ev:%d is already deleted",
+                      c->fd, event);
+        return NGX_OK;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        e = c->write;
+#if (NGX_READ_EVENT != POLLIN)
+        event = POLLIN;
+#endif
+
+    } else {
+        e = c->read;
+#if (NGX_WRITE_EVENT != POLLOUT)
+        event = POLLOUT;
+#endif
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "poll del event: fd:%d ev:%d", c->fd, event);
+
+    if (e == NULL || e->index == NGX_INVALID_INDEX) {
+        nevents--;
+
+        if (ev->index < (u_int) nevents) {
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "index: copy event %d to %d", nevents, ev->index);
+
+            event_list[ev->index] = event_list[nevents];
+
+            c = &ngx_cycle->connections[event_list[nevents].fd];
+
+            if (c->fd == -1) {
+                cycle = ngx_old_cycles.elts;
+                for (i = 0; i < ngx_old_cycles.nelts; i++) {
+                    if (cycle[i] == NULL) {
+                        continue;
+                    }
+                    c = &cycle[i]->connections[event_list[nevents].fd];
+                    if (c->fd != -1) {
+                        break;
+                    }
+                }
+            }
+
+            if (c->fd == -1) {
+                ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                              "unexpected last event");
+
+            } else {
+                if (c->read->index == (u_int) nevents) {
+                    c->read->index = ev->index;
+                }
+
+                if (c->write->index == (u_int) nevents) {
+                    c->write->index = ev->index;
+                }
+            }
+        }
+
+    } else {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "poll del index: %d", e->index);
+
+        event_list[e->index].events &= ~event;
+    }
+
+    ev->index = NGX_INVALID_INDEX;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_poll_process_events(ngx_cycle_t *cycle)
+{
+    int                 ready;
+    ngx_int_t           i, nready;
+    ngx_uint_t          n, found, lock, expire;
+    ngx_msec_t          timer;
+    ngx_err_t           err;
+    ngx_cycle_t       **old_cycle;
+    ngx_event_t        *ev;
+    ngx_epoch_msec_t    delta;
+    ngx_connection_t   *c;
+    struct timeval      tv;
+
+    for ( ;; ) {
+        timer = ngx_event_find_timer();
+
+        if (timer != 0) {
+            break;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "poll expired timer");
+
+        ngx_event_expire_timers((ngx_msec_t)
+                                (ngx_elapsed_msec - ngx_old_elapsed_msec));
+    }
+
+    /* NGX_TIMER_INFINITE == INFTIM */
+
+    if (timer == NGX_TIMER_INFINITE) {
+        expire = 0;
+
+    } else {
+        expire = 1;
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec; 
+
+#if (NGX_DEBUG0)
+    if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+        for (i = 0; i < nevents; i++) {
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "poll: %d: fd:%d ev:%04X",
+                           i, event_list[i].fd, event_list[i].events);
+        }
+    }
+#endif
+
+    if (ngx_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ngx_accept_mutex_held == 0
+                && (timer == NGX_TIMER_INFINITE
+                    || timer > ngx_accept_mutex_delay))
+            { 
+                timer = ngx_accept_mutex_delay;
+                expire = 0;
+            } 
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "poll timer: %d", timer);
+
+    ready = poll(event_list, (u_int) nevents, (int) timer);
+
+    if (ready == -1) {
+        err = ngx_errno;
+    } else {
+        err = 0;
+    }
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "poll ready %d of %d", ready, nevents);
+
+    if (err) {
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "poll() failed");
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "poll timer: %d, delta: %d", timer, (int) delta);
+    } else {
+        if (ready == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "poll() returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    lock = 1;
+    nready = 0;
+
+    for (i = 0; i < nevents && ready; i++) {
+
+#if 0
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "poll: %d: fd:%d ev:%04X rev:%04X",
+                       i, event_list[i].fd,
+                       event_list[i].events, event_list[i].revents);
+#else
+        if (event_list[i].revents) {
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "poll: %d: fd:%d ev:%04X rev:%04X",
+                           i, event_list[i].fd,
+                           event_list[i].events, event_list[i].revents);
+        }
+#endif
+
+        if (event_list[i].revents & POLLNVAL) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "poll() error fd:%d ev:%04X rev:%04X",
+                          event_list[i].fd,
+                          event_list[i].events, event_list[i].revents);
+        }
+
+        if (event_list[i].revents & ~(POLLIN|POLLOUT|POLLERR|POLLHUP|POLLNVAL))
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "strange poll() events fd:%d ev:%04X rev:%04X",
+                          event_list[i].fd,
+                          event_list[i].events, event_list[i].revents);
+        }
+
+        if (event_list[i].fd == -1) {
+            /*
+             * the disabled event, a workaround for our possible bug,
+             * see the comment below
+             */
+            continue;
+        }
+
+        c = &ngx_cycle->connections[event_list[i].fd];
+
+        if (c->fd == -1) {
+            old_cycle = ngx_old_cycles.elts;
+            for (n = 0; n < ngx_old_cycles.nelts; n++) {
+                if (old_cycle[n] == NULL) {
+                    continue;
+                }
+                c = &old_cycle[n]->connections[event_list[i].fd];
+                if (c->fd != -1) {
+                    break;
+                }
+            }
+        }
+
+        if (c->fd == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unexpected event");
+
+            /*
+             * it is certainly our fault and it should be investigated,
+             * in the meantime we disable this event to avoid a CPU spinning
+             */
+
+            if (i == nevents - 1) {
+                nevents--;
+            } else {
+                event_list[i].fd = -1;
+            }
+
+            continue;
+        }
+
+        found = 0;
+
+        if (event_list[i].revents & (POLLIN|POLLERR|POLLHUP|POLLNVAL)) {
+            found = 1;
+
+            ev = c->read;
+            ev->ready = 1;
+
+            if (ev->oneshot) {
+                if (ev->timer_set) {
+                    ngx_del_timer(ev);
+                }
+                ngx_poll_del_event(ev, NGX_READ_EVENT, 0);
+            }
+
+            if (ev->accept) {
+                ev->next = accept_events;
+                accept_events = ev;
+            } else {
+                ngx_post_event(ev);
+            }
+
+#if 0
+            ready_index[nready++] = c->read;
+#endif
+        }
+
+        if (event_list[i].revents & (POLLOUT|POLLERR|POLLHUP|POLLNVAL)) {
+            found = 1;
+            ev = c->write;
+            ev->ready = 1;
+
+            if (ev->oneshot) {
+                if (ev->timer_set) {
+                    ngx_del_timer(ev);
+                }
+                ngx_poll_del_event(ev, NGX_WRITE_EVENT, 0);
+            }
+
+            ngx_post_event(ev);
+#if 0
+            ready_index[nready++] = c->write;
+#endif
+        }
+
+        if (found) {
+            ready--;
+            continue;
+        }
+    }
+
+#if 0
+    for (i = 0; i < nready; i++) {
+        ev = ready_index[i];
+
+        if (!ev->active) {
+            continue;
+        }
+
+        ev->ready = 1;
+
+        if (ev->oneshot) {
+            if (ev->timer_set) {
+                ngx_del_timer(ev);
+            }
+
+            if (ev->write) {
+                ngx_poll_del_event(ev, NGX_WRITE_EVENT, 0);
+            } else {
+                ngx_poll_del_event(ev, NGX_READ_EVENT, 0);
+            }
+        }
+
+        ev->event_handler(ev);
+    }
+#endif
+
+    ev = accept_events;
+
+    for ( ;; ) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                      "accept event " PTR_FMT, ev);
+
+        if (ev == NULL) {
+            break;
+        }
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+
+        ev->event_handler(ev);
+
+        if (ngx_accept_disabled > 0) {
+            lock = 0;
+            break;
+        }
+
+        ev = ev->next;
+
+        if (ev == NULL) {
+            lock = 0;
+            break;
+        }
+
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+
+    }
+
+    ngx_accept_mutex_unlock();
+    accept_events = NULL;
+
+    if (lock) {
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    if (ready != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "poll ready != events");
+    }
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (!ngx_threaded) {
+        ngx_event_process_posted(cycle);
+    }
+
+    return nready;
+}
+
+
+static char *ngx_poll_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ecf->use != ngx_poll_module.ctx_index) {
+        return NGX_CONF_OK;
+    }
+
+#if (NGX_THREADS)
+
+    ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                  "poll() is not supported in the threaded mode");
+    return NGX_CONF_ERROR;
+
+#else
+
+    return NGX_CONF_OK;
+
+#endif
+}
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_rtsig_module.c
@@ -0,0 +1,814 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (TEST_BUILD_RTSIG)
+
+#define F_SETSIG       10
+#define SIGRTMIN       33
+#define si_fd          __spare__[0]
+#define KERN_RTSIGNR   30
+#define KERN_RTSIGMAX  31
+
+int sigtimedwait(const sigset_t *set, siginfo_t *info,
+                 const struct timespec *timeout)
+{
+    return -1;
+}
+
+int ngx_linux_rtsig_max;
+
+#endif
+
+
+typedef struct {
+    int        signo;
+    ngx_int_t  overflow_events;
+    ngx_int_t  overflow_test;
+    ngx_int_t  overflow_threshold;
+} ngx_rtsig_conf_t;
+
+
+extern ngx_event_module_t  ngx_poll_module_ctx;
+
+static ngx_int_t ngx_rtsig_init(ngx_cycle_t *cycle);
+static void ngx_rtsig_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_rtsig_add_connection(ngx_connection_t *c);
+static ngx_int_t ngx_rtsig_del_connection(ngx_connection_t *c, u_int flags);
+static ngx_int_t ngx_rtsig_process_events(ngx_cycle_t *cycle);
+static ngx_int_t ngx_rtsig_process_overflow(ngx_cycle_t *cycle);
+
+static void *ngx_rtsig_create_conf(ngx_cycle_t *cycle);
+static char *ngx_rtsig_init_conf(ngx_cycle_t *cycle, void *conf);
+static char *ngx_check_ngx_overflow_threshold_bounds(ngx_conf_t *cf,
+                                                     void *post, void *data);
+
+
+static sigset_t        set;
+static ngx_uint_t      overflow, overflow_current;
+static struct pollfd  *overflow_list;
+
+
+static ngx_str_t      rtsig_name = ngx_string("rtsig");
+
+static ngx_conf_num_bounds_t  ngx_overflow_threshold_bounds = {
+    ngx_check_ngx_overflow_threshold_bounds, 2, 10
+};
+
+
+static ngx_command_t  ngx_rtsig_commands[] = {
+
+    {ngx_string("rtsig_signo"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_rtsig_conf_t, signo),
+     NULL},
+
+    {ngx_string("rtsig_overflow_events"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_rtsig_conf_t, overflow_events),
+     NULL},
+
+    {ngx_string("rtsig_overflow_test"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_rtsig_conf_t, overflow_test),
+     NULL},
+
+    {ngx_string("rtsig_overflow_threshold"),
+     NGX_EVENT_CONF|NGX_CONF_TAKE1,
+     ngx_conf_set_num_slot,
+     0,
+     offsetof(ngx_rtsig_conf_t, overflow_threshold),
+     &ngx_overflow_threshold_bounds},
+
+    ngx_null_command
+};
+
+
+ngx_event_module_t  ngx_rtsig_module_ctx = {
+    &rtsig_name,
+    ngx_rtsig_create_conf,               /* create configuration */
+    ngx_rtsig_init_conf,                 /* init configuration */
+
+    {
+        NULL,                            /* add an event */
+        NULL,                            /* delete an event */
+        NULL,                            /* enable an event */
+        NULL,                            /* disable an event */
+        ngx_rtsig_add_connection,        /* add an connection */
+        ngx_rtsig_del_connection,        /* delete an connection */
+        NULL,                            /* process the changes */
+        ngx_rtsig_process_events,        /* process the events */
+        ngx_rtsig_init,                  /* init the events */
+        ngx_rtsig_done,                  /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_rtsig_module = {
+    NGX_MODULE,
+    &ngx_rtsig_module_ctx,               /* module context */
+    ngx_rtsig_commands,                  /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_int_t ngx_rtsig_init(ngx_cycle_t *cycle)
+{
+    ngx_rtsig_conf_t  *rtscf;
+
+    if (ngx_poll_module_ctx.actions.init(cycle) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    rtscf = ngx_event_get_conf(cycle->conf_ctx, ngx_rtsig_module);
+
+    sigemptyset(&set);
+    sigaddset(&set, rtscf->signo);
+    sigaddset(&set, rtscf->signo + 1);
+    sigaddset(&set, SIGIO);
+
+    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                      "sigprocmask() failed");
+        return NGX_ERROR;
+    }
+
+    if (overflow_list) {
+        ngx_free(overflow_list);
+    }
+
+    overflow_list = ngx_alloc(sizeof(struct pollfd) * rtscf->overflow_events,
+                              cycle->log);
+    if (overflow_list == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_rtsig_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_RTSIG_EVENT|NGX_HAVE_GREEDY_EVENT;
+
+    return NGX_OK;
+}
+
+
+static void ngx_rtsig_done(ngx_cycle_t *cycle)
+{
+    ngx_poll_module_ctx.actions.done(cycle);
+}
+
+
+static ngx_int_t ngx_rtsig_add_connection(ngx_connection_t *c)
+{
+    int                signo;
+    ngx_rtsig_conf_t  *rtscf;
+
+    if (c->read->accept && c->read->disabled) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "rtsig enable connection: fd:%d", c->fd);
+
+        if (fcntl(c->fd, F_SETOWN, ngx_pid) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                          "fcntl(F_SETOWN) failed");
+            return NGX_ERROR;
+        }
+
+        c->read->active = 1;
+        c->read->disabled = 0;
+    }
+
+    rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+    signo = rtscf->signo + c->read->instance;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "rtsig add connection: fd:%d signo:%d", c->fd, signo);
+
+    if (fcntl(c->fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "fcntl(O_RDWR|O_NONBLOCK|O_ASYNC) failed");
+        return NGX_ERROR;
+    }
+
+    if (fcntl(c->fd, F_SETSIG, signo) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "fcntl(F_SETSIG) failed");
+        return NGX_ERROR;
+    }
+
+    if (fcntl(c->fd, F_SETOWN, ngx_pid) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "fcntl(F_SETOWN) failed");
+        return NGX_ERROR;
+    }
+
+#if (HAVE_ONESIGFD)
+    if (fcntl(c->fd, F_SETAUXFL, O_ONESIGFD) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "fcntl(F_SETAUXFL) failed");
+        return NGX_ERROR;
+    }
+#endif
+
+    c->read->active = 1;
+    c->write->active = 1;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_rtsig_del_connection(ngx_connection_t *c, u_int flags)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "rtsig del connection: fd:%d", c->fd);
+
+    if ((flags & NGX_DISABLE_EVENT) && c->read->accept) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "rtsig disable connection: fd:%d", c->fd);
+
+        c->read->active = 0;
+        c->read->disabled = 1;
+        return NGX_OK;
+    }
+
+    if (flags & NGX_CLOSE_EVENT) {
+        c->read->active = 0;
+        c->write->active = 0;
+        return NGX_OK;
+    }
+
+    if (fcntl(c->fd, F_SETFL, O_RDWR|O_NONBLOCK) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "fcntl(O_RDWR|O_NONBLOCK) failed");
+        return NGX_ERROR;
+    }
+
+    c->read->active = 0;
+    c->write->active = 0;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_rtsig_process_events(ngx_cycle_t *cycle)
+{
+    int                 signo;
+    ngx_int_t           instance, i;
+    ngx_uint_t          expire;
+    size_t              n;
+    ngx_msec_t          timer;
+    ngx_err_t           err;
+    siginfo_t           si;
+    ngx_event_t        *rev, *wev;
+    struct timeval      tv;
+    struct timespec     ts, *tp;
+    struct sigaction    sa;
+    ngx_epoch_msec_t    delta;
+    ngx_connection_t   *c;
+    ngx_rtsig_conf_t   *rtscf;
+
+    if (overflow) {
+        timer = 0;
+        expire = 0;
+
+    } else {
+        for ( ;; ) {
+            timer = ngx_event_find_timer();
+
+#if (NGX_THREADS)
+
+            if (timer == NGX_TIMER_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (timer == NGX_TIMER_INFINITE || timer > 500) {
+                timer = 500;
+                break;
+            }
+
+#endif
+
+            if (timer != 0) {
+                break;
+            }
+
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "rtsig expired timer");
+
+            ngx_event_expire_timers((ngx_msec_t)
+                                    (ngx_elapsed_msec - ngx_old_elapsed_msec));
+
+            if (ngx_posted_events && ngx_threaded) {
+                ngx_wakeup_worker_thread(cycle);
+            }
+        }
+
+        expire = 1;
+
+        if (ngx_accept_mutex) {
+            if (ngx_accept_disabled > 0) {
+                ngx_accept_disabled--;
+
+            } else {
+                ngx_accept_mutex_held = 0;
+
+                if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                    return NGX_ERROR;
+                }
+
+                if (ngx_accept_mutex_held == 0
+                    && (timer == NGX_TIMER_INFINITE
+                        || timer > ngx_accept_mutex_delay))
+                {
+                    timer = ngx_accept_mutex_delay;
+                    expire = 0;
+                } 
+            }
+        }
+    }
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+        expire = 0;
+
+    } else {
+        ts.tv_sec = timer / 1000;
+        ts.tv_nsec = (timer % 1000) * 1000000;
+        tp = &ts;
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "rtsig timer: %d", timer);
+
+    /* Linux's sigwaitinfo() is sigtimedwait() with the NULL timeout pointer */
+
+    signo = sigtimedwait(&set, &si, tp);
+
+    if (signo == -1) {
+        err = ngx_errno;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err,
+                       "rtsig signo:%d", signo);
+
+        if (err == NGX_EAGAIN) {
+
+            if (timer == NGX_TIMER_INFINITE) {
+                ngx_accept_mutex_unlock();
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                              "sigtimedwait() returned EAGAIN without timeout");
+                return NGX_ERROR;
+            }
+
+            err = 0;
+        }
+
+    } else {
+        err = 0;
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "rtsig signo:%d fd:%d band:%X",
+                       signo, si.si_fd, si.si_band);
+    }
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    if (err) {
+        ngx_accept_mutex_unlock();
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "sigtimedwait() failed");
+        return NGX_ERROR;
+    }
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "rtsig timer: %d, delta: %d", timer, (int) delta);
+    }
+
+    rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+    if (signo == rtscf->signo || signo == rtscf->signo + 1) {
+
+        if (overflow && (ngx_uint_t) si.si_fd > overflow_current) {
+            return NGX_OK;
+        }
+
+        /* TODO: old_cycles */
+
+        c = &ngx_cycle->connections[si.si_fd];
+
+        instance = signo - rtscf->signo;
+
+        rev = c->read;
+
+        if (c->read->instance != instance) {
+
+            /*
+             * the stale event from a file descriptor
+             * that was just closed in this iteration
+             */
+
+            ngx_accept_mutex_unlock();
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "rtsig: stale event " PTR_FMT, c);
+
+            return NGX_OK;
+        }
+
+        if (si.si_band & (POLLIN|POLLHUP|POLLERR)) {
+            if (rev->active) {
+
+                if (ngx_threaded && !rev->accept) {
+                    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                        ngx_accept_mutex_unlock();
+                        return NGX_ERROR;
+                    }
+
+                    rev->posted_ready = 1;
+                    ngx_post_event(rev);
+
+                    ngx_mutex_unlock(ngx_posted_events_mutex);
+
+                } else {
+                    rev->ready = 1;
+
+                    if (!ngx_threaded && !ngx_accept_mutex_held) {
+                        rev->event_handler(rev);
+
+                    } else if (rev->accept) {
+                        if (ngx_accept_disabled <= 0) {
+                            rev->event_handler(rev);
+                        }
+
+                    } else {
+                        ngx_post_event(rev); 
+                    }
+                }
+            }
+        }
+
+        wev = c->write;
+
+        if (si.si_band & (POLLOUT|POLLHUP|POLLERR)) {
+            if (wev->active) {
+
+                if (ngx_threaded) {
+                    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                        ngx_accept_mutex_unlock();
+                        return NGX_ERROR;
+                    }
+
+                    wev->posted_ready = 1;
+                    ngx_post_event(wev);
+
+                    ngx_mutex_unlock(ngx_posted_events_mutex);
+
+                } else {
+                    wev->ready = 1;
+
+                    if (!ngx_threaded && !ngx_accept_mutex_held) {
+                        wev->event_handler(wev);
+
+                    } else {
+                        ngx_post_event(wev);
+                    }
+                }
+            }
+        }
+
+    } else if (signo == SIGIO) {
+        ngx_accept_mutex_unlock();
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "rt signal queue overflowed");
+
+        /* flush the RT signal queue */
+
+        ngx_memzero(&sa, sizeof(struct sigaction));
+        sa.sa_handler = SIG_DFL;
+        sigemptyset(&sa.sa_mask);
+
+        if (sigaction(rtscf->signo, &sa, NULL) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "sigaction(%d, SIG_DFL) failed", rtscf->signo);
+        }
+
+        if (sigaction(rtscf->signo + 1, &sa, NULL) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "sigaction(%d, SIG_DFL) failed", rtscf->signo + 1);
+        }
+
+        overflow = 1;
+        overflow_current = 0;
+        ngx_event_actions.process_events = ngx_rtsig_process_overflow;
+
+        return NGX_ERROR;
+
+    } else if (signo != -1) {
+        ngx_accept_mutex_unlock();
+
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "sigtimedwait() returned unexpected signal: %d", signo);
+
+        return NGX_ERROR;
+    }
+
+    ngx_accept_mutex_unlock();
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (ngx_posted_events) {
+        if (ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+
+        } else {
+            ngx_event_process_posted(cycle);
+        }
+    }
+
+    if (signo == -1) {
+        return NGX_AGAIN;
+    } else {
+        return NGX_OK;
+    }
+}
+
+
+/* TODO: old cylces */
+
+static ngx_int_t ngx_rtsig_process_overflow(ngx_cycle_t *cycle)
+{
+    int                name[2], rtsig_max, rtsig_nr, events, ready;
+    size_t             len;
+    ngx_int_t          tested, n, i;
+    ngx_err_t          err;
+    ngx_event_t       *rev, *wev;
+    ngx_connection_t  *c;
+    ngx_rtsig_conf_t  *rtscf;
+
+    rtscf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_rtsig_module);
+
+    tested = 0;
+
+    for ( ;; ) {
+
+        n = 0;
+        while (n < rtscf->overflow_events) {
+
+            if (overflow_current == cycle->connection_n) {
+                break;
+            }
+
+            c = &cycle->connections[overflow_current++];
+
+            if (c->fd == -1) {
+                continue;
+            }
+
+            events = 0;
+
+            if (c->read->active && c->read->event_handler) {
+                events |= POLLIN;
+            }
+
+            if (c->write->active && c->write->event_handler) {
+                events |= POLLOUT;
+            }
+
+            if (events == 0) {
+                continue;
+            }
+
+            overflow_list[n].fd = c->fd;
+            overflow_list[n].events = events;
+            overflow_list[n].revents = 0;
+            n++;
+        }
+
+        if (n == 0) {
+            break;
+        }
+
+        for ( ;; ) {
+            ready = poll(overflow_list, n, 0);
+
+            if (ready == -1) {
+                err = ngx_errno;
+                ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                              cycle->log, 0,
+                              "poll() failed while the overflow recover");
+
+                if (err == NGX_EINTR) {
+                    continue;
+                }
+            }
+
+            break;
+        }
+
+        if (ready <= 0) {
+            continue;
+        }
+
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        for (i = 0; i < n; i++) {
+            c = &cycle->connections[overflow_list[i].fd];
+
+            rev = c->read;
+
+            if (rev->active
+                && !rev->closed
+                && rev->event_handler
+                && (overflow_list[i].revents
+                                          & (POLLIN|POLLERR|POLLHUP|POLLNVAL)))
+            {
+                tested++;
+
+                if (ngx_threaded) {
+                    rev->posted_ready = 1;
+                    ngx_post_event(rev);
+
+                } else {
+                    rev->ready = 1;
+                    rev->event_handler(rev); 
+                }
+            }
+
+            wev = c->write;
+
+            if (wev->active
+                && !wev->closed
+                && wev->event_handler
+                && (overflow_list[i].revents
+                                         & (POLLOUT|POLLERR|POLLHUP|POLLNVAL)))
+            {
+                tested++;
+
+                if (ngx_threaded) {
+                    wev->posted_ready = 1;
+                    ngx_post_event(wev);
+
+                } else {
+                    wev->ready = 1;
+                    wev->event_handler(wev); 
+                }
+            }
+        }
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+
+        if (tested >= rtscf->overflow_test) {
+
+            if (ngx_linux_rtsig_max) {
+
+                /*
+                 * Check the current rt queue length to prevent
+                 * the new overflow.
+                 *
+                 * Learn the /proc/sys/kernel/rtsig-max value because
+                 * it can be changed sisnce the last checking.
+                 */
+
+                name[0] = CTL_KERN;
+                name[1] = KERN_RTSIGMAX;
+                len = sizeof(rtsig_max);
+                if (sysctl(name, sizeof(name), &rtsig_max, &len, NULL, 0) == -1)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, errno,
+                                  "sysctl(KERN_RTSIGMAX) failed");
+                    return NGX_ERROR;
+                }
+
+                name[0] = CTL_KERN;
+                name[1] = KERN_RTSIGNR;
+                len = sizeof(rtsig_nr);
+                if (sysctl(name, sizeof(name), &rtsig_nr, &len, NULL, 0) == -1)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, errno,
+                                  "sysctl(KERN_RTSIGNR) failed");
+                    return NGX_ERROR;
+                }
+
+                /*
+                 * drain the rt signal queue if the /proc/sys/kernel/rtsig-nr
+                 * is bigger than
+                 *    /proc/sys/kernel/rtsig-max / rtsig_overflow_threshold
+                 */
+
+                if (rtsig_max / rtscf->overflow_threshold < rtsig_nr) {
+                    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                                   "rtsig queue state: %d/%d",
+                                   rtsig_nr, rtsig_max);
+                    while (ngx_rtsig_process_events(cycle) == NGX_OK) {
+                        /* void */
+                    }
+                }
+
+            } else {
+
+                /*
+                 * Linux has not KERN_RTSIGMAX since 2.6.6-mm2
+                 * so drain the rt signal queue unconditionally
+                 */
+
+                while (ngx_rtsig_process_events(cycle) == NGX_OK) { /* void */ }
+            }
+
+            tested = 0;
+        }
+    }
+
+    if (ngx_posted_events) {
+        if (ngx_threaded) {
+            ngx_wakeup_worker_thread(cycle);
+
+        } else {
+            ngx_event_process_posted(cycle);
+        }
+    }
+
+    ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
+                  "rt signal queue overflow recovered");
+
+    overflow = 0;
+    ngx_event_actions.process_events = ngx_rtsig_process_events;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_rtsig_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_rtsig_conf_t  *rtscf;
+
+    ngx_test_null(rtscf, ngx_palloc(cycle->pool, sizeof(ngx_rtsig_conf_t)),
+                  NGX_CONF_ERROR);
+
+    rtscf->signo = NGX_CONF_UNSET;
+    rtscf->overflow_events = NGX_CONF_UNSET;
+    rtscf->overflow_test = NGX_CONF_UNSET;
+    rtscf->overflow_threshold = NGX_CONF_UNSET;
+
+    return rtscf;
+}
+
+
+static char *ngx_rtsig_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_rtsig_conf_t  *rtscf = conf;
+
+    /* LinuxThreads use the first 3 RT signals */
+    ngx_conf_init_value(rtscf->signo, SIGRTMIN + 10);
+
+    ngx_conf_init_value(rtscf->overflow_events, 16);
+    ngx_conf_init_value(rtscf->overflow_test, 32);
+    ngx_conf_init_value(rtscf->overflow_threshold, 10);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_check_ngx_overflow_threshold_bounds(ngx_conf_t *cf,
+                                                     void *post, void *data)
+{
+    if (ngx_linux_rtsig_max) {
+        return ngx_conf_check_num_bounds(cf, post, data);
+    }
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"rtsig_overflow_threshold\" is not supported "
+                       "since Linux 2.6.6-mm2, ignored");
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/modules/ngx_select_module.c
@@ -0,0 +1,616 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle);
+static void ngx_select_done(ngx_cycle_t *cycle);
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, int event, u_int flags);
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle);
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf);
+
+
+static fd_set         master_read_fd_set;
+static fd_set         master_write_fd_set;
+static fd_set         work_read_fd_set;
+static fd_set         work_write_fd_set;
+
+#if (WIN32)
+static int            max_read;
+static int            max_write;
+#else
+static int            max_fd;
+#endif
+
+static ngx_uint_t     nevents;
+
+static ngx_event_t  **event_index;
+#if 0
+static ngx_event_t  **ready_index;
+#endif
+
+static ngx_event_t   *accept_events;
+
+
+static ngx_str_t    select_name = ngx_string("select");
+
+ngx_event_module_t  ngx_select_module_ctx = {
+    &select_name,
+    NULL,                                  /* create configuration */
+    ngx_select_init_conf,                  /* init configuration */
+
+    {
+        ngx_select_add_event,              /* add an event */
+        ngx_select_del_event,              /* delete an event */
+        ngx_select_add_event,              /* enable an event */
+        ngx_select_del_event,              /* disable an event */
+        NULL,                              /* add an connection */
+        NULL,                              /* delete an connection */
+        NULL,                              /* process the changes */
+        ngx_select_process_events,         /* process the events */
+        ngx_select_init,                   /* init the events */
+        ngx_select_done                    /* done the events */
+    }
+
+};
+
+ngx_module_t  ngx_select_module = {
+    NGX_MODULE,
+    &ngx_select_module_ctx,                /* module context */
+    NULL,                                  /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_int_t ngx_select_init(ngx_cycle_t *cycle)
+{
+    ngx_event_t  **index;
+
+    if (event_index == NULL) {
+        FD_ZERO(&master_read_fd_set);
+        FD_ZERO(&master_write_fd_set);
+        nevents = 0;
+    }
+
+    if (ngx_process == NGX_PROCESS_WORKER
+        || cycle->old_cycle == NULL
+        || cycle->old_cycle->connection_n < cycle->connection_n)
+    {
+        ngx_test_null(index,
+                      ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+                                cycle->log),
+                      NGX_ERROR);
+
+        if (event_index) {
+            ngx_memcpy(index, event_index, sizeof(ngx_event_t *) * nevents);
+            ngx_free(event_index);
+        }
+        event_index = index;
+
+#if 0
+        if (ready_index) {
+            ngx_free(ready_index);
+        }
+        ngx_test_null(ready_index,
+                      ngx_alloc(sizeof(ngx_event_t *) * 2 * cycle->connection_n,
+                      cycle->log),
+                      NGX_ERROR);
+#endif
+    }
+
+    ngx_io = ngx_os_io;
+
+    ngx_event_actions = ngx_select_module_ctx.actions;
+
+    ngx_event_flags = NGX_USE_LEVEL_EVENT|NGX_USE_ONESHOT_EVENT;
+
+#if (WIN32)
+    max_read = max_write = 0;
+#else
+    max_fd = -1;
+#endif
+
+    return NGX_OK;
+}
+
+
+static void ngx_select_done(ngx_cycle_t *cycle)
+{
+    ngx_free(event_index);
+#if 0
+    ngx_free(ready_index);
+#endif
+
+    event_index = NULL;
+}
+
+
+static ngx_int_t ngx_select_add_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select add event fd:%d ev:%d", c->fd, event);
+
+    if (ev->index != NGX_INVALID_INDEX) {
+        ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                      "select event fd:%d ev:%d is already set", c->fd, event);
+        return NGX_OK;
+    }
+
+#if (WIN32)
+
+    if ((event == NGX_READ_EVENT) && (max_read >= FD_SETSIZE)
+        || (event == NGX_WRITE_EVENT) && (max_write >= FD_SETSIZE))
+    {
+        ngx_log_error(NGX_LOG_ERR, ev->log, 0,
+                      "maximum number of descriptors "
+                      "supported by select() is %d", FD_SETSIZE);
+        return NGX_ERROR;
+    }
+
+    if (event == NGX_READ_EVENT) {
+        FD_SET(c->fd, &master_read_fd_set);
+        max_read++;
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_SET(c->fd, &master_write_fd_set);
+        max_write++;
+    }
+
+#else
+
+    if (event == NGX_READ_EVENT) {
+        FD_SET(c->fd, &master_read_fd_set);
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_SET(c->fd, &master_write_fd_set);
+    }
+
+    if (max_fd != -1 && max_fd < c->fd) {
+        max_fd = c->fd;
+    }
+
+#endif
+
+    ev->active = 1;
+    ev->oneshot = (u_char) ((flags & NGX_ONESHOT_EVENT) ? 1 : 0);
+
+    event_index[nevents] = ev;
+    ev->index = nevents;
+    nevents++;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_select_del_event(ngx_event_t *ev, int event, u_int flags)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ev->active = 0;
+
+    if (ev->index == NGX_INVALID_INDEX) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "select del event fd:%d ev:%d", c->fd, event);
+
+#if (WIN32)
+
+    if (event == NGX_READ_EVENT) {
+        FD_CLR(c->fd, &master_read_fd_set);
+        max_read--;
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_CLR(c->fd, &master_write_fd_set);
+        max_write--;
+    }
+
+#else
+
+    if (event == NGX_READ_EVENT) {
+        FD_CLR(c->fd, &master_read_fd_set);
+
+    } else if (event == NGX_WRITE_EVENT) {
+        FD_CLR(c->fd, &master_write_fd_set);
+    }
+
+    if (max_fd == c->fd) {
+        max_fd = -1;
+    }
+
+#endif
+
+    if (ev->index < (u_int) --nevents) {
+        event_index[ev->index] = event_index[nevents];
+        event_index[ev->index]->index = ev->index;
+    }
+
+    ev->index = NGX_INVALID_INDEX;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_select_process_events(ngx_cycle_t *cycle)
+{
+    int                       ready, nready;
+    ngx_uint_t                i, found, lock, expire;
+    ngx_err_t                 err;
+    ngx_msec_t                timer;
+    ngx_event_t              *ev;
+    ngx_connection_t         *c;
+    ngx_epoch_msec_t          delta;
+    struct timeval            tv, *tp;
+#if (HAVE_SELECT_CHANGE_TIMEOUT)
+    static ngx_epoch_msec_t   deltas = 0;
+#endif
+
+    for ( ;; ) {
+        timer = ngx_event_find_timer();
+
+        if (timer != 0) {
+            break;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "select expired timer");
+
+        ngx_event_expire_timers((ngx_msec_t)
+                                    (ngx_elapsed_msec - ngx_old_elapsed_msec));
+    }
+
+    ngx_old_elapsed_msec = ngx_elapsed_msec;
+
+    expire = 1;
+
+#if !(WIN32)
+
+    if (ngx_accept_mutex) {
+        if (ngx_accept_disabled > 0) {
+            ngx_accept_disabled--;
+
+        } else {
+            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ngx_accept_mutex_held == 0
+                && (timer == NGX_TIMER_INFINITE
+                    || timer > ngx_accept_mutex_delay))
+            {
+                timer = ngx_accept_mutex_delay;
+                expire = 0;
+            }
+        }
+    }
+
+    if (max_fd == -1) {
+        for (i = 0; i < nevents; i++) {
+            c = event_index[i]->data;
+            if (max_fd < c->fd) {
+                max_fd = c->fd;
+            }
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "change max_fd: %d", max_fd);
+    }
+
+#endif
+
+#if (NGX_DEBUG)
+    if (cycle->log->log_level & NGX_LOG_DEBUG_ALL) {
+        for (i = 0; i < nevents; i++) {
+            ev = event_index[i];
+            c = ev->data;
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "select event: fd:%d wr:%d", c->fd, ev->write);
+        }
+
+#if !(WIN32)
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "max_fd: %d", max_fd);
+#endif
+    }
+#endif
+
+    if (timer == NGX_TIMER_INFINITE) {
+        tp = NULL;
+        expire = 0;
+
+    } else {
+        tv.tv_sec = timer / 1000;
+        tv.tv_usec = (timer % 1000) * 1000;
+        tp = &tv;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select timer: %d", timer);
+
+    work_read_fd_set = master_read_fd_set;
+    work_write_fd_set = master_write_fd_set;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select read fd_set: %08X", *(int *) &work_read_fd_set);
+
+#if (WIN32)
+    ready = select(0, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+#else
+    ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp);
+#endif
+
+    if (ready == -1) {
+        err = ngx_socket_errno;
+    } else {
+        err = 0;
+    }
+
+#if (HAVE_SELECT_CHANGE_TIMEOUT)
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = timer - (tv.tv_sec * 1000 + tv.tv_usec / 1000);
+
+        /*
+         * learn the real time and update the cached time
+         * if the sum of the last deltas overcomes 1 second
+         */
+
+        deltas += delta;
+        if (deltas > 1000) {
+            ngx_gettimeofday(&tv);
+            ngx_time_update(tv.tv_sec);
+            deltas = tv.tv_usec / 1000;
+
+            ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+        } else {
+            ngx_elapsed_msec += delta;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "select timer: %d, delta: %d", timer, (int) delta);
+
+    } else {
+        delta = 0;
+        ngx_gettimeofday(&tv);
+        ngx_time_update(tv.tv_sec);
+
+        ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+        if (ready == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "select() returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+#else /* !(HAVE_SELECT_CHANGE_TIMEOUT) */
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    delta = ngx_elapsed_msec;
+    ngx_elapsed_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000
+                                          + tv.tv_usec / 1000 - ngx_start_msec;
+
+    if (timer != NGX_TIMER_INFINITE) {
+        delta = ngx_elapsed_msec - delta;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "select timer: %d, delta: %d", timer, (int) delta);
+
+    } else {
+        if (ready == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "select() returned no events without timeout");
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+#endif /* HAVE_SELECT_CHANGE_TIMEOUT */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "select ready %d", ready);
+
+    if (err) {
+#if (WIN32)
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "select() failed");
+#else
+        ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                      cycle->log, err, "select() failed");
+#endif
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        ngx_accept_mutex_unlock();
+        return NGX_ERROR;
+    }
+
+    lock = 1;
+    nready = 0;
+
+    for (i = 0; i < nevents; i++) {
+        ev = event_index[i];
+        c = ev->data;
+        found = 0;
+
+        if (ev->write) {
+            if (FD_ISSET(c->fd, &work_write_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select write %d", c->fd);
+            }
+
+        } else {
+            if (FD_ISSET(c->fd, &work_read_fd_set)) {
+                found = 1;
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "select read %d", c->fd);
+            }
+        }
+
+        if (found) {
+            ev->ready = 1;
+
+            if (ev->oneshot) {
+                if (ev->timer_set) {
+                    ngx_del_timer(ev);
+                }
+
+                if (ev->write) {
+                    ngx_select_del_event(ev, NGX_WRITE_EVENT, 0);
+                } else {
+                    ngx_select_del_event(ev, NGX_READ_EVENT, 0);
+                }
+            }
+
+            if (ev->accept) {
+                ev->next = accept_events;
+                accept_events = ev;
+            } else {
+                ngx_post_event(ev);
+            }
+
+            nready++;
+
+#if 0
+            ready_index[nready++] = ev;
+#endif
+        }
+    }
+
+#if 0
+    for (i = 0; i < nready; i++) {
+        ev = ready_index[i];
+        ready--;
+
+        if (!ev->active) {
+            continue;
+        }
+
+        ev->ready = 1;
+
+        if (ev->oneshot) {
+            if (ev->timer_set) {
+                ngx_del_timer(ev);
+            }
+
+            if (ev->write) {
+                ngx_select_del_event(ev, NGX_WRITE_EVENT, 0);
+            } else {
+                ngx_select_del_event(ev, NGX_READ_EVENT, 0);
+            }
+        }
+
+        ev->event_handler(ev);
+    }
+#endif
+
+    ev = accept_events;
+
+    for ( ;; ) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                      "accept event " PTR_FMT, ev);
+
+        if (ev == NULL) {
+            break;
+        }
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+
+        ev->event_handler(ev);
+
+        if (ngx_accept_disabled > 0) {
+            lock = 0;
+            break;
+        }
+
+        ev = ev->next;
+
+        if (ev == NULL) {
+            lock = 0;
+            break;
+        }
+
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            ngx_accept_mutex_unlock();
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_accept_mutex_unlock();
+    accept_events = NULL;
+
+    if (lock) {
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    if (ready != nready) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "select ready != events");
+    }
+
+    if (expire && delta) {
+        ngx_event_expire_timers((ngx_msec_t) delta);
+    }
+
+    if (!ngx_threaded) {
+        ngx_event_process_posted(cycle);
+    }
+
+    return NGX_OK;
+}
+
+
+static char *ngx_select_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf;
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ecf->use != ngx_select_module.ctx_index) {
+        return NGX_CONF_OK;
+    }
+
+    /* disable warning: the default FD_SETSIZE is 1024U in FreeBSD 5.x */
+
+#if !(WIN32)
+    if ((unsigned) ecf->connections > FD_SETSIZE) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                      "the maximum number of files "
+                      "supported by select() is " ngx_value(FD_SETSIZE));
+        return NGX_CONF_ERROR;
+    }
+#endif
+
+#if (NGX_THREADS)
+    ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                  "select() is not supported in the threaded mode");
+    return NGX_CONF_ERROR;
+#else
+    return NGX_CONF_OK;
+#endif
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event.c
@@ -0,0 +1,805 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define DEFAULT_CONNECTIONS  512
+
+
+extern ngx_module_t ngx_select_module;
+extern ngx_event_module_t ngx_select_module_ctx;
+
+#if (HAVE_KQUEUE)
+#include <ngx_kqueue_module.h>
+#endif
+
+#if (HAVE_DEVPOLL)
+extern ngx_module_t ngx_devpoll_module;
+extern ngx_event_module_t ngx_devpoll_module_ctx;
+#endif
+
+#if (HAVE_EPOLL)
+extern ngx_module_t ngx_epoll_module;
+extern ngx_event_module_t ngx_epoll_module_ctx;
+#endif
+
+#if (HAVE_RTSIG)
+extern ngx_module_t ngx_rtsig_module;
+extern ngx_event_module_t ngx_rtsig_module_ctx;
+#endif
+
+#if (HAVE_AIO)
+#include <ngx_aio_module.h>
+#endif
+
+static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle);
+static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf);
+static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
+                                        void *conf);
+
+static void *ngx_event_create_conf(ngx_cycle_t *cycle);
+static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf);
+static char *ngx_accept_mutex_check(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_uint_t                 ngx_event_max_module;
+
+ngx_uint_t                        ngx_event_flags;
+ngx_event_actions_t               ngx_event_actions;
+
+
+ngx_atomic_t                      connection_counter;
+ngx_atomic_t                     *ngx_connection_counter = &connection_counter;
+
+
+ngx_atomic_t                     *ngx_accept_mutex_ptr;
+ngx_atomic_t                     *ngx_accept_mutex;
+ngx_uint_t                        ngx_accept_mutex_held;
+ngx_msec_t                        ngx_accept_mutex_delay;
+ngx_int_t                         ngx_accept_disabled;
+
+
+#if (NGX_STAT_STUB)
+
+ngx_atomic_t   ngx_stat_accepted0;
+ngx_atomic_t  *ngx_stat_accepted = &ngx_stat_accepted0;
+ngx_atomic_t   ngx_stat_requests0;
+ngx_atomic_t  *ngx_stat_requests = &ngx_stat_requests0;
+ngx_atomic_t   ngx_stat_active0;
+ngx_atomic_t  *ngx_stat_active = &ngx_stat_active0;
+ngx_atomic_t   ngx_stat_reading0;
+ngx_atomic_t  *ngx_stat_reading = &ngx_stat_reading0;
+ngx_atomic_t   ngx_stat_writing0;
+ngx_atomic_t  *ngx_stat_writing = &ngx_stat_reading0;
+
+#endif
+
+
+
+static ngx_command_t  ngx_events_commands[] = {
+
+    { ngx_string("events"),
+      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_events_block,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+    
+static ngx_core_module_t  ngx_events_module_ctx = {
+    ngx_string("events"),
+    NULL,
+    NULL
+};  
+
+
+ngx_module_t  ngx_events_module = {
+    NGX_MODULE,
+    &ngx_events_module_ctx,                /* module context */
+    ngx_events_commands,                   /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_str_t  event_core_name = ngx_string("event_core");
+
+static ngx_conf_post_t  ngx_accept_mutex_post = { ngx_accept_mutex_check } ;
+
+
+static ngx_command_t  ngx_event_core_commands[] = {
+
+    { ngx_string("connections"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_connections,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("use"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_use,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("multi_accept"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_event_conf_t, multi_accept),
+      NULL },
+
+    { ngx_string("accept_mutex"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      0,
+      offsetof(ngx_event_conf_t, accept_mutex),
+      &ngx_accept_mutex_post },
+
+    { ngx_string("accept_mutex_delay"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      0,
+      offsetof(ngx_event_conf_t, accept_mutex_delay),
+      NULL },
+
+    { ngx_string("debug_connection"),
+      NGX_EVENT_CONF|NGX_CONF_TAKE1,
+      ngx_event_debug_connection,
+      0,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+ngx_event_module_t  ngx_event_core_module_ctx = {
+    &event_core_name,
+    ngx_event_create_conf,                 /* create configuration */
+    ngx_event_init_conf,                   /* init configuration */
+
+    { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+};
+
+
+ngx_module_t  ngx_event_core_module = {
+    NGX_MODULE,
+    &ngx_event_core_module_ctx,            /* module context */
+    ngx_event_core_commands,               /* module directives */
+    NGX_EVENT_MODULE,                      /* module type */
+    ngx_event_module_init,                 /* init module */
+    ngx_event_process_init                 /* init process */
+};
+
+
+static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle)
+{
+#if !(WIN32)
+
+    size_t             size;
+    char              *shared;
+    ngx_core_conf_t   *ccf;
+    ngx_event_conf_t  *ecf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ccf->master == 0 || ngx_accept_mutex_ptr) {
+        return NGX_OK;
+    }
+
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+
+    /* TODO: 128 is cache line size */
+
+    size = 128            /* ngx_accept_mutex */
+           + 128;         /* ngx_connection_counter */
+
+#if (NGX_STAT_STUB)
+
+    size += 128           /* ngx_stat_accepted */
+           + 128          /* ngx_stat_requests */
+           + 128          /* ngx_stat_active */
+           + 128          /* ngx_stat_reading */
+           + 128;         /* ngx_stat_writing */
+
+#endif
+
+    if (!(shared = ngx_create_shared_memory(size, cycle->log))) {
+        return NGX_ERROR;
+    }
+
+    ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
+    ngx_connection_counter = (ngx_atomic_t *) (shared + 128);
+
+#if (NGX_STAT_STUB)
+
+    ngx_stat_accepted = (ngx_atomic_t *) (shared + 2 * 128);
+    ngx_stat_requests = (ngx_atomic_t *) (shared + 3 * 128);
+    ngx_stat_active = (ngx_atomic_t *) (shared + 4 * 128);
+    ngx_stat_reading = (ngx_atomic_t *) (shared + 5 * 128);
+    ngx_stat_writing = (ngx_atomic_t *) (shared + 6 * 128);
+
+#endif
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "counter: " PTR_FMT ", %d",
+                   ngx_connection_counter, *ngx_connection_counter);
+
+#endif
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
+{
+    ngx_uint_t           m, i;
+    ngx_socket_t         fd;
+    ngx_event_t         *rev, *wev;
+    ngx_listening_t     *s;
+    ngx_connection_t    *c;
+    ngx_core_conf_t     *ccf;
+    ngx_event_conf_t    *ecf;
+    ngx_event_module_t  *module;
+#if (WIN32)
+    ngx_iocp_conf_t     *iocpcf;
+#endif
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+    ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module);
+
+    if (ngx_accept_mutex_ptr && ccf->worker_processes > 1 && ecf->accept_mutex)
+    {
+        ngx_accept_mutex = ngx_accept_mutex_ptr;
+        ngx_accept_mutex_held = 0;
+        ngx_accept_mutex_delay = ecf->accept_mutex_delay;
+    }
+
+#if (NGX_THREADS)
+    if (!(ngx_posted_events_mutex = ngx_mutex_init(cycle->log, 0))) {
+        return NGX_ERROR;
+    }
+#endif
+
+    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    cycle->connection_n = ecf->connections;
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        if (ngx_modules[m]->ctx_index == ecf->use) {
+            module = ngx_modules[m]->ctx;
+            if (module->actions.init(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(2);
+            }
+            break;
+        }
+    }
+
+    cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * ecf->connections,
+                                   cycle->log);
+    if (cycle->connections == NULL) {
+        return NGX_ERROR;
+    }
+
+    c = cycle->connections;
+    for (i = 0; i < cycle->connection_n; i++) {
+        c[i].fd = (ngx_socket_t) -1;
+        c[i].data = NULL;
+#if (NGX_THREADS)
+        c[i].lock = 0;
+#endif
+    }
+
+    cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * ecf->connections,
+                                   cycle->log);
+    if (cycle->read_events == NULL) {
+        return NGX_ERROR;
+    }
+
+    rev = cycle->read_events;
+    for (i = 0; i < cycle->connection_n; i++) {
+        rev[i].closed = 1;
+#if (NGX_THREADS)
+        rev[i].lock = &c[i].lock;
+        rev[i].own_lock = &c[i].lock;
+#endif
+    }
+
+    cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * ecf->connections,
+                                   cycle->log);
+    if (cycle->write_events == NULL) {
+        return NGX_ERROR;
+    }
+
+    wev = cycle->write_events;
+    for (i = 0; i < cycle->connection_n; i++) {
+        wev[i].closed = 1;
+#if (NGX_THREADS)
+        wev[i].lock = &c[i].lock;
+        wev[i].own_lock = &c[i].lock;
+#endif
+    }
+
+    /* for each listening socket */
+
+    s = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        fd = s[i].fd;
+
+#if (WIN32)
+        /*
+         * Winsock assignes a socket number divisible by 4
+         * so to find a connection we divide a socket number by 4.
+         */
+
+        fd /= 4;
+#endif
+
+        c = &cycle->connections[fd];
+        rev = &cycle->read_events[fd];
+        wev = &cycle->write_events[fd];
+
+        ngx_memzero(c, sizeof(ngx_connection_t));
+        ngx_memzero(rev, sizeof(ngx_event_t));
+
+        c->fd = s[i].fd;
+        c->listening = &s[i];
+
+        c->ctx = s[i].ctx;
+        c->servers = s[i].servers;
+        c->log = s[i].log;
+        c->read = rev;
+
+        /* required by iocp in "c->write->active = 1" */
+        c->write = wev;
+
+        /* required by poll */
+        wev->index = NGX_INVALID_INDEX;
+
+        rev->log = c->log;
+        rev->data = c;
+        rev->index = NGX_INVALID_INDEX;
+
+        rev->available = 0;
+
+        rev->accept = 1;
+
+#if (HAVE_DEFERRED_ACCEPT)
+        rev->deferred_accept = s[i].deferred_accept;
+#endif
+
+        if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) {
+            if (s[i].remain) {
+
+                /*
+                 * delete the old accept events that were bound to
+                 * the old cycle read events array
+                 */
+
+                if (ngx_del_event(&cycle->old_cycle->read_events[fd],
+                                 NGX_READ_EVENT, NGX_CLOSE_EVENT) == NGX_ERROR)
+                {
+                    return NGX_ERROR;
+                }
+
+                cycle->old_cycle->connections[fd].fd = (ngx_socket_t) -1;
+            }
+        }
+
+#if (WIN32)
+
+        if (ngx_event_flags & NGX_USE_IOCP_EVENT) {
+            rev->event_handler = &ngx_event_acceptex;
+
+            if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module);
+            if (ngx_event_post_acceptex(&s[i], iocpcf->post_acceptex)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+        } else {
+            rev->event_handler = &ngx_event_accept;
+            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+#else
+
+        rev->event_handler = &ngx_event_accept;
+
+        if (ngx_accept_mutex) {
+            continue;
+        }
+
+        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+            if (ngx_add_conn(c) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+        } else {
+            if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+#endif
+    }
+
+    return NGX_OK;
+}
+
+
+static char *ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    int                    m;
+    char                  *rv;
+    void               ***ctx;
+    ngx_conf_t            pcf;
+    ngx_event_module_t   *module;
+
+    /* count the number of the event modules and set up their indices */
+
+    ngx_event_max_module = 0;
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        ngx_modules[m]->ctx_index = ngx_event_max_module++;
+    }
+
+    ngx_test_null(ctx, ngx_pcalloc(cf->pool, sizeof(void *)), NGX_CONF_ERROR);
+
+    ngx_test_null(*ctx,
+                  ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *)),
+                  NGX_CONF_ERROR);
+
+    *(void **) conf = ctx;
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->create_conf) {
+            ngx_test_null((*ctx)[ngx_modules[m]->ctx_index],
+                          module->create_conf(cf->cycle),
+                          NGX_CONF_ERROR);
+        }
+    }
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->module_type = NGX_EVENT_MODULE;
+    cf->cmd_type = NGX_EVENT_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+    *cf = pcf;
+
+    if (rv != NGX_CONF_OK)
+        return rv;
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->init_conf) {
+            rv = module->init_conf(cf->cycle,
+                                   (*ctx)[ngx_modules[m]->ctx_index]);
+            if (rv != NGX_CONF_OK) {
+                return rv;
+            }
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_event_connections(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+
+    ngx_str_t  *value;
+
+    if (ecf->connections != NGX_CONF_UNSET_UINT) {
+        return "is duplicate" ;
+    }
+
+    value = cf->args->elts;
+    ecf->connections = ngx_atoi(value[1].data, value[1].len);
+    if (ecf->connections == (ngx_uint_t) NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid number \"%s\"", value[1].data);
+
+        return NGX_CONF_ERROR;
+    }
+
+    cf->cycle->connection_n = ecf->connections;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_event_use(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+
+    ngx_int_t             m;
+    ngx_str_t            *value;
+    ngx_event_conf_t     *old_ecf;
+    ngx_event_module_t   *module;
+
+    if (ecf->use != NGX_CONF_UNSET_UINT) {
+        return "is duplicate" ;
+    }
+
+    value = cf->args->elts;
+
+    if (cf->cycle->old_cycle->conf_ctx) {
+        old_ecf = ngx_event_get_conf(cf->cycle->old_cycle->conf_ctx,
+                                     ngx_event_core_module);
+    } else {
+        old_ecf = NULL;
+    }
+
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+        if (module->name->len == value[1].len) {
+            if (ngx_strcmp(module->name->data, value[1].data) == 0) {
+                ecf->use = ngx_modules[m]->ctx_index;
+                ecf->name = module->name->data;
+
+                if (ngx_process == NGX_PROCESS_SINGLE
+                    && old_ecf
+                    && old_ecf->use != ecf->use)
+                {
+                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                        "when the server runs without a master process "
+                        "the \"%s\" event type must be the same as "
+                        "in previous configuration - \"%s\" "
+                        "and it can not be changed on the fly, "
+                        "to change it you need to stop server "
+                        "and start it again",
+                        value[1].data, old_ecf->name);
+
+                    return NGX_CONF_ERROR;
+                }
+
+                return NGX_CONF_OK;
+            }
+        }
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "invalid event type \"%s\"", value[1].data);
+
+    return NGX_CONF_ERROR;
+}
+
+
+static char *ngx_event_debug_connection(ngx_conf_t *cf, ngx_command_t *cmd,
+                                        void *conf)
+{
+#if (NGX_DEBUG)
+    ngx_event_conf_t  *ecf = conf;
+
+    in_addr_t       *addr;
+    ngx_str_t       *value;
+    struct hostent  *h;
+
+    value = cf->args->elts;
+
+    /* AF_INET only */
+
+    if (!(addr = ngx_push_array(&ecf->debug_connection))) {
+        return NGX_CONF_ERROR;
+    }
+
+    *addr = inet_addr((char *) value[1].data);
+
+    if (*addr != INADDR_NONE) {
+        return NGX_OK;
+    }
+
+    h = gethostbyname((char *) value[1].data);
+
+    if (h == NULL || h->h_addr_list[0] == NULL) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "host %s not found", value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    *addr = *(in_addr_t *)(h->h_addr_list[0]);
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"debug_connection\" is ignored, you need to rebuild "
+                       "nginx using --with-debug option to enable it");
+
+#endif
+
+    return NGX_OK;
+}
+
+
+static void *ngx_event_create_conf(ngx_cycle_t *cycle)
+{
+    ngx_event_conf_t  *ecf;
+
+    ngx_test_null(ecf, ngx_palloc(cycle->pool, sizeof(ngx_event_conf_t)),
+                  NGX_CONF_ERROR);
+
+    ecf->connections = NGX_CONF_UNSET_UINT;
+    ecf->use = NGX_CONF_UNSET_UINT;
+    ecf->multi_accept = NGX_CONF_UNSET;
+    ecf->accept_mutex = NGX_CONF_UNSET;
+    ecf->accept_mutex_delay = NGX_CONF_UNSET_MSEC;
+    ecf->name = (void *) NGX_CONF_UNSET;
+
+#if (NGX_DEBUG)
+    ngx_init_array(ecf->debug_connection, cycle->pool, 5, sizeof(in_addr_t),
+                   NGX_CONF_ERROR);
+#endif
+
+    return ecf;
+}
+
+
+static char *ngx_event_init_conf(ngx_cycle_t *cycle, void *conf)
+{
+    ngx_event_conf_t  *ecf = conf;
+#if (HAVE_RTSIG)
+    ngx_core_conf_t  *ccf;
+#endif
+
+#if (HAVE_KQUEUE)
+
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+    ngx_conf_init_unsigned_value(ecf->use, ngx_kqueue_module.ctx_index);
+    ngx_conf_init_ptr_value(ecf->name, ngx_kqueue_module_ctx.name->data);
+
+#elif (HAVE_DEVPOLL)
+
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+    ngx_conf_init_unsigned_value(ecf->use, ngx_devpoll_module.ctx_index);
+    ngx_conf_init_ptr_value(ecf->name, ngx_devpoll_module_ctx.name->data);
+
+#elif (HAVE_EPOLL)
+
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+    ngx_conf_init_unsigned_value(ecf->use, ngx_epoll_module.ctx_index);
+    ngx_conf_init_ptr_value(ecf->name, ngx_epoll_module_ctx.name->data);
+
+#elif (HAVE_RTSIG)
+
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+    ngx_conf_init_unsigned_value(ecf->use, ngx_rtsig_module.ctx_index);
+    ngx_conf_init_ptr_value(ecf->name, ngx_rtsig_module_ctx.name->data);
+
+#elif (HAVE_SELECT)
+
+#if (WIN32)
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+#else
+    ngx_conf_init_unsigned_value(ecf->connections,
+          FD_SETSIZE < DEFAULT_CONNECTIONS ? FD_SETSIZE : DEFAULT_CONNECTIONS);
+#endif
+
+    ngx_conf_init_unsigned_value(ecf->use, ngx_select_module.ctx_index);
+    ngx_conf_init_ptr_value(ecf->name, ngx_select_module_ctx.name->data);
+
+#else
+
+    ngx_int_t            i, m;
+    ngx_event_module_t  *module;
+
+    m = -1;
+    module = NULL;
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->type == NGX_EVENT_MODULE) {
+            module = ngx_modules[i]->ctx;
+
+            if (ngx_strcmp(module->name->data, event_core_name.data) == 0) {
+                continue;
+            }
+
+            m = ngx_modules[i]->ctx_index;
+            break;
+        }
+    }
+
+    if (m == -1) {
+        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "no events module found");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_init_unsigned_value(ecf->connections, DEFAULT_CONNECTIONS);
+
+    ngx_conf_init_unsigned_value(ecf->use, m);
+    ngx_conf_init_ptr_value(ecf->name, module->name->data);
+
+#endif
+
+    cycle->connection_n = ecf->connections;
+
+    ngx_conf_init_value(ecf->multi_accept, 0);
+    ngx_conf_init_value(ecf->accept_mutex, 1);
+    ngx_conf_init_msec_value(ecf->accept_mutex_delay, 500);
+
+#if (HAVE_RTSIG)
+    if (ecf->use == ngx_rtsig_module.ctx_index && ecf->accept_mutex == 0) {
+        ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                               ngx_core_module);
+        if (ccf->worker_processes) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
+                          "the \"rtsig\" method requires "
+                          "\"accept_mutex\" to be on");
+            return NGX_CONF_ERROR;
+        }
+    }
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_accept_mutex_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if !(NGX_HAVE_ATOMIC_OPS)
+
+    ngx_flag_t *fp = data;
+
+    *fp = 0;
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"accept_mutex\" is not supported, ignored");
+
+#endif
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event.h
@@ -0,0 +1,637 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_H_INCLUDED_
+#define _NGX_EVENT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_INVALID_INDEX  0xd0d0d0d0
+
+
+#if (HAVE_IOCP)
+
+typedef struct {
+    WSAOVERLAPPED    ovlp;
+    ngx_event_t     *event;
+    int              error;
+} ngx_event_ovlp_t;
+
+#endif
+
+
+typedef struct {
+    ngx_uint_t       lock;
+
+    ngx_event_t     *events;
+    ngx_event_t     *last;
+} ngx_event_mutex_t;
+
+
+struct ngx_event_s {
+    void            *data;
+
+    unsigned         write:1;
+
+    unsigned         accept:1;
+
+    unsigned         oneshot:1;
+
+    /* used to detect the stale events in kqueue, rt signals and epoll */
+    unsigned         instance:1;
+
+    /*
+     * the event was passed or would be passed to a kernel;
+     * in aio mode - operation was posted.
+     */
+    unsigned         active:1;
+
+    unsigned         disabled:1;
+
+    /* the ready event; in aio mode 0 means that no operation can be posted */
+    unsigned         ready:1;
+
+    /* aio operation is complete */
+    unsigned         complete:1;
+
+    unsigned         eof:1;
+    unsigned         error:1;
+
+    unsigned         timedout:1;
+    unsigned         timer_set:1;
+
+    unsigned         delayed:1;
+
+    unsigned         read_discarded:1;
+
+    unsigned         unexpected_eof:1;
+
+    unsigned         deferred_accept:1;
+
+    /* the pending eof reported by kqueue or in aio chain operation */
+    unsigned         pending_eof:1;
+
+#if !(NGX_THREADS)
+    unsigned         posted_ready:1;
+#endif
+
+#if (WIN32)
+    /* setsockopt(SO_UPDATE_ACCEPT_CONTEXT) was succesfull */
+    unsigned         accept_context_updated:1;
+#endif
+
+#if (HAVE_KQUEUE)
+    unsigned         kq_vnode:1;
+
+    /* the pending errno reported by kqueue */
+    int              kq_errno;
+#endif
+
+    /*
+     * kqueue only:
+     *   accept:     number of sockets that wait to be accepted
+     *   read:       bytes to read when event is ready
+     *               or lowat when event is set with NGX_LOWAT_EVENT flag
+     *   write:      available space in buffer when event is ready
+     *               or lowat when event is set with NGX_LOWAT_EVENT flag
+     *
+     * iocp: TODO
+     *
+     * otherwise:
+     *   accept:     1 if accept many, 0 otherwise
+     */
+
+#if (HAVE_KQUEUE) || (HAVE_IOCP)
+    int              available;
+#else
+    unsigned         available:1;
+#endif
+
+    /* TODO rename to handler */
+    ngx_event_handler_pt  event_handler;
+
+
+#if (HAVE_AIO)
+
+#if (HAVE_IOCP)
+    ngx_event_ovlp_t ovlp;
+#else
+    struct aiocb     aiocb;
+#endif
+
+#endif
+
+    u_int            index;
+
+    ngx_log_t       *log;
+
+    /* TODO: threads: padding to cache line */
+
+    /*
+     * STUB: The inline of "ngx_rbtree_t  rbtree;"
+     */
+
+    ngx_int_t        rbtree_key;
+    void            *rbtree_left;
+    void            *rbtree_right;
+    void            *rbtree_parent;
+    char             rbtree_color;
+
+
+    unsigned         closed:1;
+
+#if (NGX_THREADS)
+
+    unsigned         locked:1;
+
+    unsigned         posted_ready:1;
+    unsigned         posted_timedout:1;
+    unsigned         posted_eof:1;
+
+#if (HAVE_KQUEUE)
+    /* the pending errno reported by kqueue */
+    int              posted_errno;
+#endif
+
+#if (HAVE_KQUEUE) || (HAVE_IOCP)
+    int              posted_available;
+#else
+    unsigned         posted_available:1;
+#endif
+
+    ngx_atomic_t    *lock;
+    ngx_atomic_t    *own_lock;
+
+#endif
+
+    /* the links of the posted queue */
+    ngx_event_t     *next;
+    ngx_event_t    **prev;
+
+
+#if 0
+
+    /* the threads support */
+
+    /*
+     * the event thread context, we store it here
+     * if $(CC) does not understand __thread declaration
+     * and pthread_getspecific() is too costly
+     */
+
+    void            *thr_ctx;
+
+#if (NGX_EVENT_T_PADDING)
+
+    /* event should not cross cache line in SMP */
+
+    int              padding[NGX_EVENT_T_PADDING];
+#endif
+#endif
+};
+
+
+typedef struct {
+    ngx_int_t  (*add)(ngx_event_t *ev, int event, u_int flags);
+    ngx_int_t  (*del)(ngx_event_t *ev, int event, u_int flags);
+
+    ngx_int_t  (*enable)(ngx_event_t *ev, int event, u_int flags);
+    ngx_int_t  (*disable)(ngx_event_t *ev, int event, u_int flags);
+
+    ngx_int_t  (*add_conn)(ngx_connection_t *c);
+    ngx_int_t  (*del_conn)(ngx_connection_t *c, u_int flags);
+
+    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t try);
+    ngx_int_t  (*process_events)(ngx_cycle_t *cycle);
+
+    ngx_int_t  (*init)(ngx_cycle_t *cycle);
+    void       (*done)(ngx_cycle_t *cycle);
+} ngx_event_actions_t;
+
+
+extern ngx_event_actions_t   ngx_event_actions;
+
+
+/*
+ * The event filter requires to read/write the whole data -
+ * select, poll, /dev/poll, kqueue, epoll.
+ */
+#define NGX_USE_LEVEL_EVENT      0x00000001
+
+/*
+ * The event filter is deleted after a notification without an additional
+ * syscall - select, poll, kqueue, epoll, Solaris 10's event ports.
+ */
+#define NGX_USE_ONESHOT_EVENT    0x00000002
+
+/*
+ * The event filter notifies only the changes and an initial level -
+ * kqueue, epoll.
+ */
+#define NGX_USE_CLEAR_EVENT      0x00000004
+
+/*
+ * The event filter has kqueue features - the eof flag, errno,
+ * available data, etc.
+ */
+#define NGX_HAVE_KQUEUE_EVENT    0x00000008
+
+/*
+ * The event filter supports low water mark - kqueue's NOTE_LOWAT.
+ * kqueue in FreeBSD 4.1-4.2 has no NOTE_LOWAT so we need a separate flag.
+ */
+#define NGX_HAVE_LOWAT_EVENT     0x00000010
+
+/*
+ * The event filter requires to do i/o operation until EAGAIN -
+ * epoll, rt signals.
+ */
+#define NGX_HAVE_GREEDY_EVENT    0x00000020
+
+/*
+ * The event filter is epoll,
+ */
+#define NGX_USE_EPOLL_EVENT      0x00000040
+
+/*
+ * No need to add or delete the event filters - rt signals.
+ */
+#define NGX_USE_RTSIG_EVENT      0x00000080
+
+/*
+ * No need to add or delete the event filters - overlapped, aio_read,
+ * aioread, io_submit.
+ */
+#define NGX_USE_AIO_EVENT        0x00000100
+
+/*
+ * Need to add socket or handle only once - i/o completion port.
+ * It also requires HAVE_AIO and NGX_USE_AIO_EVENT to be set.
+ */
+#define NGX_USE_IOCP_EVENT       0x00000200
+
+
+
+/*
+ * The event filter is deleted before the closing file.
+ * Has no meaning for select, poll, epoll.
+ *
+ * kqueue:     kqueue deletes event filters for file that closed
+ *             so we need only to delete filters in user-level batch array
+ * /dev/poll:  we need to flush POLLREMOVE event before closing file
+ */
+
+#define NGX_CLOSE_EVENT    1
+#define NGX_DISABLE_EVENT  2
+
+
+/* these flags have a meaning only for kqueue */
+#define NGX_LOWAT_EVENT    0
+#define NGX_VNODE_EVENT    0
+
+
+#if (HAVE_KQUEUE)
+
+#define NGX_READ_EVENT     EVFILT_READ
+#define NGX_WRITE_EVENT    EVFILT_WRITE
+
+#undef  NGX_VNODE_EVENT
+#define NGX_VNODE_EVENT    EVFILT_VNODE
+
+/*
+ * NGX_CLOSE_EVENT and NGX_LOWAT_EVENT are the module flags and they would
+ * not go into a kernel so we need to choose the value that would not interfere
+ * with any existent and future kqueue flags.  kqueue has such values -
+ * EV_FLAG1, EV_EOF and EV_ERROR.  They are reserved and cleared on a kernel
+ * entrance.
+ */
+#undef  NGX_CLOSE_EVENT
+#define NGX_CLOSE_EVENT    EV_EOF
+
+#undef  NGX_LOWAT_EVENT
+#define NGX_LOWAT_EVENT    EV_FLAG1
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  EV_ONESHOT
+#define NGX_CLEAR_EVENT    EV_CLEAR
+
+#undef  NGX_DISABLE_EVENT
+#define NGX_DISABLE_EVENT  EV_DISABLE
+
+
+#elif (HAVE_DEVPOLL)
+
+#define NGX_READ_EVENT     POLLIN
+#define NGX_WRITE_EVENT    POLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+
+#elif (HAVE_EPOLL)
+
+#define NGX_READ_EVENT     EPOLLIN
+#define NGX_WRITE_EVENT    EPOLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_CLEAR_EVENT    EPOLLET
+#define NGX_ONESHOT_EVENT  0x70000000
+#if 0
+#define NGX_ONESHOT_EVENT  EPOLLONESHOT
+#endif
+
+
+#elif (HAVE_POLL)
+
+#define NGX_READ_EVENT     POLLIN
+#define NGX_WRITE_EVENT    POLLOUT
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+
+#else /* select */
+
+#define NGX_READ_EVENT     0
+#define NGX_WRITE_EVENT    1
+
+#define NGX_LEVEL_EVENT    0
+#define NGX_ONESHOT_EVENT  1
+
+#endif /* HAVE_KQUEUE */
+
+
+#if (HAVE_IOCP)
+#define NGX_IOCP_ACCEPT      0
+#define NGX_IOCP_IO          1
+#define NGX_IOCP_CONNECT     2
+#endif
+
+
+#ifndef NGX_CLEAR_EVENT
+#define NGX_CLEAR_EVENT    0    /* dummy declaration */
+#endif
+
+
+#define ngx_process_changes  ngx_event_actions.process_changes
+#define ngx_process_events   ngx_event_actions.process_events
+#define ngx_done_events      ngx_event_actions.done
+
+#define ngx_add_event        ngx_event_actions.add
+#define ngx_del_event        ngx_event_actions.del
+#define ngx_add_conn         ngx_event_actions.add_conn
+#define ngx_del_conn         ngx_event_actions.del_conn
+
+#define ngx_add_timer        ngx_event_add_timer
+#define ngx_del_timer        ngx_event_del_timer
+
+
+#define ngx_recv             ngx_io.recv
+#define ngx_recv_chain       ngx_io.recv_chain
+#define ngx_send             ngx_io.send
+#define ngx_send_chain       ngx_io.send_chain
+
+
+
+#define NGX_EVENT_MODULE      0x544E5645  /* "EVNT" */
+#define NGX_EVENT_CONF        0x02000000
+
+
+typedef struct {
+    ngx_uint_t    connections;
+    ngx_uint_t    use;
+
+    ngx_flag_t    multi_accept;
+    ngx_flag_t    accept_mutex;
+
+    ngx_msec_t    accept_mutex_delay;
+
+    u_char       *name;
+
+#if (NGX_DEBUG)
+    ngx_array_t   debug_connection;
+#endif
+} ngx_event_conf_t;
+
+
+typedef struct {
+    ngx_str_t              *name;
+
+    void                 *(*create_conf)(ngx_cycle_t *cycle);
+    char                 *(*init_conf)(ngx_cycle_t *cycle, void *conf);
+
+    ngx_event_actions_t     actions;
+} ngx_event_module_t;
+
+
+extern ngx_atomic_t          *ngx_connection_counter;
+
+extern ngx_atomic_t          *ngx_accept_mutex_ptr;
+extern ngx_atomic_t          *ngx_accept_mutex;
+extern ngx_uint_t             ngx_accept_mutex_held;
+extern ngx_msec_t             ngx_accept_mutex_delay;
+extern ngx_int_t              ngx_accept_disabled;
+
+
+#if (NGX_STAT_STUB)
+
+extern ngx_atomic_t  *ngx_stat_accepted;
+extern ngx_atomic_t  *ngx_stat_requests;
+extern ngx_atomic_t  *ngx_stat_active;
+extern ngx_atomic_t  *ngx_stat_reading;
+extern ngx_atomic_t  *ngx_stat_writing;
+
+#endif
+
+
+
+#define ngx_accept_mutex_unlock()                                             \
+           if (ngx_accept_mutex_held) {                                       \
+               *ngx_accept_mutex = 0;                                         \
+           }
+
+
+extern ngx_uint_t             ngx_event_flags;
+extern ngx_module_t           ngx_events_module;
+extern ngx_module_t           ngx_event_core_module;
+
+
+#define ngx_event_get_conf(conf_ctx, module)                                  \
+             (*(ngx_get_conf(conf_ctx, ngx_events_module))) [module.ctx_index];
+
+
+
+void ngx_event_accept(ngx_event_t *ev);
+ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle);
+ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle);
+ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle);
+
+
+#if (WIN32)
+void ngx_event_acceptex(ngx_event_t *ev);
+int ngx_event_post_acceptex(ngx_listening_t *ls, int n);
+#endif
+
+
+/* used in ngx_log_debugX() */
+#define ngx_event_ident(p)  ((ngx_connection_t *) (p))->fd
+
+
+#include <ngx_event_timer.h>
+#include <ngx_event_posted.h>
+#include <ngx_event_busy_lock.h>
+
+#if (WIN32)
+#include <ngx_iocp_module.h>
+#endif
+
+
+
+ngx_inline static int ngx_handle_read_event(ngx_event_t *rev, u_int flags)
+{
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue */
+
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT)
+                                                                == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+        }
+
+        return NGX_OK;
+
+    } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+        /* select, poll, /dev/poll */
+
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rev->active && (rev->ready || (flags & NGX_CLOSE_EVENT))) {
+            if (ngx_del_event(rev, NGX_READ_EVENT, flags) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    /* aio, iocp, epoll, rtsig */
+
+    return NGX_OK;
+}
+
+
+ngx_inline static int ngx_handle_level_read_event(ngx_event_t *rev)
+{
+    if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+        if (!rev->active && !rev->ready) {
+            if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (rev->active && rev->ready) {
+            if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_inline static int ngx_handle_write_event(ngx_event_t *wev, u_int flags)
+{
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        /* kqueue */
+
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT|flags)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+
+        return NGX_OK;
+
+    } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+
+        /* select, poll, /dev/poll */
+
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (wev->active && wev->ready) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    /* aio, iocp, epoll, rtsig */
+
+    return NGX_OK;
+}
+
+
+ngx_inline static int ngx_handle_level_write_event(ngx_event_t *wev)
+{
+    if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+        if (!wev->active && !wev->ready) {
+            if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+
+        if (wev->active && wev->ready) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+#endif /* _NGX_EVENT_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_accept.c
@@ -0,0 +1,475 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <nginx.h>
+
+
+typedef struct {
+    int      flag;
+    u_char  *name;
+} ngx_accept_log_ctx_t;
+
+
+static void ngx_close_accepted_socket(ngx_socket_t s, ngx_log_t *log);
+static size_t ngx_accept_log_error(void *data, char *buf, size_t len);
+
+
+void ngx_event_accept(ngx_event_t *ev)
+{
+    ngx_uint_t             instance, accepted;
+    socklen_t              len;
+    struct sockaddr       *sa;
+    ngx_err_t              err;
+    ngx_log_t             *log;
+    ngx_pool_t            *pool;
+    ngx_socket_t           s;
+    ngx_event_t           *rev, *wev;
+    ngx_connection_t      *c, *ls;
+    ngx_event_conf_t      *ecf;
+    ngx_accept_log_ctx_t  *ctx;
+
+    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
+
+    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+        ev->available = 1;
+
+    } else if (!(ngx_event_flags & NGX_HAVE_KQUEUE_EVENT)) {
+        ev->available = ecf->multi_accept;
+    }
+
+    ls = ev->data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "accept on %s, ready: %d",
+                   ls->listening->addr_text.data, ev->available);
+
+    ev->ready = 0;
+    accepted = 0;
+    pool = NULL;
+
+    do {
+
+        if (pool == NULL) {
+
+            /*
+             * Create the pool before accept() to avoid the copying of
+             * the sockaddr.  Although accept() can fail it is uncommon
+             * case and besides the pool can be got from the free pool list
+             */
+
+            if (!(pool = ngx_create_pool(ls->listening->pool_size, ev->log))) {
+                return;
+            }
+        }
+
+        if (!(sa = ngx_palloc(pool, ls->listening->socklen))) {
+            ngx_destroy_pool(pool);
+            return;
+        }
+
+        if (!(log = ngx_palloc(pool, sizeof(ngx_log_t)))) {
+            ngx_destroy_pool(pool);
+            return;
+        }
+
+        ngx_memcpy(log, ls->log, sizeof(ngx_log_t));
+        pool->log = log;
+
+        if (!(ctx = ngx_palloc(pool, sizeof(ngx_accept_log_ctx_t)))) {
+            ngx_destroy_pool(pool);
+            return;
+        }
+
+        /* -1 disables the connection number logging */
+        ctx->flag = -1;
+        ctx->name = ls->listening->addr_text.data;
+
+        log->data = ctx;
+        log->handler = ngx_accept_log_error;
+
+        len = ls->listening->socklen;
+
+        s = accept(ls->fd, sa, &len);
+        if (s == -1) {
+            err = ngx_socket_errno;
+
+            if (err == NGX_EAGAIN) {
+#if 0
+                if (!(ngx_event_flags & NGX_USE_RTSIG_EVENT))
+                {
+                    ngx_log_error(NGX_LOG_NOTICE, log, err,
+                                  "EAGAIN after %d accepted connection(s)",
+                                  accepted);
+                }
+#endif
+
+                ngx_destroy_pool(pool);
+                return;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, ev->log, err,
+                          "accept() on %s failed",
+                          ls->listening->addr_text.data);
+
+            if (err == NGX_ECONNABORTED) {
+                if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+                    ev->available--;
+                }
+
+                if (ev->available) {
+                    /* reuse the previously allocated pool */
+                    continue;
+                }
+            }
+
+            ngx_destroy_pool(pool);
+            return;
+        }
+
+#if (NGX_STAT_STUB)
+        (*ngx_stat_accepted)++;
+#endif
+
+        ngx_accept_disabled = (ngx_uint_t) s + NGX_ACCEPT_THRESHOLD
+                                                            - ecf->connections;
+
+        /* disable warning: Win32 SOCKET is u_int while UNIX socket is int */
+
+        if ((ngx_uint_t) s >= ecf->connections) {
+
+            ngx_log_error(NGX_LOG_ALERT, ev->log, 0,
+                          "accept() on %s returned socket #%d while "
+                          "only %d connections was configured, "
+                          "closing the connection",
+                          ls->listening->addr_text.data, s, ecf->connections);
+
+            ngx_close_accepted_socket(s, log);
+            ngx_destroy_pool(pool);
+            return;
+        }
+
+#if (NGX_STAT_STUB)
+        (*ngx_stat_active)++;
+#endif
+
+        /* set a blocking mode for aio and non-blocking mode for the others */
+
+        if (ngx_inherited_nonblocking) {
+            if ((ngx_event_flags & NGX_USE_AIO_EVENT)) {
+                if (ngx_blocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+                                  ngx_blocking_n " failed");
+
+                    ngx_close_accepted_socket(s, log);
+                    ngx_destroy_pool(pool);
+                    return;
+                }
+            }
+
+        } else {
+            if (!(ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT))) {
+                if (ngx_nonblocking(s) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+                                  ngx_nonblocking_n " failed");
+
+                    ngx_close_accepted_socket(s, log);
+                    ngx_destroy_pool(pool);
+                    return;
+                }
+            }
+        }
+
+#if (WIN32)
+        /*
+         * Winsock assignes a socket number divisible by 4
+         * so to find a connection we divide a socket number by 4.
+         */
+
+        if (s % 4) {
+            ngx_log_error(NGX_LOG_EMERG, ev->log, 0,
+                          "accept() on %s returned socket #%d, "
+                          "not divisible by 4",
+                          ls->listening->addr_text.data, s);
+            exit(1);
+        }
+
+        c = &ngx_cycle->connections[s / 4];
+        rev = &ngx_cycle->read_events[s / 4];
+        wev = &ngx_cycle->write_events[s / 4];
+#else
+        c = &ngx_cycle->connections[s];
+        rev = &ngx_cycle->read_events[s];
+        wev = &ngx_cycle->write_events[s];
+#endif
+
+        instance = rev->instance;
+
+#if (NGX_THREADS)
+
+        if (*(&c->lock)) {
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "spinlock in accept, fd:%", s);
+            ngx_spinlock(&c->lock, 1000);
+            ngx_unlock(&c->lock);
+        }
+
+#endif
+
+        ngx_memzero(rev, sizeof(ngx_event_t));
+        ngx_memzero(wev, sizeof(ngx_event_t));
+        ngx_memzero(c, sizeof(ngx_connection_t));
+
+        c->pool = pool;
+
+        c->listening = ls->listening;
+        c->sockaddr = sa;
+        c->socklen = len;
+
+        rev->instance = !instance;
+        wev->instance = !instance;
+
+        rev->index = NGX_INVALID_INDEX;
+        wev->index = NGX_INVALID_INDEX;
+
+        rev->data = c;
+        wev->data = c;
+
+        c->read = rev;
+        c->write = wev;
+
+        c->fd = s;
+        c->unexpected_eof = 1;
+
+        wev->write = 1;
+        wev->ready = 1;
+
+        if (ngx_event_flags & (NGX_USE_AIO_EVENT|NGX_USE_RTSIG_EVENT)) {
+            /* epoll, rtsig, aio, iocp */
+            rev->ready = 1;
+        }
+
+        if (ev->deferred_accept) {
+            rev->ready = 1;
+        }
+
+        c->ctx = ls->ctx;
+        c->servers = ls->servers;
+
+        c->recv = ngx_recv;
+        c->send_chain = ngx_send_chain;
+
+        c->log = log;
+        rev->log = log;
+        wev->log = log;
+
+        /*
+         * TODO: MT: - atomic increment (x86: lock xadd)
+         *             or protection by critical section or light mutex
+         *
+         * TODO: MP: - allocated in a shared memory
+         *           - atomic increment (x86: lock xadd)
+         *             or protection by critical section or light mutex
+         */
+
+        c->number = ngx_atomic_inc(ngx_connection_counter);
+
+#if (NGX_THREADS)
+        rev->lock = &c->lock;
+        wev->lock = &c->lock;
+        rev->own_lock = &c->lock;
+        wev->own_lock = &c->lock;
+#endif
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                       "accept: fd:%d c:%d", s, c->number);
+
+        if (c->listening->addr_ntop) {
+            c->addr_text.data = ngx_palloc(c->pool,
+                                           c->listening->addr_text_max_len);
+            if (c->addr_text.data == NULL) {
+                ngx_close_accepted_socket(s, log);
+                ngx_destroy_pool(pool);
+                return;
+            }
+    
+            c->addr_text.len = ngx_sock_ntop(c->listening->family, c->sockaddr,
+                                             c->addr_text.data,
+                                             c->listening->addr_text_max_len);
+            if (c->addr_text.len == 0) {
+                ngx_close_accepted_socket(s, log);
+                ngx_destroy_pool(pool);
+                return;
+            }
+        }
+
+#if (NGX_DEBUG)
+        {
+
+        uint32_t            *addr;
+        in_addr_t            i;
+        struct sockaddr_in  *addr_in;
+
+        addr_in = (struct sockaddr_in *) sa;
+        addr = ecf->debug_connection.elts;
+        for (i = 0; i < ecf->debug_connection.nelts; i++) {
+            if (addr[i] == addr_in->sin_addr.s_addr) {
+                log->log_level = NGX_LOG_DEBUG_CONNECTION|NGX_LOG_DEBUG_ALL;
+                break;
+            }
+        }
+
+        }
+#endif
+
+        if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+            if (ngx_add_conn(c) == NGX_ERROR) {
+                ngx_close_accepted_socket(s, log);
+                ngx_destroy_pool(pool);
+                return;
+            }
+        }
+
+        pool = NULL;
+
+        log->data = NULL;
+        log->handler = NULL;
+
+        ls->listening->handler(c);
+
+        if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+            ev->available--;
+        }
+
+        accepted++;
+
+    } while (ev->available);
+}
+
+
+ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
+{
+    if (*ngx_accept_mutex == 0
+        && ngx_atomic_cmp_set(ngx_accept_mutex, 0, ngx_pid))
+    {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "accept mutex locked");
+
+        if (!ngx_accept_mutex_held) {
+            if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
+                *ngx_accept_mutex = 0;
+                return NGX_ERROR;
+            }
+
+            ngx_accept_mutex_held = 1;
+        }
+
+        return NGX_OK;
+    }
+
+    if (ngx_accept_mutex_held) {
+        if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        ngx_accept_mutex_held = 0;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle)
+{
+    ngx_uint_t        i;
+    ngx_listening_t  *s;
+
+    s = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        /*
+         * we do not need to handle the Winsock sockets here (divide a socket
+         * number by 4) because this function would never called
+         * in the Winsock environment
+         */
+
+        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+            if (ngx_add_conn(&cycle->connections[s[i].fd]) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+        } else {
+            if (ngx_add_event(&cycle->read_events[s[i].fd], NGX_READ_EVENT, 0)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_disable_accept_events(ngx_cycle_t *cycle)
+{
+    ngx_uint_t        i;
+    ngx_listening_t  *s;
+
+    s = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+
+        /*
+         * we do not need to handle the Winsock sockets here (divide a socket
+         * number by 4) because this function would never called
+         * in the Winsock environment
+         */
+
+        if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+            if (!cycle->connections[s[i].fd].read->active) {
+                continue;
+            }
+
+            if (ngx_del_conn(&cycle->connections[s[i].fd], NGX_DISABLE_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+
+        } else {
+            if (!cycle->read_events[s[i].fd].active) {
+                continue;
+            }
+
+            if (ngx_del_event(&cycle->read_events[s[i].fd], NGX_READ_EVENT,
+                                               NGX_DISABLE_EVENT) == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static void ngx_close_accepted_socket(ngx_socket_t s, ngx_log_t *log)
+{
+    if (ngx_close_socket(s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+}
+
+
+static size_t ngx_accept_log_error(void *data, char *buf, size_t len)
+{
+    ngx_accept_log_ctx_t  *ctx = data;
+
+    return ngx_snprintf(buf, len, " while accept() on %s", ctx->name);
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_busy_lock.c
@@ -0,0 +1,309 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static int ngx_event_busy_lock_look_cachable(ngx_event_busy_lock_t *bl,
+                                             ngx_event_busy_lock_ctx_t *ctx);
+static void ngx_event_busy_lock_handler(ngx_event_t *ev);
+static void ngx_event_busy_lock_posted_handler(ngx_event_t *ev);
+
+
+/*
+ * NGX_OK:     the busy lock is held
+ * NGX_AGAIN:  the all busy locks are held but we will wait the specified time
+ * NGX_BUSY:   ctx->timer == 0: there are many the busy locks
+ *             ctx->timer != 0: there are many the waiting locks
+ * NGX_ERROR:  an error occured while the mutex locking
+ */
+
+ngx_int_t ngx_event_busy_lock(ngx_event_busy_lock_t *bl,
+                              ngx_event_busy_lock_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    if (ngx_mutex_lock(bl->mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->event->log, 0,
+                   "event busy lock: b:%d mb:%d",
+                   bl->busy, bl->max_busy);
+
+    if (bl->busy < bl->max_busy) {
+        bl->busy++;
+        rc = NGX_OK;
+
+    } else if (ctx->timer && bl->waiting < bl->max_waiting) {
+        bl->waiting++;
+        ngx_add_timer(ctx->event, ctx->timer);
+        ctx->event->event_handler = ngx_event_busy_lock_handler;
+
+        if (bl->events) {
+            bl->last->next = ctx;
+
+        } else {
+            bl->events = ctx;
+        }
+
+        bl->last = ctx;
+
+        rc = NGX_AGAIN;
+
+    } else {
+        rc = NGX_BUSY;
+    }
+
+    ngx_mutex_unlock(bl->mutex);
+
+    return rc;
+}
+
+
+ngx_int_t ngx_event_busy_lock_cachable(ngx_event_busy_lock_t *bl,
+                                       ngx_event_busy_lock_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    if (ngx_mutex_lock(bl->mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_event_busy_lock_look_cachable(bl, ctx);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->event->log, 0,
+                   "event busy lock: %d w:%d mw:%d",
+                   rc, bl->waiting, bl->max_waiting);
+
+    /*
+     * NGX_OK:     no the same request, there is free slot and we locked it
+     * NGX_BUSY:   no the same request and there is no free slot
+     * NGX_AGAIN:  the same request is processing
+     */
+
+    if (rc == NGX_AGAIN) {
+
+        if (ctx->timer && bl->waiting < bl->max_waiting) {
+            bl->waiting++;
+            ngx_add_timer(ctx->event, ctx->timer);
+            ctx->event->event_handler = ngx_event_busy_lock_handler;
+
+            if (bl->events == NULL) {
+                bl->events = ctx;
+            } else {
+                bl->last->next = ctx;
+            }
+            bl->last = ctx;
+
+        } else {
+            rc = NGX_BUSY;
+        }
+    }
+
+    ngx_mutex_unlock(bl->mutex);
+
+    return rc;
+}
+
+
+ngx_int_t ngx_event_busy_unlock(ngx_event_busy_lock_t *bl,
+                                ngx_event_busy_lock_ctx_t *ctx)
+{
+    ngx_event_t                *ev;
+    ngx_event_busy_lock_ctx_t  *wakeup;
+
+    if (ngx_mutex_lock(bl->mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    if (bl->events) {
+        wakeup = bl->events;
+        bl->events = bl->events->next;
+
+    } else {
+        wakeup = NULL;
+        bl->busy--;
+    }
+
+    /*
+     * MP: all ctx's and their queue must be in shared memory,
+     *     each ctx has pid to wake up
+     */
+
+    if (wakeup == NULL) {
+        ngx_mutex_unlock(bl->mutex);
+        return NGX_OK;
+    }
+
+    if (ctx->md5) {
+        for (wakeup = bl->events; wakeup; wakeup = wakeup->next) {
+            if (wakeup->md5 == NULL || wakeup->slot != ctx->slot) {
+                continue;
+            }
+
+            wakeup->handler = ngx_event_busy_lock_posted_handler;
+            wakeup->cache_updated = 1;
+
+            ev = wakeup->event;
+
+            if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            ngx_post_event(ev);
+
+            ngx_mutex_unlock(ngx_posted_events_mutex);
+        }
+
+        ngx_mutex_unlock(bl->mutex);
+
+    } else {
+        bl->waiting--;
+
+        ngx_mutex_unlock(bl->mutex);
+
+        wakeup->handler = ngx_event_busy_lock_posted_handler;
+        wakeup->locked = 1;
+
+        ev = wakeup->event;
+
+        if (ev->timer_set) {
+            ngx_del_timer(ev);
+        }
+
+        if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        ngx_post_event(ev);
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_event_busy_lock_cancel(ngx_event_busy_lock_t *bl,
+                                     ngx_event_busy_lock_ctx_t *ctx)
+{
+    ngx_event_busy_lock_ctx_t  *c, *p;
+
+    if (ngx_mutex_lock(bl->mutex) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    bl->waiting--;
+
+    if (ctx == bl->events) {
+        bl->events = ctx->next;
+
+    } else {
+        p = bl->events;
+        for (c = bl->events->next; c; c = c->next) {
+            if (c == ctx) {
+                p->next = ctx->next;
+                break;
+            }
+            p = c;
+        }
+    }
+
+    ngx_mutex_unlock(bl->mutex);
+
+    return NGX_OK;
+}
+
+
+static int ngx_event_busy_lock_look_cachable(ngx_event_busy_lock_t *bl,
+                                             ngx_event_busy_lock_ctx_t *ctx)
+{
+    ngx_int_t    free;
+    ngx_uint_t   i, bit, cachable, mask;
+
+    bit = 0;
+    cachable = 0;
+    free = -1;
+
+#if (NGX_SUPPRESS_WARN)
+    mask = 0;
+#endif
+
+    for (i = 0; i < bl->max_busy; i++) {
+
+        if ((bit & 7) == 0) {
+            mask = bl->md5_mask[i / 8];
+        }
+
+        if (mask & 1) {
+            if (ngx_memcmp(&bl->md5[i * 16], ctx->md5, 16) == 0) {
+                ctx->waiting = 1;
+                ctx->slot = i;
+                return NGX_AGAIN;
+            }
+            cachable++;
+
+        } else if (free == -1) {
+            free = i;
+        }
+
+        if (cachable == bl->cachable) {
+            if (free == -1 && cachable < bl->max_busy) {
+                free = i + 1;
+            }
+
+            break;
+        }
+
+        mask >>= 1;
+        bit++;
+    }
+
+    if (free == -1) {
+        return NGX_BUSY;
+    }
+
+#if 0
+    if (bl->busy == bl->max_busy) {
+        return NGX_BUSY;
+    }
+#endif
+
+    ngx_memcpy(&bl->md5[free * 16], ctx->md5, 16);
+    bl->md5_mask[free / 8] |= 1 << (free & 7);
+    ctx->slot = free;
+
+    bl->cachable++;
+    bl->busy++;
+
+    return NGX_OK;
+}
+
+
+static void ngx_event_busy_lock_handler(ngx_event_t *ev)
+{
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        return;
+    }
+
+    ngx_post_event(ev);
+
+    ngx_mutex_unlock(ngx_posted_events_mutex);
+
+    ev->event_handler = ngx_event_busy_lock_posted_handler;
+}
+
+
+static void ngx_event_busy_lock_posted_handler(ngx_event_t *ev)
+{
+    ngx_event_busy_lock_ctx_t  *ctx;
+
+    ctx = ev->data;
+    ctx->handler(ev);
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_busy_lock.h
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_BUSY_LOCK_H_INCLUDED_
+#define _NGX_EVENT_BUSY_LOCK_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+typedef struct ngx_event_busy_lock_ctx_s  ngx_event_busy_lock_ctx_t;
+
+struct ngx_event_busy_lock_ctx_s {
+    ngx_event_t                *event;
+    ngx_event_handler_pt        handler;
+    void                       *data;
+    ngx_msec_t                  timer;
+
+    unsigned                    locked:1;
+    unsigned                    waiting:1;
+    unsigned                    cache_updated:1;
+
+    char                       *md5;
+    ngx_int_t                   slot;
+
+    ngx_event_busy_lock_ctx_t  *next;
+};
+
+
+typedef struct {
+    u_char                     *md5_mask;
+    char                       *md5;
+    ngx_uint_t                  cachable;
+
+    ngx_uint_t                  busy;
+    ngx_uint_t                  max_busy;
+
+    ngx_uint_t                  waiting;
+    ngx_uint_t                  max_waiting;
+
+    ngx_event_busy_lock_ctx_t  *events;
+    ngx_event_busy_lock_ctx_t  *last;
+
+#if (NGX_THREADS)
+    ngx_mutex_t                *mutex;
+#endif
+} ngx_event_busy_lock_t;
+
+
+ngx_int_t ngx_event_busy_lock(ngx_event_busy_lock_t *bl,
+                              ngx_event_busy_lock_ctx_t *ctx);
+ngx_int_t ngx_event_busy_lock_cachable(ngx_event_busy_lock_t *bl,
+                                       ngx_event_busy_lock_ctx_t *ctx);
+ngx_int_t ngx_event_busy_unlock(ngx_event_busy_lock_t *bl,
+                                ngx_event_busy_lock_ctx_t *ctx);
+ngx_int_t ngx_event_busy_lock_cancel(ngx_event_busy_lock_t *bl,
+                                     ngx_event_busy_lock_ctx_t *ctx);
+
+
+#endif /* _NGX_EVENT_BUSY_LOCK_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_connect.c
@@ -0,0 +1,379 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <nginx.h>
+
+
+/* AF_INET only */
+
+int ngx_event_connect_peer(ngx_peer_connection_t *pc)
+{
+    int                  rc;
+    ngx_uint_t           instance;
+    u_int                event;
+    time_t               now;
+    ngx_err_t            err;
+    ngx_peer_t          *peer;
+    ngx_socket_t         s;
+    ngx_event_t         *rev, *wev;
+    ngx_connection_t    *c;
+    ngx_event_conf_t    *ecf;
+    struct sockaddr_in   addr;
+
+    now = ngx_time();
+
+    /* ngx_lock_mutex(pc->peers->mutex); */
+
+    if (pc->peers->last_cached) {
+
+        /* cached connection */
+
+        c = pc->peers->cached[pc->peers->last_cached];
+        pc->peers->last_cached--;
+
+        /* ngx_unlock_mutex(pc->peers->mutex); */
+
+#if (NGX_THREADS)
+        c->read->lock = c->read->own_lock;
+        c->write->lock = c->write->own_lock;
+#endif
+
+        pc->connection = c;
+        pc->cached = 1;
+        return NGX_OK;
+    }
+
+    pc->cached = 0;
+    pc->connection = NULL;
+
+    if (pc->peers->number == 1) {
+        peer = &pc->peers->peers[0];
+
+    } else {
+
+        /* there are several peers */
+
+        if (pc->tries == pc->peers->number) {
+
+            /* it's a first try - get a current peer */
+
+            pc->cur_peer = pc->peers->current++;
+
+            if (pc->peers->current >= pc->peers->number) {
+                pc->peers->current = 0;
+            }
+        }
+
+        if (pc->peers->max_fails == 0) {
+            peer = &pc->peers->peers[pc->cur_peer];
+
+        } else {
+
+            /* the peers support a fault tolerance */
+
+            for ( ;; ) {
+                peer = &pc->peers->peers[pc->cur_peer];
+
+                if (peer->fails <= pc->peers->max_fails
+                    || (now - peer->accessed > pc->peers->fail_timeout))
+                {
+                    break;
+                }
+
+                pc->cur_peer++;
+
+                if (pc->cur_peer >= pc->peers->number) {
+                    pc->cur_peer = 0;
+                }
+
+                pc->tries--;
+
+                if (pc->tries == 0) {
+                    /* ngx_unlock_mutex(pc->peers->mutex); */
+
+                    return NGX_ERROR;
+                }
+            }
+        }
+    }
+
+    /* ngx_unlock_mutex(pc->peers->mutex); */
+
+
+    s = ngx_socket(AF_INET, SOCK_STREAM, IPPROTO_IP, 0);
+
+    if (s == -1) {
+        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                      ngx_socket_n " failed");
+        return NGX_ERROR;
+    }
+
+
+    ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module);
+
+    /* disable warning: Win32 SOCKET is u_int while UNIX socket is int */
+
+    if ((ngx_uint_t) s >= ecf->connections) {
+
+        ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+                      "socket() returned socket #%d while only %d "
+                      "connections was configured, closing the socket",
+                      s, ecf->connections);
+
+        if (ngx_close_socket(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          ngx_close_socket_n "failed");
+        }
+
+        /* TODO: sleep for some time */
+
+        return NGX_ERROR;
+    }
+
+
+    if (pc->rcvbuf) {
+        if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+                       (const void *) &pc->rcvbuf, sizeof(int)) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          "setsockopt(SO_RCVBUF) failed");
+
+            if (ngx_close_socket(s) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                              ngx_close_socket_n " failed");
+            }
+
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_nonblocking(s) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                      ngx_nonblocking_n " failed");
+
+        if (ngx_close_socket(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          ngx_close_socket_n " failed");
+        }
+
+        return NGX_ERROR;
+    }
+
+#if (WIN32)
+    /*
+     * Winsock assignes a socket number divisible by 4
+     * so to find a connection we divide a socket number by 4.
+     */
+
+    if (s % 4) {
+        ngx_log_error(NGX_LOG_EMERG, pc->log, 0,
+                      ngx_socket_n
+                      " created socket %d, not divisible by 4", s);
+        exit(1);
+    }
+
+    c = &ngx_cycle->connections[s / 4];
+    rev = &ngx_cycle->read_events[s / 4];
+    wev = &ngx_cycle->write_events[s / 4];
+
+#else
+
+    c = &ngx_cycle->connections[s];
+    rev = &ngx_cycle->read_events[s];
+    wev = &ngx_cycle->write_events[s];
+
+#endif
+
+    instance = rev->instance;
+
+#if (NGX_THREADS)
+
+    if (*(&c->lock)) {
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0,
+                       "spinlock in connect, fd:%d", s);
+        ngx_spinlock(&c->lock, 1000);
+        ngx_unlock(&c->lock);
+    }
+
+#endif
+
+    ngx_memzero(c, sizeof(ngx_connection_t));
+    ngx_memzero(rev, sizeof(ngx_event_t));
+    ngx_memzero(wev, sizeof(ngx_event_t));
+
+    rev->instance = !instance;
+    wev->instance = !instance;
+
+    rev->index = NGX_INVALID_INDEX;
+    wev->index = NGX_INVALID_INDEX;
+
+    rev->data = c;
+    wev->data = c;
+
+    c->read = rev;
+    c->write = wev;
+    wev->write = 1;
+
+    c->log = pc->log;
+    rev->log = pc->log;
+    wev->log = pc->log;
+
+    c->fd = s;
+
+    c->log_error = pc->log_error;
+
+    pc->connection = c;
+
+    /*
+     * TODO: MT: - atomic increment (x86: lock xadd)
+     *             or protection by critical section or mutex
+     *
+     * TODO: MP: - allocated in a shared memory
+     *           - atomic increment (x86: lock xadd)
+     *             or protection by critical section or mutex
+     */
+
+    c->number = ngx_atomic_inc(ngx_connection_counter);
+
+#if (NGX_THREADS)
+    rev->lock = pc->lock;
+    wev->lock = pc->lock;
+    rev->own_lock = &c->lock;
+    wev->own_lock = &c->lock;
+#endif
+
+    if (ngx_add_conn) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    } 
+
+    ngx_memzero(&addr, sizeof(struct sockaddr_in));
+
+    addr.sin_family = AF_INET;
+    addr.sin_port = peer->port;
+    addr.sin_addr.s_addr = peer->addr;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0,
+                   "connect to %s, #%d", peer->addr_port_text.data, c->number);
+
+    rc = connect(s, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
+
+    if (rc == -1) {
+        err = ngx_socket_errno;
+
+        /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */
+
+        if (err != NGX_EINPROGRESS && err != NGX_EAGAIN) {
+            ngx_connection_error(c, err, "connect() failed");
+
+            if (ngx_close_socket(s) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                              ngx_close_socket_n " failed");
+            }
+
+            c->fd = (ngx_socket_t) -1;
+
+            return NGX_CONNECT_ERROR;
+        }
+    }
+
+    if (ngx_add_conn) {
+        if (rc == -1) {
+            /* NGX_EINPROGRESS */
+            return NGX_AGAIN;
+        }
+ 
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+
+        /* aio, iocp */
+
+        if (ngx_blocking(s) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                          ngx_blocking_n " failed");
+
+            if (ngx_close_socket(s) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno,
+                              ngx_close_socket_n " failed");
+            }
+
+            return NGX_ERROR;
+        }
+
+        /*
+         * aio allows to post operation on non-connected socket
+         * at least in FreeBSD.
+         * NT does not support it.
+         * 
+         * TODO: check in Win32, etc. As workaround we can use NGX_ONESHOT_EVENT
+         */
+ 
+        rev->ready = 1;
+        wev->ready = 1;
+
+        return NGX_OK;
+    }
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {     /* kqueue */
+        event = NGX_CLEAR_EVENT;
+
+    } else {                                  /* select, poll, /dev/poll */
+        event = NGX_LEVEL_EVENT;
+    }
+
+    if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (rc == -1) {
+
+        /* NGX_EINPROGRESS */
+
+        if (ngx_add_event(wev, NGX_WRITE_EVENT, event) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pc->log, 0, "connected");
+
+    wev->ready = 1;
+
+    return NGX_OK;
+}
+
+
+void ngx_event_connect_peer_failed(ngx_peer_connection_t *pc)
+{
+    time_t  now;
+
+    now = ngx_time();
+
+    /* ngx_lock_mutex(pc->peers->mutex); */
+
+    pc->peers->peers[pc->cur_peer].fails++;
+    pc->peers->peers[pc->cur_peer].accessed = now;
+
+    /* ngx_unlock_mutex(pc->peers->mutex); */
+
+    pc->cur_peer++;
+
+    if (pc->cur_peer >= pc->peers->number) {
+        pc->cur_peer = 0;
+    }
+
+    pc->tries--;
+
+    return;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_connect.h
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_CONNECT_H_INCLUDED_
+#define _NGX_EVENT_CONNECT_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_CONNECT_ERROR   -10
+
+
+typedef struct {
+    in_addr_t          addr;
+    ngx_str_t          host;
+    in_port_t          port;
+    ngx_str_t          addr_port_text;
+
+    ngx_int_t          fails;
+    time_t             accessed;
+} ngx_peer_t;
+
+
+typedef struct {
+    ngx_int_t           current;
+    ngx_int_t           number;
+    ngx_int_t           max_fails;
+    ngx_int_t           fail_timeout;
+    ngx_int_t           last_cached;
+
+ /* ngx_mutex_t        *mutex; */
+    ngx_connection_t  **cached;
+
+    ngx_peer_t          peers[1];
+} ngx_peers_t;
+
+
+typedef struct {
+    ngx_peers_t       *peers;
+    ngx_int_t          cur_peer;
+    ngx_int_t          tries;
+
+    ngx_connection_t  *connection;
+#if (NGX_THREADS)
+    ngx_atomic_t      *lock;
+#endif
+
+    int                rcvbuf;
+
+    ngx_log_t         *log;
+
+    unsigned           cached:1;
+    unsigned           log_error:2;  /* ngx_connection_log_error_e */
+} ngx_peer_connection_t;
+
+
+int ngx_event_connect_peer(ngx_peer_connection_t *pc);
+void ngx_event_connect_peer_failed(ngx_peer_connection_t *pc);
+
+
+#endif /* _NGX_EVENT_CONNECT_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_mutex.c
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_int_t ngx_event_mutex_timedlock(ngx_event_mutex_t *m, ngx_msec_t timer,
+                                    ngx_event_t *ev)
+{
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "lock event mutex " PTR_FMT " lock:%X", m, m->lock);
+
+    if (m->lock) {
+
+        if (m->events == NULL) {
+            m->events = ev;
+
+        } else {
+            m->last->next = ev;
+        }
+
+        m->last = ev;
+        ev->next = NULL;
+
+#if (NGX_THREADS0)
+        ev->light = 1;
+#endif
+
+        ngx_add_timer(ev, timer);
+
+        return NGX_AGAIN;
+    }
+
+    m->lock = 1;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_event_mutex_unlock(ngx_event_mutex_t *m, ngx_log_t *log)
+{
+    ngx_event_t  *ev;
+
+    if (m->lock == 0) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "tring to unlock the free event mutex " PTR_FMT, m);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                   "unlock event mutex " PTR_FMT ", next event: " PTR_FMT,
+                   m, m->events);
+
+    m->lock = 0;
+
+    if (m->events) {
+        ev = m->events;
+        m->events = ev->next;
+
+        ev->next = (ngx_event_t *) ngx_posted_events;
+        ngx_posted_events = ev;
+    }
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_openssl.c
@@ -0,0 +1,378 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+static ngx_int_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
+
+
+ngx_int_t ngx_ssl_init(ngx_log_t *log)
+{
+    SSL_library_init();
+    SSL_load_error_strings();
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_ssl_create_session(ngx_ssl_ctx_t *ssl_ctx, ngx_connection_t *c,
+                                 ngx_uint_t flags)
+{   
+    ngx_ssl_t  *ssl;
+
+    if (!(ssl = ngx_pcalloc(c->pool, sizeof(ngx_ssl_t)))) {
+        return NGX_ERROR;
+    }
+
+    if (!(ssl->buf = ngx_create_temp_buf(c->pool, NGX_SSL_BUFSIZE))) {
+        return NGX_ERROR;
+    }
+
+    if (flags & NGX_SSL_BUFFER) {
+        ssl->buffer = 1;
+    }
+
+    ssl->ssl = SSL_new(ssl_ctx);
+
+    if (ssl->ssl == NULL) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_new() failed");
+        return NGX_ERROR;
+    }
+
+    if (SSL_set_fd(ssl->ssl, c->fd) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_fd() failed");
+        return NGX_ERROR;
+    }
+
+    SSL_set_accept_state(ssl->ssl);
+
+    c->ssl = ssl;
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    int         n, sslerr;
+    ngx_err_t   err;
+    char       *handshake;
+
+    n = SSL_read(c->ssl->ssl, buf, size);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read: %d", n); 
+
+    if (n > 0) {
+        return n;
+    }
+
+    sslerr = SSL_get_error(c->ssl->ssl, n);
+
+    err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+    if (sslerr == SSL_ERROR_WANT_READ) {
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_WRITE) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, err,
+                      "SSL wants to write%s", handshake);
+        return NGX_ERROR;
+#if 0
+        return NGX_AGAIN;
+#endif
+    }
+
+    if (!SSL_is_init_finished(c->ssl->ssl)) {
+        handshake = "in SSL handshake";
+
+    } else {
+        handshake = "";
+    }
+
+    c->ssl->no_rcv_shut = 1;
+
+    if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, err,
+                      "client closed connection%s", handshake);
+
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_error(NGX_LOG_ALERT, c->log, err,
+                  "SSL_read() failed%s", handshake);
+
+    return NGX_ERROR;
+}
+
+
+/*
+ * OpenSSL has no SSL_writev() so we copy several bufs into our 16K buffer
+ * before SSL_write() call to decrease a SSL overhead.
+ *
+ * Besides for protocols such as HTTP it is possible to always buffer
+ * the output to decrease a SSL overhead some more.
+ */
+
+ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                off_t limit)
+{
+    int          n;
+    ngx_uint_t   flush;
+    ssize_t      send, size;
+    ngx_buf_t   *buf;
+
+    buf = c->ssl->buf;
+
+    if (in && in->next == NULL && !c->buffered && !c->ssl->buffer) {
+
+        /*
+         * we avoid a buffer copy if the incoming buf is a single,
+         * our buffer is empty, and we do not need to buffer the output
+         */
+
+        n = ngx_ssl_write(c, in->buf->pos, in->buf->last - in->buf->pos);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (n < 0) {
+            n = 0;
+        }
+
+        in->buf->pos += n;
+
+        return in;
+    }
+
+    send = 0;
+    flush = (in == NULL) ? 1 : 0;
+
+    for ( ;; ) {
+
+        while (in && buf->last < buf->end) {
+            if (in->buf->last_buf) {
+                flush = 1;
+            }
+
+            if (ngx_buf_special(in->buf)) {
+                in = in->next;
+                continue;
+            }
+
+            size = in->buf->last - in->buf->pos;
+
+            if (size > buf->end - buf->last) {
+                size = buf->end - buf->last;
+            }
+
+            /*
+             * TODO: the taking in->buf->flush into account can be
+             *       implemented using the limit on the higher level
+             */
+
+            if (send + size > limit) {
+                size = limit - send;
+                flush = 1;
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "SSL buf copy: %d", size);
+
+            ngx_memcpy(buf->last, in->buf->pos, size);
+
+            buf->last += size;
+
+            in->buf->pos += size;
+            if (in->buf->pos == in->buf->last) {
+                in = in->next;
+            }
+        }
+
+        size = buf->last - buf->pos;
+
+        if (!flush && buf->last < buf->end && c->ssl->buffer) {
+            break;
+        }
+
+        n = ngx_ssl_write(c, buf->pos, size);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (n < 0) {
+            n = 0;
+        }
+
+        buf->pos += n;
+        send += n;
+        c->sent += n;
+
+        if (n < size) {
+            break;
+        }
+
+        if (buf->pos == buf->last) {
+            buf->pos = buf->start;
+            buf->last = buf->start;
+        }
+
+        if (in == NULL || send == limit) {
+            break;
+        }
+    }
+
+    c->buffered = (buf->pos < buf->last) ? 1 : 0;
+
+    return in;
+}
+
+
+static ngx_int_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
+{
+    int        n, sslerr;
+    ngx_err_t  err;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL to write: %d", size);
+
+    n = SSL_write(c->ssl->ssl, data, size);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write: %d", n);
+
+    if (n > 0) {
+        return n;
+    }
+
+    sslerr = SSL_get_error(c->ssl->ssl, n);
+
+    err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
+
+    if (sslerr == SSL_ERROR_WANT_WRITE) {
+        c->write->ready = 0;
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_READ) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, err,
+                      "SSL wants to read%s", handshake);
+        return NGX_ERROR;
+#if 0
+        return NGX_AGAIN;
+    }
+#endif
+
+    c->ssl->no_rcv_shut = 1;
+
+    ngx_ssl_error(NGX_LOG_ALERT, c->log, err, "SSL_write() failed");
+
+    return NGX_ERROR;
+}
+
+
+ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c)
+{
+    int         n, sslerr;
+    ngx_uint_t  again;
+
+    if (c->timedout) {
+        SSL_set_shutdown(c->ssl->ssl, SSL_RECEIVED_SHUTDOWN|SSL_SENT_SHUTDOWN);
+
+    } else {
+        if (c->ssl->no_rcv_shut) {
+            SSL_set_shutdown(c->ssl->ssl, SSL_RECEIVED_SHUTDOWN);
+        }
+
+        if (c->ssl->no_send_shut) {
+            SSL_set_shutdown(c->ssl->ssl, SSL_SENT_SHUTDOWN);
+        }
+    }
+
+    again = 0;
+
+    for ( ;; ) {
+        n = SSL_shutdown(c->ssl->ssl);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n);
+
+        if (n == 0) {
+            again = 1;
+            break;
+        }
+
+        if (n == 1) {
+            SSL_free(c->ssl->ssl);
+            c->ssl = NULL;
+            return NGX_OK;
+        }
+
+        break;
+    }
+
+    if (!again) {
+        sslerr = SSL_get_error(c->ssl->ssl, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "SSL_get_error: %d", sslerr);
+    }
+
+    if (again || sslerr == SSL_ERROR_WANT_READ) {
+
+        ngx_add_timer(c->read, 10000);
+
+        if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (sslerr == SSL_ERROR_WANT_WRITE) {
+
+        if (ngx_handle_write_event(c->write, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_shutdown() failed");
+
+    return NGX_ERROR;
+}
+
+
+void ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                   char *fmt, ...)
+{   
+    int        len;
+    char       errstr[NGX_MAX_CONF_ERRSTR];
+    va_list    args;
+
+    va_start(args, fmt);
+    len = ngx_vsnprintf(errstr, sizeof(errstr) - 1, fmt, args);
+    va_end(args);
+
+    errstr[len++] = ' ';
+    errstr[len++] = '(';
+    errstr[len++] = 'S';
+    errstr[len++] = 'S';
+    errstr[len++] = 'L';
+    errstr[len++] = ':';
+    errstr[len++] = ' ';
+
+    ERR_error_string_n(ERR_get_error(), errstr + len, sizeof(errstr) - len - 1);
+
+    ngx_log_error(level, log, err, "%s)", errstr);
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_openssl.h
@@ -0,0 +1,57 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_OPENSSL_H_INCLUDED_
+#define _NGX_EVENT_OPENSSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+
+typedef struct {
+    SSL                   *ssl;
+    ngx_buf_t             *buf;
+    ngx_event_handler_pt   saved_handler;
+
+    unsigned               buffer:1;
+    unsigned               no_rcv_shut:1;
+    unsigned               no_send_shut:1;
+} ngx_ssl_t;
+
+
+typedef SSL_CTX  ngx_ssl_ctx_t;
+
+
+#define NGX_SSL_BUFFER       1
+
+
+#define NGX_SSL_BUFSIZE      16384
+
+
+ngx_int_t ngx_ssl_init(ngx_log_t *log);
+ngx_int_t ngx_ssl_create_session(ngx_ssl_ctx_t *ctx, ngx_connection_t *c,
+                                 ngx_uint_t flags);
+
+#define ngx_ssl_handshake(c)     NGX_OK
+
+ngx_int_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_ssl_send_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                off_t limit);
+ngx_int_t ngx_ssl_shutdown(ngx_connection_t *c);
+void ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
+                   char *fmt, ...);
+
+#define ngx_ssl_set_nosendshut(ssl)                                          \
+            if (ssl) {                                                       \
+                ssl->no_send_shut = 1;                                       \
+            }
+
+
+#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_pipe.c
@@ -0,0 +1,771 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_pipe.h>
+
+
+static ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p);
+static ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p);
+
+static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p);
+ngx_inline static void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf);
+ngx_inline static void ngx_event_pipe_free_shadow_raw_buf(ngx_chain_t **free,
+                                                          ngx_buf_t *buf);
+ngx_inline static void ngx_event_pipe_add_free_buf(ngx_chain_t **chain,
+                                                   ngx_chain_t *cl);
+static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p);
+
+
+ngx_int_t ngx_event_pipe(ngx_event_pipe_t *p, int do_write)
+{
+    u_int         flags;
+    ngx_event_t  *rev, *wev;
+
+    for ( ;; ) {
+        if (do_write) {
+            if (ngx_event_pipe_write_to_downstream(p) == NGX_ABORT) {
+                return NGX_ABORT;
+            }
+        }
+
+        p->read = 0;
+        p->upstream_blocked = 0;
+
+        if (ngx_event_pipe_read_upstream(p) == NGX_ABORT) {
+            return NGX_ABORT;
+        }
+
+        if (!p->read && !p->upstream_blocked) {
+            break;
+        }
+
+        do_write = 1;
+    }
+
+    if (p->upstream->fd != -1) {
+        rev = p->upstream->read;
+
+        flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0;
+
+        if (ngx_handle_read_event(rev, flags) == NGX_ERROR) {
+            return NGX_ABORT;
+        }
+
+        if (rev->active) {
+            ngx_add_timer(rev, p->read_timeout);
+        }
+    }
+
+    if (p->downstream->fd != -1) {
+        wev = p->downstream->write;
+        wev->available = p->send_lowat;
+        if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+            return NGX_ABORT;
+        }
+
+        if (wev->active) {
+            ngx_add_timer(wev, p->send_timeout);
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
+{
+    int           n, rc, size;
+    ngx_buf_t    *b;
+    ngx_chain_t  *chain, *cl, *tl;
+
+    if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe read upstream: %d", p->upstream->read->ready);
+
+    for ( ;; ) {
+
+        if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+            break;
+        }
+
+        if (p->preread_bufs == NULL && !p->upstream->read->ready) {
+            break;
+        }
+
+        if (p->preread_bufs) {
+
+            /* use the pre-read bufs if they exist */
+
+            chain = p->preread_bufs;
+            p->preread_bufs = NULL;
+            n = p->preread_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe preread: %d", n);
+
+            if (n) {
+                p->read = 1;
+            }
+
+        } else {
+
+            /*
+             * kqueue notifies about the end of file or a pending error.
+             * This test allows not to allocate a buf on these conditions
+             * and not to call ngx_recv_chain().
+             */
+
+            if (p->upstream->read->available == 0
+                && p->upstream->read->pending_eof)
+            {
+                p->upstream->read->ready = 0;
+                p->upstream->read->eof = 0;
+                p->upstream_eof = 1;
+                p->read = 1;
+
+#if (HAVE_KQUEUE)
+                if (p->upstream->read->kq_errno) {
+                    p->upstream->read->error = 1;
+                    p->upstream_error = 1;
+                    p->upstream_eof = 0;
+
+                    ngx_log_error(NGX_LOG_ERR, p->log,
+                                  p->upstream->read->kq_errno,
+                                  "readv() failed");
+                }
+#endif
+
+                break;
+            }
+
+            if (p->free_raw_bufs) {
+
+                /* use the free bufs if they exist */
+
+                chain = p->free_raw_bufs;
+                if (p->single_buf) {
+                    p->free_raw_bufs = p->free_raw_bufs->next;
+                    chain->next = NULL;
+                } else {
+                    p->free_raw_bufs = NULL;
+                }
+
+            } else if (p->allocated < p->bufs.num) {
+
+                /* allocate a new buf if it's still allowed */
+
+                if (!(b = ngx_create_temp_buf(p->pool, p->bufs.size))) {
+                    return NGX_ABORT;
+                }
+
+                p->allocated++;
+
+                ngx_alloc_link_and_set_buf(tl, b, p->pool, NGX_ABORT);
+                chain = tl;
+
+            } else if (!p->cachable && p->downstream->write->ready) {
+
+                /*
+                 * if the bufs are not needed to be saved in a cache and
+                 * a downstream is ready then write the bufs to a downstream
+                 */
+
+                p->upstream_blocked = 1;
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe downstream ready");
+
+                break;
+
+            } else if (p->cachable
+                       || p->temp_file->offset < p->max_temp_file_size)
+            {
+
+                /*
+                 * if it's allowed then save some bufs from r->in
+                 * to a temporary file, and add them to a r->out chain
+                 */
+
+                rc = ngx_event_pipe_write_chain_to_temp_file(p);
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe temp offset: %d", p->temp_file->offset);
+
+                if (rc == NGX_AGAIN) {
+                    if (ngx_event_flags & NGX_USE_LEVEL_EVENT
+                        && p->upstream->read->active
+                        && p->upstream->read->ready)
+                    {
+                        if (ngx_del_event(p->upstream->read, NGX_READ_EVENT, 0)
+                                                                  == NGX_ERROR)
+                        {
+                            return NGX_ABORT;
+                        }
+                    }
+                }
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                chain = p->free_raw_bufs;
+                if (p->single_buf) {
+                    p->free_raw_bufs = p->free_raw_bufs->next;
+                    chain->next = NULL;
+                } else {
+                    p->free_raw_bufs = NULL;
+                }
+
+            } else {
+
+                /* if there're no bufs to read in then disable a level event */
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "no pipe bufs to read in");
+    
+                break;
+            }
+
+            n = ngx_recv_chain(p->upstream, chain);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe recv chain: %d", n);
+
+            if (p->free_raw_bufs) {
+                chain->next = p->free_raw_bufs;
+            }
+            p->free_raw_bufs = chain;
+
+            if (n == NGX_ERROR) {
+                p->upstream_error = 1;
+                return NGX_ERROR;
+            }
+
+            if (n == NGX_AGAIN) {
+                if (p->single_buf) {
+                    ngx_event_pipe_remove_shadow_links(chain->buf);
+                }
+
+                break;
+            }
+
+            p->read = 1;
+
+            if (n == 0) {
+                p->upstream_eof = 1;
+                break;
+            }
+        }
+
+        p->read_length += n;
+        cl = chain;
+
+        while (cl && n > 0) {
+
+            ngx_event_pipe_remove_shadow_links(cl->buf);
+
+            size = cl->buf->end - cl->buf->last;
+
+            if (n >= size) {
+                cl->buf->last = cl->buf->end;
+
+                /* STUB */ cl->buf->num = p->num++;
+
+                if (p->input_filter(p, cl->buf) == NGX_ERROR) {
+                    return NGX_ABORT;
+                }
+
+                n -= size;
+                cl = cl->next;
+
+            } else {
+                cl->buf->last += n;
+                n = 0;
+            }
+        }
+
+        p->free_raw_bufs = cl;
+    }
+
+#if (NGX_DEBUG)
+
+    if (p->in || p->busy || p->free_raw_bufs) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0, "pipe buf");
+    }
+
+    for (cl = p->in; cl; cl = cl->next) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf in " PTR_FMT ", pos " PTR_FMT ", size: %d",
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos);
+    }
+
+    for (cl = p->busy; cl; cl = cl->next) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf busy " PTR_FMT ", pos " PTR_FMT ", size: %d",
+                       cl->buf->start, cl->buf->pos,
+                       cl->buf->last - cl->buf->pos);
+    }
+
+    for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe buf free " PTR_FMT ", last " PTR_FMT ", size: %d",
+                       cl->buf->start, cl->buf->last,
+                       cl->buf->end - cl->buf->last);
+    }
+
+#endif
+
+    if ((p->upstream_eof || p->upstream_error) && p->free_raw_bufs) {
+
+        /* STUB */ p->free_raw_bufs->buf->num = p->num++;
+
+        if (p->input_filter(p, p->free_raw_bufs->buf) == NGX_ERROR) {
+            return NGX_ABORT;
+        }
+
+        p->free_raw_bufs = p->free_raw_bufs->next;
+
+        if (p->free_bufs) {
+            for (cl = p->free_raw_bufs; cl; cl = cl->next) {
+                ngx_pfree(p->pool, cl->buf->start); 
+            }
+        }
+    }
+
+    if (p->cachable && p->in) {
+        if (ngx_event_pipe_write_chain_to_temp_file(p) == NGX_ABORT) {
+            return NGX_ABORT;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_event_pipe_write_to_downstream(ngx_event_pipe_t *p)
+{
+    size_t        bsize;
+    ngx_uint_t    flush;
+    ngx_buf_t    *b;
+    ngx_chain_t  *out, **ll, *cl, *tl;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe write downstream: %d", p->downstream->write->ready);
+
+    for ( ;; ) {
+        if (p->downstream_error) {
+            return ngx_event_pipe_drain_chains(p);
+        }
+
+        if (p->upstream_eof || p->upstream_error || p->upstream_done) {
+
+            /* pass the p->out and p->in chains to the output filter */
+
+            if (p->out) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe write downstream flush out");
+
+                if (p->output_filter(p->output_ctx, p->out) == NGX_ERROR) {
+                    p->downstream_error = 1;
+                    return ngx_event_pipe_drain_chains(p);
+                }
+
+                p->out = NULL;
+            }
+
+            if (p->in) {
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                               "pipe write downstream flush in");
+
+                if (p->output_filter(p->output_ctx, p->in) == NGX_ERROR) {
+                    p->downstream_error = 1;
+                    return ngx_event_pipe_drain_chains(p);
+                }
+
+                p->in = NULL;
+            }
+
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe write downstream done");
+
+            /* TODO: free unused bufs */
+
+            p->downstream_done = 1;
+            break;
+        }
+
+        if (!p->downstream->write->ready) {
+            break;
+        }
+
+        /* bsize is the size of the busy bufs */
+
+        bsize = 0;
+
+        for (cl = p->busy; cl; cl = cl->next) {
+            bsize += cl->buf->end - cl->buf->start;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe write busy: " SIZE_T_FMT, bsize);
+
+        out = NULL;
+        ll = NULL;
+        flush = 0;
+
+        for ( ;; ) {
+            if (p->out) {
+                cl = p->out;
+
+                if (bsize + ngx_buf_size(cl->buf) > p->busy_size) {
+                    flush = 1;
+                    break;
+                }
+
+                p->out = p->out->next;
+                ngx_event_pipe_free_shadow_raw_buf(&p->free_raw_bufs,
+                                                   cl->buf);
+
+            } else if (!p->cachable && p->in) {
+                cl = p->in;
+
+                if (bsize + ngx_buf_size(cl->buf) > p->busy_size) {
+                    flush = 1;
+                    break;
+                }
+
+                p->in = p->in->next;
+
+            } else {
+                break;
+            }
+
+            bsize += ngx_buf_size(cl->buf);
+            cl->next = NULL;
+            ngx_chain_add_link(out, ll, cl);
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe write: out:" PTR_FMT ", f:%d", out, flush);
+
+        if (out == NULL && !flush) {
+            break;
+        }
+
+        if (p->output_filter(p->output_ctx, out) == NGX_ERROR) {
+            p->downstream_error = 1;
+            return ngx_event_pipe_drain_chains(p);
+        }
+
+        ngx_chain_update_chains(&p->free, &p->busy, &out, p->tag);
+
+        for (cl = p->free; cl; cl = cl->next) {
+
+            if (cl->buf->temp_file) {
+                if (p->cachable || !p->cyclic_temp_file) {
+                    continue;
+                }
+
+                /* reset p->temp_offset if all bufs had been sent */
+
+                if (cl->buf->file_last == p->temp_file->offset) {
+                    p->temp_file->offset = 0;
+                }
+            }
+
+            /* TODO: free buf if p->free_bufs && upstream done */
+
+            /* add the free shadow raw buf to p->free_raw_bufs */
+
+            if (cl->buf->last_shadow) {
+                b = cl->buf->shadow;
+                b->pos = b->last = b->start;
+                b->shadow = NULL;
+                ngx_alloc_link_and_set_buf(tl, b, p->pool, NGX_ABORT);
+                ngx_event_pipe_add_free_buf(&p->free_raw_bufs, tl);
+
+                cl->buf->last_shadow = 0;
+            }
+
+            cl->buf->shadow = NULL;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p)
+{
+    ssize_t       size, bsize;
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl, *tl, *next, *out, **ll, **last_free, fl;
+
+    if (p->buf_to_file) {
+        fl.buf = p->buf_to_file;
+        fl.next = p->in;
+        out = &fl;
+
+    } else {
+        out = p->in;
+    }
+
+    if (!p->cachable) {
+
+        size = 0;
+        cl = out;
+        ll = NULL;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                       "pipe offset: %d", p->temp_file->offset);
+
+        do {
+            bsize = cl->buf->last - cl->buf->pos;
+
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                           "pipe buf " PTR_FMT ", pos " PTR_FMT ", size: %d",
+                           cl->buf->start, cl->buf->pos, bsize);
+
+            if ((size + bsize > p->temp_file_write_size)
+               || (p->temp_file->offset + size + bsize > p->max_temp_file_size))
+            {
+                break;
+            }
+
+            size += bsize;
+            ll = &cl->next;
+            cl = cl->next;
+
+        } while (cl);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "size: %d", size);
+
+        if (cl) {
+           p->in = cl;
+           *ll = NULL;
+
+        } else {
+           p->in = NULL;
+           p->last_in = &p->in;
+        }
+
+    } else {
+        p->in = NULL;
+        p->last_in = &p->in;
+    }
+
+    if (ngx_write_chain_to_temp_file(p->temp_file, out) == NGX_ERROR) {
+        return NGX_ABORT;
+    }
+
+    for (last_free = &p->free_raw_bufs;
+         *last_free != NULL;
+         last_free = &(*last_free)->next)
+    {
+        /* void */
+    }
+
+    if (p->buf_to_file) {
+        p->temp_file->offset = p->buf_to_file->last - p->buf_to_file->pos;
+        p->buf_to_file = NULL;
+        out = out->next;
+    }
+
+    for (cl = out; cl; cl = next) {
+        next = cl->next;
+        cl->next = NULL;
+
+        b = cl->buf;
+        b->file = &p->temp_file->file;
+        b->file_pos = p->temp_file->offset;
+        p->temp_file->offset += b->last - b->pos;
+        b->file_last = p->temp_file->offset;
+
+        b->in_file = 1;
+        b->temp_file = 1;
+
+        ngx_chain_add_link(p->out, p->last_out, cl);
+
+        if (b->last_shadow) {
+            b->shadow->last = b->shadow->pos = b->shadow->start;
+            ngx_alloc_link_and_set_buf(tl, b->shadow, p->pool, NGX_ABORT);
+            *last_free = tl;
+            last_free = &tl->next;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+/* the copy input filter */
+
+ngx_int_t ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl;
+
+    if (buf->pos == buf->last) {
+        return NGX_OK;
+    }
+
+    if (p->free) {
+        b = p->free->buf;
+        p->free = p->free->next;
+
+    } else {
+        if (!(b = ngx_alloc_buf(p->pool))) {
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_memcpy(b, buf, sizeof(ngx_buf_t));
+    b->shadow = buf;
+    b->tag = p->tag;
+    b->last_shadow = 1;
+    b->recycled = 1;
+    buf->shadow = b;
+
+    ngx_alloc_link_and_set_buf(cl, b, p->pool, NGX_ERROR);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "buf #%d", b->num);
+
+    ngx_chain_add_link(p->in, p->last_in, cl);
+
+    return NGX_OK;
+}
+
+
+ngx_inline static void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf)
+{
+    ngx_buf_t  *b, *next;
+
+    if (buf->shadow == NULL) {
+        return;
+    }
+
+    b = buf->shadow;
+
+    while (!b->last_shadow) {
+        next = b->shadow;
+
+        b->in_file = 0;
+        b->temp_file = 0;
+        b->flush = 0;
+        b->zerocopy_busy = 0;
+
+        b->shadow = NULL;
+        b = next;
+    }
+
+    b->in_file = 0;
+    b->temp_file = 0;
+    b->flush = 0;
+    b->zerocopy_busy = 0;
+    b->last_shadow = 0;
+
+    b->shadow = NULL;
+
+    buf->shadow = NULL;
+}
+
+
+ngx_inline static void ngx_event_pipe_free_shadow_raw_buf(ngx_chain_t **free,
+                                                          ngx_buf_t *buf)
+{
+    ngx_buf_t    *s;
+    ngx_chain_t  *cl, **ll;
+
+    if (buf->shadow == NULL) {
+        return;
+    }
+
+    for (s = buf->shadow; !s->last_shadow; s = s->shadow) { /* void */ }
+
+    ll = free;
+
+    for (cl = *free ; cl; cl = cl->next) {
+        if (cl->buf == s) {
+            *ll = cl->next;
+            break;
+        }
+
+        if (cl->buf->shadow) {
+            break;
+        }
+
+        ll = &cl->next;
+    }
+}
+
+
+ngx_inline static void ngx_event_pipe_add_free_buf(ngx_chain_t **chain,
+                                                   ngx_chain_t *cl)
+{
+    if (*chain == NULL) {
+        *chain = cl;
+        return;
+    }
+
+    if ((*chain)->buf->pos != (*chain)->buf->last) {
+        cl->next = (*chain)->next;
+        (*chain)->next = cl;
+
+    } else {
+        cl->next = (*chain);
+        (*chain) = cl;
+    }
+}
+
+
+static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl, *tl;
+
+    for ( ;; ) {
+        if (p->busy) {
+            cl = p->busy;
+            p->busy = NULL;
+
+        } else if (p->out) {
+            cl = p->out;
+            p->out = NULL;
+
+        } else if (p->in) {
+            cl = p->in;
+            p->in = NULL;
+
+        } else {
+            return NGX_OK;
+        }
+
+        while (cl) {
+            if (cl->buf->last_shadow) {
+                b = cl->buf->shadow;
+                b->pos = b->last = b->start;
+                b->shadow = NULL;
+                ngx_alloc_link_and_set_buf(tl, b, p->pool, NGX_ABORT);
+                ngx_event_pipe_add_free_buf(&p->free_raw_bufs, tl);
+
+                cl->buf->last_shadow = 0;
+            }
+
+            cl->buf->shadow = NULL;
+            tl = cl->next;
+            cl->next = p->free;
+            p->free = cl;
+            cl = tl;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_pipe.h
@@ -0,0 +1,93 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_PIPE_H_INCLUDED_
+#define _NGX_EVENT_PIPE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct ngx_event_pipe_s  ngx_event_pipe_t;
+
+typedef ngx_int_t (*ngx_event_pipe_input_filter_pt)(ngx_event_pipe_t *p,
+                                                    ngx_buf_t *buf);
+typedef ngx_int_t (*ngx_event_pipe_output_filter_pt)(void *data,
+                                                     ngx_chain_t *chain);
+
+
+struct ngx_event_pipe_s {
+    ngx_connection_t  *upstream;
+    ngx_connection_t  *downstream;
+
+    ngx_chain_t       *free_raw_bufs;
+    ngx_chain_t       *in;
+    ngx_chain_t      **last_in;
+
+    ngx_chain_t       *out;
+    ngx_chain_t      **last_out;
+
+    ngx_chain_t       *free;
+    ngx_chain_t       *busy;
+
+    /*
+     * the input filter i.e. that moves HTTP/1.1 chunks
+     * from the raw bufs to an incoming chain
+     */
+
+    ngx_event_pipe_input_filter_pt    input_filter;
+    void                             *input_ctx;
+
+    ngx_event_pipe_output_filter_pt   output_filter;
+    void                             *output_ctx;
+
+    unsigned           read:1;
+    unsigned           cachable:1;
+    unsigned           single_buf:1;
+    unsigned           free_bufs:1;
+    unsigned           upstream_done:1;
+    unsigned           upstream_error:1;
+    unsigned           upstream_eof:1;
+    unsigned           upstream_blocked:1;
+    unsigned           downstream_done:1;
+    unsigned           downstream_error:1;
+    unsigned           cyclic_temp_file:1;
+
+    ngx_int_t          allocated;
+    ngx_bufs_t         bufs;
+    ngx_buf_tag_t      tag;
+
+    size_t             busy_size;
+
+    off_t              read_length;
+
+    off_t              max_temp_file_size;
+    ssize_t            temp_file_write_size;
+
+    ngx_msec_t         read_timeout;
+    ngx_msec_t         send_timeout;
+    ssize_t            send_lowat;
+
+    ngx_pool_t        *pool;
+    ngx_log_t         *log;
+
+    ngx_chain_t       *preread_bufs;
+    size_t             preread_size;
+    ngx_buf_t         *buf_to_file;
+
+    ngx_temp_file_t   *temp_file;
+
+    /* STUB */ int     num;
+};
+
+
+ngx_int_t ngx_event_pipe(ngx_event_pipe_t *p, int do_write);
+ngx_int_t ngx_event_pipe_copy_input_filter(ngx_event_pipe_t *p, ngx_buf_t *buf);
+
+
+#endif /* _NGX_EVENT_PIPE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_posted.c
@@ -0,0 +1,169 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ngx_thread_volatile ngx_event_t  *ngx_posted_events;
+
+#if (NGX_THREADS)
+ngx_mutex_t                      *ngx_posted_events_mutex;
+#endif
+
+
+void ngx_event_process_posted(ngx_cycle_t *cycle)
+{
+    ngx_event_t  *ev;
+
+    for ( ;; ) {
+
+        ev = (ngx_event_t *) ngx_posted_events;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                      "posted event " PTR_FMT, ev);
+
+        if (ev == NULL) {
+            return;
+        }
+
+        ngx_delete_posted_event(ev);
+
+        ev->event_handler(ev);
+    }
+}
+
+
+#if (NGX_THREADS)
+
+void ngx_wakeup_worker_thread(ngx_cycle_t *cycle)
+{
+    ngx_int_t     i;
+    ngx_uint_t    busy;
+    ngx_event_t  *ev;
+
+#if 0
+    busy = 1;
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        return;
+    }
+
+    for (ev = (ngx_event_t *) ngx_posted_events; ev; ev = ev->next) {
+        if (*(ev->lock) == 0) {
+            busy = 0;
+            break;
+        }
+    }
+
+    ngx_mutex_unlock(ngx_posted_events_mutex);
+
+    if (busy) {
+        return;
+    }
+#endif
+
+    for (i = 0; i < ngx_threads_n; i++) {
+        if (ngx_threads[i].state == NGX_THREAD_FREE) {
+            ngx_cond_signal(ngx_threads[i].cv);
+            return;
+        }
+    }
+}
+
+
+ngx_int_t ngx_event_thread_process_posted(ngx_cycle_t *cycle)
+{
+    ngx_event_t  *ev;
+
+    for ( ;; ) {
+
+        ev = (ngx_event_t *) ngx_posted_events;
+
+        for ( ;; ) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                          "posted event " PTR_FMT, ev);
+
+            if (ev == NULL) {
+                return NGX_OK;
+            }
+
+            if (ngx_trylock(ev->lock) == 0) {
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                               "posted event " PTR_FMT " is busy", ev);
+
+                ev = ev->next;
+                continue;
+            }
+
+            if (ev->lock != ev->own_lock) {
+                if (*(ev->own_lock)) {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                                  "the own lock of the posted event "
+                                  PTR_FMT " is busy", ev);
+                    ngx_unlock(ev->lock);
+                    ev = ev->next;
+                    continue;
+                }
+                *(ev->own_lock) = 1;
+            }
+
+            ngx_delete_posted_event(ev);
+
+            ev->locked = 1;
+
+            ev->ready |= ev->posted_ready;
+            ev->timedout |= ev->posted_timedout;
+            ev->pending_eof |= ev->posted_eof;
+#if (HAVE_KQUEUE)
+            ev->kq_errno |= ev->posted_errno;
+#endif
+            if (ev->posted_available) {
+                ev->available = ev->posted_available;
+            }
+
+            ev->posted_ready = 0;
+            ev->posted_timedout = 0;
+            ev->posted_eof = 0;
+#if (HAVE_KQUEUE)
+            ev->posted_errno = 0;
+#endif
+            ev->posted_available = 0;
+
+            ngx_mutex_unlock(ngx_posted_events_mutex);
+
+            ev->event_handler(ev);
+
+            if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                return NGX_ERROR;
+            }
+
+            if (ev->locked) {
+                ngx_unlock(ev->lock);
+
+                if (ev->lock != ev->own_lock) {
+                    ngx_unlock(ev->own_lock);
+                }
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "posted event " PTR_FMT " is done", ev);
+
+            break;
+        }
+    }
+}
+
+#else
+
+void ngx_wakeup_worker_thread(ngx_cycle_t *cycle)
+{
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_posted.h
@@ -0,0 +1,55 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_POSTED_H_INCLUDED_
+#define _NGX_EVENT_POSTED_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define ngx_post_event(ev)                                                    \
+            if (ev->prev == NULL) {                                           \
+                ev->next = (ngx_event_t *) ngx_posted_events;                 \
+                ev->prev = (ngx_event_t **) &ngx_posted_events;               \
+                ngx_posted_events = ev;                                       \
+                if (ev->next) {                                               \
+                    ev->next->prev = &ev->next;                               \
+                }                                                             \
+                ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,                \
+                               "post event " PTR_FMT, ev);                    \
+            } else  {                                                         \
+                ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,                \
+                               "update posted event " PTR_FMT, ev);           \
+            }
+
+#define ngx_delete_posted_event(ev)                                           \
+        *(ev->prev) = ev->next;                                               \
+        if (ev->next) {                                                       \
+            ev->next->prev = ev->prev;                                        \
+        }                                                                     \
+        ev->prev = NULL;                                                      \
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,                        \
+                       "delete posted event " PTR_FMT, ev);
+
+
+
+void ngx_event_process_posted(ngx_cycle_t *cycle);
+void ngx_wakeup_worker_thread(ngx_cycle_t *cycle);
+
+extern ngx_thread_volatile ngx_event_t  *ngx_posted_events;
+
+
+#if (NGX_THREADS)
+ngx_int_t ngx_event_thread_process_posted(ngx_cycle_t *cycle);
+
+extern ngx_mutex_t                      *ngx_posted_events_mutex;
+#endif
+
+
+#endif /* _NGX_EVENT_POSTED_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_timer.c
@@ -0,0 +1,165 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (NGX_THREADS)
+ngx_mutex_t  *ngx_event_timer_mutex;
+#endif
+
+
+ngx_thread_volatile ngx_rbtree_t  *ngx_event_timer_rbtree;
+ngx_rbtree_t                       ngx_event_timer_sentinel;
+
+
+ngx_int_t ngx_event_timer_init(ngx_log_t *log)
+{
+    if (ngx_event_timer_rbtree) {
+#if (NGX_THREADS)
+        ngx_event_timer_mutex->log = log;
+#endif
+        return NGX_OK;
+    }
+
+    ngx_event_timer_rbtree = &ngx_event_timer_sentinel;
+
+#if (NGX_THREADS)
+    if (!(ngx_event_timer_mutex = ngx_mutex_init(log, 0))) {
+        return NGX_ERROR;
+    }
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_msec_t ngx_event_find_timer(void)
+{
+    ngx_msec_t     timer;
+    ngx_rbtree_t  *node;
+
+    if (ngx_event_timer_rbtree == &ngx_event_timer_sentinel) {
+        return NGX_TIMER_INFINITE;
+    }
+
+    if (ngx_mutex_lock(ngx_event_timer_mutex) == NGX_ERROR) {
+        return NGX_TIMER_ERROR;
+    }
+
+    node = ngx_rbtree_min((ngx_rbtree_t *) ngx_event_timer_rbtree,
+                          &ngx_event_timer_sentinel);
+
+    ngx_mutex_unlock(ngx_event_timer_mutex);
+
+    timer = (ngx_msec_t)
+         (node->key * NGX_TIMER_RESOLUTION -
+               ngx_elapsed_msec / NGX_TIMER_RESOLUTION * NGX_TIMER_RESOLUTION);
+#if 0
+                         (node->key * NGX_TIMER_RESOLUTION - ngx_elapsed_msec);
+#endif
+
+    return timer > 0 ? timer: 0 ;
+}
+
+
+void ngx_event_expire_timers(ngx_msec_t timer)
+{
+    ngx_event_t   *ev;
+    ngx_rbtree_t  *node;
+
+    if (timer < 0) {
+        /* avoid the endless loop if the time goes backward for some reason */
+        timer = 0;
+    }
+
+    for ( ;; ) {
+
+        if (ngx_event_timer_rbtree == &ngx_event_timer_sentinel) {
+            return;
+        }
+
+        if (ngx_mutex_lock(ngx_event_timer_mutex) == NGX_ERROR) {
+            return;
+        }
+
+        node = ngx_rbtree_min((ngx_rbtree_t *) ngx_event_timer_rbtree,
+                              &ngx_event_timer_sentinel);
+
+        if (node->key <= (ngx_msec_t)
+                         (ngx_old_elapsed_msec + timer) / NGX_TIMER_RESOLUTION)
+        {
+            ev = (ngx_event_t *)
+                           ((char *) node - offsetof(ngx_event_t, rbtree_key));
+
+#if (NGX_THREADS)
+
+            if (ngx_threaded && ngx_trylock(ev->lock) == 0) {
+
+                /*
+                 * We can not change the timer of the event that is been
+                 * handling by another thread.  And we can not easy walk
+                 * the rbtree to find a next expired timer so we exit the loop.
+                 * However it should be rare case when the event that is
+                 * been handling has expired timer.
+                 */
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                               "event " PTR_FMT " is busy in expire timers",
+                               ev);
+                break;
+            }
+#endif
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "event timer del: %d: %d",
+                            ngx_event_ident(ev->data), ev->rbtree_key);
+
+            ngx_rbtree_delete((ngx_rbtree_t **) &ngx_event_timer_rbtree,
+                              &ngx_event_timer_sentinel,
+                              (ngx_rbtree_t *) &ev->rbtree_key);
+
+            ngx_mutex_unlock(ngx_event_timer_mutex);
+
+#if (NGX_DEBUG)
+            ev->rbtree_left = NULL;
+            ev->rbtree_right = NULL;
+            ev->rbtree_parent = NULL;
+#endif
+
+            ev->timer_set = 0;
+
+#if (NGX_THREADS)
+            if (ngx_threaded) {
+                if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                    return;
+                }
+
+                ev->posted_timedout = 1;
+                ngx_post_event(ev);
+
+                ngx_mutex_unlock(ngx_posted_events_mutex);
+
+                ngx_unlock(ev->lock);
+
+                continue;
+            }
+#endif
+
+            ev->timedout = 1;
+
+            ev->event_handler(ev);
+
+            continue;
+        }
+
+        break;
+    }
+
+    ngx_mutex_unlock(ngx_event_timer_mutex);
+}
new file mode 100644
--- /dev/null
+++ b/src/event/ngx_event_timer.h
@@ -0,0 +1,120 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_EVENT_TIMER_H_INCLUDED_
+#define _NGX_EVENT_TIMER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_TIMER_INFINITE  -1
+#define NGX_TIMER_ERROR     -2
+
+/*
+ * 32 bit timer key value resolution
+ *
+ * 1 msec - 24 days
+ * 10 msec - 8 months
+ * 50 msec - 3 years 5 months
+ * 100 msec - 6 years 10 months
+ */
+
+#define NGX_TIMER_RESOLUTION  1
+
+
+ngx_int_t ngx_event_timer_init(ngx_log_t *log);
+ngx_msec_t ngx_event_find_timer(void);
+void ngx_event_expire_timers(ngx_msec_t timer);
+
+
+#if (NGX_THREADS)
+extern ngx_mutex_t  *ngx_event_timer_mutex;
+#endif
+
+
+extern ngx_thread_volatile ngx_rbtree_t  *ngx_event_timer_rbtree;
+extern ngx_rbtree_t                       ngx_event_timer_sentinel;
+
+
+ngx_inline static void ngx_event_del_timer(ngx_event_t *ev)
+{
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "event timer del: %d: %d",
+                    ngx_event_ident(ev->data), ev->rbtree_key);
+
+    if (ngx_mutex_lock(ngx_event_timer_mutex) == NGX_ERROR) {
+        return;
+    }
+
+    ngx_rbtree_delete((ngx_rbtree_t **) &ngx_event_timer_rbtree,
+                      &ngx_event_timer_sentinel,
+                      (ngx_rbtree_t *) &ev->rbtree_key);
+
+    ngx_mutex_unlock(ngx_event_timer_mutex);
+
+#if (NGX_DEBUG)
+    ev->rbtree_left = NULL;
+    ev->rbtree_right = NULL;
+    ev->rbtree_parent = NULL;
+#endif
+
+    ev->timer_set = 0;
+}
+
+
+ngx_inline static void ngx_event_add_timer(ngx_event_t *ev, ngx_msec_t timer)
+{
+    ngx_int_t  key;
+
+    key = (ngx_int_t)
+              (ngx_elapsed_msec / NGX_TIMER_RESOLUTION * NGX_TIMER_RESOLUTION
+                                              + timer) / NGX_TIMER_RESOLUTION;
+#if 0
+                             (ngx_elapsed_msec + timer) / NGX_TIMER_RESOLUTION;
+#endif
+
+    if (ev->timer_set) {
+
+        /*
+         * Use the previous timer value if a difference between them is less
+         * then 100 milliseconds.  It allows to minimize the rbtree operations
+         * for the fast connections.
+         */
+
+        if (abs(key - ev->rbtree_key) < 100 / NGX_TIMER_RESOLUTION) {
+            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                           "event timer: %d, old: %d, new: %d",
+                            ngx_event_ident(ev->data), ev->rbtree_key, key);
+            return;
+        }
+
+        ngx_del_timer(ev);
+    }
+
+    ev->rbtree_key = key;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "event timer add: %d: %d",
+                    ngx_event_ident(ev->data), ev->rbtree_key);
+
+    if (ngx_mutex_lock(ngx_event_timer_mutex) == NGX_ERROR) {
+        return;
+    }
+
+    ngx_rbtree_insert((ngx_rbtree_t **) &ngx_event_timer_rbtree,
+                      &ngx_event_timer_sentinel,
+                      (ngx_rbtree_t *) &ev->rbtree_key);
+
+    ngx_mutex_unlock(ngx_event_timer_mutex);
+
+    ev->timer_set = 1;
+}
+
+
+#endif /* _NGX_EVENT_TIMER_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_access_handler.c
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/* AF_INET only */
+
+typedef struct {
+    in_addr_t     mask;
+    in_addr_t     addr;
+    unsigned      deny;
+} ngx_http_access_rule_t;
+
+
+typedef struct {
+    ngx_array_t  *rules;     /* array of ngx_http_access_rule_t */
+} ngx_http_access_loc_conf_t;
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r);
+static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf);
+static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
+                                            void *parent, void *child);
+static ngx_int_t ngx_http_access_init(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_http_access_commands[] = {
+
+    { ngx_string("allow"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_access_rule,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("deny"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_access_rule,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+
+ngx_http_module_t  ngx_http_access_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_access_create_loc_conf,       /* create location configuration */
+    ngx_http_access_merge_loc_conf         /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_access_module = {
+    NGX_MODULE,
+    &ngx_http_access_module_ctx,           /* module context */
+    ngx_http_access_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_access_init,                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r)
+{
+    ngx_uint_t                   i;
+    struct sockaddr_in          *addr_in;
+    ngx_http_access_rule_t      *rule;
+    ngx_http_access_loc_conf_t  *alcf;
+
+    alcf = ngx_http_get_module_loc_conf(r, ngx_http_access_module);
+
+    if (alcf->rules == NULL) {
+        return NGX_OK;
+    }
+
+    /* AF_INET only */
+
+    addr_in = (struct sockaddr_in *) r->connection->sockaddr;
+
+    rule = alcf->rules->elts;
+    for (i = 0; i < alcf->rules->nelts; i++) {
+
+ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "%08X %08X %08X",
+               addr_in->sin_addr.s_addr, rule[i].mask, rule[i].addr);
+
+        if ((addr_in->sin_addr.s_addr & rule[i].mask) == rule[i].addr) {
+            if (rule[i].deny) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "access forbidden by rule");
+
+                return NGX_HTTP_FORBIDDEN;
+            }
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static char *ngx_http_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf)
+{
+    ngx_http_access_loc_conf_t *alcf = conf;
+
+    ngx_str_t               *value;
+    ngx_inet_cidr_t          in_cidr;
+    ngx_http_access_rule_t  *rule;
+
+    if (alcf->rules == NULL) {
+        alcf->rules = ngx_create_array(cf->pool, 5,
+                                       sizeof(ngx_http_access_rule_t));
+        if (alcf->rules == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (!(rule = ngx_push_array(alcf->rules))) {
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
+
+    if (value[1].len == 3 && ngx_strcmp(value[1].data, "all") == 0) {
+        rule->mask = 0;
+        rule->addr = 0;
+
+        return NGX_CONF_OK;
+    }
+
+    rule->addr = inet_addr((char *) value[1].data);
+
+    if (rule->addr != INADDR_NONE) {
+        rule->mask = 0xffffffff;
+
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_ptocidr(&value[1], &in_cidr) == NGX_ERROR) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid paramter \"%s\"",
+                           value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    rule->mask = in_cidr.mask;
+    rule->addr = in_cidr.addr;
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_access_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_access_loc_conf_t  *conf;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_access_loc_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    return conf;
+}
+
+
+static char *ngx_http_access_merge_loc_conf(ngx_conf_t *cf,
+                                            void *parent, void *child)
+{
+    ngx_http_access_loc_conf_t  *prev = parent;
+    ngx_http_access_loc_conf_t  *conf = child;
+
+    if (conf->rules == NULL) {
+        conf->rules = prev->rules;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_access_init(ngx_cycle_t *cycle)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+    h = ngx_push_array(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_access_handler;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_charset_filter.c
@@ -0,0 +1,553 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    char       **tables;
+    ngx_str_t    name;
+    unsigned     server;
+} ngx_http_charset_t;
+
+
+typedef struct {
+    ngx_int_t   src;
+    ngx_int_t   dst;
+    char       *src2dst;
+    char       *dst2src;
+} ngx_http_charset_tables_t;
+
+
+typedef struct {
+    ngx_array_t  charsets;               /* ngx_http_charset_t */
+    ngx_array_t  tables;                 /* ngx_http_charset_tables_t */
+} ngx_http_charset_main_conf_t;
+
+
+typedef struct {
+    ngx_flag_t  enable;
+    ngx_flag_t  autodetect;
+
+    ngx_int_t   default_charset;
+    ngx_int_t   source_charset;
+} ngx_http_charset_loc_conf_t;
+
+
+typedef struct {
+    ngx_int_t   server;
+    ngx_int_t   client;
+} ngx_http_charset_ctx_t;
+
+
+static void ngx_charset_recode(ngx_buf_t *b, char *table);
+
+static char *ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf);
+static char *ngx_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+
+static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+                                       void *conf);
+static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name);
+
+static ngx_int_t ngx_http_charset_filter_init(ngx_cycle_t *cycle);
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
+                                             void *parent, void *child);
+
+
+static ngx_command_t  ngx_http_charset_filter_commands[] = {
+
+    { ngx_string("charset_map"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
+      ngx_charset_map_block,
+      NGX_HTTP_MAIN_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("default_charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_charset_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, default_charset),
+      NULL },
+
+    { ngx_string("source_charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_charset_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, source_charset),
+      NULL },
+
+    { ngx_string("charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, enable),
+      NULL },
+
+    { ngx_string("autodetect_charset"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_charset_loc_conf_t, autodetect),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_charset_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    ngx_http_charset_create_main_conf,     /* create main configuration */
+    ngx_http_charset_init_main_conf,       /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_charset_create_loc_conf,      /* create location configuration */
+    ngx_http_charset_merge_loc_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_charset_filter_module = {
+    NGX_MODULE,
+    &ngx_http_charset_filter_module_ctx,   /* module context */
+    ngx_http_charset_filter_commands,      /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_charset_filter_init,          /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t ngx_http_charset_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_charset_t            *charsets;
+    ngx_http_charset_ctx_t        *ctx;
+    ngx_http_charset_loc_conf_t   *lcf;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+    lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+    if (lcf->enable == 0) {
+        return ngx_http_next_header_filter(r);
+    }
+
+#if 0
+    if (lcf->default_charset.len == 0) {
+        return ngx_http_next_header_filter(r);
+    }
+#endif
+
+    if (r->headers_out.content_type == NULL) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (ngx_strncasecmp(r->headers_out.content_type->value.data,
+                                                              "text/", 5) != 0
+        && ngx_strncasecmp(r->headers_out.content_type->value.data,
+                                          "application/x-javascript", 24) != 0)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (ngx_strstr(r->headers_out.content_type->value.data, "charset") != NULL)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
+        && r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
+    {
+        /*
+         * do not set charset for the redirect because NN 4.x uses this
+         * charset instead of the next page charset
+         */
+
+        r->headers_out.charset.len = 0;
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.charset.len) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    charsets = mcf->charsets.elts;
+    r->headers_out.charset = charsets[lcf->default_charset].name;
+
+    if (lcf->default_charset == lcf->source_charset) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_http_create_ctx(r, ctx, ngx_http_charset_filter_module,
+                        sizeof(ngx_http_charset_ctx_t), NGX_ERROR);
+
+    r->filter_need_in_memory = 1;
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_charset_body_filter(ngx_http_request_t *r,
+                                              ngx_chain_t *in)
+{
+    char                          *table;
+    ngx_chain_t                   *cl;
+    ngx_http_charset_t            *charsets;
+    ngx_http_charset_ctx_t        *ctx;
+    ngx_http_charset_loc_conf_t   *lcf;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
+
+    if (ctx == NULL) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
+    lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
+
+    charsets = mcf->charsets.elts;
+    table = charsets[lcf->source_charset].tables[lcf->default_charset];
+
+    for (cl = in; cl; cl = cl->next) {
+        ngx_charset_recode(cl->buf, table);
+    }
+
+    return ngx_http_next_body_filter(r, in);
+}
+
+
+static void ngx_charset_recode(ngx_buf_t *b, char *table)
+{
+    u_char  *p, c;
+
+    for (p = b->pos; p < b->last; p++) {
+        c = *p;
+        *p = table[c];
+    }
+}
+
+
+static char *ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf)
+{
+    ngx_http_charset_main_conf_t  *mcf = conf;
+
+    char                       *rv;
+    ngx_int_t                   src, dst;
+    ngx_uint_t                  i;
+    ngx_str_t                  *value;
+    ngx_conf_t                  pvcf;
+    ngx_http_charset_tables_t  *table;
+
+    value = cf->args->elts;
+
+    src = ngx_http_add_charset(&mcf->charsets, &value[1]);
+    if (src == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    dst = ngx_http_add_charset(&mcf->charsets, &value[2]);
+    if (dst == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (src == dst) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"charset_map\" between the same charsets "
+                           "\"%s\" and \"%s\"",
+                           value[1].data, value[2].data);
+        return NGX_CONF_ERROR;
+    }
+
+    table = mcf->tables.elts;
+    for (i = 0; i < mcf->tables.nelts; i++) {
+        if ((src == table->src && dst == table->dst)
+             || (src == table->dst && dst == table->src))
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate \"charset_map\" between "
+                               "\"%s\" and \"%s\"",
+                               value[1].data, value[2].data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (!(table = ngx_push_array(&mcf->tables))) {
+        return NGX_CONF_ERROR;
+    }
+
+    table->src = src;
+    table->dst = dst;
+
+    if (!(table->src2dst = ngx_palloc(cf->pool, 256))) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (!(table->dst2src = ngx_palloc(cf->pool, 256))) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; i < 128; i++) {
+        table->src2dst[i] = (char) i;
+        table->dst2src[i] = (char) i;
+    }
+
+    for (/* void */; i < 256; i++) {
+        table->src2dst[i] = '?';
+        table->dst2src[i] = '?';
+    }
+
+    pvcf = *cf;
+    cf->ctx = table;
+    cf->handler = ngx_charset_map;
+    cf->handler_conf = conf;
+    rv = ngx_conf_parse(cf, NULL);
+    *cf = pvcf;
+
+    return rv;
+}
+
+
+static char *ngx_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    ngx_int_t                   src, dst;
+    ngx_str_t                  *value;
+    ngx_http_charset_tables_t  *table;
+
+    if (cf->args->nelts != 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number");
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    src = ngx_hextoi(value[0].data, value[0].len);
+    if (src == NGX_ERROR || src > 255) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%s\"", value[0].data);
+        return NGX_CONF_ERROR;
+    }
+
+    dst = ngx_hextoi(value[1].data, value[1].len);
+    if (dst == NGX_ERROR || dst > 255) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%s\"", value[1].data);
+        return NGX_CONF_ERROR;
+    }
+
+    table = cf->ctx;
+
+    table->src2dst[src] = (char) dst;
+    table->dst2src[dst] = (char) src;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+                                       void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t                     *cp;
+    ngx_str_t                     *value;
+    ngx_http_charset_t            *charset;
+    ngx_http_charset_main_conf_t  *mcf;
+
+    cp = (ngx_int_t *) (p + cmd->offset);
+
+    if (*cp != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    mcf = ngx_http_conf_get_module_main_conf(cf,
+                                             ngx_http_charset_filter_module);
+
+    value = cf->args->elts;
+
+    *cp = ngx_http_add_charset(&mcf->charsets, &value[1]);
+    if (*cp == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cmd->offset == offsetof(ngx_http_charset_loc_conf_t, source_charset)) {
+        charset = mcf->charsets.elts;
+        charset[*cp].server = 1;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_add_charset(ngx_array_t *charsets, ngx_str_t *name)
+{
+    ngx_uint_t           i;
+    ngx_http_charset_t  *c;
+
+    c = charsets->elts;
+    for (i = 0; i < charsets->nelts; i++) {
+        if (name->len != c[i].name.len) {
+            continue;
+        }
+
+        if (ngx_strcasecmp(name->data, c[i].name.data) == 0) {
+            break;
+        }
+    }
+
+    if (i < charsets->nelts) {
+        return i;
+    }
+
+    if (!(c = ngx_push_array(charsets))) {
+        return NGX_ERROR;
+    }
+
+    c->name = *name;
+
+    return i;
+}
+
+
+static ngx_int_t ngx_http_charset_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_charset_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_charset_body_filter;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_charset_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_charset_main_conf_t  *mcf;
+
+    if (!(mcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_main_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_init_array(mcf->charsets, cf->pool, 5, sizeof(ngx_http_charset_t),
+                   NGX_CONF_ERROR);
+
+    ngx_init_array(mcf->tables, cf->pool, 10, sizeof(ngx_http_charset_tables_t),
+                   NGX_CONF_ERROR);
+
+    return mcf;
+}
+
+
+static char *ngx_http_charset_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+    ngx_http_charset_main_conf_t *mcf = conf;
+
+    ngx_uint_t                  i, n;
+    ngx_http_charset_t         *charset;
+    ngx_http_charset_tables_t  *tables;
+
+    tables = mcf->tables.elts;
+    charset = mcf->charsets.elts;
+
+    for (i = 0; i < mcf->charsets.nelts; i++) {
+        if (!charset[i].server) {
+            continue;
+        }
+
+        charset[i].tables = ngx_pcalloc(cf->pool,
+                                        sizeof(char *) * mcf->charsets.nelts);
+
+        if (charset[i].tables == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (n = 0; n < mcf->tables.nelts; n++) {
+            if ((ngx_int_t) i == tables[n].src) {
+                charset[i].tables[tables[n].dst] = tables[n].src2dst;
+                continue;
+            }
+
+            if ((ngx_int_t) i == tables[n].dst) {
+                charset[i].tables[tables[n].src] = tables[n].dst2src;
+            }
+        }
+    }
+
+    for (i = 0; i < mcf->charsets.nelts; i++) {
+        if (!charset[i].server) {
+            continue;
+        }
+
+        for (n = 0; n < mcf->charsets.nelts; n++) {
+            if (i == n) {
+                continue;
+            }
+
+            if (charset[i].tables[n]) {
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                          " no \"charset_map\" between the charsets "
+                          "\"%s\" and \"%s\"",
+                          charset[i].name.data, charset[n].name.data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_charset_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_charset_loc_conf_t  *lcf;
+
+    if (!(lcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_charset_loc_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    lcf->enable = NGX_CONF_UNSET;
+    lcf->autodetect = NGX_CONF_UNSET;
+    lcf->default_charset = NGX_CONF_UNSET;
+    lcf->source_charset = NGX_CONF_UNSET;
+
+    return lcf;
+}
+
+
+static char *ngx_http_charset_merge_loc_conf(ngx_conf_t *cf,
+                                             void *parent, void *child)
+{
+    ngx_http_charset_loc_conf_t *prev = parent;
+    ngx_http_charset_loc_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+    ngx_conf_merge_value(conf->autodetect, prev->autodetect, 0);
+
+    if (conf->source_charset == NGX_CONF_UNSET) {
+        conf->source_charset = prev->source_charset;
+    }
+
+    ngx_conf_merge_value(conf->default_charset, prev->default_charset,
+                         conf->source_charset);
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_chunked_filter.c
@@ -0,0 +1,156 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+static ngx_int_t ngx_http_chunked_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_http_module_t  ngx_http_chunked_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_chunked_filter_module = {
+    NGX_MODULE,
+    &ngx_http_chunked_filter_module_ctx,   /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_chunked_filter_init,          /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t ngx_http_chunked_header_filter(ngx_http_request_t *r)
+{
+    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_out.content_length_n == -1) {
+        if (r->http_version < NGX_HTTP_VERSION_11) {
+            r->keepalive = 0;
+
+        } else {
+            r->chunked = 1;
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r,
+                                              ngx_chain_t *in)
+{
+    u_char       *chunk;
+    size_t        size, len;
+    ngx_buf_t    *b;
+    ngx_chain_t   out, tail, *cl, *tl, **ll;
+
+    if (in == NULL || !r->chunked) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    out.buf = NULL;
+    ll = &out.next;
+
+    size = 0;
+    cl = in;
+
+    for ( ;; ) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http chunk: %d", ngx_buf_size(cl->buf));
+
+        size += ngx_buf_size(cl->buf);
+
+        ngx_test_null(tl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+        tl->buf = cl->buf;
+        *ll = tl;
+        ll = &tl->next;
+
+        if (cl->next == NULL) {
+            break;
+        }
+
+        cl = cl->next;
+    }
+
+    if (size) {
+        ngx_test_null(chunk, ngx_palloc(r->pool, 11), NGX_ERROR);
+        len = ngx_snprintf((char *) chunk, 11, SIZE_T_X_FMT CRLF, size);
+
+        ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+        b->temporary = 1;
+        b->pos = chunk;
+        b->last = chunk + len;
+
+        out.buf = b;
+    }
+
+    if (cl->buf->last_buf) {
+        ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+        b->memory = 1;
+        b->last_buf = 1;
+        b->pos = (u_char *) CRLF "0" CRLF CRLF;
+        b->last = b->pos + 7;
+
+        cl->buf->last_buf = 0;
+
+        if (size == 0) {
+            b->pos += 2;
+            out.buf = b;
+            out.next = NULL;
+
+            return ngx_http_next_body_filter(r, &out);
+        }
+
+    } else {
+        if (size == 0) {
+            *ll = NULL;
+            return ngx_http_next_body_filter(r, out.next);
+        }
+
+        ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+        b->memory = 1;
+        b->pos = (u_char *) CRLF;
+        b->last = b->pos + 2;
+    }
+
+    tail.buf = b;
+    tail.next = NULL;
+    *ll = &tail;
+
+    return ngx_http_next_body_filter(r, &out);
+}
+
+
+static ngx_int_t ngx_http_chunked_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_chunked_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_chunked_body_filter;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_gzip_filter.c
@@ -0,0 +1,1036 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <zlib.h>
+
+
+typedef struct {
+    ngx_flag_t           enable;
+    ngx_flag_t           no_buffer;
+
+    ngx_bufs_t           bufs;
+
+    ngx_uint_t           http_version;
+    ngx_uint_t           proxied;
+
+    int                  level;
+    size_t               wbits;
+    size_t               memlevel;
+    ssize_t              min_length;
+} ngx_http_gzip_conf_t;
+
+
+#define NGX_HTTP_GZIP_PROXIED_OFF       0x0002
+#define NGX_HTTP_GZIP_PROXIED_EXPIRED   0x0004
+#define NGX_HTTP_GZIP_PROXIED_NO_CACHE  0x0008
+#define NGX_HTTP_GZIP_PROXIED_NO_STORE  0x0010
+#define NGX_HTTP_GZIP_PROXIED_PRIVATE   0x0020
+#define NGX_HTTP_GZIP_PROXIED_NO_LM     0x0040
+#define NGX_HTTP_GZIP_PROXIED_NO_ETAG   0x0080
+#define NGX_HTTP_GZIP_PROXIED_AUTH      0x0100
+#define NGX_HTTP_GZIP_PROXIED_ANY       0x0200
+
+
+typedef struct {
+    ngx_chain_t         *in;
+    ngx_chain_t         *free;
+    ngx_chain_t         *busy;
+    ngx_chain_t         *out;
+    ngx_chain_t        **last_out;
+    ngx_buf_t           *in_buf;
+    ngx_buf_t           *out_buf;
+    ngx_int_t            bufs;
+
+    off_t                length;
+
+    void                *preallocated;
+    char                *free_mem;
+    ngx_uint_t           allocated;
+
+    unsigned             flush:4;
+    unsigned             redo:1;
+    unsigned             done:1;
+#if 0
+    unsigned             pass:1;
+    unsigned             blocked:1;
+#endif
+
+    size_t               zin;
+    size_t               zout;
+
+    uint32_t             crc32;
+    z_stream             zstream;
+    ngx_http_request_t  *request;
+} ngx_http_gzip_ctx_t;
+
+
+static ngx_int_t ngx_http_gzip_proxied(ngx_http_request_t *r,
+                                       ngx_http_gzip_conf_t *conf);
+static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items,
+                                        u_int size);
+static void ngx_http_gzip_filter_free(void *opaque, void *address);
+ngx_inline static int ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx);
+
+static u_char *ngx_http_gzip_log_ratio(ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data);
+
+static ngx_int_t ngx_http_gzip_pre_conf(ngx_conf_t *cf);
+static ngx_int_t ngx_http_gzip_filter_init(ngx_cycle_t *cycle);
+static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
+static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
+                                      void *parent, void *child);
+static char *ngx_http_gzip_set_window(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_set_hash(ngx_conf_t *cf, void *post, void *data);
+
+
+static ngx_conf_num_bounds_t  ngx_http_gzip_comp_level_bounds = {
+    ngx_conf_check_num_bounds, 1, 9
+};
+
+static ngx_conf_post_handler_pt  ngx_http_gzip_set_window_p =
+                                                      ngx_http_gzip_set_window;
+static ngx_conf_post_handler_pt  ngx_http_gzip_set_hash_p =
+                                                        ngx_http_gzip_set_hash;
+
+
+
+static ngx_conf_enum_t  ngx_http_gzip_http_version[] = {
+    { ngx_string("1.0"), NGX_HTTP_VERSION_10 },
+    { ngx_string("1.1"), NGX_HTTP_VERSION_11 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t  ngx_http_gzip_proxied_mask[] = {
+    { ngx_string("off"), NGX_HTTP_GZIP_PROXIED_OFF },
+    { ngx_string("expired"), NGX_HTTP_GZIP_PROXIED_EXPIRED },
+    { ngx_string("no-cache"), NGX_HTTP_GZIP_PROXIED_NO_CACHE },
+    { ngx_string("no-store"), NGX_HTTP_GZIP_PROXIED_NO_STORE },
+    { ngx_string("private"), NGX_HTTP_GZIP_PROXIED_PRIVATE },
+    { ngx_string("no_last_modified"), NGX_HTTP_GZIP_PROXIED_NO_LM },
+    { ngx_string("no_etag"), NGX_HTTP_GZIP_PROXIED_NO_ETAG },
+    { ngx_string("auth"), NGX_HTTP_GZIP_PROXIED_AUTH },
+    { ngx_string("any"), NGX_HTTP_GZIP_PROXIED_ANY },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_gzip_filter_commands[] = {
+
+    { ngx_string("gzip"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, enable),
+      NULL },
+
+    { ngx_string("gzip_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, bufs),
+      NULL },
+
+    { ngx_string("gzip_comp_level"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, level),
+      &ngx_http_gzip_comp_level_bounds },
+
+    { ngx_string("gzip_window"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, wbits),
+      &ngx_http_gzip_set_window_p },
+
+    { ngx_string("gzip_hash"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, memlevel),
+      &ngx_http_gzip_set_hash_p },
+
+    { ngx_string("gzip_no_buffer"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, no_buffer),
+      NULL },
+
+    { ngx_string("gzip_http_version"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, http_version),
+      &ngx_http_gzip_http_version },
+
+    { ngx_string("gzip_proxied"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, proxied),
+      &ngx_http_gzip_proxied_mask },
+
+    { ngx_string("gzip_min_length"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_gzip_conf_t, min_length),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_gzip_filter_module_ctx = {
+    ngx_http_gzip_pre_conf,                /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_gzip_create_conf,             /* create location configuration */
+    ngx_http_gzip_merge_conf,              /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_gzip_filter_module = {
+    NGX_MODULE,
+    &ngx_http_gzip_filter_module_ctx,      /* module context */
+    ngx_http_gzip_filter_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_gzip_filter_init,             /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_log_op_name_t ngx_http_gzip_log_fmt_ops[] = {
+    { ngx_string("gzip_ratio"), NGX_INT32_LEN + 3, ngx_http_gzip_log_ratio },
+    { ngx_null_string, 0, NULL }
+};
+
+
+
+static u_char  gzheader[10] = { 0x1f, 0x8b, Z_DEFLATED, 0, 0, 0, 0, 0, 0, 3 };
+
+#if (HAVE_LITTLE_ENDIAN)
+
+struct gztrailer {
+    uint32_t  crc32;
+    uint32_t  zlen;
+};
+
+#else /* HAVE_BIG_ENDIAN */
+
+struct gztrailer {
+    u_char  crc32[4];
+    u_char  zlen[4];
+};
+
+#endif
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t ngx_http_gzip_header_filter(ngx_http_request_t *r)
+{
+    ngx_http_gzip_ctx_t   *ctx;
+    ngx_http_gzip_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (!conf->enable
+        || r->headers_out.status != NGX_HTTP_OK
+        || r->header_only
+        || r->http_version < conf->http_version
+        || (r->headers_out.content_encoding
+            && r->headers_out.content_encoding->value.len)
+        || r->headers_in.accept_encoding == NULL
+        || (r->headers_out.content_length_n != -1
+            && r->headers_out.content_length_n < conf->min_length)
+        || ngx_strstr(r->headers_in.accept_encoding->value.data, "gzip") == NULL
+       )
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    /* TODO: "text/html" -> custom types */
+    if (r->headers_out.content_type
+        && ngx_strncasecmp(r->headers_out.content_type->value.data,
+                                                          "text/html", 9) != 0)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+
+    if (r->headers_in.via) {
+        if (conf->proxied & NGX_HTTP_GZIP_PROXIED_OFF) {
+            return ngx_http_next_header_filter(r);
+        }
+
+        if (!(conf->proxied & NGX_HTTP_GZIP_PROXIED_ANY)
+            && ngx_http_gzip_proxied(r, conf) == NGX_DECLINED)
+        {
+            return ngx_http_next_header_filter(r);
+        }
+    }
+
+
+    /*
+     * if the URL (without the "http://" prefix) is longer than 253 bytes
+     * then MSIE 4.x can not handle the compressed stream - it waits too long,
+     * hangs up or crashes
+     */
+
+    if (r->headers_in.msie4 && r->unparsed_uri.len > 200) {
+        return ngx_http_next_header_filter(r);
+    }
+
+
+    ngx_http_create_ctx(r, ctx, ngx_http_gzip_filter_module,
+                        sizeof(ngx_http_gzip_ctx_t), NGX_ERROR);
+    ctx->request = r;
+
+    r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.content_encoding == NULL) {
+        return NGX_ERROR;
+    }
+
+    r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;
+    r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";
+    r->headers_out.content_encoding->value.len = sizeof("gzip") - 1;
+    r->headers_out.content_encoding->value.data = (u_char *) "gzip";
+
+    ctx->length = r->headers_out.content_length_n;
+    r->headers_out.content_length_n = -1;
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->key.len = 0;
+        r->headers_out.content_length = NULL;
+    }
+    r->filter_need_in_memory = 1;
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_gzip_proxied(ngx_http_request_t *r,
+                                       ngx_http_gzip_conf_t *conf)
+{
+    time_t  date, expires;
+
+    if (r->headers_in.authorization
+        && (conf->proxied & NGX_HTTP_GZIP_PROXIED_AUTH))
+    {
+        return NGX_OK;
+    }
+
+    if (r->headers_out.expires) {
+
+        if (!(conf->proxied & NGX_HTTP_GZIP_PROXIED_EXPIRED)) {
+            return NGX_DECLINED;
+        }
+
+        expires = ngx_http_parse_time(r->headers_out.expires->value.data,
+                                      r->headers_out.expires->value.len);
+        if (expires == NGX_ERROR) {
+            return NGX_DECLINED;
+        }
+
+        if (r->headers_out.date) {
+            date = ngx_http_parse_time(r->headers_out.date->value.data,
+                                       r->headers_out.date->value.len);
+            if (date == NGX_ERROR) {
+                return NGX_DECLINED;
+            }
+
+        } else {
+            date = ngx_time();
+        }
+
+        if (expires < date) {
+            return NGX_OK;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    if (r->headers_out.cache_control) {
+
+        if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_CACHE)
+            && ngx_strstr(r->headers_out.cache_control->value.data, "no-cache"))
+        {
+            return NGX_OK;
+        }
+
+        if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_STORE)
+            && ngx_strstr(r->headers_out.cache_control->value.data, "no-store"))
+        {
+            return NGX_OK;
+        }
+
+        if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_PRIVATE)
+            && ngx_strstr(r->headers_out.cache_control->value.data, "private"))
+        {
+            return NGX_OK;
+        }
+
+        return NGX_DECLINED;
+    }
+
+    if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_LM)
+        && r->headers_out.last_modified)
+    {
+        return NGX_DECLINED;
+    }
+
+    if ((conf->proxied & NGX_HTTP_GZIP_PROXIED_NO_ETAG)
+        && r->headers_out.etag)
+    {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_gzip_body_filter(ngx_http_request_t *r,
+                                           ngx_chain_t *in)
+{
+    int                    rc, wbits, memlevel, last;
+    struct gztrailer      *trailer;
+    ngx_buf_t             *b;
+    ngx_chain_t           *cl;
+    ngx_http_gzip_ctx_t   *ctx;
+    ngx_http_gzip_conf_t  *conf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+    if (ctx == NULL || ctx->done) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
+
+    if (ctx->preallocated == NULL) {
+        wbits = conf->wbits;
+        memlevel = conf->memlevel;
+
+        if (ctx->length > 0) {
+
+            /* the actual zlib window size is smaller by 262 bytes */
+
+            while (ctx->length < ((1 << (wbits - 1)) - 262)) {
+                wbits--;
+                memlevel--;
+            }
+        }
+
+        /*
+         * We preallocate a memory for zlib in one buffer (200K-400K), this
+         * dicreases a number of malloc() and free() calls and also probably
+         * dicreases a number of syscalls (sbrk() or so).
+         * Besides we free this memory as soon as the gzipping will complete
+         * and do not wait while a whole response will be sent to a client.
+         *
+         * 8K is for zlib deflate_state, it takes
+         *  * 5816 bytes on x86 and sparc64 (32-bit mode)
+         *  * 5920 bytes on amd64 and sparc64
+         */
+
+        ctx->allocated = 8192 + (1 << (wbits + 2)) + (1 << (memlevel + 9));
+
+        if (!(ctx->preallocated = ngx_palloc(r->pool, ctx->allocated))) {
+            return NGX_ERROR;
+        }
+
+        ctx->free_mem = ctx->preallocated;
+
+        ctx->zstream.zalloc = ngx_http_gzip_filter_alloc;
+        ctx->zstream.zfree = ngx_http_gzip_filter_free;
+        ctx->zstream.opaque = ctx;
+
+        rc = deflateInit2(&ctx->zstream, conf->level, Z_DEFLATED,
+                          -wbits, memlevel, Z_DEFAULT_STRATEGY);
+
+        if (rc != Z_OK) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "deflateInit2() failed: %d", rc);
+            return ngx_http_gzip_error(ctx);
+        }
+
+        if (!(b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)))) {
+            return ngx_http_gzip_error(ctx);
+        }
+
+        b->memory = 1;
+        b->pos = gzheader;
+        b->last = b->pos + 10;
+
+        ngx_alloc_link_and_set_buf(cl, b, r->pool, ngx_http_gzip_error(ctx));
+        ctx->out = cl;
+        ctx->last_out = &cl->next;
+
+        ctx->crc32 = crc32(0L, Z_NULL, 0);
+        ctx->flush = Z_NO_FLUSH;
+    }
+
+    if (in) {
+        if (ngx_chain_add_copy(r->pool, &ctx->in, in) == NGX_ERROR) {
+            return ngx_http_gzip_error(ctx);
+        }
+    }
+
+    last = NGX_NONE;
+
+    for ( ;; ) {
+
+        for ( ;; ) {
+
+            /* does zlib need a new data ? */
+
+            if (ctx->zstream.avail_in == 0
+                && ctx->flush == Z_NO_FLUSH
+                && !ctx->redo)
+            {
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "gzip in: " PTR_FMT, ctx->in);
+
+                if (ctx->in == NULL) {
+                    break;
+                }
+
+                ctx->in_buf = ctx->in->buf;
+                ctx->in = ctx->in->next;
+
+                ctx->zstream.next_in = ctx->in_buf->pos;
+                ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos;
+
+                ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                               "gzip in_buf:" PTR_FMT " ni:" PTR_FMT " ai:%d",
+                               ctx->in_buf,
+                               ctx->zstream.next_in, ctx->zstream.avail_in);
+
+                /* STUB */
+                if (ctx->in_buf->last < ctx->in_buf->pos) {
+                    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                                  "zstream.avail_in is huge");
+                    ctx->done = 1;
+                    return NGX_ERROR;
+                }
+                /**/
+
+                if (ctx->in_buf->last_buf) {
+                    ctx->flush = Z_FINISH;
+
+                } else if (ctx->in_buf->flush) {
+                    ctx->flush = Z_SYNC_FLUSH;
+                }
+
+                if (ctx->zstream.avail_in == 0) {
+                    if (ctx->flush == Z_NO_FLUSH) {
+                        continue;
+                    }
+
+                } else {
+                    ctx->crc32 = crc32(ctx->crc32, ctx->zstream.next_in,
+                                       ctx->zstream.avail_in);
+                }
+            }
+
+
+            /* is there a space for the gzipped data ? */
+
+            if (ctx->zstream.avail_out == 0) {
+
+                if (ctx->free) {
+                    ctx->out_buf = ctx->free->buf;
+                    ctx->free = ctx->free->next;
+
+                } else if (ctx->bufs < conf->bufs.num) {
+                    ctx->out_buf = ngx_create_temp_buf(r->pool,
+                                                       conf->bufs.size);
+                    if (ctx->out_buf == NULL) {
+                        return ngx_http_gzip_error(ctx);
+                    }
+
+                    ctx->out_buf->tag = (ngx_buf_tag_t)
+                                                  &ngx_http_gzip_filter_module;
+                    ctx->out_buf->recycled = 1;
+                    ctx->bufs++;
+
+                } else {
+#if 0
+                    ctx->blocked = 1;
+#endif
+                    break;
+                }
+
+#if 0
+                ctx->blocked = 0;
+#endif
+                ctx->zstream.next_out = ctx->out_buf->pos;
+                ctx->zstream.avail_out = conf->bufs.size;
+            }
+
+            ngx_log_debug6(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "deflate in: ni:%X no:%X ai:%d ao:%d fl:%d redo:%d",
+                           ctx->zstream.next_in, ctx->zstream.next_out,
+                           ctx->zstream.avail_in, ctx->zstream.avail_out,
+                           ctx->flush, ctx->redo);
+
+            rc = deflate(&ctx->zstream, ctx->flush);
+
+            if (rc != Z_OK && rc != Z_STREAM_END) {
+                ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                              "deflate() failed: %d, %d", ctx->flush, rc);
+                return ngx_http_gzip_error(ctx);
+            }
+
+            ngx_log_debug5(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "deflate out: ni:%X no:%X ai:%d ao:%d rc:%d",
+                           ctx->zstream.next_in, ctx->zstream.next_out,
+                           ctx->zstream.avail_in, ctx->zstream.avail_out,
+                           rc);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "gzip in_buf:" PTR_FMT " pos:" PTR_FMT,
+                           ctx->in_buf, ctx->in_buf->pos);
+
+
+            if (ctx->zstream.next_in) {
+                ctx->in_buf->pos = ctx->zstream.next_in;
+
+                if (ctx->zstream.avail_in == 0) {
+                    ctx->zstream.next_in = NULL;
+                }
+            }
+
+            ctx->out_buf->last = ctx->zstream.next_out;
+
+            if (ctx->zstream.avail_out == 0) {
+
+                /* zlib wants to output some more gzipped data */
+
+                ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+                                           ngx_http_gzip_error(ctx));
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+
+                ctx->redo = 1;
+
+                continue;
+            }
+
+            ctx->redo = 0;
+
+            if (ctx->flush == Z_SYNC_FLUSH) {
+
+                ctx->out_buf->flush = 0;
+                ctx->flush = Z_NO_FLUSH;
+
+                ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+                                           ngx_http_gzip_error(ctx));
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+
+#if 0
+                ctx->pass = 1;
+#endif
+
+                break;
+            }
+
+            if (rc == Z_STREAM_END) {
+
+                ctx->zin = ctx->zstream.total_in;
+                ctx->zout = 10 + ctx->zstream.total_out + 8;
+
+                rc = deflateEnd(&ctx->zstream);
+
+                if (rc != Z_OK) {
+                    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                                  "deflateEnd() failed: %d", rc);
+                    return ngx_http_gzip_error(ctx);
+                }
+
+                ngx_pfree(r->pool, ctx->preallocated);
+
+                ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+                                           ngx_http_gzip_error(ctx));
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+
+                if (ctx->zstream.avail_out >= 8) {
+                    trailer = (struct gztrailer *) ctx->out_buf->last;
+                    ctx->out_buf->last += 8;
+                    ctx->out_buf->last_buf = 1;
+
+                } else {
+                    if (!(b = ngx_create_temp_buf(r->pool, 8))) {
+                        return ngx_http_gzip_error(ctx);
+                    }
+
+                    b->last_buf = 1;
+
+                    ngx_alloc_link_and_set_buf(cl, b, r->pool,
+                                               ngx_http_gzip_error(ctx));
+                    *ctx->last_out = cl;
+                    ctx->last_out = &cl->next;
+                    trailer = (struct gztrailer *) b->pos;
+                    b->last += 8;
+                }
+
+#if (HAVE_LITTLE_ENDIAN)
+                trailer->crc32 = ctx->crc32;
+                trailer->zlen = ctx->zin;
+#else
+                trailer->crc32[0] = ctx->crc32 & 0xff;
+                trailer->crc32[1] = (ctx->crc32 >> 8) & 0xff;
+                trailer->crc32[2] = (ctx->crc32 >> 16) & 0xff;
+                trailer->crc32[3] = (ctx->crc32 >> 24) & 0xff;
+
+                trailer->zlen[0] = ctx->zin & 0xff;
+                trailer->zlen[1] = (ctx->zin >> 8) & 0xff;
+                trailer->zlen[2] = (ctx->zin >> 16) & 0xff;
+                trailer->zlen[3] = (ctx->zin >> 24) & 0xff;
+#endif
+
+                ctx->zstream.avail_in = 0;
+                ctx->zstream.avail_out = 0;
+
+                ctx->done = 1;
+#if 0
+                ctx->pass = 1;
+#endif
+
+                break;
+            }
+
+            if (conf->no_buffer && ctx->in == NULL) {
+                ngx_alloc_link_and_set_buf(cl, ctx->out_buf, r->pool,
+                                           ngx_http_gzip_error(ctx));
+                *ctx->last_out = cl;
+                ctx->last_out = &cl->next;
+
+#if 0
+                ctx->pass = 1;
+#endif
+
+                break;
+            }
+        }
+
+#if 0
+
+        /* OLD CODE */
+
+        if (ctx->out) {
+            if (ctx->pass) {
+                ctx->pass = 0;
+
+            } else if (last == NGX_AGAIN) {
+                return last;
+            }
+
+        } else if (ctx->busy->buf && ngx_buf_size(ctx->busy->buf)) {
+            if (last != NGX_NONE) {
+                return last;
+            }
+
+        } else if (ctx->blocked) {
+            if (last != NGX_NONE) {
+                return last;
+            }
+
+        } else {
+            if (last == NGX_NONE) {
+                return NGX_OK;
+            }
+
+            return last;
+        }
+#endif
+
+        /* NEW CODE */
+
+        if (last == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        if (ctx->out == NULL && ctx->busy == NULL) {
+            return NGX_OK;
+        }
+
+        /**/
+
+        last = ngx_http_next_body_filter(r, ctx->out);
+
+        /*
+         * we do not check NGX_AGAIN here because the downstream filters
+         * may free some buffers and zlib may compress some data into them
+         */
+
+        if (last == NGX_ERROR) {
+            return ngx_http_gzip_error(ctx);
+        }
+
+        ngx_chain_update_chains(&ctx->free, &ctx->busy, &ctx->out,
+                                 (ngx_buf_tag_t) &ngx_http_gzip_filter_module);
+        ctx->last_out = &ctx->out;
+
+        if (ctx->done) {
+            return last;
+        }
+    }
+}
+
+
+static void *ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size)
+{
+    ngx_http_gzip_ctx_t *ctx = opaque;
+
+    void        *p;
+    ngx_uint_t   alloc;
+
+    alloc = items * size;
+    if (alloc % 512 != 0) {
+
+        /*
+         * the zlib deflate_state allocation, it takes about 6K, we allocate 8K
+         */
+
+        alloc = (alloc + ngx_pagesize - 1) & ~(ngx_pagesize - 1);
+    }
+
+    if (alloc <= ctx->allocated) {
+        p = ctx->free_mem;
+        ctx->free_mem += alloc;
+        ctx->allocated -= alloc;
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                       "gzip alloc: n:%d s:%d a:%d p:" PTR_FMT,
+                       items, size, alloc, p);
+
+        return p;
+    }
+
+    ngx_log_error(NGX_LOG_ALERT, ctx->request->connection->log, 0,
+                  "gzip filter failed to use preallocated memory: %d of %d",
+                  items * size, ctx->allocated);
+
+    p = ngx_palloc(ctx->request->pool, items * size);
+
+    return p;
+}
+
+
+static void ngx_http_gzip_filter_free(void *opaque, void *address)
+{
+#if 0
+    ngx_http_gzip_ctx_t *ctx = opaque;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ctx->request->connection->log, 0,
+                   "gzip free: %X", address);
+#endif
+}
+
+
+static u_char *ngx_http_gzip_log_ratio(ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data)
+{
+    ngx_uint_t            zint, zfrac;
+    ngx_http_gzip_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_gzip_filter_module);
+
+    if (ctx == NULL || ctx->zout == 0) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+#if 0
+    return buf + ngx_snprintf((char *) buf, NGX_INT32_LEN + 4, "%.2f",
+                              (float) ctx->zin / ctx->zout);
+#endif
+
+    /* we prefer do not use FPU */
+
+    zint = (ngx_uint_t) (ctx->zin / ctx->zout);
+    zfrac = (ngx_uint_t) ((ctx->zin * 100 / ctx->zout) % 100);
+
+    if ((ctx->zin * 1000 / ctx->zout) %10 > 4) {
+        if (++zfrac > 99) {
+            zint++;
+            zfrac = 0;
+        }
+    }
+
+    return buf + ngx_snprintf((char *) buf, NGX_INT32_LEN + 4,
+                              "%" NGX_UINT_T_FMT ".%02" NGX_UINT_T_FMT,
+                              zint, zfrac);
+}
+
+
+ngx_inline static int ngx_http_gzip_error(ngx_http_gzip_ctx_t *ctx)
+{
+    deflateEnd(&ctx->zstream);
+
+    ngx_pfree(ctx->request->pool, ctx->preallocated);
+
+    ctx->zstream.avail_in = 0;
+    ctx->zstream.avail_out = 0;
+
+    ctx->done = 1;
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t ngx_http_gzip_pre_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_op_name_t  *op;
+
+    for (op = ngx_http_gzip_log_fmt_ops; op->name.len; op++) { /* void */ }
+    op->op = NULL;
+
+    op = ngx_http_log_fmt_ops;
+
+    for (op = ngx_http_log_fmt_ops; op->op; op++) {
+        if (op->name.len == 0) {
+            op = (ngx_http_log_op_name_t *) op->op;
+        }
+    }
+
+    op->op = (ngx_http_log_op_pt) ngx_http_gzip_log_fmt_ops;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_gzip_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_gzip_header_filter;
+
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_gzip_body_filter;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_gzip_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_gzip_conf_t  *conf;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_gzip_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    /*
+
+    set by ngx_pcalloc():
+
+    conf->bufs.num = 0;
+    conf->proxied = 0;
+
+     */
+
+    conf->enable = NGX_CONF_UNSET;
+    conf->no_buffer = NGX_CONF_UNSET;
+
+    conf->http_version = NGX_CONF_UNSET_UINT;
+
+    conf->level = NGX_CONF_UNSET;
+    conf->wbits = (size_t) NGX_CONF_UNSET;
+    conf->memlevel = (size_t) NGX_CONF_UNSET;
+    conf->min_length = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
+                                      void *parent, void *child)
+{
+    ngx_http_gzip_conf_t *prev = parent;
+    ngx_http_gzip_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 4, ngx_pagesize);
+
+    ngx_conf_merge_unsigned_value(conf->http_version, prev->http_version,
+                                  NGX_HTTP_VERSION_11);
+    ngx_conf_merge_bitmask_value(conf->proxied, prev->proxied,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_HTTP_GZIP_PROXIED_OFF));
+
+    ngx_conf_merge_value(conf->level, prev->level, 1);
+    ngx_conf_merge_size_value(conf->wbits, prev->wbits, MAX_WBITS);
+    ngx_conf_merge_size_value(conf->memlevel, prev->memlevel,
+                              MAX_MEM_LEVEL - 1);
+    ngx_conf_merge_value(conf->min_length, prev->min_length, 0);
+    ngx_conf_merge_value(conf->no_buffer, prev->no_buffer, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_gzip_set_window(ngx_conf_t *cf, void *post, void *data)
+{
+    int *np = data;
+
+    int  wbits, wsize;
+
+    wbits = 15;
+
+    for (wsize = 32 * 1024; wsize > 256; wsize >>= 1) {
+
+        if (wsize == *np) {
+            *np = wbits;
+
+            return NGX_CONF_OK;
+        }
+
+        wbits--;
+    }
+
+    return "must be 512, 1k, 2k, 4k, 8k, 16k, or 32k";
+}
+
+
+static char *ngx_http_gzip_set_hash(ngx_conf_t *cf, void *post, void *data)
+{
+    int *np = data;
+
+    int  memlevel, hsize;
+
+    memlevel = 9;
+
+    for (hsize = 128 * 1024; hsize > 256; hsize >>= 1) {
+
+        if (hsize == *np) {
+            *np = memlevel;
+
+            return NGX_CONF_OK;
+        }
+
+        memlevel--;
+    }
+
+    return "must be 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, or 128k";
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_headers_filter.c
@@ -0,0 +1,239 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    time_t  expires;
+} ngx_http_headers_conf_t;
+
+
+#define NGX_HTTP_EXPIRES_UNSET   -2147483647
+#define NGX_HTTP_EXPIRES_OFF     -2147483646
+#define NGX_HTTP_EXPIRES_EPOCH   -2147483645
+
+
+static ngx_int_t ngx_http_headers_filter_init(ngx_cycle_t *cycle);
+static void *ngx_http_headers_create_conf(ngx_conf_t *cf);
+static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
+                                         void *parent, void *child);
+char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static ngx_command_t  ngx_http_headers_filter_commands[] = {
+
+    { ngx_string("expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_headers_expires,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL},
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_headers_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_headers_create_conf,          /* create location configuration */
+    ngx_http_headers_merge_conf            /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_headers_filter_module = {
+    NGX_MODULE,
+    &ngx_http_headers_filter_module_ctx,   /* module context */
+    ngx_http_headers_filter_commands,      /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_headers_filter_init,          /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_int_t ngx_http_headers_filter(ngx_http_request_t *r)
+{
+    size_t                    len;
+    ngx_table_elt_t          *expires, *cc;
+    ngx_http_headers_conf_t  *conf;
+
+    if (r->headers_out.status != NGX_HTTP_OK) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);
+
+    if (conf->expires != NGX_HTTP_EXPIRES_OFF) {
+
+        if (!(expires = ngx_list_push(&r->headers_out.headers))) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.expires = expires;
+
+        if (!(cc = ngx_list_push(&r->headers_out.headers))) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.cache_control = cc;
+
+        len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT");
+
+        expires->key.len = sizeof("Expires") - 1;
+        expires->key.data = (u_char *) "Expires";
+        expires->value.len = len - 1;
+
+        cc->key.len = sizeof("Cache-Control") - 1;
+        cc->key.data = (u_char *) "Cache-Control";
+
+        if (conf->expires == NGX_HTTP_EXPIRES_EPOCH) {
+            expires->value.data = (u_char *) "Thu, 01 Jan 1970 00:00:01 GMT";
+
+            cc->value.len = sizeof("no-cache") - 1;
+            cc->value.data = (u_char *) "no-cache";
+
+        } else {
+            expires->value.data = ngx_palloc(r->pool, len);
+            if (expires->value.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            if (conf->expires == 0) {
+                ngx_memcpy(expires->value.data, ngx_cached_http_time.data,
+                           ngx_cached_http_time.len + 1);
+
+                cc->value.len = sizeof("max-age=0") - 1;
+                cc->value.data = (u_char *) "max-age=0";
+
+            } else {
+                ngx_http_time(expires->value.data, ngx_time() + conf->expires);
+
+                if (conf->expires < 0) {
+                    cc->value.len = sizeof("no-cache") - 1;
+                    cc->value.data = (u_char *) "no-cache";
+
+                } else {
+                    cc->value.data = ngx_palloc(r->pool,
+                                          sizeof("max-age=") + TIME_T_LEN + 1);
+                    if (cc->value.data == NULL) {
+                        return NGX_ERROR;
+                    }
+
+                    cc->value.len = ngx_snprintf((char *) cc->value.data,
+                                               sizeof("max-age=") + TIME_T_LEN,
+                                               "max-age=" TIME_T_FMT,
+                                               conf->expires);
+                }
+            }
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_headers_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_headers_filter;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_headers_create_conf(ngx_conf_t *cf)
+{   
+    ngx_http_headers_conf_t  *conf;
+
+    if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_headers_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->expires = NGX_HTTP_EXPIRES_UNSET;
+
+    return conf;
+}
+
+
+static char *ngx_http_headers_merge_conf(ngx_conf_t *cf,
+                                         void *parent, void *child)
+{
+    ngx_http_headers_conf_t *prev = parent;
+    ngx_http_headers_conf_t *conf = child;
+
+    if (conf->expires == NGX_HTTP_EXPIRES_UNSET) {
+        conf->expires = (prev->expires == NGX_HTTP_EXPIRES_UNSET) ?
+                                          NGX_HTTP_EXPIRES_OFF : prev->expires;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_http_headers_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_headers_conf_t *hcf = conf;
+
+    ngx_uint_t   minus;
+    ngx_str_t   *value;
+
+    if (hcf->expires != NGX_HTTP_EXPIRES_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "epoch") == 0) {
+        hcf->expires = NGX_HTTP_EXPIRES_EPOCH;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        hcf->expires = NGX_HTTP_EXPIRES_OFF;
+        return NGX_CONF_OK;
+    }
+
+    if (value[1].data[0] == '+') {
+        value[1].data++;
+        value[1].len--;
+        minus = 0;
+
+    } else if (value[1].data[0] == '-') {
+        value[1].data++;
+        value[1].len--;
+        minus = 1;
+
+    } else {
+        minus = 0;
+    }
+
+    hcf->expires = ngx_parse_time(&value[1], 1);
+    if (hcf->expires == NGX_ERROR) {
+        return "invalid value";
+    }
+    
+    if (hcf->expires == NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 68 years";
+    }
+
+    if (minus) {
+        hcf->expires = - hcf->expires;
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_index_handler.c
@@ -0,0 +1,529 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_array_t             indices;
+    size_t                  max_index_len;
+    ngx_http_cache_hash_t  *index_cache;
+} ngx_http_index_loc_conf_t;
+
+
+typedef struct {
+    ngx_uint_t         index;
+    u_char            *last;
+    ngx_str_t          path;
+    ngx_str_t          redirect;
+    ngx_http_cache_t  *cache;
+    unsigned           tested:1;
+} ngx_http_index_ctx_t;
+
+
+#define NGX_HTTP_DEFAULT_INDEX   "index.html"
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+                                         ngx_http_index_ctx_t *ctx);
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+                                      ngx_http_index_ctx_t *ctx, ngx_err_t err);
+
+static ngx_int_t ngx_http_index_init(ngx_cycle_t *cycle);
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_index_merge_loc_conf(ngx_conf_t *cf,
+                                       void *parent, void *child);
+static char *ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd,
+                                      void *conf);
+
+
+static ngx_command_t  ngx_http_index_commands[] = {
+
+    { ngx_string("index"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_index_set_index,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("index_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE3,
+      ngx_http_set_cache_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_index_loc_conf_t, index_cache),
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_index_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_index_create_loc_conf,        /* create location configration */
+    ngx_http_index_merge_loc_conf          /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_index_module = {
+    NGX_MODULE,
+    &ngx_http_index_module_ctx,            /* module context */
+    ngx_http_index_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_index_init,                   /* init module */
+    NULL                                   /* init child */
+};
+
+
+/*
+ * Try to open the first index file before the test of the directory existence
+ * because the valid requests should be many more than invalid ones.
+ * If open() failed then stat() should be more quickly because some data
+ * is already cached in the kernel.
+ * Besides Win32 has ERROR_PATH_NOT_FOUND (NGX_ENOTDIR).
+ * Unix has ENOTDIR error, although it less helpfull - it shows only
+ * that path contains the usual file in place of the directory.
+ */
+
+ngx_int_t ngx_http_index_handler(ngx_http_request_t *r)
+{
+    u_char                     *name;
+    ngx_fd_t                    fd;
+    ngx_int_t                   rc;
+    ngx_str_t                  *index;
+    ngx_err_t                   err;
+    ngx_log_t                  *log;
+    ngx_http_index_ctx_t       *ctx;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_index_loc_conf_t  *ilcf;
+#if (NGX_HTTP_CACHE0)
+    /* crc must be in ctx !! */
+    uint32_t                    crc;
+#endif
+
+    if (r->uri.data[r->uri.len - 1] != '/') {
+        return NGX_DECLINED;
+    }
+
+    log = r->connection->log;
+
+    /*
+     * we use context because the handler supports an async file opening
+     * and thus can be called several times
+     */
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    ilcf = ngx_http_get_module_loc_conf(r, ngx_http_index_module);
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_index_module);
+    if (ctx == NULL) {
+        ngx_http_create_ctx(r, ctx, ngx_http_index_module,
+                            sizeof(ngx_http_index_ctx_t),
+                            NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+#if (NGX_HTTP_CACHE)
+
+        if (ilcf->index_cache) {
+            ctx->cache = ngx_http_cache_get(ilcf->index_cache, NULL,
+                                            &r->uri, &crc);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                           "http index cache get: " PTR_FMT, ctx->cache);
+
+            if (ctx->cache && !ctx->cache->expired) {
+
+                ctx->cache->accessed = ngx_cached_time;
+
+                ctx->redirect.len = ctx->cache->data.value.len;
+                ctx->redirect.data = ngx_palloc(r->pool, ctx->redirect.len + 1);
+                if (ctx->redirect.data == NULL) {
+                    ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
+                }
+
+                ngx_memcpy(ctx->redirect.data, ctx->cache->data.value.data,
+                           ctx->redirect.len + 1);
+                ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+
+                return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+            }
+        }
+
+#endif
+
+#if 0
+        ctx->path.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len
+                                             + ilcf->max_index_len
+                                             - clcf->alias * clcf->name.len);
+        if (ctx->path.data == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ctx->redirect.data = ngx_cpymem(ctx->path.data, clcf->root.data,
+                                        clcf->root.len);
+#endif
+
+        if (clcf->alias) {
+            ctx->path.data = ngx_palloc(r->pool, clcf->root.len
+                                              + r->uri.len + 1 - clcf->name.len
+                                              + ilcf->max_index_len);
+            if (ctx->path.data == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ctx->redirect.data = ngx_palloc(r->pool, r->uri.len
+                                            + ilcf->max_index_len);
+            if (ctx->redirect.data == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ngx_memcpy(ctx->path.data, clcf->root.data, clcf->root.len);
+
+            ctx->last = ngx_cpystrn(ctx->path.data + clcf->root.len,
+                                    r->uri.data + clcf->name.len,
+                                    r->uri.len + 1 - clcf->name.len);
+
+#if 0
+            /*
+             * aliases usually have trailling "/",
+             * set it in the start of the possible redirect
+             */
+
+            if (*ctx->redirect.data != '/') {
+                ctx->redirect.data--; 
+            }
+#endif
+
+        } else {
+            ctx->path.data = ngx_palloc(r->pool, clcf->root.len + r->uri.len
+                                                 + ilcf->max_index_len);
+            if (ctx->path.data == NULL) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            ctx->redirect.data = ngx_cpymem(ctx->path.data, clcf->root.data,
+                                            clcf->root.len);
+
+            ctx->last = ngx_cpystrn(ctx->redirect.data, r->uri.data,
+                                    r->uri.len + 1);
+        }
+    }
+
+    ctx->path.len = ctx->last - ctx->path.data;
+
+    index = ilcf->indices.elts;
+    for (/* void */; ctx->index < ilcf->indices.nelts; ctx->index++) {
+
+        if (index[ctx->index].data[0] == '/') {
+            name = index[ctx->index].data;
+
+        } else {
+            ngx_memcpy(ctx->last, index[ctx->index].data,
+                       index[ctx->index].len + 1);
+            name = ctx->path.data;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                       "open index \"%s\"", name);
+
+        fd = ngx_open_file(name, NGX_FILE_RDONLY, NGX_FILE_OPEN);
+
+        if (fd == (ngx_fd_t) NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        if (fd == NGX_INVALID_FILE) {
+            err = ngx_errno;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, err,
+                           ngx_open_file_n " %s failed", name);
+
+            if (err == NGX_ENOTDIR) {
+                return ngx_http_index_error(r, ctx, err);
+
+            } else if (err == NGX_EACCES) {
+                return ngx_http_index_error(r, ctx, err);
+            }
+
+            if (!ctx->tested) {
+                rc = ngx_http_index_test_dir(r, ctx);
+
+                if (rc != NGX_OK) {
+                    return rc;
+                }
+
+                ctx->tested = 1;
+            }
+
+            if (err == NGX_ENOENT) {
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, log, err,
+                          ngx_open_file_n " %s failed", name);
+
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+
+        /* STUB: open file cache */
+
+        r->file.name.data = name;
+        r->file.fd = fd;
+
+        if (index[ctx->index].data[0] == '/') {
+            r->file.name.len = index[ctx->index].len;
+            ctx->redirect.len = index[ctx->index].len;
+            ctx->redirect.data = index[ctx->index].data;
+
+        } else {
+            if (clcf->alias) {
+                name = ngx_cpymem(ctx->redirect.data, r->uri.data, r->uri.len);
+                ngx_memcpy(name, index[ctx->index].data,
+                           index[ctx->index].len + 1);
+            }
+
+            ctx->redirect.len = r->uri.len + index[ctx->index].len;
+            r->file.name.len = clcf->root.len + r->uri.len
+                                                - clcf->alias * clcf->name.len
+                                                       + index[ctx->index].len;
+        }
+
+        /**/
+
+
+#if (NGX_HTTP_CACHE)
+
+        if (ilcf->index_cache) {
+
+            if (ctx->cache) {
+                if (ctx->redirect.len == ctx->cache->data.value.len
+                    && ngx_memcmp(ctx->cache->data.value.data,
+                                  ctx->redirect.data, ctx->redirect.len) == 0)
+                {
+                    ctx->cache->accessed = ngx_cached_time;
+                    ctx->cache->updated = ngx_cached_time;
+                    ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+
+                    return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+                }
+            }
+
+            ctx->redirect.len++;
+            ctx->cache = ngx_http_cache_alloc(ilcf->index_cache, ctx->cache,
+                                              NULL, &r->uri, crc,
+                                              &ctx->redirect, log);
+            ctx->redirect.len--;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                           "http index cache alloc: " PTR_FMT, ctx->cache);
+
+            if (ctx->cache) {
+                ctx->cache->fd = NGX_INVALID_FILE;
+                ctx->cache->accessed = ngx_cached_time;
+                ctx->cache->last_modified = 0;
+                ctx->cache->updated = ngx_cached_time;
+                ctx->cache->memory = 1;
+                ngx_http_cache_unlock(ilcf->index_cache, ctx->cache, log);
+            }
+        }
+
+#endif
+
+        return ngx_http_internal_redirect(r, &ctx->redirect, NULL);
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t ngx_http_index_test_dir(ngx_http_request_t *r,
+                                         ngx_http_index_ctx_t *ctx)
+{
+    ngx_err_t  err;
+
+    ctx->path.data[ctx->path.len - 1] = '\0';
+    ctx->path.data[ctx->path.len] = '\0';
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http check dir: \"%s\"", ctx->path.data);
+
+    if (ngx_file_info(ctx->path.data, &r->file.info) == -1) {
+
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            ctx->path.data[ctx->path.len - 1] = '/';
+            return ngx_http_index_error(r, ctx, err);
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, r->connection->log, err,
+                      ngx_file_info_n " %s failed", ctx->path.data);
+
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ctx->path.data[ctx->path.len - 1] = '/';
+
+    if (ngx_is_dir(&r->file.info)) {
+        return NGX_OK;
+    }
+
+    /* THINK: not reached ??? */
+    return ngx_http_index_error(r, ctx, 0);
+}
+
+
+static ngx_int_t ngx_http_index_error(ngx_http_request_t *r,
+                                      ngx_http_index_ctx_t *ctx, ngx_err_t err)
+{
+    if (err == NGX_EACCES) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+                      "\"%s\" is forbidden", ctx->path.data);
+    
+        return NGX_HTTP_FORBIDDEN;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, err,
+                  "\"%s\" is not found", ctx->path.data);
+    return NGX_HTTP_NOT_FOUND;
+}
+
+
+static ngx_int_t ngx_http_index_init(ngx_cycle_t *cycle)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+    h = ngx_push_array(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_index_handler;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_index_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_index_loc_conf_t  *conf;
+
+    ngx_test_null(conf, ngx_palloc(cf->pool, sizeof(ngx_http_index_loc_conf_t)),
+                  NGX_CONF_ERROR);
+
+    ngx_init_array(conf->indices, cf->pool, 3, sizeof(ngx_str_t),
+                   NGX_CONF_ERROR);
+    conf->max_index_len = 0;
+
+    conf->index_cache = NULL;
+
+    return conf;
+}
+
+
+/* TODO: remove duplicate indices */
+
+static char *ngx_http_index_merge_loc_conf(ngx_conf_t *cf,
+                                           void *parent, void *child)
+{
+    ngx_http_index_loc_conf_t  *prev = parent;
+    ngx_http_index_loc_conf_t  *conf = child;
+
+    ngx_uint_t  i;
+    ngx_str_t  *index, *prev_index;
+
+    if (conf->max_index_len == 0) {
+        if (prev->max_index_len != 0) {
+            ngx_memcpy(conf, prev, sizeof(ngx_http_index_loc_conf_t));
+            return NGX_CONF_OK;
+        }
+
+        ngx_test_null(index, ngx_push_array(&conf->indices), NGX_CONF_ERROR);
+        index->len = sizeof(NGX_HTTP_DEFAULT_INDEX) - 1;
+        index->data = (u_char *) NGX_HTTP_DEFAULT_INDEX;
+        conf->max_index_len = sizeof(NGX_HTTP_DEFAULT_INDEX);
+
+        return NGX_CONF_OK;
+    }
+
+    if (prev->max_index_len != 0) {
+
+        prev_index = prev->indices.elts;
+        for (i = 0; i < prev->indices.nelts; i++) {
+            ngx_test_null(index, ngx_push_array(&conf->indices),
+                          NGX_CONF_ERROR);
+            index->len = prev_index[i].len;
+            index->data = prev_index[i].data;
+        }
+    }
+
+    if (conf->max_index_len < prev->max_index_len) {
+        conf->max_index_len = prev->max_index_len;
+    }
+
+    if (conf->index_cache == NULL) {
+        conf->index_cache = prev->index_cache;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+/* TODO: warn about duplicate indices */
+
+static char *ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd,
+                                      void *conf)
+{
+    ngx_http_index_loc_conf_t *ilcf = conf;
+
+    ngx_uint_t  i;
+    ngx_str_t  *index, *value;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] == '/' && ilcf->indices.nelts == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "first index \"%s\" in \"%s\" directive "
+                           "must not be absolute",
+                           value[1].data, cmd->name.data);
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        if (value[i].len == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "index \"%s\" in \"%s\" directive is invalid",
+                               value[1].data, cmd->name.data);
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_test_null(index, ngx_push_array(&ilcf->indices), NGX_CONF_ERROR);
+        index->len = value[i].len;
+        index->data = value[i].data;
+
+        if (ilcf->max_index_len < index->len + 1) {
+            ilcf->max_index_len = index->len + 1;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_not_modified_filter.c
@@ -0,0 +1,85 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_http_module_t  ngx_http_not_modified_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_not_modified_filter_module = {
+    NGX_MODULE,
+    &ngx_http_not_modified_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_not_modified_filter_init,     /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
+{
+    time_t  ims;
+
+    if (r->headers_out.status != NGX_HTTP_OK
+        || r->headers_in.if_modified_since == NULL
+        || r->headers_out.last_modified_time == -1)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ims = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,
+                              r->headers_in.if_modified_since->value.len);
+    
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http ims:%d lm:%d", ims, r->headers_out.last_modified_time);
+
+    /*
+     * I think that the equality of the dates is correcter
+     */
+
+    if (ims != NGX_ERROR && ims == r->headers_out.last_modified_time) {
+        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;
+        r->headers_out.content_type->key.len = 0;
+        r->headers_out.content_type = NULL;
+        r->headers_out.content_length_n = -1;
+        r->headers_out.content_length = NULL;
+#if 0
+        r->headers_out.accept_ranges->key.len = 0;
+#endif
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_not_modified_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_not_modified_header_filter;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_range_filter.c
@@ -0,0 +1,523 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * the single part format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Length: SIZE" CRLF
+ * "Content-Range: bytes START-END/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ *
+ *
+ * the mutlipart format:
+ *
+ * "HTTP/1.0 206 Partial Content" CRLF
+ * ... header ...
+ * "Content-Type: multipart/byteranges; boundary=0123456789" CRLF
+ * CRLF
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START0-END0/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789" CRLF
+ * "Content-Type: image/jpeg" CRLF
+ * "Content-Range: bytes START1-END1/SIZE" CRLF
+ * CRLF
+ * ... data ...
+ * CRLF
+ * "--0123456789--" CRLF
+ */
+
+
+typedef struct {
+    ngx_str_t  boundary_header;
+} ngx_http_range_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_http_module_t  ngx_http_range_header_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_range_header_filter_module = {
+    NGX_MODULE,
+    &ngx_http_range_header_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_range_header_filter_init,     /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_module_t  ngx_http_range_body_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_range_body_filter_module = {
+    NGX_MODULE,
+    &ngx_http_range_body_filter_module_ctx, /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_range_body_filter_init,       /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
+
+
+static ngx_int_t ngx_http_range_header_filter(ngx_http_request_t *r)
+{
+    ngx_int_t                     rc;
+    ngx_uint_t                    boundary, suffix, i;
+    u_char                       *p;
+    size_t                        len;
+    off_t                         start, end;
+    ngx_http_range_t             *range;
+    ngx_http_range_filter_ctx_t  *ctx;
+
+    if (r->http_version < NGX_HTTP_VERSION_10
+        || r->headers_out.status != NGX_HTTP_OK
+        || r->headers_out.content_length_n == -1
+        || !r->filter_allow_ranges)
+    {
+        return ngx_http_next_header_filter(r);
+    }
+
+    if (r->headers_in.range == NULL
+        || r->headers_in.range->value.len < 7
+        || ngx_strncasecmp(r->headers_in.range->value.data, "bytes=", 6) != 0)
+    {
+
+        r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.accept_ranges == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.accept_ranges->key.len = sizeof("Accept-Ranges") - 1;
+        r->headers_out.accept_ranges->key.data = (u_char *) "Accept-Ranges";
+        r->headers_out.accept_ranges->value.len = sizeof("bytes") - 1;
+        r->headers_out.accept_ranges->value.data = (u_char *) "bytes";
+
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_init_array(r->headers_out.ranges, r->pool, 5, sizeof(ngx_http_range_t),
+                   NGX_ERROR);
+
+    rc = 0;
+    range = NULL;
+    p = r->headers_in.range->value.data + 6;
+
+    for ( ;; ) {
+        start = 0;
+        end = 0;
+        suffix = 0;
+
+        while (*p == ' ') { p++; }
+
+        if (*p != '-') {
+            if (*p < '0' || *p > '9') {
+                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+                break;
+            }
+
+            while (*p >= '0' && *p <= '9') {
+                start = start * 10 + *p++ - '0';
+            }
+
+            while (*p == ' ') { p++; }
+
+            if (*p++ != '-') {
+                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+                break;
+            }
+
+            if (start >= r->headers_out.content_length_n) {
+                rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+                break;
+            }
+
+            while (*p == ' ') { p++; }
+
+            if (*p == ',' || *p == '\0') {
+                ngx_test_null(range, ngx_push_array(&r->headers_out.ranges),
+                              NGX_ERROR);
+                range->start = start;
+                range->end = r->headers_out.content_length_n;
+
+                if (*p++ != ',') {
+                    break;
+                }
+
+                continue;
+            }
+
+        } else {
+            suffix = 1;
+            p++;
+        }
+
+        if (*p < '0' || *p > '9') {
+            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            break;
+        }
+
+        while (*p >= '0' && *p <= '9') {
+            end = end * 10 + *p++ - '0';
+        }
+
+        while (*p == ' ') { p++; }
+
+        if (*p != ',' && *p != '\0') {
+            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            break;
+        }
+
+        if (suffix) {
+           start = r->headers_out.content_length_n - end;
+           end = r->headers_out.content_length_n - 1;
+        }
+
+        if (start > end) {
+            rc = NGX_HTTP_RANGE_NOT_SATISFIABLE;
+            break;
+        }
+
+        ngx_test_null(range, ngx_push_array(&r->headers_out.ranges), NGX_ERROR);
+        range->start = start;
+
+        if (end >= r->headers_out.content_length_n) {
+            /*
+             * Download Accelerator sends the last byte position
+             * that equals to the file length
+             */
+            range->end = r->headers_out.content_length_n;
+
+        } else {
+            range->end = end + 1;
+        }
+
+        if (*p++ != ',') {
+            break;
+        }
+    }
+
+    if (rc) {
+
+        /* rc == NGX_HTTP_RANGE_NOT_SATISFIABLE */
+
+        r->headers_out.status = rc;
+        r->headers_out.ranges.nelts = 0;
+
+        r->headers_out.content_range = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.content_range == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
+        r->headers_out.content_range->key.data = (u_char *) "Content-Range";
+
+        r->headers_out.content_range->value.data =
+                                               ngx_palloc(r->pool, 8 + 20 + 1);
+        if (r->headers_out.content_range->value.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.content_range->value.len =
+                ngx_snprintf((char *) r->headers_out.content_range->value.data,
+                             8 + 20 + 1, "bytes */" OFF_T_FMT,
+                             r->headers_out.content_length_n);
+
+        r->headers_out.content_length_n = -1;
+        if (r->headers_out.content_length) {
+            r->headers_out.content_length->key.len = 0;
+            r->headers_out.content_length = NULL;
+        }
+
+        return rc;
+
+    } else {
+        r->headers_out.status = NGX_HTTP_PARTIAL_CONTENT;
+
+        if (r->headers_out.ranges.nelts == 1) {
+
+            r->headers_out.content_range =
+                                        ngx_list_push(&r->headers_out.headers);
+            if (r->headers_out.content_range == NULL) {
+                return NGX_ERROR;
+            }
+
+            r->headers_out.content_range->key.len = sizeof("Content-Range") - 1;
+            r->headers_out.content_range->key.data = (u_char *) "Content-Range";
+
+            ngx_test_null(r->headers_out.content_range->value.data,
+                          ngx_palloc(r->pool, 6 + 20 + 1 + 20 + 1 + 20 + 1),
+                          NGX_ERROR);
+
+            /* "Content-Range: bytes SSSS-EEEE/TTTT" header */
+
+            r->headers_out.content_range->value.len =
+                   ngx_snprintf((char *)
+                                r->headers_out.content_range->value.data,
+                                6 + 20 + 1 + 20 + 1 + 20 + 1,
+                                "bytes " OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT,
+                                range->start, range->end - 1,
+                                r->headers_out.content_length_n);
+
+            r->headers_out.content_length_n = range->end - range->start;
+
+        } else {
+
+#if 0
+            /* TODO: what if no content_type ?? */
+
+            if (!(r->headers_out.content_type =
+                   ngx_http_add_header(&r->headers_out, ngx_http_headers_out)))
+            {
+                return NGX_ERROR;
+            }
+#endif
+
+            ngx_http_create_ctx(r, ctx, ngx_http_range_body_filter_module,
+                                sizeof(ngx_http_range_filter_ctx_t), NGX_ERROR);
+
+            len = 4 + 10 + 2 + 14 + r->headers_out.content_type->value.len
+                                  + 2 + 21 + 1;
+
+            if (r->headers_out.charset.len) {
+                len += 10 + r->headers_out.charset.len;
+            }
+
+            ngx_test_null(ctx->boundary_header.data, ngx_palloc(r->pool, len),
+                          NGX_ERROR);
+
+            boundary = ngx_next_temp_number(0);
+
+            /*
+             * The boundary header of the range:
+             * CRLF
+             * "--0123456789" CRLF
+             * "Content-Type: image/jpeg" CRLF
+             * "Content-Range: bytes "
+             */
+
+            if (r->headers_out.charset.len) {
+                ctx->boundary_header.len =
+                         ngx_snprintf((char *) ctx->boundary_header.data, len,
+                                      CRLF "--%010" NGX_UINT_T_FMT CRLF
+                                      "Content-Type: %s; charset=%s" CRLF
+                                      "Content-Range: bytes ",
+                                      boundary,
+                                      r->headers_out.content_type->value.data,
+                                      r->headers_out.charset.data);
+
+                r->headers_out.charset.len = 0;
+
+            } else {
+                ctx->boundary_header.len =
+                         ngx_snprintf((char *) ctx->boundary_header.data, len,
+                                      CRLF "--%010" NGX_UINT_T_FMT CRLF
+                                      "Content-Type: %s" CRLF
+                                      "Content-Range: bytes ",
+                                      boundary,
+                                      r->headers_out.content_type->value.data);
+            }
+
+            ngx_test_null(r->headers_out.content_type->value.data,
+                          ngx_palloc(r->pool, 31 + 10 + 1),
+                          NGX_ERROR);
+
+            /* "Content-Type: multipart/byteranges; boundary=0123456789" */
+
+            r->headers_out.content_type->value.len =
+                      ngx_snprintf((char *)
+                                   r->headers_out.content_type->value.data,
+                                   31 + 10 + 1,
+                                   "multipart/byteranges; boundary=%010"
+                                   NGX_UINT_T_FMT,
+                                   boundary);
+
+            /* the size of the last boundary CRLF "--0123456789--" CRLF */
+            len = 4 + 10 + 4;
+
+            range = r->headers_out.ranges.elts;
+            for (i = 0; i < r->headers_out.ranges.nelts; i++) {
+                ngx_test_null(range[i].content_range.data,
+                              ngx_palloc(r->pool, 20 + 1 + 20 + 1 + 20 + 5),
+                              NGX_ERROR);
+
+                /* the size of the range: "SSSS-EEEE/TTTT" CRLF CRLF */
+
+                range[i].content_range.len =
+                  ngx_snprintf((char *) range[i].content_range.data,
+                               20 + 1 + 20 + 1 + 20 + 5,
+                               OFF_T_FMT "-" OFF_T_FMT "/" OFF_T_FMT CRLF CRLF,
+                               range[i].start, range[i].end - 1,
+                               r->headers_out.content_length_n);
+
+                len += ctx->boundary_header.len + range[i].content_range.len
+                                    + (size_t) (range[i].end - range[i].start);
+            }
+
+            r->headers_out.content_length_n = len;
+            r->headers_out.content_length = NULL;
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_range_body_filter(ngx_http_request_t *r,
+                                            ngx_chain_t *in)
+{
+    ngx_uint_t                    i;
+    ngx_buf_t                    *b;
+    ngx_chain_t                  *out, *hcl, *rcl, *dcl, **ll;
+    ngx_http_range_t             *range;
+    ngx_http_range_filter_ctx_t  *ctx;
+
+    if (r->headers_out.ranges.nelts == 0) {
+        return ngx_http_next_body_filter(r, in);
+    }
+
+    /*
+     * the optimized version for the static files only
+     * that are passed in the single file buf
+     */
+
+    if (in && in->buf->in_file && in->buf->last_buf) {
+        range = r->headers_out.ranges.elts;
+
+        if (r->headers_out.ranges.nelts == 1) {
+            in->buf->file_pos = range->start;
+            in->buf->file_last = range->end;
+
+            return ngx_http_next_body_filter(r, in);
+        }
+
+        ctx = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
+        ll = &out;
+
+        for (i = 0; i < r->headers_out.ranges.nelts; i++) {
+
+            /*
+             * The boundary header of the range:
+             * CRLF
+             * "--0123456789" CRLF
+             * "Content-Type: image/jpeg" CRLF
+             * "Content-Range: bytes "
+             */
+
+            ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+            b->memory = 1;
+            b->pos = ctx->boundary_header.data;
+            b->last = ctx->boundary_header.data + ctx->boundary_header.len;
+
+            ngx_test_null(hcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+            hcl->buf = b;
+
+            /* "SSSS-EEEE/TTTT" CRLF CRLF */
+
+            ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+            b->temporary = 1;
+            b->pos = range[i].content_range.data;
+            b->last = range[i].content_range.data + range[i].content_range.len;
+
+            ngx_test_null(rcl, ngx_alloc_chain_link(r->pool), NGX_ERROR);
+            rcl->buf = b;
+
+            /* the range data */
+
+            ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+            b->in_file = 1;
+            b->file_pos = range[i].start;
+            b->file_last = range[i].end;
+            b->file = in->buf->file;
+
+            ngx_alloc_link_and_set_buf(dcl, b, r->pool, NGX_ERROR);
+
+            *ll = hcl;
+            hcl->next = rcl;
+            rcl->next = dcl;
+            ll = &dcl->next;
+        }
+
+        /* the last boundary CRLF "--0123456789--" CRLF  */
+
+        ngx_test_null(b, ngx_calloc_buf(r->pool), NGX_ERROR);
+        b->temporary = 1;
+        b->last_buf = 1;
+        ngx_test_null(b->pos, ngx_palloc(r->pool, 4 + 10 + 4), NGX_ERROR);
+        b->last = ngx_cpymem(b->pos, ctx->boundary_header.data, 4 + 10);
+        *b->last++ = '-'; *b->last++ = '-';
+        *b->last++ = CR; *b->last++ = LF;
+
+        ngx_alloc_link_and_set_buf(hcl, b, r->pool, NGX_ERROR);
+        *ll = hcl;
+
+        return ngx_http_next_body_filter(r, out);
+    }
+
+    /* TODO: alert */
+
+    return ngx_http_next_body_filter(r, in);
+}
+
+
+static ngx_int_t ngx_http_range_header_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_range_header_filter;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_range_body_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_body_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_range_body_filter;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_rewrite_handler.c
@@ -0,0 +1,466 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_REWRITE_COPY_MATCH  0
+#define NGX_HTTP_REWRITE_COPY_SHORT  1
+#define NGX_HTTP_REWRITE_COPY_LONG   2
+
+
+typedef struct {
+    ngx_int_t    op;
+    size_t       len;
+    uintptr_t    data;
+} ngx_http_rewrite_op_t;
+
+
+typedef struct {
+    ngx_regex_t  *regex;
+    ngx_uint_t    msize;
+
+    ngx_array_t   ops;
+    ngx_uint_t    size;
+
+    ngx_str_t     re_name;
+    ngx_str_t     s_name;
+
+    ngx_uint_t    status;
+    unsigned      last:1;
+} ngx_http_rewrite_rule_t;
+
+
+typedef struct {
+    ngx_array_t   rules;
+    ngx_flag_t    log;
+} ngx_http_rewrite_srv_conf_t;
+
+
+typedef struct {
+    ngx_str_t     redirect;
+} ngx_http_rewrite_loc_conf_t;
+
+
+static void *ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf,
+                                             void *parent, void *child);
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_rewrite_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf);
+static char *ngx_http_redirect(ngx_conf_t *cf, void *post, void *data);
+static ngx_int_t ngx_http_rewrite_init(ngx_cycle_t *cycle);
+
+
+static ngx_conf_post_handler_pt  ngx_http_redirect_p = ngx_http_redirect;
+
+
+static ngx_command_t  ngx_http_rewrite_commands[] = {
+
+    { ngx_string("rewrite"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_TAKE23,
+      ngx_http_rewrite_rule,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("redirect"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      &ngx_http_redirect_p },
+
+    { ngx_string("rewrite_log"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_rewrite_srv_conf_t, log),
+      NULL },
+
+      ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_rewrite_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_rewrite_create_srv_conf,      /* create server configuration */
+    ngx_http_rewrite_merge_srv_conf,       /* merge server configuration */
+
+    ngx_http_rewrite_create_loc_conf,      /* create location configration */
+    NULL,                                  /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_rewrite_module = {
+    NGX_MODULE,
+    &ngx_http_rewrite_module_ctx,          /* module context */
+    ngx_http_rewrite_commands,             /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_rewrite_init,                 /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_int_t ngx_http_rewrite_handler(ngx_http_request_t *r)
+{
+    int                          *matches;
+    u_char                       *p;
+    size_t                        len;
+    uintptr_t                     data;
+    ngx_int_t                     rc;
+    ngx_uint_t                    i, m, n;
+    ngx_str_t                     uri;
+    ngx_http_rewrite_op_t        *op;
+    ngx_http_rewrite_rule_t      *rule;
+    ngx_http_rewrite_srv_conf_t  *scf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http rewrite handler");
+
+    scf = ngx_http_get_module_srv_conf(r, ngx_http_rewrite_module);
+
+    rule = scf->rules.elts;
+    for (i = 0; i < scf->rules.nelts; i++) {
+
+        if (rule[i].msize) {
+            if (!(matches = ngx_palloc(r->pool, rule[i].msize * sizeof(int)))) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+        } else {
+            matches = NULL;
+        }
+
+        rc = ngx_regex_exec(rule[i].regex, &r->uri, matches, rule[i].msize);
+
+        if (rc == NGX_DECLINED) {
+            if (scf->log) {
+                ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                              "\"%s\" does not match \"%s\"",
+                              rule[i].re_name.data, r->uri.data);
+            }
+
+            continue;
+        }
+
+        if (rc < 0) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          ngx_regex_exec_n
+                          " failed: %d on \"%s\" using \"%s\"",
+                          rc, r->uri.data, rule[i].re_name.data);
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (scf->log) {
+            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                          "\"%s\" matches \"%s\"",
+                          rule[i].re_name.data, r->uri.data);
+        }
+
+        if (rule[i].status) {
+            return rule[i].status;
+        }
+
+        uri.len = rule[i].size;
+
+        for (n = 1; n < (ngx_uint_t) rc; n++) {
+           uri.len += matches[2 * n + 1] - matches[2 * n];
+        }
+
+        if (!(uri.data = ngx_palloc(r->pool, uri.len + 1))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        p = uri.data;
+
+        op = rule[i].ops.elts;
+        for (n = 0; n < rule[i].ops.nelts; n++) {
+            if (op[n].op == NGX_HTTP_REWRITE_COPY_SHORT) {
+                len = op[n].len;
+                data = op[n].data;
+                while (len--) {
+                    *p++ = (char) (data & 0xff);
+                    data >>= 8;
+                }
+
+            } else if (op[n].op == NGX_HTTP_REWRITE_COPY_LONG) {
+                p = ngx_cpymem(p, (void *) op[n].data, op[n].len);
+
+            } else { /* NGX_HTTP_REWRITE_COPY_MATCH */
+                m = 2 * op[n].data;
+                p = ngx_cpymem(p, &r->uri.data[matches[m]],
+                               matches[m + 1] - matches[m]);
+            }
+        }
+
+        *p = '\0';
+
+        if (scf->log) {
+            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
+                          "rewritten uri: \"%s\"", uri.data);
+        }
+
+        r->uri = uri;
+
+        if (ngx_http_set_exten(r) != NGX_OK) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (rule[i].last) {
+            return NGX_DECLINED;
+        }
+    }
+
+    return NGX_DECLINED;
+}
+
+
+static ngx_int_t ngx_http_redirect_handler(ngx_http_request_t *r)
+{
+    u_char                       *p;
+    ngx_http_rewrite_loc_conf_t  *rlcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http redirect handler");
+
+    rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+    r->headers_out.location = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.location == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (rlcf->redirect.data[0] != '/') {
+        r->headers_out.location->key.len = sizeof("Location") - 1;
+        r->headers_out.location->key.data = (u_char *) "Location";
+    }
+
+    r->headers_out.location->value.len =  rlcf->redirect.len
+                                          + r->unparsed_uri.len;
+    r->headers_out.location->value.data = ngx_palloc(r->pool,
+                                           r->headers_out.location->value.len);
+
+    if (r->headers_out.location->value.data == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    p = ngx_cpymem(r->headers_out.location->value.data, rlcf->redirect.data,
+                   rlcf->redirect.len);
+    p = ngx_cpystrn(p, r->unparsed_uri.data + 1, r->unparsed_uri.len);
+
+    return NGX_HTTP_MOVED_TEMPORARILY;
+}
+
+
+static void *ngx_http_rewrite_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_rewrite_srv_conf_t  *conf;
+
+    if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_rewrite_srv_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_init_array(conf->rules, cf->pool, 5, sizeof(ngx_http_rewrite_rule_t),
+                   NGX_CONF_ERROR);
+
+    conf->log = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *ngx_http_rewrite_merge_srv_conf(ngx_conf_t *cf,
+                                             void *parent, void *child)
+{
+    ngx_http_rewrite_srv_conf_t *prev = parent;
+    ngx_http_rewrite_srv_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->log, prev->log, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_rewrite_loc_conf_t  *conf;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    return conf;
+}
+
+
+static char *ngx_http_rewrite_rule(ngx_conf_t *cf, ngx_command_t *cmd,
+                                   void *conf)
+{
+    ngx_http_rewrite_srv_conf_t *scf = conf;
+
+    u_char                   *data, *p;
+    size_t                    len;
+    ngx_str_t                *value, err;
+    ngx_uint_t                i;
+    ngx_http_rewrite_op_t    *op;
+    ngx_http_rewrite_rule_t  *rule;
+    u_char                    errstr[NGX_MAX_CONF_ERRSTR];
+
+    if (!(rule = ngx_push_array(&scf->rules))) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_init_array(rule->ops, cf->pool, 5, sizeof(ngx_http_rewrite_op_t),
+                   NGX_CONF_ERROR);
+
+    rule->msize = 0;
+    rule->size = 0;
+    rule->status = 0;
+    rule->last = 0;
+
+    value = cf->args->elts;
+
+    /* STUB */ {
+        err.len = NGX_MAX_CONF_ERRSTR;
+        err.data = errstr;
+
+        rule->regex = ngx_regex_compile(&value[1], 0, cf->pool, &err);
+    
+        if (rule->regex == NULL) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
+            return NGX_CONF_ERROR;
+        }
+    
+        rule->re_name = value[1];
+        rule->s_name = value[2];
+
+        if (ngx_strcasecmp(value[2].data, "forbidden:") == 0) {
+
+            if (cf->args->nelts == 3) {
+                rule->status = NGX_HTTP_FORBIDDEN;
+                rule->last = 1;
+                return NGX_CONF_OK;
+            }
+
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid parameter \"%s\"", value[3].data);
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; i < value[2].len; /* void */) {
+
+            if (!(op = ngx_push_array(&rule->ops))) {
+                return NGX_CONF_ERROR;
+            }
+
+            data = &value[2].data[i];
+
+            if (value[2].data[i] == '$'
+                && i < value[2].len
+                && value[2].data[i + 1] >= '1'
+                && value[2].data[i + 1] <= '9')
+            {
+                op->op = NGX_HTTP_REWRITE_COPY_MATCH; 
+                op->data = value[2].data[++i] - '0';
+
+                if (rule->msize < op->data) {
+                    rule->msize = op->data;
+                }
+
+                i++;
+
+            } else {
+                i++;
+
+                while (i < value[2].len && value[2].data[i] != '$') {
+                    i++;
+                }
+
+                len = &value[2].data[i] - data;
+                rule->size += len;
+
+                if (len) {
+
+                    op->len = len;
+
+                    if (len <= sizeof(uintptr_t)) {
+                        op->op = NGX_HTTP_REWRITE_COPY_SHORT; 
+                        op->data = 0;
+
+                        while (len--) {
+                            op->data <<= 8;
+                            op->data |= data[len];
+                        }
+
+                    } else {
+                        op->op = NGX_HTTP_REWRITE_COPY_LONG;
+
+                        if (!(p = ngx_palloc(cf->pool, len))) {
+                            return NGX_CONF_ERROR;
+                        }
+
+                        ngx_memcpy(p, data, len);
+                        op->data = (uintptr_t) p;
+                    }
+                }
+            }
+        }
+
+        if (rule->msize) {
+            rule->msize++;
+            rule->msize *= 3;
+        }
+
+        if (cf->args->nelts > 3) {
+            if (ngx_strcmp(value[3].data, "last") == 0) {
+                rule->last = 1;
+
+            } else {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid parameter \"%s\"", value[3].data);
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_redirect(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_redirect_handler;
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_rewrite_init(ngx_cycle_t *cycle)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+    h = ngx_push_array(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_rewrite_handler;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_DEFLAUT_CERTIFICATE      "cert.pem"
+#define NGX_DEFLAUT_CERTIFICATE_KEY  "cert.pem"
+
+
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
+                                         void *parent, void *child);
+
+
+static ngx_command_t  ngx_http_ssl_commands[] = {
+
+    { ngx_string("ssl"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, enable),
+      NULL },
+
+    { ngx_string("ssl_certificate"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, certificate),
+      NULL },
+
+    { ngx_string("ssl_certificate_key"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, certificate_key),
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_ssl_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_http_ssl_create_srv_conf,          /* create server configuration */
+    ngx_http_ssl_merge_srv_conf,           /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_ssl_module = {
+    NGX_MODULE,
+    &ngx_http_ssl_module_ctx,              /* module context */
+    ngx_http_ssl_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_ssl_srv_conf_t  *scf;
+
+    if (!(scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssl_srv_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    scf->enable = NGX_CONF_UNSET;
+
+    return scf;
+}
+
+
+static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf,
+                                         void *parent, void *child)
+{
+    ngx_http_ssl_srv_conf_t *prev = parent;
+    ngx_http_ssl_srv_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, 0);
+
+    if (conf->enable == 0) {
+        return NGX_CONF_OK;
+    }
+
+    ngx_conf_merge_str_value(conf->certificate, prev->certificate,
+                             NGX_DEFLAUT_CERTIFICATE);
+
+    ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key,
+                             NGX_DEFLAUT_CERTIFICATE_KEY);
+
+    /* TODO: configure methods */
+
+    conf->ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+
+    if (conf->ssl_ctx == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0, "SSL_CTX_new() failed");
+        return NGX_CONF_ERROR;
+    }
+
+    if (SSL_CTX_use_certificate_file(conf->ssl_ctx,
+                                     (char *) conf->certificate.data,
+                                     SSL_FILETYPE_PEM) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+                      "SSL_CTX_use_certificate_file(\"%s\") failed",
+                      conf->certificate.data);
+        return NGX_CONF_ERROR;
+    }
+
+    if (SSL_CTX_use_PrivateKey_file(conf->ssl_ctx,
+                                    (char *) conf->certificate_key.data,
+                                    SSL_FILETYPE_PEM) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+                      "SSL_CTX_use_PrivateKey_file(\"%s\") failed",
+                      conf->certificate_key.data);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+#if 0
+
+static ngx_int_t ngx_http_ssl_init_process(ngx_cycle_t *cycle)
+{
+    ngx_uint_t                   i;
+    ngx_http_ssl_srv_conf_t     *sscf;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+    cscfp = cmcf->servers.elts;
+
+    for (i = 0; i < cmcf->servers.nelts; i++) {
+        sscf = cscfp[i]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+        if (sscf->enable) {
+            cscfp[i]->recv = ngx_ssl_recv;
+            cscfp[i]->send_chain = ngx_ssl_send_chain;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_SSL_H_INCLUDED_
+#define _NGX_HTTP_SSL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_flag_t      enable;
+    ngx_str_t       certificate;
+    ngx_str_t       certificate_key;
+
+    ngx_ssl_ctx_t  *ssl_ctx;
+} ngx_http_ssl_srv_conf_t;
+
+
+ngx_int_t ngx_http_ssl_read(ngx_http_request_t *r, u_char *buf, size_t size);
+ngx_int_t ngx_http_ssl_shutdown(ngx_http_request_t *r);
+ngx_chain_t *ngx_http_ssl_write(ngx_connection_t *c, ngx_chain_t *in,
+                                off_t limit);
+
+void ngx_http_ssl_close_connection(SSL *ssl, ngx_log_t *log);
+
+
+extern ngx_module_t  ngx_http_ssl_module;
+
+
+#endif /* _NGX_HTTP_SSL_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_static_handler.c
@@ -0,0 +1,584 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_http_cache_hash_t  *redirect_cache;
+} ngx_http_static_loc_conf_t;
+
+
+static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r);
+static void *ngx_http_static_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_static_merge_loc_conf(ngx_conf_t *cf,
+                                            void *parent, void *child);
+static ngx_int_t ngx_http_static_init(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_http_static_commands[] = {
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("redirect_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE3,
+      ngx_http_set_cache_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_static_loc_conf_t, redirect_cache),
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+
+ngx_http_module_t  ngx_http_static_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+    
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+    
+    ngx_http_static_create_loc_conf,       /* create location configuration */
+    ngx_http_static_merge_loc_conf         /* merge location configuration */
+};  
+
+
+ngx_module_t  ngx_http_static_module = {
+    NGX_MODULE,
+    &ngx_http_static_module_ctx,           /* module context */
+    ngx_http_static_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_static_init,                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+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);
+}
+
+
+static void *ngx_http_static_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_static_loc_conf_t  *conf;
+
+    if (!(conf = ngx_palloc(cf->pool, sizeof(ngx_http_static_loc_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->redirect_cache = NULL;
+
+    return conf;
+}
+
+
+static char *ngx_http_static_merge_loc_conf(ngx_conf_t *cf,
+                                            void *parent, void *child)
+{
+    ngx_http_static_loc_conf_t  *prev = parent;
+    ngx_http_static_loc_conf_t  *conf = child;
+
+    if (conf->redirect_cache == NULL) {
+        conf->redirect_cache = prev->redirect_cache;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static ngx_int_t ngx_http_static_init(ngx_cycle_t *cycle)
+{
+    ngx_http_handler_pt        *h;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+    
+    h = ngx_push_array(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
+    if (h == NULL) {
+        return NGX_ERROR;
+    }
+
+    *h = ngx_http_static_handler;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/ngx_http_userid_filter.c
@@ -0,0 +1,588 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#define NGX_HTTP_USERID_OFF   0
+#define NGX_HTTP_USERID_LOG   1
+#define NGX_HTTP_USERID_V1    2
+#define NGX_HTTP_USERID_ON    3
+
+/* 31 Dec 2037 23:55:55 GMT */
+#define NGX_HTTP_USERID_MAX_EXPIRES  2145916555
+
+
+typedef struct {
+    ngx_flag_t  enable;
+
+    ngx_int_t   service;
+
+    ngx_str_t   name;
+    ngx_str_t   domain;
+    ngx_str_t   path;
+    time_t      expires;
+
+    ngx_int_t   p3p;
+    ngx_str_t   p3p_string;
+} ngx_http_userid_conf_t;
+
+
+typedef struct {
+    uint32_t          uid_got[4];
+    uint32_t          uid_set[4];
+} ngx_http_userid_ctx_t;
+
+
+static ngx_int_t ngx_http_userid_get_uid(ngx_http_request_t *r,
+                                         ngx_http_userid_ctx_t *ctx,
+                                         ngx_http_userid_conf_t *conf);
+static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
+                                         ngx_http_userid_ctx_t *ctx,
+                                         ngx_http_userid_conf_t *conf);
+
+static u_char *ngx_http_userid_log_uid_got(ngx_http_request_t *r, u_char *buf,
+                                           uintptr_t data);
+static u_char *ngx_http_userid_log_uid_set(ngx_http_request_t *r, u_char *buf,
+                                           uintptr_t data);
+
+static ngx_int_t ngx_http_userid_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_http_userid_pre_conf(ngx_conf_t *cf);
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf);
+static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
+                                        void *child);
+char *ngx_conf_check_domain(ngx_conf_t *cf, void *post, void *data);
+char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+static uint32_t  sequencer_v1 = 1;
+static uint32_t  sequencer_v2 = 0x03030302;
+
+
+static u_char expires[] = "; expires=Thu, 31-Dec-37 23:55:55 GMT";
+
+
+static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
+
+
+static ngx_conf_enum_t  ngx_http_userid_state[] = {
+    { ngx_string("off"), NGX_HTTP_USERID_OFF },
+    { ngx_string("log"), NGX_HTTP_USERID_LOG },
+    { ngx_string("v1"), NGX_HTTP_USERID_V1 },
+    { ngx_string("on"), NGX_HTTP_USERID_ON },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_post_handler_pt  ngx_conf_check_domain_p =
+                                                         ngx_conf_check_domain;
+
+
+static ngx_command_t  ngx_http_userid_commands[] = {
+
+    { ngx_string("userid"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, enable),
+      ngx_http_userid_state },
+
+    { ngx_string("userid_service"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, service),
+      NULL },
+
+    { ngx_string("userid_name"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, name),
+      NULL },
+
+    { ngx_string("userid_domain"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, domain),
+      &ngx_conf_check_domain_p },
+
+    { ngx_string("userid_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_userid_conf_t, path),
+      NULL },
+
+    { ngx_string("userid_expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_userid_expires,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_userid_filter_module_ctx = {
+    ngx_http_userid_pre_conf,              /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_userid_create_conf,           /* create location configration */
+    ngx_http_userid_merge_conf             /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_userid_filter_module = {
+    NGX_MODULE,
+    &ngx_http_userid_filter_module_ctx,    /* module context */
+    ngx_http_userid_commands,              /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_userid_init,                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_http_log_op_name_t ngx_http_userid_log_fmt_ops[] = {
+    { ngx_string("uid_got"), 0, ngx_http_userid_log_uid_got },
+    { ngx_string("uid_set"), 0, ngx_http_userid_log_uid_set },
+    { ngx_null_string, 0, NULL }
+};
+
+
+static ngx_int_t ngx_http_userid_filter(ngx_http_request_t *r)
+{
+    ngx_int_t                rc;
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+    if (conf->enable == NGX_HTTP_USERID_OFF) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    ngx_http_create_ctx(r, ctx, ngx_http_userid_filter_module,
+                        sizeof(ngx_http_userid_ctx_t), NGX_ERROR);
+
+    rc = ngx_http_userid_get_uid(r, ctx, conf);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    if (conf->enable == NGX_HTTP_USERID_LOG || ctx->uid_got[3] != 0) {
+        return ngx_http_next_header_filter(r);
+    }
+
+    rc = ngx_http_userid_set_uid(r, ctx, conf);
+
+    if (rc != NGX_OK) {
+        return rc;
+    }
+
+    return ngx_http_next_header_filter(r);
+}
+
+
+static ngx_int_t ngx_http_userid_get_uid(ngx_http_request_t *r,
+                                         ngx_http_userid_ctx_t *ctx,
+                                         ngx_http_userid_conf_t *conf)
+{
+    u_char            *start, *last, *end;
+    ngx_uint_t         i;
+    ngx_str_t          src, dst;
+    ngx_table_elt_t  **cookies;
+
+    cookies = r->headers_in.cookies.elts;
+
+    for (i = 0; i < r->headers_in.cookies.nelts; i++) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "cookie: \"%s\"", cookies[i]->value.data);
+
+        end = cookies[i]->value.data + cookies[i]->value.len;
+
+        for (start = cookies[i]->value.data; start < end; /* void */) {
+
+            if (conf->name.len >= cookies[i]->value.len
+                || ngx_strncmp(start, conf->name.data, conf->name.len) != 0)
+            {
+                start += conf->name.len;
+                while (start < end && *start++ != ';') { /* void */ }
+
+                for (/* void */; start < end && *start == ' '; start++) { /**/ }
+
+                continue;
+            }
+
+            for (start += conf->name.len; start < end && *start == ' '; start++)
+            {
+                /* void */
+            }
+
+            if (*start != '=') {
+                break;
+            }
+
+            for (start++; start < end && *start == ' '; start++) { /* void */ }
+
+            for (last = start; last < end && *last != ';'; last++) { /**/ }
+
+            if (last - start < 22) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "client sent too short userid cookie \"%s\"",
+                              cookies[i]->value.data);
+                break;
+            }
+
+            /*
+             * we have to limit encoded string to 22 characters
+             * because there are already the millions cookies with a garbage
+             * instead of the correct base64 trail "=="
+             */
+
+            src.len = 22;
+            src.data = start;
+            dst.data = (u_char *) ctx->uid_got;
+
+            if (ngx_decode_base64(&src, &dst) == NGX_ERROR) {
+                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                              "client sent invalid userid cookie \"%s\"",
+                              cookies[i]->value.data);
+                break;
+            }
+
+            ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "uid: %08X%08X%08X%08X",
+                           ctx->uid_got[0], ctx->uid_got[1],
+                           ctx->uid_got[2], ctx->uid_got[3]);
+
+            return NGX_OK;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_userid_set_uid(ngx_http_request_t *r,
+                                         ngx_http_userid_ctx_t *ctx,
+                                         ngx_http_userid_conf_t *conf)
+
+{
+    u_char              *cookie, *p;
+    size_t               len;
+    socklen_t            slen;
+    struct sockaddr_in   addr_in;
+    ngx_str_t            src, dst;
+    ngx_table_elt_t     *set_cookie;
+
+    /* TODO: mutex for sequencers */
+
+    if (conf->enable == NGX_HTTP_USERID_V1) {
+        if (conf->service == NGX_CONF_UNSET) {
+            ctx->uid_set[0] = 0;
+        } else {
+            ctx->uid_set[0] = htonl(conf->service);
+        }
+
+        ctx->uid_set[1] = ngx_time();
+        ctx->uid_set[2] = ngx_pid;
+        ctx->uid_set[3] = sequencer_v1;
+        sequencer_v1 += 0x100;
+
+    } else {
+        if (conf->service == NGX_CONF_UNSET) {
+            if (r->in_addr == 0) {
+                slen = sizeof(struct sockaddr_in);
+                if (getsockname(r->connection->fd,
+                                (struct sockaddr *) &addr_in, &slen) == -1)
+                {
+                    ngx_log_error(NGX_LOG_CRIT, r->connection->log,
+                                  ngx_socket_errno,
+                                  "getsockname() failed");
+                }
+
+                r->in_addr = addr_in.sin_addr.s_addr;
+            }
+
+            ctx->uid_set[0] = htonl(r->in_addr);
+
+        } else {
+            ctx->uid_set[0] = htonl(conf->service);
+        }
+
+        ctx->uid_set[1] = htonl(ngx_time());
+        ctx->uid_set[2] = htonl(ngx_pid);
+        ctx->uid_set[3] = htonl(sequencer_v2);
+        sequencer_v2 += 0x100;
+        if (sequencer_v2 < 0x03030302) {
+            sequencer_v2 = 0x03030302;
+        }
+    }
+
+    len = conf->name.len + 1 + ngx_base64_encoded_length(16) + 1;
+
+    if (conf->expires) {
+        len += sizeof(expires) - 1 + 2;
+    }
+
+    if (conf->domain.len > 1) {
+        len += sizeof("; domain=") - 1 + conf->domain.len;
+    }
+
+    if (conf->path.len) {
+        len += sizeof("; path=") - 1 + conf->path.len;
+    }
+
+    if (!(cookie = ngx_palloc(r->pool, len))) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(cookie, conf->name.data, conf->name.len);
+    *p++ = '=';
+
+    src.len = 16;
+    src.data = (u_char *) ctx->uid_set;
+    dst.data = p;
+
+    ngx_encode_base64(&src, &dst);
+
+    p += dst.len;
+
+    if (conf->expires == NGX_HTTP_USERID_MAX_EXPIRES) {
+        p = ngx_cpymem(p, expires, sizeof(expires) - 1);
+
+    } else if (conf->expires) {
+        p = ngx_cpymem(p, expires, sizeof("; expires=") - 1);
+        p += ngx_http_cookie_time(p, ngx_time() + conf->expires);
+    }
+
+    if (conf->domain.len > 1) {
+        p = ngx_cpymem(p, "; domain=", sizeof("; domain=") - 1);
+        p = ngx_cpymem(p, conf->domain.data, conf->domain.len);
+    }
+
+    if (conf->path.len) {
+        p = ngx_cpymem(p, "; path=", sizeof("; path=") - 1);
+        p = ngx_cpymem(p, conf->path.data, conf->path.len);
+    }
+
+    *p = '\0';
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "uid cookie: \"%s\"", cookie);
+
+    if (!(set_cookie = ngx_list_push(&r->headers_out.headers))) {
+        return NGX_ERROR;
+    }
+
+    set_cookie->key.len = sizeof("Set-Cookie") - 1;
+    set_cookie->key.data = (u_char *) "Set-Cookie";
+    set_cookie->value.len = p - cookie;
+    set_cookie->value.data = cookie;
+
+    return NGX_OK;
+}
+
+
+static u_char *ngx_http_userid_log_uid_got(ngx_http_request_t *r, u_char *buf,
+                                           uintptr_t data)
+{
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+    if (ctx == NULL || ctx->uid_got[3] == 0) {
+        if (buf == NULL) {
+            return (u_char *) 1;
+        }
+
+        *buf = '-';
+        return buf + 1;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+    if (buf == NULL) {
+        return (u_char *) (conf->name.len + 1 + 32);
+    }
+
+    buf = ngx_cpymem(buf, conf->name.data, conf->name.len);
+
+    *buf++ = '=';
+
+    return buf + ngx_snprintf((char *) buf, 33, "%08X%08X%08X%08X",
+                              ctx->uid_got[0], ctx->uid_got[1],
+                              ctx->uid_got[2], ctx->uid_got[3]);
+}
+
+
+static u_char *ngx_http_userid_log_uid_set(ngx_http_request_t *r, u_char *buf,
+                                           uintptr_t data)
+{
+    ngx_http_userid_ctx_t   *ctx;
+    ngx_http_userid_conf_t  *conf;
+
+    ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
+
+    if (ctx == NULL || ctx->uid_set[3] == 0) {
+        if (buf == NULL) {
+            return (u_char *) 1;
+        }
+
+        *buf = '-';
+        return buf + 1;
+    }
+
+    conf = ngx_http_get_module_loc_conf(r, ngx_http_userid_filter_module);
+
+    if (buf == NULL) {
+        return (u_char *) (conf->name.len + 1 + 32);
+    }
+
+    buf = ngx_cpymem(buf, conf->name.data, conf->name.len);
+
+    *buf++ = '=';
+
+    return buf + ngx_snprintf((char *) buf, 33, "%08X%08X%08X%08X",
+                              ctx->uid_set[0], ctx->uid_set[1],
+                              ctx->uid_set[2], ctx->uid_set[3]);
+}
+
+
+static ngx_int_t ngx_http_userid_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_header_filter = ngx_http_top_header_filter;
+    ngx_http_top_header_filter = ngx_http_userid_filter;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_userid_pre_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_op_name_t  *op;
+
+    for (op = ngx_http_userid_log_fmt_ops; op->name.len; op++) { /* void */ }
+    op->op = NULL;
+
+    op = ngx_http_log_fmt_ops;
+
+    for (op = ngx_http_log_fmt_ops; op->op; op++) {
+        if (op->name.len == 0) {
+            op = (ngx_http_log_op_name_t *) op->op;
+        }
+    }
+
+    op->op = (ngx_http_log_op_pt) ngx_http_userid_log_fmt_ops;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_userid_create_conf(ngx_conf_t *cf)
+{   
+    ngx_http_userid_conf_t  *conf;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_userid_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    /* set by ngx_pcalloc():
+
+    conf->name.len = 0;
+    conf->name.date = NULL;
+    conf->domain.len = 0;
+    conf->domain.date = NULL;
+    conf->path.len = 0;
+    conf->path.date = NULL;
+
+    */
+
+    conf->enable = NGX_CONF_UNSET;
+    conf->service = NGX_CONF_UNSET;
+    conf->expires = NGX_CONF_UNSET;
+
+    return conf;
+}   
+
+
+static char *ngx_http_userid_merge_conf(ngx_conf_t *cf, void *parent,
+                                        void *child)
+{
+    ngx_http_userid_conf_t *prev = parent;
+    ngx_http_userid_conf_t *conf = child;
+
+    ngx_conf_merge_value(conf->enable, prev->enable, NGX_HTTP_USERID_OFF);
+
+    ngx_conf_merge_str_value(conf->name, prev->name, "uid");
+    ngx_conf_merge_str_value(conf->domain, prev->domain, ".");
+    ngx_conf_merge_str_value(conf->path, prev->path, "/");
+
+    ngx_conf_merge_value(conf->service, prev->service, NGX_CONF_UNSET);
+    ngx_conf_merge_sec_value(conf->expires, prev->expires, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_conf_check_domain(ngx_conf_t *cf, void *post, void *data)
+{
+    ngx_str_t  *domain = data;
+
+    if (domain->len == 4 && ngx_strcmp(domain->data, "none") == 0) {
+        domain->len = 1;
+        domain->data = (u_char *) ".";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+char *ngx_http_userid_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_userid_conf_t *ucf = conf;
+
+    ngx_str_t   *value;
+
+    if (ucf->expires != NGX_CONF_UNSET) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "max") == 0) {
+        ucf->expires = NGX_HTTP_USERID_MAX_EXPIRES;
+        return NGX_CONF_OK;
+    }
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        ucf->expires = 0;
+        return NGX_CONF_OK;
+    }
+
+    ucf->expires = ngx_parse_time(&value[1], 1);
+    if (ucf->expires == NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (ucf->expires == NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 68 years";
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_cache.c
@@ -0,0 +1,629 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
+                                                  int rc);
+static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p);
+
+
+int ngx_http_proxy_get_cached_response(ngx_http_proxy_ctx_t *p)
+{
+    char                            *last;
+    ngx_http_request_t              *r;
+    ngx_http_proxy_cache_t          *c;
+    ngx_http_proxy_upstream_conf_t  *u;
+
+    r = p->request;
+
+    if (!(c = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_cache_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    p->cache = c;
+
+    c->ctx.file.fd = NGX_INVALID_FILE;
+    c->ctx.file.log = r->connection->log;
+    c->ctx.path = p->lcf->cache_path;
+
+    u = p->lcf->upstream;
+
+    c->ctx.key.len = u->url.len + r->uri.len - u->location->len + r->args.len;
+    if (!(c->ctx.key.data = ngx_palloc(r->pool, c->ctx.key.len + 1))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    last = ngx_cpymem(c->ctx.key.data, u->url.data, u->url.len);
+
+    last = ngx_cpymem(last, r->uri.data + u->location->len,
+                      r->uri.len - u->location->len);
+
+    if (r->args.len > 0) {
+        *(last++) = '?';
+        last = ngx_cpymem(last, r->args.data, r->args.len);
+    }
+    *last = '\0';
+
+    p->header_in = ngx_create_temp_hunk(r->pool, p->lcf->header_buffer_size);
+    if (p->header_in == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+    p->header_in->tag = (ngx_hunk_tag_t) &ngx_http_proxy_module;
+
+    c->ctx.buf = p->header_in; 
+    c->ctx.log = r->connection->log;
+
+    return ngx_http_proxy_process_cached_response(p,
+                                          ngx_http_cache_get_file(r, &c->ctx));
+}
+
+
+static int ngx_http_proxy_process_cached_response(ngx_http_proxy_ctx_t *p,
+                                                  int rc)
+{
+    if (rc == NGX_OK) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_HIT;
+        p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+
+        if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        p->valid_header_in = 1;
+
+        return ngx_http_proxy_send_cached_response(p);
+    }
+
+    if (rc == NGX_HTTP_CACHE_STALE) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_EXPR;
+
+    } else if (rc == NGX_HTTP_CACHE_AGED) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_AGED;
+    }
+
+    if (rc == NGX_HTTP_CACHE_STALE || rc == NGX_HTTP_CACHE_AGED) {
+        p->state->expired = ngx_time() - p->cache->ctx.expires;
+        p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+
+        if (ngx_http_proxy_process_cached_header(p) == NGX_ERROR) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+        p->header_in->last = p->header_in->pos;
+
+        p->stale = 1;
+        p->valid_header_in = 1;
+
+    } else if (rc == NGX_DECLINED) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_MISS;
+        p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+        p->header_in->last = p->header_in->pos;
+    }
+
+    if (p->lcf->busy_lock) {
+        p->try_busy_lock = 1;
+
+        p->header_in->pos = p->header_in->start;
+        p->header_in->last = p->header_in->start;
+
+        p->busy_lock.time = 0;
+        p->busy_lock.event = p->request->connection->read;
+        p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler;
+        p->busy_lock.md5 = p->cache->ctx.md5;
+
+        ngx_http_proxy_cache_busy_lock(p);
+        return NGX_DONE;
+    }
+
+    return ngx_http_proxy_request_upstream(p);
+}
+
+
+static int ngx_http_proxy_process_cached_header(ngx_http_proxy_ctx_t *p)
+{
+    int                      rc, i;
+    ngx_table_elt_t         *h;
+    ngx_http_request_t      *r;
+    ngx_http_proxy_cache_t  *c;
+
+    rc = ngx_http_proxy_parse_status_line(p);
+
+    c = p->cache;
+    r = p->request;
+
+    if (rc == NGX_AGAIN) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "\"proxy_header_buffer_size\" "
+                      "is too small to read header from \"%s\"",
+                      c->ctx.file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "no valid HTTP/1.0 header in \"%s\"",
+                      c->ctx.file.name.data);
+        return NGX_ERROR;
+    }
+
+    /* rc == NGX_OK */
+
+    c->status = p->status;
+    c->status_line.len = p->status_end - p->status_start;
+    c->status_line.data = ngx_palloc(r->pool, c->status_line.len + 1);
+    if (c->status_line.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    /* reset for the possible parsing the upstream header */
+
+    p->status = 0;
+    p->status_count = 0;
+
+    ngx_cpystrn(c->status_line.data, p->status_start, c->status_line.len + 1);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http cache status %d \"%s\"", 
+                   c->status, c->status_line.data);
+
+    /* TODO: ngx_init_table */
+    c->headers_in.headers = ngx_create_table(r->pool, 20);
+
+    for ( ;; ) {
+        rc = ngx_http_parse_header_line(r, p->header_in);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            h = ngx_http_add_header(&c->headers_in, ngx_http_proxy_headers_in);
+            if (h == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_palloc(r->pool,
+                                     h->key.len + 1 + h->value.len + 1);
+            if (h->key.data == NULL) {
+                return NGX_ERROR;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            ngx_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
+            ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
+
+            for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) {
+                if (ngx_http_proxy_headers_in[i].name.len != h->key.len) {
+                    continue;
+                }
+
+                if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data,
+                                                             h->key.data) == 0)
+                {
+                    *((ngx_table_elt_t **) ((char *) &c->headers_in
+                                   + ngx_http_proxy_headers_in[i].offset)) = h;
+                    break;
+                }
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http cache header: \"%s: %s\"",
+                           h->key.data, h->value.data);
+
+            continue;
+
+        } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http cache header done");
+
+            c->ctx.file_start = p->header_in->pos - p->header_in->start;
+
+            return NGX_OK;
+
+        } else if (rc == NGX_HTTP_PARSE_INVALID_HEADER) {
+
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "invalid header in \"%s\"",
+                          c->ctx.file.name.data);
+            return NGX_ERROR;
+        }
+
+        /* rc == NGX_AGAIN || rc == NGX_HTTP_PARSE_TOO_LONG_HEADER */
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "\"proxy_header_buffer_size\" "
+                      "is too small to read header from \"%s\"",
+                      c->ctx.file.name.data);
+        return NGX_ERROR;
+    }
+}
+
+
+void ngx_http_proxy_cache_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+    int  rc, ft_type;
+
+    rc = ngx_http_busy_lock_cachable(p->lcf->busy_lock, &p->busy_lock,
+                                     p->try_busy_lock);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+                   "http cache busy lock cachable: %d", rc);
+
+    if (rc == NGX_OK) {
+        if (p->try_busy_lock) {
+            p->busy_locked = 1;
+            p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+            p->header_in->last = p->header_in->pos;
+
+            ngx_http_proxy_request_upstream(p);
+            return;
+        }
+
+        ngx_http_proxy_cache_look_complete_request(p);
+        return;
+    }
+
+    p->try_busy_lock = 0;
+
+    if (p->cache->ctx.file.fd != NGX_INVALID_FILE
+        && !p->cache->ctx.file.info_valid)
+    {
+        if (ngx_fd_info(p->cache->ctx.file.fd, &p->cache->ctx.file.info)
+                                                             == NGX_FILE_ERROR)
+        {
+            ngx_log_error(NGX_LOG_CRIT, p->request->connection->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed",
+                          p->cache->ctx.file.name.data);
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        p->cache->ctx.file.info_valid = 1;
+    }
+
+    if (rc == NGX_AGAIN) {
+
+        if ((ngx_event_flags & (NGX_USE_CLEAR_EVENT|NGX_HAVE_KQUEUE_EVENT))
+            && !p->request->connection->write->active)
+        {
+            /*
+             * kqueue allows to detect when client closes prematurely
+             * connection
+             */
+
+            p->request->connection->write->event_handler =
+                                        ngx_http_proxy_check_broken_connection;
+
+            if (ngx_add_event(p->request->connection->write, NGX_WRITE_EVENT,
+                                                NGX_CLEAR_EVENT) == NGX_ERROR)
+            {
+                ngx_http_proxy_finalize_request(p,
+                                                NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+        }
+
+        return;
+    }
+
+    ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+    if (rc == NGX_DONE) {
+        ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+
+    } else {
+        /* rc == NGX_ERROR */
+        ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+    }
+    
+    if (p->stale && (p->lcf->use_stale & ft_type)) {
+        ngx_http_proxy_finalize_request(p,
+                                        ngx_http_proxy_send_cached_response(p));
+        return;
+    }
+    
+    p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+    ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+
+static void ngx_http_proxy_cache_look_complete_request(ngx_http_proxy_ctx_t *p)
+{
+    int                    rc;
+    ngx_http_cache_ctx_t  *ctx;
+
+    if (!(ctx = ngx_pcalloc(p->request->pool, sizeof(ngx_http_cache_ctx_t)))) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    *ctx = p->cache->ctx;
+
+    rc = ngx_http_cache_open_file(ctx, ngx_file_uniq(&p->cache->ctx.file.info));
+
+    if (rc == NGX_DECLINED || rc == NGX_HTTP_CACHE_THE_SAME) {
+        p->try_busy_lock = 1;
+        p->busy_lock.time = 0;
+        ngx_http_proxy_cache_busy_lock(p);
+        return;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+                   "http cache old fd:%d, new fd:%d",
+                   p->cache->ctx.file.fd, ctx->file.fd);
+
+    if (p->cache->ctx.file.fd != NGX_INVALID_FILE) {
+        if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, p->request->connection->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          p->cache->ctx.file.name.data);
+        }
+    }
+
+    p->cache->ctx = *ctx;
+
+    p->status = 0;
+    p->status_count = 0;
+
+    ngx_http_proxy_finalize_request(p,
+                                ngx_http_proxy_process_cached_response(p, rc));
+}
+
+
+int ngx_http_proxy_send_cached_response(ngx_http_proxy_ctx_t *p)
+{
+    int                  rc, len, i;
+    off_t                rest;
+    ngx_hunk_t          *h0, *h1;
+    ngx_chain_t          out[2];
+    ngx_http_request_t  *r;
+
+    r = p->request;
+
+    r->headers_out.status = p->cache->status;
+
+#if 0
+    r->headers_out.content_length_n = -1;
+    r->headers_out.content_length = NULL;
+#endif
+
+    /* copy an cached header to r->headers_out */
+    
+    if (ngx_http_proxy_copy_header(p, &p->cache->headers_in) == NGX_ERROR) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    /* we need to allocate all before the header would be sent */
+
+    len = p->header_in->end - (p->header_in->start + p->cache->ctx.file_start);
+
+    h0 = NULL;
+    h1 = NULL;
+
+    if (len) {
+        if (!((h0 = ngx_calloc_hunk(r->pool)))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (!((h0->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    if (len < p->cache->ctx.length) {
+        if (!((h1 = ngx_calloc_hunk(r->pool)))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (!((h1->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t))))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+    }
+
+    rc = ngx_http_send_header(r);
+
+    /* NEEDED ??? */ p->header_sent = 1;
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    rest = p->cache->ctx.length;
+
+    if (len) {
+        if (p->valid_header_in) {
+            h0->pos = p->header_in->start + p->cache->ctx.file_start;
+
+            if (len > p->cache->ctx.length) {
+                h0->last = h0->pos + p->cache->ctx.length;
+
+            } else {
+                h0->last = p->header_in->end;
+            }
+
+            h0->type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
+        }
+
+        h0->type |= NGX_HUNK_FILE;
+        h0->file_pos = p->cache->ctx.file_start;
+
+        h0->file->fd = p->cache->ctx.file.fd;
+        h0->file->log = r->connection->log;
+
+        if (len > p->cache->ctx.length) {
+            h0->file_last = h0->file_pos + p->cache->ctx.length;
+            rest = 0;
+
+        } else {
+            h0->file_last = h0->file_pos + len;
+            rest -= len;
+        }
+
+        out[0].hunk = h0;
+        out[0].next = &out[1];
+        i = 0;
+
+    } else {
+        i = -1;
+    }
+
+    if (rest) {
+        h1->file_pos = p->cache->ctx.file_start + len;
+        h1->file_last = h1->file_pos + rest;
+        h1->type = NGX_HUNK_FILE;
+
+        h1->file->fd = p->cache->ctx.file.fd;
+        h1->file->log = r->connection->log;
+
+        out[++i].hunk = h1;
+    }
+
+    out[i].next = NULL;
+    if (!r->main) {
+        out[i].hunk->type |= NGX_HUNK_LAST;
+    }
+
+    r->file.fd = p->cache->ctx.file.fd;
+
+    return ngx_http_output_filter(r, out);
+}
+
+
+int ngx_http_proxy_is_cachable(ngx_http_proxy_ctx_t *p)
+{
+    time_t                        date, last_modified, expires, t;
+    ngx_http_proxy_headers_in_t  *h;
+
+    switch (p->upstream->status) {
+    case NGX_HTTP_OK:
+    case NGX_HTTP_MOVED_PERMANENTLY:
+    case NGX_HTTP_MOVED_TEMPORARILY:
+        break;
+
+#if 0
+    case NGX_HTTP_NOT_MODIFIED:
+        return 1;
+#endif
+
+    default:
+        return 0;
+    }
+
+    h = &p->upstream->headers_in;
+
+    date = NGX_ERROR;
+    if (h->date) {
+        date = ngx_http_parse_time(h->date->value.data, h->date->value.len);
+    }
+    if (date == NGX_ERROR) {
+        date = ngx_time();
+    }
+    p->cache->ctx.date = date;
+
+    last_modified = NGX_ERROR;
+    if (h->last_modified) {
+        last_modified = ngx_http_parse_time(h->last_modified->value.data,
+                                            h->last_modified->value.len);
+        p->cache->ctx.last_modified = last_modified;
+    }
+
+    if (h->x_accel_expires) {
+        expires = ngx_atoi(h->x_accel_expires->value.data,
+                           h->x_accel_expires->value.len);
+        if (expires != NGX_ERROR) {
+            p->state->reason = NGX_HTTP_PROXY_CACHE_XAE;
+            p->state->expires = expires;
+            p->cache->ctx.expires = date + expires;
+            return (expires > 0);
+        }
+    }
+
+    if (!p->lcf->ignore_expires) {
+
+        /* TODO: Cache-Control: no-cache, max-age= */
+
+        if (h->expires) {
+            expires = ngx_http_parse_time(h->expires->value.data,
+                                          h->expires->value.len);
+            if (expires != NGX_ERROR) {
+                p->state->reason = NGX_HTTP_PROXY_CACHE_EXP;
+                p->state->expires = expires - date;
+                p->cache->ctx.expires = expires;
+                return (date < expires);
+            }
+        }
+    }
+
+    if (p->upstream->status == NGX_HTTP_MOVED_PERMANENTLY) {
+        p->state->reason = NGX_HTTP_PROXY_CACHE_MVD;
+        p->state->expires = /* STUB: 1 hour */ 60 * 60;
+        p->cache->ctx.expires = /* STUB: 1 hour */ 60 * 60;
+        return 1;
+    }
+
+    if (p->upstream->status == NGX_HTTP_MOVED_TEMPORARILY) {
+        return 1;
+    }
+
+    if (last_modified != NGX_ERROR && p->lcf->lm_factor > 0) {
+
+        /* FIXME: time_t == int_64_t, we can use fpu */ 
+
+        p->state->reason = NGX_HTTP_PROXY_CACHE_LMF;
+        t = (time_t)
+              ((((int64_t) (date - last_modified)) * p->lcf->lm_factor) / 100);
+        p->state->expires = t;
+        p->cache->ctx.expires = ngx_time() + t;
+        return 1;
+    }
+
+    if (p->lcf->default_expires > 0) {
+        p->state->reason = NGX_HTTP_PROXY_CACHE_PDE;
+        p->state->expires = p->lcf->default_expires;
+        p->cache->ctx.expires = ngx_time() + p->lcf->default_expires;
+        return 1;
+    }
+
+    return 0;
+}
+
+
+int ngx_http_proxy_update_cache(ngx_http_proxy_ctx_t *p)
+{
+    ngx_event_pipe_t  *ep;
+
+    if (p->cache == NULL) {
+        return NGX_OK;
+    }
+
+    ep = p->upstream->event_pipe;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+                   "http cache update len: " OFF_T_FMT ":" OFF_T_FMT,
+                   p->cache->ctx.length, ep->read_length);
+
+    if (p->cache->ctx.length == -1) {
+        /* TODO: test rc */
+        ngx_write_file(&ep->temp_file->file,
+                       (char *) &ep->read_length, sizeof(off_t),
+                       offsetof(ngx_http_cache_header_t, length));
+    }
+
+    return ngx_http_cache_update_file(p->request, &p->cache->ctx,
+                                      &ep->temp_file->file.name);
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_handler.c
@@ -0,0 +1,1280 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r);
+
+static u_char *ngx_http_proxy_log_proxy_state(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data);
+static u_char *ngx_http_proxy_log_cache_state(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data);
+static u_char *ngx_http_proxy_log_reason(ngx_http_request_t *r, u_char *buf,
+                                         uintptr_t data);
+
+static ngx_int_t ngx_http_proxy_pre_conf(ngx_conf_t *cf);
+static void *ngx_http_proxy_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf,
+                                           void *parent, void *child);
+
+static char *ngx_http_proxy_set_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+                                     void *conf);
+static char *ngx_http_proxy_parse_upstream(ngx_str_t *url,
+                                           ngx_http_proxy_upstream_conf_t *u);
+
+
+static ngx_conf_bitmask_t  next_upstream_masks[] = {
+    { ngx_string("error"), NGX_HTTP_PROXY_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_PROXY_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_PROXY_FT_INVALID_HEADER },
+    { ngx_string("http_500"), NGX_HTTP_PROXY_FT_HTTP_500 },
+    { ngx_string("http_404"), NGX_HTTP_PROXY_FT_HTTP_404 },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_bitmask_t  use_stale_masks[] = {
+    { ngx_string("error"), NGX_HTTP_PROXY_FT_ERROR },
+    { ngx_string("timeout"), NGX_HTTP_PROXY_FT_TIMEOUT },
+    { ngx_string("invalid_header"), NGX_HTTP_PROXY_FT_INVALID_HEADER },
+    { ngx_string("http_500"), NGX_HTTP_PROXY_FT_HTTP_500 },
+    { ngx_string("busy_lock"), NGX_HTTP_PROXY_FT_BUSY_LOCK },
+    { ngx_string("max_waiting"), NGX_HTTP_PROXY_FT_MAX_WAITING },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_num_bounds_t  ngx_http_proxy_lm_factor_bounds = {
+    ngx_conf_check_num_bounds, 0, 100
+};
+
+
+static ngx_command_t  ngx_http_proxy_commands[] = {
+
+    { ngx_string("proxy_pass"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_proxy_set_pass,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("proxy_connect_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, connect_timeout),
+      NULL },
+
+    { ngx_string("proxy_send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, send_timeout),
+      NULL },
+
+    { ngx_string("proxy_preserve_host"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, preserve_host),
+      NULL },
+
+    { ngx_string("proxy_set_x_real_ip"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, set_x_real_ip),
+      NULL },
+
+    { ngx_string("proxy_add_x_forwarded_for"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, add_x_forwarded_for),
+      NULL },
+
+    { ngx_string("proxy_header_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, header_buffer_size),
+      NULL },
+
+    { ngx_string("proxy_read_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, read_timeout),
+      NULL },
+
+    { ngx_string("proxy_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, bufs),
+      NULL },
+
+    { ngx_string("proxy_busy_buffers_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, busy_buffers_size),
+      NULL },
+
+#if (NGX_HTTP_FILE_CACHE)
+
+    { ngx_string("proxy_cache_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, cache_path),
+      ngx_garbage_collector_http_cache_handler },
+
+#endif
+
+    { ngx_string("proxy_temp_path"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234,
+      ngx_conf_set_path_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, temp_path),
+      (void *) ngx_garbage_collector_temp_handler },
+
+    { ngx_string("proxy_temp_file_write_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, temp_file_write_size),
+      NULL },
+
+    { ngx_string("proxy_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, cache),
+      NULL },
+
+
+    { ngx_string("proxy_busy_lock"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE13,
+      ngx_http_set_busy_lock_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, busy_lock),
+      NULL },
+
+
+    { ngx_string("proxy_pass_server"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, pass_server),
+      NULL },
+
+    { ngx_string("proxy_pass_x_accel_expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, pass_x_accel_expires),
+      NULL },
+
+    { ngx_string("proxy_ignore_expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, ignore_expires),
+      NULL },
+
+    { ngx_string("proxy_lm_factor"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, lm_factor),
+      &ngx_http_proxy_lm_factor_bounds },
+
+    { ngx_string("proxy_default_expires"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_sec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, default_expires),
+      NULL },
+
+
+    { ngx_string("proxy_next_upstream"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, next_upstream),
+      &next_upstream_masks },
+
+    { ngx_string("proxy_use_stale"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_ANY,
+      ngx_conf_set_bitmask_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, use_stale),
+      &use_stale_masks },
+
+      ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_proxy_module_ctx = {
+    ngx_http_proxy_pre_conf,               /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_proxy_create_loc_conf,        /* create location configration */
+    ngx_http_proxy_merge_loc_conf          /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_proxy_module = {
+    NGX_MODULE,
+    &ngx_http_proxy_module_ctx,            /* module context */
+    ngx_http_proxy_commands,               /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+
+static ngx_http_log_op_name_t ngx_http_proxy_log_fmt_ops[] = {
+    { ngx_string("proxy"), /* STUB */ 100,
+                           ngx_http_proxy_log_proxy_state },
+    { ngx_string("proxy_cache_state"), sizeof("BYPASS") - 1,
+                                       ngx_http_proxy_log_cache_state },
+    { ngx_string("proxy_reason"), sizeof("BPS") - 1,
+                                  ngx_http_proxy_log_reason },
+    { ngx_null_string, 0, NULL }
+};
+
+
+
+ngx_http_header_t ngx_http_proxy_headers_in[] = {
+    { ngx_string("Date"), offsetof(ngx_http_proxy_headers_in_t, date) },
+    { ngx_string("Server"), offsetof(ngx_http_proxy_headers_in_t, server) },
+
+    { ngx_string("Expires"), offsetof(ngx_http_proxy_headers_in_t, expires) },
+    { ngx_string("Cache-Control"),
+                        offsetof(ngx_http_proxy_headers_in_t, cache_control) },
+    { ngx_string("ETag"), offsetof(ngx_http_proxy_headers_in_t, etag) },
+    { ngx_string("X-Accel-Expires"),
+                      offsetof(ngx_http_proxy_headers_in_t, x_accel_expires) },
+
+    { ngx_string("Connection"),
+                           offsetof(ngx_http_proxy_headers_in_t, connection) },
+    { ngx_string("Content-Type"),
+                         offsetof(ngx_http_proxy_headers_in_t, content_type) },
+    { ngx_string("Content-Length"),
+                       offsetof(ngx_http_proxy_headers_in_t, content_length) },
+    { ngx_string("Last-Modified"),
+                        offsetof(ngx_http_proxy_headers_in_t, last_modified) },
+    { ngx_string("Location"),
+                             offsetof(ngx_http_proxy_headers_in_t, location) },
+    { ngx_string("Accept-Ranges"),
+                        offsetof(ngx_http_proxy_headers_in_t, accept_ranges) },
+    { ngx_string("X-Pad"), offsetof(ngx_http_proxy_headers_in_t, x_pad) },
+
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t cache_states[] = {
+    ngx_string("PASS"),
+    ngx_string("BYPASS"),
+    ngx_string("AUTH"),
+    ngx_string("PGNC"),
+    ngx_string("MISS"),
+    ngx_string("EXPR"),
+    ngx_string("AGED"),
+    ngx_string("HIT")
+};
+
+
+static ngx_str_t cache_reasons[] = {
+    ngx_string("BPS"),
+    ngx_string("XAE"),
+    ngx_string("CTL"),
+    ngx_string("EXP"),
+    ngx_string("MVD"),
+    ngx_string("LMF"),
+    ngx_string("PDE")
+};
+
+
+static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r)
+{
+    ngx_http_proxy_ctx_t  *p;
+
+    ngx_http_create_ctx(r, p, ngx_http_proxy_module,
+                        sizeof(ngx_http_proxy_ctx_t),
+                        NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    p->lcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
+    p->request = r;
+
+    /* TODO: we currently support reverse proxy only */
+    p->accel = 1;
+
+    ngx_init_array(p->states, r->pool, p->lcf->peers->number,
+                   sizeof(ngx_http_proxy_state_t),
+                   NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    if (!(p->state = ngx_push_array(&p->states))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    ngx_memzero(p->state, sizeof(ngx_http_proxy_state_t));
+
+#if (NGX_HTTP_FILE_CACHE)
+
+    if (!p->lcf->cache
+        || (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD))
+    {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_PASS;
+
+    } else if (r->bypass_cache) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_BYPASS;
+
+    } else if (r->headers_in.authorization) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_AUTH;
+
+    } else if (r->no_cache) {
+        p->state->cache_state = NGX_HTTP_PROXY_CACHE_PGNC;
+        p->cachable = 1;
+
+    } else {
+        p->cachable = 1;
+    }
+
+
+    if (p->state->cache_state != 0) {
+        return ngx_http_proxy_request_upstream(p);
+    }
+
+    return ngx_http_proxy_get_cached_response(p);
+
+#else
+
+    p->state->cache_state = NGX_HTTP_PROXY_CACHE_PASS;
+
+    return ngx_http_proxy_request_upstream(p);
+
+#endif
+}
+
+
+void ngx_http_proxy_check_broken_connection(ngx_event_t *ev)
+{
+    int                    n;
+    char                   buf[1];
+    ngx_err_t              err;
+    ngx_connection_t      *c;
+    ngx_http_request_t    *r;
+    ngx_http_proxy_ctx_t  *p;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                   "http proxy check client, write event:%d", ev->write);
+
+#if (HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+
+        if (!ev->pending_eof) {
+            return;
+        }
+
+        c = ev->data;
+        r = c->data;
+        p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+        ev->eof = 1;
+
+        if (ev->kq_errno) {
+            ev->error = 1;
+        }
+
+        if (!p->cachable && p->upstream->peer.connection) {
+            ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+                          "kevent() reported that client closed "
+                          "prematurely connection, "
+                          "so upstream connection is closed too");
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+            return;
+        }
+
+        ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+                      "kevent() reported that client closed "
+                      "prematurely connection");
+
+        if (p->upstream == NULL || p->upstream->peer.connection == NULL) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        }
+
+        return;
+    }
+
+#endif
+
+    c = ev->data;
+    r = c->data;
+    p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+
+    n = recv(c->fd, buf, 1, MSG_PEEK);
+
+    err = ngx_socket_errno;
+
+    /*
+     * we do not need to disable the write event because
+     * that event has NGX_USE_CLEAR_EVENT type
+     */
+
+    if (ev->write && (n >= 0 || err == NGX_EAGAIN)) {
+        return;
+    }
+
+    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+        if (ngx_del_event(ev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    if (n > 0) {
+        return;
+    }
+
+    ev->eof = 1;
+
+    if (n == -1) {
+        if (err == NGX_EAGAIN) {
+            return;
+        }
+
+        ev->error = 1;
+
+    } else {
+        /* n == 0 */
+        err = 0;
+    }
+
+    if (!p->cachable && p->upstream->peer.connection) {
+        ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                      "client closed prematurely connection, "
+                      "so upstream connection is closed too");
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, ev->log, err,
+                  "client closed prematurely connection");
+
+    if (p->upstream == NULL || p->upstream->peer.connection == NULL) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+    }
+}
+
+
+void ngx_http_proxy_busy_lock_handler(ngx_event_t *rev)
+{
+    ngx_connection_t      *c;
+    ngx_http_request_t    *r;
+    ngx_http_proxy_ctx_t  *p;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http proxy busy lock");
+
+    c = rev->data;
+    r = c->data;
+    p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+    p->action = "waiting upstream in busy lock";
+
+    if (p->request->connection->write->eof) {
+        ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    if (rev->timedout) {
+        rev->timedout = 0;
+        p->busy_lock.time++;
+        p->state->bl_time = p->busy_lock.time;
+
+#if (NGX_HTTP_FILE_CACHE)
+
+        if (p->state->cache_state < NGX_HTTP_PROXY_CACHE_MISS) {
+            ngx_http_proxy_upstream_busy_lock(p);
+
+        } else {
+            ngx_http_proxy_cache_busy_lock(p);
+        }
+#else
+
+        ngx_http_proxy_upstream_busy_lock(p);
+
+#endif
+
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http proxy: client sent while busy lock");
+
+    /*
+     * TODO: kevent() notify about error, otherwise we need to
+     * call ngx_peek(): recv(MSG_PEEK) to get errno. THINK about aio.
+     * if there's no error we need to disable event.
+     */
+
+#if 0
+#if (HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) && rev->kq_eof) {
+        ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+        ngx_del_timer(rev);
+
+        ngx_log_error(NGX_LOG_ERR, c->log, rev->kq_errno,
+                      "client() closed connection");
+
+        if (ngx_del_event(rev, NGX_READ_EVENT, NGX_CLOSE_EVENT) == NGX_ERROR) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+#endif
+#endif
+
+}
+
+
+void ngx_http_proxy_finalize_request(ngx_http_proxy_ctx_t *p, int rc)
+{
+    ngx_http_request_t  *r;
+
+    r = p->request;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "finalize http proxy request");
+
+    if (p->upstream && p->upstream->peer.connection) {
+        ngx_http_proxy_close_connection(p);
+    }
+
+    if (p->header_sent
+        && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE))
+    {
+        rc = 0;
+    }
+
+    if (p->saved_ctx) {
+        r->connection->log->data = p->saved_ctx;
+        r->connection->log->handler = p->saved_handler;
+    }
+
+    if (p->upstream && p->upstream->event_pipe) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http proxy temp fd: %d",
+                       p->upstream->event_pipe->temp_file->file.fd);
+    }
+
+    if (p->cache) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http proxy cache fd: %d",
+                       p->cache->ctx.file.fd);
+    }
+
+    if (p->upstream && p->upstream->event_pipe) {
+        r->file.fd = p->upstream->event_pipe->temp_file->file.fd;
+
+    } else if (p->cache) {
+        r->file.fd = p->cache->ctx.file.fd;
+    }
+
+    if (rc == 0 && r->main == NULL) {
+        rc = ngx_http_send_last(r);
+    }
+
+    ngx_http_finalize_request(r, rc);
+}
+
+
+void ngx_http_proxy_close_connection(ngx_http_proxy_ctx_t *p)
+{
+    ngx_socket_t       fd;
+    ngx_connection_t  *c;
+
+    c = p->upstream->peer.connection;
+    p->upstream->peer.connection = NULL;
+
+    if (p->lcf->busy_lock) {
+        p->lcf->busy_lock->busy--;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http proxy close connection: %d", c->fd);
+
+    if (c->fd == -1) {
+#if 0
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "connection already closed");
+#endif
+        return;
+    }
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    /* TODO: move connection to the connection pool */
+
+    if (ngx_del_conn) {
+        ngx_del_conn(c, NGX_CLOSE_EVENT);
+
+    } else {
+        if (c->read->active || c->read->disabled) {
+            ngx_del_event(c->read, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+        }
+
+        if (c->write->active || c->read->disabled) {
+            ngx_del_event(c->write, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
+        }
+    }
+
+    /*
+     * we have to clean the connection information before the closing
+     * because another thread may reopen the same file descriptor
+     * before we clean the connection
+     */
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_OK) {
+
+        if (c->read->prev) {
+            ngx_delete_posted_event(c->read);
+        }
+
+        if (c->write->prev) {
+            ngx_delete_posted_event(c->write);
+        }
+
+        c->read->closed = 1;
+        c->write->closed = 1;
+
+        ngx_mutex_unlock(ngx_posted_events_mutex);
+    }
+
+    fd = c->fd;
+    c->fd = (ngx_socket_t) -1;
+    c->data = NULL;
+
+    if (ngx_close_socket(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno,
+                      ngx_close_socket_n " failed");
+    }
+}
+
+
+size_t ngx_http_proxy_log_error(void *data, char *buf, size_t len)
+{
+    ngx_http_proxy_log_ctx_t *ctx = data;
+
+    ngx_http_request_t     *r;
+    ngx_peer_connection_t  *peer;
+
+    r = ctx->proxy->request;
+    peer = &ctx->proxy->upstream->peer;
+
+    return ngx_snprintf(buf, len,
+                        " while %s, client: %s, URL: %s, upstream: %s%s%s%s%s",
+                        ctx->proxy->action,
+                        r->connection->addr_text.data,
+                        r->unparsed_uri.data,
+                        peer->peers->peers[peer->cur_peer].addr_port_text.data,
+                        ctx->proxy->lcf->upstream->uri.data,
+                        r->uri.data + ctx->proxy->lcf->upstream->location->len,
+                        r->args.len ? "?" : "",
+                        r->args.len ? r->args.data : (u_char *) "");
+}
+
+
+static u_char *ngx_http_proxy_log_proxy_state(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *p;
+
+    p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+    if (p == NULL) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+    if (p->state->cache_state == 0) {
+        *buf++ = '-';
+
+    } else {
+        buf = ngx_cpymem(buf, cache_states[p->state->cache_state - 1].data,
+                         cache_states[p->state->cache_state - 1].len);
+    }
+
+    *buf++ = '/';
+
+    if (p->state->expired == 0) {
+        *buf++ = '-';
+
+    } else {
+        buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+                            TIME_T_FMT, p->state->expired);
+    }
+
+    *buf++ = '/';
+
+    if (p->state->bl_time == 0) {
+        *buf++ = '-';
+
+    } else {
+        buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+                            TIME_T_FMT, p->state->bl_time);
+    }
+
+    *buf++ = '/';
+
+    *buf++ = '*';
+
+    *buf++ = ' ';
+
+    if (p->state->status == 0) {
+        *buf++ = '-';
+
+    } else {
+        buf += ngx_snprintf((char *) buf, 4, "%" NGX_UINT_T_FMT,
+                            p->state->status);
+    }
+
+    *buf++ = '/';
+
+    if (p->state->reason == 0) {
+        *buf++ = '-';
+
+    } else {
+        buf = ngx_cpymem(buf, cache_reasons[p->state->reason - 1].data,
+                         cache_reasons[p->state->reason - 1].len);
+    }
+
+    *buf++ = '/';
+
+    if (p->state->reason < NGX_HTTP_PROXY_CACHE_XAE) {
+        *buf++ = '-';
+
+    } else {
+        buf += ngx_snprintf((char *) buf, TIME_T_LEN,
+                            TIME_T_FMT, p->state->expires);
+    }
+
+    *buf++ = ' ';
+    *buf++ = '*';
+
+    return buf;
+}
+
+
+static u_char *ngx_http_proxy_log_cache_state(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *p;
+
+    p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+    if (p == NULL || p->state->cache_state == 0) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+    return ngx_cpymem(buf, cache_states[p->state->cache_state - 1].data,
+                      cache_states[p->state->cache_state - 1].len);
+}
+
+
+static u_char *ngx_http_proxy_log_reason(ngx_http_request_t *r, u_char *buf,
+                                         uintptr_t data)
+{
+    ngx_http_proxy_ctx_t  *p;
+
+    p = ngx_http_get_module_err_ctx(r, ngx_http_proxy_module);
+
+    if (p == NULL || p->state->reason == 0) {
+        *buf = '-';
+        return buf + 1;
+    }
+
+    return ngx_cpymem(buf, cache_reasons[p->state->reason - 1].data,
+                      cache_reasons[p->state->reason - 1].len);
+}
+
+
+static ngx_int_t ngx_http_proxy_pre_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_op_name_t  *op;
+
+    for (op = ngx_http_proxy_log_fmt_ops; op->name.len; op++) { /* void */ }
+    op->op = NULL;
+
+    op = ngx_http_log_fmt_ops;
+
+    for (op = ngx_http_log_fmt_ops; op->op; op++) {
+        if (op->name.len == 0) {
+            op = (ngx_http_log_op_name_t *) op->op;
+        }
+    }
+
+    op->op = (ngx_http_log_op_pt) ngx_http_proxy_log_fmt_ops;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_proxy_loc_conf_t  *conf;
+
+    ngx_test_null(conf,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_loc_conf_t)),
+                  NGX_CONF_ERROR);
+
+    /* set by ngx_pcalloc():
+
+    conf->bufs.num = 0;
+
+    conf->path = NULL;
+
+    conf->next_upstream = 0;
+    conf->use_stale = 0;
+
+    conf->upstreams = NULL;
+    conf->peers = NULL;
+
+    conf->cache_path = NULL;
+    conf->temp_path = NULL;
+
+    conf->busy_lock = NULL;
+
+    */
+
+    conf->connect_timeout = NGX_CONF_UNSET_MSEC;
+    conf->send_timeout = NGX_CONF_UNSET_MSEC;
+
+    conf->preserve_host = NGX_CONF_UNSET;
+    conf->set_x_real_ip = NGX_CONF_UNSET;
+    conf->add_x_forwarded_for = NGX_CONF_UNSET;
+
+    conf->header_buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->read_timeout = NGX_CONF_UNSET_MSEC;
+    conf->busy_buffers_size = NGX_CONF_UNSET_SIZE;
+
+    /*
+     * "proxy_max_temp_file_size" is hardcoded to 1G for reverse proxy,
+     * it should be configurable in the generic proxy
+     */
+    conf->max_temp_file_size = 1024 * 1024 * 1024;
+
+    conf->temp_file_write_size = NGX_CONF_UNSET_SIZE;
+
+    /* "proxy_cyclic_temp_file" is disabled */
+    conf->cyclic_temp_file = 0;
+
+    conf->cache = NGX_CONF_UNSET;
+
+    conf->pass_server = NGX_CONF_UNSET;
+    conf->pass_x_accel_expires = NGX_CONF_UNSET;
+    conf->ignore_expires = NGX_CONF_UNSET;
+    conf->lm_factor = NGX_CONF_UNSET;
+    conf->default_expires = NGX_CONF_UNSET;
+
+    return conf;
+}
+
+
+static char *ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf,
+                                           void *parent, void *child)
+{
+    ngx_http_proxy_loc_conf_t *prev = parent;
+    ngx_http_proxy_loc_conf_t *conf = child;
+
+    size_t   size;
+
+    ngx_conf_merge_msec_value(conf->connect_timeout,
+                              prev->connect_timeout, 60000);
+    ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+
+    ngx_conf_merge_value(conf->preserve_host, prev->preserve_host, 0);
+    ngx_conf_merge_value(conf->set_x_real_ip, prev->set_x_real_ip, 0);
+    ngx_conf_merge_value(conf->add_x_forwarded_for,
+                         prev->add_x_forwarded_for, 0);
+
+    ngx_conf_merge_msec_value(conf->read_timeout, prev->read_timeout, 60000);
+
+    ngx_conf_merge_size_value(conf->header_buffer_size,
+                              prev->header_buffer_size, (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 8, ngx_pagesize);
+
+    if (conf->bufs.num < 2) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "there must be at least 2 \"proxy_buffers\"");
+        return NGX_CONF_ERROR;
+    }
+
+    size = conf->header_buffer_size;
+    if (size < conf->bufs.size) {
+        size = conf->bufs.size;
+    }
+
+
+    ngx_conf_merge_size_value(conf->busy_buffers_size,
+                              prev->busy_buffers_size, NGX_CONF_UNSET_SIZE);
+
+    if (conf->busy_buffers_size == NGX_CONF_UNSET_SIZE) {
+        conf->busy_buffers_size = 2 * size;
+
+    } else if (conf->busy_buffers_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_busy_buffers_size\" must be equal or bigger than "
+             "maximum of the value of \"proxy_header_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+
+    } else if (conf->busy_buffers_size > (conf->bufs.num - 1) * conf->bufs.size)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_busy_buffers_size\" must be less than "
+             "the size of all \"proxy_buffers\" minus one buffer");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->temp_file_write_size,
+                              prev->temp_file_write_size, NGX_CONF_UNSET_SIZE);
+
+    if (conf->temp_file_write_size == NGX_CONF_UNSET_SIZE) {
+        conf->temp_file_write_size = 2 * size;
+
+    } else if (conf->temp_file_write_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_temp_file_write_size\" must be equal or bigger than "
+             "maximum of the value of \"proxy_header_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_size_value(conf->max_temp_file_size,
+                              prev->max_temp_file_size, NGX_CONF_UNSET_SIZE);
+
+    if (conf->max_temp_file_size == NGX_CONF_UNSET_SIZE) {
+        conf->max_temp_file_size = 2 * size;
+
+    } else if (conf->max_temp_file_size < size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+             "\"proxy_max_temp_file_size\" must be equal or bigger than "
+             "maximum of the value of \"proxy_header_buffer_size\" and "
+             "one of the \"proxy_buffers\"");
+
+        return NGX_CONF_ERROR;
+    }
+
+
+    ngx_conf_merge_bitmask_value(conf->next_upstream, prev->next_upstream,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_HTTP_PROXY_FT_ERROR
+                                  |NGX_HTTP_PROXY_FT_TIMEOUT));
+
+    ngx_conf_merge_bitmask_value(conf->use_stale, prev->use_stale,
+                                 NGX_CONF_BITMASK_SET);
+
+    ngx_conf_merge_path_value(conf->cache_path, prev->cache_path,
+                              "cache", 1, 2, 0, cf->pool);
+
+    ngx_conf_merge_path_value(conf->temp_path, prev->temp_path,
+                              "temp", 1, 2, 0, cf->pool);
+
+    ngx_conf_merge_value(conf->cache, prev->cache, 0);
+
+
+    /* conf->cache must be merged */
+
+    if (conf->busy_lock == NULL) {
+        conf->busy_lock = prev->busy_lock;
+    }
+
+    if (conf->busy_lock && conf->cache && conf->busy_lock->md5 == NULL) {
+
+        /* ngx_calloc_shared() */
+        conf->busy_lock->md5_mask =
+                     ngx_pcalloc(cf->pool, (conf->busy_lock->max_busy + 7) / 8);
+        if (conf->busy_lock->md5_mask == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        /* 16 bytes are 128 bits of the md5 */
+
+        /* ngx_alloc_shared() */
+        conf->busy_lock->md5 = ngx_palloc(cf->pool,
+                                          16 * conf->busy_lock->max_busy);
+        if (conf->busy_lock->md5 == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+
+    ngx_conf_merge_value(conf->pass_server, prev->pass_server, 0);
+    ngx_conf_merge_value(conf->pass_x_accel_expires,
+                         prev->pass_x_accel_expires, 0);
+    ngx_conf_merge_value(conf->ignore_expires, prev->ignore_expires, 0);
+    ngx_conf_merge_value(conf->lm_factor, prev->lm_factor, 0);
+    ngx_conf_merge_sec_value(conf->default_expires, prev->default_expires, 0);
+
+    return NULL;
+}
+
+
+
+static char *ngx_http_proxy_set_pass(ngx_conf_t *cf, ngx_command_t *cmd,
+                                     void *conf)
+{
+    ngx_http_proxy_loc_conf_t *lcf = conf;
+
+    ngx_uint_t                 i, len;
+    char                      *err;
+    u_char                    *host;
+    in_addr_t                  addr;
+    ngx_str_t                 *value;
+    struct hostent            *h;
+    ngx_http_core_loc_conf_t  *clcf;
+
+
+    value = cf->args->elts;
+
+    if (ngx_strncasecmp(value[1].data, "http://", 7) != 0) {
+        return "invalid URL prefix";
+    }
+
+    ngx_test_null(lcf->upstream,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_proxy_upstream_conf_t)),
+                  NGX_CONF_ERROR);
+
+    lcf->upstream->url.len = value[1].len;
+    if (!(lcf->upstream->url.data = ngx_palloc(cf->pool, value[1].len + 1))) {
+        return NGX_CONF_ERROR;
+    }
+    ngx_cpystrn(lcf->upstream->url.data, value[1].data, value[1].len + 1);
+
+    value[1].data += 7;
+    value[1].len -= 7;
+
+    err = ngx_http_proxy_parse_upstream(&value[1], lcf->upstream);
+
+    if (err) {
+        return err;
+    }
+
+    ngx_test_null(host, ngx_palloc(cf->pool, lcf->upstream->host.len + 1),
+                  NGX_CONF_ERROR);
+    ngx_cpystrn(host, lcf->upstream->host.data, lcf->upstream->host.len + 1);
+
+    /* AF_INET only */
+
+    addr = inet_addr((char *) host);
+
+    if (addr == INADDR_NONE) {
+        h = gethostbyname((char *) host);
+
+        if (h == NULL || h->h_addr_list[0] == NULL) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "host %s not found", host);
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; h->h_addr_list[i] != NULL; i++) { /* void */ }
+
+        /* MP: ngx_shared_palloc() */
+
+        ngx_test_null(lcf->peers,
+                      ngx_pcalloc(cf->pool,
+                                  sizeof(ngx_peers_t)
+                                  + sizeof(ngx_peer_t) * (i - 1)),
+                      NGX_CONF_ERROR);
+
+        lcf->peers->number = i;
+
+        for (i = 0; h->h_addr_list[i] != NULL; i++) {
+            lcf->peers->peers[i].host.data = host;
+            lcf->peers->peers[i].host.len = lcf->upstream->host.len;
+            lcf->peers->peers[i].addr = *(in_addr_t *)(h->h_addr_list[i]);
+            lcf->peers->peers[i].port = lcf->upstream->port;
+
+            len = INET_ADDRSTRLEN + lcf->upstream->port_text.len + 1;
+            ngx_test_null(lcf->peers->peers[i].addr_port_text.data,
+                          ngx_palloc(cf->pool, len),
+                          NGX_CONF_ERROR);
+
+            len = ngx_inet_ntop(AF_INET,
+                                &lcf->peers->peers[i].addr,
+                                lcf->peers->peers[i].addr_port_text.data,
+                                len);
+
+            lcf->peers->peers[i].addr_port_text.data[len++] = ':';
+
+            ngx_cpystrn(lcf->peers->peers[i].addr_port_text.data + len,
+                        lcf->upstream->port_text.data,
+                        lcf->upstream->port_text.len + 1);
+
+            lcf->peers->peers[i].addr_port_text.len =
+                                        len + lcf->upstream->port_text.len + 1;
+        }
+
+    } else {
+
+        /* MP: ngx_shared_palloc() */
+
+        ngx_test_null(lcf->peers, ngx_pcalloc(cf->pool, sizeof(ngx_peers_t)),
+                      NGX_CONF_ERROR);
+
+        lcf->peers->number = 1;
+
+        lcf->peers->peers[0].host.data = host;
+        lcf->peers->peers[0].host.len = lcf->upstream->host.len;
+        lcf->peers->peers[0].addr = addr;
+        lcf->peers->peers[0].port = lcf->upstream->port;
+
+        len = lcf->upstream->host.len + lcf->upstream->port_text.len + 1;
+
+        ngx_test_null(lcf->peers->peers[0].addr_port_text.data,
+                      ngx_palloc(cf->pool, len + 1),
+                      NGX_CONF_ERROR);
+
+        len = lcf->upstream->host.len;
+
+        ngx_memcpy(lcf->peers->peers[0].addr_port_text.data,
+                   lcf->upstream->host.data, len);
+
+        lcf->peers->peers[0].addr_port_text.data[len++] = ':';
+
+        ngx_cpystrn(lcf->peers->peers[0].addr_port_text.data + len,
+                    lcf->upstream->port_text.data,
+                    lcf->upstream->port_text.len + 1);
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+    lcf->upstream->location = &clcf->name;
+    clcf->handler = ngx_http_proxy_handler;
+
+    if (clcf->name.data[clcf->name.len - 1] == '/') {
+        clcf->auto_redirect = 1;
+    }
+
+    return NULL;
+}
+
+
+static char *ngx_http_proxy_parse_upstream(ngx_str_t *url,
+                                           ngx_http_proxy_upstream_conf_t *u)
+{
+    size_t  i;
+
+    if (url->data[0] == ':' || url->data[0] == '/') {
+        return "invalid upstream URL";
+    }
+
+    u->host.data = url->data;
+    u->host_header.data = url->data;
+
+    for (i = 1; i < url->len; i++) {
+        if (url->data[i] == ':') {
+            u->port_text.data = &url->data[i] + 1;
+            u->host.len = i;
+        }
+
+        if (url->data[i] == '/') {
+            u->uri.data = &url->data[i];
+            u->uri.len = url->len - i;
+            u->host_header.len = i;
+
+            if (u->host.len == 0) {
+                u->host.len = i;
+            }
+
+            if (u->port_text.data == NULL) {
+                u->default_port = 1;
+                u->port = htons(80);
+                u->port_text.len = 2;
+                u->port_text.data = (u_char *) "80";
+                return NULL;
+            }
+
+            u->port_text.len = &url->data[i] - u->port_text.data;
+
+            if (u->port_text.len > 0) {
+                u->port = (in_port_t) ngx_atoi(u->port_text.data,
+                                               u->port_text.len);
+                if (u->port > 0) {
+
+                    if (u->port == 80) {
+                        u->default_port = 1;
+                    }
+
+                    u->port = htons(u->port);
+                    return NULL;
+                }
+            }
+
+            return "invalid port in upstream URL";
+        }
+    }
+
+    if (u->host.len == 0) {
+        u->host.len = i;
+    }
+
+    u->host_header.len = i;
+
+    u->uri.data = (u_char *) "/";
+    u->uri.len = 1;
+
+    if (u->port_text.data == NULL) {
+        u->default_port = 1;
+        u->port = htons(80);
+        u->port_text.len = 2;
+        u->port_text.data = (u_char *) "80";
+        return NULL;
+    }
+
+    u->port_text.len = &url->data[i] - u->port_text.data;
+
+    if (u->port_text.len > 0) {
+        u->port = (in_port_t) ngx_atoi(u->port_text.data, u->port_text.len);
+        if (u->port > 0) {
+            u->port = htons(u->port);
+            return NULL;
+        }
+    }
+
+    return "invalid port in upstream URL";
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_handler.h
@@ -0,0 +1,260 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_PROXY_HANDLER_H_INCLUDED_
+#define _NGX_HTTP_PROXY_HANDLER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_event_pipe.h>
+#include <ngx_http.h>
+
+
+typedef enum {
+    NGX_HTTP_PROXY_CACHE_PASS = 1,
+    NGX_HTTP_PROXY_CACHE_BYPASS,
+    NGX_HTTP_PROXY_CACHE_AUTH,
+    NGX_HTTP_PROXY_CACHE_PGNC,
+    NGX_HTTP_PROXY_CACHE_MISS,
+    NGX_HTTP_PROXY_CACHE_EXPR,
+    NGX_HTTP_PROXY_CACHE_AGED,
+    NGX_HTTP_PROXY_CACHE_HIT
+} ngx_http_proxy_state_e;
+
+
+typedef enum {
+    NGX_HTTP_PROXY_CACHE_BPS = 1,
+    NGX_HTTP_PROXY_CACHE_XAE,
+    NGX_HTTP_PROXY_CACHE_CTL,
+    NGX_HTTP_PROXY_CACHE_EXP,
+    NGX_HTTP_PROXY_CACHE_MVD,
+    NGX_HTTP_PROXY_CACHE_LMF,
+    NGX_HTTP_PROXY_CACHE_PDE
+} ngx_http_proxy_reason_e;
+
+
+typedef struct {
+    ngx_str_t                        url;
+    ngx_str_t                        host;
+    ngx_str_t                        uri;
+    ngx_str_t                        host_header;
+    ngx_str_t                        port_text;
+    ngx_str_t                       *location;
+
+    in_port_t                        port;
+
+    unsigned                         default_port:1;
+} ngx_http_proxy_upstream_conf_t;
+
+
+typedef struct {
+    size_t                           header_buffer_size;
+    size_t                           busy_buffers_size;
+    size_t                           max_temp_file_size;
+    size_t                           temp_file_write_size;
+
+    ngx_msec_t                       connect_timeout;
+    ngx_msec_t                       send_timeout;
+    ngx_msec_t                       read_timeout;
+    time_t                           default_expires;
+
+    ngx_int_t                        lm_factor;
+
+    ngx_uint_t                       next_upstream;
+    ngx_uint_t                       use_stale;
+
+    ngx_bufs_t                       bufs;
+
+    ngx_flag_t                       cyclic_temp_file;
+    ngx_flag_t                       cache;
+    ngx_flag_t                       preserve_host;
+    ngx_flag_t                       set_x_real_ip;
+    ngx_flag_t                       add_x_forwarded_for;
+    ngx_flag_t                       pass_server;
+    ngx_flag_t                       pass_x_accel_expires;
+    ngx_flag_t                       ignore_expires;
+
+    ngx_path_t                      *cache_path;
+    ngx_path_t                      *temp_path;
+
+    ngx_http_busy_lock_t            *busy_lock;
+
+    ngx_http_proxy_upstream_conf_t  *upstream;
+    ngx_peers_t                     *peers;
+} ngx_http_proxy_loc_conf_t;
+
+
+/*
+ * "EXPR/10/5/- 200/EXP/60 4"
+ * "MISS/-/-/B 503/-/- -"
+ * "EXPR/10/20/SB HIT/-/- -"
+ * "EXPR/10/15/NB HIT/-/- -"
+ */
+
+typedef struct {
+    ngx_http_proxy_state_e           cache_state;
+    time_t                           expired;
+    time_t                           bl_time;
+    ngx_uint_t                       bl_state;
+
+    ngx_uint_t                       status;
+    ngx_http_proxy_reason_e          reason;
+    time_t                           time;
+    time_t                           expires;
+
+    ngx_str_t                       *peer;
+} ngx_http_proxy_state_t;
+
+
+typedef struct {
+    ngx_list_t                       headers;
+#if 0
+    ngx_table_t                      headers;   /* it must be first field */
+#endif
+
+    ngx_table_elt_t                 *date;
+    ngx_table_elt_t                 *server;
+
+    ngx_table_elt_t                 *expires;
+    ngx_table_elt_t                 *cache_control;
+    ngx_table_elt_t                 *etag;
+    ngx_table_elt_t                 *x_accel_expires;
+
+    ngx_table_elt_t                 *connection;
+    ngx_table_elt_t                 *content_type;
+    ngx_table_elt_t                 *content_length;
+    ngx_table_elt_t                 *last_modified;
+    ngx_table_elt_t                 *location;
+    ngx_table_elt_t                 *accept_ranges;
+    ngx_table_elt_t                 *x_pad;
+
+    off_t                            content_length_n;
+} ngx_http_proxy_headers_in_t;
+
+
+typedef struct {
+    ngx_http_cache_ctx_t             ctx;
+    ngx_uint_t                       status;
+    ngx_str_t                        status_line;
+
+    ngx_http_proxy_headers_in_t      headers_in;
+} ngx_http_proxy_cache_t;
+
+
+typedef struct {
+    ngx_peer_connection_t            peer;
+    ngx_uint_t                       status;
+    ngx_str_t                        status_line;
+    ngx_uint_t                       method;
+
+    ngx_output_chain_ctx_t          *output_chain_ctx;
+    ngx_event_pipe_t                *event_pipe;
+
+    ngx_http_proxy_headers_in_t      headers_in;
+} ngx_http_proxy_upstream_t;
+
+
+typedef struct ngx_http_proxy_ctx_s  ngx_http_proxy_ctx_t;
+
+struct ngx_http_proxy_ctx_s {
+    ngx_http_request_t           *request;
+    ngx_http_proxy_loc_conf_t    *lcf;
+    ngx_http_proxy_upstream_t    *upstream;
+    ngx_http_proxy_cache_t       *cache;
+
+    ngx_buf_t                    *header_in;
+
+    ngx_http_busy_lock_ctx_t      busy_lock;
+
+    unsigned                      accel:1;
+
+    unsigned                      cachable:1;
+    unsigned                      stale:1;
+    unsigned                      try_busy_lock:1;
+    unsigned                      busy_locked:1;
+    unsigned                      valid_header_in:1;
+
+    unsigned                      request_sent:1;
+    unsigned                      header_sent:1;
+
+
+    /* used to parse an upstream HTTP header */
+    ngx_uint_t                    status;
+    u_char                       *status_start;
+    u_char                       *status_end;
+    ngx_uint_t                    status_count;
+    ngx_uint_t                    parse_state;
+
+    ngx_http_proxy_state_t       *state;
+    ngx_array_t                   states;    /* of ngx_http_proxy_state_t */
+
+    /*
+     * we declare "action" as "char *" because the actions are usually
+     * the static strings and in the "u_char *" case we have to override
+     * all the time their types
+     */
+
+    char                         *action;
+    ngx_http_log_ctx_t           *saved_ctx;
+    ngx_log_handler_pt            saved_handler;
+};
+
+
+typedef struct {
+    ngx_uint_t             connection;
+    ngx_http_proxy_ctx_t  *proxy;
+} ngx_http_proxy_log_ctx_t;
+
+
+#define NGX_HTTP_PROXY_PARSE_NO_HEADER       30
+
+
+#define NGX_HTTP_PROXY_FT_ERROR              0x02
+#define NGX_HTTP_PROXY_FT_TIMEOUT            0x04
+#define NGX_HTTP_PROXY_FT_INVALID_HEADER     0x08
+#define NGX_HTTP_PROXY_FT_HTTP_500           0x10
+#define NGX_HTTP_PROXY_FT_HTTP_404           0x20
+#define NGX_HTTP_PROXY_FT_BUSY_LOCK          0x40
+#define NGX_HTTP_PROXY_FT_MAX_WAITING        0x80
+
+
+int ngx_http_proxy_request_upstream(ngx_http_proxy_ctx_t *p);
+
+#if (NGX_HTTP_FILE_CACHE)
+
+int ngx_http_proxy_get_cached_response(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_send_cached_response(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_is_cachable(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_update_cache(ngx_http_proxy_ctx_t *p);
+
+void ngx_http_proxy_cache_busy_lock(ngx_http_proxy_ctx_t *p);
+
+#endif
+
+void ngx_http_proxy_check_broken_connection(ngx_event_t *ev);
+
+void ngx_http_proxy_busy_lock_handler(ngx_event_t *rev);
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p);
+
+size_t ngx_http_proxy_log_error(void *data, char *buf, size_t len);
+void ngx_http_proxy_finalize_request(ngx_http_proxy_ctx_t *p, int rc);
+void ngx_http_proxy_close_connection(ngx_http_proxy_ctx_t *p);
+
+int ngx_http_proxy_parse_status_line(ngx_http_proxy_ctx_t *p);
+int ngx_http_proxy_copy_header(ngx_http_proxy_ctx_t *p,
+                               ngx_http_proxy_headers_in_t *headers_in);
+
+
+
+extern ngx_module_t  ngx_http_proxy_module;
+extern ngx_http_header_t ngx_http_proxy_headers_in[];
+
+
+
+#endif /* _NGX_HTTP_PROXY_HANDLER_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_header.c
@@ -0,0 +1,198 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static int ngx_http_proxy_rewrite_location_header(ngx_http_proxy_ctx_t *p,
+                                                  ngx_table_elt_t *loc);
+
+int ngx_http_proxy_copy_header(ngx_http_proxy_ctx_t *p,
+                               ngx_http_proxy_headers_in_t *headers_in)
+{
+    ngx_uint_t           i;
+    ngx_list_part_t     *part;
+    ngx_table_elt_t     *ho, *h;
+    ngx_http_request_t  *r;
+
+    r = p->request;
+
+    part = &headers_in->headers.part;
+    h = part->elts;
+
+#if 0
+    h = headers_in->headers.elts;
+    for (i = 0; i < headers_in->headers.nelts; i++) {
+#endif
+
+    for (i = 0 ; /* void */; i++) {
+  
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+  
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        /* ignore some headers */
+
+        if (&h[i] == headers_in->connection) {
+            continue;
+        }
+
+        if (&h[i] == headers_in->x_pad) {
+            continue;
+        }
+
+        if (p->accel) {
+            if (&h[i] == headers_in->date
+                || &h[i] == headers_in->accept_ranges) {
+                continue;
+            }
+
+            if (&h[i] == headers_in->x_accel_expires
+                && !p->lcf->pass_x_accel_expires)
+            {
+                continue;
+            }
+
+            if (&h[i] == headers_in->server && !p->lcf->pass_server) {
+                continue;
+            }
+
+            if (&h[i] == headers_in->location) {
+                if (ngx_http_proxy_rewrite_location_header(p, &h[i])
+                                                                  == NGX_ERROR)
+                {
+                    return NGX_ERROR;
+                }
+
+                continue;
+            }
+        }
+
+
+        /* "Content-Type" is handled specially */
+
+        if (&h[i] == headers_in->content_type) {
+            r->headers_out.content_type = &h[i];
+            r->headers_out.content_type->key.len = 0;
+            continue;
+        }
+
+
+        /* copy some header pointers and set up r->headers_out */
+
+        if (!(ho = ngx_list_push(&r->headers_out.headers))) {
+            return NGX_ERROR;
+        }
+
+        *ho = h[i];
+
+        if (&h[i] == headers_in->expires) {
+            r->headers_out.expires = ho;
+            continue;
+        }
+
+        if (&h[i] == headers_in->cache_control) {
+            r->headers_out.cache_control = ho;
+            continue;
+        }
+
+        if (&h[i] == headers_in->etag) {
+            r->headers_out.etag = ho;
+            continue;
+        }
+
+        if (&h[i] == headers_in->last_modified) {
+            r->headers_out.last_modified = ho;
+            /* TODO: update r->headers_out.last_modified_time */
+            continue;
+        }
+
+        /*
+         * ngx_http_header_filter() passes the following headers as is
+         * and does not handle them specially if they are set:
+         *     r->headers_out.server,
+         *     r->headers_out.date,
+         *     r->headers_out.content_length
+         */
+
+        if (&h[i] == headers_in->server) {
+            r->headers_out.server = ho;
+            continue;
+        }
+
+        if (&h[i] == headers_in->date) {
+            r->headers_out.date = ho;
+            continue;
+        }
+
+        if (&h[i] == headers_in->content_length) {
+            r->headers_out.content_length = ho;
+            r->headers_out.content_length_n = ngx_atoi(ho->value.data,
+                                                       ho->value.len);
+            continue;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+static int ngx_http_proxy_rewrite_location_header(ngx_http_proxy_ctx_t *p,
+                                                  ngx_table_elt_t *loc)
+{
+    u_char                          *last;
+    ngx_table_elt_t                 *location;
+    ngx_http_request_t              *r;
+    ngx_http_proxy_upstream_conf_t  *uc;
+
+    r = p->request;
+    uc = p->lcf->upstream;
+
+    if (!(location = ngx_list_push(&r->headers_out.headers))) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * we do not set r->headers_out.location to avoid the handling
+     * the local redirects without a host name by ngx_http_header_filter()
+     */
+
+#if 0
+    r->headers_out.location = location;
+#endif
+
+    if (uc->url.len > loc->value.len
+        || ngx_rstrncmp(loc->value.data, uc->url.data, uc->url.len) != 0)
+    {
+        *location = *loc;
+        return NGX_OK;
+    }
+
+    /* TODO: proxy_reverse */
+
+    location->value.len = uc->location->len
+                                          + (loc->value.len - uc->url.len) + 1;
+    if (!(location->value.data = ngx_palloc(r->pool, location->value.len))) {
+        return NGX_ERROR;
+    }
+
+    last = ngx_cpymem(location->value.data,
+                      uc->location->data, uc->location->len);
+
+    ngx_cpystrn(last, loc->value.data + uc->url.len,
+                loc->value.len - uc->url.len + 1);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_parse.c
@@ -0,0 +1,213 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+int ngx_http_proxy_parse_status_line(ngx_http_proxy_ctx_t *p)
+{
+    u_char   ch;
+    u_char  *pos;
+    enum  {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done,
+        sw_done
+    } state;
+
+    state = p->parse_state;
+    pos = p->header_in->pos;
+
+    while (pos < p->header_in->last && state < sw_done) {
+        ch = *pos++;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+
+            p->status = p->status * 10 + ch - '0';
+
+            if (++p->status_count == 3) {
+                state = sw_space_after_status;
+                p->status_start = pos - 3;
+            }
+
+            break;
+
+         /* space or end of line */
+         case sw_space_after_status:
+            switch (ch) {
+            case ' ':
+                state = sw_status_text;
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                state = sw_done;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        /* any text until end of line */
+        case sw_status_text:
+            switch (ch) {
+            case CR:
+                state = sw_almost_done;
+
+                break;
+            case LF:
+                state = sw_done;
+                break;
+            }
+            break;
+
+        /* end of request line */
+        case sw_almost_done:
+            p->status_end = pos - 2;
+            switch (ch) {
+            case LF:
+                state = sw_done;
+                break;
+            default:
+                return NGX_HTTP_PROXY_PARSE_NO_HEADER;
+            }
+            break;
+
+        /* suppress warning */
+        case sw_done:
+            break;
+        }
+    }
+
+    p->header_in->pos = pos;
+
+    if (state == sw_done) {
+        if (p->status_end == NULL) {
+            p->status_end = pos - 1;
+        }
+
+        p->parse_state = sw_start;
+        return NGX_OK;
+    }
+
+    p->parse_state = state;
+    return NGX_AGAIN;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/modules/proxy/ngx_http_proxy_upstream.c
@@ -0,0 +1,1492 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_event_connect.h>
+#include <ngx_event_pipe.h>
+#include <ngx_http.h>
+#include <ngx_http_proxy_handler.h>
+
+
+static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_init_upstream(void *data);
+static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_send_request_handler(ngx_event_t *wev);
+static void ngx_http_proxy_dummy_handler(ngx_event_t *wev);
+static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev);
+static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev);
+static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *);
+static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p);
+static void ngx_http_proxy_process_body(ngx_event_t *ev);
+static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, int ft_type);
+
+
+static ngx_str_t http_methods[] = {
+    ngx_string("GET "),
+    ngx_string("HEAD "),
+    ngx_string("POST ")
+};
+
+
+static char *upstream_header_errors[] = {
+    "upstream sent invalid header",
+    "upstream sent too long header line"
+};
+
+
+static char  http_version[] = " HTTP/1.0" CRLF;
+static char  host_header[] = "Host: ";
+static char  x_real_ip_header[] = "X-Real-IP: ";
+static char  x_forwarded_for_header[] = "X-Forwarded-For: ";
+static char  connection_close_header[] = "Connection: close" CRLF;
+
+
+int ngx_http_proxy_request_upstream(ngx_http_proxy_ctx_t *p)
+{
+    int                         rc;
+    ngx_temp_file_t            *tf;
+    ngx_http_request_t         *r;
+    ngx_http_request_body_t    *rb;
+    ngx_http_proxy_upstream_t  *u;
+
+    r = p->request;
+
+    if (!(u = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_upstream_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    p->upstream = u;
+
+    u->peer.log_error = NGX_ERROR_ERR;
+    u->peer.peers = p->lcf->peers;
+    u->peer.tries = p->lcf->peers->number;
+#if (NGX_THREADS)
+    u->peer.lock = &r->connection->lock;
+#endif
+
+    u->method = r->method;
+
+    if (!(rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+    r->request_body = rb;
+
+    if (r->headers_in.content_length_n <= 0) {
+        ngx_http_proxy_init_upstream(p);
+        return NGX_DONE;
+    }
+
+    if (!(tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    tf->file.fd = NGX_INVALID_FILE;
+    tf->file.log = r->connection->log;
+    tf->path = p->lcf->temp_path;
+    tf->pool = r->pool;
+    tf->warn = "a client request body is buffered to a temporary file";
+    /* tf->persistent = 0; */
+
+    rb->handler = ngx_http_proxy_init_upstream;
+    rb->data = p;
+    /* rb->bufs = NULL; */
+    /* rb->buf = NULL; */
+    /* rb->rest = 0; */
+
+    rb->temp_file = tf;
+
+    rc = ngx_http_read_client_request_body(r);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        return rc;
+    }
+
+    return NGX_DONE;
+}
+
+
+static ngx_chain_t *ngx_http_proxy_create_request(ngx_http_proxy_ctx_t *p)
+{
+    size_t                           len;
+    ngx_uint_t                       i;
+    ngx_buf_t                       *b;
+    ngx_chain_t                     *chain;
+    ngx_list_part_t                 *part;
+    ngx_table_elt_t                 *header;
+    ngx_http_request_t              *r;
+    ngx_http_proxy_upstream_conf_t  *uc;
+
+    r = p->request;
+    uc = p->lcf->upstream;
+
+    if (p->upstream->method) {
+        len = http_methods[p->upstream->method - 1].len;
+
+    } else {
+        len = r->method_name.len;
+    }
+
+    len += uc->uri.len
+           + r->uri.len - uc->location->len
+           + 1 + r->args.len                                 /* 1 is for "?" */
+           + sizeof(http_version) - 1
+           + sizeof(connection_close_header) - 1
+           + 2;                         /* 2 is for "\r\n" at the header end */
+
+
+    if (p->lcf->preserve_host && r->headers_in.host) {
+        len += sizeof(host_header) - 1
+               + r->headers_in.host_name_len
+               + 1                                           /* 1 is for ":" */
+               + uc->port_text.len
+               + 2;                                       /* 2 is for "\r\n" */
+    } else {                                              /* 2 is for "\r\n" */
+        len += sizeof(host_header) - 1 + uc->host_header.len + 2;
+    }
+
+
+    if (p->lcf->set_x_real_ip) {                          /* 2 is for "\r\n" */
+        len += sizeof(x_real_ip_header) - 1 + INET_ADDRSTRLEN - 1 + 2;
+    }
+
+
+    if (p->lcf->add_x_forwarded_for) {
+        if (r->headers_in.x_forwarded_for) {
+            len += sizeof(x_forwarded_for_header) - 1
+                   + r->headers_in.x_forwarded_for->value.len
+                   + 2                                      /* 2 is ofr ", " */
+                   + INET_ADDRSTRLEN - 1
+                   + 2;                                   /* 2 is for "\r\n" */
+        } else {
+            len += sizeof(x_forwarded_for_header) - 1 + INET_ADDRSTRLEN - 1 + 2;
+                                                          /* 2 is for "\r\n" */
+        }
+    }
+
+
+    part = &r->headers_in.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (&header[i] == r->headers_in.host) {
+            continue;
+        }
+
+        if (&header[i] == r->headers_in.connection) {
+            continue;
+        }
+
+        /* 2 is for ": " and 2 is for "\r\n" */
+        len += header[i].key.len + 2 + header[i].value.len + 2;
+    }
+
+#if (NGX_DEBUG)
+    len++;
+#endif
+
+    ngx_test_null(b, ngx_create_temp_buf(r->pool, len), NULL);
+    ngx_alloc_link_and_set_buf(chain, b, r->pool, NULL);
+
+
+    /* the request line */
+
+    if (p->upstream->method) {
+        b->last = ngx_cpymem(b->last,
+                             http_methods[p->upstream->method - 1].data,
+                             http_methods[p->upstream->method - 1].len);
+    } else {
+        b->last = ngx_cpymem(b->last, r->method_name.data, r->method_name.len);
+    }
+
+    b->last = ngx_cpymem(b->last, uc->uri.data, uc->uri.len);
+
+    b->last = ngx_cpymem(b->last,
+                         r->uri.data + uc->location->len,
+                         r->uri.len - uc->location->len);
+
+    if (r->args.len > 0) {
+        *(b->last++) = '?';
+        b->last = ngx_cpymem(b->last, r->args.data, r->args.len);
+    }
+
+    b->last = ngx_cpymem(b->last, http_version, sizeof(http_version) - 1);
+
+
+    /* the "Connection: close" header */
+
+    b->last = ngx_cpymem(b->last, connection_close_header,
+                         sizeof(connection_close_header) - 1);
+
+
+    /* the "Host" header */
+
+    b->last = ngx_cpymem(b->last, host_header, sizeof(host_header) - 1);
+
+    if (p->lcf->preserve_host && r->headers_in.host) {
+        b->last = ngx_cpymem(b->last, r->headers_in.host->value.data,
+                             r->headers_in.host_name_len);
+
+        if (!uc->default_port) {
+            *(b->last++) = ':';
+            b->last = ngx_cpymem(b->last, uc->port_text.data,
+                                 uc->port_text.len);
+        }
+
+    } else {
+        b->last = ngx_cpymem(b->last, uc->host_header.data,
+                             uc->host_header.len);
+    }
+    *(b->last++) = CR; *(b->last++) = LF;
+
+
+    /* the "X-Real-IP" header */
+
+    if (p->lcf->set_x_real_ip) {
+        b->last = ngx_cpymem(b->last, x_real_ip_header,
+                             sizeof(x_real_ip_header) - 1);
+        b->last = ngx_cpymem(b->last, r->connection->addr_text.data,
+                             r->connection->addr_text.len);
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+
+    /* the "X-Forwarded-For" header */
+
+    if (p->lcf->add_x_forwarded_for) {
+        if (r->headers_in.x_forwarded_for) {
+            b->last = ngx_cpymem(b->last, x_forwarded_for_header,
+                                 sizeof(x_forwarded_for_header) - 1);
+
+            b->last = ngx_cpymem(b->last,
+                                 r->headers_in.x_forwarded_for->value.data,
+                                 r->headers_in.x_forwarded_for->value.len);
+
+            *(b->last++) = ','; *(b->last++) = ' ';
+
+        } else {
+            b->last = ngx_cpymem(b->last, x_forwarded_for_header,
+                                 sizeof(x_forwarded_for_header) - 1);
+        }
+
+        b->last = ngx_cpymem(b->last, r->connection->addr_text.data,
+                             r->connection->addr_text.len);
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+
+    part = &r->headers_in.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (&header[i] == r->headers_in.host) {
+            continue;
+        }
+
+        if (&header[i] == r->headers_in.connection) {
+            continue;
+        }
+
+        if (&header[i] == r->headers_in.keep_alive) {
+            continue;
+        }
+
+        if (&header[i] == r->headers_in.x_forwarded_for
+            && p->lcf->add_x_forwarded_for)
+        {
+            continue;
+        }
+
+        b->last = ngx_cpymem(b->last, header[i].key.data, header[i].key.len);
+
+        *(b->last++) = ':'; *(b->last++) = ' ';
+
+        b->last = ngx_cpymem(b->last, header[i].value.data,
+                             header[i].value.len);
+
+        *(b->last++) = CR; *(b->last++) = LF;
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http proxy header: \"%s: %s\"",
+                       header[i].key.data, header[i].value.data);
+    }
+
+    /* add "\r\n" at the header end */
+    *(b->last++) = CR; *(b->last++) = LF;
+
+#if (NGX_DEBUG)
+    *(b->last) = '\0';
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http proxy header:\n\"%s\"", b->pos);
+#endif
+
+    return chain;
+}
+
+
+static void ngx_http_proxy_init_upstream(void *data)
+{
+    ngx_http_proxy_ctx_t *p = data;
+
+    ngx_chain_t               *cl;
+    ngx_http_request_t        *r;
+    ngx_output_chain_ctx_t    *output;
+    ngx_chain_writer_ctx_t    *writer;
+    ngx_http_proxy_log_ctx_t  *ctx;
+
+    r = p->request;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                  "http proxy init upstream, client timer: %d",
+                  r->connection->read->timer_set);
+
+    if (r->connection->read->timer_set) {
+        ngx_del_timer(r->connection->read);
+    }
+
+    r->connection->read->event_handler = ngx_http_proxy_check_broken_connection;
+
+    if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+        r->connection->write->event_handler =
+                                        ngx_http_proxy_check_broken_connection;
+
+        if (!r->connection->write->active) {
+            if (ngx_add_event(r->connection->write, NGX_WRITE_EVENT,
+                                                NGX_CLEAR_EVENT) == NGX_ERROR)
+            {
+                ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+        }
+    }
+
+
+    if (!(cl = ngx_http_proxy_create_request(p))) {
+        ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    if (r->request_body->bufs) {
+        cl->next = r->request_body->bufs;
+    }
+
+    r->request_body->bufs = cl;
+
+    if (!(ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_proxy_log_ctx_t)))) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+    ctx->connection = r->connection->number;
+    ctx->proxy = p;
+
+    p->upstream->peer.log = r->connection->log;
+    p->saved_ctx = r->connection->log->data;
+    p->saved_handler = r->connection->log->handler;
+    r->connection->log->data = ctx;
+    r->connection->log->handler = ngx_http_proxy_log_error;
+    p->action = "connecting to upstream";
+
+    if (!(output = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t)))) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    p->upstream->output_chain_ctx = output;
+
+    output->sendfile = r->sendfile;
+    output->pool = r->pool;
+    output->bufs.num = 1;
+    output->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+    output->output_filter = (ngx_output_chain_filter_pt) ngx_chain_writer;
+
+    if (!(writer = ngx_palloc(r->pool, sizeof(ngx_chain_writer_ctx_t)))) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    output->filter_ctx = writer;
+    writer->pool = r->pool;
+
+#if 0
+    if (p->lcf->busy_lock && p->busy_lock == NULL) {
+#else
+    if (p->lcf->busy_lock && !p->busy_locked) {
+#endif
+        ngx_http_proxy_upstream_busy_lock(p);
+    } else {
+        ngx_http_proxy_connect(p);
+    }
+}
+
+
+static void ngx_http_proxy_reinit_upstream(ngx_http_proxy_ctx_t *p)
+{
+    ngx_chain_t             *cl;
+    ngx_output_chain_ctx_t  *output;
+
+    /* reinit the request chain */
+
+    for (cl = p->request->request_body->bufs; cl; cl = cl->next) {
+        cl->buf->pos = cl->buf->start;
+        cl->buf->file_pos = 0;
+    }
+
+    /* reinit the ngx_output_chain() context */
+
+    output = p->upstream->output_chain_ctx;
+
+    output->buf = NULL;
+    output->in = NULL;
+    output->free = NULL;
+    output->busy = NULL;
+
+    /* reinit r->header_in buffer */
+
+    if (p->header_in) {
+        if (p->cache) {
+            p->header_in->pos = p->header_in->start + p->cache->ctx.header_size;
+            p->header_in->last = p->header_in->pos;
+
+        } else {
+            p->header_in->pos = p->header_in->start;
+            p->header_in->last = p->header_in->start;
+        }
+    }
+
+    /* add one more state */
+
+    if (!(p->state = ngx_push_array(&p->states))) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    p->status = 0;
+    p->status_count = 0;
+}
+
+
+#if 0
+
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_event_busy_lock(p->lcf->busy_lock, p->busy_lock);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_OK) {
+        ngx_http_proxy_connect(p);
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        p->state->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    /* rc == NGX_BUSY */
+
+#if (NGX_HTTP_CACHE)
+
+    if (p->busy_lock->timer) {
+        ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+    } else {
+        ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+    }
+
+    if (p->stale && (p->lcf->use_stale & ft_type)) {
+        ngx_http_proxy_finalize_request(p,
+                                        ngx_http_proxy_send_cached_response(p));
+        return;
+    }
+
+#endif
+
+    p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+    ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+#endif
+
+
+#if 1
+
+void ngx_http_proxy_upstream_busy_lock(ngx_http_proxy_ctx_t *p)
+{
+    ngx_int_t  rc;
+#if (NGX_HTTP_CACHE)
+    ngx_int_t  ft_type;
+#endif
+
+    if (p->busy_lock.time == 0) {
+        p->busy_lock.event = p->request->connection->read;
+        p->busy_lock.event_handler = ngx_http_proxy_busy_lock_handler;
+    }
+
+    rc = ngx_http_busy_lock(p->lcf->busy_lock, &p->busy_lock);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_OK) {
+        ngx_http_proxy_connect(p);
+        return;
+    }
+
+    ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+#if (NGX_HTTP_CACHE)
+
+    if (rc == NGX_DONE) {
+        ft_type = NGX_HTTP_PROXY_FT_BUSY_LOCK;
+
+    } else {
+        /* rc == NGX_ERROR */
+        ft_type = NGX_HTTP_PROXY_FT_MAX_WAITING;
+    }
+
+    if (p->stale && (p->lcf->use_stale & ft_type)) {
+        ngx_http_proxy_finalize_request(p,
+                                        ngx_http_proxy_send_cached_response(p));
+        return;
+    }
+
+#endif
+
+    p->state->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+    ngx_http_proxy_finalize_request(p, NGX_HTTP_SERVICE_UNAVAILABLE);
+}
+
+#endif
+
+
+static void ngx_http_proxy_connect(ngx_http_proxy_ctx_t *p)
+{
+    int                      rc;
+    ngx_connection_t        *c;
+    ngx_http_request_t      *r;
+    ngx_output_chain_ctx_t  *output;
+    ngx_chain_writer_ctx_t  *writer;
+
+    p->action = "connecting to upstream";
+
+    p->request->connection->single_connection = 0;
+
+    rc = ngx_event_connect_peer(&p->upstream->peer);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+                   "http proxy connect: %d", rc);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    p->state->peer =
+     &p->upstream->peer.peers->peers[p->upstream->peer.cur_peer].addr_port_text;
+
+    if (rc == NGX_CONNECT_ERROR) {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+        return;
+    }
+
+    r = p->request;
+    c = p->upstream->peer.connection;
+
+    c->data = p;
+    c->write->event_handler = ngx_http_proxy_send_request_handler;
+    c->read->event_handler = ngx_http_proxy_process_upstream_status_line;
+
+    c->pool = r->pool;
+    c->read->log = c->write->log = c->log = r->connection->log;
+
+    /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */
+
+    output = p->upstream->output_chain_ctx;
+    writer = output->filter_ctx;
+    writer->out = NULL;
+    writer->last = &writer->out;
+    writer->connection = c;
+    writer->limit = OFF_T_MAX_VALUE;
+
+    if (p->upstream->peer.tries > 1 && p->request_sent) {
+        ngx_http_proxy_reinit_upstream(p);
+    }
+
+    if (r->request_body->buf) {
+        if (r->request_body->temp_file->file.fd != NGX_INVALID_FILE) {
+
+            if (!(output->free = ngx_alloc_chain_link(r->pool))) {
+                ngx_http_proxy_finalize_request(p,
+                                                NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            output->free->buf = r->request_body->buf;
+            output->free->next = NULL;
+            output->allocated = 1;
+
+            r->request_body->buf->pos = r->request_body->buf->start;
+            r->request_body->buf->last = r->request_body->buf->start;
+            r->request_body->buf->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+        } else {
+            r->request_body->buf->pos = r->request_body->buf->start;
+        }
+    }
+
+    p->request_sent = 0;
+
+    if (rc == NGX_AGAIN) {
+        ngx_add_timer(c->write, p->lcf->connect_timeout);
+        return;
+    }
+
+    /* rc == NGX_OK */
+
+#if 1 /* test only, see below about "post aio operation" */
+
+    if (c->read->ready) {
+        /* post aio operation */
+        ngx_http_proxy_process_upstream_status_line(c->read);
+        return;
+    }
+
+#endif
+
+    ngx_http_proxy_send_request(p);
+}
+
+
+static void ngx_http_proxy_send_request(ngx_http_proxy_ctx_t *p)
+{
+    int                rc;
+    ngx_connection_t  *c;
+
+    c = p->upstream->peer.connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http proxy send request");
+
+#if (HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT)
+        && !p->request_sent
+        && c->write->pending_eof)
+    {
+        ngx_log_error(NGX_LOG_ERR, c->log, c->write->kq_errno,
+                      "connect() failed");
+
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+        return;
+    }
+
+#endif
+
+    p->action = "sending request to upstream";
+
+    rc = ngx_output_chain(p->upstream->output_chain_ctx,
+                          p->request_sent ? NULL:
+                                            p->request->request_body->bufs);
+
+    if (rc == NGX_ERROR) {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+        return;
+    }
+
+    p->request_sent = 1;
+
+    if (c->write->timer_set) {
+        ngx_del_timer(c->write);
+    }
+
+    if (rc == NGX_AGAIN) {
+        ngx_add_timer(c->write, p->lcf->send_timeout);
+
+        c->write->available = /* STUB: lowat */ 0;
+        if (ngx_handle_write_event(c->write, NGX_LOWAT_EVENT) == NGX_ERROR) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+
+        return;
+    }
+
+    /* rc == NGX_OK */
+
+    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+        if (ngx_tcp_push(c->fd) == NGX_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, c->log,
+                          ngx_socket_errno,
+                          ngx_tcp_push_n " failed");
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return; 
+        }
+
+        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+        return;
+    }
+
+    ngx_add_timer(c->read, p->lcf->read_timeout);
+
+#if 0
+    if (c->read->ready) {
+
+        /* post aio operation */
+
+        /*
+         * although we can post aio operation just in the end
+         * of ngx_http_proxy_connect() CHECK IT !!!
+         * it's better to do here because we postpone header buffer allocation
+         */
+
+        ngx_http_proxy_process_upstream_status_line(c->read);
+        return;
+    }
+#endif
+
+    c->write->event_handler = ngx_http_proxy_dummy_handler;
+
+    if (ngx_handle_level_write_event(c->write) == NGX_ERROR) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+}
+
+
+static void ngx_http_proxy_send_request_handler(ngx_event_t *wev)
+{
+    ngx_connection_t      *c;
+    ngx_http_proxy_ctx_t  *p;
+
+    c = wev->data;
+    p = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+                   "http proxy send request handler");
+
+    if (wev->timedout) {
+        p->action = "sending request to upstream";
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+        return;
+    }
+
+    if (p->request->connection->write->eof
+        && (!p->cachable || !p->request_sent))
+    {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    ngx_http_proxy_send_request(p);
+}
+
+
+static void ngx_http_proxy_dummy_handler(ngx_event_t *wev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http proxy dummy handler");
+}
+
+
+static void ngx_http_proxy_process_upstream_status_line(ngx_event_t *rev)
+{
+    int                    rc;
+    ssize_t                n;
+    ngx_connection_t      *c;
+    ngx_http_proxy_ctx_t  *p;
+
+    c = rev->data;
+    p = c->data;
+    p->action = "reading upstream status line";
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http proxy process status line");
+
+    if (rev->timedout) {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+        return;
+    }
+
+    if (p->header_in == NULL) {
+        p->header_in = ngx_create_temp_buf(p->request->pool,
+                                           p->lcf->header_buffer_size);
+        if (p->header_in == NULL) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+        p->header_in->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+
+        if (p->cache) {
+            p->header_in->pos += p->cache->ctx.header_size;
+            p->header_in->last = p->header_in->pos;
+        }
+    }
+
+    n = ngx_http_proxy_read_upstream_header(p);
+
+    if (n == NGX_AGAIN) {
+        return;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                      "upstream prematurely closed connection");
+    }
+
+    if (n == NGX_ERROR || n == 0) {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+        return;
+    }
+
+    p->valid_header_in = 0;
+
+    p->upstream->peer.cached = 0;
+
+    rc = ngx_http_proxy_parse_status_line(p);
+
+    if (rc == NGX_AGAIN) {
+        if (p->header_in->pos == p->header_in->last) {
+            ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                          "upstream sent too long status line");
+            ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+        }
+        return;
+    }
+
+    if (rc == NGX_HTTP_PROXY_PARSE_NO_HEADER) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                      "upstream sent no valid HTTP/1.0 header");
+
+        if (p->accel) {
+            ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+
+        } else {
+            p->request->http_version = NGX_HTTP_VERSION_9;
+            p->upstream->status = NGX_HTTP_OK;
+            ngx_http_proxy_send_response(p);
+        }
+
+        return;
+    }
+
+    /* rc == NGX_OK */
+
+    p->upstream->status = p->status;
+    p->state->status = p->status;
+
+    if (p->status == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+
+        if (p->upstream->peer.tries > 1
+            && (p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_500))
+        {
+            ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_500);
+            return;
+        }
+
+#if (NGX_HTTP_CACHE)
+
+        if (p->upstream->peer.tries == 0
+            && p->stale
+            && (p->lcf->use_stale & NGX_HTTP_PROXY_FT_HTTP_500))
+        {
+            ngx_http_proxy_finalize_request(p,
+                                       ngx_http_proxy_send_cached_response(p));
+
+            return;
+        }
+
+#endif
+    }
+
+    if (p->status == NGX_HTTP_NOT_FOUND
+        && p->upstream->peer.tries > 1
+        && p->lcf->next_upstream & NGX_HTTP_PROXY_FT_HTTP_404)
+    {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_HTTP_404);
+        return;
+    }
+
+    /* TODO: "proxy_error_page" */
+
+    p->upstream->status_line.len = p->status_end - p->status_start;
+    p->upstream->status_line.data = ngx_palloc(p->request->pool,
+                                              p->upstream->status_line.len + 1);
+    if (p->upstream->status_line.data == NULL) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+    ngx_cpystrn(p->upstream->status_line.data, p->status_start,
+                p->upstream->status_line.len + 1);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http proxy status %d \"%s\"",
+                   p->upstream->status, p->upstream->status_line.data);
+
+
+    /* init or reinit the p->upstream->headers_in.headers table */
+
+    if (p->upstream->headers_in.headers.part.elts) {
+        p->upstream->headers_in.headers.part.nelts = 0;
+        p->upstream->headers_in.headers.part.next = NULL;
+        p->upstream->headers_in.headers.last =
+                                         &p->upstream->headers_in.headers.part;
+
+        ngx_memzero(&p->upstream->headers_in.date,
+                    sizeof(ngx_http_proxy_headers_in_t) - sizeof(ngx_list_t));
+
+    } else {
+        if (ngx_list_init(&p->upstream->headers_in.headers, p->request->pool,
+                                     20, sizeof(ngx_table_elt_t)) == NGX_ERROR)
+        {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
+
+    c->read->event_handler = ngx_http_proxy_process_upstream_headers;
+    ngx_http_proxy_process_upstream_headers(rev);
+}
+
+
+static void ngx_http_proxy_process_upstream_headers(ngx_event_t *rev)
+{
+    int                    i, rc;
+    ssize_t                n;
+    ngx_table_elt_t       *h;
+    ngx_connection_t      *c;
+    ngx_http_request_t    *r;
+    ngx_http_proxy_ctx_t  *p;
+
+    c = rev->data;
+    p = c->data;
+    r = p->request;
+    p->action = "reading upstream headers";
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http proxy process header line");
+
+    if (rev->timedout) {
+        ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_TIMEOUT);
+        return;
+    }
+
+    rc = NGX_AGAIN;
+
+    for ( ;; ) {
+        if (rc == NGX_AGAIN) {
+            n = ngx_http_proxy_read_upstream_header(p);
+
+            if (n == 0) {
+                ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                              "upstream prematurely closed connection");
+            }
+
+            if (n == NGX_ERROR || n == 0) {
+                ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_ERROR);
+                return;
+            }
+
+            if (n == NGX_AGAIN) {
+                return;
+            }
+        }
+
+        rc = ngx_http_parse_header_line(p->request, p->header_in);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            if (!(h = ngx_list_push(&p->upstream->headers_in.headers))) {
+                ngx_http_proxy_finalize_request(p,
+                                                NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->value.len = r->header_end - r->header_start;
+
+            h->key.data = ngx_palloc(p->request->pool,
+                                     h->key.len + 1 + h->value.len + 1);
+            if (h->key.data == NULL) {
+                ngx_http_proxy_finalize_request(p,
+                                                NGX_HTTP_INTERNAL_SERVER_ERROR);
+                return;
+            }
+
+            h->value.data = h->key.data + h->key.len + 1;
+            ngx_cpystrn(h->key.data, r->header_name_start, h->key.len + 1);
+            ngx_cpystrn(h->value.data, r->header_start, h->value.len + 1);
+
+            for (i = 0; ngx_http_proxy_headers_in[i].name.len != 0; i++) {
+                if (ngx_http_proxy_headers_in[i].name.len != h->key.len) {
+                    continue;
+                }
+
+                if (ngx_strcasecmp(ngx_http_proxy_headers_in[i].name.data,
+                                                           h->key.data) == 0)
+                {
+                    *((ngx_table_elt_t **) ((char *) &p->upstream->headers_in
+                                   + ngx_http_proxy_headers_in[i].offset)) = h;
+                    break;
+                }
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http proxy header: \"%s: %s\"",
+                           h->key.data, h->value.data);
+
+            continue;
+
+        } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http proxy header done");
+
+            /* TODO: hook to process the upstream header */
+
+#if (NGX_HTTP_CACHE)
+
+            if (p->cachable) {
+                p->cachable = ngx_http_proxy_is_cachable(p);
+            }
+
+#endif
+
+            ngx_http_proxy_send_response(p);
+            return;
+
+        } else if (rc != NGX_AGAIN) {
+
+            /* there was error while a header line parsing */
+
+            ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                      upstream_header_errors[rc - NGX_HTTP_PARSE_HEADER_ERROR]);
+
+            ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+            return;
+        }
+
+        /* rc == NGX_AGAIN: a header line parsing is still not complete */
+
+        if (p->header_in->last == p->header_in->end) {
+            ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                          "upstream sent too big header");
+
+            ngx_http_proxy_next_upstream(p, NGX_HTTP_PROXY_FT_INVALID_HEADER);
+            return;
+        }
+    }
+}
+
+
+static ssize_t ngx_http_proxy_read_upstream_header(ngx_http_proxy_ctx_t *p)
+{
+    ssize_t       n;
+    ngx_event_t  *rev;
+
+    rev = p->upstream->peer.connection->read;
+
+    n = p->header_in->last - p->header_in->pos;
+
+    if (n > 0) {
+        return n;
+    }
+
+    n = ngx_recv(p->upstream->peer.connection, p->header_in->last,
+                 p->header_in->end - p->header_in->last);
+
+    if (n == NGX_AGAIN) {
+#if 0
+        ngx_add_timer(rev, p->lcf->read_timeout);
+#endif
+
+        if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+            ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, 0,
+                      "upstream closed prematurely connection");
+    }
+
+    if (n == 0 || n == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    p->header_in->last += n;
+
+    return n;
+}
+
+
+static void ngx_http_proxy_send_response(ngx_http_proxy_ctx_t *p)
+{
+    int                           rc;
+    ngx_event_pipe_t             *ep;
+    ngx_http_request_t           *r;
+    ngx_http_cache_header_t      *header;
+    ngx_http_core_loc_conf_t     *clcf;
+
+    r = p->request;
+
+    r->headers_out.status = p->upstream->status;
+
+#if 0
+    r->headers_out.content_length_n = -1;
+    r->headers_out.content_length = NULL;
+#endif
+
+    /* copy an upstream header to r->headers_out */
+
+    if (ngx_http_proxy_copy_header(p, &p->upstream->headers_in) == NGX_ERROR) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        return;
+    }
+
+    /* TODO: preallocate event_pipe bufs, look "Content-Length" */
+
+    rc = ngx_http_send_header(r);
+
+    p->header_sent = 1;
+
+    if (p->cache && p->cache->ctx.file.fd != NGX_INVALID_FILE) {
+        if (ngx_close_file(p->cache->ctx.file.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          p->cache->ctx.file.name.data);
+        }
+    }
+
+    if (p->cachable) {
+        header = (ngx_http_cache_header_t *) p->header_in->start;
+
+        header->expires = p->cache->ctx.expires;
+        header->last_modified = p->cache->ctx.last_modified;
+        header->date = p->cache->ctx.date;
+        header->length = r->headers_out.content_length_n;
+        p->cache->ctx.length = r->headers_out.content_length_n;
+
+        header->key_len = p->cache->ctx.key.len;
+        ngx_memcpy(&header->key, p->cache->ctx.key.data, header->key_len);
+        header->key[header->key_len] = LF;
+    }
+
+    ep = ngx_pcalloc(r->pool, sizeof(ngx_event_pipe_t));
+    if (ep == NULL) {
+        ngx_http_proxy_finalize_request(p, 0);
+        return;
+    }
+
+    p->upstream->event_pipe = ep;
+
+    ep->input_filter = ngx_event_pipe_copy_input_filter;
+    ep->output_filter = (ngx_event_pipe_output_filter_pt)
+                                                        ngx_http_output_filter;
+    ep->output_ctx = r;
+    ep->tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
+    ep->bufs = p->lcf->bufs;
+    ep->busy_size = p->lcf->busy_buffers_size;
+    ep->upstream = p->upstream->peer.connection;
+    ep->downstream = r->connection;
+    ep->pool = r->pool;
+    ep->log = r->connection->log;
+
+    ep->cachable = p->cachable;
+
+    if (!(ep->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)))) {
+        ngx_http_proxy_finalize_request(p, 0);
+        return;
+    }
+
+    ep->temp_file->file.fd = NGX_INVALID_FILE;
+    ep->temp_file->file.log = r->connection->log;
+    ep->temp_file->path = p->lcf->temp_path;
+    ep->temp_file->pool = r->pool;
+
+    if (p->cachable) {
+        ep->temp_file->persistent = 1;
+    } else {
+        ep->temp_file->warn = "an upstream response is buffered "
+                              "to a temporary file";
+    }
+
+    ep->max_temp_file_size = p->lcf->max_temp_file_size;
+    ep->temp_file_write_size = p->lcf->temp_file_write_size;
+
+    if (!(ep->preread_bufs = ngx_alloc_chain_link(r->pool))) {
+        ngx_http_proxy_finalize_request(p, 0);
+        return;
+    }
+    ep->preread_bufs->buf = p->header_in;
+    ep->preread_bufs->next = NULL;
+
+    ep->preread_size = p->header_in->last - p->header_in->pos;
+
+    if (p->cachable) {
+        ep->buf_to_file = ngx_calloc_buf(r->pool);
+        if (ep->buf_to_file == NULL) {
+            ngx_http_proxy_finalize_request(p, 0);
+            return;
+        }
+        ep->buf_to_file->pos = p->header_in->start;
+        ep->buf_to_file->last = p->header_in->pos;
+        ep->buf_to_file->temporary = 1;
+    }
+
+    if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+        /* the posted aio operation can currupt a shadow buffer */
+        ep->single_buf = 1;
+    }
+
+    /* TODO: ep->free_bufs = 0 if use ngx_create_chain_of_bufs() */
+    ep->free_bufs = 1;
+
+    /*
+     * event_pipe would do p->header_in->last += ep->preread_size
+     * as though these bytes were read.
+     */
+    p->header_in->last = p->header_in->pos;
+
+    if (p->lcf->cyclic_temp_file) {
+
+        /*
+         * we need to disable the use of sendfile() if we use cyclic temp file
+         * because the writing a new data can interfere with sendfile()
+         * that uses the same kernel file pages (at least on FreeBSD)
+         */
+
+        ep->cyclic_temp_file = 1;
+        r->sendfile = 0;
+
+    } else {
+        ep->cyclic_temp_file = 0;
+        r->sendfile = 1;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    ep->read_timeout = p->lcf->read_timeout;
+    ep->send_timeout = clcf->send_timeout;
+    ep->send_lowat = clcf->send_lowat;
+
+    p->upstream->peer.connection->read->event_handler =
+                                                   ngx_http_proxy_process_body;
+    r->connection->write->event_handler = ngx_http_proxy_process_body;
+
+    ngx_http_proxy_process_body(p->upstream->peer.connection->read);
+
+    return;
+}
+
+
+static void ngx_http_proxy_process_body(ngx_event_t *ev)
+{
+    ngx_connection_t      *c;
+    ngx_http_request_t    *r;
+    ngx_http_proxy_ctx_t  *p;
+    ngx_event_pipe_t      *ep;
+
+    c = ev->data;
+
+    if (ev->write) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                       "http proxy process downstream");
+        r = c->data;
+        p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
+        p->action = "sending to client";
+
+    } else {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                       "http proxy process upstream");
+        p = c->data;
+        r = p->request;
+        p->action = "reading upstream body";
+    }
+
+    ep = p->upstream->event_pipe;
+
+    if (ev->timedout) {
+        if (ev->write) {
+            ep->downstream_error = 1;
+            ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
+                          "client timed out");
+
+        } else {
+            ep->upstream_error = 1;
+            ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
+                          "upstream timed out");
+        }
+
+    } else {
+        if (ngx_event_pipe(ep, ev->write) == NGX_ABORT) {
+            ngx_http_proxy_finalize_request(p, 0);
+            return;
+        }
+    }
+
+    if (p->upstream->peer.connection) {
+
+#if (NGX_HTTP_FILE_CACHE)
+
+        if (ep->upstream_done && p->cachable) {
+            if (ngx_http_proxy_update_cache(p) == NGX_ERROR) {
+                ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+                ngx_http_proxy_finalize_request(p, 0);
+                return;
+            }
+
+        } else if (ep->upstream_eof && p->cachable) {
+
+            /* TODO: check length & update cache */
+
+            if (ngx_http_proxy_update_cache(p) == NGX_ERROR) {
+                ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+                ngx_http_proxy_finalize_request(p, 0);
+                return;
+            }
+        }
+
+#endif
+
+        if (ep->upstream_done || ep->upstream_eof || ep->upstream_error) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                           "http proxy upstream exit: " PTR_FMT, ep->out);
+            ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+            ngx_http_proxy_finalize_request(p, 0);
+            return;
+        }
+    }
+
+    if (ep->downstream_error) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                       "http proxy downstream error");
+        if (!p->cachable && p->upstream->peer.connection) {
+            ngx_http_proxy_finalize_request(p, 0);
+        }
+    }
+}
+
+
+static void ngx_http_proxy_next_upstream(ngx_http_proxy_ctx_t *p, int ft_type)
+{
+    int  status;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, p->request->connection->log, 0,
+                   "http proxy next upstream: %d", ft_type);
+
+    ngx_http_busy_unlock(p->lcf->busy_lock, &p->busy_lock);
+
+    if (ft_type != NGX_HTTP_PROXY_FT_HTTP_404) {
+        ngx_event_connect_peer_failed(&p->upstream->peer);
+    }
+
+    if (ft_type == NGX_HTTP_PROXY_FT_TIMEOUT) {
+        ngx_log_error(NGX_LOG_ERR, p->request->connection->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+    }
+
+    if (p->upstream->peer.cached && ft_type == NGX_HTTP_PROXY_FT_ERROR) {
+        status = 0;
+
+    } else {
+        switch(ft_type) {
+        case NGX_HTTP_PROXY_FT_TIMEOUT:
+            status = NGX_HTTP_GATEWAY_TIME_OUT;
+            break;
+
+        case NGX_HTTP_PROXY_FT_HTTP_500:
+            status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+            break;
+
+        case NGX_HTTP_PROXY_FT_HTTP_404:
+            status = NGX_HTTP_NOT_FOUND;
+            break;
+
+        /*
+         * NGX_HTTP_PROXY_FT_BUSY_LOCK and NGX_HTTP_PROXY_FT_MAX_WAITING
+         * never reach here
+         */
+
+        default:
+            status = NGX_HTTP_BAD_GATEWAY;
+        }
+    }
+
+    if (p->upstream->peer.connection) {
+        ngx_http_proxy_close_connection(p);
+    }
+
+    if (p->request->connection->write->eof) {
+        ngx_http_proxy_finalize_request(p, NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
+    if (status) {
+        p->state->status = status;
+
+        if (p->upstream->peer.tries == 0 || !(p->lcf->next_upstream & ft_type))
+        {
+
+#if (NGX_HTTP_CACHE)
+
+            if (p->stale && (p->lcf->use_stale & ft_type)) {
+                ngx_http_proxy_finalize_request(p,
+                                       ngx_http_proxy_send_cached_response(p));
+                return;
+            }
+
+#endif
+
+            ngx_http_proxy_finalize_request(p, status);
+            return;
+        }
+    }
+
+    if (p->lcf->busy_lock && !p->busy_locked) {
+        ngx_http_proxy_upstream_busy_lock(p);
+    } else {
+        ngx_http_proxy_connect(p);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http.c
@@ -0,0 +1,642 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+                                      ngx_array_t *locations,
+                                      void **loc_conf,
+                                      ngx_http_module_t *module,
+                                      ngx_uint_t ctx_index);
+
+int         ngx_http_max_module;
+
+ngx_uint_t  ngx_http_total_requests;
+uint64_t    ngx_http_total_sent;
+
+
+ngx_int_t  (*ngx_http_top_header_filter) (ngx_http_request_t *r);
+ngx_int_t  (*ngx_http_top_body_filter) (ngx_http_request_t *r, ngx_chain_t *ch);
+
+
+static ngx_command_t  ngx_http_commands[] = {
+
+    {ngx_string("http"),
+     NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+     ngx_http_block,
+     0,
+     0,
+     NULL},
+
+    ngx_null_command
+};
+
+    
+static ngx_core_module_t  ngx_http_module_ctx = {
+    ngx_string("http"),
+    NULL,
+    NULL
+};  
+
+
+ngx_module_t  ngx_http_module = {
+    NGX_MODULE,
+    &ngx_http_module_ctx,                  /* module context */
+    ngx_http_commands,                     /* module directives */
+    NGX_CORE_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+static char *ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char                        *rv;
+    ngx_uint_t                   mi, m, s, l, p, a, n;
+    ngx_uint_t                   port_found, addr_found, virtual_names;
+    ngx_conf_t                   pcf;
+    ngx_array_t                  in_ports;
+    ngx_listening_t             *ls;
+    ngx_http_listen_t           *lscf;
+    ngx_http_module_t           *module;
+    ngx_http_handler_pt         *h;
+    ngx_http_conf_ctx_t         *ctx;
+    ngx_http_in_port_t          *in_port, *inport;
+    ngx_http_in_addr_t          *in_addr, *inaddr;
+    ngx_http_server_name_t      *s_name, *name;
+    ngx_http_core_srv_conf_t   **cscfp, *cscf;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_main_conf_t   *cmcf;
+#if (WIN32)
+    ngx_iocp_conf_t             *iocpcf;
+#endif
+
+    /* the main http context */
+    ngx_test_null(ctx,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)),
+                  NGX_CONF_ERROR);
+
+    *(ngx_http_conf_ctx_t **) conf = ctx;
+
+    /* count the number of the http modules and set up their indices */
+
+    ngx_http_max_module = 0;
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        ngx_modules[m]->ctx_index = ngx_http_max_module++;
+    }
+
+    /* the main http main_conf, it's the same in the all http contexts */
+    ngx_test_null(ctx->main_conf,
+                  ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+                  NGX_CONF_ERROR);
+
+    /* the http null srv_conf, it's used to merge the server{}s' srv_conf's */
+    ngx_test_null(ctx->srv_conf,
+                  ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+                  NGX_CONF_ERROR);
+
+    /* the http null loc_conf, it's used to merge the server{}s' loc_conf's */
+    ngx_test_null(ctx->loc_conf,
+                  ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+                  NGX_CONF_ERROR);
+
+
+    /* create the main_conf, srv_conf and loc_conf in all http modules */
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+        mi = ngx_modules[m]->ctx_index;
+
+        if (module->pre_conf) {
+            if (module->pre_conf(cf) != NGX_OK) {
+                return NGX_CONF_ERROR;
+            }
+        }
+
+        if (module->create_main_conf) {
+            ngx_test_null(ctx->main_conf[mi], module->create_main_conf(cf),
+                          NGX_CONF_ERROR);
+        }
+
+        if (module->create_srv_conf) {
+            ngx_test_null(ctx->srv_conf[mi], module->create_srv_conf(cf),
+                          NGX_CONF_ERROR);
+        }
+
+        if (module->create_loc_conf) {
+            ngx_test_null(ctx->loc_conf[mi], module->create_loc_conf(cf),
+                          NGX_CONF_ERROR);
+        }
+    }
+
+    /* parse inside the http{} block */
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->module_type = NGX_HTTP_MODULE;
+    cf->cmd_type = NGX_HTTP_MAIN_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+
+    if (rv != NGX_CONF_OK) {
+        *cf = pcf;
+        return rv;
+    }
+
+    /*
+     * init http{} main_conf's, merge the server{}s' srv_conf's
+     * and its location{}s' loc_conf's
+     */
+
+    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+    cscfp = cmcf->servers.elts;
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+        mi = ngx_modules[m]->ctx_index;
+
+        /* init http{} main_conf's */
+
+        if (module->init_main_conf) {
+            rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+            if (rv != NGX_CONF_OK) {
+                *cf = pcf;
+                return rv;
+            }
+        }
+
+        for (s = 0; s < cmcf->servers.nelts; s++) {
+
+            /* merge the server{}s' srv_conf's */
+
+            if (module->merge_srv_conf) {
+                rv = module->merge_srv_conf(cf,
+                                            ctx->srv_conf[mi],
+                                            cscfp[s]->ctx->srv_conf[mi]);
+                if (rv != NGX_CONF_OK) {
+                    *cf = pcf;
+                    return rv;
+                }
+            }
+
+            if (module->merge_loc_conf) {
+
+                /* merge the server{}'s loc_conf */
+
+                rv = module->merge_loc_conf(cf,
+                                            ctx->loc_conf[mi],
+                                            cscfp[s]->ctx->loc_conf[mi]);
+                if (rv != NGX_CONF_OK) {
+                    *cf = pcf;
+                    return rv;
+                }
+
+                /* merge the locations{}' loc_conf's */
+
+                rv = ngx_http_merge_locations(cf, &cscfp[s]->locations,
+                                              cscfp[s]->ctx->loc_conf,
+                                              module, mi);
+                if (rv != NGX_CONF_OK) {
+                    *cf = pcf;
+                    return rv;
+                }
+
+#if 0
+                clcfp = (ngx_http_core_loc_conf_t **) cscfp[s]->locations.elts;
+
+                for (l = 0; l < cscfp[s]->locations.nelts; l++) {
+                    rv = module->merge_loc_conf(cf,
+                                                cscfp[s]->ctx->loc_conf[mi],
+                                                clcfp[l]->loc_conf[mi]);
+                    if (rv != NGX_CONF_OK) {
+                        *cf = pcf;
+                        return rv;
+                    }
+                }
+#endif
+            }
+        }
+    }
+
+    /* we needed "http"'s cf->ctx while merging configuration */
+    *cf = pcf;
+
+    /* init lists of the handlers */
+
+    ngx_init_array(cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers,
+                   cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+                   NGX_CONF_ERROR);
+    cmcf->phases[NGX_HTTP_REWRITE_PHASE].type = NGX_OK;
+
+
+    /* the special find config phase for single handler */
+
+    ngx_init_array(cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].handlers,
+                   cf->cycle->pool, 1, sizeof(ngx_http_handler_pt),
+                   NGX_CONF_ERROR);
+    cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].type = NGX_OK;
+
+    ngx_test_null(h, ngx_push_array(
+                           &cmcf->phases[NGX_HTTP_FIND_CONFIG_PHASE].handlers),
+                  NGX_CONF_ERROR);
+    *h = ngx_http_find_location_config;
+
+
+    ngx_init_array(cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers,
+                   cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+                   NGX_CONF_ERROR);
+    cmcf->phases[NGX_HTTP_ACCESS_PHASE].type = NGX_DECLINED;
+
+
+    ngx_init_array(cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers,
+                   cf->cycle->pool, 10, sizeof(ngx_http_handler_pt),
+                   NGX_CONF_ERROR);
+    cmcf->phases[NGX_HTTP_CONTENT_PHASE].type = NGX_OK;
+
+
+    /*
+     * create the lists of the ports, the addresses and the server names
+     * to allow quickly find the server core module configuration at run-time
+     */
+
+    ngx_init_array(in_ports, cf->pool, 10, sizeof(ngx_http_in_port_t),
+                   NGX_CONF_ERROR);
+
+    /* "server" directives */
+    cscfp = cmcf->servers.elts;
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        /* "listen" directives */
+        lscf = cscfp[s]->listen.elts;
+        for (l = 0; l < cscfp[s]->listen.nelts; l++) {
+
+            port_found = 0;
+
+            /* AF_INET only */
+
+            in_port = in_ports.elts;
+            for (p = 0; p < in_ports.nelts; p++) {
+
+                if (lscf[l].port == in_port[p].port) {
+
+                    /* the port is already in the port list */
+
+                    port_found = 1;
+                    addr_found = 0;
+
+                    in_addr = in_port[p].addrs.elts;
+                    for (a = 0; a < in_port[p].addrs.nelts; a++) {
+
+                        if (lscf[l].addr == in_addr[a].addr) {
+
+                            /* the address is already bound to this port */
+
+                            /* "server_name" directives */
+                            s_name = cscfp[s]->server_names.elts;
+                            for (n = 0; n < cscfp[s]->server_names.nelts; n++) {
+
+                                /*
+                                 * add the server name and server core module
+                                 * configuration to the address:port
+                                 */
+
+                                /* TODO: duplicate names can be checked here */
+
+                                ngx_test_null(name,
+                                              ngx_push_array(&in_addr[a].names),
+                                              NGX_CONF_ERROR);
+
+                                name->name = s_name[n].name;
+                                name->core_srv_conf = s_name[n].core_srv_conf;
+                            }
+
+                            /*
+                             * check duplicate "default" server that
+                             * serves this address:port
+                             */
+
+                            if (lscf[l].default_server) {
+                                if (in_addr[a].default_server) {
+                                    ngx_log_error(NGX_LOG_ERR, cf->log, 0,
+                                           "duplicate default server in %s:%d",
+                                           lscf[l].file_name.data,
+                                           lscf[l].line);
+
+                                    return NGX_CONF_ERROR;
+                                }
+
+                                in_addr[a].core_srv_conf = cscfp[s];
+                                in_addr[a].default_server = 1;
+                            }
+
+                            addr_found = 1;
+
+                            break;
+
+                        } else if (in_addr[a].addr == INADDR_ANY) {
+
+                            /*
+                             * "*:port" must be the last resort so move it
+                             * to the end of the address list and add
+                             * the new address at its place
+                             */
+
+                            ngx_test_null(inaddr,
+                                          ngx_push_array(&in_port[p].addrs),
+                                          NGX_CONF_ERROR);
+
+                            ngx_memcpy(inaddr, &in_addr[a],
+                                       sizeof(ngx_http_in_addr_t));
+
+                            in_addr[a].addr = lscf[l].addr;
+                            in_addr[a].default_server = lscf[l].default_server;
+                            in_addr[a].core_srv_conf = cscfp[s];
+
+                            /*
+                             * create the empty list of the server names that
+                             * can be served on this address:port
+                             */
+
+                            ngx_init_array(inaddr->names, cf->pool, 10,
+                                           sizeof(ngx_http_server_name_t),
+                                           NGX_CONF_ERROR);
+
+                            addr_found = 1;
+
+                            break;
+                        }
+                    }
+
+                    if (!addr_found) {
+
+                        /*
+                         * add the address to the addresses list that
+                         * bound to this port
+                         */
+
+                        ngx_test_null(inaddr,
+                                      ngx_push_array(&in_port[p].addrs),
+                                      NGX_CONF_ERROR);
+
+                        inaddr->addr = lscf[l].addr;
+                        inaddr->default_server = lscf[l].default_server;
+                        inaddr->core_srv_conf = cscfp[s];
+
+                        /*
+                         * create the empty list of the server names that
+                         * can be served on this address:port
+                         */
+
+                        ngx_init_array(inaddr->names, cf->pool, 10,
+                                       sizeof(ngx_http_server_name_t),
+                                       NGX_CONF_ERROR);
+                    }
+                }
+            }
+
+            if (!port_found) {
+
+                /* add the port to the in_port list */
+
+                ngx_test_null(in_port,
+                              ngx_push_array(&in_ports),
+                              NGX_CONF_ERROR);
+
+                in_port->port = lscf[l].port;
+
+                ngx_test_null(in_port->port_text.data, ngx_palloc(cf->pool, 7),
+                              NGX_CONF_ERROR);
+                in_port->port_text.len = ngx_snprintf((char *)
+                                                      in_port->port_text.data,
+                                                      7, ":%d",
+                                                      in_port->port);
+
+                /* create list of the addresses that bound to this port ... */
+
+                ngx_init_array(in_port->addrs, cf->pool, 10,
+                               sizeof(ngx_http_in_addr_t),
+                               NGX_CONF_ERROR);
+
+                ngx_test_null(inaddr, ngx_push_array(&in_port->addrs),
+                              NGX_CONF_ERROR);
+
+                /* ... and add the address to this list */
+
+                inaddr->addr = lscf[l].addr;
+                inaddr->default_server = lscf[l].default_server;
+                inaddr->core_srv_conf = cscfp[s];
+
+                /*
+                 * create the empty list of the server names that
+                 * can be served on this address:port
+                 */
+
+                ngx_init_array(inaddr->names, cf->pool, 10,
+                               sizeof(ngx_http_server_name_t),
+                               NGX_CONF_ERROR);
+            }
+        }
+    }
+
+    /* optimize the lists of the ports, the addresses and the server names */
+
+    /* AF_INET only */
+
+    in_port = in_ports.elts;
+    for (p = 0; p < in_ports.nelts; p++) {
+
+        /* check whether the all server names point to the same server */
+
+        in_addr = in_port[p].addrs.elts;
+        for (a = 0; a < in_port[p].addrs.nelts; a++) {
+
+            virtual_names = 0;
+
+            name = in_addr[a].names.elts;
+            for (n = 0; n < in_addr[a].names.nelts; n++) {
+                if (in_addr[a].core_srv_conf != name[n].core_srv_conf) {
+                    virtual_names = 1;
+                    break;
+                }
+            }
+
+            /*
+             * if the all server names point to the same server
+             * then we do not need to check them at run-time
+             */
+
+            if (!virtual_names) {
+                in_addr[a].names.nelts = 0;
+            }
+        }
+
+        /*
+         * if there's the binding to "*:port" then we need to bind()
+         * to "*:port" only and ignore the other bindings
+         */
+
+        if (in_addr[a - 1].addr == INADDR_ANY) {
+            a--;
+
+        } else {
+            a = 0;
+        }
+
+        in_addr = in_port[p].addrs.elts;
+        while (a < in_port[p].addrs.nelts) {
+
+            ls = ngx_listening_inet_stream_socket(cf, in_addr[a].addr,
+                                                  in_port[p].port);
+            if (ls == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            ls->backlog = -1;
+#if 0
+#if 0
+            ls->nonblocking = 1;
+#else
+            ls->nonblocking = 0;
+#endif
+#endif
+            ls->addr_ntop = 1;
+
+            ls->handler = ngx_http_init_connection;
+
+            cscf = in_addr[a].core_srv_conf;
+            ls->pool_size = cscf->connection_pool_size;
+            ls->post_accept_timeout = cscf->post_accept_timeout;
+
+            clcf = cscf->ctx->loc_conf[ngx_http_core_module.ctx_index];
+            ls->log = clcf->err_log;
+
+#if (WIN32)
+            iocpcf = ngx_event_get_conf(cf->cycle->conf_ctx, ngx_iocp_module);
+            if (iocpcf->acceptex_read) {
+                ls->post_accept_buffer_size = cscf->client_header_buffer_size;
+            }
+#endif
+
+            ls->ctx = ctx;
+
+            if (in_port[p].addrs.nelts > 1) {
+
+                in_addr = in_port[p].addrs.elts;
+                if (in_addr[in_port[p].addrs.nelts - 1].addr != INADDR_ANY) {
+
+                    /*
+                     * if this port has not the "*:port" binding then create
+                     * the separate ngx_http_in_port_t for the all bindings
+                     */
+
+                    ngx_test_null(inport,
+                                  ngx_palloc(cf->pool,
+                                             sizeof(ngx_http_in_port_t)),
+                                  NGX_CONF_ERROR);
+
+                    inport->port = in_port[p].port;
+                    inport->port_text = in_port[p].port_text;
+
+                    /* init list of the addresses ... */
+
+                    ngx_init_array(inport->addrs, cf->pool, 1,
+                                   sizeof(ngx_http_in_addr_t),
+                                   NGX_CONF_ERROR);
+
+                    /* ... and set up it with the first address */
+
+                    inport->addrs.nelts = 1;
+                    inport->addrs.elts = in_port[p].addrs.elts;
+
+                    ls->servers = inport;
+
+                    /* prepare for the next cycle */
+
+                    in_port[p].addrs.elts = (char *) in_port[p].addrs.elts
+                                                       + in_port[p].addrs.size;
+                    in_port[p].addrs.nelts--;
+
+                    in_addr = (ngx_http_in_addr_t *) in_port[p].addrs.elts;
+                    a = 0;
+
+                    continue;
+                }
+            }
+
+            ls->servers = &in_port[p];
+            a++;
+        }
+    }
+
+#if (NGX_DEBUG)
+    in_port = in_ports.elts;
+    for (p = 0; p < in_ports.nelts; p++) {
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+                      "port: %d %08x", in_port[p].port, &in_port[p]);
+        in_addr = in_port[p].addrs.elts;
+        for (a = 0; a < in_port[p].addrs.nelts; a++) {
+            u_char ip[20];
+            ngx_inet_ntop(AF_INET, &in_addr[a].addr, ip, 20);
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+                           "%s %08x", ip, in_addr[a].core_srv_conf);
+            s_name = in_addr[a].names.elts;
+            for (n = 0; n < in_addr[a].names.nelts; n++) {
+                 ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0,
+                                "%s %08x", s_name[n].name.data,
+                                s_name[n].core_srv_conf);
+            }
+        }
+    }
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_merge_locations(ngx_conf_t *cf,
+                                      ngx_array_t *locations,
+                                      void **loc_conf,
+                                      ngx_http_module_t *module,
+                                      ngx_uint_t ctx_index)
+{
+    char                       *rv;
+    ngx_uint_t                  i;
+    ngx_http_core_loc_conf_t  **clcfp;
+
+    clcfp = /* (ngx_http_core_loc_conf_t **) */ locations->elts;
+
+    for (i = 0; i < locations->nelts; i++) {
+        rv = module->merge_loc_conf(cf, loc_conf[ctx_index],
+                                    clcfp[i]->loc_conf[ctx_index]);
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+
+        rv = ngx_http_merge_locations(cf, &clcfp[i]->locations,
+                                      clcfp[i]->loc_conf, module, ctx_index);
+        if (rv != NGX_CONF_OK) {
+            return rv;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http.h
@@ -0,0 +1,112 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_H_INCLUDED_
+#define _NGX_HTTP_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_garbage_collector.h>
+
+typedef struct ngx_http_request_s  ngx_http_request_t;
+typedef struct ngx_http_cleanup_s  ngx_http_cleanup_t;
+
+#if (NGX_HTTP_CACHE)
+#include <ngx_http_cache.h>
+#endif
+/* STUB */
+#include <ngx_http_cache.h>
+
+#include <ngx_http_request.h>
+#include <ngx_http_config.h>
+#include <ngx_http_busy_lock.h>
+#include <ngx_http_log_handler.h>
+#include <ngx_http_core_module.h>
+
+#if (NGX_HTTP_SSL)
+#include <ngx_http_ssl_module.h>
+#endif
+
+
+typedef struct {
+    u_int     connection;
+
+    /*
+     * we declare "action" as "char *" because the actions are usually
+     * the static strings and in the "u_char *" case we have to override
+     * all the time their types
+     */
+
+    char     *action;
+    u_char   *client;
+    u_char   *url;
+} ngx_http_log_ctx_t;
+
+
+#define ngx_http_get_module_ctx(r, module)       r->ctx[module.ctx_index]
+#define ngx_http_get_module_err_ctx(r, module)                                \
+         (r->err_ctx ? r->err_ctx[module.ctx_index] : r->ctx[module.ctx_index])
+
+#define ngx_http_create_ctx(r, cx, module, size, error)                       \
+            do {                                                              \
+                ngx_test_null(cx, ngx_pcalloc(r->pool, size), error);         \
+                r->ctx[module.ctx_index] = cx;                                \
+            } while (0)
+
+#define ngx_http_delete_ctx(r, module)                                        \
+            r->ctx[module.ctx_index] = NULL;
+
+
+void ngx_http_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b);
+ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r);
+ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b);
+
+ngx_int_t ngx_http_find_server_conf(ngx_http_request_t *r);
+void ngx_http_handler(ngx_http_request_t *r);
+void ngx_http_finalize_request(ngx_http_request_t *r, int error);
+void ngx_http_writer(ngx_event_t *wev);
+
+void ngx_http_empty_handler(ngx_event_t *wev);
+
+ngx_int_t ngx_http_send_last(ngx_http_request_t *r);
+void ngx_http_close_request(ngx_http_request_t *r, int error);
+void ngx_http_close_connection(ngx_connection_t *c);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_send_header(ngx_http_request_t *r);
+ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, int error);
+
+
+time_t ngx_http_parse_time(u_char *value, size_t len);
+size_t ngx_http_get_time(char *buf, time_t t);
+
+
+
+ngx_int_t ngx_http_discard_body(ngx_http_request_t *r);
+
+
+extern ngx_module_t  ngx_http_module;
+
+
+extern ngx_uint_t  ngx_http_total_requests;
+extern uint64_t    ngx_http_total_sent;
+
+
+extern ngx_http_output_header_filter_pt  ngx_http_top_header_filter;
+extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;
+
+
+/* STUB */
+ngx_int_t ngx_http_log_handler(ngx_http_request_t *r);
+/**/
+
+
+#endif /* _NGX_HTTP_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_busy_lock.c
@@ -0,0 +1,300 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static int ngx_http_busy_lock_look_cachable(ngx_http_busy_lock_t *bl,
+                                            ngx_http_busy_lock_ctx_t *bc,
+                                            int lock);
+
+
+int ngx_http_busy_lock(ngx_http_busy_lock_t *bl, ngx_http_busy_lock_ctx_t *bc)
+{
+    if (bl->busy < bl->max_busy) {
+        bl->busy++;
+
+        if (bc->time) {
+            bc->time = 0;
+            bl->waiting--;
+        }
+
+        return NGX_OK;
+    }
+
+    if (bc->time) {
+        if (bc->time < bl->timeout) {
+            ngx_add_timer(bc->event, 1000);
+            return NGX_AGAIN;
+        }
+
+        bl->waiting--;
+        return NGX_DONE;
+
+    }
+
+    if (bl->timeout == 0) {
+        return NGX_DONE;
+    }
+
+    if (bl->waiting < bl->max_waiting) {
+        bl->waiting++;
+
+        ngx_add_timer(bc->event, 1000);
+        bc->event->event_handler = bc->event_handler;
+
+        /* TODO: ngx_handle_level_read_event() */
+
+        return NGX_AGAIN;
+    }
+
+    return NGX_ERROR;
+}
+
+
+int ngx_http_busy_lock_cachable(ngx_http_busy_lock_t *bl,
+                                ngx_http_busy_lock_ctx_t *bc, int lock)
+{
+    int  rc;
+
+    rc = ngx_http_busy_lock_look_cachable(bl, bc, lock);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, bc->event->log, 0,
+                   "http busylock: %d w:%d mw::%d",
+                   rc, bl->waiting, bl->max_waiting);
+
+    if (rc == NGX_OK) {  /* no the same request, there's free slot */
+        return NGX_OK;
+    }
+
+    if (rc == NGX_ERROR && !lock) { /* no the same request, no free slot */
+        return NGX_OK;
+    }
+
+    /* rc == NGX_AGAIN:  the same request */
+
+    if (bc->time) {
+        if (bc->time < bl->timeout) {
+            ngx_add_timer(bc->event, 1000);
+            return NGX_AGAIN;
+        }
+
+        bl->waiting--;
+        return NGX_DONE;
+
+    }
+
+    if (bl->timeout == 0) {
+        return NGX_DONE;
+    }
+
+    if (bl->waiting < bl->max_waiting) {
+        bl->waiting++;
+        ngx_add_timer(bc->event, 1000);
+        bc->event->event_handler = bc->event_handler;
+
+        /* TODO: ngx_handle_level_read_event() */
+
+        return NGX_AGAIN;
+    }
+
+    return NGX_ERROR;
+}
+
+
+void ngx_http_busy_unlock(ngx_http_busy_lock_t *bl,
+                          ngx_http_busy_lock_ctx_t *bc)
+{
+    if (bl == NULL) {
+        return;
+    }
+
+    if (bl->md5) {
+        bl->md5_mask[bc->slot / 8] &= ~(1 << (bc->slot & 7));
+        bl->cachable--;
+    }
+
+    bl->busy--;
+}
+
+
+static int ngx_http_busy_lock_look_cachable(ngx_http_busy_lock_t *bl,
+                                            ngx_http_busy_lock_ctx_t *bc,
+                                            int lock)
+{
+    int    i, b, cachable, free;
+    u_int  mask;
+
+    b = 0;
+    cachable = 0;
+    free = -1;
+
+#if (NGX_SUPPRESS_WARN)
+    mask = 0;
+#endif
+
+    for (i = 0; i < bl->max_busy; i++) {
+
+        if ((b & 7) == 0) {
+            mask = bl->md5_mask[i / 8];
+        }
+
+        if (mask & 1) {
+            if (ngx_memcmp(&bl->md5[i * 16], bc->md5, 16) == 0) {
+                return NGX_AGAIN;
+            }
+            cachable++;
+
+        } else if (free == -1) {
+            free = i;
+        }
+
+#if 1
+        if (cachable == bl->cachable) {
+            if (free == -1 && cachable < bl->max_busy) {
+                free = i + 1;
+            }
+
+            break;
+        }
+#endif
+
+        mask >>= 1;
+        b++;
+    }
+
+    if (free == -1) {
+        return NGX_ERROR;
+    }
+
+    if (lock) {
+        if (bl->busy == bl->max_busy) {
+            return NGX_ERROR;
+        }
+
+        ngx_memcpy(&bl->md5[free * 16], bc->md5, 16);
+        bl->md5_mask[free / 8] |= 1 << (free & 7);
+        bc->slot = free;
+
+        bl->cachable++;
+        bl->busy++;
+    }
+
+    return NGX_OK;
+}
+
+
+char *ngx_http_set_busy_lock_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf)
+{
+    char  *p = conf;
+
+    ngx_uint_t             i, dup, invalid;
+    ngx_str_t             *value, line;
+    ngx_http_busy_lock_t  *bl, **blp;
+
+    blp = (ngx_http_busy_lock_t **) (p + cmd->offset);
+    if (*blp) {
+        return "is duplicate";
+    }
+
+    /* ngx_calloc_shared() */
+    if (!(bl = ngx_pcalloc(cf->pool, sizeof(ngx_http_busy_lock_t)))) {
+        return NGX_CONF_ERROR;
+    }
+    *blp = bl;
+
+    /* ngx_calloc_shared() */
+    if (!(bl->mutex = ngx_pcalloc(cf->pool, sizeof(ngx_event_mutex_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    dup = 0;
+    invalid = 0;
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (value[i].data[1] != '=') {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        switch (value[i].data[0]) {
+
+        case 'b':
+            if (bl->max_busy) {
+                dup = 1;
+                break;
+            }
+
+            bl->max_busy = ngx_atoi(value[i].data + 2, value[i].len - 2);
+            if (bl->max_busy == NGX_ERROR) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        case 'w':
+            if (bl->max_waiting) {
+                dup = 1;
+                break;
+            }
+
+            bl->max_waiting = ngx_atoi(value[i].data + 2, value[i].len - 2);
+            if (bl->max_waiting == NGX_ERROR) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        case 't':
+            if (bl->timeout) {
+                dup = 1;
+                break;
+            }
+
+            line.len = value[i].len - 2;
+            line.data = value[i].data + 2;
+
+            bl->timeout = ngx_parse_time(&line, 1);
+            if (bl->timeout == NGX_ERROR) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        default:
+            invalid = 1;
+        }
+
+        if (dup) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (invalid) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    if (bl->timeout == 0 && bl->max_waiting) {
+        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                           "busy lock waiting is useless with zero timeout, ignoring");
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_busy_lock.h
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_BUSY_LOCK_H_INCLUDED_
+#define _NGX_HTTP_BUSY_LOCK_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    u_char             *md5_mask;
+    char               *md5;
+    int                 cachable;
+
+    int                 busy;
+    int                 max_busy;
+
+    int                 waiting;
+    int                 max_waiting;
+
+    time_t              timeout;
+
+    ngx_event_mutex_t  *mutex;
+} ngx_http_busy_lock_t;
+
+
+typedef struct {
+    time_t         time;
+    ngx_event_t   *event;
+    void         (*event_handler)(ngx_event_t *ev);
+    u_char        *md5;
+    int            slot;
+} ngx_http_busy_lock_ctx_t;
+
+
+int ngx_http_busy_lock(ngx_http_busy_lock_t *bl, ngx_http_busy_lock_ctx_t *bc);
+int ngx_http_busy_lock_cachable(ngx_http_busy_lock_t *bl,
+                                ngx_http_busy_lock_ctx_t *bc, int lock);
+void ngx_http_busy_unlock(ngx_http_busy_lock_t *bl,
+                          ngx_http_busy_lock_ctx_t *bc);
+
+char *ngx_http_set_busy_lock_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf);
+
+
+#endif /* _NGX_HTTP_BUSY_LOCK_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_cache.c
@@ -0,0 +1,480 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+
+static ngx_http_module_t  ngx_http_cache_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL                                   /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_cache_module = {
+    NGX_MODULE,
+    &ngx_http_cache_module_ctx,            /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+ngx_http_cache_t *ngx_http_cache_get(ngx_http_cache_hash_t *hash,
+                                     ngx_http_cleanup_t *cleanup,
+                                     ngx_str_t *key, uint32_t *crc)
+{
+    ngx_uint_t         i;
+    ngx_http_cache_t  *c;
+
+    *crc = ngx_crc(key->data, key->len);
+
+    c = hash->elts + *crc % hash->hash * hash->nelts;
+
+    if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+        return (void *) NGX_ERROR;
+    }
+
+    for (i = 0; i < hash->nelts; i++) {
+        if (c[i].crc == *crc
+            && c[i].key.len == key->len
+            && ngx_rstrncmp(c[i].key.data, key->data, key->len) == 0)
+        {
+#if 0
+            if (c[i].expired) {
+                ngx_mutex_unlock(&hash->mutex);
+                return (void *) NGX_AGAIN;
+            }
+#endif
+
+            c[i].refs++;
+
+            if ((!(c[i].notify && (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT)))
+                && (ngx_cached_time - c[i].updated >= hash->update))
+            {
+                c[i].expired = 1;
+            }
+
+            ngx_mutex_unlock(&hash->mutex);
+
+            if (cleanup) {
+                cleanup->data.cache.hash = hash;
+                cleanup->data.cache.cache = &c[i];
+                cleanup->valid = 1;
+                cleanup->cache = 1;
+            }
+
+            return &c[i];
+        }
+    }
+
+    ngx_mutex_unlock(&hash->mutex);
+
+    return NULL;
+}
+
+
+ngx_http_cache_t *ngx_http_cache_alloc(ngx_http_cache_hash_t *hash,
+                                       ngx_http_cache_t *cache,
+                                       ngx_http_cleanup_t *cleanup,
+                                       ngx_str_t *key, uint32_t crc,
+                                       ngx_str_t *value, ngx_log_t *log)
+{
+    time_t             old;
+    ngx_uint_t         i;
+    ngx_http_cache_t  *c;
+
+    old = ngx_cached_time + 1;
+
+    c = hash->elts + crc % hash->hash * hash->nelts;
+
+    if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+        return (void *) NGX_ERROR;
+    }
+
+    if (cache == NULL) {
+
+        /* allocate a new entry */
+
+        for (i = 0; i < hash->nelts; i++) {
+            if (c[i].refs > 0) {
+                /* a busy entry */
+                continue;
+            }
+
+            if (c[i].key.len == 0) {
+                /* a free entry is found */
+                cache = &c[i];
+                break;
+            }
+
+            /* looking for the oldest cache entry */
+
+            if (old > c[i].accessed) {
+
+                old = c[i].accessed;
+                cache = &c[i];
+            }
+        }
+
+        if (cache == NULL) {
+            ngx_mutex_unlock(&hash->mutex);
+            return NULL;
+        }
+
+        ngx_http_cache_free(cache, key, value, log);
+
+        if (cache->key.data == NULL) {
+            cache->key.data = ngx_alloc(key->len, log);
+            if (cache->key.data == NULL) {
+                ngx_http_cache_free(cache, NULL, NULL, log);
+                ngx_mutex_unlock(&hash->mutex);
+                return NULL;
+            }
+        }
+
+        cache->key.len = key->len;
+        ngx_memcpy(cache->key.data, key->data, key->len);
+
+    } else if (value) {
+        ngx_http_cache_free(cache, key, value, log);
+    }
+
+    if (value) {
+        if (cache->data.value.data == NULL) {
+            cache->data.value.data = ngx_alloc(value->len, log);
+            if (cache->data.value.data == NULL) {
+                ngx_http_cache_free(cache, NULL, NULL, log);
+                ngx_mutex_unlock(&hash->mutex);
+                return NULL;
+            }
+        }
+
+        cache->data.value.len = value->len;
+        ngx_memcpy(cache->data.value.data, value->data, value->len);
+    }
+
+    cache->crc = crc;
+    cache->key.len = key->len;
+
+    cache->refs = 1;
+    cache->count = 0;
+
+    cache->deleted = 0;
+    cache->expired = 0;
+    cache->memory = 0;
+    cache->mmap = 0;
+    cache->notify = 0;
+
+    if (cleanup) {
+        cleanup->data.cache.hash = hash;
+        cleanup->data.cache.cache = cache;
+        cleanup->valid = 1;
+        cleanup->cache = 1;
+    }
+
+    ngx_mutex_unlock(&hash->mutex);
+
+    return cache;
+}
+
+
+void ngx_http_cache_free(ngx_http_cache_t *cache,
+                         ngx_str_t *key, ngx_str_t *value, ngx_log_t *log)
+{
+    if (cache->memory) {
+        if (cache->data.value.data
+            && (value == NULL || value->len > cache->data.value.len))
+        {
+            ngx_free(cache->data.value.data);
+            cache->data.value.data = NULL;
+        }
+    }
+
+    /* TODO: mmap */
+
+    cache->data.value.len = 0;
+
+    if (cache->fd != NGX_INVALID_FILE) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+                       "http cache close fd: %d", cache->fd);
+
+        if (ngx_close_file(cache->fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          cache->key.data);
+        }
+
+        cache->fd = NGX_INVALID_FILE;
+    }
+
+    if (cache->key.data && (key == NULL || key->len > cache->key.len)) {
+        ngx_free(cache->key.data);
+        cache->key.data = NULL;
+    }
+
+    cache->key.len = 0;
+
+    cache->refs = 0;
+}
+
+
+void ngx_http_cache_lock(ngx_http_cache_hash_t *hash, ngx_http_cache_t *cache)
+{
+    if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+        return;
+    }
+}
+
+
+void ngx_http_cache_unlock(ngx_http_cache_hash_t *hash,
+                           ngx_http_cache_t *cache, ngx_log_t *log)
+{
+    if (ngx_mutex_lock(&hash->mutex) == NGX_ERROR) {
+        return;
+    }
+
+    cache->refs--;
+
+    if (cache->refs == 0 && cache->deleted) {
+        ngx_http_cache_free(cache, NULL, NULL, log);
+    }
+
+    ngx_mutex_unlock(&hash->mutex);
+}
+
+
+#if 0
+
+ngx_http_cache_add_file_event(ngx_http_cache_hash_t *hash,
+                              ngx_http_cache_t *cache)
+{
+    ngx_event_t                 *ev;
+    ngx_http_cache_event_ctx_t  *ctx;
+
+    ev = &ngx_cycle->read_events[fd];
+    ngx_memzero(ev, sizeof(ngx_event_t);
+
+    ev->data = data;
+    ev->event_handler = ngx_http_cache_invalidate;
+
+    return ngx_add_event(ev, NGX_VNODE_EVENT, 0);
+}
+
+
+void ngx_http_cache_invalidate(ngx_event_t *ev)
+{
+    ngx_http_cache_event_ctx_t  *ctx;
+
+    ctx = ev->data;
+
+    ngx_http_cache_lock(&ctx->hash->mutex);
+
+    if (ctx->cache->refs == 0)
+        ngx_http_cache_free(ctx->cache, NULL, NULL, ctx->log);
+
+    } else {
+        ctx->cache->deleted = 1;
+    }
+
+    ngx_http_cache_unlock(&ctx->hash->mutex);
+}
+
+#endif
+
+
+/* TODO: currently fd only */
+
+ngx_int_t ngx_http_send_cached(ngx_http_request_t *r)
+{
+    ngx_int_t            rc;
+    ngx_hunk_t          *h;
+    ngx_chain_t          out;
+    ngx_http_log_ctx_t  *ctx;
+
+    ctx = r->connection->log->data;
+    ctx->action = "sending response to client";
+
+    r->headers_out.status = NGX_HTTP_OK;
+    r->headers_out.content_length_n = r->cache->data.size;
+    r->headers_out.last_modified_time = r->cache->last_modified;
+
+    if (ngx_http_set_content_type(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    /* we need to allocate all before the header would be sent */
+
+    if (!(h = ngx_pcalloc(r->pool, sizeof(ngx_hunk_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (!(h->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)))) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+        return rc;
+    }
+
+    h->type = r->main ? NGX_HUNK_FILE : NGX_HUNK_FILE|NGX_HUNK_LAST;
+
+    h->file_pos = 0;
+    h->file_last = r->cache->data.size;
+
+    h->file->fd = r->cache->fd;
+    h->file->log = r->connection->log;
+
+    out.hunk = h;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+char *ngx_http_set_cache_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_int_t              i, j, dup, invalid;
+    ngx_str_t              *value, line;
+    ngx_http_cache_t       *c;
+    ngx_http_cache_hash_t  *ch, **chp;
+
+    chp = (ngx_http_cache_hash_t **) (p + cmd->offset);
+    if (*chp) {
+        return "is duplicate";
+    }
+
+    if (!(ch = ngx_pcalloc(cf->pool, sizeof(ngx_http_cache_hash_t)))) {
+        return NGX_CONF_ERROR;
+    }
+    *chp = ch;
+
+    dup = 0;
+    invalid = 0;
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (value[i].data[1] != '=') {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        switch (value[i].data[0]) {
+
+        case 'h':
+            if (ch->hash) {
+                dup = 1;
+                break;
+            }
+
+            ch->hash = ngx_atoi(value[i].data + 2, value[i].len - 2);
+            if (ch->hash == (size_t)  NGX_ERROR || ch->hash == 0) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        case 'n':
+            if (ch->nelts) {
+                dup = 1;
+                break;
+            }
+
+            ch->nelts = ngx_atoi(value[i].data + 2, value[i].len - 2);
+            if (ch->nelts == (size_t) NGX_ERROR || ch->nelts == 0) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        case 'l':
+            if (ch->life) {
+                dup = 1;
+                break;
+            }
+
+            line.len = value[i].len - 2;
+            line.data = value[i].data + 2;
+
+            ch->life = ngx_parse_time(&line, 1);
+            if (ch->life == NGX_ERROR || ch->life == 0) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        case 'u':
+            if (ch->update) {
+                dup = 1;
+                break;
+            }
+
+            line.len = value[i].len - 2;
+            line.data = value[i].data + 2;
+
+            ch->update = ngx_parse_time(&line, 1);
+            if (ch->update == NGX_ERROR || ch->update == 0) {
+                invalid = 1;
+                break;
+            }
+
+            continue;
+
+        default:
+            invalid = 1;
+        }
+
+        if (dup) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "duplicate value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (invalid) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    ch->elts = ngx_pcalloc(cf->pool,
+                           ch->hash * ch->nelts * sizeof(ngx_http_cache_t));
+    if (ch->elts == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (i = 0; i < (ngx_int_t) ch->hash; i++) {
+        c = ch->elts + i * ch->nelts;
+
+        for (j = 0; j < (ngx_int_t) ch->nelts; j++) {
+            c[j].fd = NGX_INVALID_FILE;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_cache.h
@@ -0,0 +1,131 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CACHE_H_INCLUDED_
+#define _NGX_HTTP_CACHE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+/*
+ * The 7 uses before an allocation.
+ * We can use maximum 7 bits, i.e up to the 127 uses.
+ */
+#define NGX_HTTP_CACHE_LAZY_ALLOCATION_BITS  3
+
+typedef struct {
+    uint32_t         crc;
+    ngx_str_t        key;
+    time_t           accessed;
+
+    unsigned         refs:20;    /* 1048576 references */
+
+    unsigned         count:NGX_HTTP_CACHE_LAZY_ALLOCATION_BITS;
+
+    unsigned         deleted:1;
+    unsigned         expired:1;
+    unsigned         memory:1;
+    unsigned         mmap:1;
+    unsigned         notify:1;
+
+    ngx_fd_t         fd;
+#if (NGX_USE_HTTP_FILE_CACHE_UNIQ)
+    ngx_file_uniq_t  uniq;       /* no needed with kqueue */
+#endif
+    time_t           last_modified;
+    time_t           updated;
+
+    union {
+        off_t        size;
+        ngx_str_t    value;
+    } data;
+} ngx_http_cache_t;
+
+
+typedef struct {
+    time_t       expires;
+    time_t       last_modified;
+    time_t       date;
+    off_t        length;
+    size_t       key_len;
+    char         key[1];
+} ngx_http_cache_header_t;
+
+
+#define NGX_HTTP_CACHE_HASH   7
+#define NGX_HTTP_CACHE_NELTS  4
+
+typedef struct {
+    ngx_http_cache_t         *elts;
+    size_t                    hash;
+    size_t                    nelts;
+    time_t                    life;
+    time_t                    update;
+#if (NGX_THREADS)
+    ngx_mutex_t               mutex;
+#endif
+    ngx_pool_t               *pool;
+} ngx_http_cache_hash_t;
+
+
+typedef struct {
+    ngx_http_cache_hash_t    *hash;
+    ngx_http_cache_t         *cache;
+    ngx_file_t                file;
+    ngx_str_t                 key;
+    uint32_t                  crc;
+    u_char                    md5[16];
+    ngx_path_t               *path;
+    ngx_buf_t                *buf;
+    time_t                    expires;
+    time_t                    last_modified;
+    time_t                    date;
+    off_t                     length;
+    ssize_t                   header_size;
+    size_t                    file_start;
+    ngx_log_t                *log;
+} ngx_http_cache_ctx_t;
+
+
+
+#define NGX_HTTP_CACHE_STALE     1
+#define NGX_HTTP_CACHE_AGED      2
+#define NGX_HTTP_CACHE_THE_SAME  3
+
+
+ngx_http_cache_t *ngx_http_cache_get(ngx_http_cache_hash_t *cache,
+                                     ngx_http_cleanup_t *cleanup,
+                                     ngx_str_t *key, uint32_t *crc);
+
+ngx_http_cache_t *ngx_http_cache_alloc(ngx_http_cache_hash_t *hash,
+                                       ngx_http_cache_t *cache,
+                                       ngx_http_cleanup_t *cleanup,
+                                       ngx_str_t *key, uint32_t crc,
+                                       ngx_str_t *value, ngx_log_t *log);
+void ngx_http_cache_free(ngx_http_cache_t *cache,
+                         ngx_str_t *key, ngx_str_t *value, ngx_log_t *log);
+void ngx_http_cache_lock(ngx_http_cache_hash_t *hash, ngx_http_cache_t *cache);
+void ngx_http_cache_unlock(ngx_http_cache_hash_t *hash,
+                           ngx_http_cache_t *cache, ngx_log_t *log);
+
+int ngx_http_cache_get_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx);
+int ngx_http_cache_open_file(ngx_http_cache_ctx_t *ctx, ngx_file_uniq_t uniq);
+int ngx_http_cache_update_file(ngx_http_request_t *r,ngx_http_cache_ctx_t *ctx,
+                               ngx_str_t *temp_file);
+
+int ngx_http_send_cached(ngx_http_request_t *r);
+
+
+int ngx_garbage_collector_http_cache_handler(ngx_gc_t *gc, ngx_str_t *name,
+                                             ngx_dir_t *dir);
+
+char *ngx_http_set_cache_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+#endif /* _NGX_HTTP_CACHE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_config.h
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CONFIG_H_INCLUDED_
+#define _NGX_HTTP_CONFIG_H_INCLUDED_
+
+
+#include <ngx_alloc.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    void   **main_conf;
+    void   **srv_conf;
+    void   **loc_conf;
+} ngx_http_conf_ctx_t;
+
+
+typedef struct {
+    ngx_int_t   (*pre_conf)(ngx_conf_t *cf);
+
+    void       *(*create_main_conf)(ngx_conf_t *cf);
+    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+    void       *(*create_srv_conf)(ngx_conf_t *cf);
+    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
+
+    void       *(*create_loc_conf)(ngx_conf_t *cf);
+    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
+} ngx_http_module_t;
+
+
+#define NGX_HTTP_MODULE           0x50545448   /* "HTTP" */
+
+#define NGX_HTTP_MAIN_CONF        0x02000000
+#define NGX_HTTP_SRV_CONF         0x04000000
+#define NGX_HTTP_LOC_CONF         0x08000000
+
+
+#define NGX_HTTP_MAIN_CONF_OFFSET  offsetof(ngx_http_conf_ctx_t, main_conf)
+#define NGX_HTTP_SRV_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, srv_conf)
+#define NGX_HTTP_LOC_CONF_OFFSET   offsetof(ngx_http_conf_ctx_t, loc_conf)
+
+
+#define ngx_http_get_module_main_conf(r, module) r->main_conf[module.ctx_index]
+#define ngx_http_get_module_srv_conf(r, module)  r->srv_conf[module.ctx_index]
+#define ngx_http_get_module_loc_conf(r, module)  r->loc_conf[module.ctx_index]
+
+/*
+ * ngx_http_conf_get_module_srv_conf() and ngx_http_conf_get_module_loc_conf()
+ * must not be used at the merge phase because cf->ctx points to http{}'s ctx
+ */
+
+#define ngx_http_conf_get_module_main_conf(cf, module)                        \
+            ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_http_conf_get_module_srv_conf(cf, module)                         \
+            ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+#define ngx_http_conf_get_module_loc_conf(cf, module)                         \
+            ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]
+
+#define ngx_http_cycle_get_module_main_conf(cycle, module)                    \
+       ((ngx_http_conf_ctx_t *)                                               \
+           cycle->conf_ctx[ngx_http_module.index])->main_conf[module.ctx_index]
+
+
+
+#endif /* _NGX_HTTP_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_copy_filter.c
@@ -0,0 +1,132 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_bufs_t  bufs;
+} ngx_http_copy_filter_conf_t;
+
+
+static void *ngx_http_copy_filter_create_conf(ngx_conf_t *cf);
+static char *ngx_http_copy_filter_merge_conf(ngx_conf_t *cf,
+                                             void *parent, void *child);
+static ngx_int_t ngx_http_copy_filter_init(ngx_cycle_t *cycle);
+
+
+static ngx_command_t  ngx_http_copy_filter_commands[] = {
+
+    {ngx_string("output_buffers"),
+     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
+     ngx_conf_set_bufs_slot,
+     NGX_HTTP_LOC_CONF_OFFSET,
+     offsetof(ngx_http_copy_filter_conf_t, bufs),
+     NULL},
+
+    ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_copy_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_copy_filter_create_conf,      /* create location configuration */
+    ngx_http_copy_filter_merge_conf        /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_copy_filter_module = {
+    NGX_MODULE,
+    &ngx_http_copy_filter_module_ctx,      /* module context */
+    ngx_http_copy_filter_commands,         /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_copy_filter_init,             /* init module */
+    NULL                                   /* init process */
+};
+
+
+static ngx_http_output_body_filter_pt    ngx_http_next_filter;
+
+
+ngx_int_t ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_output_chain_ctx_t       *ctx;
+    ngx_http_copy_filter_conf_t  *conf;
+
+    if (r->connection->write->error) {
+        return NGX_ERROR;
+    }
+
+    ctx = ngx_http_get_module_ctx(r->main ? r->main : r,
+                                            ngx_http_copy_filter_module);
+
+    if (ctx == NULL) {
+        conf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                            ngx_http_copy_filter_module);
+
+        ngx_http_create_ctx(r, ctx, ngx_http_copy_filter_module,
+                            sizeof(ngx_output_chain_ctx_t), NGX_ERROR);
+
+        ctx->sendfile = r->sendfile;
+        ctx->need_in_memory = r->filter_need_in_memory;
+        ctx->need_in_temp = r->filter_need_temporary;
+
+        ctx->pool = r->pool;
+        ctx->bufs = conf->bufs;
+        ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module;
+
+        ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_filter;
+        ctx->filter_ctx = r;
+
+    }
+
+    return ngx_output_chain(ctx, in);
+}
+
+
+static void *ngx_http_copy_filter_create_conf(ngx_conf_t *cf)
+{
+    ngx_http_copy_filter_conf_t *conf;
+
+    ngx_test_null(conf,
+                  ngx_palloc(cf->pool, sizeof(ngx_http_copy_filter_conf_t)),
+                  NULL);
+
+    conf->bufs.num = 0;
+
+    return conf;
+}
+
+
+static char *ngx_http_copy_filter_merge_conf(ngx_conf_t *cf,
+                                             void *parent, void *child)
+{
+    ngx_http_copy_filter_conf_t *prev = parent;
+    ngx_http_copy_filter_conf_t *conf = child;
+
+    ngx_conf_merge_bufs_value(conf->bufs, prev->bufs, 1, 32768);
+
+    return NULL;
+}
+
+
+static ngx_int_t ngx_http_copy_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_next_filter = ngx_http_top_body_filter;
+    ngx_http_top_body_filter = ngx_http_copy_filter;
+
+    return NGX_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_core_module.c
@@ -0,0 +1,1819 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+/* STUB */
+#define NGX_HTTP_LOCATION_EXACT           1
+#define NGX_HTTP_LOCATION_AUTO_REDIRECT   2
+#define NGX_HTTP_LOCATION_REGEX           3
+
+
+static void ngx_http_phase_event_handler(ngx_event_t *rev);
+static void ngx_http_run_phases(ngx_http_request_t *r);
+static ngx_int_t ngx_http_find_location(ngx_http_request_t *r,
+                                        ngx_array_t *locations, size_t len);
+
+static void *ngx_http_core_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf);
+static void *ngx_http_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_srv_conf(ngx_conf_t *cf,
+                                          void *parent, void *child);
+static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_core_merge_loc_conf(ngx_conf_t *cf,
+                                          void *parent, void *child);
+
+static char *ngx_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
+static int ngx_cmp_locations(const void *first, const void *second);
+static char *ngx_location_block(ngx_conf_t *cf, ngx_command_t *cmd,
+                                void *dummy);
+static char *ngx_types_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_set_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_server_name(ngx_conf_t *cf, ngx_command_t *cmd,
+                                 void *conf);
+static char *ngx_set_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_set_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+static char *ngx_http_lowat_check(ngx_conf_t *cf, void *post, void *data);
+
+static ngx_conf_post_t  ngx_http_lowat_post = { ngx_http_lowat_check } ;
+
+
+static ngx_conf_enum_t  ngx_http_restrict_host_names[] = {
+    { ngx_string("off"), NGX_HTTP_RESTRICT_HOST_OFF },
+    { ngx_string("on"), NGX_HTTP_RESTRICT_HOST_ON },
+    { ngx_string("close"), NGX_HTTP_RESTRICT_HOST_CLOSE },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t  ngx_http_core_commands[] = {
+
+    { ngx_string("server"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_server_block,
+      0,
+      0,
+      NULL },
+
+    { ngx_string("connection_pool_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, connection_pool_size),
+      NULL },
+
+    { ngx_string("post_accept_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, post_accept_timeout),
+      NULL },
+
+    { ngx_string("request_pool_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, request_pool_size),
+      NULL },
+
+    { ngx_string("client_header_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, client_header_timeout),
+      NULL },
+
+    { ngx_string("client_header_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, client_header_buffer_size),
+      NULL },
+
+    { ngx_string("large_client_header_buffers"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2,
+      ngx_conf_set_bufs_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, large_client_header_buffers),
+      NULL },
+
+    { ngx_string("restrict_host_names"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_core_srv_conf_t, restrict_host_names),
+      &ngx_http_restrict_host_names },
+
+    { ngx_string("location"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE12,
+      ngx_location_block,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("listen"),
+#if 0
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+#else
+      NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+#endif
+      ngx_set_listen,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("server_name"),
+      NGX_HTTP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_set_server_name,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
+                                          |NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+      ngx_types_block,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("default_type"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, default_type),
+      NULL },
+
+    { ngx_string("root"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_set_root,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("alias"),
+      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_set_root,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("client_max_body_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_max_body_size),
+      NULL },
+
+    { ngx_string("client_body_buffer_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_buffer_size),
+      NULL },
+
+    { ngx_string("client_body_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, client_body_timeout),
+      NULL },
+
+    { ngx_string("sendfile"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, sendfile),
+      NULL },
+
+    { ngx_string("tcp_nopush"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, tcp_nopush),
+      NULL },
+
+    { ngx_string("send_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, send_timeout),
+      NULL },
+
+    { ngx_string("send_lowat"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, send_lowat),
+      &ngx_http_lowat_post },
+
+    { ngx_string("postpone_output"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, postpone_output),
+      NULL },
+
+    { ngx_string("limit_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, limit_rate),
+      NULL },
+
+    { ngx_string("keepalive_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+      ngx_set_keepalive,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("lingering_time"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, lingering_time),
+      NULL },
+
+    { ngx_string("lingering_timeout"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, lingering_timeout),
+      NULL },
+
+    { ngx_string("reset_timedout_connection"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, reset_timedout_connection),
+      NULL },
+
+    { ngx_string("msie_padding"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, msie_padding),
+      NULL },
+
+    { ngx_string("error_page"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
+      ngx_set_error_page,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("error_log"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_set_error_log,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+#if (NGX_HTTP_CACHE)
+
+    { ngx_string("open_file_cache"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE4,
+      ngx_http_set_cache_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, open_files),
+      NULL },
+
+#endif
+
+      ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_core_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    ngx_http_core_create_main_conf,        /* create main configuration */
+    ngx_http_core_init_main_conf,          /* init main configuration */
+
+    ngx_http_core_create_srv_conf,         /* create server configuration */
+    ngx_http_core_merge_srv_conf,          /* merge server configuration */
+
+    ngx_http_core_create_loc_conf,         /* create location configuration */
+    ngx_http_core_merge_loc_conf           /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_core_module = {
+    NGX_MODULE,
+    &ngx_http_core_module_ctx,             /* module context */
+    ngx_http_core_commands,                /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init process */
+};
+
+
+void ngx_http_handler(ngx_http_request_t *r)
+{
+    ngx_http_log_ctx_t  *lcx;
+
+    r->connection->unexpected_eof = 0;
+
+    lcx = r->connection->log->data;
+    lcx->action = NULL;
+
+    switch (r->headers_in.connection_type) {
+    case 0:
+        if (r->http_version > NGX_HTTP_VERSION_10) {
+            r->keepalive = 1;
+        } else {
+            r->keepalive = 0;
+        }
+        break;
+
+    case NGX_HTTP_CONNECTION_CLOSE:
+        r->keepalive = 0;
+        break;
+
+    case NGX_HTTP_CONNECTION_KEEP_ALIVE:
+        r->keepalive = 1;
+        break;
+    }
+
+    if (r->keepalive && r->headers_in.msie && r->method == NGX_HTTP_POST) {
+
+        /*
+         * MSIE may wait for some time if the response for the POST request
+         * is sent over the keepalive connection
+         */
+
+        r->keepalive = 0;
+    }
+
+#if 0
+    /* TEST STUB */ r->http_version = NGX_HTTP_VERSION_10;
+    /* TEST STUB */ r->keepalive = 0;
+#endif
+
+    if (r->headers_in.content_length_n > 0) {
+        r->lingering_close = 1;
+
+    } else {
+        r->lingering_close = 0;
+    }
+
+#if 0
+    /* TEST STUB */ r->lingering_close = 1;
+#endif
+
+    r->connection->write->event_handler = ngx_http_phase_event_handler;
+
+    ngx_http_run_phases(r);
+
+    return;
+}
+
+
+static void ngx_http_phase_event_handler(ngx_event_t *ev)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = ev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "phase event handler");
+
+    ngx_http_run_phases(r);
+
+    return;
+}
+
+
+static void ngx_http_run_phases(ngx_http_request_t *r)
+{
+    char                       *path;
+    ngx_int_t                   rc;
+    ngx_http_handler_pt        *h;
+    ngx_http_core_loc_conf_t   *clcf;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+    for (/* void */; r->phase < NGX_HTTP_LAST_PHASE; r->phase++) {
+
+        if (r->phase == NGX_HTTP_CONTENT_PHASE && r->content_handler) {
+            r->connection->write->event_handler = ngx_http_empty_handler;
+            rc = r->content_handler(r);
+            ngx_http_finalize_request(r, rc);
+            return;
+        }
+
+        h = cmcf->phases[r->phase].handlers.elts;
+        for (r->phase_handler = cmcf->phases[r->phase].handlers.nelts - 1;
+             r->phase_handler >= 0;
+             r->phase_handler--)
+        {
+            rc = h[r->phase_handler](r);
+
+            if (rc == NGX_DONE) {
+
+                /*
+                 * we should never use r here because 
+                 * it could point to already freed data
+                 */
+
+                return;
+            }
+
+            if (rc == NGX_DECLINED) {
+                continue;
+            }
+
+            if (rc >= NGX_HTTP_SPECIAL_RESPONSE || rc == NGX_ERROR) {
+                ngx_http_finalize_request(r, rc);
+                return;
+            }
+
+            if (r->phase == NGX_HTTP_CONTENT_PHASE) {
+                ngx_http_finalize_request(r, rc);
+                return;
+            }
+
+            if (rc == NGX_AGAIN) {
+                return;
+            }
+
+            if (rc == NGX_OK && cmcf->phases[r->phase].type == NGX_OK) {
+                break;
+            }
+        }
+    }
+
+
+    if (r->uri.data[r->uri.len - 1] == '/') {
+
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (!(path = ngx_palloc(r->pool, clcf->root.len + r->uri.len))) {
+            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    
+        ngx_cpystrn(ngx_cpymem(path, clcf->root.data, clcf->root.len),
+                    r->uri.data, r->uri.len + 1);
+    
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "directory index of \"%s\" is forbidden", path);
+
+        ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
+
+    ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+    return;
+}
+
+
+ngx_int_t ngx_http_find_location_config(ngx_http_request_t *r)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *clcf;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    rc = ngx_http_find_location(r, &cscf->locations, 0);
+
+    if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+        return rc;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    r->connection->log->file = clcf->err_log->file;
+    if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+        r->connection->log->log_level = clcf->err_log->log_level;
+    }
+
+    if (!(ngx_io.flags & NGX_IO_SENDFILE) || !clcf->sendfile) {
+        r->sendfile = 0;
+
+    } else {
+        r->sendfile = 1;
+    }
+
+    if (!clcf->tcp_nopush) {
+        /* disable TCP_NOPUSH/TCP_CORK use */
+        r->connection->tcp_nopush = NGX_TCP_NOPUSH_DISABLED;
+    }
+
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http cl: " SIZE_T_FMT " max: " SIZE_T_FMT,
+                   r->headers_in.content_length_n,
+                   clcf->client_max_body_size);
+
+    if (r->headers_in.content_length_n != -1
+        && clcf->client_max_body_size
+        && clcf->client_max_body_size < (size_t) r->headers_in.content_length_n)
+    {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "client intented to send too large body: "
+                      SIZE_T_FMT " bytes",
+                      r->headers_in.content_length_n);
+
+        return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE;
+    }
+
+
+    if (rc == NGX_HTTP_LOCATION_AUTO_REDIRECT) {
+        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 = clcf->name;
+
+        return NGX_HTTP_MOVED_PERMANENTLY;
+    }
+
+    if (clcf->handler) {
+        r->content_handler = clcf->handler;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_find_location(ngx_http_request_t *r,
+                                        ngx_array_t *locations, size_t len)
+{
+    ngx_int_t                  n, rc;
+    ngx_uint_t                 i, found;
+    ngx_http_core_loc_conf_t  *clcf, **clcfp;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "find location");
+
+    found = 0;
+
+    clcfp = locations->elts;
+    for (i = 0; i < locations->nelts; i++) {
+
+#if (HAVE_PCRE)
+        if (clcfp[i]->regex) {
+            break;
+        }
+#endif
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "find location: %s\"%s\"",
+                       clcfp[i]->exact_match ? "= " : "",
+                       clcfp[i]->name.data);
+
+        if (clcfp[i]->auto_redirect
+            && r->uri.len == clcfp[i]->name.len - 1
+            && ngx_strncmp(r->uri.data, clcfp[i]->name.data,
+                                                  clcfp[i]->name.len - 1) == 0)
+        {
+            /* the locations are lexicographically sorted */
+
+            r->loc_conf = clcfp[i]->loc_conf;
+
+            return NGX_HTTP_LOCATION_AUTO_REDIRECT;
+        }
+
+        if (r->uri.len < clcfp[i]->name.len) {
+            continue;
+        }
+
+        n = ngx_strncmp(r->uri.data, clcfp[i]->name.data, clcfp[i]->name.len);
+
+        if (n < 0) {
+            /* the locations are lexicographically sorted */
+            break;
+        }
+
+        if (n == 0) {
+            if (clcfp[i]->exact_match && r->uri.len == clcfp[i]->name.len) {
+                r->loc_conf = clcfp[i]->loc_conf;
+                return NGX_HTTP_LOCATION_EXACT;
+            }
+
+            if (len > clcfp[i]->name.len) {
+                /* the previous match is longer */
+                break;
+            }
+
+            r->loc_conf = clcfp[i]->loc_conf;
+            found = 1;
+        }
+    }
+
+    if (found) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->locations.nelts) {
+            rc = ngx_http_find_location(r, &clcf->locations, len);
+
+            if (rc != NGX_OK) {
+                return rc;
+            }
+        }
+    }
+
+#if (HAVE_PCRE)
+
+    /* regex matches */
+
+    for (/* void */; i < locations->nelts; i++) {
+
+        if (!clcfp[i]->regex) {
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "find location: ~ \"%s\"",
+                       clcfp[i]->name.data);
+
+        n = ngx_regex_exec(clcfp[i]->regex, &r->uri, NULL, 0);
+
+        if (n == NGX_DECLINED) {
+            continue;
+        }
+
+        if (n < 0) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          ngx_regex_exec_n
+                          " failed: %d on \"%s\" using \"%s\"",
+                          n, r->uri.data, clcfp[i]->name.data);
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        /* match */
+
+        r->loc_conf = clcfp[i]->loc_conf;
+
+        return NGX_HTTP_LOCATION_REGEX;
+    }
+
+#endif /* HAVE_PCRE */
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r)
+{
+    uint32_t                   key;
+    ngx_uint_t                 i;
+    ngx_http_type_t           *type;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r->headers_out.content_type = ngx_list_push(&r->headers_out.headers);
+    if (r->headers_out.content_type == NULL) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->headers_out.content_type->key.len = 0;
+    r->headers_out.content_type->key.data = NULL;
+    r->headers_out.content_type->value.len = 0;
+    r->headers_out.content_type->value.data = NULL;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->exten.len) {
+#if 0
+        key = ngx_crc(r->exten.data, r->exten.key);
+#endif
+        ngx_http_types_hash_key(key, r->exten);
+
+        type = clcf->types[key].elts;
+        for (i = 0; i < clcf->types[key].nelts; i++) {
+            if (r->exten.len != type[i].exten.len) {
+                continue;
+            }
+
+            if (ngx_memcmp(r->exten.data, type[i].exten.data, r->exten.len)
+                                                                           == 0)
+            {
+                r->headers_out.content_type->value = type[i].type;
+                break;
+            }
+        }
+    }
+
+    if (r->headers_out.content_type->value.len == 0) {
+        r->headers_out.content_type->value = clcf->default_type;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
+{
+    if (r->main) {
+        return NGX_OK;
+    }
+
+    if (r->err_ctx) {
+        r->headers_out.status = r->err_status;
+        r->headers_out.status_line.len = 0;
+    }
+
+    return (*ngx_http_top_header_filter)(r);
+}
+
+
+ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    ngx_int_t  rc;
+
+    if (r->connection->write->error) {
+        return NGX_ERROR;
+    }
+
+    rc = ngx_http_top_body_filter(r, in);
+
+    if (rc == NGX_ERROR) {
+
+        /* NGX_ERROR could be returned by any filter */
+
+        r->connection->write->error = 1;
+    }
+
+    return rc;
+}
+
+
+int ngx_http_redirect(ngx_http_request_t *r, int redirect)
+{
+    /* STUB */
+
+    /* log request */
+
+    ngx_http_close_request(r, 0);
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_set_exten(ngx_http_request_t *r)
+{
+    ngx_int_t  i;
+
+    r->exten.len = 0;
+    r->exten.data = NULL;
+
+    for (i = r->uri.len - 1; i > 1; i--) {
+        if (r->uri.data[i] == '.' && r->uri.data[i - 1] != '/') {
+            r->exten.len = r->uri.len - i - 1;
+
+            if (r->exten.len > 0) {
+                if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+                    return NGX_ERROR;
+                }
+
+                ngx_cpystrn(r->exten.data, &r->uri.data[i + 1],
+                            r->exten.len + 1);
+            }
+
+            break;
+
+        } else if (r->uri.data[i] == '/') {
+            break;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
+                                     ngx_str_t *uri, ngx_str_t *args)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "internal redirect: \"%s\"", uri->data);
+
+    r->uri.len = uri->len;
+    r->uri.data = uri->data;
+
+    if (args) {
+        r->args.len = args->len;
+        r->args.data = args->data;
+    }
+
+    if (ngx_http_set_exten(r) != NGX_OK) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    if (r->err_ctx) {
+
+        /* allocate the new modules contexts */
+
+        r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+        if (r->ctx == NULL) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+    } else {
+
+        /* clear the modules contexts */
+
+        ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+    }
+
+    r->phase = 0;
+    r->phase_handler = 0;
+
+    ngx_http_handler(r);
+
+    return NGX_DONE;
+}
+
+
+#if 0       /* STUB: test the delay http handler */
+
+int ngx_http_delay_handler(ngx_http_request_t *r)
+{
+    static int  on;
+
+    if (on++ == 0) {
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http set delay");
+        ngx_add_timer(r->connection->write, 10000);
+        return NGX_AGAIN;
+    }
+
+    r->connection->write->timedout = 0;
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http reset delay");
+    return NGX_DECLINED;
+}
+
+#endif
+
+
+#if 0
+
+static ngx_int_t ngx_http_core_init_process(ngx_cycle_t *cycle)
+{
+    ngx_uint_t                   i;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);
+
+#if 0
+    ngx_http_core_init_module:
+
+    ngx_http_handler_pt         *h;
+
+    ngx_test_null(h, ngx_push_array(
+                             &cmcf->phases[NGX_HTTP_TRANSLATE_PHASE].handlers),
+                  NGX_ERROR);
+    *h = ngx_http_delay_handler;
+#endif
+
+    cscfp = cmcf->servers.elts;
+
+    for (i = 0; i < cmcf->servers.nelts; i++) {
+        if (cscfp[i]->recv == NULL) {
+            cscfp[i]->recv = ngx_io.recv;
+            cscfp[i]->send_chain = ngx_io.send_chain;
+        }
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
+static char *ngx_server_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    int                          m;
+    char                        *rv;
+    ngx_http_module_t           *module;
+    ngx_conf_t                   pvcf;
+    ngx_http_conf_ctx_t         *ctx, *http_ctx;
+    ngx_http_core_main_conf_t   *cmcf;
+    ngx_http_core_srv_conf_t    *cscf, **cscfp;
+
+    ngx_test_null(ctx,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)),
+                  NGX_CONF_ERROR);
+
+    http_ctx = cf->ctx;
+    ctx->main_conf = http_ctx->main_conf;
+
+    /* the server{}'s srv_conf */
+
+    ngx_test_null(ctx->srv_conf,
+                  ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+                  NGX_CONF_ERROR);
+
+    /* the server{}'s loc_conf */
+
+    ngx_test_null(ctx->loc_conf,
+                  ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module),
+                  NGX_CONF_ERROR);
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->create_srv_conf) {
+            ngx_test_null(ctx->srv_conf[ngx_modules[m]->ctx_index],
+                          module->create_srv_conf(cf),
+                          NGX_CONF_ERROR);
+        }
+
+        if (module->create_loc_conf) {
+            ngx_test_null(ctx->loc_conf[ngx_modules[m]->ctx_index],
+                          module->create_loc_conf(cf),
+                          NGX_CONF_ERROR);
+        }
+    }
+
+    /* create links of the srv_conf's */
+
+    cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+    cscf->ctx = ctx;
+
+    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+    ngx_test_null(cscfp, ngx_push_array(&cmcf->servers), NGX_CONF_ERROR);
+    *cscfp = cscf;
+
+    /* parse inside server{} */
+
+    pvcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_SRV_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+    *cf = pvcf;
+
+    if (rv != NGX_CONF_OK) {
+        return rv;
+    }
+
+    ngx_qsort(cscf->locations.elts, (size_t) cscf->locations.nelts,
+              sizeof(ngx_http_core_loc_conf_t *), ngx_cmp_locations);
+
+    return rv;
+}
+
+
+static int ngx_cmp_locations(const void *one, const void *two)
+{
+    ngx_int_t                  rc;
+    ngx_http_core_loc_conf_t  *first, *second;
+
+    first = *(ngx_http_core_loc_conf_t **) one;
+    second = *(ngx_http_core_loc_conf_t **) two;
+
+#if (HAVE_PCRE)
+
+    if (first->regex && !second->regex) {
+        /* shift the regex matches to the end */
+        return 1;
+    }
+
+    if (first->regex || second->regex) {
+        /* do not sort the regex matches */
+        return 0;
+    }
+
+#endif
+
+    rc = ngx_strcmp(first->name.data, second->name.data);
+
+    if (rc == 0 && second->exact_match) {
+        /* an exact match must be before the same inclusive one */
+        return 1;
+    }
+
+    return rc;
+}
+
+
+static char *ngx_location_block(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+    char                      *rv;
+    ngx_int_t                  m;
+    ngx_str_t                 *value;
+    ngx_conf_t                 pcf;
+    ngx_http_module_t         *module;
+    ngx_http_conf_ctx_t       *ctx, *pctx;
+    ngx_http_core_srv_conf_t  *cscf;
+    ngx_http_core_loc_conf_t  *clcf, *pclcf, **clcfp;
+#if (HAVE_PCRE)
+    ngx_str_t                  err;
+    u_char                     errstr[NGX_MAX_CONF_ERRSTR];
+#endif
+
+    if (!(ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    pctx = cf->ctx;
+    ctx->main_conf = pctx->main_conf;
+    ctx->srv_conf = pctx->srv_conf;
+
+    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+    if (ctx->loc_conf == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->create_loc_conf) {
+            ctx->loc_conf[ngx_modules[m]->ctx_index] =
+                                                   module->create_loc_conf(cf);
+            if (ctx->loc_conf[ngx_modules[m]->ctx_index] == NULL) {
+                 return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
+    clcf->loc_conf = ctx->loc_conf;
+
+    value = cf->args->elts;
+
+    if (cf->args->nelts == 3) {
+        if (value[1].len == 1 && value[1].data[0] == '=') {
+            clcf->name.len = value[2].len;
+            clcf->name.data = value[2].data;
+            clcf->exact_match = 1;
+
+        } else if ((value[1].len == 1 && value[1].data[0] == '~')
+                   || (value[1].len == 2
+                       && value[1].data[0] == '~'
+                       && value[1].data[1] == '*'))
+        {
+#if (HAVE_PCRE)
+            err.len = NGX_MAX_CONF_ERRSTR;
+            err.data = errstr;
+
+            clcf->regex = ngx_regex_compile(&value[2],
+                                     value[1].len == 2 ? NGX_REGEX_CASELESS: 0,
+                                     cf->pool, &err);
+
+            if (clcf->regex == NULL) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
+                return NGX_CONF_ERROR;
+            }
+
+            clcf->name = value[2];
+#else
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "the using of the regex \"%s\" "
+                               "requires PCRE library",
+                               value[2].data);
+            return NGX_CONF_ERROR;
+#endif
+
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid location modifier \"%s\"",
+                               value[1].data);
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        clcf->name.len = value[1].len;
+        clcf->name.data = value[1].data;
+    }
+
+    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];
+
+    if (pclcf->name.len == 0) {
+        cscf = ctx->srv_conf[ngx_http_core_module.ctx_index];
+        if (!(clcfp = ngx_push_array(&cscf->locations))) {
+            return NGX_CONF_ERROR;
+        }
+
+    } else {
+        clcf->prev_location = pclcf;
+
+        if (pclcf->exact_match) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "location \"%s\" could not be inside "
+                               "the exact location \"%s\"",
+                               clcf->name.data, pclcf->name.data);
+            return NGX_CONF_ERROR;
+        }
+
+#if (HAVE_PCRE)
+        if (clcf->regex == NULL
+            && ngx_strncmp(clcf->name.data, pclcf->name.data, pclcf->name.len)
+                                                                         != 0)
+#else
+        if (ngx_strncmp(clcf->name.data, pclcf->name.data, pclcf->name.len)
+                                                                         != 0)
+#endif
+        {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "location \"%s\" is outside location \"%s\"",
+                               clcf->name.data, pclcf->name.data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (pclcf->locations.elts == NULL) {
+            ngx_init_array(pclcf->locations, cf->pool, 5, sizeof(void *),
+                           NGX_CONF_ERROR);
+        }
+
+        if (!(clcfp = ngx_push_array(&pclcf->locations))) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    *clcfp = clcf;
+
+    pcf = *cf;
+    cf->ctx = ctx;
+    cf->cmd_type = NGX_HTTP_LOC_CONF;
+    rv = ngx_conf_parse(cf, NULL);
+    *cf = pcf;
+
+    return rv;
+}
+
+
+static char *ngx_types_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char        *rv;
+    ngx_conf_t   pcf;
+
+    pcf = *cf;
+    cf->handler = ngx_set_type;
+    cf->handler_conf = conf;
+    rv = ngx_conf_parse(cf, NULL);
+    *cf = pcf;
+
+    return rv;
+}
+
+
+static char *ngx_set_type(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+{
+    ngx_http_core_loc_conf_t *lcf = conf;
+
+    uint32_t          key;
+    ngx_uint_t        i;
+    ngx_str_t        *args;
+    ngx_http_type_t  *type;
+
+    if (lcf->types == NULL) {
+        lcf->types = ngx_palloc(cf->pool, NGX_HTTP_TYPES_HASH_PRIME
+                                                        * sizeof(ngx_array_t));
+        if (lcf->types == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; i < NGX_HTTP_TYPES_HASH_PRIME; i++) {
+            if (ngx_array_init(&lcf->types[i], cf->pool, 5,
+                                         sizeof(ngx_http_type_t)) == NGX_ERROR)
+            {
+                return NGX_CONF_ERROR;
+            }
+        }
+    }
+
+    args = (ngx_str_t *) cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        ngx_http_types_hash_key(key, args[i]);
+
+        if (!(type = ngx_array_push(&lcf->types[key]))) {
+            return NGX_CONF_ERROR;
+        }
+
+        type->exten = args[i];
+        type->type = args[0];
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_main_conf_t *cmcf;
+
+    ngx_test_null(cmcf,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_core_main_conf_t)),
+                  NGX_CONF_ERROR);
+
+    ngx_init_array(cmcf->servers, cf->pool,
+                   5, sizeof(ngx_http_core_srv_conf_t *),
+                   NGX_CONF_ERROR);
+
+    return cmcf;
+}
+
+
+static char *ngx_http_core_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+#if 0
+    ngx_http_core_main_conf_t *cmcf = conf;
+
+    /* TODO: remove it if no directives */
+#endif
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ngx_test_null(cscf,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_core_srv_conf_t)),
+                  NGX_CONF_ERROR);
+
+    /*
+
+    set by ngx_pcalloc():
+
+    conf->client_large_buffers.num = 0;
+
+    */
+
+
+    ngx_init_array(cscf->locations, cf->pool,
+                   5, sizeof(void *), NGX_CONF_ERROR);
+    ngx_init_array(cscf->listen, cf->pool, 5, sizeof(ngx_http_listen_t),
+                   NGX_CONF_ERROR);
+    ngx_init_array(cscf->server_names, cf->pool,
+                   5, sizeof(ngx_http_server_name_t), NGX_CONF_ERROR);
+
+    cscf->connection_pool_size = NGX_CONF_UNSET_SIZE;
+    cscf->post_accept_timeout = NGX_CONF_UNSET_MSEC;
+    cscf->request_pool_size = NGX_CONF_UNSET_SIZE;
+    cscf->client_header_timeout = NGX_CONF_UNSET_MSEC;
+    cscf->client_header_buffer_size = NGX_CONF_UNSET_SIZE;
+    cscf->restrict_host_names = NGX_CONF_UNSET_UINT;
+
+    return cscf;
+}
+
+
+static char *ngx_http_core_merge_srv_conf(ngx_conf_t *cf,
+                                          void *parent, void *child)
+{
+    ngx_http_core_srv_conf_t *prev = parent;
+    ngx_http_core_srv_conf_t *conf = child;
+
+    ngx_http_listen_t          *l;
+    ngx_http_server_name_t     *n;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    /* TODO: it does not merge, it inits only */
+
+    if (conf->listen.nelts == 0) {
+        ngx_test_null(l, ngx_push_array(&conf->listen), NGX_CONF_ERROR);
+        l->addr = INADDR_ANY;
+#if (WIN32)
+        l->port = 80;
+#else
+        /* STUB: getuid() should be cached */
+        l->port = (getuid() == 0) ? 80 : 8000;
+#endif
+        l->family = AF_INET;
+    }
+
+    if (conf->server_names.nelts == 0) {
+        ngx_test_null(n, ngx_push_array(&conf->server_names), NGX_CONF_ERROR);
+        ngx_test_null(n->name.data, ngx_palloc(cf->pool, NGX_MAXHOSTNAMELEN),
+                      NGX_CONF_ERROR);
+
+        if (gethostname((char *) n->name.data, NGX_MAXHOSTNAMELEN) == -1) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+                               "gethostname() failed");
+            return NGX_CONF_ERROR;
+        }
+
+        n->name.len = ngx_strlen(n->name.data);
+        n->core_srv_conf = conf;
+
+#if 0
+        ctx = (ngx_http_conf_ctx_t *)
+                                    cf->cycle->conf_ctx[ngx_http_module.index];
+        cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+#endif
+        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+        if (cmcf->max_server_name_len < n->name.len) {
+            cmcf->max_server_name_len = n->name.len;
+        }
+    }
+
+    ngx_conf_merge_size_value(conf->connection_pool_size,
+                              prev->connection_pool_size, 256);
+    ngx_conf_merge_msec_value(conf->post_accept_timeout,
+                              prev->post_accept_timeout, 60000);
+    ngx_conf_merge_size_value(conf->request_pool_size,
+                              prev->request_pool_size, 4096);
+    ngx_conf_merge_msec_value(conf->client_header_timeout,
+                              prev->client_header_timeout, 60000);
+    ngx_conf_merge_size_value(conf->client_header_buffer_size,
+                              prev->client_header_buffer_size, 1024);
+    ngx_conf_merge_bufs_value(conf->large_client_header_buffers,
+                              prev->large_client_header_buffers,
+                              4, ngx_pagesize);
+
+    if (conf->large_client_header_buffers.size < conf->connection_pool_size) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "the \"large_client_header_buffers\" size must be "
+                           "equal to or bigger than \"connection_pool_size\"");
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_conf_merge_unsigned_value(conf->restrict_host_names,
+                                  prev->restrict_host_names, 0);
+
+    return NGX_CONF_OK;
+}
+
+
+static void *ngx_http_core_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_core_loc_conf_t *lcf;
+
+    ngx_test_null(lcf,
+                  ngx_pcalloc(cf->pool, sizeof(ngx_http_core_loc_conf_t)),
+                  NGX_CONF_ERROR);
+
+    /* set by ngx_pcalloc():
+
+    lcf->root.len = 0;
+    lcf->root.data = NULL;
+    lcf->types = NULL;
+    lcf->default_type.len = 0;
+    lcf->default_type.data = NULL;
+    lcf->err_log = NULL;
+    lcf->error_pages = NULL;
+
+    lcf->regex = NULL;
+    lcf->exact_match = 0;
+    lcf->auto_redirect = 0;
+    lcf->alias = 0;
+
+    */
+
+    lcf->client_max_body_size = NGX_CONF_UNSET_SIZE;
+    lcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE;
+    lcf->client_body_timeout = NGX_CONF_UNSET_MSEC;
+    lcf->sendfile = NGX_CONF_UNSET;
+    lcf->tcp_nopush = NGX_CONF_UNSET;
+    lcf->send_timeout = NGX_CONF_UNSET_MSEC;
+    lcf->send_lowat = NGX_CONF_UNSET_SIZE;
+    lcf->postpone_output = NGX_CONF_UNSET_SIZE;
+    lcf->limit_rate = NGX_CONF_UNSET_SIZE;
+    lcf->keepalive_timeout = NGX_CONF_UNSET_MSEC;
+    lcf->keepalive_header = NGX_CONF_UNSET;
+    lcf->lingering_time = NGX_CONF_UNSET_MSEC;
+    lcf->lingering_timeout = NGX_CONF_UNSET_MSEC;
+    lcf->reset_timedout_connection = NGX_CONF_UNSET;
+    lcf->msie_padding = NGX_CONF_UNSET;
+
+    return lcf;
+}
+
+
+static ngx_http_type_t default_types[] = {
+    { ngx_string("html"), ngx_string("text/html") },
+    { ngx_string("gif"), ngx_string("image/gif") },
+    { ngx_string("jpg"), ngx_string("image/jpeg") },
+    { ngx_null_string, ngx_null_string }
+};
+
+
+static char *ngx_http_core_merge_loc_conf(ngx_conf_t *cf,
+                                          void *parent, void *child)
+{
+    ngx_http_core_loc_conf_t *prev = parent;
+    ngx_http_core_loc_conf_t *conf = child;
+
+    int               i, key;
+    ngx_http_type_t  *t;
+
+    ngx_conf_merge_str_value(conf->root, prev->root, "html");
+
+    if (ngx_conf_full_name(cf->cycle, &conf->root) == NGX_ERROR) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (conf->types == NULL) {
+        if (prev->types) {
+            conf->types = prev->types;
+
+        } else {
+            ngx_test_null(conf->types,
+                          ngx_palloc(cf->pool, NGX_HTTP_TYPES_HASH_PRIME
+                                                        * sizeof(ngx_array_t)),
+                          NGX_CONF_ERROR);
+
+            for (i = 0; i < NGX_HTTP_TYPES_HASH_PRIME; i++) {
+                ngx_init_array(conf->types[i], cf->pool,
+                               5, sizeof(ngx_http_type_t), NGX_CONF_ERROR);
+            }
+
+            for (i = 0; default_types[i].exten.len; i++) {
+                ngx_http_types_hash_key(key, default_types[i].exten);
+
+                ngx_test_null(t, ngx_push_array(&conf->types[key]),
+                              NGX_CONF_ERROR);
+                t->exten.len = default_types[i].exten.len;
+                t->exten.data = default_types[i].exten.data;
+                t->type.len = default_types[i].type.len;
+                t->type.data = default_types[i].type.data;
+            }
+        }
+    }
+
+    if (conf->err_log == NULL) {
+        if (prev->err_log) {
+            conf->err_log = prev->err_log;
+        } else {
+            conf->err_log = cf->cycle->new_log;
+        }
+    }
+
+    if (conf->error_pages == NULL && prev->error_pages) {
+        conf->error_pages = prev->error_pages;
+    }
+
+    ngx_conf_merge_str_value(conf->default_type,
+                             prev->default_type, "text/plain");
+
+    ngx_conf_merge_size_value(conf->client_max_body_size,
+                              prev->client_max_body_size, 1 * 1024 * 1024);
+    ngx_conf_merge_size_value(conf->client_body_buffer_size,
+                              prev->client_body_buffer_size,
+                              (size_t) 2 * ngx_pagesize);
+    ngx_conf_merge_msec_value(conf->client_body_timeout,
+                              prev->client_body_timeout, 60000);
+    ngx_conf_merge_value(conf->sendfile, prev->sendfile, 0);
+    ngx_conf_merge_value(conf->tcp_nopush, prev->tcp_nopush, 0);
+    ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+    ngx_conf_merge_size_value(conf->send_lowat, prev->send_lowat, 0);
+    ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output,
+                              1460);
+    ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0);
+    ngx_conf_merge_msec_value(conf->keepalive_timeout,
+                              prev->keepalive_timeout, 75000);
+    ngx_conf_merge_sec_value(conf->keepalive_header,
+                              prev->keepalive_header, 0);
+    ngx_conf_merge_msec_value(conf->lingering_time,
+                              prev->lingering_time, 30000);
+    ngx_conf_merge_msec_value(conf->lingering_timeout,
+                              prev->lingering_timeout, 5000);
+
+    ngx_conf_merge_value(conf->reset_timedout_connection,
+                         prev->reset_timedout_connection, 0);
+    ngx_conf_merge_value(conf->msie_padding, prev->msie_padding, 1);
+
+    if (conf->open_files == NULL) {
+        conf->open_files = prev->open_files;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_srv_conf_t *scf = conf;
+
+    u_char             *addr;
+    ngx_int_t           port;
+    ngx_uint_t          p;
+    struct hostent     *h;
+    ngx_str_t          *args;
+    ngx_http_listen_t  *ls;
+
+    /*
+     * TODO: check duplicate 'listen' directives,
+     *       add resolved name to server names ???
+     */
+
+    if (!(ls = ngx_array_push(&scf->listen))) {
+        return NGX_CONF_ERROR;
+    }
+
+    /* AF_INET only */
+
+    ls->family = AF_INET;
+    ls->default_server = 0;
+    ls->file_name = cf->conf_file->file.name;
+    ls->line = cf->conf_file->line;
+
+    args = cf->args->elts;
+    addr = args[1].data;
+
+    for (p = 0; p < args[1].len; p++) {
+        if (addr[p] == ':') {
+            addr[p++] = '\0';
+            break;
+        }
+    }
+
+    if (p == args[1].len) {
+        /* no ":" in the "listen" */
+        p = 0;
+    }
+
+    port = ngx_atoi(&addr[p], args[1].len - p);
+
+    if (port == NGX_ERROR && p == 0) {
+
+        /* "listen host" */
+        ls->port = 80;
+
+    } else if ((port == NGX_ERROR && p != 0) /* "listen host:NONNUMBER" */
+               || (port < 1 || port > 65536)) { /* "listen 99999" */
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid port \"%s\" in \"%s\" directive, "
+                           "it must be a number between 1 and 65535",
+                           &addr[p], cmd->name.data);
+
+        return NGX_CONF_ERROR;
+
+    } else if (p == 0) {
+        ls->addr = INADDR_ANY;
+        ls->port = (in_port_t) port;
+        return NGX_CONF_OK;
+
+    } else {
+        ls->port = (in_port_t) port;
+    }
+
+    ls->addr = inet_addr((const char *) addr);
+    if (ls->addr == INADDR_NONE) {
+        h = gethostbyname((const char *) addr);
+
+        if (h == NULL || h->h_addr_list[0] == NULL) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                              "can not resolve host \"%s\" "
+                              "in \"%s\" directive", addr, cmd->name.data);
+            return NGX_CONF_ERROR;
+        }
+
+        ls->addr = *(in_addr_t *)(h->h_addr_list[0]);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_srv_conf_t *scf = conf;
+
+    ngx_uint_t                  i;
+    ngx_str_t                  *value;
+    ngx_http_server_name_t     *sn;
+    ngx_http_core_main_conf_t  *cmcf;
+
+    /* TODO: several names */
+    /* TODO: warn about duplicate 'server_name' directives */
+
+#if 0
+    ctx = (ngx_http_conf_ctx_t *) cf->cycle->conf_ctx[ngx_http_module.index];
+    cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
+#endif
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        if (value[i].len == 0) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "server name \"%s\" is invalid "
+                               "in \"%s\" directive",
+                               value[i].data, cmd->name.data);
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_test_null(sn, ngx_push_array(&scf->server_names), NGX_CONF_ERROR);
+
+        sn->name.len = value[i].len;
+        sn->name.data = value[i].data;
+        sn->core_srv_conf = scf;
+
+        if (cmcf->max_server_name_len < sn->name.len) {
+            cmcf->max_server_name_len = sn->name.len;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_root(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *lcf = conf;
+
+    ngx_uint_t   alias;
+    ngx_str_t   *value;
+
+    alias = (cmd->name.len == sizeof("alias") - 1) ? 1 : 0;
+
+    if (lcf->root.data) {
+
+        /* the (ngx_uint_t) cast is required by gcc 2.7.2.3 */
+
+        if ((ngx_uint_t) lcf->alias == alias) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"%s\" directive is duplicate",
+                               cmd->name.data);
+        } else {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"%s\" directive is duplicate, "
+                               "\"%s\" directive is specified before",
+                               cmd->name.data, lcf->alias ? "alias" : "root");
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    value = cf->args->elts;
+
+    lcf->alias = alias;
+    lcf->root = value[1];
+
+    if (!alias && lcf->root.data[lcf->root.len - 1] == '/') {
+        lcf->root.len--;
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_error_page(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *lcf = conf;
+
+    int                   overwrite;
+    ngx_uint_t            i, n;
+    ngx_str_t            *value;
+    ngx_http_err_page_t  *err;
+
+    if (lcf->error_pages == NULL) {
+        lcf->error_pages = ngx_create_array(cf->pool, 5,
+                                            sizeof(ngx_http_err_page_t));
+        if (lcf->error_pages == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    value = cf->args->elts;
+
+    i = cf->args->nelts - 2;
+
+    if (value[i].data[0] == '=') {
+        if (i == 1) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        overwrite = ngx_atoi(&value[i].data[1], value[i].len - 1);
+
+        if (overwrite == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        n = 2;
+
+    } else {
+        overwrite = 0;
+        n = 1;
+    }
+
+    for (i = 1; i < cf->args->nelts - n; i++) {
+        if (!(err = ngx_push_array(lcf->error_pages))) {
+            return NGX_CONF_ERROR;
+        }
+
+        err->status = ngx_atoi(value[i].data, value[i].len);
+        if (err->status == NGX_ERROR) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%s\"", value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        if (err->status < 400 || err->status > 599) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "value \"%s\" must be between 400 and 599",
+                               value[i].data);
+            return NGX_CONF_ERROR;
+        }
+
+        err->overwrite = overwrite;
+        err->uri = value[cf->args->nelts - 1];
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *lcf = conf;
+
+    ngx_str_t  *value;
+
+    if (lcf->keepalive_timeout != NGX_CONF_UNSET_MSEC) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    lcf->keepalive_timeout = ngx_parse_time(&value[1], 0);
+    if (lcf->keepalive_timeout == (ngx_msec_t) NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (lcf->keepalive_timeout == (ngx_msec_t) NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 597 hours";
+    }
+
+    if (cf->args->nelts == 2) {
+        return NGX_CONF_OK;
+    }
+
+    lcf->keepalive_header = ngx_parse_time(&value[2], 1);
+    if (lcf->keepalive_header == NGX_ERROR) {
+        return "invalid value";
+    }
+
+    if (lcf->keepalive_header == NGX_PARSE_LARGE_TIME) {
+        return "value must be less than 68 years";
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_set_error_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_core_loc_conf_t *lcf = conf;
+
+    if (!(lcf->err_log = ngx_log_create_errlog(cf->cycle, cf->args))) {
+        return NGX_CONF_ERROR;
+    }
+
+    return ngx_set_error_log_levels(cf, lcf->err_log);
+}
+
+
+static char *ngx_http_lowat_check(ngx_conf_t *cf, void *post, void *data)
+{
+#if (HAVE_LOWAT_EVENT)
+
+    ssize_t *np = data;
+
+    if (*np >= ngx_freebsd_net_inet_tcp_sendspace) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "\"send_lowat\" must be less than %d "
+                           "(sysctl net.inet.tcp.sendspace)",
+                           ngx_freebsd_net_inet_tcp_sendspace);
+
+        return NGX_CONF_ERROR;
+    }
+
+#else
+
+    ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                       "\"send_lowat\" is not supported, ignored");
+
+#endif
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_core_module.h
@@ -0,0 +1,214 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_CORE_H_INCLUDED_
+#define _NGX_HTTP_CORE_H_INCLUDED_
+
+
+#include <ngx_string.h>
+#include <ngx_array.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    in_addr_t  addr;
+    in_port_t  port;
+    int        family;
+    ngx_str_t  file_name;
+    int        line;
+
+    unsigned   default_server:1;
+} ngx_http_listen_t;
+
+
+typedef enum {
+    NGX_HTTP_REWRITE_PHASE = 0,
+
+    NGX_HTTP_FIND_CONFIG_PHASE,
+
+    NGX_HTTP_ACCESS_PHASE,
+    NGX_HTTP_CONTENT_PHASE,
+
+    NGX_HTTP_LAST_PHASE
+} ngx_http_phases;
+
+
+typedef struct {
+    ngx_array_t          handlers;
+    ngx_int_t            type;                /* NGX_OK, NGX_DECLINED */
+} ngx_http_phase_t;
+
+
+typedef struct {
+    ngx_array_t       servers;         /* array of ngx_http_core_srv_conf_t */
+
+    ngx_http_phase_t  phases[NGX_HTTP_LAST_PHASE];
+    ngx_array_t       index_handlers;
+
+    size_t            max_server_name_len;
+} ngx_http_core_main_conf_t;
+
+
+typedef struct {
+    /*
+     * array of ngx_http_core_loc_conf_t, used in the translation handler
+     * and in the merge phase
+     */
+    ngx_array_t           locations;
+
+    /* "listen", array of ngx_http_listen_t */
+    ngx_array_t           listen;
+
+    /* "server_name", array of ngx_http_server_name_t */
+    ngx_array_t           server_names;
+
+    /* server ctx */
+    ngx_http_conf_ctx_t  *ctx;
+
+    size_t                connection_pool_size;
+    size_t                request_pool_size;
+    size_t                client_header_buffer_size;
+
+    ngx_bufs_t            large_client_header_buffers;
+
+    ngx_msec_t            post_accept_timeout;
+    ngx_msec_t            client_header_timeout;
+
+    ngx_uint_t            restrict_host_names;
+} ngx_http_core_srv_conf_t;
+
+
+/* list of structures to find core_srv_conf quickly at run time */
+
+typedef struct {
+    in_port_t     port;
+    ngx_str_t     port_text;
+    ngx_array_t   addrs;       /* array of ngx_http_in_addr_t */
+} ngx_http_in_port_t;
+
+
+typedef struct {
+    in_addr_t                  addr;
+    ngx_array_t                names;     /* array of ngx_http_server_name_t */
+    ngx_http_core_srv_conf_t  *core_srv_conf;  /* default server conf
+                                                  for this address:port */
+
+    unsigned                   default_server:1;
+} ngx_http_in_addr_t;
+
+
+typedef struct {
+    ngx_str_t                  name;
+    ngx_http_core_srv_conf_t  *core_srv_conf; /* virtual name server conf */
+} ngx_http_server_name_t;
+
+
+#define NGX_HTTP_TYPES_HASH_PRIME  13
+
+#define ngx_http_types_hash_key(key, ext)                                   \
+        {                                                                   \
+            u_int n;                                                        \
+            for (key = 0, n = 0; n < ext.len; n++) {                        \
+                key += ext.data[n];                                         \
+            }                                                               \
+            key %= NGX_HTTP_TYPES_HASH_PRIME;                               \
+        }
+
+typedef struct {
+    ngx_str_t  exten;
+    ngx_str_t  type;
+} ngx_http_type_t;
+
+
+typedef struct {
+    ngx_int_t  status;
+    ngx_int_t  overwrite;
+    ngx_str_t  uri;
+} ngx_http_err_page_t;
+
+
+typedef struct ngx_http_core_loc_conf_s  ngx_http_core_loc_conf_t;
+
+struct ngx_http_core_loc_conf_s {
+    ngx_str_t     name;          /* location name */
+
+#if (HAVE_PCRE)
+    ngx_regex_t  *regex;
+#endif
+
+    unsigned      exact_match:1;
+    unsigned      auto_redirect:1;
+    unsigned      alias:1;
+
+    /* array of inclusive ngx_http_core_loc_conf_t */
+    ngx_array_t   locations;
+
+    /* pointer to the modules' loc_conf */
+    void        **loc_conf ;
+
+    ngx_http_handler_pt  handler;
+
+    ngx_str_t     root;                    /* root, alias */
+
+    ngx_array_t  *types;
+    ngx_str_t     default_type;
+
+    size_t        client_max_body_size;    /* client_max_body_size */
+    size_t        client_body_buffer_size; /* client_body_buffer_size */
+    size_t        send_lowat;              /* send_lowat */
+    size_t        postpone_output;         /* postpone_output */
+    size_t        limit_rate;              /* limit_rate */
+
+    ngx_msec_t    client_body_timeout;     /* client_body_timeout */
+    ngx_msec_t    send_timeout;            /* send_timeout */
+    ngx_msec_t    keepalive_timeout;       /* keepalive_timeout */
+    ngx_msec_t    lingering_time;          /* lingering_time */
+    ngx_msec_t    lingering_timeout;       /* lingering_timeout */
+
+    time_t        keepalive_header;        /* keepalive_timeout */
+
+    ngx_flag_t    sendfile;                /* sendfile */
+    ngx_flag_t    tcp_nopush;              /* tcp_nopush */
+    ngx_flag_t    reset_timedout_connection; /* reset_timedout_connection */
+    ngx_flag_t    msie_padding;            /* msie_padding */
+
+    ngx_array_t  *error_pages;             /* error_page */
+
+    ngx_http_cache_hash_t  *open_files;
+
+    ngx_log_t    *err_log;
+
+    ngx_http_core_loc_conf_t  *prev_location;
+};
+
+
+extern ngx_http_module_t  ngx_http_core_module_ctx;
+extern ngx_module_t  ngx_http_core_module;
+
+extern int ngx_http_max_module;
+
+
+
+ngx_int_t ngx_http_find_location_config(ngx_http_request_t *r);
+ngx_int_t ngx_http_core_translate_handler(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
+ngx_int_t ngx_http_set_exten(ngx_http_request_t *r);
+
+ngx_int_t ngx_http_internal_redirect(ngx_http_request_t *r,
+                                     ngx_str_t *uri, ngx_str_t *args);
+
+
+typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r);
+typedef ngx_int_t (*ngx_http_output_body_filter_pt)
+                                   (ngx_http_request_t *r, ngx_chain_t *chain);
+
+
+ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *chain);
+
+
+#endif /* _NGX_HTTP_CORE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_file_cache.c
@@ -0,0 +1,239 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+#if (HAVE_OPENSSL_MD5_H)
+#include <openssl/md5.h>
+#else
+#include <md5.h>
+#endif
+
+#if (HAVE_OPENSSL_MD5)
+#define  MD5Init    MD5_Init
+#define  MD5Update  MD5_Update
+#define  MD5Final   MD5_Final
+#endif
+
+
+
+int ngx_http_cache_get_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx)
+{
+    MD5_CTX  md5;
+
+    /* we use offsetof() because sizeof() pads struct size to int size */
+    ctx->header_size = offsetof(ngx_http_cache_header_t, key)
+                                                            + ctx->key.len + 1;
+
+    ctx->file.name.len = ctx->path->name.len + 1 + ctx->path->len + 32;
+    if (!(ctx->file.name.data = ngx_palloc(r->pool, ctx->file.name.len + 1))) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(ctx->file.name.data, ctx->path->name.data, ctx->path->name.len);
+
+    MD5Init(&md5);
+    MD5Update(&md5, (u_char *) ctx->key.data, ctx->key.len);
+    MD5Final(ctx->md5, &md5);
+
+    ngx_md5_text(ctx->file.name.data + ctx->path->name.len + 1 + ctx->path->len,
+                 ctx->md5);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+               "file cache uri: %s, md5: %s", ctx->key.data,
+               ctx->file.name.data + ctx->path->name.len + 1 + ctx->path->len);
+
+    ngx_create_hashed_filename(&ctx->file, ctx->path);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "file cache name: %s", ctx->file.name.data);
+
+    /* TODO: look open files cache */
+
+    return ngx_http_cache_open_file(ctx, 0);
+}
+
+
+int ngx_http_cache_open_file(ngx_http_cache_ctx_t *ctx, ngx_file_uniq_t uniq)
+{
+    ssize_t                   n;
+    ngx_err_t                 err;
+    ngx_http_cache_header_t  *h;
+
+    ctx->file.fd = ngx_open_file(ctx->file.name.data,
+                                 NGX_FILE_RDONLY, NGX_FILE_OPEN);
+
+    if (ctx->file.fd == NGX_INVALID_FILE) {
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT || err == NGX_ENOTDIR) {
+            return NGX_DECLINED;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                      ngx_open_file_n " \"%s\" failed", ctx->file.name.data);
+        return NGX_ERROR;
+    }
+
+    if (uniq) {
+        if (ngx_fd_info(ctx->file.fd, &ctx->file.info) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_CRIT, ctx->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", ctx->file.name.data);
+
+            return NGX_ERROR;
+        }
+
+        if (ngx_file_uniq(&ctx->file.info) == uniq) {
+            if (ngx_close_file(ctx->file.fd) == NGX_FILE_ERROR) {
+                ngx_log_error(NGX_LOG_ALERT, ctx->log, ngx_errno,
+                              ngx_close_file_n " \"%s\" failed",
+                              ctx->file.name.data);
+            }
+
+            return NGX_HTTP_CACHE_THE_SAME;
+        }
+    }
+
+    n = ngx_read_file(&ctx->file, ctx->buf->pos,
+                      ctx->buf->end - ctx->buf->last, 0);
+
+    if (n == NGX_ERROR || n == NGX_AGAIN) {
+        return n;
+    }
+
+    if (n <= ctx->header_size) {
+        ngx_log_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "cache file \"%s\" is too small", ctx->file.name.data);
+        return NGX_ERROR;
+    }
+
+    h = (ngx_http_cache_header_t *) ctx->buf->pos;
+    ctx->expires = h->expires;
+    ctx->last_modified= h->last_modified;
+    ctx->date = h->date;
+    ctx->length = h->length;
+
+    if (h->key_len > (size_t) (ctx->buf->end - ctx->buf->pos)) {
+        ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
+                      "cache file \"%s\" is probably invalid",
+                      ctx->file.name.data);
+        return NGX_DECLINED;
+    }
+
+    if (ctx->key.len
+        && (h->key_len != ctx->key.len
+            || ngx_strncmp(h->key, ctx->key.data, h->key_len) != 0))
+    {
+        h->key[h->key_len] = '\0';
+        ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
+                          "md5 collision: \"%s\" and \"%s\"",
+                          h->key, ctx->key.data);
+        return NGX_DECLINED;
+    }
+
+    ctx->buf->last += n;
+
+    if (ctx->expires < ngx_time()) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ctx->log, 0,
+                       "http file cache expired");
+
+        return NGX_HTTP_CACHE_STALE;
+    }
+
+    /* TODO: NGX_HTTP_CACHE_AGED */
+
+    return NGX_OK;
+}
+
+
+int ngx_http_cache_update_file(ngx_http_request_t *r, ngx_http_cache_ctx_t *ctx,
+                               ngx_str_t *temp_file)
+{
+    int        retry;
+    ngx_err_t  err;
+
+    retry = 0;
+
+    for ( ;; ) {
+        if (ngx_rename_file(temp_file->data, ctx->file.name.data) == NGX_OK) {
+            return NGX_OK;
+        }
+
+        err = ngx_errno;
+
+#if (WIN32)
+        if (err == NGX_EEXIST) {
+            if (ngx_win32_rename_file(temp_file, &ctx->file.name, r->pool)
+                                                                  == NGX_ERROR)
+            {
+                return NGX_ERROR;
+            }
+        }
+#endif
+
+        if (retry || (err != NGX_ENOENT && err != NGX_ENOTDIR)) {
+            ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+                          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;
+    }
+}
+
+
+int ngx_garbage_collector_http_cache_handler(ngx_gc_t *gc, ngx_str_t *name,
+                                             ngx_dir_t *dir)
+{
+    int                   rc;
+    char                  data[sizeof(ngx_http_cache_header_t)];
+    ngx_hunk_t            buf;
+    ngx_http_cache_ctx_t  ctx;
+
+    ctx.file.fd = NGX_INVALID_FILE;
+    ctx.file.name = *name;
+    ctx.file.log = gc->log;
+
+    ctx.header_size = sizeof(ngx_http_cache_header_t);
+    ctx.buf = &buf;
+    ctx.log = gc->log;
+    ctx.key.len = 0;
+
+    buf.type = NGX_HUNK_IN_MEMORY|NGX_HUNK_TEMP;
+    buf.pos = data;
+    buf.last = data;
+    buf.start = data;
+    buf.end = data + sizeof(ngx_http_cache_header_t);
+
+    rc = ngx_http_cache_open_file(&ctx, 0);
+
+    /* TODO: NGX_AGAIN */
+
+    if (rc != NGX_ERROR && rc != NGX_DECLINED && rc != NGX_HTTP_CACHE_STALE) {
+        return NGX_OK;
+    }
+
+    if (ngx_delete_file(name->data) == NGX_FILE_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, gc->log, ngx_errno,
+                      ngx_delete_file_n " \"%s\" failed", name->data);
+        return NGX_ERROR;
+    }
+
+    gc->deleted++;
+    gc->freed += ngx_de_size(dir);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_header_filter.c
@@ -0,0 +1,459 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static ngx_int_t ngx_http_header_filter_init(ngx_cycle_t *cycle);
+static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r);
+
+
+static ngx_http_module_t  ngx_http_header_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_header_filter_module = {
+    NGX_MODULE,
+    &ngx_http_header_filter_module_ctx,    /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_header_filter_init,           /* init module */
+    NULL                                   /* init child */
+};
+
+
+static char server_string[] = "Server: " NGINX_VER CRLF;
+
+
+static ngx_str_t http_codes[] = {
+
+    ngx_string("200 OK"),
+    ngx_null_string,  /* "201 Created" */
+    ngx_null_string,  /* "202 Accepted" */
+    ngx_null_string,  /* "203 Non-Authoritative Information" */
+    ngx_null_string,  /* "204 No Content" */
+    ngx_null_string,  /* "205 Reset Content" */
+    ngx_string("206 Partial Content"),
+    ngx_null_string,  /* "207 Multi-Status" */
+
+#if 0
+    ngx_null_string,  /* "300 Multiple Choices" */
+#endif
+
+    ngx_string("301 Moved Permanently"),
+#if 0
+    ngx_string("302 Moved Temporarily"),
+#else
+    ngx_string("302 Found"),
+#endif
+    ngx_null_string,  /* "303 See Other" */
+    ngx_string("304 Not Modified"),
+
+    ngx_string("400 Bad Request"),
+    ngx_string("401 Unauthorized"),
+    ngx_null_string,  /* "402 Payment Required" */
+    ngx_string("403 Forbidden"),
+    ngx_string("404 Not Found"),
+    ngx_string("405 Not Allowed"),
+    ngx_null_string,  /* "406 Not Acceptable" */
+    ngx_null_string,  /* "407 Proxy Authentication Required" */
+    ngx_string("408 Request Time-out"),
+    ngx_null_string,  /* "409 Conflict" */
+    ngx_null_string,  /* "410 Gone" */
+    ngx_string("411 Length Required"),
+    ngx_null_string,  /* "412 Precondition Failed" */
+    ngx_string("413 Request Entity Too Large"),
+    ngx_null_string,  /* "414 Request-URI Too Large" but we never send it
+                       * because we treat such requests as the HTTP/0.9
+                       * requests and send only a body without a header
+                       */
+    ngx_null_string,  /* "415 Unsupported Media Type" */
+    ngx_string("416 Requested Range Not Satisfiable"),
+
+    ngx_string("500 Internal Server Error"),
+    ngx_string("501 Method Not Implemented"),
+    ngx_string("502 Bad Gateway"),
+    ngx_string("503 Service Temporarily Unavailable"),
+    ngx_string("504 Gateway Time-out")
+};
+
+
+ngx_http_header_t  ngx_http_headers_out[] = {
+    { ngx_string("Server"), offsetof(ngx_http_headers_out_t, server) },
+    { ngx_string("Date"), offsetof(ngx_http_headers_out_t, date) },
+    { ngx_string("Content-Type"),
+                             offsetof(ngx_http_headers_out_t, content_type) },
+    { ngx_string("Content-Length"),
+                           offsetof(ngx_http_headers_out_t, content_length) },
+    { ngx_string("Content-Encoding"),
+                         offsetof(ngx_http_headers_out_t, content_encoding) },
+    { ngx_string("Location"), offsetof(ngx_http_headers_out_t, location) },
+    { ngx_string("Last-Modified"),
+                            offsetof(ngx_http_headers_out_t, last_modified) },
+    { ngx_string("Accept-Ranges"),
+                            offsetof(ngx_http_headers_out_t, accept_ranges) },
+    { ngx_string("Expires"), offsetof(ngx_http_headers_out_t, expires) },
+    { ngx_string("Cache-Control"),
+                            offsetof(ngx_http_headers_out_t, cache_control) },
+    { ngx_string("ETag"), offsetof(ngx_http_headers_out_t, etag) },
+
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r)
+{
+    u_char                    *p;
+    size_t                     len;
+    ngx_uint_t                 status, i;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *ln;
+    ngx_list_part_t           *part;
+    ngx_table_elt_t           *header;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->http_version < NGX_HTTP_VERSION_10) {
+        return NGX_OK;
+    }
+
+    if (r->method == NGX_HTTP_HEAD) {
+        r->header_only = 1;
+    }
+
+    if (r->headers_out.last_modified_time != -1) {
+        if (r->headers_out.status != NGX_HTTP_OK
+            && r->headers_out.status != NGX_HTTP_NOT_MODIFIED
+            && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT)
+        {
+            r->headers_out.last_modified_time = -1;
+            r->headers_out.last_modified = NULL;
+        }
+    }
+
+    /* 2 is for trailing "\r\n" and 2 is for "\r\n" in the end of header */
+    len = sizeof("HTTP/1.x ") - 1 + 2 + 2;
+
+    /* status line */
+    if (r->headers_out.status_line.len) {
+        len += r->headers_out.status_line.len;
+#if (NGX_SUPPRESS_WARN)
+        status = NGX_INVALID_ARRAY_INDEX;
+#endif
+
+    } else {
+
+        if (r->headers_out.status < NGX_HTTP_MOVED_PERMANENTLY) {
+            /* 2XX */
+            status = r->headers_out.status - NGX_HTTP_OK;
+
+        } else if (r->headers_out.status < NGX_HTTP_BAD_REQUEST) {
+            /* 3XX */
+            status = r->headers_out.status - NGX_HTTP_MOVED_PERMANENTLY + 8;
+
+            if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
+                r->header_only = 1;
+            }
+
+        } else if (r->headers_out.status < NGX_HTTP_INTERNAL_SERVER_ERROR) {
+            /* 4XX */
+            status = r->headers_out.status - NGX_HTTP_BAD_REQUEST + 8 + 4;
+
+        } else {
+            /* 5XX */
+            status = r->headers_out.status
+                                 - NGX_HTTP_INTERNAL_SERVER_ERROR + 8 + 4 + 17;
+        }
+
+        len += http_codes[status].len;
+    }
+
+    if (r->headers_out.server && r->headers_out.server->key.len) {
+        len += r->headers_out.server->key.len
+               + r->headers_out.server->value.len + 2;
+    } else {
+        len += sizeof(server_string) - 1;
+    }
+
+    if (r->headers_out.date && r->headers_out.date->key.len) {
+        len += r->headers_out.date->key.len
+               + r->headers_out.date->value.len + 2;
+    } else {
+        len += sizeof("Date: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+    }
+
+    if (r->headers_out.content_length == NULL) {
+        if (r->headers_out.content_length_n >= 0) {
+            len += sizeof("Content-Length: ") - 1 + NGX_OFF_T_LEN + 2;
+        }
+    }
+
+    if (r->headers_out.content_type && r->headers_out.content_type->value.len) {
+        r->headers_out.content_type->key.len = 0;
+        len += sizeof("Content-Type: ") - 1
+               + r->headers_out.content_type->value.len + 2;
+
+        if (r->headers_out.charset.len) {
+            len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
+        }
+    }
+
+    if (r->headers_out.location
+        && r->headers_out.location->value.len
+        && r->headers_out.location->value.data[0] == '/')
+    {
+        r->headers_out.location->key.len = 0;
+        len += sizeof("Location: http://") - 1
+               + r->server_name->len + r->headers_out.location->value.len + 2;
+
+        if (r->port != 80) {
+            len += r->port_text->len;
+        }
+    }
+
+    if (r->headers_out.last_modified && r->headers_out.last_modified->key.len) {
+        len += r->headers_out.last_modified->key.len
+               + r->headers_out.last_modified->value.len + 2;
+
+    } else if (r->headers_out.last_modified_time != -1) {
+        len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
+    }
+
+    if (r->chunked) {
+        len += sizeof("Transfer-Encoding: chunked" CRLF) - 1;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->keepalive) {
+        len += sizeof("Connection: keep-alive" CRLF) - 1;
+
+        /*
+         * MSIE and Opera ignore the "Keep-Alive: timeout=<N>" header.
+         * MSIE keeps the connection alive for about 60-65 seconds.
+         * Opera keeps the connection alive very long.
+         * Mozilla keeps the connection alive for N plus about 1-10 seconds.
+         * Konqueror keeps the connection alive for about N seconds.
+         */
+
+        if (clcf->keepalive_header
+            && (r->headers_in.gecko || r->headers_in.konqueror))
+        {
+            len += sizeof("Keep-Alive: timeout=") - 1 + TIME_T_LEN + 2;
+        }
+
+    } else {
+        len += sizeof("Connection: closed" CRLF) - 1;
+    }
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].key.len == 0) {
+            continue;
+        }
+
+        /* 2 is for ": " and 2 is for "\r\n" */
+        len += header[i].key.len + 2 + header[i].value.len + 2;
+    }
+
+    if (!(b = ngx_create_temp_buf(r->pool, len))) {
+        return NGX_ERROR;
+    }
+
+    /* "HTTP/1.x " */
+    b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
+
+    /* status line */
+    if (r->headers_out.status_line.len) {
+        b->last = ngx_cpymem(b->last, r->headers_out.status_line.data,
+                             r->headers_out.status_line.len);
+
+    } else {
+        b->last = ngx_cpymem(b->last, http_codes[status].data,
+                             http_codes[status].len);
+    }
+    *(b->last++) = CR; *(b->last++) = LF;
+
+    if (!(r->headers_out.server && r->headers_out.server->key.len)) {
+        b->last = ngx_cpymem(b->last, server_string, sizeof(server_string) - 1);
+    }
+
+    if (!(r->headers_out.date && r->headers_out.date->key.len)) {
+        b->last = ngx_cpymem(b->last, "Date: ", sizeof("Date: ") - 1);
+        b->last = ngx_cpymem(b->last, ngx_cached_http_time.data,
+                             ngx_cached_http_time.len);
+
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+    if (r->headers_out.content_length == NULL) {
+        if (r->headers_out.content_length_n >= 0) {
+            b->last += ngx_snprintf((char *) b->last,
+                                sizeof("Content-Length: ") + NGX_OFF_T_LEN + 2,
+                                "Content-Length: " OFF_T_FMT CRLF,
+                                r->headers_out.content_length_n);
+        }
+    }
+
+    if (r->headers_out.content_type && r->headers_out.content_type->value.len) {
+        b->last = ngx_cpymem(b->last, "Content-Type: ",
+                             sizeof("Content-Type: ") - 1);
+        p = b->last;
+        b->last = ngx_cpymem(b->last, r->headers_out.content_type->value.data,
+                             r->headers_out.content_type->value.len);
+
+        if (r->headers_out.charset.len) {
+            b->last = ngx_cpymem(b->last, "; charset=",
+                                 sizeof("; charset=") - 1);
+            b->last = ngx_cpymem(b->last, r->headers_out.charset.data,
+                                 r->headers_out.charset.len);
+
+            r->headers_out.content_type->value.len = b->last - p;
+            r->headers_out.content_type->value.data = p;
+        }
+
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+    if (r->headers_out.location
+        && r->headers_out.location->value.len
+        && r->headers_out.location->value.data[0] == '/')
+    {
+        p = b->last + sizeof("Location: ") - 1;
+        b->last = ngx_cpymem(b->last, "Location: http://",
+                             sizeof("Location: http://") - 1);
+        b->last = ngx_cpymem(b->last, r->server_name->data,
+                             r->server_name->len);
+        if (r->port != 80) {
+            b->last = ngx_cpymem(b->last, r->port_text->data,
+                                 r->port_text->len);
+        }
+
+        b->last = ngx_cpymem(b->last, r->headers_out.location->value.data,
+                             r->headers_out.location->value.len);
+
+        r->headers_out.location->value.len = b->last - p;
+        r->headers_out.location->value.data = p;
+
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+    if (!(r->headers_out.last_modified && r->headers_out.last_modified->key.len)
+        && r->headers_out.last_modified_time != -1)
+    {
+        b->last = ngx_cpymem(b->last, "Last-Modified: ",
+                             sizeof("Last-Modified: ") - 1);
+        b->last += ngx_http_time(b->last, r->headers_out.last_modified_time);
+
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+    if (r->chunked) {
+        b->last = ngx_cpymem(b->last, "Transfer-Encoding: chunked" CRLF,
+                             sizeof("Transfer-Encoding: chunked" CRLF) - 1);
+    }
+
+    if (r->keepalive) {
+        b->last = ngx_cpymem(b->last, "Connection: keep-alive" CRLF,
+                             sizeof("Connection: keep-alive" CRLF) - 1);
+
+        if (clcf->keepalive_header
+            && (r->headers_in.gecko || r->headers_in.konqueror))
+        {
+            b->last += ngx_snprintf((char *) b->last,
+                            sizeof("Keep-Alive: timeout=") + TIME_T_LEN + 2,
+                            "Keep-Alive: timeout=" TIME_T_FMT CRLF,
+                            clcf->keepalive_header);
+        }
+
+    } else {
+        b->last = ngx_cpymem(b->last, "Connection: close" CRLF,
+                             sizeof("Connection: close" CRLF) - 1);
+    }
+
+    part = &r->headers_out.headers.part;
+    header = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        if (header[i].key.len == 0) {
+            continue;
+        }
+
+        b->last = ngx_cpymem(b->last, header[i].key.data, header[i].key.len);
+        *(b->last++) = ':' ; *(b->last++) = ' ' ;
+
+        b->last = ngx_cpymem(b->last, header[i].value.data,
+                             header[i].value.len);
+        *(b->last++) = CR; *(b->last++) = LF;
+    }
+
+#if (NGX_DEBUG)
+    *(b->last) = '\0';
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "%s\n", b->pos);
+#endif
+
+    /* the end of HTTP header */
+    *(b->last++) = CR; *(b->last++) = LF;
+
+    r->header_size = b->last - b->pos;
+
+    if (r->header_only) {
+        b->last_buf = 1;
+    }
+
+    if (!(ln = ngx_alloc_chain_link(r->pool))) {
+        return NGX_ERROR;
+    }
+
+    ln->buf = b;
+    ln->next = NULL;
+
+    return ngx_http_write_filter(r, ln);
+}
+
+
+static ngx_int_t ngx_http_header_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_top_header_filter = ngx_http_header_filter;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_log_handler.c
@@ -0,0 +1,978 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static u_char *ngx_http_log_addr(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data);
+static u_char *ngx_http_log_connection(ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data);
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data);
+static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data);
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data);
+static u_char *ngx_http_log_request(ngx_http_request_t *r, u_char *buf,
+                                    uintptr_t data);
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+                                   uintptr_t data);
+static u_char *ngx_http_log_length(ngx_http_request_t *r, u_char *buf,
+                                   uintptr_t data);
+static u_char *ngx_http_log_apache_length(ngx_http_request_t *r, u_char *buf,
+                                          uintptr_t data);
+static u_char *ngx_http_log_header_in(ngx_http_request_t *r, u_char *buf,
+                                      uintptr_t data);
+static u_char *ngx_http_log_connection_header_out(ngx_http_request_t *r,
+                                                  u_char *buf, uintptr_t data);
+static u_char *ngx_http_log_transfer_encoding_header_out(ngx_http_request_t *r,
+                                                         u_char *buf,
+                                                         uintptr_t data);
+static u_char *ngx_http_log_unknown_header_in(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data);
+static u_char *ngx_http_log_header_out(ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data);
+static u_char *ngx_http_log_unknown_header_out(ngx_http_request_t *r, u_char *buf,
+                                               uintptr_t data);
+
+static ngx_int_t ngx_http_log_pre_conf(ngx_conf_t *cf);
+static void *ngx_http_log_create_main_conf(ngx_conf_t *cf);
+static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent,
+                                         void *child);
+static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf);
+static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,
+                                     void *conf);
+static ngx_int_t ngx_http_log_parse_format(ngx_conf_t *cf, ngx_array_t *ops,
+                                           ngx_str_t *line);
+
+
+static ngx_command_t  ngx_http_log_commands[] = {
+
+    {ngx_string("log_format"),
+     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE,
+     ngx_http_log_set_format,
+     NGX_HTTP_MAIN_CONF_OFFSET,
+     0,
+     NULL},
+
+    {ngx_string("access_log"),
+     NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+     ngx_http_log_set_log,
+     NGX_HTTP_LOC_CONF_OFFSET,
+     0,
+     NULL},
+
+    ngx_null_command
+};
+
+
+ngx_http_module_t  ngx_http_log_module_ctx = {
+    ngx_http_log_pre_conf,                 /* pre conf */
+
+    ngx_http_log_create_main_conf,         /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    ngx_http_log_create_loc_conf,          /* create location configration */
+    ngx_http_log_merge_loc_conf            /* merge location configration */
+};
+
+
+ngx_module_t  ngx_http_log_module = {
+    NGX_MODULE,
+    &ngx_http_log_module_ctx,              /* module context */
+    ngx_http_log_commands,                 /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    NULL,                                  /* init module */
+    NULL                                   /* init child */
+};
+
+
+static ngx_str_t http_access_log = ngx_string(NGX_HTTP_LOG_PATH);
+
+
+static ngx_str_t ngx_http_combined_fmt =
+    ngx_string("%addr - - [%time] \"%request\" %status %apache_length "
+               "\"%{Referer}i\" \"%{User-Agent}i\"");
+
+
+ngx_http_log_op_name_t ngx_http_log_fmt_ops[] = {
+    { ngx_string("addr"), INET_ADDRSTRLEN - 1, ngx_http_log_addr },
+    { ngx_string("conn"), NGX_INT32_LEN, ngx_http_log_connection },
+    { ngx_string("pipe"), 1, ngx_http_log_pipe },
+    { ngx_string("time"), sizeof("28/Sep/1970:12:00:00") - 1,
+                          ngx_http_log_time },
+    { ngx_string("msec"), TIME_T_LEN + 4, ngx_http_log_msec },
+    { ngx_string("request"), 0, ngx_http_log_request },
+    { ngx_string("status"), 3, ngx_http_log_status },
+    { ngx_string("length"), NGX_OFF_T_LEN, ngx_http_log_length },
+    { ngx_string("apache_length"), NGX_OFF_T_LEN, ngx_http_log_apache_length },
+    { ngx_string("i"), NGX_HTTP_LOG_ARG, ngx_http_log_header_in },
+    { ngx_string("o"), NGX_HTTP_LOG_ARG, ngx_http_log_header_out },
+    { ngx_null_string, 0, NULL }
+};
+
+
+ngx_int_t ngx_http_log_handler(ngx_http_request_t *r)
+{
+    ngx_uint_t                i, l;
+    uintptr_t                 data;
+    u_char                   *line, *p;
+    size_t                    len;
+    ngx_http_log_t           *log;
+    ngx_http_log_op_t        *op;
+    ngx_http_log_loc_conf_t  *lcf;
+#if (WIN32)
+    u_long                    written;
+#endif
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http log handler");
+
+    lcf = ngx_http_get_module_loc_conf(r, ngx_http_log_module);
+
+    if (lcf->off) {
+        return NGX_OK;
+    }
+
+    log = lcf->logs->elts;
+    for (l = 0; l < lcf->logs->nelts; l++) {
+
+        len = 0;
+        op = log[l].ops->elts;
+        for (i = 0; i < log[l].ops->nelts; i++) {
+            if (op[i].len == 0) {
+                len += (size_t) op[i].op(r, NULL, op[i].data);
+
+            } else {
+                len += op[i].len;
+            }
+        }
+
+#if (WIN32)
+        len += 2;
+#else
+        len++;
+#endif
+
+        ngx_test_null(line, ngx_palloc(r->pool, len), NGX_ERROR);
+        p = line;
+
+        for (i = 0; i < log[l].ops->nelts; i++) {
+            if (op[i].op == NGX_HTTP_LOG_COPY_SHORT) {
+                len = op[i].len;
+                data = op[i].data;
+                while (len--) {
+                    *p++ = (char) (data & 0xff);
+                    data >>= 8;
+                }
+
+            } else if (op[i].op == NGX_HTTP_LOG_COPY_LONG) {
+                p = ngx_cpymem(p, (void *) op[i].data, op[i].len);
+
+            } else {
+                p = op[i].op(r, p, op[i].data);
+            }
+        }
+
+#if (WIN32)
+        *p++ = CR; *p++ = LF;
+        WriteFile(log[l].file->fd, line, p - line, &written, NULL);
+#else
+        *p++ = LF;
+        write(log[l].file->fd, line, p - line);
+#endif
+    }
+
+    return NGX_OK;
+}
+
+
+static u_char *ngx_http_log_addr(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data)
+{
+    return ngx_cpymem(buf, r->connection->addr_text.data,
+                      r->connection->addr_text.len);
+}
+
+
+static u_char *ngx_http_log_connection(ngx_http_request_t *r, u_char *buf,
+                                     uintptr_t data)
+{
+    return buf + ngx_snprintf((char *) buf, NGX_INT_T_LEN + 1,
+                              "%" NGX_UINT_T_FMT,
+                              r->connection->number);
+}
+
+
+static u_char *ngx_http_log_pipe(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data)
+{
+    if (r->pipeline) {
+        *buf = 'p';
+    } else {
+        *buf = '.';
+    }
+
+    return buf + 1;
+}
+
+
+static u_char *ngx_http_log_time(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data)
+{
+    return ngx_cpymem(buf, ngx_cached_http_log_time.data,
+                      ngx_cached_http_log_time.len);
+}
+
+
+static u_char *ngx_http_log_msec(ngx_http_request_t *r, u_char *buf,
+                                 uintptr_t data)
+{
+    struct timeval  tv;
+
+    ngx_gettimeofday(&tv);
+
+    return buf + ngx_snprintf((char *) buf, TIME_T_LEN + 5, "%ld.%03ld",
+                              tv.tv_sec, tv.tv_usec / 1000);
+}
+
+
+static u_char *ngx_http_log_request(ngx_http_request_t *r, u_char *buf,
+                                    uintptr_t data)
+{
+    if (buf == NULL) {
+        /* find the request line length */
+        return (u_char *) r->request_line.len;
+    }
+
+    return ngx_cpymem(buf, r->request_line.data, r->request_line.len);
+}
+
+
+static u_char *ngx_http_log_status(ngx_http_request_t *r, u_char *buf,
+                                   uintptr_t data)
+{
+    return buf + ngx_snprintf((char *) buf, 4, "%" NGX_UINT_T_FMT,
+                        r->err_status ? r->err_status : r->headers_out.status);
+}
+
+
+static u_char *ngx_http_log_length(ngx_http_request_t *r, u_char *buf,
+                                   uintptr_t data)
+{
+    return buf + ngx_snprintf((char *) buf, NGX_OFF_T_LEN + 1, OFF_T_FMT,
+                              r->connection->sent);
+}
+
+
+static u_char *ngx_http_log_apache_length(ngx_http_request_t *r, u_char *buf,
+                                          uintptr_t data)
+{
+    return buf + ngx_snprintf((char *) buf, NGX_OFF_T_LEN + 1, OFF_T_FMT,
+                              r->connection->sent - r->header_size);
+}
+
+
+static u_char *ngx_http_log_header_in(ngx_http_request_t *r, u_char *buf,
+                                      uintptr_t data)
+{
+    ngx_uint_t          i;
+    ngx_str_t          *s;
+    ngx_table_elt_t    *h;
+    ngx_http_log_op_t  *op;
+
+    if (r) {
+        h = *(ngx_table_elt_t **) ((char *) &r->headers_in + data);
+
+        if (h == NULL) {
+
+            /* no header */
+
+            if (buf) {
+                *buf = '-';
+            }
+
+            return buf + 1;
+        }
+
+        if (buf == NULL) {
+            /* find the header length */
+            return (u_char *) h->value.len;
+        }
+
+        return ngx_cpymem(buf, h->value.data, h->value.len);
+    }
+
+    /* find an offset while a format string compilation */
+
+    op = (ngx_http_log_op_t *) buf;
+    s = (ngx_str_t *) data;
+
+    op->len = 0;
+
+    for (i = 0; ngx_http_headers_in[i].name.len != 0; i++) {
+        if (ngx_http_headers_in[i].name.len != s->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(ngx_http_headers_in[i].name.data, s->data, s->len)
+                                                                          == 0)
+        {
+            op->op = ngx_http_log_header_in;
+            op->data = ngx_http_headers_in[i].offset;
+            return NULL;
+        }
+    }
+
+    op->op = ngx_http_log_unknown_header_in;
+    op->data = (uintptr_t) s;
+
+    return NULL;
+}
+
+
+static u_char *ngx_http_log_unknown_header_in(ngx_http_request_t *r,
+                                              u_char *buf, uintptr_t data)
+{
+    ngx_uint_t        i;
+    ngx_str_t        *s;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *h;
+
+    s = (ngx_str_t *) data;
+
+    part = &r->headers_in.headers.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (h[i].key.len != s->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(h[i].key.data, s->data, s->len) == 0) {
+            if (buf == NULL) {
+                /* find the header length */
+                return (u_char *) h[i].value.len;
+            }
+
+            return ngx_cpymem(buf, h[i].value.data, h[i].value.len);
+        }
+    }
+
+    /* no header */
+
+    if (buf) {
+        *buf = '-';
+    }
+
+    return buf + 1;
+}
+
+
+static u_char *ngx_http_log_header_out(ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data)
+{
+    ngx_uint_t          i;
+    ngx_str_t          *s;
+    ngx_table_elt_t    *h;
+    ngx_http_log_op_t  *op;
+
+    if (r) {
+
+        /* run-time execution */
+
+        if (r->http_version < NGX_HTTP_VERSION_10) {
+            if (buf) {
+                *buf = '-';
+            }
+
+            return buf + 1;
+        }
+
+        h = *(ngx_table_elt_t **) ((char *) &r->headers_out + data);
+
+        if (h == NULL) {
+
+            /*
+             * No header pointer was found.
+             * However, some headers: "Date", "Server", "Content-Length",
+             * and "Last-Modified" have a special handling in the header filter
+             * but we do not set up their pointers in the filter because
+             * they are too seldom needed to be logged.
+             */
+
+            if (data == offsetof(ngx_http_headers_out_t, date)) {
+                if (buf == NULL) {
+                    return (u_char *) ngx_cached_http_time.len;
+                }
+                return ngx_cpymem(buf, ngx_cached_http_time.data,
+                                  ngx_cached_http_time.len);
+            }
+
+            if (data == offsetof(ngx_http_headers_out_t, server)) {
+                if (buf == NULL) {
+                    return (u_char *) (sizeof(NGINX_VER) - 1);
+                }
+                return ngx_cpymem(buf, NGINX_VER, sizeof(NGINX_VER) - 1);
+            }
+
+            if (data == offsetof(ngx_http_headers_out_t, content_length)) {
+                if (r->headers_out.content_length_n == -1) {
+                    if (buf) {
+                        *buf = '-';
+                    }
+                    return buf + 1;
+                }
+
+                if (buf == NULL) {
+                    return (u_char *) NGX_OFF_T_LEN;
+                }
+                return buf + ngx_snprintf((char *) buf,
+                                          NGX_OFF_T_LEN + 2, OFF_T_FMT,
+                                          r->headers_out.content_length_n);
+            }
+
+            if (data == offsetof(ngx_http_headers_out_t, last_modified)) {
+                if (r->headers_out.last_modified_time == -1) {
+                    if (buf) {
+                        *buf = '-';
+                    }
+                    return buf + 1;
+                }
+
+                if (buf == NULL) {
+                    return (u_char *)
+                                   sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
+                }
+                return buf + ngx_http_time(buf,
+                                           r->headers_out.last_modified_time);
+            }
+
+            if (buf) {
+                *buf = '-';
+            }
+
+            return buf + 1;
+        }
+
+        if (buf == NULL) {
+            /* find the header length */
+            return (u_char *) h->value.len;
+        }
+
+        return ngx_cpymem(buf, h->value.data, h->value.len);
+    }
+
+    /* find an offset while a format string compilation */
+
+    op = (ngx_http_log_op_t *) buf;
+    s = (ngx_str_t *) data;
+
+    op->len = 0;
+
+    for (i = 0; ngx_http_headers_out[i].name.len != 0; i++) {
+        if (ngx_http_headers_out[i].name.len != s->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(ngx_http_headers_out[i].name.data, s->data, s->len)
+                                                                          == 0)
+        {
+            op->op = ngx_http_log_header_out;
+            op->data = ngx_http_headers_out[i].offset;
+            return NULL;
+        }
+    }
+
+    if (s->len == sizeof("Connection") - 1
+        && ngx_strncasecmp(s->data, "Connection", s->len) == 0)
+    {
+        op->op = ngx_http_log_connection_header_out;
+        op->data = (uintptr_t) NULL;
+        return NULL;
+    }
+
+    if (s->len == sizeof("Transfer-Encoding") - 1
+        && ngx_strncasecmp(s->data, "Transfer-Encoding", s->len) == 0) {
+        op->op = ngx_http_log_transfer_encoding_header_out;
+        op->data = (uintptr_t) NULL;
+        return NULL;
+    }
+
+    op->op = ngx_http_log_unknown_header_out;
+    op->data = (uintptr_t) s;
+
+    return NULL;
+}
+
+
+static u_char *ngx_http_log_connection_header_out(ngx_http_request_t *r,
+                                                  u_char *buf, uintptr_t data)
+{
+    if (buf == NULL) {
+        return (u_char *) ((r->keepalive) ? sizeof("keep-alive") - 1:
+                                            sizeof("close") - 1);
+    }
+
+    if (r->keepalive) {
+        return ngx_cpymem(buf, "keep-alive", sizeof("keep-alive") - 1);
+
+    } else {
+        return ngx_cpymem(buf, "close", sizeof("close") - 1);
+    }
+}
+
+
+static u_char *ngx_http_log_transfer_encoding_header_out(ngx_http_request_t *r,
+                                                         u_char *buf,
+                                                         uintptr_t data)
+{
+    if (buf == NULL) {
+        return (u_char *) ((r->chunked) ? sizeof("chunked") - 1 : 1);
+    }
+
+    if (r->chunked) {
+        return ngx_cpymem(buf, "chunked", sizeof("chunked") - 1);
+    }
+
+    *buf = '-';
+
+    return buf + 1;
+}
+
+
+static u_char *ngx_http_log_unknown_header_out(ngx_http_request_t *r,
+                                               u_char *buf,
+                                               uintptr_t data)
+{
+    ngx_uint_t        i;
+    ngx_str_t        *s;
+    ngx_list_part_t  *part;
+    ngx_table_elt_t  *h;
+
+    s = (ngx_str_t *) data;
+
+    part = &r->headers_out.headers.part;
+    h = part->elts;
+
+    for (i = 0; /* void */; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            h = part->elts;
+            i = 0;
+        }
+
+        if (h[i].key.len != s->len) {
+            continue;
+        }
+
+        if (ngx_strncasecmp(h[i].key.data, s->data, s->len) == 0) {
+            if (buf == NULL) {
+                /* find the header length */
+                return (u_char *) h[i].value.len;
+            }
+
+            return ngx_cpymem(buf, h[i].value.data, h[i].value.len);
+        }
+    }
+
+    /* no header */
+
+    if (buf) {
+        *buf = '-';
+    }
+
+    return buf + 1;
+}
+
+
+static ngx_int_t ngx_http_log_pre_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_op_name_t  *op;
+
+    for (op = ngx_http_log_fmt_ops; op->name.len; op++) { /* void */ }
+    op->op = NULL;
+
+    return NGX_OK;
+}
+
+
+static void *ngx_http_log_create_main_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_main_conf_t  *conf;
+
+    char       *rc;
+    ngx_str_t  *value;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_main_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_init_array(conf->formats, cf->pool, 5, sizeof(ngx_http_log_fmt_t),
+                  NGX_CONF_ERROR);
+
+    cf->args->nelts = 0;
+
+    if (!(value = ngx_push_array(cf->args))) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (!(value = ngx_push_array(cf->args))) {
+        return NGX_CONF_ERROR;
+    }
+
+    value->len = sizeof("combined") - 1;
+    value->data = (u_char *) "combined";
+
+    if (!(value = ngx_push_array(cf->args))) {
+        return NGX_CONF_ERROR;
+    }
+
+    *value = ngx_http_combined_fmt;
+
+    rc = ngx_http_log_set_format(cf, NULL, conf);
+    if (rc != NGX_CONF_OK) {
+        return NULL;
+    }
+
+    return conf;
+}
+
+
+static void *ngx_http_log_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_log_loc_conf_t  *conf;
+
+    if (!(conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_log_loc_conf_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    return conf;
+}
+
+
+static char *ngx_http_log_merge_loc_conf(ngx_conf_t *cf, void *parent,
+                                         void *child)
+{
+    ngx_http_log_loc_conf_t *prev = parent;
+    ngx_http_log_loc_conf_t *conf = child;
+
+    ngx_http_log_t            *log;
+    ngx_http_log_fmt_t        *fmt;
+    ngx_http_log_main_conf_t  *lmcf;
+
+    if (conf->logs == NULL) {
+
+        if (conf->off) {
+            return NGX_CONF_OK;
+        }
+
+        if (prev->logs) {
+            conf->logs = prev->logs;
+
+        } else {
+
+            if (prev->off) {
+                conf->off = prev->off;
+                return NGX_CONF_OK;
+            }
+
+            conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+            if (conf->logs == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            if (!(log = ngx_array_push(conf->logs))) {
+                return NGX_CONF_ERROR;
+            }
+
+            log->file = ngx_conf_open_file(cf->cycle, &http_access_log);
+            if (log->file == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+            fmt = lmcf->formats.elts;
+
+            /* the default "combined" format */
+            log->ops = fmt[0].ops;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,
+                                  void *conf)
+{
+    ngx_http_log_loc_conf_t *llcf = conf;
+
+    ngx_uint_t                 i;
+    ngx_str_t                 *value, name;
+    ngx_http_log_t            *log;
+    ngx_http_log_fmt_t        *fmt;
+    ngx_http_log_main_conf_t  *lmcf;
+
+    value = cf->args->elts;
+
+    if (ngx_strcmp(value[1].data, "off") == 0) {
+        llcf->off = 1;
+        return NGX_CONF_OK;
+    }
+
+    if (llcf->logs == NULL) {
+        llcf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_http_log_t));
+        if (llcf->logs == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+    lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_log_module);
+
+    if (!(log = ngx_array_push(llcf->logs))) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (!(log->file = ngx_conf_open_file(cf->cycle, &value[1]))) {
+        return NGX_CONF_ERROR;
+    }
+
+    if (cf->args->nelts == 3) {
+        name = value[2];
+    } else {
+        name.len = sizeof("combined") - 1;
+        name.data = (u_char *) "combined";
+    }
+
+    fmt = lmcf->formats.elts;
+    for (i = 0; i < lmcf->formats.nelts; i++) {
+        if (fmt[i].name.len == name.len
+            && ngx_strcasecmp(fmt[i].name.data, name.data) == 0)
+        {
+            log->ops = fmt[i].ops;
+            return NGX_CONF_OK;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static char *ngx_http_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,
+                                     void *conf)
+{
+    ngx_http_log_main_conf_t *lmcf = conf;
+
+    ngx_uint_t                  s, f, invalid;
+    u_char                     *data, *p, *fname;
+    size_t                      i, len, fname_len;
+    ngx_str_t                  *value, arg, *a;
+    ngx_http_log_op_t          *op;
+    ngx_http_log_fmt_t         *fmt;
+    ngx_http_log_op_name_t     *name;
+
+    value = cf->args->elts;
+
+    fmt = lmcf->formats.elts;
+    for (f = 0; f < lmcf->formats.nelts; f++) {
+        if (fmt[f].name.len == value[1].len
+            && ngx_strcmp(fmt->name.data, value[1].data) == 0)
+        {
+            return "duplicate \"log_format\" name";
+        }
+    }
+
+    if (!(fmt = ngx_push_array(&lmcf->formats))) {
+        return NGX_CONF_ERROR;
+    }
+
+    fmt->name = value[1];
+
+    if (!(fmt->ops = ngx_create_array(cf->pool, 20,
+                                      sizeof(ngx_http_log_op_t)))) {
+        return NGX_CONF_ERROR;
+    }
+
+    invalid = 0;
+    data = NULL;
+
+    for (s = 2; s < cf->args->nelts && !invalid; s++) {
+
+        i = 0;
+
+        while (i < value[s].len) {
+
+            if (!(op = ngx_push_array(fmt->ops))) {
+                return NGX_CONF_ERROR;
+            }
+
+            data = &value[s].data[i];
+
+            if (value[s].data[i] == '%') {
+                i++;
+
+                if (i == value[s].len) {
+                    invalid = 1;
+                    break;
+                }
+
+                if (value[s].data[i] == '{') {
+                    i++;
+
+                    arg.data = &value[s].data[i];
+
+                    while (i < value[s].len && value[s].data[i] != '}') {
+                        i++;
+                    }
+
+                    arg.len = &value[s].data[i] - arg.data;
+
+                    if (i == value[s].len || arg.len == 0) {
+                        invalid = 1;
+                        break;
+                    }
+
+                    i++;
+
+                } else {
+                    arg.len = 0;
+                }
+
+                fname = &value[s].data[i];
+
+                while (i < value[s].len
+                       && ((value[s].data[i] >= 'a' && value[s].data[i] <= 'z')
+                           || value[s].data[i] == '_'))
+                {
+                    i++;
+                }
+
+                fname_len = &value[s].data[i] - fname;
+
+                if (fname_len == 0) {
+                    invalid = 1;
+                    break;
+                }
+
+                for (name = ngx_http_log_fmt_ops; name->op; name++) {
+                    if (name->name.len == 0) {
+                        name = (ngx_http_log_op_name_t *) name->op;
+                    }
+
+                    if (name->name.len == fname_len
+                        && ngx_strncmp(name->name.data, fname, fname_len) == 0)
+                    {
+                        if (name->len != NGX_HTTP_LOG_ARG) {
+                            if (arg.len) {
+                                fname[fname_len] = '\0';
+                                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                               "\"%s\" must not have argument",
+                                               data);
+                                return NGX_CONF_ERROR;
+                            }
+
+                            op->len = name->len;
+                            op->op = name->op;
+                            op->data = 0;
+
+                            break;
+                        }
+
+                        if (arg.len == 0) {
+                            fname[fname_len] = '\0';
+                            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                               "\"%s\" requires argument", 
+                                               data);
+                            return NGX_CONF_ERROR;
+                        }
+
+                        if (!(a = ngx_palloc(cf->pool, sizeof(ngx_str_t)))) {
+                            return NGX_CONF_ERROR;
+                        }
+
+                        *a = arg;
+                        name->op(NULL, (u_char *) op, (uintptr_t) a);
+
+                        break;
+                    }
+                }
+
+                if (name->name.len == 0) {
+                    invalid = 1;
+                    break;
+                }
+
+            } else {
+                i++;
+
+                while (i < value[s].len && value[s].data[i] != '%') {
+                    i++;
+                }
+
+                len = &value[s].data[i] - data;
+
+                if (len) {
+
+                    op->len = len;
+
+                    if (len <= sizeof(uintptr_t)) {
+                        op->op = NGX_HTTP_LOG_COPY_SHORT;
+                        op->data = 0;
+
+                        while (len--) {
+                            op->data <<= 8;
+                            op->data |= data[len];
+                        }
+
+                    } else {
+                        op->op = NGX_HTTP_LOG_COPY_LONG;
+
+                        if (!(p = ngx_palloc(cf->pool, len))) {
+                            return NGX_CONF_ERROR;
+                        }
+
+                        ngx_memcpy(p, data, len);
+                        op->data = (uintptr_t) p;
+                    }
+                }
+            }
+        }
+    }
+
+    if (invalid) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid parameter \"%s\"", data);
+        return NGX_CONF_ERROR;
+    }
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_log_handler.h
@@ -0,0 +1,65 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_LOG_HANDLER_H_INCLUDED_
+#define _NGX_HTTP_LOG_HANDLER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+typedef u_char *(*ngx_http_log_op_pt) (ngx_http_request_t *r, u_char *buf,
+                                       uintptr_t data);
+
+#define NGX_HTTP_LOG_COPY_SHORT  (ngx_http_log_op_pt) 0
+#define NGX_HTTP_LOG_COPY_LONG   (ngx_http_log_op_pt) -1
+
+#define NGX_HTTP_LOG_ARG         (u_int) -1
+
+
+typedef struct {
+    size_t               len;
+    ngx_http_log_op_pt   op;
+    uintptr_t            data;
+} ngx_http_log_op_t;
+
+
+typedef struct {
+    ngx_str_t            name;
+    ngx_array_t         *ops;        /* array of ngx_http_log_op_t */
+} ngx_http_log_fmt_t;
+
+
+typedef struct {
+    ngx_str_t            name;
+    size_t               len;
+    ngx_http_log_op_pt   op;
+} ngx_http_log_op_name_t;
+
+
+typedef struct {
+    ngx_array_t          formats;    /* array of ngx_http_log_fmt_t */
+} ngx_http_log_main_conf_t;
+
+
+typedef struct {
+    ngx_open_file_t     *file;
+    ngx_array_t         *ops;        /* array of ngx_http_log_op_t */
+} ngx_http_log_t;
+
+
+typedef struct {
+    ngx_array_t         *logs;       /* array of ngx_http_log_t */
+    ngx_uint_t           off;        /* unsigned  off:1 */
+} ngx_http_log_loc_conf_t;
+
+
+extern ngx_http_log_op_name_t ngx_http_log_fmt_ops[];
+
+
+#endif /* _NGX_HTTP_LOG_HANDLER_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_parse.c
@@ -0,0 +1,868 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+    u_char  ch, *p, *m;
+    enum {
+        sw_start = 0,
+        sw_method,
+        sw_space_after_method,
+        sw_spaces_before_uri,
+        sw_schema,
+        sw_schema_slash,
+        sw_schema_slash_slash,
+        sw_host,
+        sw_port,
+        sw_after_slash_in_uri,
+        sw_check_uri,
+        sw_uri,
+        sw_http_09,
+        sw_http_H,
+        sw_http_HT,
+        sw_http_HTT,
+        sw_http_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_almost_done,
+        sw_done
+    } state;
+
+    state = r->state;
+    p = b->pos;
+
+    while (p < b->last && state < sw_done) {
+        ch = *p++;
+
+        /* gcc 2.95.2 and msvc 6.0 compile this switch as an jump table */
+
+        switch (state) {
+
+        /* HTTP methods: GET, HEAD, POST */
+        case sw_start:
+            r->request_start = p - 1;
+
+            if (ch == CR || ch == LF) {
+                break;
+            }
+
+            if (ch < 'A' || ch > 'Z') {
+                return NGX_HTTP_PARSE_INVALID_METHOD;
+            }
+
+            state = sw_method;
+            break;
+
+        case sw_method:
+            if (ch == ' ') {
+                r->method_end = p - 1;
+                m = r->request_start;
+
+                if (r->method_end - m == 3) {
+
+                    if (m[0] == 'G' && m[1] == 'E' && m[2] == 'T') {
+                        r->method = NGX_HTTP_GET;
+                    }
+
+                } else if (r->method_end - m == 4) {
+
+                    if (m[0] == 'P' && m[1] == 'O'
+                        && m[2] == 'T' && m[3] == 'T')
+                    {
+                        r->method = NGX_HTTP_POST;
+
+                    } else if (m[0] == 'H' && m[1] == 'E'
+                               && m[2] == 'A' && m[3] == 'D')
+                    {
+                        r->method = NGX_HTTP_HEAD;
+                    }
+                }
+
+                state = sw_spaces_before_uri;
+                break;
+            }
+
+            if (ch < 'A' || ch > 'Z') {
+                return NGX_HTTP_PARSE_INVALID_METHOD;
+            }
+
+            break;
+
+        /* single space after method */
+        case sw_space_after_method:
+            switch (ch) {
+            case ' ':
+                state = sw_spaces_before_uri;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_METHOD;
+            }
+            break;
+
+        /* space* before URI */
+        case sw_spaces_before_uri:
+            switch (ch) {
+            case '/':
+                r->uri_start = p - 1;
+                state = sw_after_slash_in_uri;
+                break;
+            case ' ':
+                break;
+            default:
+                if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+                    r->schema_start = p - 1;
+                    state = sw_schema;
+                    break;
+                }
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema:
+            switch (ch) {
+            case ':':
+                r->schema_end = p - 1;
+                state = sw_schema_slash;
+                break;
+            default:
+                if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
+                    break;
+                }
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema_slash:
+            switch (ch) {
+            case '/':
+                state = sw_schema_slash_slash;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_schema_slash_slash:
+            switch (ch) {
+            case '/':
+                r->host_start = p - 1;
+                state = sw_host;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_host:
+            switch (ch) {
+            case ':':
+                r->host_end = p - 1;
+                state = sw_port;
+                break;
+            case '/':
+                r->host_end = p - 1;
+                r->uri_start = p - 1;
+                state = sw_after_slash_in_uri;
+                break;
+            default:
+                if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
+                    || (ch >= '0' && ch <= '9') || ch == '.' || ch == '-')
+                {
+                    break;
+                }
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_port:
+            switch (ch) {
+            case '/':
+                r->port_end = p - 1;
+                r->uri_start = p - 1;
+                state = sw_after_slash_in_uri;
+                break;
+            default:
+                if (ch < '0' && ch > '9') {
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
+                }
+                break;
+            }
+            break;
+
+        /* check "/.", "//", and "%" in URI */
+        case sw_after_slash_in_uri:
+            switch (ch) {
+            case CR:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_done;
+                break;
+            case ' ':
+                r->uri_end = p - 1;
+                state = sw_http_09;
+                break;
+            case '.':
+            case '%':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '/':
+                r->complex_uri = 1;
+                break;
+            case '?':
+                r->args_start = p;
+                state = sw_uri;
+                break;
+            default:
+                state = sw_check_uri;
+                break;
+            }
+            break;
+
+        /* check "/" and "%" in URI */
+        case sw_check_uri:
+            switch (ch) {
+            case CR:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_done;
+                break;
+            case ' ':
+                r->uri_end = p - 1;
+                state = sw_http_09;
+                break;
+            case '.':
+                r->uri_ext = p;
+                break;
+            case '/':
+                r->uri_ext = NULL;
+                state = sw_after_slash_in_uri;
+                break;
+            case '%':
+                r->complex_uri = 1;
+                state = sw_uri;
+                break;
+            case '?':
+                r->args_start = p;
+                state = sw_uri;
+                break;
+            }
+            break;
+
+        /* URI */
+        case sw_uri:
+            switch (ch) {
+            case CR:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->uri_end = p - 1;
+                r->http_minor = 9;
+                state = sw_done;
+                break;
+            case ' ':
+                r->uri_end = p - 1;
+                state = sw_http_09;
+                break;
+            }
+            break;
+
+        /* space+ after URI */
+        case sw_http_09:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                r->http_minor = 9;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->http_minor = 9;
+                state = sw_done;
+                break;
+            case 'H':
+                state = sw_http_H;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_H:
+            switch (ch) {
+            case 'T':
+                state = sw_http_HT;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_http_HTT;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_http_HTTP;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        case sw_http_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        /* first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_major = ch - '0';
+            state = sw_major_digit;
+            break;
+
+        /* major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_major = r->http_major * 10 + ch - '0';
+            break;
+
+        /* first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_minor = ch - '0';
+            state = sw_minor_digit;
+            break;
+
+        /* minor HTTP version or end of request line */
+        case sw_minor_digit:
+            if (ch == CR) {
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                state = sw_done;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+
+            r->http_minor = r->http_minor * 10 + ch - '0';
+            break;
+
+        /* end of request line */
+        case sw_almost_done:
+            r->request_end = p - 2;
+            switch (ch) {
+            case LF:
+                state = sw_done;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_REQUEST;
+            }
+            break;
+
+        /* suppress warning */
+        case sw_done:
+            break;
+        }
+    }
+
+    b->pos = p;
+
+    if (state == sw_done) {
+        if (r->request_end == NULL) {
+            r->request_end = p - 1;
+        }
+
+        r->http_version = r->http_major * 1000 + r->http_minor;
+        r->state = sw_start;
+
+        if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
+            return NGX_HTTP_PARSE_INVALID_09_METHOD;
+        }
+
+        return NGX_OK;
+
+    } else {
+        r->state = state;
+        return NGX_AGAIN;
+    }
+}
+
+
+ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b)
+{
+    u_char  c, ch, *p;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_almost_done,
+        sw_header_almost_done,
+        sw_ignore_line,
+        sw_done,
+        sw_header_done
+    } state;
+
+    state = r->state;
+    p = b->pos;
+
+    while (p < b->last && state < sw_done) {
+        ch = *p++;
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+            switch (ch) {
+            case CR:
+                r->header_end = p - 1;
+                state = sw_header_almost_done;
+                break;
+            case LF:
+                r->header_end = p - 1;
+                state = sw_header_done;
+                break;
+            default:
+                state = sw_name;
+                r->header_name_start = p - 1;
+
+                c = (u_char) (ch | 0x20);
+                if (c >= 'a' && c <= 'z') {
+                    break;
+                }
+
+                if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
+                    break;
+                }
+
+                if (ch >= '0' && ch <= '9') {
+                    break;
+                }
+
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            if (ch == ':') {
+                r->header_name_end = p - 1;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
+                break;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            /* IIS can send duplicate "HTTP/1.1 ..." lines */
+            if (ch == '/'
+                && r->proxy
+                && p - r->header_start == 5
+                && ngx_strncmp(r->header_start, "HTTP", 4) == 0)
+            {
+                state = sw_ignore_line;
+                break;
+            }
+
+            return NGX_HTTP_PARSE_INVALID_HEADER;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                r->header_start = r->header_end = p - 1;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->header_start = r->header_end = p - 1;
+                state = sw_done;
+                break;
+            default:
+                r->header_start = p - 1;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                r->header_end = p - 1;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                r->header_end = p - 1;
+                state = sw_almost_done;
+                break;
+            case LF:
+                r->header_end = p - 1;
+                state = sw_done;
+                break;
+            }
+            break;
+
+        /* space* before end of header line */
+        case sw_space_after_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                state = sw_done;
+                break;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* ignore header line */
+        case sw_ignore_line:
+            switch (ch) {
+            case LF:
+                state = sw_start;
+                break;
+            default:
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                state = sw_done;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+            break;
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                state = sw_header_done;
+                break;
+            default:
+                return NGX_HTTP_PARSE_INVALID_HEADER;
+            }
+            break;
+
+        /* suppress warning */
+        case sw_done:
+        case sw_header_done:
+            break;
+        }
+    }
+
+    b->pos = p;
+
+    if (state == sw_done) {
+        r->state = sw_start;
+        return NGX_OK;
+
+    } else if (state == sw_header_done) {
+        r->state = sw_start;
+        return NGX_HTTP_PARSE_HEADER_DONE;
+
+    } else {
+        r->state = state;
+        return NGX_AGAIN;
+    }
+}
+
+
+ngx_int_t ngx_http_parse_complex_uri(ngx_http_request_t *r)
+{
+    u_char  c, ch, decoded, *p, *u;
+    enum {
+        sw_usual = 0,
+        sw_slash,
+        sw_dot,
+        sw_dot_dot,
+#if (WIN32)
+        sw_dot_dot_dot,
+#endif
+        sw_quoted,
+        sw_quoted_second
+    } state, quoted_state;
+
+    decoded = '\0';
+    quoted_state = sw_usual;
+
+    state = sw_usual;
+    p = r->uri_start;
+    u = r->uri.data;
+    r->uri_ext = NULL;
+
+    ch = *p++;
+
+    while (p < r->uri_start + r->uri.len + 1) {
+
+        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "s:%d in:'%x:%c', out:'%c'", state, ch, ch, *u);
+
+        switch (state) {
+        case sw_usual:
+            switch(ch) {
+            case '/':
+                r->uri_ext = NULL;
+                state = sw_slash;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            case '.':
+                r->uri_ext = u + 1;
+            default:
+                *u++ = ch;
+                break;
+            }
+            ch = *p++;
+            break;
+
+        case sw_slash:
+            switch(ch) {
+            case '/':
+                break;
+            case '.':
+                state = sw_dot;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+            ch = *p++;
+            break;
+
+        case sw_dot:
+            switch(ch) {
+            case '/':
+                state = sw_slash;
+                u--;
+                break;
+            case '.':
+                state = sw_dot_dot;
+                *u++ = ch;
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+            ch = *p++;
+            break;
+
+        case sw_dot_dot:
+            switch(ch) {
+            case '/':
+                state = sw_slash;
+                u -= 4;
+                if (u < r->uri.data) {
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
+                }
+                while (*(u - 1) != '/') {
+                    u--;
+                }
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+#if (WIN32)
+            case '.':
+                state = sw_dot_dot_dot;
+                *u++ = ch;
+                break;
+#endif
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+            ch = *p++;
+            break;
+
+#if (WIN32)
+        case sw_dot_dot_dot:
+            switch(ch) {
+            case '/':
+                state = sw_slash;
+                u -= 5;
+                if (u < r->uri.data) {
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
+                }
+                while (*u != '/') {
+                    u--;
+                }
+                if (u < r->uri.data) {
+                    return NGX_HTTP_PARSE_INVALID_REQUEST;
+                }
+                while (*(u - 1) != '/') {
+                    u--;
+                }
+                break;
+            case '%':
+                quoted_state = state;
+                state = sw_quoted;
+                break;
+            default:
+                state = sw_usual;
+                *u++ = ch;
+                break;
+            }
+            ch = *p++;
+            break;
+#endif
+
+        case sw_quoted:
+            if (ch >= '0' && ch <= '9') {
+                decoded = (u_char) (ch - '0');
+                state = sw_quoted_second;
+                ch = *p++;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                decoded = (u_char) (c - 'a' + 10);
+                state = sw_quoted_second;
+                ch = *p++;
+                break;
+            }
+
+            return NGX_HTTP_PARSE_INVALID_REQUEST;
+
+        case sw_quoted_second:
+            if (ch >= '0' && ch <= '9') {
+                ch = (u_char) ((decoded << 4) + ch - '0');
+                if (ch == '%') {
+                    state = sw_usual;
+                    *u++ = ch;
+                    ch = *p++;
+                    break;
+                }
+                state = quoted_state;
+                break;
+            }
+
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'f') {
+                ch = (u_char) ((decoded << 4) + c - 'a' + 10);
+                if (ch == '%') {
+                    state = sw_usual;
+                    *u++ = ch;
+                    ch = *p++;
+                    break;
+                }
+                state = quoted_state;
+                break;
+            }
+
+            return NGX_HTTP_PARSE_INVALID_REQUEST;
+        }
+    }
+
+    r->uri.len = u - r->uri.data;
+    r->uri.data[r->uri.len] = '\0';
+
+    if (r->uri_ext) {
+        r->exten.len = u - r->uri_ext;
+
+        if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        ngx_cpystrn(r->exten.data, r->uri_ext, r->exten.len + 1);
+    }
+
+    r->uri_ext = NULL;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_parse_time.c
@@ -0,0 +1,287 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_types.h>
+
+
+static int mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+time_t ngx_http_parse_time(u_char *value, size_t len)
+{
+    u_char  *p, *end;
+    int      day, month, year, hour, min, sec;
+    enum {
+        no = 0,
+        rfc822,   /* Tue 10 Nov 2002 23:50:13    */
+        rfc850,   /* Tuesday, 10-Dec-02 23:50:13 */
+        isoc      /* Tue Dec 10 23:50:13 2002    */
+    } fmt;
+
+    fmt = 0;
+    end = value + len;
+
+#if (NGX_SUPPRESS_WARN)
+    day = 32;
+    year = 2038;
+#endif
+
+    for (p = value; p < end; p++) {
+        if (*p == ',') {
+            break;
+        }
+
+        if (*p == ' ') {
+            fmt = isoc;
+            break;
+        }
+    }
+
+    for (p++; p < end; p++)
+        if (*p != ' ') {
+            break;
+        }
+
+    if (end - p < 18) {
+        return NGX_ERROR;
+        }
+
+    if (fmt != isoc) {
+        if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+            return NGX_ERROR;
+        }
+
+        day = (*p - '0') * 10 + *(p + 1) - '0';
+        p += 2;
+
+        if (*p == ' ') {
+            if (end - p < 18) {
+                return NGX_ERROR;
+            }
+            fmt = rfc822;
+
+        } else if (*p == '-') {
+            fmt = rfc850;
+
+        } else {
+            return NGX_ERROR;
+        }
+
+        p++;
+    }
+
+    switch (*p) {
+
+    case 'J':
+        month = *(p + 1) == 'a' ? 0 : *(p + 2) == 'n' ? 5 : 6;
+        break;
+
+    case 'F':
+        month = 1;
+        break;
+
+    case 'M':
+        month = *(p + 2) == 'r' ? 2 : 4;
+        break;
+
+    case 'A':
+        month = *(p + 1) == 'p' ? 3 : 7;
+        break;
+
+    case 'S':
+        month = 8;
+        break;
+
+    case 'O':
+        month = 9;
+        break;
+
+    case 'N':
+        month = 10;
+        break;
+
+    case 'D':
+        month = 11;
+        break;
+
+    default:
+        return NGX_ERROR;
+    }
+
+    p += 3;
+
+    if ((fmt == rfc822 && *p != ' ') || (fmt == rfc850 && *p != '-')) {
+        return NGX_ERROR;
+    }
+
+    p++;
+
+    if (fmt == rfc822) {
+        if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9'
+            || *(p + 2) < '0' || *(p + 2) > '9'
+            || *(p + 3) < '0' || *(p + 3) > '9')
+        {
+            return NGX_ERROR;
+        }
+
+        year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100
+               + (*(p + 2) - '0') * 10 + *(p + 3) - '0';
+        p += 4;
+
+    } else if (fmt == rfc850) {
+        if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+            return NGX_ERROR;
+        }
+
+        year = (*p - '0') * 10 + *(p + 1) - '0';
+        year += (year < 70) ? 2000 : 1900;
+        p += 2;
+    }
+
+    if (fmt == isoc) {
+        if (*p == ' ') {
+            p++;
+        }
+
+        if (*p < '0' || *p > '9') {
+            return NGX_ERROR;
+        }
+
+        day = *p++ - '0';
+
+        if (*p != ' ') {
+            if (*p < '0' || *p > '9') {
+                return NGX_ERROR;
+            }
+
+            day = day * 10 + *p++ - '0';
+        }
+
+        if (end - p < 14) {
+            return NGX_ERROR;
+        }
+    }
+
+    if (*p++ != ' ') {
+        return NGX_ERROR;
+    }
+
+    if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+        return NGX_ERROR;
+    }
+
+    hour = (*p - '0') * 10 + *(p + 1) - '0';
+    p += 2;
+
+    if (*p++ != ':') {
+        return NGX_ERROR;
+    }
+
+    if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+        return NGX_ERROR;
+    }
+
+    min = (*p - '0') * 10 + *(p + 1) - '0';
+    p += 2;
+
+    if (*p++ != ':') {
+        return NGX_ERROR;
+    }
+
+    if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') {
+        return NGX_ERROR;
+    }
+
+    sec = (*p - '0') * 10 + *(p + 1) - '0';
+
+    if (fmt == isoc) {
+        p += 2;
+
+        if (*p++ != ' ') {
+            return NGX_ERROR;
+        }
+
+        if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9'
+            || *(p + 2) < '0' || *(p + 2) > '9'
+            || *(p + 3) < '0' || *(p + 3) > '9')
+        {
+            return NGX_ERROR;
+        }
+
+        year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100
+               + (*(p + 2) - '0') * 10 + *(p + 3) - '0';
+    }
+
+#if 0
+    printf("%d.%d.%d %d:%d:%d\n", day, month + 1, year, hour, min, sec);
+#endif
+
+    if (hour > 23 || min > 59 || sec > 59) {
+         return NGX_ERROR;
+    }
+
+    if (day == 29 && month == 1) {
+        if ((year & 3) || ((year % 100 == 0) && (year % 400) != 0)) {
+            return NGX_ERROR;
+        }
+
+    } else if (day > mday[month]) {
+        return NGX_ERROR;
+    }
+
+    if (sizeof(time_t) <= 4 && year >= 2038) {
+        return NGX_ERROR;
+    }
+
+    /*
+     * shift new year to March 1 and start months from 1 (not 0),
+     * it's needed for Gauss's formula
+     */
+
+    if (--month <= 0) {
+       month += 12;
+       year -= 1;
+    }
+
+           /* Gauss's formula for Grigorian days from 1 March 1 BC */
+
+    return (365 * year + year / 4 - year / 100 + year / 400
+            + 367 * month / 12 - 31
+            + day
+
+           /*
+            * 719527 days were between March 1, 1 BC and March 1, 1970,
+            * 31 and 28 days in January and February 1970
+            */
+
+            - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec;
+}
+
+#if 0
+char zero[] = "Sun, 01 Jan 1970 08:49:30";
+char one[]  = "Sunday, 11-Dec-02 08:49:30";
+char two[]  = "Sun Mar 1 08:49:37 2000";
+char thr[]  = "Sun Dec 11 08:49:37 2002";
+
+main()
+{
+    int rc;
+
+    rc = ngx_http_parse_time(zero, sizeof(zero) - 1);
+    printf("rc: %d\n", rc);
+
+    rc = ngx_http_parse_time(one, sizeof(one) - 1);
+    printf("rc: %d\n", rc);
+
+    rc = ngx_http_parse_time(two, sizeof(two) - 1);
+    printf("rc: %d\n", rc);
+
+    rc = ngx_http_parse_time(thr, sizeof(thr) - 1);
+    printf("rc: %d\n", rc);
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_request.c
@@ -0,0 +1,2149 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_init_request(ngx_event_t *ev);
+#if (NGX_HTTP_SSL)
+static void ngx_http_ssl_handshake(ngx_event_t *rev);
+#endif
+static void ngx_http_process_request_line(ngx_event_t *rev);
+static void ngx_http_process_request_headers(ngx_event_t *rev);
+static ssize_t ngx_http_read_request_header(ngx_http_request_t *r);
+static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+                                                    ngx_uint_t request_line);
+static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r);
+
+static void ngx_http_set_write_handler(ngx_http_request_t *r);
+
+static void ngx_http_block_read(ngx_event_t *ev);
+static void ngx_http_read_discarded_body_event(ngx_event_t *rev);
+static ngx_int_t ngx_http_read_discarded_body(ngx_http_request_t *r);
+
+static void ngx_http_set_keepalive(ngx_http_request_t *r);
+static void ngx_http_keepalive_handler(ngx_event_t *ev);
+static void ngx_http_set_lingering_close(ngx_http_request_t *r);
+static void ngx_http_lingering_close_handler(ngx_event_t *ev);
+
+static void ngx_http_client_error(ngx_http_request_t *r,
+                                  int client_error, int error);
+static size_t ngx_http_log_error(void *data, char *buf, size_t len);
+
+
+/* NGX_HTTP_PARSE_... errors */
+
+static char *client_header_errors[] = {
+    "client %s sent invalid method",
+    "client %s sent invalid request",
+    "client %s sent too long URI",
+    "client %s sent invalid method in HTTP/0.9 request",
+
+    "client %s sent invalid header, URL: %s",
+    "client %s sent too long header line, URL: %s",
+    "client %s sent HTTP/1.1 request without \"Host\" header, URL: %s",
+    "client %s sent invalid \"Content-Length\" header, URL: %s",
+    "client %s sent POST method without \"Content-Length\" header, URL: %s",
+    "client %s sent plain HTTP request to HTTPS port, URL: %s",
+    "client %s sent invalid \"Host\" header \"%s\", URL: %s"
+};
+
+
+ngx_http_header_t  ngx_http_headers_in[] = {
+    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host) },
+    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection) },
+    { ngx_string("If-Modified-Since"),
+                         offsetof(ngx_http_headers_in_t, if_modified_since) },
+    { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent) },
+    { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer) },
+    { ngx_string("Content-Length"),
+                            offsetof(ngx_http_headers_in_t, content_length) },
+
+    { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range) },
+#if 0
+    { ngx_string("If-Range"), offsetof(ngx_http_headers_in_t, if_range) },
+#endif
+
+#if (NGX_HTTP_GZIP)
+    { ngx_string("Accept-Encoding"),
+                           offsetof(ngx_http_headers_in_t, accept_encoding) },
+    { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via) },
+#endif
+
+    { ngx_string("Authorization"),
+                             offsetof(ngx_http_headers_in_t, authorization) },
+
+    { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive) },
+
+#if (NGX_HTTP_PROXY)
+    { ngx_string("X-Forwarded-For"),
+                           offsetof(ngx_http_headers_in_t, x_forwarded_for) },
+#endif
+    
+    { ngx_null_string, 0 }
+};
+
+
+#if 0
+static void ngx_http_dummy(ngx_event_t *wev)
+{
+    return;
+}
+#endif
+
+
+void ngx_http_init_connection(ngx_connection_t *c)
+{
+    ngx_event_t         *rev;
+    ngx_http_log_ctx_t  *ctx;
+
+    if (!(ctx = ngx_pcalloc(c->pool, sizeof(ngx_http_log_ctx_t)))) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ctx->connection = c->number;
+    ctx->client = c->addr_text.data;
+    ctx->action = "reading client request line";
+    c->log->data = ctx;
+    c->log->handler = ngx_http_log_error;
+    c->log_error = NGX_ERROR_INFO;
+
+    rev = c->read;
+    rev->event_handler = ngx_http_init_request;
+
+    /* STUB: epoll edge */ c->write->event_handler = ngx_http_empty_handler;
+
+    if (rev->ready) {
+        /* the deferred accept(), rtsig, aio, iocp */
+
+        if (ngx_accept_mutex) {
+            if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            ngx_post_event(rev); 
+
+            ngx_mutex_unlock(ngx_posted_events_mutex);
+            return;
+        }
+
+#if (NGX_STAT_STUB)
+        (*ngx_stat_reading)++;
+#endif
+
+        ngx_http_init_request(rev);
+        return;
+    }
+
+    ngx_add_timer(rev, c->listening->post_accept_timeout);
+
+    if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+#if 0
+    /* TODO: learn SO_SNDBUF (to use in zerocopy) via kqueue's EV_CLEAR event */
+
+    c->write->ready = 0;
+    c->write->event_handler = ngx_http_dummy;
+
+    if (ngx_handle_write_event(c->write, 0) == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+#endif
+
+#if (NGX_STAT_STUB)
+    (*ngx_stat_reading)++;
+#endif
+
+}
+
+
+static void ngx_http_init_request(ngx_event_t *rev)
+{
+    ngx_uint_t                 i;
+    socklen_t                  len;
+    struct sockaddr_in         addr_in;
+    ngx_connection_t          *c;
+    ngx_http_request_t        *r;
+    ngx_http_in_port_t        *in_port;
+    ngx_http_in_addr_t        *in_addr;
+    ngx_http_connection_t     *hc;
+    ngx_http_server_name_t    *server_name;
+    ngx_http_core_srv_conf_t  *cscf;
+    ngx_http_core_loc_conf_t  *clcf;
+#if (NGX_HTTP_SSL)
+    ngx_http_ssl_srv_conf_t   *sscf;
+#endif
+
+    c = rev->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+
+#if (NGX_STAT_STUB)
+        (*ngx_stat_reading)--;
+#endif
+
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    hc = c->data;
+
+    if (hc) {
+
+#if (NGX_STAT_STUB)
+        (*ngx_stat_reading)++;
+#endif
+
+    } else {
+        if (!(hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)))) {
+
+#if (NGX_STAT_STUB)
+            (*ngx_stat_reading)--;
+#endif
+
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+    r = hc->request;
+
+    if (r) {
+        ngx_memzero(r, sizeof(ngx_http_request_t));
+
+        r->pipeline = hc->pipeline;
+
+        if (hc->nbusy) {
+            r->header_in = hc->busy[0];
+        }
+
+    } else {
+        if (!(r = ngx_pcalloc(c->pool, sizeof(ngx_http_request_t)))) {
+
+#if (NGX_STAT_STUB)
+            (*ngx_stat_reading)--;
+#endif
+
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        hc->request = r;
+    }
+
+#if (NGX_STAT_STUB)
+    r->stat_reading = 1;
+#endif
+
+    c->data = r;
+    r->http_connection = hc;
+
+    c->sent = 0;
+    r->signature = NGX_HTTP_MODULE;
+
+    /* find the server configuration for the address:port */
+
+    /* AF_INET only */
+
+    in_port = c->servers;
+    in_addr = in_port->addrs.elts;
+
+    r->port = in_port->port;
+    r->port_text = &in_port->port_text;
+
+    i = 0;
+
+    if (in_port->addrs.nelts > 1) {
+
+        /*
+         * There are several addresses on this port and one of them
+         * is the "*:port" wildcard so getsockname() is needed to determine
+         * the server address.
+         *
+         * AcceptEx() already gave this address.
+         */
+
+#if (WIN32)
+        if (c->local_sockaddr) {
+            r->in_addr =
+                   ((struct sockaddr_in *) c->local_sockaddr)->sin_addr.s_addr;
+
+        } else {
+#endif
+            len = sizeof(struct sockaddr_in);
+            if (getsockname(c->fd, (struct sockaddr *) &addr_in, &len) == -1) {
+                ngx_connection_error(c, ngx_socket_errno,
+                                     "getsockname() failed");
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            r->in_addr = addr_in.sin_addr.s_addr;
+
+#if (WIN32)
+        }
+#endif
+
+        /* the last in_port->addrs address is "*" */
+
+        for ( /* void */ ; i < in_port->addrs.nelts - 1; i++) {
+            if (in_addr[i].addr == r->in_addr) {
+                break;
+            }
+        }
+
+    } else {
+        r->in_addr = in_addr[0].addr;
+    }
+
+    r->virtual_names = &in_addr[i].names;
+
+    /* the default server configuration for the address:port */
+    cscf = in_addr[i].core_srv_conf;
+
+    r->main_conf = cscf->ctx->main_conf;
+    r->srv_conf = cscf->ctx->srv_conf;
+    r->loc_conf = cscf->ctx->loc_conf;
+
+    rev->event_handler = ngx_http_process_request_line;
+
+#if (NGX_HTTP_SSL)
+
+    sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
+    if (sscf->enable) {
+
+        if (c->ssl == NULL) {
+            if (ngx_ssl_create_session(sscf->ssl_ctx, c, NGX_SSL_BUFFER)
+                                                                  == NGX_ERROR)
+            {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            /*
+             * The majority of browsers do not send the "close notify" alert.
+             * Among them are MSIE, Mozilla, Netscape 4, Konqueror, and Links.
+             * And what is more MSIE ignores the server's alert.
+             *
+             * Opera always sends the alert.
+             */
+
+            c->ssl->no_rcv_shut = 1;
+            rev->event_handler = ngx_http_ssl_handshake;
+        }
+
+        r->filter_need_in_memory = 1;
+    }
+
+#endif
+
+    server_name = cscf->server_names.elts;
+    r->server_name = &server_name->name;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    c->log->file = clcf->err_log->file;
+    if (!(c->log->log_level & NGX_LOG_DEBUG_CONNECTION)) {
+        c->log->log_level = clcf->err_log->log_level;
+    }
+
+    if (c->buffer == NULL) {
+        c->buffer = ngx_create_temp_buf(c->pool,
+                                        cscf->client_header_buffer_size);
+        if (c->buffer == NULL) {
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+    if (r->header_in == NULL) {
+        r->header_in = c->buffer;
+    }
+
+    if (!(r->pool = ngx_create_pool(cscf->request_pool_size, c->log))) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (ngx_array_init(&r->cleanup, r->pool, 5, sizeof(ngx_http_cleanup_t))
+                                                                  == NGX_ERROR)
+    { 
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+
+    if (ngx_list_init(&r->headers_out.headers, r->pool, 20,
+                                         sizeof(ngx_table_elt_t)) == NGX_ERROR)
+    {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+
+    r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
+    if (r->ctx == NULL) {
+        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    c->single_connection = 1;
+    r->connection = c;
+
+    r->file.fd = NGX_INVALID_FILE;
+
+    r->headers_in.content_length_n = -1;
+    r->headers_in.keep_alive_n = -1;
+    r->headers_out.content_length_n = -1;
+    r->headers_out.last_modified_time = -1;
+
+    r->http_state = NGX_HTTP_READING_REQUEST_STATE;
+
+#if (NGX_STAT_STUB)
+    (*ngx_stat_requests)++;
+#endif
+
+    rev->event_handler(rev);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void ngx_http_ssl_handshake(ngx_event_t *rev)
+{
+    int                  n;
+    ngx_int_t            rc;
+    u_char               buf[1];
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http check ssl handshake");
+
+    if (rev->timedout) {
+        ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    n = recv(c->fd, buf, 1, MSG_PEEK); 
+
+    if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+        return;
+    }
+
+    if (n == 1) {
+        if (buf[0] == 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                           "https ssl handshake: 0x%X", buf[0]);
+
+            c->recv = ngx_ssl_recv;
+            c->send_chain = ngx_ssl_send_chain;
+
+            rc = ngx_ssl_handshake(c);
+
+            if (rc == NGX_ERROR) {
+                ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
+                ngx_http_close_connection(r->connection);
+                return;
+            }
+
+            if (rc != NGX_OK) {
+                return;
+            }
+
+        } else {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                           "plain http");
+
+            r->plain_http = 1;
+        }
+    }
+
+    rev->event_handler = ngx_http_process_request_line;
+    ngx_http_process_request_line(rev);
+}
+
+#endif
+
+
+static void ngx_http_process_request_line(ngx_event_t *rev)
+{
+    u_char              *p;
+    ssize_t              n;
+    ngx_int_t            rc, rv;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+    ngx_http_log_ctx_t  *ctx;
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http process request line");
+
+    if (rev->timedout) {
+        ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    rc = NGX_AGAIN;
+
+    for ( ;; ) {
+
+        if (rc == NGX_AGAIN) {
+            n = ngx_http_read_request_header(r);
+
+            if (n == NGX_AGAIN || n == NGX_ERROR) {
+                return;
+            }
+        }
+
+        rc = ngx_http_parse_request_line(r, r->header_in);
+
+        if (rc == NGX_OK) {
+
+            /* the request line has been parsed successfully */
+
+            /* copy unparsed URI */
+
+            r->unparsed_uri.len = r->uri_end - r->uri_start;
+            r->unparsed_uri.data = ngx_palloc(r->pool, r->unparsed_uri.len + 1);
+            if (r->unparsed_uri.data == NULL) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            ngx_cpystrn(r->unparsed_uri.data, r->uri_start,
+                        r->unparsed_uri.len + 1);
+
+
+            /* copy URI */
+
+            if (r->args_start) {
+                r->uri.len = r->args_start - 1 - r->uri_start;
+            } else {
+                r->uri.len = r->uri_end - r->uri_start;
+            }
+
+            if (!(r->uri.data = ngx_palloc(r->pool, r->uri.len + 1))) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            if (r->complex_uri) {
+                rc = ngx_http_parse_complex_uri(r);
+
+                if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+                    ngx_http_close_request(r, rc);
+                    ngx_http_close_connection(c);
+                    return;
+                }
+
+                if (rc != NGX_OK) {
+                    r->request_line.len = r->request_end - r->request_start;
+                    r->request_line.data = r->request_start;
+
+                    ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+                    return;
+                }
+
+            } else {
+                ngx_cpystrn(r->uri.data, r->uri_start, r->uri.len + 1);
+            }
+
+
+            r->request_line.len = r->request_end - r->request_start;
+            r->request_line.data = r->request_start;
+            r->request_line.data[r->request_line.len] = '\0';
+
+            if (r->method == 0) {
+                r->method_name.len = r->method_end - r->request_start + 1;
+                r->method_name.data = r->request_line.data;
+            }
+
+            if (r->uri_ext) {
+
+                /* copy URI extention */
+
+                if (r->args_start) {
+                    r->exten.len = r->args_start - 1 - r->uri_ext;
+                } else {
+                    r->exten.len = r->uri_end - r->uri_ext;
+                }
+
+                if (!(r->exten.data = ngx_palloc(r->pool, r->exten.len + 1))) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    ngx_http_close_connection(c);
+                    return;
+                }
+
+                ngx_cpystrn(r->exten.data, r->uri_ext, r->exten.len + 1);
+            }
+
+            if (r->args_start && r->uri_end > r->args_start) {
+
+                /* copy URI arguments */
+
+                r->args.len = r->uri_end - r->args_start;
+
+                if (!(r->args.data = ngx_palloc(r->pool, r->args.len + 1))) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    ngx_http_close_connection(c);
+                    return;
+                }
+
+                ngx_cpystrn(r->args.data, r->args_start, r->args.len + 1);
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http request line: \"%s\"", r->request_line.data);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http uri: \"%s\"", r->uri.data);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http args: \"%s\"",
+                           r->args.data ? r->args.data : (u_char *) "");
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http exten: \"%s\"",
+                           r->exten.data ? r->exten.data : (u_char *) "");
+
+            if (r->http_version < NGX_HTTP_VERSION_10) {
+                rev->event_handler = ngx_http_block_read;
+                ngx_http_handler(r);
+                return;
+            }
+
+
+            if (ngx_list_init(&r->headers_in.headers, r->pool, 20,
+                                         sizeof(ngx_table_elt_t)) == NGX_ERROR)
+            {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+
+            if (ngx_array_init(&r->headers_in.cookies, r->pool, 5,
+                                       sizeof(ngx_table_elt_t *)) == NGX_ERROR)
+            {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+
+            ctx = c->log->data;
+            ctx->action = "reading client request headers";
+            ctx->url = r->unparsed_uri.data;
+
+            rev->event_handler = ngx_http_process_request_headers;
+            ngx_http_process_request_headers(rev);
+
+            return;
+
+        } else if (rc != NGX_AGAIN) {
+
+            /* there was error while a request line parsing */
+
+            for (p = r->request_start; p < r->header_in->last; p++) {
+                if (*p == CR || *p == LF) {
+                    break;
+                }
+            }
+
+            r->request_line.len = p - r->request_start;
+            r->request_line.data = r->request_start;
+
+            if (rc == NGX_HTTP_PARSE_INVALID_METHOD) {
+                r->http_version = NGX_HTTP_VERSION_10;
+            }
+
+            ngx_http_client_error(r, rc,
+                                  (rc == NGX_HTTP_PARSE_INVALID_METHOD) ?
+                                         NGX_HTTP_NOT_IMPLEMENTED:
+                                         NGX_HTTP_BAD_REQUEST);
+            return;
+        }
+
+        /* NGX_AGAIN: a request line parsing is still incomplete */
+
+        if (r->header_in->pos == r->header_in->end) {
+
+            rv = ngx_http_alloc_large_header_buffer(r, 1);
+
+            if (rv == NGX_ERROR) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            if (rv == NGX_DECLINED) {
+                ngx_http_client_error(r, NGX_HTTP_PARSE_TOO_LONG_URI,
+                                      NGX_HTTP_REQUEST_URI_TOO_LARGE);
+                return;
+            }
+        }
+    }
+}
+
+
+static void ngx_http_process_request_headers(ngx_event_t *rev)
+{
+    ssize_t              n;
+    ngx_int_t            rc, rv, i;
+    ngx_table_elt_t     *h, **cookie;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "http process request header line");
+
+    if (rev->timedout) {
+        ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    rc = NGX_AGAIN;
+
+    for ( ;; ) {
+
+        if (rc == NGX_AGAIN) {
+
+            if (r->header_in->pos == r->header_in->end) {
+
+                rv = ngx_http_alloc_large_header_buffer(r, 0);
+
+                if (rv == NGX_ERROR) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    ngx_http_close_connection(c);
+                    return;
+                }
+
+                if (rv == NGX_DECLINED) {
+                    ngx_http_client_error(r, NGX_HTTP_PARSE_TOO_LONG_HEADER,
+                                          NGX_HTTP_BAD_REQUEST);
+                    return;
+                }
+            }
+
+            n = ngx_http_read_request_header(r);
+
+            if (n == NGX_AGAIN || n == NGX_ERROR) {
+                return;
+            }
+        }
+
+        rc = ngx_http_parse_header_line(r, r->header_in);
+
+        if (rc == NGX_OK) {
+
+            /* a header line has been parsed successfully */
+
+            r->headers_n++;
+
+            if (!(h = ngx_list_push(&r->headers_in.headers))) {
+                ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                ngx_http_close_connection(c);
+                return;
+            }
+
+            h->key.len = r->header_name_end - r->header_name_start;
+            h->key.data = r->header_name_start;
+            h->key.data[h->key.len] = '\0';
+
+            h->value.len = r->header_end - r->header_start;
+            h->value.data = r->header_start;
+            h->value.data[h->value.len] = '\0';
+
+            if (h->key.len == sizeof("Cookie") - 1
+                && ngx_strcasecmp(h->key.data, "Cookie") == 0)
+            {
+                if (!(cookie = ngx_array_push(&r->headers_in.cookies))) {
+                    ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    ngx_http_close_connection(c);
+                    return;
+                }
+
+                *cookie = h;
+
+            } else {
+
+                for (i = 0; ngx_http_headers_in[i].name.len != 0; i++) {
+                    if (ngx_http_headers_in[i].name.len != h->key.len) {
+                        continue;
+                    }
+
+                    if (ngx_strcasecmp(ngx_http_headers_in[i].name.data,
+                                       h->key.data) == 0)
+                    {
+                        *((ngx_table_elt_t **) ((char *) &r->headers_in
+                                         + ngx_http_headers_in[i].offset)) = h;
+                        break;
+                    }
+                }
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http header: \"%s: %s\"",
+                           h->key.data, h->value.data);
+
+            continue;
+
+        } else if (rc == NGX_HTTP_PARSE_HEADER_DONE) {
+
+            /* a whole header has been parsed successfully */
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http header done");
+
+            r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE;
+
+            rc = ngx_http_process_request_header(r);
+
+            if (rc != NGX_OK) {
+                ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+                return;
+            }
+
+            if (rev->timer_set) {
+                ngx_del_timer(rev);
+            }
+
+#if (NGX_STAT_STUB)
+            (*ngx_stat_reading)--;
+            r->stat_reading = 0;
+            (*ngx_stat_writing)++;
+            r->stat_writing = 1;
+#endif
+
+            rev->event_handler = ngx_http_block_read;
+            ngx_http_handler(r);
+            return;
+
+        } else if (rc != NGX_AGAIN) {
+
+            /* there was error while a header line parsing */
+
+#if (NGX_DEBUG)
+            if (rc == NGX_HTTP_PARSE_INVALID_HEADER
+                && (rev->log->log_level & NGX_LOG_DEBUG_HTTP))
+            {
+                u_char *p;
+                for (p = r->header_name_start;
+                     p < r->header_in->last - 1;
+                     p++)
+                {
+                    if (*p == LF) {
+                        break;
+                    }
+                }
+                *p = '\0';
+                ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                               "http invalid header: \"%s\"",
+                               r->header_name_start);
+            }
+#endif
+
+            ngx_http_client_error(r, rc, NGX_HTTP_BAD_REQUEST);
+            return;
+        }
+
+        /* NGX_AGAIN: a header line parsing is still not complete */
+
+    }
+}
+
+
+static ssize_t ngx_http_read_request_header(ngx_http_request_t *r)
+{
+    ssize_t                    n;
+    ngx_event_t               *rev;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    rev = r->connection->read;
+
+    n = r->header_in->last - r->header_in->pos;
+
+    if (n > 0) {
+        return n;
+    }
+
+    if (!rev->ready) {
+        return NGX_AGAIN;
+    }
+
+    n = r->connection->recv(r->connection, r->header_in->last,
+                            r->header_in->end - r->header_in->last);
+
+    if (n == NGX_AGAIN) {
+        if (!r->header_timeout_set) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+            ngx_add_timer(rev, cscf->client_header_timeout);
+            r->header_timeout_set = 1;
+        }
+
+        if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+            ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+            ngx_http_close_connection(r->connection);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
+                      "client closed prematurely connection");
+    }
+
+    if (n == 0 || n == NGX_ERROR) {
+        ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST);
+        ngx_http_close_connection(r->connection);
+        return NGX_ERROR;
+    }
+
+    r->header_in->last += n;
+
+    return n;
+}
+
+
+static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r,
+                                                    ngx_uint_t request_line)
+{
+    u_char                    *old, *new;
+    ngx_buf_t                 *b;
+    ngx_http_connection_t     *hc;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http alloc large header buffer");
+
+    if (request_line && r->state == 0) {
+
+        /* the client fills up the buffer with "\r\n" */
+
+        r->header_in->pos = r->header_in->start;
+        r->header_in->last = r->header_in->start;
+
+        return NGX_OK;
+    }
+
+    old = request_line ? r->request_start : r->header_name_start;
+
+    cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+    if (r->state != 0
+        && (size_t) (r->header_in->pos - old)
+                                     >= cscf->large_client_header_buffers.size)
+    {
+        return NGX_DECLINED;
+    }
+
+    hc = r->http_connection;
+
+    if (hc->nfree) {
+        b = hc->free[--hc->nfree];
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http large header free: " PTR_FMT " " SIZE_T_FMT,
+                       b->pos, b->end - b->last);
+
+    } else if (hc->nbusy < cscf->large_client_header_buffers.num) {
+
+        if (hc->busy == NULL) {
+            hc->busy = ngx_palloc(r->connection->pool,
+                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
+            if (hc->busy == NULL) {
+                return NGX_ERROR;
+            }
+        }
+
+        b = ngx_create_temp_buf(r->connection->pool,
+                                cscf->large_client_header_buffers.size);
+        if (b == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http large header alloc: " PTR_FMT " " SIZE_T_FMT,
+                       b->pos, b->end - b->last);
+
+    } else {
+        return NGX_DECLINED;
+    }
+
+    hc->busy[hc->nbusy++] = b;
+
+    if (r->state == 0) {
+        /*
+         * r->state == 0 means that a header line was parsed successfully
+         * and we do not need to copy incomplete header line and
+         * to relocate the parser header pointers
+         */
+
+        r->header_in = b;
+
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http large header copy: %d", r->header_in->pos - old);
+
+    new = b->start;
+
+    ngx_memcpy(new, old, r->header_in->pos - old);
+
+    b->pos = new + (r->header_in->pos - old);
+    b->last = new + (r->header_in->pos - old);
+
+    if (request_line) {
+        r->request_start = new;
+
+        if (r->request_end) {
+            r->request_end = new + (r->request_end - old);
+        }
+
+        r->method_end = new + (r->method_end - old);
+
+        r->uri_start = new + (r->uri_start - old);
+        r->uri_end = new + (r->uri_end - old);
+
+        if (r->schema_start) {
+            r->schema_start = new + (r->schema_start - old);
+            r->schema_end = new + (r->schema_end - old);
+        }
+
+        if (r->host_start) {
+            r->host_start = new + (r->host_start - old);
+            r->host_end = new + (r->host_end - old);
+        }
+
+        if (r->port_start) {
+            r->port_start = new + (r->port_start - old);
+            r->port_end = new + (r->port_end - old);
+        }
+
+        if (r->uri_ext) {
+            r->uri_ext = new + (r->uri_ext - old);
+        }
+
+        if (r->args_start) {
+            r->args_start = new + (r->args_start - old);
+        }
+
+    } else {
+        r->header_name_start = new;
+        r->header_name_end = new + (r->header_name_end - old);
+        r->header_start = new + (r->header_start - old);
+        r->header_end = new + (r->header_end - old);
+    }
+
+    r->header_in = b;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r)
+{
+    u_char                    *ua, *user_agent;
+    size_t                     len;
+    ngx_uint_t                 i;
+    ngx_http_server_name_t    *name;
+    ngx_http_core_srv_conf_t  *cscf;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (r->headers_in.host) {
+        for (len = 0; len < r->headers_in.host->value.len; len++) {
+            if (r->headers_in.host->value.data[len] == ':') {
+                break;
+            }
+        }
+        r->headers_in.host_name_len = len;
+
+        /* find the name based server configuration */
+
+        name = r->virtual_names->elts;
+        for (i = 0; i < r->virtual_names->nelts; i++) {
+            if (r->headers_in.host_name_len != name[i].name.len) {
+                continue;
+            }
+
+            if (ngx_strncasecmp(r->headers_in.host->value.data,
+                                name[i].name.data,
+                                r->headers_in.host_name_len) == 0)
+            {
+                r->srv_conf = name[i].core_srv_conf->ctx->srv_conf;
+                r->loc_conf = name[i].core_srv_conf->ctx->loc_conf;
+                r->server_name = &name[i].name;
+
+                clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+                r->connection->log->file = clcf->err_log->file;
+                if (!(r->connection->log->log_level & NGX_LOG_DEBUG_CONNECTION))
+                {
+                    r->connection->log->log_level = clcf->err_log->log_level;
+                }
+
+                break;
+            }
+        }
+
+        if (i == r->virtual_names->nelts) {
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+            if (cscf->restrict_host_names != NGX_HTTP_RESTRICT_HOST_OFF) {
+                return NGX_HTTP_PARSE_INVALID_HOST;
+            }
+        }
+
+    } else {
+        if (r->http_version > NGX_HTTP_VERSION_10) {
+            return NGX_HTTP_PARSE_NO_HOST_HEADER;
+        }
+        r->headers_in.host_name_len = 0;
+    }
+
+    if (r->headers_in.content_length) {
+        r->headers_in.content_length_n =
+                             ngx_atoi(r->headers_in.content_length->value.data,
+                                      r->headers_in.content_length->value.len);
+
+        if (r->headers_in.content_length_n == NGX_ERROR) {
+            return NGX_HTTP_PARSE_INVALID_CL_HEADER;
+        }
+    }
+
+    if (r->method == NGX_HTTP_POST && r->headers_in.content_length_n <= 0) {
+        return NGX_HTTP_PARSE_POST_WO_CL_HEADER;
+    }
+
+    if (r->plain_http) {
+        return NGX_HTTP_PARSE_HTTP_TO_HTTPS;
+    }
+
+    if (r->headers_in.connection) {
+        if (r->headers_in.connection->value.len == 5
+            && ngx_strcasecmp(r->headers_in.connection->value.data, "close")
+                                                                          == 0)
+        {
+            r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+        } else if (r->headers_in.connection->value.len == 10
+                   && ngx_strcasecmp(r->headers_in.connection->value.data,
+                                                            "keep-alive") == 0)
+        {
+            r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
+
+            if (r->headers_in.keep_alive) {
+                r->headers_in.keep_alive_n =
+                                 ngx_atoi(r->headers_in.keep_alive->value.data,
+                                          r->headers_in.keep_alive->value.len);
+            }
+        }
+    }
+
+    if (r->headers_in.user_agent) {
+
+        /*
+         * check some widespread browsers while the headers are still
+         * in CPU cache
+         */
+
+        user_agent = r->headers_in.user_agent->value.data;
+
+        ua = (u_char *) ngx_strstr(user_agent, "MSIE");
+
+        if (ua && ua + 8 < user_agent + r->headers_in.user_agent->value.len) {
+
+            r->headers_in.msie = 1;
+
+            if (ua[4] == ' ' && ua[5] == '4' && ua[6] == '.') {
+                r->headers_in.msie4 = 1;
+            }
+
+#if 0
+            /* MSIE ignores the SSL "close notify" alert */
+
+            ngx_ssl_set_nosendshut(r->connection->ssl);
+#endif
+        }
+
+        if (ngx_strstr(user_agent, "Opera")) {
+            r->headers_in.opera = 1;
+            r->headers_in.msie = 0;
+            r->headers_in.msie4 = 0;
+        }
+
+        if (!r->headers_in.msie && !r->headers_in.opera) {
+
+            if (ngx_strstr(user_agent, "Gecko/")) {
+                r->headers_in.gecko = 1;
+
+            } else if (ngx_strstr(user_agent, "Konqueror")) {
+                r->headers_in.konqueror = 1;
+            }
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+void ngx_http_finalize_request(ngx_http_request_t *r, int rc)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    /* r can be already destroyed when rc == NGX_DONE */
+
+    if (rc == NGX_DONE || r->main) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http finalize request: %d", rc);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+
+        if (r->connection->read->timer_set) {
+            ngx_del_timer(r->connection->read);
+        }
+
+        if (r->connection->write->timer_set) {
+            ngx_del_timer(r->connection->write);
+        }
+
+        if (rc == NGX_HTTP_CLIENT_CLOSED_REQUEST || r->closed) {
+            ngx_http_close_request(r, 0);
+            ngx_http_close_connection(r->connection);
+            return;
+        }
+
+        ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
+
+        return;
+
+    } else if (rc == NGX_ERROR) {
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(r->connection);
+        return;
+
+    } else if (rc == NGX_AGAIN) {
+        ngx_http_set_write_handler(r);
+        return;
+    }
+
+    if (r->connection->read->timer_set) {
+        ngx_del_timer(r->connection->read);
+    }
+
+    if (r->connection->write->timer_set) {
+        r->connection->write->delayed = 0;
+        ngx_del_timer(r->connection->write);
+    }
+
+    if (r->connection->read->pending_eof) {
+#if (NGX_KQUEUE)
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log,
+                       r->connection->read->kq_errno,
+                       "kevent() reported about an closed connection");
+#endif
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(r->connection);
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (!ngx_terminate
+         && !ngx_exiting
+         && r->keepalive != 0
+         && clcf->keepalive_timeout > 0)
+    {
+        ngx_http_set_keepalive(r);
+        return;
+
+    } else if (r->lingering_close && clcf->lingering_timeout > 0) {
+        ngx_http_set_lingering_close(r);
+        return;
+    }
+
+    ngx_http_close_request(r, 0);
+    ngx_http_close_connection(r->connection);
+}
+
+
+static void ngx_http_set_write_handler(ngx_http_request_t *r)
+{
+    ngx_event_t               *wev;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    wev = r->connection->write;
+    wev->event_handler = ngx_http_writer;
+
+    r->http_state = NGX_HTTP_WRITING_REQUEST_STATE;
+
+    if (wev->ready && wev->delayed) {
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                        ngx_http_core_module);
+    if (!wev->delayed) {
+        ngx_add_timer(wev, clcf->send_timeout);
+    }
+
+    wev->available = clcf->send_lowat;
+    if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(r->connection);
+    }
+
+    return;
+}
+
+
+void ngx_http_writer(ngx_event_t *wev)
+{
+    int                        rc;
+    ngx_connection_t          *c;
+    ngx_http_request_t        *r;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http writer handler");
+
+    c = wev->data;
+    r = c->data;
+
+    if (wev->timedout) {
+        if (!wev->delayed) {
+            ngx_http_client_error(r, 0, NGX_HTTP_REQUEST_TIME_OUT);
+            return;
+        }
+
+        wev->timedout = 0;
+        wev->delayed = 0;
+
+        if (!wev->ready) {
+            clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                                ngx_http_core_module);
+            ngx_add_timer(wev, clcf->send_timeout);
+
+            wev->available = clcf->send_lowat;
+
+            if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+                ngx_http_close_request(r, 0);
+                ngx_http_close_connection(r->connection);
+            }
+
+            return;
+        }
+
+    } else {
+        if (wev->delayed) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0,
+                           "http writer delayed");
+
+            clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                                ngx_http_core_module);
+            wev->available = clcf->send_lowat;
+
+            if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+                ngx_http_close_request(r, 0);
+                ngx_http_close_connection(r->connection);
+            }
+
+            return;
+        }
+    }
+
+    rc = ngx_http_output_filter(r, NULL);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                  "http writer output filter: %d", rc);
+
+    if (rc == NGX_AGAIN) {
+        clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                            ngx_http_core_module);
+        if (!wev->ready && !wev->delayed) {
+            ngx_add_timer(wev, clcf->send_timeout);
+        }
+
+        wev->available = clcf->send_lowat;
+
+        if (ngx_handle_write_event(wev, NGX_LOWAT_EVENT) == NGX_ERROR) {
+            ngx_http_close_request(r, 0);
+            ngx_http_close_connection(r->connection);
+        }
+
+        return;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http writer done");
+
+    ngx_http_finalize_request(r, rc);
+}
+
+
+static void ngx_http_block_read(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_http_request_t        *r;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http read blocked");
+
+    /* aio does not call this handler */
+
+    if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && rev->active) {
+        if (ngx_del_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
+            c = rev->data;
+            r = c->data;
+            ngx_http_close_request(r, 0);
+            ngx_http_close_connection(c);
+        }
+    }
+}
+
+
+ngx_int_t ngx_http_discard_body(ngx_http_request_t *r)
+{
+    ssize_t       size;
+    ngx_event_t  *rev;
+
+    rev = r->connection->read;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
+
+    if (rev->timer_set) {
+        ngx_del_timer(rev);
+    }
+
+    if (r->headers_in.content_length_n <= 0) {
+        return NGX_OK;
+    }
+
+    size = r->header_in->last - r->header_in->pos;
+
+    if (size) {
+        if (r->headers_in.content_length_n > size) {
+            r->headers_in.content_length_n -= size;
+
+        } else {
+            r->header_in->pos += r->headers_in.content_length_n;
+            r->headers_in.content_length_n = 0;
+            return NGX_OK;
+        }
+    }
+
+    rev->event_handler = ngx_http_read_discarded_body_event;
+
+    if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+        return NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    return ngx_http_read_discarded_body(r);
+}
+
+
+static void ngx_http_read_discarded_body_event(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = rev->data;
+    r = c->data;
+
+    rc = ngx_http_read_discarded_body(r);
+
+    if (rc == NGX_AGAIN) {
+        if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+            ngx_http_close_request(r, rc);
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+    if (rc != NGX_OK) {
+        ngx_http_close_request(r, rc);
+        ngx_http_close_connection(c);
+    }
+}
+
+
+static ngx_int_t ngx_http_read_discarded_body(ngx_http_request_t *r)
+{
+    ssize_t  size, n;
+    u_char   buffer[NGX_HTTP_DISCARD_BUFFER_SIZE];
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http read discarded body");
+
+    if (r->headers_in.content_length_n == 0) {
+        return NGX_OK;
+    }
+
+
+    size = r->headers_in.content_length_n;
+
+    if (size > NGX_HTTP_DISCARD_BUFFER_SIZE) {
+        size = NGX_HTTP_DISCARD_BUFFER_SIZE;
+    }
+
+    n = r->connection->recv(r->connection, buffer, size);
+
+    if (n == NGX_ERROR) {
+
+        r->closed = 1;
+
+        /*
+         * if a client request body is discarded then we already set
+         * some HTTP response code for client and we can ignore the error
+         */
+
+        return NGX_OK;
+    }
+
+    if (n == NGX_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    r->headers_in.content_length_n -= n;
+
+    return NGX_OK;
+}
+
+
+static void ngx_http_set_keepalive(ngx_http_request_t *r)
+{
+    ngx_int_t                  i;
+    ngx_buf_t                 *b, *f;
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *c;
+    ngx_http_connection_t     *hc;
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_core_srv_conf_t  *cscf;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+    rev = c->read;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "set http keepalive handler");
+
+    ctx = (ngx_http_log_ctx_t *) c->log->data;
+    ctx->action = "closing request";
+
+    hc = r->http_connection;
+    b = r->header_in;
+
+    if (b->pos < b->last) {
+
+        /* the pipelined request */
+
+        if (b != c->buffer) {
+
+            /* move the large header buffers to the free list */
+
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+            if (hc->free == NULL) {
+                hc->free = ngx_palloc(c->pool,
+                  cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *));
+
+                if (hc->free == NULL) {
+                    ngx_http_close_connection(c);
+                    return;
+                }
+            }
+
+            for (i = 0; i < hc->nbusy - 1; i++) {
+                f = hc->busy[i];
+                hc->free[hc->nfree++] = f;
+                f->pos = f->start;
+                f->last = f->start;
+            }
+
+            hc->busy[0] = b;
+            hc->nbusy = 1;
+        }
+    }
+
+    ngx_http_close_request(r, 0);
+    c->data = hc;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    ngx_add_timer(rev, clcf->keepalive_timeout);
+
+    if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    wev = c->write;
+    wev->event_handler = ngx_http_empty_handler;
+
+    if (b->pos < b->last) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "pipelined request");
+
+        hc->pipeline = 1;
+        ctx->action = "reading client pipelined request line";
+        ngx_http_init_request(rev);
+        return;
+    }
+
+    hc->pipeline = 0;
+
+    if (ngx_pfree(c->pool, r) == NGX_OK) {
+        hc->request = NULL;
+    }
+
+    b = c->buffer;
+
+    if (ngx_pfree(c->pool, b->start) == NGX_OK) {
+        b->pos = NULL;
+
+    } else {
+        b->pos = b->start;
+        b->last = b->start;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc free: " PTR_FMT " %d",
+                   hc->free, hc->nfree);
+
+    if (hc->free) {
+        for (i = 0; i < hc->nfree; i++) {
+            ngx_pfree(c->pool, hc->free[i]);
+            hc->free[i] = NULL;
+        }
+
+        hc->nfree = 0;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "hc busy: " PTR_FMT " %d",
+                   hc->busy, hc->nbusy);
+
+    if (hc->busy) {
+        for (i = 0; i < hc->nbusy; i++) {
+            ngx_pfree(c->pool, hc->busy[i]);
+            hc->busy[i] = NULL;
+        }
+
+        hc->nbusy = 0;
+    }
+
+    rev->event_handler = ngx_http_keepalive_handler;
+
+    if (wev->active) {
+        if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_DISABLE_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+        } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                ngx_http_close_connection(c);
+                return;
+            }
+        }
+    }
+
+    ctx->action = "keepalive";
+
+    if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+        if (ngx_tcp_push(c->fd) == NGX_ERROR) {
+            ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed");
+            ngx_http_close_connection(c);
+            return;
+        }
+        c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+    }
+
+#if 0
+    /* if "keepalive_buffers off" then we need some other place */
+    r->http_state = NGX_HTTP_KEEPALIVE_STATE;
+#endif
+
+    if (rev->ready) {
+        ngx_http_keepalive_handler(rev);
+    }
+}
+
+
+static void ngx_http_keepalive_handler(ngx_event_t *rev)
+{
+    size_t                  size;
+    ssize_t                 n;
+    ngx_buf_t              *b;
+    ngx_connection_t       *c;
+    ngx_http_log_ctx_t     *ctx;
+    ngx_http_connection_t  *hc;
+
+    c = rev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http keepalive handler");
+
+    if (rev->timedout) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    ctx = (ngx_http_log_ctx_t *) rev->log->data;
+
+#if (HAVE_KQUEUE)
+
+    if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+        if (rev->pending_eof) {
+            ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                          "kevent() reported that client %s closed "
+                          "keepalive connection", ctx->client);
+            ngx_http_close_connection(c);
+            return;
+        }
+    }
+
+#endif
+
+    hc = c->data;
+    b = c->buffer;
+    size = b->end - b->start;
+
+    if (b->pos == NULL) {
+        if (!(b->pos = ngx_palloc(c->pool, size))) {
+            ngx_http_close_connection(c);
+            return;
+        }
+
+        b->start = b->pos;
+        b->last = b->pos;
+        b->end = b->pos + size;
+    }
+
+    /*
+     * MSIE closes a keepalive connection with RST flag
+     * so we ignore ECONNRESET here.
+     */
+
+    c->log_error = NGX_ERROR_IGNORE_ECONNRESET;
+    ngx_set_socket_errno(0);
+
+    n = c->recv(c, b->last, size);
+    c->log_error = NGX_ERROR_INFO;
+
+    if (n == NGX_AGAIN) {
+        return;
+    }
+
+    if (n == NGX_ERROR) {
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    rev->log->handler = NULL;
+
+    if (n == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno,
+                      "client %s closed keepalive connection", ctx->client);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    b->last += n;
+    rev->log->handler = ngx_http_log_error;
+    ctx->action = "reading client request line";
+
+    ngx_http_init_request(rev);
+}
+
+
+static void ngx_http_set_lingering_close(ngx_http_request_t *r)
+{   
+    ngx_event_t               *rev, *wev;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    rev = c->read;
+    rev->event_handler = ngx_http_lingering_close_handler;
+
+    r->lingering_time = ngx_time() + clcf->lingering_time / 1000;
+    ngx_add_timer(rev, clcf->lingering_timeout);
+
+    if (ngx_handle_level_read_event(rev) == NGX_ERROR) {
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    wev = c->write;
+    wev->event_handler = ngx_http_empty_handler;
+
+    if (wev->active) {
+        if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, NGX_DISABLE_EVENT)
+                                                                  == NGX_ERROR)
+            {
+                ngx_http_close_connection(c);
+                return;
+            }
+
+        } else if (ngx_event_flags & NGX_USE_LEVEL_EVENT) {
+            if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) == NGX_ERROR) {
+                ngx_http_close_connection(c);
+                return;
+            }
+        }
+    }
+
+    if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+        ngx_connection_error(c, ngx_socket_errno,
+                             ngx_shutdown_socket_n " failed");
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    if (rev->ready) {
+        ngx_http_lingering_close_handler(rev);
+    }
+}
+
+
+static void ngx_http_lingering_close_handler(ngx_event_t *rev)
+{
+    ssize_t                    n;
+    ngx_msec_t                 timer;
+    ngx_connection_t          *c;
+    ngx_http_request_t        *r;
+    ngx_http_core_loc_conf_t  *clcf;
+    u_char                     buffer[NGX_HTTP_LINGERING_BUFFER_SIZE];
+
+    c = rev->data;
+    r = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http lingering close handler");
+
+    if (rev->timedout) {
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    timer = r->lingering_time - ngx_time();
+    if (timer <= 0) {
+        ngx_http_close_request(r, 0);
+        ngx_http_close_connection(c);
+        return;
+    }
+
+    do {
+        n = c->recv(c, buffer, NGX_HTTP_LINGERING_BUFFER_SIZE);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "lingering read: %d", n);
+
+        if (n == NGX_ERROR || n == 0) {
+            ngx_http_close_request(r, 0);
+            ngx_http_close_connection(c);
+            return;
+        }
+
+    } while (rev->ready);
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    timer *= 1000;
+
+    if (timer > clcf->lingering_timeout) {
+        timer = clcf->lingering_timeout;
+    }
+
+    ngx_add_timer(rev, timer);
+
+    return;
+}
+
+
+void ngx_http_empty_handler(ngx_event_t *wev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, wev->log, 0, "http empty handler");
+
+    return;
+}
+
+
+ngx_int_t ngx_http_send_last(ngx_http_request_t *r)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t   out;
+
+    if (!(b = ngx_calloc_buf(r->pool))) {
+        return NGX_ERROR;
+    }
+
+    b->last_buf = 1;
+    out.buf = b;
+    out.next = NULL;
+
+    return ngx_http_output_filter(r, &out);
+}
+
+
+void ngx_http_close_request(ngx_http_request_t *r, int error)
+{
+    ngx_uint_t                 i;
+    ngx_log_t                 *log;
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_cleanup_t        *cleanup;
+    ngx_http_core_loc_conf_t  *clcf;
+    struct linger              l;
+
+    log = r->connection->log;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http close request");
+
+    if (r->pool == NULL) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "http request already closed");
+        return;
+    }
+
+#if (NGX_STAT_STUB)
+    if (r->stat_reading) {
+        (*ngx_stat_reading)--;
+    }
+
+    if (r->stat_writing) {
+        (*ngx_stat_writing)--;
+    }
+#endif
+
+    if (error && r->headers_out.status == 0) {
+        r->headers_out.status = error;
+    }
+
+    ngx_http_log_handler(r);
+
+    cleanup = r->cleanup.elts;
+    for (i = 0; i < r->cleanup.nelts; i++) {
+        if (!cleanup[i].valid) {
+            continue;
+        }
+
+#if (NGX_HTTP_CACHE)
+
+        if (cleanup[i].cache) {
+            ngx_http_cache_unlock(cleanup[i].data.cache.hash,
+                                  cleanup[i].data.cache.cache, log);
+            continue;
+        }
+
+#endif
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http cleanup fd: %d",
+                       cleanup[i].data.file.fd);
+
+        if (ngx_close_file(cleanup[i].data.file.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed",
+                          cleanup[i].data.file.name);
+        }
+    }
+
+    /* STUB */
+    if (r->file.fd != NGX_INVALID_FILE) {
+        if (ngx_close_file(r->file.fd) == NGX_FILE_ERROR) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", r->file.name.data);
+        }
+    }
+
+    if (r->request_body
+        && r->request_body->temp_file
+        && r->request_body->temp_file->file.fd != NGX_INVALID_FILE)
+    {
+        if (ngx_close_file(r->request_body->temp_file->file.fd)
+                                                             == NGX_FILE_ERROR)
+        {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          ngx_close_file_n " deleted file \"%s\" failed",
+                          r->request_body->temp_file->file.name.data);
+        }
+    }
+
+    if (r->connection->timedout) {
+        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+        if (clcf->reset_timedout_connection) {
+            l.l_onoff = 1;
+            l.l_linger = 0;
+
+            if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
+                           (const void *) &l, sizeof(struct linger)) == -1)
+            {
+                ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
+                              "setsockopt(SO_LINGER) failed");
+            }
+        }
+    }
+
+    /* ctx->url was allocated from r->pool */
+    ctx = log->data;
+    ctx->url = NULL;
+
+    r->request_line.len = 0;
+
+    ngx_destroy_pool(r->pool);
+
+    return;
+}
+
+
+#if (NGX_HTTP_SSL)
+
+void ngx_ssl_close_handler(ngx_event_t *ev)
+{
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http ssl close handler");
+
+    if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+        return;
+    }
+
+    ngx_http_close_connection(c);
+}
+
+#endif
+
+
+void ngx_http_close_connection(ngx_connection_t *c)
+{
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "close http connection: %d", c->fd);
+
+#if (NGX_STAT_STUB)
+    (*ngx_stat_active)--;
+#endif
+
+    ngx_close_connection(c);
+}
+
+
+static void ngx_http_client_error(ngx_http_request_t *r,
+                                  int client_error, int error)
+{
+    ngx_http_log_ctx_t        *ctx;
+    ngx_http_core_srv_conf_t  *cscf;
+
+    ctx = r->connection->log->data;
+
+    if (error == NGX_HTTP_REQUEST_TIME_OUT) {
+        ngx_log_error(NGX_LOG_INFO, r->connection->log, NGX_ETIMEDOUT,
+                      "client timed out");
+        r->connection->timedout = 1;
+        ngx_http_close_request(r, error);
+        ngx_http_close_connection(r->connection);
+        return;
+    }
+
+    r->connection->log->handler = NULL;
+
+    if (ctx->url) {
+        switch (client_error) {
+
+        case NGX_HTTP_PARSE_INVALID_HOST:
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                    client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+                    ctx->client, r->headers_in.host->value.data, ctx->url);
+
+            error = NGX_HTTP_INVALID_HOST;
+
+            cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+            if (cscf->restrict_host_names == NGX_HTTP_RESTRICT_HOST_CLOSE) {
+                ngx_http_close_request(r, error);
+                ngx_http_close_connection(r->connection);
+                return;
+            }
+
+            break;
+
+        case NGX_HTTP_PARSE_HTTP_TO_HTTPS:
+            error = NGX_HTTP_TO_HTTPS;
+
+            /* fall through */
+
+        default:
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                    client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+                    ctx->client, ctx->url);
+        }
+
+    } else {
+        if (error == NGX_HTTP_REQUEST_URI_TOO_LARGE) {
+            r->request_line.len = r->header_in->end - r->request_start;
+            r->request_line.data = r->request_start;
+        }
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                    client_header_errors[client_error - NGX_HTTP_CLIENT_ERROR],
+                    ctx->client);
+    }
+
+    r->connection->log->handler = ngx_http_log_error;
+
+    ngx_http_finalize_request(r, error);
+}
+
+
+static size_t ngx_http_log_error(void *data, char *buf, size_t len)
+{
+    ngx_http_log_ctx_t *ctx = data;
+
+    if (ctx->action && ctx->url) {
+        return ngx_snprintf(buf, len, " while %s, client: %s, URL: %s",
+                            ctx->action, ctx->client, ctx->url);
+
+    } else if (ctx->action == NULL && ctx->url) {
+        return ngx_snprintf(buf, len, ", client: %s, URL: %s",
+                            ctx->client, ctx->url);
+
+    } else {
+        return ngx_snprintf(buf, len, " while %s, client: %s",
+                            ctx->action, ctx->client);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_request.h
@@ -0,0 +1,372 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_HTTP_REQUEST_H_INCLUDED_
+#define _NGX_HTTP_REQUEST_H_INCLUDED_
+
+
+#define NGX_HTTP_DISCARD_BUFFER_SIZE       4096
+#define NGX_HTTP_LINGERING_BUFFER_SIZE     4096
+
+
+#define NGX_HTTP_VERSION_9                 9
+#define NGX_HTTP_VERSION_10                1000
+#define NGX_HTTP_VERSION_11                1001
+
+#define NGX_HTTP_GET                       1
+#define NGX_HTTP_HEAD                      2
+#define NGX_HTTP_POST                      3
+
+#define NGX_HTTP_CONNECTION_CLOSE          1
+#define NGX_HTTP_CONNECTION_KEEP_ALIVE     2
+
+
+#define NGX_NONE                           1
+
+
+#define NGX_HTTP_PARSE_HEADER_DONE         1
+
+#define NGX_HTTP_CLIENT_ERROR              10
+#define NGX_HTTP_PARSE_INVALID_METHOD      10
+#define NGX_HTTP_PARSE_INVALID_REQUEST     11
+#define NGX_HTTP_PARSE_TOO_LONG_URI        12
+#define NGX_HTTP_PARSE_INVALID_09_METHOD   13
+
+#define NGX_HTTP_PARSE_HEADER_ERROR        14
+#define NGX_HTTP_PARSE_INVALID_HEADER      14
+#define NGX_HTTP_PARSE_TOO_LONG_HEADER     15
+#define NGX_HTTP_PARSE_NO_HOST_HEADER      17
+#define NGX_HTTP_PARSE_INVALID_CL_HEADER   18
+#define NGX_HTTP_PARSE_POST_WO_CL_HEADER   19
+#define NGX_HTTP_PARSE_HTTP_TO_HTTPS       20
+#define NGX_HTTP_PARSE_INVALID_HOST        21
+
+
+#define NGX_HTTP_OK                        200
+#define NGX_HTTP_PARTIAL_CONTENT           206
+
+#define NGX_HTTP_SPECIAL_RESPONSE          300
+#define NGX_HTTP_MOVED_PERMANENTLY         301
+#define NGX_HTTP_MOVED_TEMPORARILY         302
+#define NGX_HTTP_NOT_MODIFIED              304
+
+#define NGX_HTTP_BAD_REQUEST               400
+#define NGX_HTTP_FORBIDDEN                 403
+#define NGX_HTTP_NOT_FOUND                 404
+#define NGX_HTTP_NOT_ALLOWED               405
+#define NGX_HTTP_REQUEST_TIME_OUT          408
+#define NGX_HTTP_REQUEST_ENTITY_TOO_LARGE  413
+#define NGX_HTTP_REQUEST_URI_TOO_LARGE     414
+#define NGX_HTTP_RANGE_NOT_SATISFIABLE     416
+
+
+/* Our own HTTP codes */
+
+#define NGX_HTTP_NGX_CODES                 NGX_HTTP_TO_HTTPS
+
+/*
+ * We use the special code for the plain HTTP requests that are sent to
+ * HTTPS port to distinguish it from 4XX in an error page redirection 
+ */
+#define NGX_HTTP_TO_HTTPS                  497
+
+/*
+ * We use the special code for the requests with invalid host name
+ * to distinguish it from 4XX in an error page redirection 
+ */
+#define NGX_HTTP_INVALID_HOST              498
+
+/*
+ * HTTP does not define the code for the case when a client closed
+ * the connection while we are processing its request so we introduce
+ * own code to log such situation when a client has closed the connection
+ * before we even try to send the HTTP header to it
+ */
+#define NGX_HTTP_CLIENT_CLOSED_REQUEST     499
+
+
+#define NGX_HTTP_INTERNAL_SERVER_ERROR     500
+#define NGX_HTTP_NOT_IMPLEMENTED           501
+#define NGX_HTTP_BAD_GATEWAY               502
+#define NGX_HTTP_SERVICE_UNAVAILABLE       503
+#define NGX_HTTP_GATEWAY_TIME_OUT          504
+
+
+typedef enum {
+    NGX_HTTP_RESTRICT_HOST_OFF = 0,
+    NGX_HTTP_RESTRICT_HOST_ON,
+    NGX_HTTP_RESTRICT_HOST_CLOSE
+} ngx_http_restrict_host_e;
+
+
+typedef enum {
+    NGX_HTTP_INITING_REQUEST_STATE = 0,
+    NGX_HTTP_READING_REQUEST_STATE,
+    NGX_HTTP_PROCESS_REQUEST_STATE,
+
+    NGX_HTTP_CONNECT_UPSTREAM_STATE,
+    NGX_HTTP_WRITING_UPSTREAM_STATE,
+    NGX_HTTP_READING_UPSTREAM_STATE,
+
+    NGX_HTTP_WRITING_REQUEST_STATE,
+    NGX_HTTP_LINGERING_CLOSE_STATE,
+    NGX_HTTP_KEEPALIVE_STATE
+} ngx_http_state_e;
+
+
+typedef struct {
+    ngx_str_t         name;
+    ngx_uint_t        offset;
+} ngx_http_header_t;
+
+
+typedef struct {
+    ngx_list_t        headers;
+
+    ngx_table_elt_t  *host;
+    ngx_table_elt_t  *connection;
+    ngx_table_elt_t  *if_modified_since;
+    ngx_table_elt_t  *user_agent;
+    ngx_table_elt_t  *referer;
+    ngx_table_elt_t  *content_length;
+
+    ngx_table_elt_t  *range;
+
+#if (NGX_HTTP_GZIP)
+    ngx_table_elt_t  *accept_encoding;
+    ngx_table_elt_t  *via;
+#endif
+
+    ngx_table_elt_t  *authorization;
+
+    ngx_table_elt_t  *keep_alive;
+
+#if (NGX_HTTP_PROXY)
+    ngx_table_elt_t  *x_forwarded_for;
+#endif
+
+    ngx_array_t       cookies;
+
+    size_t            host_name_len;
+    ssize_t           content_length_n;
+    size_t            connection_type;
+    ssize_t           keep_alive_n;
+
+    unsigned          msie:1;
+    unsigned          msie4:1;
+    unsigned          opera:1;
+    unsigned          gecko:1;
+    unsigned          konqueror:1;
+} ngx_http_headers_in_t;
+
+
+typedef struct {
+    off_t             start;
+    off_t             end;
+    ngx_str_t         content_range;
+} ngx_http_range_t;
+
+
+typedef struct {
+    ngx_list_t        headers;
+
+    ngx_uint_t        status;
+    ngx_str_t         status_line;
+
+    ngx_table_elt_t  *server;
+    ngx_table_elt_t  *date;
+    ngx_table_elt_t  *content_type;
+    ngx_table_elt_t  *content_length;
+    ngx_table_elt_t  *content_encoding;
+    ngx_table_elt_t  *location;
+    ngx_table_elt_t  *last_modified;
+    ngx_table_elt_t  *content_range;
+    ngx_table_elt_t  *accept_ranges;
+    ngx_table_elt_t  *expires;
+    ngx_table_elt_t  *cache_control;
+    ngx_table_elt_t  *etag;
+
+    ngx_str_t         charset;
+    ngx_array_t       ranges;
+
+    off_t             content_length_n;
+    time_t            date_time;
+    time_t            last_modified_time;
+} ngx_http_headers_out_t;
+
+
+typedef struct {
+    ngx_temp_file_t   *temp_file;
+    ngx_chain_t       *bufs;
+    ngx_buf_t         *buf;
+    size_t             rest;
+    void             (*handler) (void *data); 
+    void              *data;
+} ngx_http_request_body_t;
+
+
+struct ngx_http_cleanup_s {
+    union {
+        struct {
+            ngx_fd_t                 fd;
+            u_char                  *name;
+        } file;
+
+        struct {
+            ngx_http_cache_hash_t   *hash;
+            ngx_http_cache_t        *cache;
+        } cache;
+    } data;
+
+    unsigned                         valid:1;
+    unsigned                         cache:1;
+};
+
+
+typedef struct {
+    ngx_http_request_t   *request;
+
+    ngx_buf_t           **busy;
+    ngx_int_t             nbusy;
+
+    ngx_buf_t           **free;
+    ngx_int_t             nfree;
+
+    ngx_uint_t            pipeline;      /* unsigned  pipeline:1; */
+} ngx_http_connection_t;
+
+
+typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
+
+struct ngx_http_request_s {
+    uint32_t                  signature;         /* "HTTP" */
+
+    ngx_connection_t         *connection;
+
+    void                    **ctx;
+    void                    **main_conf;
+    void                    **srv_conf;
+    void                    **loc_conf;
+
+    ngx_http_cache_t         *cache;
+
+    ngx_file_t                file;
+
+    ngx_pool_t               *pool;
+    ngx_buf_t                *header_in;
+
+    ngx_http_headers_in_t     headers_in;
+    ngx_http_headers_out_t    headers_out;
+
+    ngx_http_request_body_t  *request_body;
+
+    time_t               lingering_time;
+
+    ngx_uint_t           method;
+    ngx_uint_t           http_version;
+    ngx_uint_t           http_major;
+    ngx_uint_t           http_minor;
+
+    ngx_str_t            request_line;
+    ngx_str_t            uri;
+    ngx_str_t            args;
+    ngx_str_t            exten;
+    ngx_str_t            unparsed_uri;
+
+    ngx_str_t            method_name;
+
+    ngx_http_request_t  *main;
+
+    uint32_t             in_addr;
+    ngx_uint_t           port;
+    ngx_str_t           *port_text;    /* ":80" */
+    ngx_str_t           *server_name;
+    ngx_array_t         *virtual_names;
+
+    ngx_uint_t           phase;
+    ngx_int_t            phase_handler;
+    ngx_http_handler_pt  content_handler;
+
+    ngx_array_t          cleanup;
+
+    /* used to learn the Apache compatible response length without a header */
+    size_t               header_size;
+
+    u_char              *discarded_buffer;
+    void               **err_ctx;
+    ngx_uint_t           err_status;
+
+    ngx_http_connection_t  *http_connection;
+
+    unsigned             http_state:4;
+
+#if 0
+    /* URI is not started with '/' - "GET http://" */
+    unsigned             unusual_uri:1;
+#endif
+    /* URI with "/.", "%" and on Win32 with "//" */
+    unsigned             complex_uri:1;
+    unsigned             header_timeout_set:1;
+
+    unsigned             proxy:1;
+    unsigned             bypass_cache:1;
+    unsigned             no_cache:1;
+
+#if 0
+    unsigned             cachable:1;
+#endif
+    unsigned             pipeline:1;
+
+    /* can we use sendfile ? */
+    unsigned             sendfile:1;
+
+    unsigned             plain_http:1;
+    unsigned             chunked:1;
+    unsigned             header_only:1;
+    unsigned             keepalive:1;
+    unsigned             lingering_close:1;
+    unsigned             closed:1;
+
+    unsigned             filter_need_in_memory:1;
+    unsigned             filter_ssi_need_in_memory:1;
+    unsigned             filter_need_temporary:1;
+    unsigned             filter_allow_ranges:1;
+
+#if (NGX_STAT_STUB)
+    unsigned             stat_reading:1;
+    unsigned             stat_writing:1;
+#endif
+
+    ngx_uint_t           headers_n;
+
+    /* used to parse HTTP headers */
+    ngx_uint_t           state;
+    u_char              *uri_start;
+    u_char              *uri_end;
+    u_char              *uri_ext;
+    u_char              *args_start;
+    u_char              *request_start;
+    u_char              *request_end;
+    u_char              *method_end;
+    u_char              *schema_start;
+    u_char              *schema_end;
+    u_char              *host_start;
+    u_char              *host_end;
+    u_char              *port_start;
+    u_char              *port_end;
+    u_char              *header_name_start;
+    u_char              *header_name_end;
+    u_char              *header_start;
+    u_char              *header_end;
+};
+
+
+extern ngx_http_header_t ngx_http_headers_in[];
+extern ngx_http_header_t ngx_http_headers_out[];
+
+
+
+#endif /* _NGX_HTTP_REQUEST_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_request_body.c
@@ -0,0 +1,227 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+static void ngx_http_read_client_request_body_handler(ngx_event_t *rev);
+static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r);
+
+
+ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r)
+{
+    ssize_t                    size;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *cl;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    size = r->header_in->last - r->header_in->pos;
+
+    if (size) {
+
+        /* there is the pre-read part of the request body */
+
+        ngx_test_null(b, ngx_calloc_buf(r->pool),
+                      NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+        b->temporary = 1;
+        b->start = b->pos = r->header_in->pos;
+        b->end = b->last = r->header_in->last;
+
+        ngx_alloc_link_and_set_buf(r->request_body->bufs, b, r->pool,
+                                    NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+        if (size >= r->headers_in.content_length_n) {
+
+            /* the whole request body was pre-read */
+
+            r->header_in->pos += r->headers_in.content_length_n;
+
+            r->request_body->handler(r->request_body->data);
+
+            return NGX_OK;
+        }
+
+        r->header_in->pos = r->header_in->last;
+    }
+
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    r->request_body->rest = r->headers_in.content_length_n - size;
+
+    if (r->request_body->rest
+                             < clcf->client_body_buffer_size
+                                        + (clcf->client_body_buffer_size >> 2))
+    {
+        size = r->request_body->rest;
+
+    } else {
+        size = clcf->client_body_buffer_size;
+    }
+
+    ngx_test_null(r->request_body->buf, ngx_create_temp_buf(r->pool, size),
+                  NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    ngx_alloc_link_and_set_buf(cl, r->request_body->buf, r->pool,
+                               NGX_HTTP_INTERNAL_SERVER_ERROR);
+
+    if (r->request_body->bufs) {
+        r->request_body->bufs->next = cl;
+
+    } else {
+        r->request_body->bufs = cl;
+    }
+
+    r->connection->read->event_handler =
+                                     ngx_http_read_client_request_body_handler;
+
+    return ngx_http_do_read_client_request_body(r);
+}
+
+
+static void ngx_http_read_client_request_body_handler(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    c = rev->data;
+    r = c->data;
+
+    if (rev->timedout) {
+        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
+        return;
+    }
+
+    rc = ngx_http_do_read_client_request_body(r);
+
+    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+        ngx_http_finalize_request(r, rc);
+    }
+}
+
+
+static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r)
+{
+    size_t                     size;
+    ssize_t                    n;
+    ngx_buf_t                 *b;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = r->connection;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http read client request body");
+
+    for ( ;; ) {
+        if (r->request_body->buf->last == r->request_body->buf->end) {
+            n = ngx_write_chain_to_temp_file(r->request_body->temp_file,
+                     r->request_body->bufs->next ? r->request_body->bufs->next:
+                                                   r->request_body->bufs);
+
+            /* TODO: n == 0 or not complete and level event */
+
+            if (n == NGX_ERROR) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            r->request_body->temp_file->offset += n;
+
+            r->request_body->buf->pos = r->request_body->buf->start;
+            r->request_body->buf->last = r->request_body->buf->start;
+        }
+
+        size = r->request_body->buf->end - r->request_body->buf->last;
+
+        if (size > r->request_body->rest) {
+            size = r->request_body->rest;
+        }
+
+        n = c->recv(c, r->request_body->buf->last, size);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "http client request body recv " SIZE_T_FMT, n);
+
+        if (n == NGX_AGAIN) {
+            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+            ngx_add_timer(c->read, clcf->client_body_timeout);
+
+            if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+                return NGX_HTTP_INTERNAL_SERVER_ERROR;
+            }
+
+            return NGX_AGAIN;
+        }
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                          "client closed prematurely connection");
+        }
+
+        if (n == 0 || n == NGX_ERROR) {
+            r->closed = 1;
+            return NGX_HTTP_BAD_REQUEST;
+        }
+
+        r->request_body->buf->last += n;
+        r->request_body->rest -= n;
+
+        if (r->request_body->rest == 0) {
+            break;
+        }
+
+        if (r->request_body->buf->last < r->request_body->buf->end) {
+            break;
+        }
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http client request body rest " SIZE_T_FMT,
+                   r->request_body->rest);
+
+    if (r->request_body->rest) {
+        return NGX_AGAIN;
+    }
+
+    if (r->request_body->temp_file->file.fd != NGX_INVALID_FILE) {
+
+        /* save the last part */
+        n = ngx_write_chain_to_temp_file(r->request_body->temp_file,
+                     r->request_body->bufs->next ? r->request_body->bufs->next:
+                                                   r->request_body->bufs);
+
+        /* TODO: n == 0 or not complete and level event */
+
+        if (n == NGX_ERROR) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        if (!(b = ngx_calloc_buf(r->pool))) {
+            return NGX_HTTP_INTERNAL_SERVER_ERROR;
+        }
+
+        b->in_file = 1;
+        b->file_pos = 0;
+        b->file_last = r->request_body->temp_file->file.offset;
+        b->file = &r->request_body->temp_file->file;
+
+        if (r->request_body->bufs->next) {
+            r->request_body->bufs->next->buf = b;
+
+        } else {
+            r->request_body->bufs->buf = b;
+        }
+    }
+
+    r->request_body->handler(r->request_body->data);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_special_response.c
@@ -0,0 +1,358 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <nginx.h>
+
+
+static u_char error_tail[] =
+"<hr><center>" NGINX_VER "</center>" CRLF
+"</body>" CRLF
+"</html>" CRLF
+;
+
+
+static u_char msie_stub[] =
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+"<!-- The padding to disable MSIE's friendly error page -->" CRLF
+;
+
+
+static char error_301_page[] =
+"<html>" CRLF
+"<head><title>301 Moved Permanently</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>301 Moved Permanently</h1></center>" CRLF
+;
+
+
+static char error_302_page[] =
+"<html>" CRLF
+"<head><title>302 Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>302 Found</h1></center>" CRLF
+;
+
+
+static char error_400_page[] =
+"<html>" CRLF
+"<head><title>400 Bad Request</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+;
+
+
+static char error_403_page[] =
+"<html>" CRLF
+"<head><title>403 Forbidden</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>403 Forbidden</h1></center>" CRLF
+;
+
+
+static char error_404_page[] =
+"<html>" CRLF
+"<head><title>404 Not Found</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>404 Not Found</h1></center>" CRLF
+;
+
+
+static char error_405_page[] =
+"<html>" CRLF
+"<head><title>405 Not Allowed</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>405 Not Allowed</h1></center>" CRLF
+;
+
+
+static char error_408_page[] =
+"<html>" CRLF
+"<head><title>408 Request Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>408 Request Time-out</h1></center>" CRLF
+;
+
+
+static char error_413_page[] =
+"<html>" CRLF
+"<head><title>413 Request Entity Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>413 Request Entity Too Large</h1></center>" CRLF
+;
+
+
+static char error_414_page[] =
+"<html>" CRLF
+"<head><title>414 Request-URI Too Large</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>414 Request-URI Too Large</h1></center>" CRLF
+;
+
+
+static char error_416_page[] =
+"<html>" CRLF
+"<head><title>416 Requested Range Not Satisfiable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>416 Requested Range Not Satisfiable</h1></center>" CRLF
+;
+
+
+static char error_497_page[] =
+"<html>" CRLF
+"<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>"
+CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>400 Bad Request</h1></center>" CRLF
+"<center>The plain HTTP request was sent to HTTPS port</center>" CRLF
+;
+
+
+static char error_500_page[] =
+"<html>" CRLF
+"<head><title>500 Internal Server Error</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>500 Internal Server Error</h1></center>" CRLF
+;
+
+
+static char error_501_page[] =
+"<html>" CRLF
+"<head><title>501 Method Not Implemented</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>500 Method Not Implemented</h1></center>" CRLF
+;
+
+
+static char error_502_page[] =
+"<html>" CRLF
+"<head><title>502 Bad Gateway</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>502 Bad Gateway</h1></center>" CRLF
+;
+
+
+static char error_503_page[] =
+"<html>" CRLF
+"<head><title>503 Service Temporarily Unavailable</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>503 Service Temporarily Unavailable</h1></center>" CRLF
+;
+
+
+static char error_504_page[] =
+"<html>" CRLF
+"<head><title>504 Gateway Time-out</title></head>" CRLF
+"<body bgcolor=\"white\">" CRLF
+"<center><h1>504 Gateway Time-out</h1></center>" CRLF
+;
+
+
+static ngx_str_t error_pages[] = {
+ /* ngx_null_string, */          /* 300 */
+    ngx_string(error_301_page),
+    ngx_string(error_302_page),
+    ngx_null_string,             /* 303 */
+
+    ngx_string(error_400_page),
+    ngx_null_string,             /* 401 */
+    ngx_null_string,             /* 402 */
+    ngx_string(error_403_page),
+    ngx_string(error_404_page),
+    ngx_string(error_405_page),
+    ngx_null_string,             /* 406 */
+    ngx_null_string,             /* 407 */
+    ngx_string(error_408_page),
+    ngx_null_string,             /* 409 */
+    ngx_null_string,             /* 410 */
+    ngx_null_string,             /* 411 */
+    ngx_null_string,             /* 412 */
+    ngx_string(error_413_page),
+    ngx_string(error_414_page),
+    ngx_null_string,             /* 415 */
+    ngx_string(error_416_page),
+
+    ngx_string(error_497_page),  /* 497, http to https */
+    ngx_string(error_404_page),  /* 498, invalid host name */
+    ngx_null_string,             /* 499, client closed connection */
+
+    ngx_string(error_500_page),
+    ngx_string(error_501_page),
+    ngx_string(error_502_page),
+    ngx_string(error_503_page),
+    ngx_string(error_504_page)
+};
+
+
+ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, int error)
+{
+    ngx_int_t                  rc;
+    ngx_uint_t                 err, i, msie_padding;
+    ngx_buf_t                 *b;
+    ngx_chain_t               *out, **ll, *cl;
+    ngx_http_err_page_t       *err_page;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    rc = ngx_http_discard_body(r);
+
+    if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
+        error = NGX_HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    r->headers_out.status = error;
+
+    if (r->keepalive != 0) {
+        switch (error) {
+            case NGX_HTTP_BAD_REQUEST:
+            case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE:
+            case NGX_HTTP_REQUEST_URI_TOO_LARGE:
+            case NGX_HTTP_TO_HTTPS:
+            case NGX_HTTP_INTERNAL_SERVER_ERROR:
+                r->keepalive = 0;
+        }
+    }
+
+    if (r->lingering_close == 1) {
+        switch (error) {
+            case NGX_HTTP_BAD_REQUEST:
+            case NGX_HTTP_TO_HTTPS:
+                r->lingering_close = 0;
+        }
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (r->err_ctx == NULL && clcf->error_pages) {
+        err_page = clcf->error_pages->elts;
+        for (i = 0; i < clcf->error_pages->nelts; i++) {
+            if (err_page[i].status == error) {
+                if (err_page[i].overwrite) {
+                    r->err_status = err_page[i].overwrite;
+                } else {
+                    r->err_status = error;
+                }
+                r->err_ctx = r->ctx;
+                return ngx_http_internal_redirect(r, &err_page[i].uri, NULL);
+            }
+        }
+    }
+
+    if (error < NGX_HTTP_BAD_REQUEST) {
+        /* 3XX */
+        err = error - NGX_HTTP_MOVED_PERMANENTLY;
+
+    } else if (error < NGX_HTTP_NGX_CODES) {
+        /* 4XX */
+        err = error - NGX_HTTP_BAD_REQUEST + 3;
+
+    } else {
+        /* 49X, 5XX */
+        err = error - NGX_HTTP_NGX_CODES + 3 + 17;
+
+        switch (error) {
+            case NGX_HTTP_TO_HTTPS:
+                r->headers_out.status = NGX_HTTP_BAD_REQUEST;
+                error = NGX_HTTP_BAD_REQUEST;
+                break;
+
+            case NGX_HTTP_INVALID_HOST:
+                r->headers_out.status = NGX_HTTP_NOT_FOUND;
+                error = NGX_HTTP_NOT_FOUND;
+                break;
+        }
+    }
+
+    msie_padding = 0;
+
+    if (error_pages[err].len) {
+        r->headers_out.content_length_n = error_pages[err].len
+                                          + sizeof(error_tail) - 1;
+
+        if (clcf->msie_padding
+            && r->headers_in.msie
+            && r->http_version >= NGX_HTTP_VERSION_10
+            && error >= NGX_HTTP_BAD_REQUEST
+            && error != NGX_HTTP_REQUEST_URI_TOO_LARGE)
+        {
+            r->headers_out.content_length_n += sizeof(msie_stub) - 1;
+            msie_padding = 1;
+        }
+
+        r->headers_out.content_type = ngx_list_push(&r->headers_out.headers);
+        if (r->headers_out.content_type == NULL) {
+            return NGX_ERROR;
+        }
+
+        r->headers_out.content_type->key.len = sizeof("Content-Type") - 1;
+        r->headers_out.content_type->key.data = (u_char *) "Content-Type";
+        r->headers_out.content_type->value.len = sizeof("text/html") - 1;
+        r->headers_out.content_type->value.data = (u_char *) "text/html";
+
+    } else {
+        r->headers_out.content_length_n = -1;
+    }
+
+    if (r->headers_out.content_length) {
+        r->headers_out.content_length->key.len = 0;
+        r->headers_out.content_length = NULL;
+    }
+
+    rc = ngx_http_send_header(r);
+
+    if (rc == NGX_ERROR || r->header_only) {
+        return rc;
+    }
+
+    if (error_pages[err].len == 0) {
+        return NGX_OK;
+    }
+
+    out = NULL;
+    ll = NULL;
+
+    if (!(b = ngx_calloc_buf(r->pool))) {
+        return NGX_ERROR;
+    }
+    b->memory = 1;
+    b->pos = error_pages[err].data;
+    b->last = error_pages[err].data + error_pages[err].len;
+
+    ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+    ngx_chain_add_link(out, ll, cl);
+
+
+    if (!(b = ngx_calloc_buf(r->pool))) {
+        return NGX_ERROR;
+    }
+    b->memory = 1;
+    b->pos = error_tail;
+    b->last = error_tail + sizeof(error_tail) - 1;
+
+    ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+    ngx_chain_add_link(out, ll, cl);
+
+    if (msie_padding) {
+        if (!(b = ngx_calloc_buf(r->pool))) {
+            return NGX_ERROR;
+        }
+        b->memory = 1;
+        b->pos = msie_stub;
+        b->last = msie_stub + sizeof(msie_stub) - 1;
+
+        ngx_alloc_link_and_set_buf(cl, b, r->pool, NGX_ERROR);
+        ngx_chain_add_link(out, ll, cl);
+    }
+
+    b->last_buf = 1;
+
+    return ngx_http_output_filter(r, out);
+}
new file mode 100644
--- /dev/null
+++ b/src/http/ngx_http_write_filter.c
@@ -0,0 +1,166 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_http.h>
+
+
+typedef struct {
+    ngx_chain_t  *out;
+} ngx_http_write_filter_ctx_t;
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_cycle_t *cycle);
+
+
+ngx_http_module_t  ngx_http_write_filter_module_ctx = {
+    NULL,                                  /* pre conf */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    NULL,                                  /* create server configuration */
+    NULL,                                  /* merge server configuration */
+
+    NULL,                                  /* create location configuration */
+    NULL,                                  /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_write_filter_module = {
+    NGX_MODULE,
+    &ngx_http_write_filter_module_ctx,     /* module context */
+    NULL,                                  /* module directives */
+    NGX_HTTP_MODULE,                       /* module type */
+    ngx_http_write_filter_init,            /* init module */
+    NULL                                   /* init process */
+};
+
+
+ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+    int                           last;
+    off_t                         size, flush, sent;
+    ngx_chain_t                  *cl, *ln, **ll, *chain;
+    ngx_connection_t             *c;
+    ngx_http_core_loc_conf_t     *clcf;
+    ngx_http_write_filter_ctx_t  *ctx;
+
+    ctx = ngx_http_get_module_ctx(r->main ? r->main : r,
+                                  ngx_http_write_filter_module);
+
+    if (ctx == NULL) {
+        ngx_http_create_ctx(r, ctx, ngx_http_write_filter_module,
+                            sizeof(ngx_http_write_filter_ctx_t), NGX_ERROR);
+    }
+
+    size = 0;
+    flush = 0;
+    last = 0;
+    ll = &ctx->out;
+
+    /* find the size, the flush point and the last link of the saved chain */
+
+    for (cl = ctx->out; cl; cl = cl->next) {
+        ll = &cl->next;
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = size;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    /* add the new chain to the existent one */
+
+    for (ln = in; ln; ln = ln->next) {
+        ngx_alloc_link_and_set_buf(cl, ln->buf, r->pool, NGX_ERROR);
+        *ll = cl;
+        ll = &cl->next;
+
+        size += ngx_buf_size(cl->buf);
+
+        if (cl->buf->flush || cl->buf->recycled) {
+            flush = size;
+        }
+
+        if (cl->buf->last_buf) {
+            last = 1;
+        }
+    }
+
+    c = r->connection;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http write filter: l:%d f:" OFF_T_FMT " s:" OFF_T_FMT,
+                   last, flush, size);
+
+    clcf = ngx_http_get_module_loc_conf(r->main ? r->main : r,
+                                        ngx_http_core_module);
+
+    /*
+     * avoid the output if there is no last buf, no flush point,
+     * there are the incoming bufs and the size of all bufs
+     * is smaller than "postpone_output" directive
+     */
+
+    if (!last && flush == 0 && in && size < (off_t) clcf->postpone_output) {
+        return NGX_OK;
+    }
+
+    if (c->write->delayed) {
+        return NGX_AGAIN;
+    }
+
+    if (size == 0 && !c->buffered) {
+        if (!last) {
+            ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                          "the http output chain is empty");
+        }
+        return NGX_OK;
+    }
+
+    sent = c->sent;
+
+    chain = c->send_chain(c, ctx->out,
+                          clcf->limit_rate ? clcf->limit_rate: OFF_T_MAX_VALUE);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http write filter %X", chain);
+
+    if (clcf->limit_rate) {
+        sent = c->sent - sent;
+        c->write->delayed = 1;
+        ngx_add_timer(r->connection->write,
+                      (ngx_msec_t) (sent * 1000 / clcf->limit_rate));
+    }
+
+    if (chain == NGX_CHAIN_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ctx->out = chain;
+
+    if (chain || c->buffered) {
+        return NGX_AGAIN;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_cycle_t *cycle)
+{
+    ngx_http_top_body_filter = ngx_http_write_filter;
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_aio.h
@@ -0,0 +1,21 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_AIO_H_INCLUDED_
+#define _NGX_AIO_H_INCLUDED_
+
+
+#include <ngx_core.h>
+
+
+ssize_t ngx_aio_read(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_aio_read_chain(ngx_connection_t *c, ngx_chain_t *cl);
+ssize_t ngx_aio_write(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_aio_write_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                 off_t limit);
+
+
+#endif /* _NGX_AIO_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_aio_read.c
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_aio.h>
+
+#if (HAVE_KQUEUE)
+#include <ngx_kqueue_module.h>
+#endif
+
+
+/*
+ * the ready data requires 3 syscalls:
+ *     aio_write(), aio_error(), aio_return()
+ * the non-ready data requires 4 (kqueue) or 5 syscalls:
+ *     aio_write(), aio_error(), notifiction, aio_error(), aio_return()
+ *                               timeout, aio_cancel(), aio_error()
+ */
+
+ssize_t ngx_aio_read(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    int           n;
+    ngx_event_t  *rev;
+
+    rev = c->read;
+
+    if (!rev->ready) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second aio post");
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "rev->complete: %d", rev->complete);
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "aio size: %d", size);
+
+    if (!rev->complete) {
+        ngx_memzero(&rev->aiocb, sizeof(struct aiocb));
+
+        rev->aiocb.aio_fildes = c->fd;
+        rev->aiocb.aio_buf = buf;
+        rev->aiocb.aio_nbytes = size;
+
+#if (HAVE_KQUEUE)
+        rev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+        rev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+        rev->aiocb.aio_sigevent.sigev_value.sigval_ptr = rev;
+#endif
+
+        if (aio_read(&rev->aiocb) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, rev->log, ngx_errno,
+                          "aio_read() failed");
+            rev->error = 1;
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "aio_read: #%d OK", c->fd);
+
+        rev->active = 1;
+        rev->ready = 0;
+    }
+
+    rev->complete = 0;
+
+    n = aio_error(&rev->aiocb);
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "aio_error() failed");
+        rev->error = 1;
+        return NGX_ERROR;
+    }
+
+    if (n != 0) {
+        if (n == NGX_EINPROGRESS) {
+            if (rev->ready) {
+                ngx_log_error(NGX_LOG_ALERT, c->log, n,
+                              "aio_read() still in progress");
+                rev->ready = 0;
+            }
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, c->log, n, "aio_read() failed");
+        rev->error = 1;
+        rev->ready = 0;
+        return NGX_ERROR;
+    }
+
+    n = aio_return(&rev->aiocb);
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+                      "aio_return() failed");
+
+        rev->error = 1;
+        rev->ready = 0;
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+                   "aio_read: #%d %d", c->fd, n);
+
+    if (n == 0) {
+        rev->eof = 1;
+        rev->ready = 0;
+    } else {
+        rev->ready = 1;
+    }
+
+    rev->active = 0;
+
+    return n;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_aio_read_chain.c
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_aio.h>
+
+
+ssize_t ngx_aio_read_chain(ngx_connection_t *c, ngx_chain_t *cl)
+{
+    int           n;
+    u_char       *buf, *prev;
+    size_t        size;
+    ssize_t       total;
+    ngx_err_t     err;
+
+    if (c->read->pending_eof) {
+        c->read->ready = 0;
+        return 0;
+    }
+
+    total = 0;
+
+    while (cl) {
+
+        /* we can post the single aio operation only */
+
+        if (!c->read->ready) {
+            return total ? total : NGX_AGAIN;
+        }
+
+        buf = cl->buf->last;
+        prev = cl->buf->last;
+        size = 0;
+
+        /* coalesce the neighbouring bufs */
+
+        while (cl && prev == cl->buf->last) {
+            size += cl->buf->end - cl->buf->last;
+            prev = cl->buf->end;
+            cl = cl->next;
+        }
+
+        n = ngx_aio_read(c, buf, size);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_read: %d", n);
+
+        if (n == NGX_AGAIN) {
+            return total ? total : NGX_AGAIN;
+        }
+
+        if (n == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+
+        if (n == 0) {
+            c->read->pending_eof = 1;
+            if (total) {
+                c->read->eof = 0;
+                c->read->ready = 1;
+            }
+            return total;
+        }
+
+        if (n > 0) {
+            total += n;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "aio_read total: %d", total);
+    }
+
+    return total ? total : NGX_AGAIN;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_aio_write.c
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_aio.h>
+
+#if (HAVE_KQUEUE)
+#include <ngx_kqueue_module.h>
+#endif
+
+
+/*
+ * the ready data requires 3 syscalls:
+ *     aio_write(), aio_error(), aio_return()
+ * the non-ready data requires 4 (kqueue) or 5 syscalls:
+ *     aio_write(), aio_error(), notifiction, aio_error(), aio_return()
+ *                               timeout, aio_cancel(), aio_error()
+ */
+
+ssize_t ngx_aio_write(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    int           n;
+    ngx_event_t  *wev;
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return NGX_AGAIN;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+                   "aio: wev->complete: %d", wev->complete);
+
+    if (!wev->complete) {
+        ngx_memzero(&wev->aiocb, sizeof(struct aiocb));
+
+        wev->aiocb.aio_fildes = c->fd;
+        wev->aiocb.aio_buf = buf;
+        wev->aiocb.aio_nbytes = size;
+
+#if (HAVE_KQUEUE)
+        wev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+        wev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+        wev->aiocb.aio_sigevent.sigev_value.sigval_ptr = wev;
+#endif
+
+        if (aio_write(&wev->aiocb) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno,
+                          "aio_write() failed");
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: OK");
+
+        wev->active = 1;
+        wev->ready = 0;
+    }
+
+    wev->complete = 0;
+
+    n = aio_error(&wev->aiocb);
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno, "aio_error() failed");
+        wev->error = 1;
+        return NGX_ERROR;
+    }
+
+    if (n != 0) {
+        if (n == NGX_EINPROGRESS) {
+            if (wev->ready) {
+                ngx_log_error(NGX_LOG_ALERT, wev->log, n,
+                              "aio_write() still in progress");
+                wev->ready = 0;
+            }
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_write() failed");
+        wev->error = 1;
+        wev->ready = 0;
+
+#if 1
+        n = aio_return(&wev->aiocb);
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+                          "aio_return() failed");
+        }
+
+        ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_return() %d", n);
+#endif
+
+        return NGX_ERROR;
+    }
+
+    n = aio_return(&wev->aiocb);
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+                      "aio_return() failed");
+
+        wev->error = 1;
+        wev->ready = 0;
+        return NGX_ERROR;
+    }
+
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: %d", n);
+
+    wev->active = 0;
+    wev->ready = 1;
+
+    return n;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_aio_write_chain.c
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_aio.h>
+
+
+ngx_chain_t *ngx_aio_write_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                 off_t limit)
+{
+    int           n;
+    u_char       *buf, *prev;
+    off_t         send, sent;
+    size_t        len;
+    ssize_t       size;
+    ngx_err_t     err;
+    ngx_chain_t  *cl;
+
+    send = 0;
+    sent = 0;
+    cl = in;
+
+    while (cl) {
+
+        if (cl->buf->pos == cl->buf->last) {
+            cl = cl->next;
+            continue;
+        }
+
+        /* we can post the single aio operation only */
+
+        if (!c->write->ready) {
+            return cl;
+        }
+
+        buf = cl->buf->pos;
+        prev = buf;
+        len = 0;
+
+        /* coalesce the neighbouring bufs */
+
+        while (cl && prev == cl->buf->pos && send < limit) {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            size = cl->buf->last - cl->buf->pos;
+
+            if (send + size > limit) {
+                size = limit - send;
+            }
+
+            len += size;
+            prev = cl->buf->pos + size;
+            send += size;
+            cl = cl->next;
+        }
+
+        n = ngx_aio_write(c, buf, len);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_write: %d", n);
+
+        if (n == NGX_ERROR) {
+            return NGX_CHAIN_ERROR;
+        }
+
+        if (n > 0) {
+            sent += n;
+            c->sent += n;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "aio_write sent: " OFF_T_FMT, c->sent);
+
+        for (cl = in; cl; cl = cl->next) {
+
+            if (sent >= cl->buf->last - cl->buf->pos) {
+                sent -= cl->buf->last - cl->buf->pos;
+                cl->buf->pos = cl->buf->last;
+
+                continue;
+            }
+
+            cl->buf->pos += sent;
+
+            break;
+        }
+    }
+
+    return cl;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_alloc.c
@@ -0,0 +1,80 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+int ngx_pagesize;
+
+
+void *ngx_alloc(size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    if (!(p = malloc(size))) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "malloc() " SIZE_T_FMT " bytes failed", size);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0,
+                   "malloc: " PTR_FMT ":" SIZE_T_FMT, p, size);
+
+    return p;
+}
+
+
+void *ngx_calloc(size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    p = ngx_alloc(size, log);
+
+    if (p) {
+        ngx_memzero(p, size);
+    }
+
+    return p;
+}
+
+
+#if (HAVE_POSIX_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    if (posix_memalign(&p, alignment, size) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "posix_memalign() " SIZE_T_FMT " bytes aligned to "
+                      SIZE_T_FMT " failed", size, alignment);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0,
+                   "posix_memalign: " PTR_FMT ":" SIZE_T_FMT, p, size);
+
+    return p;
+}
+
+#elif (HAVE_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    if (!(p = memalign(alignment, size))) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "memalign() " SIZE_T_FMT " bytes aligned to "
+                      SIZE_T_FMT " failed", size, alignment);
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0,
+                   "memalign: " PTR_FMT ":" SIZE_T_FMT, p, size);
+
+    return p;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_alloc.h
@@ -0,0 +1,42 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ALLOC_H_INCLUDED_
+#define _NGX_ALLOC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *ngx_alloc(size_t size, ngx_log_t *log);
+void *ngx_calloc(size_t size, ngx_log_t *log);
+
+#define ngx_free          free
+
+
+/*
+ * Linux has memalign() or posix_memalign()
+ * Solaris has memalign()
+ * FreeBSD has not memalign() or posix_memalign() but its malloc() alignes
+ * allocations bigger than page size at the page boundary.
+ */
+
+#if (HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
+
+#else
+
+#define ngx_memalign(alignment, size, log)  ngx_alloc(size, log)
+
+#endif
+
+
+extern int ngx_pagesize;
+
+
+#endif /* _NGX_ALLOC_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_atomic.h
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ATOMIC_H_INCLUDED_
+#define _NGX_ATOMIC_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if ( __i386__ || __amd64__ )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+typedef volatile uint32_t  ngx_atomic_t;
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK  "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+static ngx_inline uint32_t ngx_atomic_inc(ngx_atomic_t *value)
+{
+    uint32_t  old;
+
+    __asm__ volatile (
+
+        NGX_SMP_LOCK
+    "   xaddl  %0, %2;   "
+    "   incl   %0;       "
+
+    : "=q" (old) : "0" (1), "m" (*value));
+
+    return old;
+}
+
+
+#if 0
+
+static ngx_inline uint32_t ngx_atomic_dec(ngx_atomic_t *value)
+{
+    uint32_t  old;
+
+    __asm__ volatile (
+
+        NGX_SMP_LOCK
+    "   xaddl  %0, %1;   "
+    "   decl   %0;       "
+
+    : "=q" (old) : "0" (-1), "m" (*value));
+
+    return old;
+}
+
+#endif
+
+
+static ngx_inline uint32_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+                                              ngx_atomic_t old,
+                                              ngx_atomic_t set)
+{
+    uint32_t  res;
+
+    __asm__ volatile (
+
+        NGX_SMP_LOCK
+    "   cmpxchgl  %3, %1;   "
+    "   setz      %%al;     "
+    "   movzbl    %%al, %0; "
+
+    : "=a" (res) : "m" (*lock), "a" (old), "q" (set));
+
+    return res;
+}
+
+
+#elif ( __sparc__ )
+
+#define NGX_HAVE_ATOMIC_OPS  1
+
+typedef volatile uint32_t  ngx_atomic_t;
+
+
+static ngx_inline uint32_t ngx_atomic_inc(ngx_atomic_t *value)
+{
+    uint32_t  old, new, res;
+
+    old = *value;
+
+    for ( ;; ) {
+
+        new = old + 1;
+        res = new;
+
+        __asm__ volatile (
+
+        "casa [%1] 0x80, %2, %0"
+
+        : "+r" (res) : "r" (value), "r" (old));
+
+        if (res == old) {
+            return new;
+        }
+
+        old = res;
+    }
+}
+
+
+static ngx_inline uint32_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+                                              ngx_atomic_t old,
+                                              ngx_atomic_t set)
+{
+    uint32_t  res = (uint32_t) set;
+
+    __asm__ volatile (
+
+    "casa [%1] 0x80, %2, %0"
+
+    : "+r" (res) : "r" (lock), "r" (old));
+
+    return (res == old);
+}
+
+#else
+
+#define NGX_HAVE_ATOMIC_OPS  0
+
+typedef volatile uint32_t  ngx_atomic_t;
+
+#define ngx_atomic_inc(x)  ++(*(x));
+
+static ngx_inline uint32_t ngx_atomic_cmp_set(ngx_atomic_t *lock,
+                                              ngx_atomic_t old,
+                                              ngx_atomic_t set)
+{
+     return 1;
+}
+
+#endif
+
+
+void ngx_spinlock(ngx_atomic_t *lock, ngx_uint_t spin);
+
+#define ngx_trylock(lock)  (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
+#define ngx_unlock(lock)    *(lock) = 0
+
+
+#endif /* _NGX_ATOMIC_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_channel.c
@@ -0,0 +1,237 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_channel.h>
+
+
+ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+                            ngx_log_t *log) 
+{
+    ssize_t             n;
+    ngx_err_t           err;
+    struct iovec        iov[1];
+    struct msghdr       msg;
+
+#if (HAVE_MSGHDR_MSG_CONTROL)
+
+    union {
+        struct cmsghdr  cm;
+        char            space[CMSG_SPACE(sizeof(int))];
+    } cmsg;
+
+    if (ch->fd == -1) {
+        msg.msg_control = NULL;
+        msg.msg_controllen = 0;
+
+    } else {
+        msg.msg_control = (caddr_t) &cmsg;
+        msg.msg_controllen = sizeof(cmsg);
+
+        cmsg.cm.cmsg_len = sizeof(cmsg);
+        cmsg.cm.cmsg_level = SOL_SOCKET; 
+        cmsg.cm.cmsg_type = SCM_RIGHTS;
+        *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
+    }
+
+#else
+
+    if (ch->fd == -1) {
+        msg.msg_accrights = NULL;
+        msg.msg_accrightslen = 0;
+
+    } else {
+        msg.msg_accrights = (caddr_t) &ch->fd;
+        msg.msg_accrightslen = sizeof(int);
+    }
+
+#endif
+
+    iov[0].iov_base = (char *) ch;
+    iov[0].iov_len = size;
+
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+    n = sendmsg(s, &msg, 0);
+
+    if (n == -1) {
+        err = ngx_errno;
+        if (err == NGX_EAGAIN) {
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+                           ngx_log_t *log)
+{   
+    ssize_t             n;
+    ngx_err_t           err;
+    struct iovec        iov[1];
+    struct msghdr       msg;
+
+#if (HAVE_MSGHDR_MSG_CONTROL)
+    union {
+        struct cmsghdr  cm;
+        char            space[CMSG_SPACE(sizeof(int))];
+    } cmsg;
+#else
+    int                 fd;
+#endif
+
+    iov[0].iov_base = (char *) ch;
+    iov[0].iov_len = size;
+
+    msg.msg_name = NULL;
+    msg.msg_namelen = 0;
+    msg.msg_iov = iov;
+    msg.msg_iovlen = 1;
+
+#if (HAVE_MSGHDR_MSG_CONTROL)
+    msg.msg_control = (caddr_t) &cmsg;
+    msg.msg_controllen = sizeof(cmsg);
+#else
+    msg.msg_accrights = (caddr_t) &fd;
+    msg.msg_accrightslen = sizeof(int);
+#endif
+
+    n = recvmsg(s, &msg, 0);
+
+    if (n == -1) {
+        err = ngx_errno;
+        if (err == NGX_EAGAIN) {
+            return NGX_AGAIN;
+        }
+
+        ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n < sizeof(ngx_channel_t)) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "recvmsg() returned not enough data");
+        return NGX_ERROR;
+    }
+
+#if (HAVE_MSGHDR_MSG_CONTROL)
+
+    if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+
+        if (cmsg.cm.cmsg_len < sizeof(cmsg)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "recvmsg() returned too small ancillary data");
+            return NGX_ERROR;
+        }
+
+        if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
+        {
+            ngx_log_error(NGX_LOG_ALERT, log, 0,
+                          "recvmsg() returned invalid ancillary data "
+                          "level %d or type %d",
+                          cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
+            return NGX_ERROR;
+        }
+
+        ch->fd = *(int *) CMSG_DATA(&cmsg.cm);
+    }
+
+    if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0,
+                      "recvmsg() truncated data");
+    }
+
+#else
+
+    if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+        if (msg.msg_accrightslen != sizeof(int)) {
+            ngx_log_error(NGX_LOG_ALERT, log, 0, 
+                          "recvmsg() returned no ancillary data");
+            return NGX_ERROR;
+        }
+
+        ch->fd = fd;
+    }
+
+#endif
+
+    return n;
+}
+
+
+ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
+                                ngx_int_t event, ngx_event_handler_pt handler)
+{
+    ngx_event_t       *ev, *rev, *wev;
+    ngx_connection_t  *c;
+
+    c = &cycle->connections[fd];
+    rev = &cycle->read_events[fd];
+    wev = &cycle->write_events[fd];
+
+    ngx_memzero(c, sizeof(ngx_connection_t));
+    ngx_memzero(rev, sizeof(ngx_event_t));
+    ngx_memzero(wev, sizeof(ngx_event_t));
+
+    c->fd = fd;
+    c->pool = cycle->pool;
+
+    c->read = rev;
+    c->write = wev;
+
+    c->log = cycle->log;
+    rev->log = cycle->log;
+    wev->log = cycle->log;
+    rev->index = NGX_INVALID_INDEX;
+    wev->index = NGX_INVALID_INDEX;
+    rev->data = c;
+    wev->data = c;
+
+#if (NGX_THREADS)
+    rev->lock = &c->lock;
+    wev->lock = &c->lock;
+    rev->own_lock = &c->lock;
+    wev->own_lock = &c->lock;
+#endif
+
+    ev = (event == NGX_READ_EVENT) ? rev : wev;
+
+    ev->event_handler = handler;
+
+    if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+        if (ngx_add_conn(c) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    
+    } else { 
+        if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
+void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
+{
+    if (close(fd[0]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() failed");
+    }
+
+    if (close(fd[1]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() failed");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_channel.h
@@ -0,0 +1,33 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_CHANNEL_H_INCLUDED_
+#define _NGX_CHANNEL_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+typedef struct {
+     ngx_uint_t  command;
+     ngx_pid_t   pid;
+     ngx_int_t   slot;
+     ngx_fd_t    fd;
+} ngx_channel_t;
+
+
+ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+                            ngx_log_t *log);
+ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+                           ngx_log_t *log);
+ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
+                                ngx_int_t event, ngx_event_handler_pt handler);
+void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);
+
+
+#endif /* _NGX_CHANNEL_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_daemon.c
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+int ngx_daemon(ngx_log_t *log)
+{
+    int  fd;
+
+    switch (fork()) {
+    case -1:
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
+        return NGX_ERROR;
+
+    case 0:
+        break;
+
+    default:
+        exit(0);
+    }
+
+    ngx_pid = ngx_getpid();
+
+    if (setsid() == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
+        return NGX_ERROR;
+    }
+
+    umask(0);
+
+    fd = open("/dev/null", O_RDWR);
+    if (fd == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                      "open(\"/dev/null\") failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(fd, STDIN_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(fd, STDOUT_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
+        return NGX_ERROR;
+    }
+
+#if 0
+    if (dup2(fd, STDERR_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
+        return NGX_ERROR;
+    }
+#endif
+
+    if (fd > STDERR_FILENO) {
+        if (close(fd) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_errno.c
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (HAVE_STRERROR_R)
+
+ngx_int_t ngx_strerror_r(int err, char *errstr, size_t size)
+{
+    size_t  len;
+
+    if (size == 0) {
+        return 0;
+    }
+
+    errstr[0] = '\0';
+
+    strerror_r(err, errstr, size);
+
+    for (len = 0; len < size; len++) {
+        if (errstr[len] == '\0') {
+            break;
+        }
+    }
+
+    return len;
+}
+
+#elif (HAVE_GNU_STRERROR_R)
+
+/* Linux strerror_r() */
+
+ngx_int_t ngx_strerror_r(int err, char *errstr, size_t size)
+{
+    char    *str;
+    size_t   len;
+
+    if (size == 0) {
+        return 0;
+    }
+
+    errstr[0] = '\0';
+
+    str = strerror_r(err, errstr, size);
+
+    if (str != errstr) {
+        return ngx_cpystrn(errstr, str, size) - (u_char *) errstr;
+    }
+
+    for (len = 0; len < size; len++) {
+        if (errstr[len] == '\0') {
+            break;
+        }
+    }
+
+    return len;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_errno.h
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_ERRNO_H_INCLUDED_
+#define _NGX_ERRNO_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef int               ngx_err_t;
+
+#define NGX_ENOENT        ENOENT
+#define NGX_ESRCH         ESRCH
+#define NGX_EINTR         EINTR
+#define NGX_ECHILD        ECHILD
+#define NGX_ENOMEM        ENOMEM
+#define NGX_EACCES        EACCES
+#define NGX_EBUSY         EBUSY
+#define NGX_EEXIST        EEXIST
+#define NGX_ENOTDIR       ENOTDIR
+#define NGX_EINVAL        EINVAL
+#define NGX_EPIPE         EPIPE
+#define NGX_EAGAIN        EWOULDBLOCK
+#define NGX_EINPROGRESS   EINPROGRESS
+#define NGX_EADDRINUSE    EADDRINUSE
+#define NGX_ECONNABORTED  ECONNABORTED
+#define NGX_ECONNRESET    ECONNRESET
+#define NGX_ENOTCONN      ENOTCONN
+#define NGX_ETIMEDOUT     ETIMEDOUT
+#define NGX_ECONNREFUSED  ECONNREFUSED
+#define NGX_EHOSTUNREACH  EHOSTUNREACH
+#define NGX_ECANCELED     ECANCELED
+#define NGX_ENOMOREFILES  0
+
+
+
+#define ngx_errno                  errno
+#define ngx_socket_errno           errno
+#define ngx_set_errno(err)         errno = err
+#define ngx_set_socket_errno(err)  errno = err
+
+
+#if (HAVE_STRERROR_R || HAVE_GNU_STRERROR_R)
+
+ngx_int_t ngx_strerror_r(int err, char *errstr, size_t size);
+
+#else
+
+#define ngx_strerror_r(err, errstr, size)  \
+             (char *) ngx_cpystrn(errstr, strerror(err), size) - (errstr)
+
+#endif
+
+
+#endif /* _NGX_ERRNO_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_files.c
@@ -0,0 +1,268 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+    ssize_t n;
+
+    ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+                   "read: %d, %X, %d, " OFF_T_FMT, file->fd, buf, size, offset);
+
+#if (HAVE_PREAD)
+
+    n = pread(file->fd, buf, size, offset);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+                      "pread() failed, file \"%s\"", file->name.data);
+        return NGX_ERROR;
+    }
+
+#else
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "lseek() failed");
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+    n = read(file->fd, buf, size);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "read() failed");
+        return NGX_ERROR;
+    }
+
+    file->sys_offset += n;
+
+#endif
+
+    file->offset += n;
+
+    return n;
+}
+
+
+ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+    ssize_t n;
+
+#if (HAVE_PWRITE)
+
+    n = pwrite(file->fd, buf, size, offset);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "pwrite() failed");
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != size) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+                      "pwrite() has written only %d of %d", n, size);
+        return NGX_ERROR;
+    }
+
+#else
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "lseek() failed");
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+    n = write(file->fd, buf, size);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "write() failed");
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != size) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+                      "write() has written only %d of %d", n, size);
+        return NGX_ERROR;
+    }
+
+    file->sys_offset += n;
+
+#endif
+
+    file->offset += n;
+
+    return n;
+}
+
+
+int ngx_open_tempfile(u_char *name, ngx_uint_t persistent)
+{
+    ngx_fd_t  fd;
+
+    fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR, 0600);
+
+    if (fd != -1 && !persistent) {
+        unlink((const char *) name);
+    }
+
+    return fd;
+}
+
+
+ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl,
+                                off_t offset, ngx_pool_t *pool)
+{
+    u_char        *prev;
+    size_t         size;
+    ssize_t        n;
+    struct iovec  *iov;
+    ngx_err_t      err;
+    ngx_array_t    io;
+
+    /* use pwrite() if there's the only buf in a chain */
+
+    if (cl->next == NULL) {
+        return ngx_write_file(file, cl->buf->pos,
+                              (size_t) (cl->buf->last - cl->buf->pos),
+                              offset);
+    }
+
+    prev = NULL;
+    iov = NULL;
+    size = 0;
+
+    ngx_init_array(io, pool, 10, sizeof(struct iovec), NGX_ERROR);
+
+    /* create the iovec and coalesce the neighbouring bufs */
+
+    while (cl) {
+        if (prev == cl->buf->pos) {
+            iov->iov_len += cl->buf->last - cl->buf->pos;
+
+        } else {
+            ngx_test_null(iov, ngx_push_array(&io), NGX_ERROR);
+            iov->iov_base = (void *) cl->buf->pos;
+            iov->iov_len = cl->buf->last - cl->buf->pos;
+        }
+
+        size += cl->buf->last - cl->buf->pos;
+        prev = cl->buf->last;
+        cl = cl->next;
+    }
+
+    /* use pwrite() if there's the only iovec buffer */
+
+    if (io.nelts == 1) {
+        iov = io.elts;
+        return ngx_write_file(file, (u_char *) iov[0].iov_base, iov[0].iov_len,
+                              offset);
+    }
+
+    if (file->sys_offset != offset) {
+        if (lseek(file->fd, offset, SEEK_SET) == -1) {
+            ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "lseek() failed");
+            return NGX_ERROR;
+        }
+
+        file->sys_offset = offset;
+    }
+
+    n = writev(file->fd, io.elts, io.nelts);
+
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno, "writev() failed");
+        return NGX_ERROR;
+    }
+
+    if ((size_t) n != size) {
+        ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+                      "writev() has written only %d of %d", n, size);
+        return NGX_ERROR;
+    }
+
+    file->sys_offset += n;
+    file->offset += n;
+
+    return n;
+}
+
+
+int ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir)
+{
+    dir->dir = opendir((const char *) name->data);
+
+    if (dir->dir == NULL) {
+        return NGX_ERROR;
+    }
+
+    dir->info_valid = 0;
+
+    return NGX_OK;
+}
+
+
+#if 0
+
+ssize_t ngx_read_file(ngx_file_t *file, char *buf, size_t size, off_t offset)
+{
+    if (!file->read->ready) {
+
+        ngx_memzero(&file->iocb, sizeof(iocb));
+        file->iocb.aio_fildes = file->fd;
+        file->iocb.aio_buf = buf;
+        file->iocb.aio_nbytes = size;
+        file->iocb.aio_offset = offset;
+#if (USE_AIO_KQUEUE)
+        file->iocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+        file->iocb.aio_sigevent.sigev_notify_kqueue = tid->kq;
+        file->iocb.aio_sigevent.sigev_value = (union sigval) file;
+#endif
+#if (USE_AIO_SIGNAL)
+        file->iocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
+        file->iocb.aio_sigevent.sigev_signo = NGX_SIGAIO;
+#ifndef __FreeBSD__
+        file->iocb.aio_sigevent.sigev_value.sival_ptr = file;
+#endif
+#endif
+
+        if (aio_read(&file->iocb) == -1) {
+            ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno,
+                          "aio_read() failed");
+            return NGX_ERROR;
+
+        n = aio_error(&file->iocb);
+        if (n == EINPROGRESS)
+            return NGX_AGAIN;
+
+        if (n == -1) {
+            ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno,
+                          "aio_read() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    ngx_assert(file->iocb.aio_buf == buf), return NGX_ERROR,
+               "ngx_aio_read_file: another buffer is passed");
+
+    n = aio_return(&file->iocb);
+    if (n == -1) {
+        ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno,
+                      "aio_read() failed");
+        return NGX_ERROR;
+    }
+
+    return n;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_files.h
@@ -0,0 +1,115 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FILES_H_INCLUDED_
+#define _NGX_FILES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_INVALID_FILE         -1
+#define NGX_FILE_ERROR           -1
+
+
+
+#define ngx_open_file(name, access, create)                                 \
+                                 open((const char *) name, access|create, 0644)
+#define ngx_open_file_n          "open()"
+
+#define NGX_FILE_RDONLY          O_RDONLY
+#define NGX_FILE_RDWR            O_RDWR
+#define NGX_FILE_CREATE_OR_OPEN  O_CREAT
+#define NGX_FILE_OPEN            0
+#define NGX_FILE_TRUNCATE        O_TRUNC
+#define NGX_FILE_APPEND          O_APPEND
+
+
+#define ngx_close_file           close
+#define ngx_close_file_n         "close()"
+
+
+#define ngx_delete_file(name)    unlink((const char *) name)
+#define ngx_delete_file_n        "unlink()"
+
+
+int ngx_open_tempfile(u_char *name, ngx_uint_t persistent);
+#define ngx_open_tempfile_n      "open()"
+
+
+ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset);
+#define ngx_read_file_n          "read()"
+
+
+ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size,
+                       off_t offset);
+
+ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *ce,
+                                off_t offset, ngx_pool_t *pool);
+
+
+#define ngx_rename_file          rename
+#define ngx_rename_file_n        "rename"
+
+
+#define ngx_file_info(file, sb)  stat((const char *) file, sb)
+#define ngx_file_info_n          "stat()"
+
+#define ngx_fd_info(fd, sb)      fstat(fd, sb)
+#define ngx_fd_info_n            "fstat()"
+
+#define ngx_is_dir(sb)           (S_ISDIR((sb)->st_mode))
+#define ngx_is_file(sb)          (S_ISREG((sb)->st_mode))
+#define ngx_file_size(sb)        (sb)->st_size
+#define ngx_file_mtime(sb)       (sb)->st_mtime
+#define ngx_file_uniq(sb)        (sb)->st_ino
+
+
+
+#define ngx_getcwd(buf, size)    (getcwd(buf, size) != NULL)
+#define ngx_getcwd_n             "getcwd()"
+#define NGX_MAX_PATH             PATH_MAX
+
+#define NGX_DIR_MASK_LEN         0
+
+
+int ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir);
+#define ngx_open_dir_n           "opendir()"
+
+
+#define ngx_close_dir(d)         closedir((d)->dir)
+#define ngx_close_dir_n          "closedir()"
+
+
+#define ngx_read_dir(d)                                                      \
+                           (((d)->de = readdir((d)->dir)) ? NGX_OK : NGX_ERROR)
+#define ngx_read_dir_n           "readdir()"
+
+
+#define ngx_create_dir(name)     mkdir((const char *) name, 0700)
+#define ngx_create_dir_n         "mkdir()"
+
+
+#define ngx_delete_dir(name)     rmdir((const char *) name)
+#define ngx_delete_dir_n         "rmdir()"
+
+
+#define ngx_de_name(dir)         (dir)->de->d_name
+#ifdef __FreeBSD__
+#define ngx_de_namelen(dir)      (dir)->de->d_namlen
+#else
+#define ngx_de_namelen(dir)      ngx_strlen((dir)->de->d_name)
+#endif
+#define ngx_de_info(name, dir)   stat((const char *) name, &(dir)->info)
+#define ngx_de_info_n            "stat()"
+#define ngx_de_is_dir(dir)       (S_ISDIR((dir)->info.st_mode))
+#define ngx_de_is_file(dir)      (S_ISREG((dir)->info.st_mode))
+#define ngx_de_size(dir)         (dir)->info.st_size
+#define ngx_de_mtime(dir)        (dir)->info.st_mtime
+
+
+#endif /* _NGX_FILES_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd.h
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_H_INCLUDED_
+#define _NGX_FREEBSD_H_INCLUDED_
+
+
+ngx_chain_t *ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                        off_t limit);
+
+
+extern int ngx_freebsd_kern_osreldate;
+extern int ngx_freebsd_hw_ncpu;
+extern int ngx_freebsd_net_inet_tcp_sendspace;
+extern int ngx_freebsd_kern_ipc_zero_copy_send;
+
+extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+extern ngx_uint_t ngx_freebsd_use_tcp_nopush;
+
+
+#endif /* _NGX_FREEBSD_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_config.h
@@ -0,0 +1,124 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_CONFIG_H_INCLUDED_
+#define _NGX_FREEBSD_CONFIG_H_INCLUDED_
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+
+#include <sys/uio.h>
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <libutil.h>            /* setproctitle() before 4.1 */
+#include <osreldate.h>
+#include <sys/sysctl.h>
+#include <netinet/tcp.h>        /* TCP_NOPUSH */
+
+
+#if __FreeBSD_version < 400017
+
+#include <sys/param.h>          /* ALIGN() */
+
+/* FreeBSD 3.x has no CMSG_SPACE() at all and has the broken CMSG_DATA() */
+
+#undef  CMSG_SPACE
+#define CMSG_SPACE(l)       (ALIGN(sizeof(struct cmsghdr)) + ALIGN(l))
+
+#undef  CMSG_DATA
+#define CMSG_DATA(cmsg)     ((u_char *)(cmsg) + ALIGN(sizeof(struct cmsghdr)))
+
+#endif
+
+
+#include <ngx_auto_config.h>
+
+
+#ifndef HAVE_SELECT
+#define HAVE_SELECT  1
+#endif
+
+
+#ifndef HAVE_POLL
+#define HAVE_POLL  1
+#endif
+#if (HAVE_POLL)
+#include <poll.h>
+#endif
+
+       /* FreeBSD aio supported via kqueue */
+
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 430000) \
+    || __FreeBSD_version >= 500014
+
+#ifndef HAVE_AIO
+#define HAVE_AIO  1
+#endif
+
+#endif
+
+#if (HAVE_AIO)
+#include <aio.h>
+#endif
+
+
+#if defined SO_ACCEPTFILTER && !defined HAVE_DEFERRED_ACCEPT
+#define HAVE_DEFERRED_ACCEPT  1
+#endif
+
+
+#if (HAVE_KQUEUE)
+#include <sys/event.h>
+#endif
+
+
+#if (__FreeBSD_version < 430000 || __FreeBSD_version < 500012)
+
+pid_t rfork_thread(int flags, void *stack, int (*func)(void *arg), void *arg);
+
+#endif
+
+#ifndef IOV_MAX
+#define IOV_MAX   1024
+#endif
+
+
+#ifndef HAVE_INHERITED_NONBLOCK
+#define HAVE_INHERITED_NONBLOCK  1
+#endif
+
+
+#define ngx_setproctitle  setproctitle
+
+
+extern char *malloc_options;
+
+
+#endif /* _NGX_FREEBSD_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_init.c
@@ -0,0 +1,225 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/* FreeBSD 3.0 at least */
+char ngx_freebsd_kern_ostype[20];
+char ngx_freebsd_kern_osrelease[20];
+int ngx_freebsd_kern_osreldate;
+int ngx_freebsd_hw_ncpu;
+int ngx_freebsd_net_inet_tcp_sendspace;
+
+/* FreeBSD 4.9 */
+int ngx_freebsd_machdep_hlt_logical_cpus;
+
+/* FreeBSD 5.0 */
+int ngx_freebsd_kern_ipc_zero_copy_send;
+
+
+ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+ngx_uint_t ngx_freebsd_use_tcp_nopush;
+
+
+ngx_os_io_t ngx_os_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_unix_send,
+#if (HAVE_SENDFILE)
+    ngx_freebsd_sendfile_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+typedef struct {
+    char        *name;
+    int         *value;
+    size_t       size;
+    ngx_uint_t   exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+    { "hw.ncpu",
+      &ngx_freebsd_hw_ncpu,
+      sizeof(int), 0 },
+
+    { "machdep.hlt_logical_cpus",
+      &ngx_freebsd_machdep_hlt_logical_cpus,
+      sizeof(int), 0 },
+
+    { "net.inet.tcp.sendspace",
+      &ngx_freebsd_net_inet_tcp_sendspace,
+      sizeof(int), 0 },
+
+    { "kern.ipc.zero_copy.send",
+      &ngx_freebsd_kern_ipc_zero_copy_send,
+      sizeof(int), 0 },
+
+    { NULL, NULL, 0, 0 }
+};
+
+
+void ngx_debug_init()
+{
+#if (NGX_DEBUG && !NGX_NO_DEBUG_MALLOC)
+
+#if __FreeBSD_version >= 500014
+    _malloc_options = "J";
+#else
+    malloc_options = "J";
+#endif
+
+#endif
+}
+
+
+ngx_int_t ngx_os_init(ngx_log_t *log)
+{
+    int         version;
+    size_t      size;
+    ngx_err_t   err;
+    ngx_uint_t  i;
+
+    size = sizeof(ngx_freebsd_kern_ostype);
+    if (sysctlbyname("kern.ostype",
+                     ngx_freebsd_kern_ostype, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.ostype) failed");
+        return NGX_ERROR;
+    }
+
+    size = sizeof(ngx_freebsd_kern_osrelease);
+    if (sysctlbyname("kern.osrelease",
+                     ngx_freebsd_kern_osrelease, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.osrelease) failed");
+        return NGX_ERROR;
+    }
+
+
+    size = sizeof(int);
+    if (sysctlbyname("kern.osreldate",
+                     &ngx_freebsd_kern_osreldate, &size, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctlbyname(kern.osreldate) failed");
+        return NGX_ERROR;
+    }
+
+    version = ngx_freebsd_kern_osreldate;
+
+
+#if (HAVE_SENDFILE)
+
+    /*
+     * The determination of the sendfile() "nbytes bug" is complex enough.
+     * There are two sendfile() syscalls: a new #393 has no bug while
+     * an old #336 has the bug in some versions and has not in others.
+     * Besides libc_r wrapper also emulates the bug in some versions.
+     * There is no way to say exactly if syscall #336 in FreeBSD circa 4.6
+     * has the bug.  We use the algorithm that is correct at least for
+     * RELEASEs and for syscalls only (not libc_r wrapper).
+     *
+     * 4.6.1-RELEASE and below have the bug
+     * 4.6.2-RELEASE and above have the new syscall
+     *
+     * We detect the new sendfile() syscall available at the compile time
+     * to allow an old binary to run correctly on an updated FreeBSD system.
+     */
+
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 460102) \
+    || __FreeBSD_version == 460002 || __FreeBSD_version >= 500039
+
+    /* a new syscall without the bug */
+
+    ngx_freebsd_sendfile_nbytes_bug = 0;
+
+#else
+
+    /* an old syscall that may have the bug */
+
+    ngx_freebsd_sendfile_nbytes_bug = 1;
+
+#endif
+
+#endif /* HAVE_SENDFILE */
+
+
+    if ((version < 500000 && version >= 440003) || version >= 500017) {
+        ngx_freebsd_use_tcp_nopush = 1;
+    }
+
+
+    for (i = 0; sysctls[i].name; i++) {
+        *sysctls[i].value = 0;
+        size = sysctls[i].size;
+
+        if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+                                                                          == 0)
+        {
+            sysctls[i].exists = 1;
+            continue;
+        }
+
+        err = ngx_errno;
+
+        if (err == NGX_ENOENT) {
+            continue;
+        }
+
+#if 0
+        if (sysctls[i].value == &ngx_freebsd_machdep_hlt_logical_cpus) {
+            continue;
+        }
+#endif
+
+        ngx_log_error(NGX_LOG_ALERT, log, err,
+                      "sysctlbyname(%s) failed", sysctls[i].name);
+        return NGX_ERROR;
+    }
+
+    if (ngx_freebsd_machdep_hlt_logical_cpus) {
+        ngx_ncpu = ngx_freebsd_hw_ncpu / 2;
+    } else {
+        ngx_ncpu = ngx_freebsd_hw_ncpu;
+    }
+
+    return ngx_posix_init(log);
+}
+
+
+void ngx_os_status(ngx_log_t *log)
+{
+    ngx_uint_t  i;
+
+    ngx_log_error(NGX_LOG_INFO, log, 0, "OS: %s %s",
+                  ngx_freebsd_kern_ostype, ngx_freebsd_kern_osrelease);
+
+#ifdef __DragonFly_version
+    ngx_log_error(NGX_LOG_INFO, log, 0,
+                  "kern.osreldate: %d, built on %d",
+                  ngx_freebsd_kern_osreldate, __DragonFly_version);
+#else
+    ngx_log_error(NGX_LOG_INFO, log, 0,
+                  "kern.osreldate: %d, built on %d",
+                  ngx_freebsd_kern_osreldate, __FreeBSD_version);
+#endif
+
+    for (i = 0; sysctls[i].name; i++) {
+        if (sysctls[i].exists) {
+            ngx_log_error(NGX_LOG_INFO, log, 0, "%s: %d",
+                          sysctls[i].name, *sysctls[i].value);
+        }
+    }
+
+    ngx_posix_status(log);
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_rfork_thread.c
@@ -0,0 +1,738 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+/*
+ * The threads implementation uses the rfork(RFPROC|RFTHREAD|RFMEM) syscall
+ * to create threads.  All threads use the stacks of the same size mmap()ed
+ * below the main stack.  Thus the current thread id is determinated via
+ * the stack pointer value.
+ *
+ * The mutex implementation uses the ngx_atomic_cmp_set() operation
+ * to acquire a mutex and the SysV semaphore to wait on a mutex and to wake up
+ * the waiting threads.  The light mutex does not use semaphore, so after
+ * spinning in the lock the thread calls sched_yield().  However the light
+ * mutecies are intended to be used with the "trylock" operation only.
+ * The SysV semop() is a cheap syscall, particularly if it has little sembuf's
+ * and does not use SEM_UNDO.
+ *
+ * The condition variable implementation uses signal #64.  The signal handler
+ * is SIG_IGN so the kill() is a cheap syscall.  The thread waits a signal
+ * in kevent().  The use of the EVFILT_SIGNAL is safe since FreeBSD 4.7.
+ *
+ * This threads implementation currently works on i386 (486+) and amd64
+ * platforms only.
+ */
+
+
+char                 *ngx_freebsd_kern_usrstack;
+size_t                ngx_thread_stack_size;
+
+
+static size_t         rz_size;
+static size_t         usable_stack_size;
+static char          *last_stack;
+
+static ngx_uint_t     nthreads;
+static ngx_uint_t     max_threads;
+
+static ngx_uint_t     nkeys;
+static ngx_tid_t     *tids;      /* the threads tids array */
+void                **ngx_tls;   /* the threads tls's array */
+
+/* the thread-safe libc errno */
+
+static int   errno0;   /* the main thread's errno */
+static int  *errnos;   /* the threads errno's array */
+
+int *__error()
+{
+    int  tid;
+
+    tid = ngx_gettid();
+
+    return tid ? &errnos[tid - 1] : &errno0;
+}
+
+
+/*
+ * __isthreaded enables the spinlocks in some libc functions, i.e. in malloc()
+ * and some other places.  Nevertheless we protect our malloc()/free() calls
+ * by own mutex that is more efficient than the spinlock.
+ *
+ * _spinlock() is a weak referenced stub in src/lib/libc/gen/_spinlock_stub.c
+ * that does nothing.
+ */
+
+extern int  __isthreaded;
+
+void _spinlock(ngx_atomic_t *lock)
+{
+    ngx_int_t  tries;
+
+    tries = 0;
+
+    for ( ;; ) {
+
+        if (*lock) {
+            if (ngx_ncpu > 1 && tries++ < 1000) {
+                continue;
+            }
+
+            sched_yield();
+            tries = 0;
+
+        } else {
+            if (ngx_atomic_cmp_set(lock, 0, 1)) {
+                return;
+            }
+        }
+    }
+}
+
+
+/*
+ * Before FreeBSD 5.1 _spinunlock() is a simple #define in
+ * src/lib/libc/include/spinlock.h that zeroes lock.
+ *
+ * Since FreeBSD 5.1 _spinunlock() is a weak referenced stub in
+ * src/lib/libc/gen/_spinlock_stub.c that does nothing.
+ */
+
+#ifndef _spinunlock
+
+void _spinunlock(ngx_atomic_t *lock)
+{
+    *lock = 0;
+}
+
+#endif
+
+
+int ngx_create_thread(ngx_tid_t *tid, void* (*func)(void *arg), void *arg,
+                      ngx_log_t *log)
+{
+    int    id, err;
+    char  *stack, *stack_top;
+
+    if (nthreads >= max_threads) {
+        ngx_log_error(NGX_LOG_CRIT, log, 0,
+                      "no more than %d threads can be created", max_threads);
+        return NGX_ERROR;
+    }
+
+    last_stack -= ngx_thread_stack_size;
+
+    stack = mmap(last_stack, usable_stack_size, PROT_READ|PROT_WRITE,
+                 MAP_STACK, -1, 0);
+
+    if (stack == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "mmap(" PTR_FMT ":" SIZE_T_FMT
+                      ", MAP_STACK) thread stack failed",
+                      last_stack, usable_stack_size);
+        return NGX_ERROR;
+    }
+
+    if (stack != last_stack) {
+        ngx_log_error(NGX_LOG_ALERT, log, 0, "stack address was changed");
+    }
+
+    stack_top = stack + usable_stack_size;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
+                   "thread stack: " PTR_FMT "-" PTR_FMT, stack, stack_top);
+
+    ngx_set_errno(0);
+
+    id = rfork_thread(RFPROC|RFTHREAD|RFMEM, stack_top,
+                      (ngx_rfork_thread_func_pt) func, arg);
+
+    err = ngx_errno;
+
+    if (id == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, err, "rfork() failed");
+
+    } else {
+        *tid = id;
+        nthreads = (ngx_freebsd_kern_usrstack - stack_top)
+                                                       / ngx_thread_stack_size;
+        tids[nthreads] = id;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "rfork()ed thread: %d", id);
+    }
+
+    return err;
+}
+
+
+ngx_int_t ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle)
+{
+    char              *red_zone, *zone;
+    size_t             len;
+    ngx_int_t          i;
+    struct sigaction   sa;
+
+    max_threads = n + 1;
+
+    for (i = 0; i < n; i++) {
+        ngx_memzero(&sa, sizeof(struct sigaction));
+        sa.sa_handler = SIG_IGN;
+        sigemptyset(&sa.sa_mask);
+        if (sigaction(NGX_CV_SIGNAL, &sa, NULL) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "sigaction(%d, SIG_IGN) failed", NGX_CV_SIGNAL);
+            return NGX_ERROR;
+        }
+    }
+
+    len = sizeof(ngx_freebsd_kern_usrstack);
+    if (sysctlbyname("kern.usrstack", &ngx_freebsd_kern_usrstack, &len,
+                                                                NULL, 0) == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "sysctlbyname(kern.usrstack) failed");
+        return NGX_ERROR;
+    }
+
+    /* the main thread stack red zone */
+    rz_size = ngx_pagesize;
+    red_zone = ngx_freebsd_kern_usrstack - (size + rz_size);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                   "usrstack: " PTR_FMT " red zone: " PTR_FMT,
+                   ngx_freebsd_kern_usrstack, red_zone);
+
+    zone = mmap(red_zone, rz_size, PROT_NONE, MAP_ANON, -1, 0);
+    if (zone == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "mmap(" PTR_FMT ":" SIZE_T_FMT
+                      ", PROT_NONE, MAP_ANON) red zone failed",
+                      red_zone, rz_size);
+        return NGX_ERROR;
+    }
+
+    if (zone != red_zone) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                      "red zone address was changed");
+    }
+
+    /* create the threads errno's array */
+
+    if (!(errnos = ngx_calloc(n * sizeof(int), cycle->log))) {
+        return NGX_ERROR;
+    }
+
+    /* create the threads tids array */
+
+    if (!(tids = ngx_calloc((n + 1) * sizeof(ngx_tid_t), cycle->log))) {
+        return NGX_ERROR;
+    }
+
+    tids[0] = ngx_pid;
+
+    /* create the threads tls's array */
+
+    ngx_tls = ngx_calloc(NGX_THREAD_KEYS_MAX * (n + 1) * sizeof(void *),
+                         cycle->log);
+    if (ngx_tls == NULL) {
+        return NGX_ERROR;
+    }
+
+    nthreads = 1;
+
+    last_stack = zone + rz_size;
+    usable_stack_size = size;
+    ngx_thread_stack_size = size + rz_size;
+
+    /* allow the spinlock in libc malloc() */
+    __isthreaded = 1;
+
+    ngx_threaded = 1;
+
+    return NGX_OK;
+}
+
+
+ngx_tid_t ngx_thread_self()
+{
+    int        tid;
+    ngx_tid_t  pid;
+
+    tid = ngx_gettid();
+
+    if (tids == NULL) {
+        return ngx_pid;
+    }
+
+    return tids[tid];
+}
+
+
+ngx_int_t ngx_thread_key_create(ngx_tls_key_t *key)
+{
+    if (nkeys >= NGX_THREAD_KEYS_MAX) {
+        return NGX_ENOMEM;
+    }
+
+    *key = nkeys++;
+
+    return 0;
+}
+
+
+ngx_int_t ngx_thread_set_tls(ngx_tls_key_t key, void *value)
+{
+    if (key >= NGX_THREAD_KEYS_MAX) {
+        return NGX_EINVAL;
+    }
+
+    ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()] = value;
+    return 0;
+}
+
+
+ngx_mutex_t *ngx_mutex_init(ngx_log_t *log, uint flags)
+{
+    ngx_mutex_t  *m;
+    union semun   op;
+
+    if (!(m = ngx_alloc(sizeof(ngx_mutex_t), log))) {
+        return NULL;
+    }
+
+    m->lock = 0;
+    m->log = log;
+
+    if (flags & NGX_MUTEX_LIGHT) {
+        m->semid = -1;
+        return m;
+    }
+
+    m->semid = semget(IPC_PRIVATE, 1, SEM_R|SEM_A);
+    if (m->semid == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semget() failed");
+        return NULL;
+    }
+
+    op.val = 0;
+
+    if (semctl(m->semid, 0, SETVAL, op) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semctl(SETVAL) failed");
+
+        if (semctl(m->semid, 0, IPC_RMID) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                          "semctl(IPC_RMID) failed");
+        }
+
+        return NULL;
+    }
+
+    return m;
+}
+
+
+void ngx_mutex_destroy(ngx_mutex_t *m)
+{
+    if (semctl(m->semid, 0, IPC_RMID) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+                      "semctl(IPC_RMID) failed");
+    }
+
+    ngx_free((void *) m);
+}
+
+
+ngx_int_t ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try)
+{
+    uint32_t       lock, new, old;
+    ngx_uint_t     tries;
+    struct sembuf  op;
+
+    if (!ngx_threaded) {
+        return NGX_OK;
+    }
+
+#if (NGX_DEBUG)
+    if (try) {
+        ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                       "try lock mutex " PTR_FMT " lock:%X", m, m->lock);
+    } else {
+        ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                       "lock mutex " PTR_FMT " lock:%X", m, m->lock);
+    }
+#endif
+
+    old = m->lock;
+    tries = 0;
+
+    for ( ;; ) {
+        if (old & NGX_MUTEX_LOCK_BUSY) {
+
+            if (try) {
+                return NGX_AGAIN;
+            }
+
+            if (ngx_freebsd_hw_ncpu > 1 && tries++ < 1000) {
+
+                /* the spinlock is used only on the SMP system */
+
+                old = m->lock;
+                continue;
+            }
+
+            if (m->semid == -1) {
+                sched_yield();
+
+                tries = 0;
+                old = m->lock;
+                continue;
+            }
+
+            ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                           "mutex " PTR_FMT " lock:%X", m, m->lock);
+
+            /*
+             * The mutex is locked so we increase a number
+             * of the threads that are waiting on the mutex
+             */
+
+            lock = old + 1;
+
+            if ((lock & ~NGX_MUTEX_LOCK_BUSY) > nthreads) {
+                ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+                              "%d threads wait for mutex " PTR_FMT
+                              ", while only %d threads are available",
+                              lock & ~NGX_MUTEX_LOCK_BUSY, m, nthreads);
+                return NGX_ERROR;
+            }
+
+            if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+                ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                               "wait mutex " PTR_FMT " lock:%X", m, m->lock);
+
+                /*
+                 * The number of the waiting threads has been increased
+                 * and we would wait on the SysV semaphore.
+                 * A semaphore should wake up us more efficiently than
+                 * a simple sched_yield() or usleep().
+                 */
+
+                op.sem_num = 0;
+                op.sem_op = -1;
+                op.sem_flg = 0;
+
+                if (semop(m->semid, &op, 1) == -1) {
+                    ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+                                  "semop() failed while waiting "
+                                  "on mutex " PTR_FMT, m);
+                    return NGX_ERROR;
+                }
+
+                ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                               "mutex waked up " PTR_FMT " lock:%X",
+                               m, m->lock);
+
+                tries = 0;
+                old = m->lock;
+                continue;
+            }
+
+            old = m->lock;
+
+        } else {
+            lock = old | NGX_MUTEX_LOCK_BUSY;
+
+            if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+                /* we locked the mutex */
+
+                break;
+            }
+
+            old = m->lock;
+        }
+
+        if (tries++ > 1000) {
+
+            ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                           "mutex " PTR_FMT " is contested", m);
+
+            /* the mutex is probably contested so we are giving up now */
+
+            sched_yield();
+
+            tries = 0;
+            old = m->lock;
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is locked, lock:%X", m, m->lock);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_mutex_unlock(ngx_mutex_t *m)
+{
+    uint32_t       lock, new, old;
+    struct sembuf  op;
+
+    if (!ngx_threaded) {
+        return NGX_OK;
+    }
+
+    old = m->lock;
+
+    if (!(old & NGX_MUTEX_LOCK_BUSY)) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, 0,
+                      "trying to unlock the free mutex " PTR_FMT, m);
+        return NGX_ERROR;
+    }
+
+    /* free the mutex */
+
+#if 0
+    ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "unlock mutex " PTR_FMT " lock:%X", m, old);
+#endif
+
+    for ( ;; ) {
+        lock = old & ~NGX_MUTEX_LOCK_BUSY;
+
+        if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+            break;
+        }
+
+        old = m->lock;
+    }
+
+    if (m->semid == -1) {
+        ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                       "mutex " PTR_FMT " is unlocked", m);
+
+        return NGX_OK;
+    }
+
+    /* check whether we need to wake up a waiting thread */
+
+    old = m->lock;
+
+    for ( ;; ) {
+        if (old & NGX_MUTEX_LOCK_BUSY) {
+
+            /* the mutex is just locked by another thread */
+
+            break;
+        }
+
+        if (old == 0) {
+            break;
+        }
+
+        /* there are the waiting threads */
+
+        lock = old - 1;
+
+        if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+            /* wake up the thread that waits on semaphore */
+
+            ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                           "wake up mutex " PTR_FMT "", m);
+
+            op.sem_num = 0;
+            op.sem_op = 1;
+            op.sem_flg = 0;
+
+            if (semop(m->semid, &op, 1) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+                              "semop() failed while waking up on mutex "
+                              PTR_FMT, m);
+                return NGX_ERROR;
+            }
+
+            break;
+        }
+
+        old = m->lock;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is unlocked", m);
+
+    return NGX_OK;
+}
+
+
+ngx_cond_t *ngx_cond_init(ngx_log_t *log)
+{
+    ngx_cond_t  *cv;
+
+    if (!(cv = ngx_alloc(sizeof(ngx_cond_t), log))) {
+        return NULL;
+    }
+
+    cv->signo = NGX_CV_SIGNAL;
+    cv->tid = 0;
+    cv->log = log;
+    cv->kq = -1;
+
+    return cv;
+}
+
+
+void ngx_cond_destroy(ngx_cond_t *cv)
+{
+    if (close(cv->kq) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno,
+                      "kqueue close() failed");
+    }
+
+    ngx_free(cv);
+}
+
+
+ngx_int_t ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m)
+{
+    int              n;
+    ngx_err_t        err;
+    struct kevent    kev;
+    struct timespec  ts;
+
+    if (cv->kq == -1) {
+
+        /*
+         * We have to add the EVFILT_SIGNAL filter in the rfork()ed thread.
+         * Otherwise the thread would not get a signal event.
+         *
+         * However, we have not to open the kqueue in the thread,
+         * it is simply handy do it together.
+         */
+
+        cv->kq = kqueue();
+        if (cv->kq == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kqueue() failed");
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                       "cv kq:%d signo:%d", cv->kq, cv->signo);
+
+        kev.ident = cv->signo;
+        kev.filter = EVFILT_SIGNAL;
+        kev.flags = EV_ADD;
+        kev.fflags = 0;
+        kev.data = 0;
+        kev.udata = NULL;
+
+        ts.tv_sec = 0;
+        ts.tv_nsec = 0;
+
+        if (kevent(cv->kq, &kev, 1, NULL, 0, &ts) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kevent() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    if (ngx_mutex_unlock(m) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " wait, kq:%d, signo:%d",
+                   cv, cv->kq, cv->signo);
+
+    for ( ;; ) {
+        n = kevent(cv->kq, NULL, 0, &kev, 1, NULL);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                       "cv " PTR_FMT " kevent: %d", cv, n);
+
+        if (n == -1) {
+            err = ngx_errno;
+            ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+                          cv->log, ngx_errno,
+                          "kevent() failed while waiting condition variable "
+                          PTR_FMT, cv);
+
+            if (err == NGX_EINTR) {
+                break;
+            }
+
+            return NGX_ERROR;
+        }
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+                          "kevent() returned no events "
+                          "while waiting condition variable " PTR_FMT,
+                          cv);
+            continue;
+        }
+
+        if (kev.filter != EVFILT_SIGNAL) {
+            ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+                          "kevent() returned unexpected events: %d "
+                          "while waiting condition variable " PTR_FMT,
+                          kev.filter, cv);
+            continue;
+        }
+
+        if (kev.ident != (uintptr_t) cv->signo) {
+            ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+                          "kevent() returned unexpected signal: %d ",
+                          "while waiting condition variable " PTR_FMT,
+                          kev.ident, cv);
+            continue;
+        }
+
+        break;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " is waked up", cv);
+
+    if (ngx_mutex_lock(m) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_cond_signal(ngx_cond_t *cv)
+{
+    ngx_err_t  err;
+
+    ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " to signal " PID_T_FMT " %d",
+                   cv, cv->tid, cv->signo);
+
+    if (kill(cv->tid, cv->signo) == -1) {
+
+        err = ngx_errno;
+
+        ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+                      "kill() failed while signaling condition variable "
+                      PTR_FMT, cv);
+
+        if (err == NGX_ESRCH) {
+            cv->tid = -1;
+        }
+
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " is signaled", cv);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_rfork_thread.h
@@ -0,0 +1,121 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+#define _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+
+
+#include <sys/ipc.h>
+#include <sys/sem.h>
+#include <sched.h>
+
+typedef pid_t  ngx_tid_t;
+
+#undef ngx_log_pid
+#define ngx_log_pid    ngx_thread_self()
+#define ngx_log_tid    0
+
+#define TID_T_FMT      PID_T_FMT
+
+
+#define NGX_MUTEX_LIGHT      1
+
+#define NGX_MUTEX_LOCK_BUSY  0x80000000
+
+typedef volatile struct {
+    ngx_atomic_t  lock;
+    ngx_log_t    *log;
+    int           semid;
+} ngx_mutex_t;
+
+
+#define NGX_CV_SIGNAL        64
+
+typedef struct {
+    int           signo;
+    int           kq;
+    ngx_tid_t     tid;
+    ngx_log_t    *log;
+} ngx_cond_t;
+
+
+#define ngx_thread_sigmask(how, set, oset)                         \
+            (sigprocmask(how, set, oset) == -1) ? ngx_errno : 0
+
+#define ngx_thread_sigmask_n   "sigprocmask()"
+
+#define ngx_thread_join(t, p)
+
+#define ngx_setthrtitle(n)     setproctitle(n)
+
+
+extern char    *ngx_freebsd_kern_usrstack;
+extern size_t   ngx_thread_stack_size;
+
+
+static inline int ngx_gettid()
+{
+    char  *sp;
+
+    if (ngx_thread_stack_size == 0) {
+        return 0;
+    }
+
+#if ( __i386__ )
+
+    __asm__ volatile ("mov %%esp, %0" : "=q" (sp));
+
+#elif ( __amd64__ )
+
+    __asm__ volatile ("mov %%rsp, %0" : "=q" (sp));
+
+#else
+
+#error "rfork()ed threads are not supported on this platform"
+
+#endif
+
+    return (ngx_freebsd_kern_usrstack - sp) / ngx_thread_stack_size;
+}
+
+
+ngx_tid_t ngx_thread_self();
+
+
+typedef ngx_uint_t               ngx_tls_key_t;
+
+#define NGX_THREAD_KEYS_MAX      16
+
+extern void    **ngx_tls;
+
+ngx_int_t ngx_thread_key_create(ngx_tls_key_t *key);
+#define ngx_thread_key_create_n  "the tls key creation"
+
+ngx_int_t ngx_thread_set_tls(ngx_tls_key_t key, void *value);
+#define ngx_thread_set_tls_n     "the tls key setting"
+
+
+static void *ngx_thread_get_tls(ngx_tls_key_t key)
+{   
+    if (key >= NGX_THREAD_KEYS_MAX) {
+        return NULL;
+    }
+
+    return ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()];
+}
+
+
+#define ngx_mutex_trylock(m)  ngx_mutex_dolock(m, 1)
+#define ngx_mutex_lock(m)     ngx_mutex_dolock(m, 0)
+ngx_int_t ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try);
+ngx_int_t ngx_mutex_unlock(ngx_mutex_t *m);
+
+
+typedef int (*ngx_rfork_thread_func_pt)(void *arg);
+
+
+
+#endif /* _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_sendfile_chain.c
@@ -0,0 +1,380 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * Although FreeBSD sendfile() allows to pass a header and a trailer
+ * it can not send a header with a part of the file in one packet until
+ * FreeBSD 5.3.  Besides over the fast ethernet connection sendfile()
+ * may send the partially filled packets, i.e. the 8 file pages may be sent
+ * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
+ * and then again the 11 full 1460-bytes packets.
+ *
+ * So we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
+ * to postpone the sending - it not only sends a header and the first part
+ * of the file in one packet but also sends the file pages in the full packets.
+ *
+ * But until FreeBSD 4.5 the turning TCP_NOPUSH off does not flush a pending
+ * data that less than MSS so that data may be sent with 5 second delay.
+ * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5 although it can be used
+ * for non-keepalive HTTP connections.
+ */
+
+
+#define NGX_HEADERS   8
+#define NGX_TRAILERS  4
+
+
+ngx_chain_t *ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                        off_t limit)
+{
+    int              rc;
+    u_char          *prev;
+    off_t            fprev, sent, send, sprev, aligned;
+    size_t           hsize, fsize;
+    ssize_t          size;
+    ngx_uint_t       eintr, eagain, complete;
+    ngx_err_t        err;
+    ngx_buf_t       *file;
+    ngx_array_t      header, trailer;
+    ngx_event_t     *wev;
+    ngx_chain_t     *cl;
+    struct sf_hdtr   hdtr;
+    struct iovec    *iov, headers[NGX_HEADERS], trailers[NGX_TRAILERS];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) && wev->pending_eof) {
+        ngx_log_error(NGX_LOG_INFO, c->log, wev->kq_errno,
+                      "kevent() reported about an closed connection");
+
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    send = 0;
+    eagain = 0;
+
+    header.elts = headers;
+    header.size = sizeof(struct iovec);
+    header.nalloc = NGX_HEADERS;
+    header.pool = c->pool;
+
+    trailer.elts = trailers;
+    trailer.size = sizeof(struct iovec);
+    trailer.nalloc = NGX_TRAILERS;
+    trailer.pool = c->pool;
+
+    for ( ;; ) {
+        file = NULL;
+        fsize = 0;
+        hsize = 0;
+        eintr = 0;
+        complete = 0;
+        sprev = send;
+
+        header.nelts = 0;
+        trailer.nelts = 0;
+
+        /* create the header iovec and coalesce the neighbouring bufs */
+
+        prev = NULL;
+        iov = NULL;
+
+        for (cl = in;
+             cl && header.nelts < IOV_MAX && send < limit;
+             cl = cl->next)
+        {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (!ngx_buf_in_memory_only(cl->buf)) {
+                break;
+            }
+
+            size = cl->buf->last - cl->buf->pos;
+
+            if (send + size > limit) {
+                size = limit - send;
+            }
+
+            if (prev == cl->buf->pos) {
+                iov->iov_len += size;
+
+            } else {
+                if (!(iov = ngx_array_push(&header))) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                iov->iov_base = (void *) cl->buf->pos;
+                iov->iov_len = size;
+            }
+
+            prev = cl->buf->pos + size;
+            hsize += size;
+            send += size;
+        }
+
+        /* get the file buf */
+
+        if (cl && cl->buf->in_file && send < limit) {
+            file = cl->buf;
+
+            /* coalesce the neighbouring file bufs */
+
+            do {
+                size = (size_t) (cl->buf->file_last - cl->buf->file_pos);
+
+                if (send + size > limit) {
+                    size = limit - send;
+
+                    aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+                                                      & ~(ngx_pagesize - 1);
+
+                    if (aligned <= cl->buf->file_last) {
+                        size = aligned - cl->buf->file_pos;
+                    }
+                }
+
+                fsize += size;
+                send += size;
+                fprev = cl->buf->file_pos + size;
+                cl = cl->next;
+
+            } while (cl
+                     && cl->buf->in_file
+                     && send < limit
+                     && file->file->fd == cl->buf->file->fd
+                     && fprev == cl->buf->file_pos);
+        }
+
+        if (file) {
+            /* create the tailer iovec and coalesce the neighbouring bufs */
+
+            prev = NULL;
+            iov = NULL;
+
+            for (/* void */;
+                 cl && header.nelts < IOV_MAX && send < limit;
+                 cl = cl->next)
+            {
+                if (ngx_buf_special(cl->buf)) {
+                    continue;
+                }
+
+                if (!ngx_buf_in_memory_only(cl->buf)) {
+                    break;
+                }
+
+                size = cl->buf->last - cl->buf->pos;
+
+                if (send + size > limit) {
+                    size = limit - send;
+                }
+
+                if (prev == cl->buf->pos) {
+                    iov->iov_len += size;
+
+                } else {
+                    if (!(iov = ngx_array_push(&trailer))) {
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                    iov->iov_base = (void *) cl->buf->pos;
+                    iov->iov_len = size;
+                }
+
+                prev = cl->buf->pos + size;
+                send += size;
+            }
+        }
+
+        if (file) {
+
+            if (ngx_freebsd_use_tcp_nopush
+                && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
+            {
+
+                if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
+                    err = ngx_errno;
+
+                    /*
+                     * there is a tiny chance to be interrupted, however
+                     * we continue a processing without the TCP_NOPUSH
+                     */
+
+                    if (err != NGX_EINTR) {
+                        wev->error = 1;
+                        ngx_connection_error(c, err,
+                                             ngx_tcp_nopush_n " failed");
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                } else {
+                    c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                                   "tcp_nopush");
+                }
+            }
+
+            hdtr.headers = (struct iovec *) header.elts;
+            hdtr.hdr_cnt = header.nelts;
+            hdtr.trailers = (struct iovec *) trailer.elts;
+            hdtr.trl_cnt = trailer.nelts;
+
+            /*
+             * the "nbytes bug" of the old sendfile() syscall:
+             * http://www.freebsd.org/cgi/query-pr.cgi?pr=33771
+             */
+
+            if (ngx_freebsd_sendfile_nbytes_bug == 0) {
+                hsize = 0;
+            }
+
+            sent = 0;
+
+            rc = sendfile(file->file->fd, c->fd, file->file_pos,
+                          fsize + hsize, &hdtr, &sent, 0);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                    if (err == NGX_EINTR) {
+                        eintr = 1;
+
+                    } else {
+                        eagain = 1;
+                    }
+
+                    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+                                   "sendfile() sent only " OFF_T_FMT " bytes",
+                                   sent);
+
+                } else {
+                    wev->error = 1;
+                    ngx_connection_error(c, err, "sendfile() failed");
+                    return NGX_CHAIN_ERROR;
+                }
+            }
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "sendfile: %d, @" OFF_T_FMT " " OFF_T_FMT ":%d",
+                           rc, file->file_pos, sent, fsize + hsize);
+
+        } else {
+            rc = writev(c->fd, header.elts, header.nelts);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "writev: %d of " SIZE_T_FMT, rc, hsize);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                    if (err == NGX_EINTR) {
+                        eintr = 1;
+                    }
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                                   "writev() not ready");
+
+                } else {
+                    wev->error = 1;
+                    ngx_connection_error(c, err, "writev() failed");
+                    return NGX_CHAIN_ERROR;
+                }
+            }
+
+            sent = rc > 0 ? rc : 0;
+        }
+
+        if (send - sprev == sent) {
+            complete = 1;
+        }
+
+        c->sent += sent;
+
+        for (cl = in; cl; cl = cl->next) {
+
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (sent == 0) {
+                break;
+            }
+
+            size = ngx_buf_size(cl->buf);
+
+            if (sent >= size) {
+                sent -= size;
+
+                if (ngx_buf_in_memory(cl->buf)) {
+                    cl->buf->pos = cl->buf->last;
+                }
+
+                if (cl->buf->in_file) {
+                    cl->buf->file_pos = cl->buf->file_last;
+                }
+
+                continue;
+            }
+
+            if (ngx_buf_in_memory(cl->buf)) {
+                cl->buf->pos += sent;
+            }
+
+            if (cl->buf->in_file) {
+                cl->buf->file_pos += sent;
+            }
+
+            break;
+        }
+
+        if (eagain) {
+
+            /*
+             * sendfile() can return EAGAIN even if it has sent
+             * a whole file part but the successive sendfile() call would
+             * return EAGAIN right away and would not send anything.
+             * We use it as a hint.
+             */
+
+            wev->ready = 0;
+            return cl;
+        }
+
+        if (eintr) {
+            continue;
+        }
+
+        if (!complete) {
+            wev->ready = 0;
+            return cl;
+        }
+
+        if (send >= limit || cl == NULL) {
+            return cl;
+        }
+
+        in = cl;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_linux.h
@@ -0,0 +1,17 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LINUX_H_INCLUDED_
+#define _NGX_LINUX_H_INCLUDED_
+
+
+ngx_chain_t *ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                      off_t limit);
+
+extern int ngx_linux_rtsig_max;
+
+
+#endif /* _NGX_LINUX_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_linux_config.h
@@ -0,0 +1,101 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_LINUX_CONFIG_H_INCLUDED_
+#define _NGX_LINUX_CONFIG_H_INCLUDED_
+
+
+#define _GNU_SOURCE             /* pread(), pwrite(), gethostname() */
+
+#define _FILE_OFFSET_BITS  64
+#define _LARGEFILE_SOURCE
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <time.h>               /* tzset() */
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <netinet/tcp.h>        /* TCP_CORK */
+
+
+#include <ngx_auto_config.h>
+
+
+#if (HAVE_PRCTL)
+#include <sys/prctl.h>
+#endif
+
+#if (HAVE_SENDFILE64)
+#include <sys/sendfile.h>
+#else
+extern ssize_t sendfile(int s, int fd, int32_t *offset, size_t size);
+#endif
+
+
+
+#ifndef HAVE_SELECT
+#define HAVE_SELECT  1
+#endif
+
+
+#ifndef HAVE_POLL
+#define HAVE_POLL  1
+#endif
+#if (HAVE_POLL)
+#include <poll.h>
+#endif
+
+#if (HAVE_EPOLL)
+#include <sys/epoll.h>
+#endif /* HAVE_EPOLL */
+
+
+#if defined TCP_DEFER_ACCEPT && !defined HAVE_DEFERRED_ACCEPT
+#define HAVE_DEFERRED_ACCEPT  1
+#endif
+
+
+#ifndef HAVE_INHERITED_NONBLOCK
+#define HAVE_INHERITED_NONBLOCK  0
+#endif
+
+
+#ifndef HAVE_SELECT_CHANGE_TIMEOUT
+#define HAVE_SELECT_CHANGE_TIMEOUT   1
+#endif
+
+
+#define ngx_setproctitle(title)
+
+
+#endif /* _NGX_LINUX_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_linux_init.c
@@ -0,0 +1,80 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char ngx_linux_kern_ostype[50];
+char ngx_linux_kern_osrelease[20];
+
+int ngx_linux_rtsig_max;
+
+
+ngx_os_io_t ngx_os_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_unix_send,
+#if (HAVE_SENDFILE)
+    ngx_linux_sendfile_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+ngx_int_t ngx_os_init(ngx_log_t *log)
+{
+    int  name[2], len;
+
+    name[0] = CTL_KERN;
+    name[1] = KERN_OSTYPE;
+    len = sizeof(ngx_linux_kern_ostype);
+    if (sysctl(name, sizeof(name), ngx_linux_kern_ostype, &len, NULL, 0)
+                                                                       == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctl(KERN_OSTYPE) failed");
+        return NGX_ERROR;
+    }
+
+    name[0] = CTL_KERN;
+    name[1] = KERN_OSRELEASE;
+    len = sizeof(ngx_linux_kern_osrelease);
+    if (sysctl(name, sizeof(name), ngx_linux_kern_osrelease, &len, NULL, 0)
+                                                                       == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysctl(KERN_OSRELEASE) failed");
+        return NGX_ERROR;
+    }
+
+
+    name[0] = CTL_KERN;
+    name[1] = KERN_RTSIGMAX;
+    len = sizeof(ngx_linux_rtsig_max);
+    if (sysctl(name, sizeof(name), &ngx_linux_rtsig_max, &len, NULL, 0) == -1) {
+        ngx_log_error(NGX_LOG_INFO, log, ngx_errno,
+                      "sysctl(KERN_RTSIGMAX) failed");
+        ngx_linux_rtsig_max = 0;
+
+    }
+
+
+    return ngx_posix_init(log);
+}
+
+
+void ngx_os_status(ngx_log_t *log)
+{
+    ngx_log_error(NGX_LOG_INFO, log, 0, "OS: %s %s",
+                  ngx_linux_kern_ostype, ngx_linux_kern_osrelease);
+
+    ngx_log_error(NGX_LOG_INFO, log, 0, "sysctl(KERN_RTSIGMAX): %d",
+                  ngx_linux_rtsig_max);
+
+    ngx_posix_status(log);
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_linux_sendfile_chain.c
@@ -0,0 +1,285 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+/*
+ * On Linux up to 2.4.21 sendfile() (syscall #187) works with 32-bit
+ * offsets only and the including <sys/sendfile.h> breaks the compiling
+ * if off_t is 64 bit wide.  So we use own sendfile() definition where offset
+ * parameter is int32_t and use sendfile() with the file parts below 2G.
+ *
+ * Linux 2.4.21 has a new sendfile64() syscall #239.
+ */
+
+
+#define NGX_HEADERS   8
+
+
+ngx_chain_t *ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                      off_t limit)
+{
+    int              rc;
+    u_char          *prev;
+    off_t            fprev, send, sprev, aligned;
+    size_t           fsize;
+    ssize_t          size, sent;
+    ngx_uint_t       eintr, complete;
+    ngx_err_t        err;
+    ngx_buf_t       *file;
+    ngx_array_t      header;
+    ngx_event_t     *wev;
+    ngx_chain_t     *cl;
+    struct iovec    *iov, headers[NGX_HEADERS];
+#if (HAVE_SENDFILE64)
+    off_t            offset;
+#else
+    int32_t          offset;
+#endif
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+    send = 0;
+
+    header.elts = headers;
+    header.size = sizeof(struct iovec);
+    header.nalloc = NGX_HEADERS;
+    header.pool = c->pool;
+
+    for ( ;; ) {
+        file = NULL;
+        fsize = 0;
+        eintr = 0;
+        complete = 0;
+        sprev = send;
+
+        header.nelts = 0;
+
+        prev = NULL;
+        iov = NULL;
+
+        /* create the iovec and coalesce the neighbouring bufs */
+
+        for (cl = in;
+             cl && header.nelts < IOV_MAX && send < limit;
+             cl = cl->next)
+        {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (!ngx_buf_in_memory_only(cl->buf)) {
+                break;
+            }
+
+            size = cl->buf->last - cl->buf->pos;
+
+            if (send + size > limit) {
+                size = limit - send;
+            }
+
+            if (prev == cl->buf->pos) {
+                iov->iov_len += size;
+
+            } else {
+                if (!(iov = ngx_array_push(&header))) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                iov->iov_base = (void *) cl->buf->pos;
+                iov->iov_len = size;
+            }
+
+            prev = cl->buf->pos + size;
+            send += size;
+        }
+
+        /* set TCP_CORK if there is a header before a file */
+
+        if (c->tcp_nopush == NGX_TCP_NOPUSH_UNSET
+            && header.nelts != 0
+            && cl
+            && cl->buf->in_file)
+        {
+            if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
+                err = ngx_errno;
+
+                /*
+                 * there is a tiny chance to be interrupted, however
+                 * we continue a processing without the TCP_CORK
+                 */
+
+                if (err != NGX_EINTR) { 
+                    wev->error = 1;
+                    ngx_connection_error(c, err, ngx_tcp_nopush_n " failed");
+                    return NGX_CHAIN_ERROR;
+                }
+
+            } else {
+                c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                               "tcp_nopush");
+            }
+        }
+
+        /* get the file buf */
+
+        if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) {
+            file = cl->buf;
+
+            /* coalesce the neighbouring file bufs */
+
+            do {
+                size = (size_t) (cl->buf->file_last - cl->buf->file_pos);
+
+                if (send + size > limit) {
+                    size = limit - send;
+
+                    aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+                                                      & ~(ngx_pagesize - 1);
+
+                    if (aligned <= cl->buf->file_last) {
+                        size = aligned - cl->buf->file_pos;
+                    }
+                }
+
+                fsize += size;
+                send += size;
+                fprev = cl->buf->file_pos + size;
+                cl = cl->next;
+
+            } while (cl
+                     && cl->buf->in_file
+                     && send < limit
+                     && file->file->fd == cl->buf->file->fd
+                     && fprev == cl->buf->file_pos);
+        }
+
+        if (file) {
+#if (HAVE_SENDFILE64)
+            offset = file->file_pos;
+#else
+            offset = (int32_t) file->file_pos;
+#endif
+            rc = sendfile(c->fd, file->file->fd, &offset, fsize);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                    if (err == NGX_EINTR) {
+                        eintr = 1;
+                    }
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                                   "sendfile() is not ready");
+
+                } else {
+                    wev->error = 1;
+                    ngx_connection_error(c, err, "sendfile() failed");
+                    return NGX_CHAIN_ERROR;
+                }
+            }
+
+            sent = rc > 0 ? rc : 0;
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                           "sendfile: %d, @" OFF_T_FMT " %d:%d",
+                           rc, file->file_pos, sent, fsize);
+
+        } else {
+            rc = writev(c->fd, header.elts, header.nelts);
+
+            if (rc == -1) {
+                err = ngx_errno;
+
+                if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                    if (err == NGX_EINTR) {
+                        eintr = 1;
+                    }
+
+                    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                                   "writev() not ready");
+
+                } else {
+                    wev->error = 1;
+                    ngx_connection_error(c, err, "writev() failed");
+                    return NGX_CHAIN_ERROR; 
+                }
+            }
+
+            sent = rc > 0 ? rc : 0;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "writev: %d", sent);
+        }
+
+        if (send - sprev == sent) {
+            complete = 1;
+        }
+
+        c->sent += sent;
+
+        for (cl = in; cl; cl = cl->next) {
+
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (sent == 0) {
+                break;
+            }
+
+            size = ngx_buf_size(cl->buf);
+
+            if (sent >= size) {
+                sent -= size;
+
+                if (ngx_buf_in_memory(cl->buf)) {
+                    cl->buf->pos = cl->buf->last;
+                }
+
+                if (cl->buf->in_file) {
+                    cl->buf->file_pos = cl->buf->file_last;
+                }
+
+                continue;
+            }
+
+            if (ngx_buf_in_memory(cl->buf)) {
+                cl->buf->pos += sent;
+            }
+
+            if (cl->buf->in_file) {
+                cl->buf->file_pos += sent;
+            }
+
+            break;
+        }
+
+        if (eintr) {
+            continue; 
+        }
+
+        if (!complete) { 
+            wev->ready = 0;
+            return cl;
+        }
+
+        if (send >= limit || cl == NULL) {
+            return cl;
+        }
+
+        in = cl;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_os.h
@@ -0,0 +1,84 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_OS_H_INCLUDED_
+#define _NGX_OS_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_IO_SENDFILE    1
+#define NGX_IO_ZEROCOPY    2
+
+#if (HAVE_SENDFILE)
+#define NGX_HAVE_SENDFILE  NGX_IO_SENDFILE
+#else
+#define NGX_HAVE_SENDFILE  0
+#endif
+
+#if (HAVE_ZEROCOPY)
+#define NGX_HAVE_ZEROCOPY  NGX_IO_ZEROCOPY
+#else
+#define NGX_HAVE_ZEROCOPY  0
+#endif
+
+
+typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in);
+typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size);
+typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in,
+                                          off_t limit);
+
+typedef struct {
+    ngx_recv_pt        recv;
+    ngx_recv_chain_pt  recv_chain;
+    ngx_send_pt        send;
+    ngx_send_chain_pt  send_chain;
+    ngx_uint_t         flags;
+} ngx_os_io_t;
+
+
+void ngx_debug_init();
+ngx_int_t ngx_os_init(ngx_log_t *log);
+void ngx_os_status(ngx_log_t *log);
+ngx_int_t ngx_daemon(ngx_log_t *log);
+ngx_int_t ngx_posix_init(ngx_log_t *log);
+void ngx_posix_status(ngx_log_t *log);
+ngx_int_t ngx_posix_post_conf_init(ngx_log_t *log);
+
+
+ssize_t ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size);
+ssize_t ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *entry);
+ssize_t ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size);
+ngx_chain_t *ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in,
+                              off_t limit);
+
+
+extern ngx_os_io_t  ngx_os_io;
+extern ngx_int_t    ngx_ncpu;
+extern ngx_int_t    ngx_max_sockets;
+extern ngx_int_t    ngx_inherited_nonblocking;
+
+#define ngx_stderr_fileno  STDERR_FILENO
+
+#ifdef __FreeBSD__
+#include <ngx_freebsd.h>
+#endif
+
+
+#ifdef __linux__
+#include <ngx_linux.h>
+#endif
+
+
+#ifdef SOLARIS
+#include <ngx_solaris.h>
+#endif
+
+
+#endif /* _NGX_OS_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_posix_config.h
@@ -0,0 +1,62 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_POSIX_CONFIG_H_INCLUDED_
+#define _NGX_POSIX_CONFIG_H_INCLUDED_
+
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+
+#include <sys/uio.h>
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <ngx_auto_config.h>
+
+
+#ifndef HAVE_SELECT
+#define HAVE_SELECT  1
+#endif
+
+
+#ifndef HAVE_POLL
+#define HAVE_POLL  1
+#endif
+#if (HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#define ngx_setproctitle(title)
+
+
+#define NGX_POSIX_IO  1
+
+
+#endif /* _NGX_POSIX_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_posix_init.c
@@ -0,0 +1,299 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+ngx_int_t  ngx_ncpu;
+ngx_int_t  ngx_max_sockets;
+ngx_int_t  ngx_inherited_nonblocking;
+
+
+struct rlimit  rlmt;
+
+
+#if (NGX_POSIX_IO)
+
+ngx_os_io_t ngx_os_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    NULL,
+    ngx_writev_chain,
+    0
+};
+
+
+int ngx_os_init(ngx_log_t *log)
+{
+    return ngx_posix_init(log);
+}
+
+
+#endif
+
+
+void ngx_signal_handler(int signo);
+
+
+typedef struct {
+     int     signo;
+     char   *signame;
+     void  (*handler)(int signo);
+} ngx_signal_t;
+
+
+ngx_signal_t  signals[] = {
+    { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
+      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_REOPEN_SIGNAL),
+      "SIG" ngx_value(NGX_REOPEN_SIGNAL),
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_NOACCEPT_SIGNAL),
+      "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_TERMINATE_SIGNAL),
+      "SIG" ngx_value(NGX_TERMINATE_SIGNAL),
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
+      "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
+      ngx_signal_handler },
+
+    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
+      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
+      ngx_signal_handler },
+
+    { SIGALRM, "SIGALRM", ngx_signal_handler },
+
+    { SIGINT, "SIGINT", ngx_signal_handler },
+
+    { SIGIO, "SIGIO", ngx_signal_handler },
+
+    { SIGCHLD, "SIGCHLD", ngx_signal_handler },
+
+    { SIGPIPE, "SIGPIPE, SIG_IGN", SIG_IGN },
+
+    { 0, NULL, NULL }
+};
+
+
+ngx_int_t ngx_posix_init(ngx_log_t *log)
+{
+    ngx_signal_t      *sig;
+    struct sigaction   sa;
+
+    ngx_pagesize = getpagesize();
+
+    if (ngx_ncpu == 0) {
+        ngx_ncpu = 1;
+    }
+
+    for (sig = signals; sig->signo != 0; sig++) {
+        ngx_memzero(&sa, sizeof(struct sigaction));
+        sa.sa_handler = sig->handler;
+        sigemptyset(&sa.sa_mask);
+        if (sigaction(sig->signo, &sa, NULL) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+                          "sigaction(%s) failed", sig->signame);
+            return NGX_ERROR;
+        }
+    }
+
+    if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, errno,
+                      "getrlimit(RLIMIT_NOFILE) failed)");
+        return NGX_ERROR;
+    }
+
+    ngx_max_sockets = rlmt.rlim_cur;
+
+#if (HAVE_INHERITED_NONBLOCK)
+    ngx_inherited_nonblocking = 1;
+#else
+    ngx_inherited_nonblocking = 0;
+#endif
+
+    return NGX_OK;
+}
+
+
+void ngx_posix_status(ngx_log_t *log)
+{
+    ngx_log_error(NGX_LOG_INFO, log, 0,
+                  "getrlimit(RLIMIT_NOFILE): " RLIM_T_FMT ":" RLIM_T_FMT,
+                  rlmt.rlim_cur, rlmt.rlim_max);
+}
+
+
+void ngx_signal_handler(int signo)
+{
+    char            *action;
+    struct timeval   tv;
+    ngx_int_t        ignore;
+    ngx_err_t        err;
+    ngx_signal_t    *sig;
+
+    ignore = 0;
+
+    err = ngx_errno;
+
+    for (sig = signals; sig->signo != 0; sig++) {
+        if (sig->signo == signo) {
+            break;
+        }
+    }
+
+    ngx_gettimeofday(&tv);
+    ngx_time_update(tv.tv_sec);
+
+    action = "";
+
+    switch (ngx_process) {
+
+    case NGX_PROCESS_MASTER:
+    case NGX_PROCESS_SINGLE:
+        switch (signo) {
+
+        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+            ngx_quit = 1;
+            action = ", shutting down";
+            break;
+
+        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        case SIGINT:
+            ngx_terminate = 1;
+            action = ", exiting";
+            break;
+
+        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+            ngx_noaccept = 1;
+            action = ", stop the accepting connections";
+            break;
+
+        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+            ngx_reconfigure = 1;
+            action = ", reconfiguring";
+            break;
+
+        case ngx_signal_value(NGX_REOPEN_SIGNAL):
+            ngx_reopen = 1;
+            action = ", reopen logs";
+            break;
+
+        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+            if (getppid() > 1 || ngx_new_binary > 0) {
+
+                /*
+                 * Ignore the signal in the new binary if its parent is
+                 * not the init process, i.e. the old binary's process
+                 * is still running.  Or ingore the signal in the old binary's
+                 * process if the new binary's process is already running.
+                 */
+
+                action = ", ignoring";
+                ignore = 1;
+                break;
+            }
+
+            ngx_change_binary = 1;
+            action = ", changing binary";
+            break;
+
+        case SIGALRM:
+            if (!ngx_terminate) {
+                ngx_timer = 1;
+                action = ", shutting down old worker processes";
+            }
+
+            break;
+
+        case SIGIO:
+            ngx_sigio = 1;
+            break;
+
+        case SIGCHLD:
+            ngx_reap = 1;
+            break;
+        }
+
+        break;
+
+    case NGX_PROCESS_WORKER:
+        switch (signo) {
+
+        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+            ngx_quit = 1;
+            action = ", shutting down";
+            break;
+
+        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        case SIGINT:
+            ngx_terminate = 1;
+            action = ", exiting";
+            break;
+
+        case ngx_signal_value(NGX_REOPEN_SIGNAL):
+            ngx_reopen = 1;
+            action = ", reopen logs";
+            break;
+
+        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
+        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
+        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
+        case SIGIO:
+            action = ", ignoring";
+            break;
+        }
+
+        break;
+    }
+
+    ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0,
+                  "signal %d (%s) received%s", signo, sig->signame, action);
+
+    if (ignore) {
+        ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
+                      "the changing binary signal is ignored: "
+                      "you should shutdown or terminate "
+                      "before either old or new binary's process");
+    }
+
+    if (signo == SIGCHLD) {
+        ngx_process_get_status();
+    }
+
+    ngx_set_errno(err);
+}
+
+
+int ngx_posix_post_conf_init(ngx_log_t *log)
+{
+    ngx_fd_t  pp[2];
+
+    if (pipe(pp) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "pipe() failed");
+        return NGX_ERROR;
+    }
+
+    if (dup2(pp[1], STDERR_FILENO) == -1) {
+        ngx_log_error(NGX_LOG_EMERG, log, errno, "dup2(STDERR) failed");
+        return NGX_ERROR;
+    }
+
+    if (pp[1] > STDERR_FILENO) {
+        if (close(pp[1]) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, log, errno, "close() failed");
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_process.c
@@ -0,0 +1,283 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+static void ngx_execute_proc(ngx_cycle_t *cycle, void *data);
+
+ngx_int_t      ngx_process_slot;
+ngx_socket_t   ngx_channel;
+ngx_int_t      ngx_last_process;
+ngx_process_t  ngx_processes[NGX_MAX_PROCESSES];
+
+
+ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
+                            ngx_spawn_proc_pt proc, void *data,
+                            char *name, ngx_int_t respawn)
+{
+    u_long     on;
+    ngx_pid_t  pid;
+    ngx_int_t  s;
+
+    if (respawn >= 0) {
+        s = respawn;
+
+    } else {
+        for (s = 0; s < ngx_last_process; s++) {
+            if (ngx_processes[s].pid == -1) {
+                break;
+            }
+        }
+
+        if (s == NGX_MAX_PROCESSES) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                          "no more than %d processes can be spawned",
+                          NGX_MAX_PROCESSES);
+            return NGX_ERROR;
+        }
+    }
+
+
+    if (respawn != NGX_PROCESS_DETACHED) {
+
+        /* Solaris 9 still has no AF_LOCAL */
+
+        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
+        {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "socketpair() failed while spawning \"%s\"", name);
+            return NGX_ERROR;
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "channel %d:%d",
+                       ngx_processes[s].channel[0],
+                       ngx_processes[s].channel[1]);
+
+        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_nonblocking_n " failed while spawning \"%s\"",
+                          name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          ngx_nonblocking_n " failed while spawning \"%s\"",
+                          name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        on = 1;
+        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+                           name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
+                           name);
+            ngx_close_channel(ngx_processes[s].channel, cycle->log);
+            return NGX_ERROR;
+        }
+
+        ngx_channel = ngx_processes[s].channel[1];
+
+    } else {
+        ngx_processes[s].channel[0] = -1;
+        ngx_processes[s].channel[1] = -1;
+    }
+
+    ngx_process_slot = s;
+
+
+    pid = fork();
+
+    switch (pid) {
+
+    case -1:
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "fork() failed while spawning \"%s\"", name);
+        ngx_close_channel(ngx_processes[s].channel, cycle->log);
+        return NGX_ERROR;
+
+    case 0:
+        ngx_pid = ngx_getpid();
+        proc(cycle, data);
+        break;
+
+    default:
+        break;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                   "spawn %s: " PID_T_FMT, name, pid);
+
+    ngx_processes[s].pid = pid;
+    ngx_processes[s].exited = 0;
+
+    if (respawn >= 0) {
+        return pid;
+    }
+
+    ngx_processes[s].proc = proc;
+    ngx_processes[s].data = data;
+    ngx_processes[s].name = name;
+    ngx_processes[s].exiting = 0;
+
+    switch (respawn) {
+
+    case NGX_PROCESS_RESPAWN:
+        ngx_processes[s].respawn = 1;
+        ngx_processes[s].just_respawn = 0;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_JUST_RESPAWN:
+        ngx_processes[s].respawn = 1;
+        ngx_processes[s].just_respawn = 1;
+        ngx_processes[s].detached = 0;
+        break;
+
+    case NGX_PROCESS_DETACHED:
+        ngx_processes[s].respawn = 0;
+        ngx_processes[s].just_respawn = 0;
+        ngx_processes[s].detached = 1;
+        break;
+    }
+
+    if (s == ngx_last_process) {
+        ngx_last_process++;
+    }
+
+    return pid;
+}
+
+
+ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx)
+{
+    return ngx_spawn_process(cycle, ngx_execute_proc, ctx, ctx->name,
+                             NGX_PROCESS_DETACHED);
+}
+
+
+static void ngx_execute_proc(ngx_cycle_t *cycle, void *data)
+{
+    ngx_exec_ctx_t  *ctx = data;
+
+    if (execve(ctx->path, ctx->argv, ctx->envp) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "execve() failed while executing %s \"%s\"",
+                      ctx->name, ctx->path);
+    }
+
+    exit(1);
+}
+
+
+void ngx_process_get_status()
+{
+    int              status;
+    char            *process;
+    ngx_pid_t        pid;
+    ngx_err_t        err;
+    ngx_int_t        i;
+    ngx_uint_t       one;
+    struct timeval   tv;
+    one = 0;
+
+    for ( ;; ) {
+        pid = waitpid(-1, &status, WNOHANG);
+
+        if (pid == 0) {
+            return;
+        }
+
+        if (pid == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EINTR) {
+                continue;
+            }
+
+            if (err == NGX_ECHILD && one) {
+                return;
+            }
+
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, errno,
+                          "waitpid() failed");
+            return;
+        }
+
+
+        if (ngx_accept_mutex_ptr) {
+
+            /*
+             * unlock the accept mutex if the abnormally exited process
+             * held it
+             */
+
+            ngx_atomic_cmp_set(ngx_accept_mutex_ptr, pid, 0);
+        }
+
+
+        one = 1;
+        process = "unknown process";
+
+        for (i = 0; i < ngx_last_process; i++) {
+            if (ngx_processes[i].pid == pid) {
+                ngx_processes[i].status = status;
+                ngx_processes[i].exited = 1;
+                process = ngx_processes[i].name;
+                break;
+            }
+        }
+
+        if (WTERMSIG(status)) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "%s " PID_T_FMT " exited on signal %d%s",
+                          process, pid, WTERMSIG(status),
+                          WCOREDUMP(status) ? " (core dumped)" : "");
+
+        } else {
+            ngx_log_error(NGX_LOG_INFO, ngx_cycle->log, 0,
+                          "%s " PID_T_FMT " exited with code %d",
+                          process, pid, WEXITSTATUS(status));
+        }
+
+        if (WEXITSTATUS(status) == 2 && ngx_processes[i].respawn) {
+            ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                          "%s " PID_T_FMT
+                          " exited with fatal code %d and could not respawn",
+                          process, pid, WEXITSTATUS(status));
+            ngx_processes[i].respawn = 0;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_process.h
@@ -0,0 +1,67 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PROCESS_H_INCLUDED_
+#define _NGX_PROCESS_H_INCLUDED_
+
+
+typedef pid_t       ngx_pid_t;
+
+typedef void (*ngx_spawn_proc_pt) (ngx_cycle_t *cycle, void *data);
+
+typedef struct {
+    ngx_pid_t           pid;
+    int                 status;
+    ngx_socket_t        channel[2];
+
+    ngx_spawn_proc_pt   proc;
+    void               *data;
+    char               *name;
+
+    unsigned            respawn:1;
+    unsigned            just_respawn:1;
+    unsigned            detached:1;
+    unsigned            exiting:1;
+    unsigned            exited:1;
+} ngx_process_t;
+
+
+typedef struct {
+    char         *path;
+    char         *name;
+    char *const  *argv;
+    char *const  *envp;
+} ngx_exec_ctx_t;
+
+
+#define NGX_MAX_PROCESSES         1024
+
+#define NGX_PROCESS_NORESPAWN     -1
+#define NGX_PROCESS_RESPAWN       -2
+#define NGX_PROCESS_JUST_RESPAWN  -3
+#define NGX_PROCESS_DETACHED      -4
+
+
+#define ngx_getpid   getpid
+#define ngx_log_pid  ngx_pid
+
+ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
+                            ngx_spawn_proc_pt proc, void *data,
+                            char *name, ngx_int_t respawn);
+ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx);
+void ngx_process_get_status(void);
+
+#define ngx_sched_yield()  sched_yield()
+
+
+extern ngx_pid_t      ngx_pid;
+extern ngx_socket_t   ngx_channel;
+extern ngx_int_t      ngx_process_slot;
+extern ngx_int_t      ngx_last_process;
+extern ngx_process_t  ngx_processes[NGX_MAX_PROCESSES];
+
+
+#endif /* _NGX_PROCESS_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_process_cycle.c
@@ -0,0 +1,970 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_channel.h>
+
+
+static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
+                                       ngx_int_t type);
+static void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo);
+static ngx_uint_t ngx_reap_childs(ngx_cycle_t *cycle);
+static void ngx_master_exit(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx);
+static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data);
+static void ngx_channel_handler(ngx_event_t *ev);
+#if (NGX_THREADS)
+static void ngx_wakeup_worker_threads(ngx_cycle_t *cycle);
+static void *ngx_worker_thread_cycle(void *data);
+#endif
+
+
+ngx_uint_t    ngx_process;
+ngx_pid_t     ngx_pid;
+ngx_uint_t    ngx_threaded;
+
+sig_atomic_t  ngx_reap;
+sig_atomic_t  ngx_timer;
+sig_atomic_t  ngx_sigio;
+sig_atomic_t  ngx_terminate;
+sig_atomic_t  ngx_quit;
+ngx_uint_t    ngx_exiting;
+sig_atomic_t  ngx_reconfigure;
+sig_atomic_t  ngx_reopen;
+
+sig_atomic_t  ngx_change_binary;
+ngx_pid_t     ngx_new_binary;
+ngx_uint_t    ngx_inherited;
+ngx_uint_t    ngx_daemonized;
+
+sig_atomic_t  ngx_noaccept;
+ngx_uint_t    ngx_noaccepting;
+ngx_uint_t    ngx_restart;
+
+
+#if (NGX_THREADS)
+volatile ngx_thread_t  ngx_threads[NGX_MAX_THREADS];
+ngx_int_t              ngx_threads_n;
+#endif
+
+
+u_char  master_process[] = "master process";
+
+
+void ngx_master_process_cycle(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx)
+{
+    char              *title;
+    u_char            *p;
+    size_t             size;
+    ngx_int_t          i;
+    sigset_t           set;
+    struct timeval     tv;
+    struct itimerval   itv;
+    ngx_uint_t         live;
+    ngx_msec_t         delay;
+    ngx_core_conf_t   *ccf;
+
+    sigemptyset(&set);
+    sigaddset(&set, SIGCHLD);
+    sigaddset(&set, SIGALRM);
+    sigaddset(&set, SIGIO);
+    sigaddset(&set, SIGINT);
+    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
+
+    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "sigprocmask() failed");
+    }
+
+    sigemptyset(&set);
+
+
+    size = sizeof(master_process);
+
+    for (i = 0; i < ctx->argc; i++) {
+        size += ngx_strlen(ctx->argv[i]) + 1;
+    }
+
+    title = ngx_palloc(cycle->pool, size);
+
+    p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);
+    for (i = 0; i < ctx->argc; i++) {
+        *p++ = ' ';
+        p = ngx_cpystrn(p, (u_char *) ctx->argv[i], size);
+    }
+
+    ngx_setproctitle(title);
+
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    ngx_start_worker_processes(cycle, ccf->worker_processes,
+                               NGX_PROCESS_RESPAWN);
+
+    ngx_new_binary = 0;
+    delay = 0;
+    live = 1;
+
+    for ( ;; ) {
+        if (delay) {
+            delay *= 2;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                           "temination cycle: %d", delay);
+
+            itv.it_interval.tv_sec = 0;
+            itv.it_interval.tv_usec = 0;
+            itv.it_value.tv_sec = delay / 1000;
+            itv.it_value.tv_usec = (delay % 1000 ) * 1000;
+
+            if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                              "setitimer() failed");
+            }
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
+
+        sigsuspend(&set);
+
+        ngx_gettimeofday(&tv);
+        ngx_time_update(tv.tv_sec);
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "wake up");
+
+        if (ngx_reap) {
+            ngx_reap = 0;
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap childs");
+
+            live = ngx_reap_childs(cycle);
+        }
+
+        if (!live && (ngx_terminate || ngx_quit)) {
+            ngx_master_exit(cycle, ctx);
+        }
+
+        if (ngx_terminate) {
+            if (delay == 0) {
+                delay = 50;
+            }
+
+            if (delay > 1000) {
+                ngx_signal_worker_processes(cycle, SIGKILL);
+            } else {
+                ngx_signal_worker_processes(cycle,
+                                       ngx_signal_value(NGX_TERMINATE_SIGNAL));
+            }
+
+            continue;
+        }
+
+        if (ngx_quit) {
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+            continue;
+        }
+
+        if (ngx_timer) {
+            ngx_timer = 0;
+            ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                       NGX_PROCESS_JUST_RESPAWN);
+            live = 1;
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+        }
+
+        if (ngx_reconfigure) {
+            ngx_reconfigure = 0;
+
+            if (ngx_new_binary) {
+                ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "start new workers");
+
+                ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                           NGX_PROCESS_RESPAWN);
+                ngx_noaccepting = 0;
+
+                continue;
+            }
+
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "reconfiguring");
+
+            cycle = ngx_init_cycle(cycle);
+            if (cycle == NULL) {
+                cycle = (ngx_cycle_t *) ngx_cycle;
+                continue;
+            }
+
+            ngx_cycle = cycle;
+            ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
+                                                   ngx_core_module);
+            ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                       NGX_PROCESS_JUST_RESPAWN);
+            live = 1;
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+        }
+
+        if (ngx_restart) {
+            ngx_restart = 0;
+            ngx_start_worker_processes(cycle, ccf->worker_processes,
+                                       NGX_PROCESS_RESPAWN);
+            live = 1;
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, ccf->user);
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_REOPEN_SIGNAL));
+        }
+
+        if (ngx_change_binary) {
+            ngx_change_binary = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "changing binary");
+            ngx_new_binary = ngx_exec_new_binary(cycle, ctx->argv);
+        }
+
+        if (ngx_noaccept) {
+            ngx_noaccept = 0;
+            ngx_noaccepting = 1;
+            ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+        }
+    }
+}
+
+
+void ngx_single_process_cycle(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx)
+{
+    ngx_uint_t  i;
+
+#if 0
+    ngx_setproctitle("single worker process");
+#endif
+
+    ngx_init_temp_number();
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->init_process) {
+            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(2);
+            }
+        }
+    }
+
+    for ( ;; ) {
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+        ngx_process_events(cycle);
+
+        if (ngx_terminate || ngx_quit) {
+            ngx_master_exit(cycle, ctx);
+        }
+
+        if (ngx_reconfigure) {
+            ngx_reconfigure = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "reconfiguring");
+
+            cycle = ngx_init_cycle(cycle);
+            if (cycle == NULL) {
+                cycle = (ngx_cycle_t *) ngx_cycle;
+                continue;
+            }
+
+            ngx_cycle = cycle;
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "reopening logs");
+            ngx_reopen_files(cycle, (ngx_uid_t) -1);
+        }
+    }
+}
+
+
+static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
+                                       ngx_int_t type)
+{
+    ngx_int_t         i;
+    ngx_channel_t     ch;
+    struct itimerval  itv;
+
+    ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "start worker processes");
+
+    ch.command = NGX_CMD_OPEN_CHANNEL;
+
+    while (n--) {
+        ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
+                          "worker process", type);
+
+        ch.pid = ngx_processes[ngx_process_slot].pid;
+        ch.slot = ngx_process_slot;
+        ch.fd = ngx_processes[ngx_process_slot].channel[0];
+
+        for (i = 0; i < ngx_last_process; i++) {
+
+            if (i == ngx_process_slot
+                || ngx_processes[i].pid == -1
+                || ngx_processes[i].channel[0] == -1)
+            {
+                continue;
+            }
+
+            ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                           "pass channel s:%d pid:" PID_T_FMT
+                           " fd:%d to s:%d pid:" PID_T_FMT " fd:%d",
+                           ch.slot, ch.pid, ch.fd,
+                           i, ngx_processes[i].pid,
+                           ngx_processes[i].channel[0]);
+
+            /* TODO: NGX_AGAIN */
+
+            ngx_write_channel(ngx_processes[i].channel[0],
+                              &ch, sizeof(ngx_channel_t), cycle->log);
+        }
+    }
+
+    /*
+     * we have to limit the maximum life time of the worker processes
+     * by 10 days because our millisecond event timer is limited
+     * by 24 days on 32-bit platforms
+     */
+
+    itv.it_interval.tv_sec = 0;
+    itv.it_interval.tv_usec = 0;
+    itv.it_value.tv_sec = 10 * 24 * 60 * 60;
+    itv.it_value.tv_usec = 0;
+
+    if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "setitimer() failed");
+    }
+}
+
+
+static void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
+{
+    ngx_int_t      i;
+    ngx_err_t      err;
+    ngx_channel_t  ch;
+
+
+    switch (signo) {
+
+    case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
+        ch.command = NGX_CMD_QUIT;
+        break;
+
+    case ngx_signal_value(NGX_TERMINATE_SIGNAL):
+        ch.command = NGX_CMD_TERMINATE;
+        break;
+
+    case ngx_signal_value(NGX_REOPEN_SIGNAL):
+        ch.command = NGX_CMD_REOPEN;
+        break;
+
+    default:
+        ch.command = 0;
+    }
+
+    ch.fd = -1;
+
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "child: %d " PID_T_FMT " e:%d t:%d d:%d r:%d j:%d",
+                       i,
+                       ngx_processes[i].pid,
+                       ngx_processes[i].exiting,
+                       ngx_processes[i].exited,
+                       ngx_processes[i].detached,
+                       ngx_processes[i].respawn,
+                       ngx_processes[i].just_respawn);
+
+        if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
+            continue;
+        }
+
+        if (ngx_processes[i].just_respawn) {
+            ngx_processes[i].just_respawn = 0;
+            continue;
+        }
+
+        if (ngx_processes[i].exiting
+            && signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
+        {
+            continue;
+        }
+
+        if (ch.command) {
+            if (ngx_write_channel(ngx_processes[i].channel[0],
+                           &ch, sizeof(ngx_channel_t), cycle->log) == NGX_OK)
+            {
+                if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+                    ngx_processes[i].exiting = 1;
+                }
+
+                continue;
+            }
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "kill (" PID_T_FMT ", %d)" ,
+                       ngx_processes[i].pid, signo);
+
+        if (kill(ngx_processes[i].pid, signo) == -1) {
+            err = ngx_errno;
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          "kill(%d, %d) failed",
+                          ngx_processes[i].pid, signo);
+
+            if (err == NGX_ESRCH) {
+                ngx_processes[i].exited = 1;
+                ngx_processes[i].exiting = 0;
+                ngx_reap = 1;
+            }
+
+            continue;
+        }
+
+        if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
+            ngx_processes[i].exiting = 1;
+        }
+    }
+}
+
+
+static ngx_uint_t ngx_reap_childs(ngx_cycle_t *cycle)
+{
+    ngx_int_t      i, n;
+    ngx_uint_t     live;
+    ngx_channel_t  ch;
+
+    ch.command = NGX_CMD_CLOSE_CHANNEL;
+    ch.fd = -1;
+
+    live = 0;
+    for (i = 0; i < ngx_last_process; i++) {
+
+        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "child: %d " PID_T_FMT " e:%d t:%d d:%d r:%d j:%d",
+                       i,
+                       ngx_processes[i].pid,
+                       ngx_processes[i].exiting,
+                       ngx_processes[i].exited,
+                       ngx_processes[i].detached,
+                       ngx_processes[i].respawn,
+                       ngx_processes[i].just_respawn);
+
+        if (ngx_processes[i].pid == -1) {
+            continue;
+        }
+
+        if (ngx_processes[i].exited) {
+
+            if (!ngx_processes[i].detached) {
+                ngx_close_channel(ngx_processes[i].channel, cycle->log);
+
+                ngx_processes[i].channel[0] = -1;
+                ngx_processes[i].channel[1] = -1;
+
+                ch.pid = ngx_processes[i].pid;
+                ch.slot = i;
+
+                for (n = 0; n < ngx_last_process; n++) {
+                    if (ngx_processes[n].exited
+                        || ngx_processes[n].pid == -1
+                        || ngx_processes[n].channel[0] == -1)
+                    {
+                        continue;
+                    }
+
+                    ngx_log_debug3(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                       "pass close channel s:%d pid:" PID_T_FMT
+                       " to:" PID_T_FMT, ch.slot, ch.pid, ngx_processes[n].pid);
+
+                    /* TODO: NGX_AGAIN */
+
+                    ngx_write_channel(ngx_processes[n].channel[0],
+                                      &ch, sizeof(ngx_channel_t), cycle->log);
+                }
+            }
+
+            if (ngx_processes[i].respawn
+                && !ngx_processes[i].exiting
+                && !ngx_terminate
+                && !ngx_quit)
+            {
+                if (ngx_spawn_process(cycle, ngx_processes[i].proc,
+                                      ngx_processes[i].data,
+                                      ngx_processes[i].name, i)
+                                                                  == NGX_ERROR)
+                {
+                    ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+                                  "can not respawn %s", ngx_processes[i].name);
+                    continue;
+                }
+
+                live = 1;
+
+                continue;
+            }
+
+            if (ngx_processes[i].pid == ngx_new_binary) {
+                ngx_new_binary = 0;
+                if (ngx_noaccepting) {
+                    ngx_restart = 1;
+                    ngx_noaccepting = 0;
+                }
+            }
+
+            if (i == ngx_last_process - 1) {
+                ngx_last_process--;
+
+            } else {
+                ngx_processes[i].pid = -1;
+            }
+
+        } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) {
+            live = 1;
+        }
+    }
+
+    return live;
+}
+
+
+static void ngx_master_exit(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx)
+{
+    ngx_delete_pidfile(cycle);
+
+    ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "exit");
+
+    ngx_destroy_pool(cycle->pool);
+
+    exit(0);
+}
+
+
+static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
+{
+    sigset_t           set;
+    ngx_err_t          err;
+    ngx_int_t          n;
+    ngx_uint_t         i;
+    struct timeval     tv;
+    ngx_listening_t   *ls;
+    ngx_core_conf_t   *ccf;
+    ngx_connection_t  *c;
+
+
+    ngx_gettimeofday(&tv);
+
+    ngx_start_msec = (ngx_epoch_msec_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
+    ngx_old_elapsed_msec = 0;
+    ngx_elapsed_msec = 0;
+
+
+    ngx_process = NGX_PROCESS_WORKER;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    if (ccf->group != (gid_t) NGX_CONF_UNSET) {
+        if (setgid(ccf->group) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "setgid(%d) failed", ccf->group);
+            /* fatal */
+            exit(2);
+        }
+    }
+
+    if (ccf->user != (uid_t) NGX_CONF_UNSET) {
+        if (setuid(ccf->user) == -1) {
+            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
+                          "setuid(%d) failed", ccf->user);
+            /* fatal */
+            exit(2);
+        }
+    }
+
+#if (HAVE_PR_SET_DUMPABLE)
+
+    /* allow coredump after setuid() in Linux 2.4.x */
+
+    if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "prctl(PR_SET_DUMPABLE) failed");
+    }
+
+#endif
+
+    sigemptyset(&set);
+
+    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "sigprocmask() failed");
+    }
+
+    ngx_init_temp_number();
+
+    /*
+     * disable deleting previous events for the listening sockets because
+     * in the worker processes there are no events at all at this point
+     */ 
+    ls = cycle->listening.elts;
+    for (i = 0; i < cycle->listening.nelts; i++) {
+        ls[i].remain = 0;
+    }
+
+    for (i = 0; ngx_modules[i]; i++) {
+        if (ngx_modules[i]->init_process) {
+            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
+                /* fatal */
+                exit(2);
+            }
+        }
+    }
+
+    for (n = 0; n < ngx_last_process; n++) {
+
+        if (ngx_processes[n].pid == -1) {
+            continue;
+        }
+
+        if (n == ngx_process_slot) {
+            continue;
+        }
+
+        if (ngx_processes[n].channel[1] == -1) {
+            continue;
+        }
+
+        if (close(ngx_processes[n].channel[1]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                          "close() failed");
+        }
+    }
+
+    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+                      "close() failed");
+    }
+
+#if 0
+    ngx_last_process = 0;
+#endif
+
+    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
+                                             ngx_channel_handler) == NGX_ERROR)
+    {
+        /* fatal */
+        exit(2);
+    }
+
+    ngx_setproctitle("worker process");
+
+#if (NGX_THREADS)
+
+    if (ngx_time_mutex_init(cycle->log) == NGX_ERROR) {
+        /* fatal */
+        exit(2);
+    }
+
+    if (ngx_threads_n) {
+        if (ngx_init_threads(ngx_threads_n,
+                                   ccf->thread_stack_size, cycle) == NGX_ERROR)
+        {
+            /* fatal */
+            exit(2);
+        }
+
+        err = ngx_thread_key_create(&ngx_core_tls_key);
+        if (err != 0) {
+            ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                          ngx_thread_key_create_n " failed");
+            /* fatal */
+            exit(2);
+        }
+
+        for (n = 0; n < ngx_threads_n; n++) {
+
+            if (!(ngx_threads[n].cv = ngx_cond_init(cycle->log))) {
+                /* fatal */
+                exit(2);
+            }
+
+            if (ngx_create_thread((ngx_tid_t *) &ngx_threads[n].tid,
+                                  ngx_worker_thread_cycle,
+                                  (void *) &ngx_threads[n], cycle->log) != 0)
+            {
+                /* fatal */
+                exit(2);
+            }
+        }
+    }
+
+#endif
+
+    for ( ;; ) {
+        if (ngx_exiting
+            && ngx_event_timer_rbtree == &ngx_event_timer_sentinel)
+        {
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "exiting");
+
+
+#if (NGX_THREADS)
+            ngx_terminate = 1;
+
+            ngx_wakeup_worker_threads(cycle);
+#endif
+
+            /*
+             * we do not destroy cycle->pool here because a signal handler
+             * that uses cycle->log can be called at this point
+             */
+            exit(0);
+        }
+
+        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
+
+        ngx_process_events(cycle);
+
+        if (ngx_terminate) {
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "exiting");
+
+#if (NGX_THREADS)
+            ngx_wakeup_worker_threads(cycle);
+#endif
+
+            /*
+             * we do not destroy cycle->pool here because a signal handler
+             * that uses cycle->log can be called at this point
+             */
+            exit(0);
+        }
+
+        if (ngx_quit) {
+            ngx_quit = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
+                          "gracefully shutting down");
+            ngx_setproctitle("worker process is shutting down");
+
+            if (!ngx_exiting) {
+                ngx_close_listening_sockets(cycle);
+                ngx_exiting = 1;
+            }
+        }
+
+        if (ngx_reopen) {
+            ngx_reopen = 0;
+            ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "reopen logs");
+            ngx_reopen_files(cycle, -1);
+        }
+    }
+}
+
+
+static void ngx_channel_handler(ngx_event_t *ev)
+{
+    ngx_int_t          n;
+    ngx_channel_t      ch;
+    ngx_connection_t  *c;
+
+    c = ev->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel handler");
+
+    n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0, "channel: %d", n);
+
+    if (n <= 0) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                   "channel command: %d", ch.command);
+
+    switch (ch.command) {
+
+    case NGX_CMD_QUIT:
+        ngx_quit = 1;
+        break;
+
+    case NGX_CMD_TERMINATE:
+        ngx_terminate = 1;
+        break;
+
+    case NGX_CMD_REOPEN:
+        ngx_reopen = 1;
+        break;
+
+    case NGX_CMD_OPEN_CHANNEL:
+
+        ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                       "get channel s:%d pid:" PID_T_FMT " fd:%d",
+                       ch.slot, ch.pid, ch.fd);
+
+        ngx_processes[ch.slot].pid = ch.pid;
+        ngx_processes[ch.slot].channel[0] = ch.fd;
+        break;
+
+    case NGX_CMD_CLOSE_CHANNEL:
+
+        ngx_log_debug4(NGX_LOG_DEBUG_CORE, ev->log, 0,
+                       "close channel s:%d pid:" PID_T_FMT " our:" PID_T_FMT
+                       " fd:%d",
+                       ch.slot, ch.pid, ngx_processes[ch.slot].pid,
+                       ngx_processes[ch.slot].channel[0]);
+
+        if (close(ngx_processes[ch.slot].channel[0]) == -1) {
+            ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, "close() failed");
+        }
+
+        ngx_processes[ch.slot].channel[0] = -1;
+        break;
+    }
+}
+
+
+#if (NGX_THREADS)
+
+static void ngx_wakeup_worker_threads(ngx_cycle_t *cycle)
+{
+    ngx_int_t   i;
+    ngx_uint_t  live;
+
+    for ( ;; ) {
+
+        live = 0;
+
+        for (i = 0; i < ngx_threads_n; i++) {
+            if (ngx_threads[i].state < NGX_THREAD_EXIT) {
+                ngx_cond_signal(ngx_threads[i].cv);
+
+                if (ngx_threads[i].cv->tid == (ngx_tid_t) -1) {
+                    ngx_threads[i].state = NGX_THREAD_DONE;
+                } else {
+                    live = 1;
+                }
+            }
+
+            if (ngx_threads[i].state == NGX_THREAD_EXIT) {
+                ngx_thread_join(ngx_threads[i].tid, NULL);
+                ngx_threads[i].state = NGX_THREAD_DONE;
+            }
+        }
+
+        if (live == 0) {
+            ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                           "all worker threads are joined");
+
+            /* STUB */
+            ngx_done_events(cycle);
+            ngx_mutex_destroy(ngx_event_timer_mutex);
+            ngx_mutex_destroy(ngx_posted_events_mutex);
+
+            return;
+        }
+
+        ngx_sched_yield();
+    }
+}
+
+
+static void* ngx_worker_thread_cycle(void *data)
+{
+    ngx_thread_t  *thr = data;
+
+    sigset_t          set;
+    ngx_err_t         err;
+    ngx_core_tls_t   *tls;
+    ngx_cycle_t      *cycle;
+    struct timeval    tv;
+
+    thr->cv->tid = ngx_thread_self();
+
+    cycle = (ngx_cycle_t *) ngx_cycle;
+
+    sigemptyset(&set);
+    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
+    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
+
+    err = ngx_thread_sigmask(SIG_BLOCK, &set, NULL);
+    if (err) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                      ngx_thread_sigmask_n " failed");
+        return (void *) 1;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                   "thread " TID_T_FMT " started", ngx_thread_self());
+
+    ngx_setthrtitle("worker thread");
+
+    if (!(tls = ngx_calloc(sizeof(ngx_core_tls_t), cycle->log))) {
+        return (void *) 1;
+    }
+
+    err = ngx_thread_set_tls(ngx_core_tls_key, tls);
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                      ngx_thread_set_tls_n " failed");
+        return (void *) 1;
+    }
+
+    if (ngx_mutex_lock(ngx_posted_events_mutex) == NGX_ERROR) {
+        return (void *) 1;
+    }
+
+    for ( ;; ) {
+        thr->state = NGX_THREAD_FREE;
+
+        if (ngx_cond_wait(thr->cv, ngx_posted_events_mutex) == NGX_ERROR) {
+            return (void *) 1;
+        }
+
+        if (ngx_terminate) {
+            thr->state = NGX_THREAD_EXIT;
+
+            ngx_mutex_unlock(ngx_posted_events_mutex);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+                           "thread %d is done", ngx_thread_self());
+
+            return (void *) 0;
+        }
+
+        thr->state = NGX_THREAD_BUSY;
+
+        if (ngx_event_thread_process_posted(cycle) == NGX_ERROR) {
+            return (void *) 1;
+        }
+
+        if (ngx_event_thread_process_posted(cycle) == NGX_ERROR) {
+            return (void *) 1;
+        }
+
+        if (ngx_process_changes) {
+            if (ngx_process_changes(cycle, 1) == NGX_ERROR) {
+                return (void *) 1;
+            }
+        }
+    }
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_process_cycle.h
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_
+#define _NGX_PROCESS_CYCLE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#define NGX_CMD_OPEN_CHANNEL   1
+#define NGX_CMD_CLOSE_CHANNEL  2
+#define NGX_CMD_QUIT           3
+#define NGX_CMD_TERMINATE      4
+#define NGX_CMD_REOPEN         5
+
+
+typedef struct {
+    int           argc;
+    char *const  *argv;
+} ngx_master_ctx_t;
+
+
+#define NGX_PROCESS_SINGLE   0
+#define NGX_PROCESS_MASTER   1
+#define NGX_PROCESS_WORKER   2
+
+
+void ngx_master_process_cycle(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx);
+void ngx_single_process_cycle(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx);
+
+
+extern ngx_uint_t      ngx_process;
+extern ngx_pid_t       ngx_pid;
+extern ngx_pid_t       ngx_new_binary;
+extern ngx_uint_t      ngx_inherited;
+extern ngx_uint_t      ngx_daemonized;
+extern ngx_uint_t      ngx_threaded;
+extern ngx_uint_t      ngx_exiting;
+
+extern sig_atomic_t    ngx_reap;
+extern sig_atomic_t    ngx_timer;
+extern sig_atomic_t    ngx_sigio;
+extern sig_atomic_t    ngx_quit;
+extern sig_atomic_t    ngx_terminate;
+extern sig_atomic_t    ngx_noaccept;
+extern sig_atomic_t    ngx_reconfigure;
+extern sig_atomic_t    ngx_reopen;
+extern sig_atomic_t    ngx_change_binary;
+
+
+#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_pthread_thread.c
@@ -0,0 +1,273 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+static ngx_uint_t   nthreads;
+static ngx_uint_t   max_threads;
+
+
+static pthread_attr_t  thr_attr;
+
+
+int ngx_create_thread(ngx_tid_t *tid, void* (*func)(void *arg), void *arg,
+                      ngx_log_t *log)
+{
+    int  err;
+
+    if (nthreads >= max_threads) {
+        ngx_log_error(NGX_LOG_CRIT, log, 0,
+                      "no more than %d threads can be created", max_threads);
+        return NGX_ERROR;
+    }
+
+    err = pthread_create(tid, &thr_attr, func, arg);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, log, err, "pthread_create() failed");
+        return err;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0,
+                   "thread is created: " TID_T_FMT, *tid);
+
+    nthreads++;
+
+    return err;
+}
+
+
+ngx_int_t ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle)
+{   
+    int  err;
+
+    max_threads = n;
+
+    err = pthread_attr_init(&thr_attr);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                      "pthread_attr_init() failed");
+        return NGX_ERROR;
+    }
+
+    err = pthread_attr_setstacksize(&thr_attr, size);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
+                      "pthread_attr_setstacksize() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_threaded = 1;
+
+    return NGX_OK;
+}
+
+
+ngx_mutex_t *ngx_mutex_init(ngx_log_t *log, uint flags)
+{
+    int           err;
+    ngx_mutex_t  *m;
+
+    if (!(m = ngx_alloc(sizeof(ngx_mutex_t), log))) {
+        return NULL;
+    }
+    
+    m->log = log;
+
+    err = pthread_mutex_init(&m->mutex, NULL);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, err,
+                      "pthread_mutex_init() failed");
+        return NULL;
+    }
+
+    return m;
+}
+
+
+void ngx_mutex_destroy(ngx_mutex_t *m)
+{
+    int  err;
+
+    err = pthread_mutex_destroy(&m->mutex);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, err,
+                      "pthread_mutex_destroy(" PTR_FMT ") failed", m);
+    }
+
+    ngx_free(m);
+}
+
+
+ngx_int_t ngx_mutex_lock(ngx_mutex_t *m)
+{
+    int  err;
+
+    if (!ngx_threaded) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "lock mutex " PTR_FMT, m);
+
+    err = pthread_mutex_lock(&m->mutex);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, err,
+                      "pthread_mutex_lock(" PTR_FMT ") failed", m);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is locked", m);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_mutex_trylock(ngx_mutex_t *m)
+{
+    int  err;
+
+    if (!ngx_threaded) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "try lock mutex " PTR_FMT, m);
+
+    err = pthread_mutex_trylock(&m->mutex);
+
+    if (err == NGX_EBUSY) {
+        return NGX_AGAIN;
+    }
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, err,
+                      "pthread_mutex_trylock(" PTR_FMT ") failed", m);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is locked", m);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_mutex_unlock(ngx_mutex_t *m)
+{
+    int  err;
+
+    if (!ngx_threaded) {
+        return NGX_OK;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0, "unlock mutex " PTR_FMT, m);
+
+    err = pthread_mutex_unlock(&m->mutex);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, m->log, err,
+                      "pthread_mutex_unlock(" PTR_FMT ") failed", m);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is unlocked", m);
+
+    return NGX_OK;
+}
+
+
+ngx_cond_t *ngx_cond_init(ngx_log_t *log)
+{
+    int          err;
+    ngx_cond_t  *cv;
+
+    if (!(cv = ngx_alloc(sizeof(ngx_cond_t), log))) {
+        return NULL;
+    }
+    
+    cv->log = log;
+
+    err = pthread_cond_init(&cv->cond, NULL);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+                      "pthread_cond_init() failed");
+        return NULL;
+    }
+
+    return cv;
+}
+
+
+void ngx_cond_destroy(ngx_cond_t *cv)
+{
+    int  err;
+
+    err = pthread_cond_destroy(&cv->cond);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+                      "pthread_cond_destroy(" PTR_FMT ") failed", cv);
+    }
+
+    ngx_free(cv);
+}
+
+
+ngx_int_t ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m)
+{
+    int  err;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " wait", cv);
+
+    err = pthread_cond_wait(&cv->cond, &m->mutex);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+                      "pthread_cond_wait(" PTR_FMT ") failed", cv);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " is waked up", cv);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+                   "mutex " PTR_FMT " is locked", m);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t ngx_cond_signal(ngx_cond_t *cv)
+{
+    int  err;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " to signal", cv);
+
+    err = pthread_cond_signal(&cv->cond);
+
+    if (err != 0) {
+        ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+                      "pthread_cond_signal(" PTR_FMT ") failed", cv);
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0,
+                   "cv " PTR_FMT " is signaled", cv);
+
+    return NGX_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_readv_chain.c
@@ -0,0 +1,219 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (HAVE_KQUEUE)
+
+ssize_t ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain)
+{
+    u_char        *prev;
+    ssize_t        n, size;
+    ngx_err_t      err;
+    ngx_array_t    io;
+    ngx_event_t   *rev;
+    struct iovec  *iov;
+
+    rev = c->read; 
+
+    if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "readv: eof:%d, avail:%d, err:%d",
+                       rev->pending_eof, rev->available, rev->kq_errno);
+
+        if (rev->available == 0) {
+            if (rev->pending_eof) {
+                rev->ready = 0;
+                rev->eof = 1;
+
+                ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                              "kevent() reported about an closed connection");
+
+                if (rev->kq_errno) {
+                    rev->error = 1;
+                    ngx_set_socket_errno(rev->kq_errno);
+                    return NGX_ERROR;
+                }
+
+                return 0;
+
+            } else {
+                return NGX_AGAIN;
+            }
+        }
+    }
+
+    prev = NULL;
+    iov = NULL;
+    size = 0;
+
+    ngx_init_array(io, c->pool, 10, sizeof(struct iovec), NGX_ERROR);
+
+    /* coalesce the neighbouring bufs */
+
+    while (chain) {
+        if (prev == chain->buf->last) {
+            iov->iov_len += chain->buf->end - chain->buf->last;
+
+        } else {
+            ngx_test_null(iov, ngx_push_array(&io), NGX_ERROR);
+            iov->iov_base = (void *) chain->buf->last;
+            iov->iov_len = chain->buf->end - chain->buf->last;
+        }
+
+        size += chain->buf->end - chain->buf->last;
+        prev = chain->buf->end;
+        chain = chain->next;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "readv: %d, last:%d", io.nelts, iov->iov_len);
+
+    rev = c->read;
+
+    do {
+        n = readv(c->fd, (struct iovec *) io.elts, io.nelts);
+
+        if (n >= 0) {
+            if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+                rev->available -= n;
+
+                /*
+                 * rev->available can be negative here because some additional
+                 * bytes can be received between kevent() and recv()
+                 */
+
+                if (rev->available <= 0) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    if (rev->available < 0) {
+                        rev->available = 0;
+                    }
+                }
+
+                return n;
+            }
+
+            if (n < size) {
+                rev->ready = 0;
+            }
+
+            if (n == 0) {
+                rev->eof = 1;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "readv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "readv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR){
+        c->read->error = 1;
+    }
+
+    return n;
+}
+
+#else /* ! NAVE_KQUEUE */
+
+ssize_t ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain)
+{
+    u_char        *prev;
+    ssize_t        n, size;
+    ngx_err_t      err;
+    ngx_array_t    io;
+    ngx_event_t   *rev;
+    struct iovec  *iov;
+
+    prev = NULL;
+    iov = NULL;
+    size = 0;
+
+    ngx_init_array(io, c->pool, 10, sizeof(struct iovec), NGX_ERROR);
+
+    /* coalesce the neighbouring bufs */
+
+    while (chain) {
+        if (prev == chain->buf->last) {
+            iov->iov_len += chain->buf->end - chain->buf->last;
+
+        } else {
+            ngx_test_null(iov, ngx_push_array(&io), NGX_ERROR);
+            iov->iov_base = chain->buf->last;
+            iov->iov_len = chain->buf->end - chain->buf->last;
+        }
+
+        size += chain->buf->end - chain->buf->last;
+        prev = chain->buf->end;
+        chain = chain->next;
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "readv: %d:%d", io.nelts, iov->iov_len);
+
+    rev = c->read;
+
+    do {
+        n = readv(c->fd, (struct iovec *) io.elts, io.nelts);
+
+        if (n == 0) {
+            rev->ready = 0;
+            rev->eof = 1;
+
+            return n;
+
+        } else if (n > 0) {
+
+            if (n < size && !(ngx_event_flags & NGX_HAVE_GREEDY_EVENT)) {
+                rev->ready = 0;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "readv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "readv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR){
+        c->read->error = 1;
+    }
+
+    return n;
+}
+
+#endif /* NAVE_KQUEUE */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_recv.c
@@ -0,0 +1,173 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#if (HAVE_KQUEUE)
+
+ssize_t ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *rev;
+
+    rev = c->read;
+
+    if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: eof:%d, avail:%d, err:%d",
+                       rev->pending_eof, rev->available, rev->kq_errno);
+
+        if (rev->available == 0) {
+            if (rev->pending_eof) {
+                rev->ready = 0;
+                rev->eof = 1;
+
+                ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno,
+                              "kevent() reported about an closed connection");
+
+                if (rev->kq_errno) {
+                    rev->error = 1;
+                    ngx_set_socket_errno(rev->kq_errno);
+
+                    if (rev->kq_errno == NGX_ECONNRESET
+                        && c->log_error == NGX_ERROR_IGNORE_ECONNRESET)
+                    {
+                        return 0;
+                    }
+
+                    return NGX_ERROR;
+                }
+
+                return 0;
+
+            } else {
+                return NGX_AGAIN;
+            }
+        }
+    }
+
+    do {
+        n = recv(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: fd:%d %d of %d", c->fd, n, size);
+
+        if (n >= 0) {
+            if (ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) {
+                rev->available -= n;
+
+                /*
+                 * rev->available can be negative here because some additional
+                 * bytes can be received between kevent() and recv()
+                 */
+
+                if (rev->available <= 0) {
+                    if (!rev->pending_eof) {
+                        rev->ready = 0;
+                    }
+
+                    if (rev->available < 0) {
+                        rev->available = 0;
+                    }
+                }
+
+                return n;
+            }
+
+            if ((size_t) n < size) {
+                rev->ready = 0;
+            }
+
+            if (n == 0) {
+                rev->eof = 1;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "recv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "recv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR){
+        rev->error = 1;
+    }
+
+    return n;
+}
+
+#else /* ! NAVE_KQUEUE */
+
+ssize_t ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *rev;
+
+    rev = c->read;
+
+    do {
+        n = recv(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "recv: fd:%d %d of %d", c->fd, n, size);
+
+        if (n == 0) {
+            rev->ready = 0;
+            rev->eof = 1;
+            return n;
+
+        } else if (n > 0) {
+
+            if ((size_t) n < size
+                && !(ngx_event_flags & NGX_HAVE_GREEDY_EVENT))
+            {
+                rev->ready = 0;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "recv() not ready");
+            n = NGX_AGAIN;
+
+        } else {
+            n = ngx_connection_error(c, err, "recv() failed");
+            break;
+        }
+
+    } while (err == NGX_EINTR);
+
+    rev->ready = 0;
+
+    if (n == NGX_ERROR){
+        rev->error = 1;
+    }
+
+    return n;
+}
+
+#endif /* NAVE_KQUEUE */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_send.c
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+ssize_t ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
+{
+    ssize_t       n;
+    ngx_err_t     err;
+    ngx_event_t  *wev;
+
+    wev = c->write;
+
+#if (HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) && wev->pending_eof) {
+        ngx_log_error(NGX_LOG_INFO, c->log, wev->kq_errno,
+                      "kevent() reported about an closed connection");
+
+        wev->error = 1;
+        return NGX_ERROR;
+    }
+
+#endif
+
+    for ( ;; ) {
+        n = send(c->fd, buf, size, 0);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "send: fd:%d %d of %d", c->fd, n, size);
+
+        if (n > 0) {
+            if (n < (ssize_t) size) {
+                wev->ready = 0;
+            }
+
+            return n;
+        }
+
+        err = ngx_socket_errno;
+
+        if (n == 0) {
+            ngx_log_error(NGX_LOG_ALERT, c->log, err, "send() returned zero");
+            wev->ready = 0;
+            return n;
+        }
+
+        if (err == NGX_EAGAIN || err == NGX_EINTR) {
+            wev->ready = 0;
+
+            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                           "send() not ready");
+
+            if (err == NGX_EAGAIN) {
+                return NGX_AGAIN;
+            }
+
+        } else {
+            wev->error = 1;
+            ngx_connection_error(c, err, "recv() failed");
+            return NGX_ERROR;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_shared.c
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+#if (HAVE_MAP_ANON)
+
+void *ngx_create_shared_memory(size_t size, ngx_log_t *log)
+{
+    void  *p;
+
+    p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
+
+    if (p == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "mmap(MAP_ANON|MAP_SHARED, " SIZE_T_FMT ") failed",
+                      size);
+        return NULL;
+    }
+
+    return p;
+}
+
+#elif (HAVE_MAP_DEVZERO)
+
+void *ngx_create_shared_memory(size_t size, ngx_log_t *log)
+{
+    void      *p;
+    ngx_fd_t   fd;
+
+    fd = open("/dev/zero", O_RDWR);
+
+    if (fd == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "open(/dev/zero) failed");
+        return NULL;
+    }
+
+    p = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+
+    if (p == MAP_FAILED) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "mmap(/dev/zero, MAP_SHARED, " SIZE_T_FMT ") failed",
+                      size);
+        p = NULL;
+    }
+
+    if (close(fd) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() failed");
+    }
+
+    return p;
+}
+
+#elif (HAVE_SYSVSHM)
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+
+void *ngx_create_shared_memory(size_t size, ngx_log_t *log)
+{
+    int    id;
+    void  *p;
+
+    id = shmget(IPC_PRIVATE, size, (SHM_R|SHM_W|IPC_CREAT));
+
+    if (id == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "shmget(" SIZE_T_FMT ") failed", size);
+        return NULL;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "shmget id: %d", id);
+
+    p = shmat(id, NULL, 0);
+
+    if (p == (void *) -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "shmat() failed");
+        p = NULL;
+    }
+
+    if (shmctl(id, IPC_RMID, NULL) == -1) {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "shmctl(IPC_RMID) failed");
+        p = NULL;
+    }
+
+    return p;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_shared.h
@@ -0,0 +1,18 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SHARED_H_INCLUDED_
+#define _NGX_SHARED_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void *ngx_create_shared_memory(size_t size, ngx_log_t *log);
+
+
+#endif /* _NGX_SHARED_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_socket.c
@@ -0,0 +1,104 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+/*
+ * ioctl(FIONBIO) sets a blocking mode with the single syscall
+ * while fcntl(F_SETFL, ~O_NONBLOCK) needs to learn before
+ * the previous state using fcntl(F_GETFL).
+ *
+ * ioctl() and fcntl() are syscalls on at least FreeBSD 2.x, Linux 2.2
+ * and Solaris 7.
+ *
+ * ioctl() in Linux 2.4 and 2.6 uses BKL, however fcntl(F_SETFL) uses it too.
+ */
+
+
+#if (HAVE_FIONBIO)
+
+int ngx_nonblocking(ngx_socket_t s)
+{
+    u_long  nb;
+
+    nb = 1;
+
+    return ioctl(s, FIONBIO, &nb);
+}
+
+
+int ngx_blocking(ngx_socket_t s)
+{
+    u_long  nb;
+
+    nb = 0;
+
+    return ioctl(s, FIONBIO, &nb);
+}
+
+#endif
+
+
+#ifdef __FreeBSD__
+
+int ngx_tcp_nopush(ngx_socket_t s)
+{
+    int  tcp_nopush;
+
+    tcp_nopush = 1;
+
+    return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                      (const void *) &tcp_nopush, sizeof(int));
+}
+
+
+int ngx_tcp_push(ngx_socket_t s)
+{
+    int  tcp_nopush;
+
+    tcp_nopush = 0;
+
+    return setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,
+                      (const void *) &tcp_nopush, sizeof(int));
+}
+
+#elif __linux__
+
+int ngx_tcp_nopush(ngx_socket_t s)
+{
+    int  cork;
+
+    cork = 1;
+
+    return setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                      (const void *) &cork, sizeof(int));
+}
+
+int ngx_tcp_push(ngx_socket_t s)
+{
+    int  cork;
+
+    cork = 0;
+
+    return setsockopt(s, IPPROTO_TCP, TCP_CORK,
+                      (const void *) &cork, sizeof(int));
+}
+
+#else
+
+int ngx_tcp_nopush(ngx_socket_t s)
+{
+    return NGX_OK;
+}
+
+int ngx_tcp_push(ngx_socket_t s)
+{
+    return NGX_OK;
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_socket.h
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOCKET_H_INCLUDED_
+#define _NGX_SOCKET_H_INCLUDED_
+
+
+#include <ngx_config.h>
+
+
+#define NGX_WRITE_SHUTDOWN SHUT_WR
+
+typedef int  ngx_socket_t;
+
+#define ngx_socket(af, type, proto, flags)   socket(af, type, proto)
+#define ngx_socket_n        "socket()"
+
+
+#if (HAVE_FIONBIO)
+
+int ngx_nonblocking(ngx_socket_t s);
+int ngx_blocking(ngx_socket_t s);
+
+#define ngx_nonblocking_n   "ioctl(FIONBIO)"
+#define ngx_blocking_n      "ioctl(!FIONBIO)"
+
+#else
+
+#define ngx_nonblocking(s)  fcntl(s, F_SETFL, O_NONBLOCK)
+#define ngx_nonblocking_n   "fcntl(O_NONBLOCK)"
+
+#endif
+
+int ngx_tcp_nopush(ngx_socket_t s);
+int ngx_tcp_push(ngx_socket_t s);
+
+#ifdef __linux__
+
+#define ngx_tcp_nopush_n   "setsockopt(TCP_CORK)"
+#define ngx_tcp_push_n     "setsockopt(!TCP_CORK)"
+
+#else
+
+#define ngx_tcp_nopush_n   "setsockopt(TCP_NOPUSH)"
+#define ngx_tcp_push_n     "setsockopt(!TCP_NOPUSH)"
+
+#endif
+
+
+#define ngx_shutdown_socket    shutdown
+#define ngx_shutdown_socket_n  "shutdown()"
+
+#define ngx_close_socket    close
+#define ngx_close_socket_n  "close()"
+
+
+#endif /* _NGX_SOCKET_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_solaris.h
@@ -0,0 +1,15 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOLARIS_H_INCLUDED_
+#define _NGX_SOLARIS_H_INCLUDED_
+
+
+ngx_chain_t *ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                         off_t limit);
+
+
+#endif /* _NGX_SOLARIS_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_solaris_config.h
@@ -0,0 +1,86 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_SOLARIS_CONFIG_H_INCLUDED_
+#define _NGX_SOLARIS_CONFIG_H_INCLUDED_
+
+
+#define SOLARIS  1
+
+#define _REENTRANT
+
+#define _FILE_OFFSET_BITS  64   /* must be before <sys/types.h> */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stddef.h>             /* offsetof() */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+
+#include <sys/uio.h>
+#include <sys/filio.h>          /* FIONBIO */
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sched.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <sys/sendfile.h>
+#include <sys/systeminfo.h>
+#include <limits.h>             /* IOV_MAX */
+#include <inttypes.h>
+
+#include <ngx_auto_config.h>
+
+
+#ifndef HAVE_SELECT
+#define HAVE_SELECT  1
+#endif
+
+
+#ifndef HAVE_POLL
+#define HAVE_POLL  1
+#endif
+#if (HAVE_POLL)
+#include <poll.h>
+#endif
+
+
+#if (HAVE_AIO)
+#include <aio.h>
+#endif
+
+
+#if (HAVE_DEVPOLL)
+#include <sys/ioctl.h>
+#include <sys/devpoll.h>
+#endif
+
+
+#ifndef HAVE_INHERITED_NONBLOCK
+#define HAVE_INHERITED_NONBLOCK  1
+#endif
+
+
+#define ngx_setproctitle(title)
+
+
+#endif /* _NGX_SOLARIS_CONFIG_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_solaris_init.c
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+char ngx_solaris_sysname[20];
+char ngx_solaris_release[10];
+char ngx_solaris_version[50];
+
+
+ngx_os_io_t ngx_os_io = {
+    ngx_unix_recv,
+    ngx_readv_chain,
+    ngx_unix_send,
+#if (HAVE_SENDFILE)
+    ngx_solaris_sendfilev_chain,
+    NGX_IO_SENDFILE
+#else
+    ngx_writev_chain,
+    0
+#endif
+};
+
+
+ngx_int_t ngx_os_init(ngx_log_t *log)
+{
+    if (sysinfo(SI_SYSNAME, ngx_solaris_sysname, sizeof(ngx_solaris_sysname))
+                                                                         == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_SYSNAME) failed");
+        return NGX_ERROR;
+    }
+
+    if (sysinfo(SI_RELEASE, ngx_solaris_release, sizeof(ngx_solaris_release))
+                                                                         == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_RELEASE) failed");
+        return NGX_ERROR;
+    }
+
+    if (sysinfo(SI_VERSION, ngx_solaris_version, sizeof(ngx_solaris_version))
+                                                                         == -1)
+    {
+        ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+                      "sysinfo(SI_SYSNAME) failed");
+        return NGX_ERROR;
+    }
+
+    return ngx_posix_init(log);
+}
+
+
+void ngx_os_status(ngx_log_t *log)
+{
+
+    ngx_log_error(NGX_LOG_INFO, log, 0, "OS: %s %s",
+                  ngx_solaris_sysname, ngx_solaris_release);
+
+    ngx_log_error(NGX_LOG_INFO, log, 0, "version: %s",
+                  ngx_solaris_version);
+
+    ngx_posix_status(log);
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_solaris_sendfilev_chain.c
@@ -0,0 +1,206 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_SENDFILEVECS   16
+
+
+ngx_chain_t *ngx_solaris_sendfilev_chain(ngx_connection_t *c, ngx_chain_t *in,
+                                         off_t limit)
+{
+    int             fd;
+    u_char         *prev;
+    off_t           fprev, sprev, send, aligned;
+    ssize_t         size, sent, n;
+    ngx_int_t       eintr, complete;
+    ngx_err_t       err;
+    sendfilevec_t  *sfv, sfvs[NGX_SENDFILEVECS];
+    ngx_array_t     vec;
+    ngx_event_t    *wev;
+    ngx_chain_t    *cl, *tail;
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+    send = 0;
+    complete = 0;
+
+    vec.elts = sfvs;
+    vec.size = sizeof(sendfilevec_t);
+    vec.nalloc = NGX_SENDFILEVECS;
+    vec.pool = c->pool;
+
+    for ( ;; ) {
+        fd = SFV_FD_SELF;
+        prev = NULL;
+        fprev = 0;
+        sfv = NULL;
+        eintr = 0;
+        sent = 0;
+        sprev = send;
+
+        vec.nelts = 0;
+
+        /* create the sendfilevec and coalesce the neighbouring bufs */
+
+        for (cl = in; cl && vec.nelts < IOV_MAX && send < limit; cl = cl->next)
+        {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (ngx_buf_in_memory_only(cl->buf)) {
+                fd = SFV_FD_SELF;
+
+                size = cl->buf->last - cl->buf->pos;
+
+                if (send + size > limit) {
+                    size = limit - send;
+                }
+
+                if (prev == cl->buf->pos) {
+                    sfv->sfv_len += size;
+
+                } else {
+                    if (!(sfv = ngx_array_push(&vec))) {
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                    sfv->sfv_fd = SFV_FD_SELF;
+                    sfv->sfv_flag = 0;
+                    sfv->sfv_off = (off_t) (uintptr_t) cl->buf->pos;
+                    sfv->sfv_len = size;
+                }
+
+                prev = cl->buf->pos + size;
+                send += size;
+
+            } else {
+                prev = NULL;
+
+                size = (size_t) (cl->buf->file_last - cl->buf->file_pos);
+
+                if (send + size > limit) {
+                    size = limit - send;
+
+                    aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+                                                      & ~(ngx_pagesize - 1);
+
+                    if (aligned <= cl->buf->file_last) {
+                        size = aligned - cl->buf->file_pos;
+                    }
+                }
+
+                if (fd == cl->buf->file->fd && fprev == cl->buf->file_pos) {
+                    sfv->sfv_len += size;
+
+                } else {
+                    if (!(sfv = ngx_array_push(&vec))) {
+                        return NGX_CHAIN_ERROR;
+                    }
+
+                    fd = cl->buf->file->fd;
+                    sfv->sfv_fd = fd;
+                    sfv->sfv_flag = 0;
+                    sfv->sfv_off = cl->buf->file_pos;
+                    sfv->sfv_len = size;
+                }
+
+                fprev = cl->buf->file_pos + size;
+                send += size;
+            }
+        }
+
+        n = sendfilev(c->fd, vec.elts, vec.nelts, &sent);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                if (err == NGX_EINTR) {
+                    eintr = 1;
+                }
+
+                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+                              "sendfilev() sent only " SIZE_T_FMT " bytes",
+                              sent);
+
+            } else {
+                wev->error = 1;
+                ngx_connection_error(c, err, "sendfilev() failed");
+                return NGX_CHAIN_ERROR;
+            }
+        }
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "sendfilev: %d " SIZE_T_FMT, n, sent);
+
+        if (send - sprev == sent) {
+            complete = 1;
+        }
+
+        c->sent += sent;
+
+        for (cl = in; cl; cl = cl->next) {
+
+            if (ngx_buf_special(cl->buf)) {
+                continue; 
+            }
+
+            if (sent == 0) {
+                break;
+            }
+
+            size = ngx_buf_size(cl->buf);
+
+            if (sent >= size) {
+                sent -= size;
+
+                if (ngx_buf_in_memory(cl->buf)) {
+                    cl->buf->pos = cl->buf->last;
+                }
+
+                if (cl->buf->in_file) {
+                    cl->buf->file_pos = cl->buf->file_last;
+                }
+
+                continue;
+            }
+
+            if (ngx_buf_in_memory(cl->buf)) {
+                cl->buf->pos += sent;
+            }
+
+            if (cl->buf->in_file) {
+                cl->buf->file_pos += sent;
+            }
+
+            break;
+        }
+
+        if (eintr) {
+            continue;
+        }
+
+        if (!complete) {
+            wev->ready = 0;
+            return cl;
+        }
+
+        if (send >= limit || cl == NULL) {
+            return cl;
+        }
+
+        in = cl;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_thread.h
@@ -0,0 +1,128 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_THREAD_H_INCLUDED_
+#define _NGX_THREAD_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#if (NGX_THREADS)
+
+#define NGX_MAX_THREADS      128
+
+#if (NGX_USE_RFORK)
+#include <ngx_freebsd_rfork_thread.h>
+
+
+#else /* use pthreads */
+
+#include <pthread.h>
+
+typedef pthread_t                    ngx_tid_t;
+
+#define ngx_thread_self()            pthread_self()
+#define ngx_log_tid                  (int) ngx_thread_self()
+
+#if defined(__FreeBSD__) && !defined(NGX_LINUXTHREADS)
+#define TID_T_FMT                    PTR_FMT
+#else
+#define TID_T_FMT                    "%d"
+#endif
+
+
+typedef pthread_key_t                ngx_tls_key_t;
+
+#define ngx_thread_key_create(key)   pthread_key_create(key, NULL)
+#define ngx_thread_key_create_n      "pthread_key_create()"
+#define ngx_thread_set_tls           pthread_setspecific
+#define ngx_thread_set_tls_n         "pthread_setspecific()"
+#define ngx_thread_get_tls           pthread_getspecific
+
+
+#define NGX_MUTEX_LIGHT     0
+
+typedef struct {
+    pthread_mutex_t   mutex;
+    ngx_log_t        *log;
+} ngx_mutex_t;
+
+typedef struct {
+    pthread_cond_t    cond;
+    ngx_tid_t         tid;
+    ngx_log_t        *log;
+} ngx_cond_t;
+
+#define ngx_thread_sigmask     pthread_sigmask
+#define ngx_thread_sigmask_n  "pthread_sigmask()"
+
+#define ngx_thread_join(t, p)  pthread_join(t, p)
+
+#define ngx_setthrtitle(n)
+
+
+
+ngx_int_t ngx_mutex_trylock(ngx_mutex_t *m);
+ngx_int_t ngx_mutex_lock(ngx_mutex_t *m);
+ngx_int_t ngx_mutex_unlock(ngx_mutex_t *m);
+
+#endif
+
+
+#define ngx_thread_volatile   volatile
+
+
+typedef struct {
+    ngx_tid_t    tid;
+    ngx_cond_t  *cv;
+    ngx_uint_t   state;
+} ngx_thread_t;
+
+#define NGX_THREAD_FREE   1
+#define NGX_THREAD_BUSY   2
+#define NGX_THREAD_EXIT   3
+#define NGX_THREAD_DONE   4
+
+extern ngx_int_t              ngx_threads_n;
+extern volatile ngx_thread_t  ngx_threads[NGX_MAX_THREADS];
+
+
+ngx_int_t ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle);
+int ngx_create_thread(ngx_tid_t *tid, void* (*func)(void *arg), void *arg,
+                      ngx_log_t *log);
+
+
+ngx_mutex_t *ngx_mutex_init(ngx_log_t *log, uint flags);
+void ngx_mutex_destroy(ngx_mutex_t *m);
+
+
+ngx_cond_t *ngx_cond_init(ngx_log_t *log);
+void ngx_cond_destroy(ngx_cond_t *cv);
+ngx_int_t ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m);
+ngx_int_t ngx_cond_signal(ngx_cond_t *cv);
+
+
+#else /* !NGX_THREADS */
+
+#define ngx_thread_volatile
+
+#define ngx_log_tid  0
+#define TID_T_FMT    "%d"
+
+#define ngx_mutex_trylock(m)  NGX_OK
+#define ngx_mutex_lock(m)     NGX_OK
+#define ngx_mutex_unlock(m)
+
+#define ngx_cond_signal(cv)
+
+#define ngx_thread_main()     1
+
+#endif
+
+
+
+#endif /* _NGX_THREAD_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_time.c
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+void ngx_localtime(ngx_tm_t *tm)
+{
+#if (HAVE_LOCALTIME_R)
+    time_t     now;
+
+    now = ngx_time();
+    localtime_r(&now, tm);
+
+#else
+    time_t     now;
+    ngx_tm_t  *t;
+
+    now = ngx_time();
+    t = localtime(&now);
+    *tm = *t;
+
+#endif
+
+    tm->ngx_tm_mon++;
+    tm->ngx_tm_year += 1900;
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_time.h
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_TIME_H_INCLUDED_
+#define _NGX_TIME_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef uint64_t       ngx_epoch_msec_t;
+
+typedef ngx_int_t      ngx_msec_t;
+
+typedef struct tm      ngx_tm_t;
+
+#define ngx_tm_sec     tm_sec
+#define ngx_tm_min     tm_min
+#define ngx_tm_hour    tm_hour
+#define ngx_tm_mday    tm_mday
+#define ngx_tm_mon     tm_mon
+#define ngx_tm_year    tm_year
+#define ngx_tm_wday    tm_wday
+#define ngx_tm_isdst   tm_isdst
+
+#define ngx_tm_sec_t   int
+#define ngx_tm_min_t   int
+#define ngx_tm_hour_t  int
+#define ngx_tm_mday_t  int
+#define ngx_tm_mon_t   int
+#define ngx_tm_year_t  int
+#define ngx_tm_wday_t  int
+
+
+#if (HAVE_GMTOFF)
+#define ngx_tm_gmtoff  tm_gmtoff
+#define ngx_tm_zone    tm_zone
+#endif
+
+
+#if (SOLARIS)
+#define ngx_timezone(isdst) (- (isdst ? altzone : timezone) / 60)
+#endif
+
+
+void ngx_localtime(ngx_tm_t *tm);
+
+#define ngx_gettimeofday(tp)  gettimeofday(tp, NULL);
+#define ngx_msleep(ms)        usleep(ms * 1000)
+
+
+#endif /* _NGX_TIME_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_types.h
@@ -0,0 +1,26 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_TYPES_H_INCLUDED_
+#define _NGX_TYPES_H_INCLUDED_
+
+
+#include <ngx_config.h>
+
+
+typedef int            ngx_fd_t;
+typedef struct stat    ngx_file_info_t;
+typedef ino_t          ngx_file_uniq_t;
+
+typedef struct {
+    DIR              *dir;
+    struct dirent    *de;
+    struct stat       info;
+    unsigned          info_valid;
+} ngx_dir_t;
+
+
+#endif /* _NGX_TYPES_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_user.h
@@ -0,0 +1,19 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_USER_H_INCLUDED_
+#define _NGX_USER_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef uid_t  ngx_uid_t;
+typedef gid_t  ngx_gid_t;
+
+
+#endif /* _NGX_USER_H_INCLUDED_ */
new file mode 100644
--- /dev/null
+++ b/src/os/unix/ngx_writev_chain.c
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+
+
+#define NGX_IOVS  8
+
+
+ngx_chain_t *ngx_writev_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+    u_char        *prev;
+    ssize_t        n, size;
+    off_t          send, sprev, sent;
+    ngx_uint_t     eintr, complete;
+    ngx_err_t      err;
+    ngx_array_t    vec;
+    ngx_chain_t   *cl;
+    ngx_event_t   *wev;
+    struct iovec  *iov, iovs[NGX_IOVS];
+
+    wev = c->write;
+
+    if (!wev->ready) {
+        return in;
+    }
+
+#if (HAVE_KQUEUE)
+
+    if ((ngx_event_flags & NGX_HAVE_KQUEUE_EVENT) && wev->pending_eof) {
+        ngx_log_error(NGX_LOG_INFO, c->log, wev->kq_errno,
+                      "kevent() reported about an closed connection");
+
+        wev->error = 1;
+        return NGX_CHAIN_ERROR;
+    }
+
+#endif
+
+    send = 0;
+    complete = 0;
+
+    vec.elts = iovs;
+    vec.size = sizeof(struct iovec);
+    vec.nalloc = NGX_IOVS;
+    vec.pool = c->pool;
+
+    for ( ;; ) {
+        prev = NULL;
+        iov = NULL;
+        eintr = 0;
+        sprev = send;
+
+        vec.nelts = 0;
+
+        /* create the iovec and coalesce the neighbouring bufs */
+
+        for (cl = in; cl && vec.nelts < IOV_MAX && send < limit; cl = cl->next)
+        {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            size = cl->buf->last - cl->buf->pos;
+
+            if (send + size > limit) {
+                size = limit - send;
+            }
+
+            if (prev == cl->buf->pos) {
+                iov->iov_len += size;
+
+            } else {
+                if (!(iov = ngx_array_push(&vec))) {
+                    return NGX_CHAIN_ERROR;
+                }
+
+                iov->iov_base = (void *) cl->buf->pos;
+                iov->iov_len = size;
+            }
+
+            prev = cl->buf->pos + size;
+            send += size;
+        }
+
+        n = writev(c->fd, vec.elts, vec.nelts);
+
+        if (n == -1) {
+            err = ngx_errno;
+
+            if (err == NGX_EAGAIN || err == NGX_EINTR) {
+                if (err == NGX_EINTR) {
+                    eintr = 1;
+                }
+
+                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+                               "writev() not ready");
+
+            } else {
+                wev->error = 1;
+                ngx_connection_error(c, err, "writev() failed");
+                return NGX_CHAIN_ERROR;
+            }
+        }
+
+        sent = n > 0 ? n : 0;
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                       "writev: " OFF_T_FMT, sent);
+
+        if (send - sprev == sent) {
+            complete = 1;
+        }
+
+        c->sent += sent;
+
+        for (cl = in; cl && sent > 0; cl = cl->next) {
+            if (ngx_buf_special(cl->buf)) {
+                continue;
+            }
+
+            if (sent == 0) {
+                break;
+            }
+
+            size = cl->buf->last - cl->buf->pos;
+
+            if (sent >= size) {
+                sent -= size;
+                cl->buf->pos = cl->buf->last;
+
+                continue;
+            }
+
+            cl->buf->pos += sent;
+
+            break;
+        }
+
+        if (eintr) {
+            continue;
+        }
+
+        if (!complete) {
+            wev->ready = 0;
+            return cl;
+        }
+
+        if (send >= limit || cl == NULL) {
+            return cl;
+        }
+
+        in = cl;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/src/os/unix/rfork_thread.S
@@ -0,0 +1,75 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+
+#include <sys/syscall.h>
+#include <machine/asm.h>
+
+/*
+ * rfork_thread(3) - rfork_thread(flags, stack, func, arg);
+ */
+
+#define	KERNCALL	int $0x80
+
+ENTRY(rfork_thread)
+	push	%ebp
+	mov	%esp, %ebp
+	push	%esi
+
+	mov	12(%ebp), %esi	# the stack address
+
+	sub	$4, %esi
+	mov	20(%ebp), %eax	# the thread argument
+	mov	%eax, (%esi)
+
+	sub	$4, %esi
+	mov	16(%ebp), %eax	# the start thread address
+	mov	%eax, (%esi)
+
+	push	8(%ebp)		# rfork(2) flags
+	push	$0
+	mov	$SYS_rfork, %eax
+	KERNCALL
+	jc	error
+
+	cmp	$0, %edx
+	jne	child
+
+parent:
+	add	$8, %esp
+	pop	%esi
+	mov	%ebp, %esp
+	pop	%ebp
+	ret
+
+child:
+	mov	%esi, %esp
+	pop	%eax
+	call	*%eax		# call a thread start address ...
+	add	$4, %esp
+
+	push	%eax
+	push	$0
+	mov	$SYS_exit, %eax	# ... and exit(2) after a thread would return
+	KERNCALL
+
+error:
+	add	$8, %esp
+	pop	%esi
+	mov	%ebp, %esp
+	pop	%ebp
+	PIC_PROLOGUE
+
+	/* libc's cerror: jmp  PIC_PLT(HIDENAME(cerror)) */
+
+	push	%eax
+	call	PIC_PLT(CNAME(__error))
+	pop	%ecx
+	PIC_EPILOGUE
+	mov	%ecx, (%eax)
+	mov	$-1, %eax
+	mov	$-1, %edx
+	ret