diff options
119 files changed, 5344 insertions, 2016 deletions
@@ -41,3 +41,4 @@ a3ea27be5ebde3d54e06cf6fe613cb4b53ab76d2 1.15.0-1 0361f3eda67a17295ab324c831cb9d8c560286ed 1.16.0-1 4b13438632bc37ca599113be90af64f6e2f09d83 1.17.0 e0658022962c0d5b0a32a1b0e3090bdec328e3da 1.17.0-1 +a34bc498d976affa5b50584d3d93a4a9a04d5c39 1.18.0 @@ -1,4 +1,15 @@ +Changes with Unit 1.18.0 28 May 2020 + + *) Feature: the "rootfs" isolation option for changing root filesystem + for an application. + + *) Feature: multiple "targets" in PHP applications. + + *) Feature: support for percent-encoding in the "uri" and "arguments" + matching options and in the "pass" option. + + Changes with Unit 1.17.0 16 Apr 2020 *) Feature: a "return" action with optional "location" for immediate diff --git a/auto/isolation b/auto/isolation index d231de12..4238b859 100644 --- a/auto/isolation +++ b/auto/isolation @@ -6,6 +6,9 @@ NXT_ISOLATION=NO NXT_HAVE_CLONE=NO NXT_HAVE_CLONE_NEWUSER=NO +NXT_HAVE_MOUNT=NO +NXT_HAVE_UNMOUNT=NO +NXT_HAVE_ROOTFS=NO nsflags="USER NS PID NET UTS CGROUP" @@ -55,3 +58,130 @@ if [ $nxt_found = yes ]; then fi done fi + + +nxt_feature="Linux pivot_root()" +nxt_feature_name=NXT_HAVE_PIVOT_ROOT +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include <sys/syscall.h> + + int main() { + return __NR_pivot_root; + }" +. auto/feature + + +nxt_feature="prctl(PR_SET_NO_NEW_PRIVS)" +nxt_feature_name=NXT_HAVE_PR_SET_NO_NEW_PRIVS0 +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include <sys/prctl.h> + + int main() { + return PR_SET_NO_NEW_PRIVS; + }" +. auto/feature + + +nxt_feature="Linux mount()" +nxt_feature_name=NXT_HAVE_LINUX_MOUNT +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include <sys/mount.h> + + int main() { + return mount((void*)0, (void*)0, (void*)0, 0, (void*)0); + }" +. auto/feature + +if [ $nxt_found = yes ]; then + NXT_HAVE_MOUNT=YES +fi + + +nxt_feature="Bind mount()" +nxt_feature_name=NXT_HAVE_BIND_MOUNT +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include <sys/mount.h> + + int main() { + return MS_BIND | MS_REC + }" +. auto/feature + +if [ $nxt_found = yes ]; then + NXT_HAVE_MOUNT=YES +fi + + +if [ $nxt_found = no ]; then + nxt_feature="FreeBSD nmount()" + nxt_feature_name=NXT_HAVE_FREEBSD_NMOUNT + nxt_feature_run=no + nxt_feature_incs= + nxt_feature_libs= + nxt_feature_test="#include <sys/mount.h> + + int main() { + return nmount((void *)0, 0, 0); + }" + . auto/feature + + if [ $nxt_found = yes ]; then + NXT_HAVE_MOUNT=YES + fi +fi + + +nxt_feature="Linux umount2()" +nxt_feature_name=NXT_HAVE_LINUX_UMOUNT2 +nxt_feature_run=no +nxt_feature_incs= +nxt_feature_libs= +nxt_feature_test="#include <sys/mount.h> + + int main() { + return umount2((void *)0, 0); + }" +. auto/feature + +if [ $nxt_found = yes ]; then + NXT_HAVE_UNMOUNT=YES +fi + +if [ $nxt_found = no ]; then + nxt_feature="unmount()" + nxt_feature_name=NXT_HAVE_UNMOUNT + nxt_feature_run=no + nxt_feature_incs= + nxt_feature_libs= + nxt_feature_test="#include <sys/mount.h> + + int main() { + return unmount((void *)0, 0); + }" + . auto/feature + + if [ $nxt_found = yes ]; then + NXT_HAVE_UNMOUNT=YES + fi +fi + +if [ $NXT_HAVE_MOUNT = YES -a $NXT_HAVE_UNMOUNT = YES ]; then + NXT_HAVE_ROOTFS=YES + + cat << END >> $NXT_AUTO_CONFIG_H + +#ifndef NXT_HAVE_ISOLATION_ROOTFS +#define NXT_HAVE_ISOLATION_ROOTFS 1 +#endif + +END + +fi diff --git a/auto/modules/java b/auto/modules/java index 68b10836..2e6f292d 100644 --- a/auto/modules/java +++ b/auto/modules/java @@ -172,13 +172,13 @@ if [ -z "$NXT_JAVA_LIB_PATH" ]; then exit 1 fi - NXT_JAVA_LIB_PATH="${NXT_JAVA_LIB_PATH}/server" + NXT_JAVA_LIB_SERVER_PATH="${NXT_JAVA_LIB_PATH}/server" $echo " $NXT_JAVA_LIB_PATH" $echo "got library path $NXT_JAVA_LIB_PATH" >> $NXT_AUTOCONF_ERR fi -NXT_JAVA_LDFLAGS="-L${NXT_JAVA_LIB_PATH} -Wl,-rpath ${NXT_JAVA_LIB_PATH} -ljvm" +NXT_JAVA_LDFLAGS="-L${NXT_JAVA_LIB_SERVER_PATH} -Wl,-rpath ${NXT_JAVA_LIB_SERVER_PATH} -ljvm" nxt_found=no @@ -227,6 +227,7 @@ NXT_JAVA_INSTALL_JARS= NXT_JAVA_UNINSTALL_JARS= NXT_JAVA_JARS=$NXT_BUILD_DIR/$NXT_JAVA_MODULE/nxt_jars.h +NXT_JAVA_MOUNTS_HEADER=$NXT_BUILD_DIR/$NXT_JAVA_MODULE/nxt_java_mounts.h mkdir -p $NXT_BUILD_DIR/$NXT_JAVA_MODULE cat << END > $NXT_JAVA_JARS @@ -308,6 +309,32 @@ cat << END >> $NXT_JAVA_JARS #endif /* _NXT_JAVA_JARS_INCLUDED_ */ END +NXT_JAVA_LIBJVM="$NXT_JAVA_LIB_SERVER_PATH/libjvm.so" + +if [ "$NXT_SYSTEM" = "Darwin" ]; then +NXT_JAVA_LIBC_DIR="/usr/lib" +else +NXT_JAVA_LIBC_DIR=`ldd "$NXT_JAVA_LIBJVM" | grep libc.so | cut -d' ' -f3` +NXT_JAVA_LIBC_DIR=`dirname $NXT_JAVA_LIBC_DIR` +fi + +cat << END > $NXT_JAVA_MOUNTS_HEADER +#ifndef _NXT_JAVA_MOUNTS_H_INCLUDED_ +#define _NXT_JAVA_MOUNTS_H_INCLUDED_ + + +static const nxt_fs_mount_t nxt_java_mounts[] = { + {(u_char *) "proc", (u_char *) "/proc", (u_char *) "proc", 0, NULL}, + {(u_char *) "$NXT_JAVA_LIBC_DIR", (u_char *) "$NXT_JAVA_LIBC_DIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_JAVA_HOME", (u_char *) "$NXT_JAVA_HOME", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, +}; + + +#endif /* _NXT_JAVA_MOUNTS_H_INCLUDED_ */ +END + $echo " + Java module: ${NXT_JAVA_MODULE}.unit.so" . auto/cc/deps diff --git a/auto/modules/php b/auto/modules/php index e2e5498a..2cec2f44 100644 --- a/auto/modules/php +++ b/auto/modules/php @@ -100,7 +100,11 @@ if /bin/sh -c "${NXT_PHP_CONFIG} --version" >> $NXT_AUTOCONF_ERR 2>&1; then `${NXT_PHP_CONFIG} --libs`" else - NXT_PHP_LIB="-lphp${NXT_PHP_VERSION%%.*}" + if [ $NXT_PHP_MAJOR_VERSION -ge 8 ]; then + NXT_PHP_LIB="-lphp" + else + NXT_PHP_LIB="-lphp${NXT_PHP_VERSION%%.*}" + fi if [ "$NXT_PHP_LIB_PATH" != "" ]; then # "php-config --ldflags" does not contain path to libphp, but diff --git a/auto/modules/python b/auto/modules/python index 6c8198f5..ad862f3c 100644 --- a/auto/modules/python +++ b/auto/modules/python @@ -68,6 +68,7 @@ if /bin/sh -c "$NXT_PYTHON_CONFIG --prefix" >> $NXT_AUTOCONF_ERR 2>&1; then NXT_PYTHON_CONFIG="${NXT_PYTHON_CONFIG} --embed" fi + NXT_PYTHON_EXEC=`${NXT_PYTHON_CONFIG} --exec-prefix`/bin/${NXT_PYTHON} NXT_PYTHON_INCLUDE=`${NXT_PYTHON_CONFIG} --includes` NXT_PYTHON_LIBS=`${NXT_PYTHON_CONFIG} --ldflags` @@ -129,6 +130,37 @@ if grep ^$NXT_PYTHON_MODULE: $NXT_MAKEFILE 2>&1 > /dev/null; then exit 1; fi + +NXT_PYTHON_MOUNTS_HEADER=$NXT_BUILD_DIR/nxt_python_mounts.h + +$NXT_PYTHON_EXEC -c 'import os.path +import sys +pyver = "python" + str(sys.version_info[0]) + "." + str(sys.version_info[1]) + +print("static const nxt_fs_mount_t nxt_python%d%d_mounts[] = {" % (sys.version_info[0], sys.version_info[1])) + +pattern = "{(u_char *) \"%s\", (u_char *) \"%s\", (u_char *) \"bind\", NXT_MS_BIND|NXT_MS_REC, NULL}," +base = None +for p in sys.path: + if len(p) > 0: + if os.path.basename(p) == pyver: + base = p + +if base is None: + raise Exception("failed to compute sys.path mount points") + +print(pattern % (base, base)) + +for p in sys.path: + if len(p) > 0: + if not p.startswith(base): + print(pattern % (p, p)) + +print("};\n\n") + +' >> $NXT_PYTHON_MOUNTS_HEADER + + $echo " + Python module: ${NXT_PYTHON_MODULE}.unit.so" . auto/cc/deps @@ -165,7 +197,7 @@ END done - + cat << END >> $NXT_MAKEFILE .PHONY: ${NXT_PYTHON_MODULE} diff --git a/auto/modules/ruby b/auto/modules/ruby index 407406ce..f7334cc7 100644 --- a/auto/modules/ruby +++ b/auto/modules/ruby @@ -51,6 +51,7 @@ $echo "configuring Ruby module ..." >> $NXT_AUTOCONF_ERR NXT_RUBY=${NXT_RUBY=ruby} NXT_RUBY_MODULE=${NXT_RUBY_MODULE=${NXT_RUBY}} +NXT_RUBY_MOUNTS_HEADER=$NXT_BUILD_DIR/nxt_ruby_mounts.h nxt_found=no @@ -58,6 +59,14 @@ if /bin/sh -c "$NXT_RUBY -v" >> $NXT_AUTOCONF_ERR 2>&1; then NXT_RUBY_RUBYHDRDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["rubyhdrdir"])'` NXT_RUBY_ARCHHDRDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["rubyarchhdrdir"])'` + NXT_RUBY_SITEARCHDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["sitearchhdrdir"])'` + NXT_RUBY_SITEDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["sitedir"])'` + NXT_RUBY_LIBDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["rubylibdir"])'` + NXT_RUBY_TOPDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["topdir"])'` + NXT_RUBY_PREFIXDIR=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["rubylibprefix"])'` + NXT_RUBY_GEMDIR=`gem environment gemdir` + NXT_RUBY_GEMPATH=`gem environment gempath` + NXT_RUBY_INCPATH="-I$NXT_RUBY_ARCHHDRDIR -I$NXT_RUBY_RUBYHDRDIR" NXT_RUBY_LIBNAME=`$NXT_RUBY -r rbconfig -e 'printf("%s",RbConfig::CONFIG["RUBY_SO_NAME"])'` @@ -135,6 +144,35 @@ if grep ^$NXT_RUBY_MODULE: $NXT_MAKEFILE 2>&1 > /dev/null; then exit 1; fi + +cat << END > $NXT_RUBY_MOUNTS_HEADER + +static const nxt_fs_mount_t nxt_ruby_mounts[] = { + {(u_char *) "$NXT_RUBY_RUBYHDRDIR", (u_char *) "$NXT_RUBY_RUBYHDRDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_ARCHHDRDIR", (u_char *) "$NXT_RUBY_ARCHHDRDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_SITEDIR", (u_char *) "$NXT_RUBY_SITEDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_LIBDIR", (u_char *) "$NXT_RUBY_LIBDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_GEMDIR", (u_char *) "$NXT_RUBY_GEMDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_TOPDIR", (u_char *) "$NXT_RUBY_TOPDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + {(u_char *) "$NXT_RUBY_PREFIXDIR", (u_char *) "$NXT_RUBY_PREFIXDIR", + (u_char *) "bind", NXT_MS_BIND | NXT_MS_REC, NULL}, + +END + +for path in `echo $NXT_RUBY_GEMPATH | tr ':' '\n'`; do + $echo "{(u_char *) \"$path\", (u_char *) \"$path\"," >> $NXT_RUBY_MOUNTS_HEADER + $echo "(u_char *) \"bind\", NXT_MS_BIND | NXT_MS_REC, NULL}," >> $NXT_RUBY_MOUNTS_HEADER +done + +$echo "};" >> $NXT_RUBY_MOUNTS_HEADER + + $echo " + Ruby module: ${NXT_RUBY_MODULE}.unit.so" . auto/cc/deps diff --git a/auto/sources b/auto/sources index c6b34bbc..2075ca0f 100644 --- a/auto/sources +++ b/auto/sources @@ -78,7 +78,7 @@ NXT_LIB_SRCS=" \ src/nxt_conf.c \ src/nxt_conf_validation.c \ src/nxt_main_process.c \ - src/nxt_worker_process.c \ + src/nxt_signal_handlers.c \ src/nxt_controller.c \ src/nxt_router.c \ src/nxt_h1proto.c \ @@ -177,6 +177,11 @@ NXT_LIB_UTF8_FILE_NAME_TEST_SRCS=" \ " +if [ $NXT_HAVE_ROOTFS = YES ]; then + NXT_LIB_SRCS="$NXT_LIB_SRCS src/nxt_fs.c" +fi + + if [ $NXT_TLS = YES ]; then nxt_have=NXT_TLS . auto/have NXT_LIB_SRCS="$NXT_LIB_SRCS $NXT_LIB_TLS_SRCS" diff --git a/docs/changes.xml b/docs/changes.xml index 2ff5fcab..686896d4 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -13,6 +13,53 @@ unit-perl unit-ruby unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11" + ver="1.18.0" rev="1" + date="2020-05-28" time="18:00:00 +0300" + packager="Andrei Belov <defan@nginx.com>"> + +<change> +<para> +NGINX Unit updated to 1.18.0. +</para> +</change> + +</changes> + + +<changes apply="unit" ver="1.18.0" rev="1" + date="2020-05-28" time="18:00:00 +0300" + packager="Andrei Belov <defan@nginx.com>"> + +<change type="feature"> +<para> +the "rootfs" isolation option for changing root filesystem for an application. +</para> +</change> + +<change type="feature"> +<para> +multiple "targets" in PHP applications. +</para> +</change> + +<change type="feature"> +<para> +support for percent-encoding in the "uri" and "arguments" matching options +and in the "pass" option. +</para> +</change> + +</changes> + + +<changes apply="unit-php + unit-python unit-python2.7 + unit-python3.4 unit-python3.5 unit-python3.6 unit-python3.7 + unit-python3.8 + unit-go + unit-perl + unit-ruby + unit-jsc-common unit-jsc8 unit-jsc10 unit-jsc11" ver="1.17.0" rev="1" date="2020-04-16" time="18:00:00 +0300" packager="Andrei Belov <defan@nginx.com>"> diff --git a/pkg/deb/Makefile b/pkg/deb/Makefile index 797ff438..55bf7082 100644 --- a/pkg/deb/Makefile +++ b/pkg/deb/Makefile @@ -16,6 +16,18 @@ BUILD_DEPENDS = $(BUILD_DEPENDS_unit) MODULES= +# Ubuntu 20.04 +ifeq ($(CODENAME),focal) +include Makefile.php +include Makefile.python27 +include Makefile.python38 +include Makefile.go +include Makefile.perl +include Makefile.ruby +include Makefile.jsc-common +include Makefile.jsc11 +endif + # Ubuntu 19.10 ifeq ($(CODENAME),eoan) include Makefile.php diff --git a/pkg/deb/Makefile.jsc-common b/pkg/deb/Makefile.jsc-common index 42b4ad74..928376c3 100644 --- a/pkg/deb/Makefile.jsc-common +++ b/pkg/deb/Makefile.jsc-common @@ -6,7 +6,7 @@ MODULE_SUMMARY_jsc_common= Java shared packages for NGINX Unit MODULE_VERSION_jsc_common= $(VERSION) MODULE_RELEASE_jsc_common= 1 -ifneq (,$(findstring $(CODENAME),eoan disco buster)) +ifneq (,$(findstring $(CODENAME),focal eoan disco buster)) JAVA_MINVERSION= 11 else JAVA_MINVERSION= 8 diff --git a/pkg/docker/Dockerfile.full b/pkg/docker/Dockerfile.full index fb9144c2..08593732 100644 --- a/pkg/docker/Dockerfile.full +++ b/pkg/docker/Dockerfile.full @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.go1.11-dev b/pkg/docker/Dockerfile.go1.11-dev index 2b213836..ee51f83e 100644 --- a/pkg/docker/Dockerfile.go1.11-dev +++ b/pkg/docker/Dockerfile.go1.11-dev @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.minimal b/pkg/docker/Dockerfile.minimal index af97aa4f..08be78bc 100644 --- a/pkg/docker/Dockerfile.minimal +++ b/pkg/docker/Dockerfile.minimal @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.perl5.28 b/pkg/docker/Dockerfile.perl5.28 index 793b48d1..a8f84c17 100644 --- a/pkg/docker/Dockerfile.perl5.28 +++ b/pkg/docker/Dockerfile.perl5.28 @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.php7.3 b/pkg/docker/Dockerfile.php7.3 index 5e3f0e97..fe60cf44 100644 --- a/pkg/docker/Dockerfile.php7.3 +++ b/pkg/docker/Dockerfile.php7.3 @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python2.7 b/pkg/docker/Dockerfile.python2.7 index 9e3a431c..f80ca098 100644 --- a/pkg/docker/Dockerfile.python2.7 +++ b/pkg/docker/Dockerfile.python2.7 @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.python3.7 b/pkg/docker/Dockerfile.python3.7 index 2517896b..8d5b4d9b 100644 --- a/pkg/docker/Dockerfile.python3.7 +++ b/pkg/docker/Dockerfile.python3.7 @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/pkg/docker/Dockerfile.ruby2.5 b/pkg/docker/Dockerfile.ruby2.5 index 7258bd28..365d71ac 100644 --- a/pkg/docker/Dockerfile.ruby2.5 +++ b/pkg/docker/Dockerfile.ruby2.5 @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>" -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ diff --git a/src/nxt_application.c b/src/nxt_application.c index bebe3907..566bf256 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -17,14 +17,21 @@ #include <glob.h> +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) +#include <sys/prctl.h> +#endif + typedef struct { nxt_app_type_t type; nxt_str_t version; nxt_str_t file; + nxt_array_t *mounts; } nxt_module_t; +static nxt_int_t nxt_discovery_start(nxt_task_t *task, + nxt_process_data_t *data); static nxt_buf_t *nxt_discovery_modules(nxt_task_t *task, const char *path); static nxt_int_t nxt_discovery_module(nxt_task_t *task, nxt_mp_t *mp, nxt_array_t *modules, const char *name); @@ -34,7 +41,45 @@ static void nxt_discovery_quit(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_app_module_t *nxt_app_module_load(nxt_task_t *task, const char *name); +static nxt_int_t nxt_app_prefork(nxt_task_t *task, nxt_process_t *process, + nxt_mp_t *mp); +static nxt_int_t nxt_app_setup(nxt_task_t *task, nxt_process_t *process); static nxt_int_t nxt_app_set_environment(nxt_conf_value_t *environment); +static u_char *nxt_cstr_dup(nxt_mp_t *mp, u_char *dst, u_char *src); + +#if (NXT_HAVE_ISOLATION_ROOTFS) +static nxt_int_t nxt_app_prepare_rootfs(nxt_task_t *task, + nxt_process_t *process); +static nxt_int_t nxt_app_prepare_lang_mounts(nxt_task_t *task, + nxt_process_t *process, nxt_array_t *syspaths); +static nxt_int_t nxt_app_set_isolation_rootfs(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); +#endif + +static nxt_int_t nxt_app_set_isolation(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); + +#if (NXT_HAVE_CLONE) +static nxt_int_t nxt_app_set_isolation_namespaces(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); +static nxt_int_t nxt_app_clone_flags(nxt_task_t *task, + nxt_conf_value_t *namespaces, nxt_clone_t *clone); +#endif + +#if (NXT_HAVE_CLONE_NEWUSER) +static nxt_int_t nxt_app_set_isolation_creds(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); +static nxt_int_t nxt_app_isolation_credential_map(nxt_task_t *task, + nxt_mp_t *mem_pool, nxt_conf_value_t *map_array, + nxt_clone_credential_map_t *map); +#endif + +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) +static nxt_int_t nxt_app_set_isolation_new_privs(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); +#endif + +nxt_str_t nxt_server = nxt_string(NXT_SERVER); static uint32_t compat[] = { @@ -42,14 +87,53 @@ static uint32_t compat[] = { }; -nxt_str_t nxt_server = nxt_string(NXT_SERVER); +static nxt_app_module_t *nxt_app; -static nxt_app_module_t *nxt_app; +static const nxt_port_handlers_t nxt_discovery_process_port_handlers = { + .quit = nxt_signal_quit_handler, + .new_port = nxt_port_new_port_handler, + .change_file = nxt_port_change_log_file_handler, + .mmap = nxt_port_mmap_handler, + .data = nxt_port_data_handler, + .remove_pid = nxt_port_remove_pid_handler, + .rpc_ready = nxt_port_rpc_handler, + .rpc_error = nxt_port_rpc_handler, +}; -nxt_int_t -nxt_discovery_start(nxt_task_t *task, void *data) +static const nxt_port_handlers_t nxt_app_process_port_handlers = { + .quit = nxt_signal_quit_handler, + .rpc_ready = nxt_port_rpc_handler, + .rpc_error = nxt_port_rpc_handler, +}; + + +const nxt_process_init_t nxt_discovery_process = { + .name = "discovery", + .type = NXT_PROCESS_DISCOVERY, + .prefork = NULL, + .restart = 0, + .setup = nxt_process_core_setup, + .start = nxt_discovery_start, + .port_handlers = &nxt_discovery_process_port_handlers, + .signals = nxt_process_signals, +}; + + +const nxt_process_init_t nxt_app_process = { + .type = NXT_PROCESS_APP, + .setup = nxt_app_setup, + .prefork = nxt_app_prefork, + .restart = 0, + .start = NULL, /* set to module->start */ + .port_handlers = &nxt_app_process_port_handlers, + .signals = nxt_process_signals, +}; + + +static nxt_int_t +nxt_discovery_start(nxt_task_t *task, nxt_process_data_t *data) { uint32_t stream; nxt_buf_t *b; @@ -57,7 +141,7 @@ nxt_discovery_start(nxt_task_t *task, void *data) nxt_port_t *main_port, *discovery_port; nxt_runtime_t *rt; - nxt_debug(task, "DISCOVERY"); + nxt_log(task, NXT_LOG_INFO, "discovery started"); rt = task->thread->runtime; @@ -93,16 +177,17 @@ nxt_discovery_start(nxt_task_t *task, void *data) static nxt_buf_t * nxt_discovery_modules(nxt_task_t *task, const char *path) { - char *name; - u_char *p, *end; - size_t size; - glob_t glb; - nxt_mp_t *mp; - nxt_buf_t *b; - nxt_int_t ret; - nxt_uint_t i, n; - nxt_array_t *modules; - nxt_module_t *module; + char *name; + u_char *p, *end; + size_t size; + glob_t glb; + nxt_mp_t *mp; + nxt_buf_t *b; + nxt_int_t ret; + nxt_uint_t i, n, j; + nxt_array_t *modules, *mounts; + nxt_module_t *module; + nxt_fs_mount_t *mnt; b = NULL; @@ -145,11 +230,26 @@ nxt_discovery_modules(nxt_task_t *task, const char *path) size += nxt_length("{\"type\": ,"); size += nxt_length(" \"version\": \"\","); - size += nxt_length(" \"file\": \"\"},"); + size += nxt_length(" \"file\": \"\","); + size += nxt_length(" \"mounts\": []},"); size += NXT_INT_T_LEN + module[i].version.length + module[i].file.length; + + mounts = module[i].mounts; + + size += mounts->nelts * nxt_length("{\"src\": \"\", \"dst\": \"\", " + "\"fstype\": \"\", \"flags\": , " + "\"data\": \"\"},"); + + mnt = mounts->elts; + + for (j = 0; j < mounts->nelts; j++) { + size += nxt_strlen(mnt[j].src) + nxt_strlen(mnt[j].dst) + + nxt_strlen(mnt[j].fstype) + NXT_INT_T_LEN + + (mnt[j].data == NULL ? 0 : nxt_strlen(mnt[j].data)); + } } b = nxt_buf_mem_alloc(mp, size, 0); @@ -164,12 +264,34 @@ nxt_discovery_modules(nxt_task_t *task, const char *path) *p++ = '['; for (i = 0; i < n; i++) { - p = nxt_sprintf(p, end, - "{\"type\": %d, \"version\": \"%V\", \"file\": \"%V\"},", - module[i].type, &module[i].version, &module[i].file); + mounts = module[i].mounts; + + p = nxt_sprintf(p, end, "{\"type\": %d, \"version\": \"%V\", " + "\"file\": \"%V\", \"mounts\": [", + module[i].type, &module[i].version, &module[i].file); + + mnt = mounts->elts; + for (j = 0; j < mounts->nelts; j++) { + p = nxt_sprintf(p, end, + "{\"src\": \"%s\", \"dst\": \"%s\", " + "\"fstype\": \"%s\", \"flags\": %d, " + "\"data\": \"%s\"},", + mnt[j].src, mnt[j].dst, mnt[j].fstype, mnt[j].flags, + mnt[j].data == NULL ? (u_char *) "" : mnt[j].data); + } + + *p++ = ']'; + *p++ = '}'; + *p++ = ','; } *p++ = ']'; + + if (nxt_slow_path(p >= end)) { + nxt_alert(task, "discovery write past the buffer"); + goto fail; + } + b->mem.free = p; fail: @@ -184,13 +306,16 @@ static nxt_int_t nxt_discovery_module(nxt_task_t *task, nxt_mp_t *mp, nxt_array_t *modules, const char *name) { - void *dl; - nxt_str_t version; - nxt_int_t ret; - nxt_uint_t i, n; - nxt_module_t *module; - nxt_app_type_t type; - nxt_app_module_t *app; + void *dl; + nxt_str_t version; + nxt_int_t ret; + nxt_uint_t i, j, n; + nxt_array_t *mounts; + nxt_module_t *module; + nxt_app_type_t type; + nxt_fs_mount_t *to; + nxt_app_module_t *app; + const nxt_fs_mount_t *from; /* * Only memory allocation failure should return NXT_ERROR. @@ -267,6 +392,47 @@ nxt_discovery_module(nxt_task_t *task, nxt_mp_t *mp, nxt_array_t *modules, nxt_memcpy(module->file.start, name, module->file.length); + module->mounts = nxt_array_create(mp, app->nmounts, + sizeof(nxt_fs_mount_t)); + + if (nxt_slow_path(module->mounts == NULL)) { + goto fail; + } + + mounts = module->mounts; + + for (j = 0; j < app->nmounts; j++) { + from = &app->mounts[j]; + to = nxt_array_zero_add(mounts); + if (nxt_slow_path(to == NULL)) { + goto fail; + } + + to->src = nxt_cstr_dup(mp, to->src, from->src); + if (nxt_slow_path(to->src == NULL)) { + goto fail; + } + + to->dst = nxt_cstr_dup(mp, to->dst, from->dst); + if (nxt_slow_path(to->dst == NULL)) { + goto fail; + } + + to->fstype = nxt_cstr_dup(mp, to->fstype, from->fstype); + if (nxt_slow_path(to->fstype == NULL)) { + goto fail; + } + + if (from->data != NULL) { + to->data = nxt_cstr_dup(mp, to->data, from->data); + if (nxt_slow_path(to->data == NULL)) { + goto fail; + } + } + + to->flags = from->flags; + } + } else { nxt_alert(task, "dlsym(\"%s\"), failed: \"%s\"", name, dlerror()); } @@ -301,18 +467,110 @@ nxt_discovery_completion_handler(nxt_task_t *task, void *obj, void *data) static void nxt_discovery_quit(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_worker_process_quit_handler(task, msg); + nxt_signal_quit_handler(task, msg); } -nxt_int_t -nxt_app_start(nxt_task_t *task, void *data) +static nxt_int_t +nxt_app_prefork(nxt_task_t *task, nxt_process_t *process, nxt_mp_t *mp) +{ + nxt_int_t cap_setid, cap_chroot; + nxt_int_t ret; + nxt_runtime_t *rt; + nxt_common_app_conf_t *app_conf; + nxt_app_lang_module_t *lang; + + rt = task->thread->runtime; + app_conf = process->data.app; + cap_setid = rt->capabilities.setid; + cap_chroot = rt->capabilities.chroot; + + lang = nxt_app_lang_module(rt, &app_conf->type); + + nxt_assert(lang != NULL); + + if (app_conf->isolation != NULL) { + ret = nxt_app_set_isolation(task, app_conf->isolation, process); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + +#if (NXT_HAVE_CLONE_NEWUSER) + if (nxt_is_clone_flag_set(process->isolation.clone.flags, NEWUSER)) { + cap_setid = 1; + cap_chroot = 1; + } +#endif + +#if (NXT_HAVE_ISOLATION_ROOTFS) + if (process->isolation.rootfs != NULL) { + if (!cap_chroot) { + nxt_log(task, NXT_LOG_ERR, + "The \"rootfs\" field requires privileges"); + + return NXT_ERROR; + } + + if (lang->mounts != NULL && lang->mounts->nelts > 0) { + ret = nxt_app_prepare_lang_mounts(task, process, lang->mounts); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + } +#endif + + if (cap_setid) { + ret = nxt_process_creds_set(task, process, &app_conf->user, + &app_conf->group); + + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + } else { + if (!nxt_str_eq(&app_conf->user, (u_char *) rt->user_cred.user, + nxt_strlen(rt->user_cred.user))) + { + nxt_alert(task, "cannot set user \"%V\" for app \"%V\": " + "missing capabilities", &app_conf->user, &app_conf->name); + + return NXT_ERROR; + } + + if (app_conf->group.length > 0 + && !nxt_str_eq(&app_conf->group, (u_char *) rt->group, + nxt_strlen(rt->group))) + { + nxt_alert(task, "cannot set group \"%V\" for app \"%V\": " + "missing capabilities", &app_conf->group, + &app_conf->name); + + return NXT_ERROR; + } + } + +#if (NXT_HAVE_CLONE_NEWUSER) + ret = nxt_process_vldt_isolation_creds(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } +#endif + + return NXT_OK; +} + + +static nxt_int_t +nxt_app_setup(nxt_task_t *task, nxt_process_t *process) { nxt_int_t ret; + nxt_process_init_t *init; nxt_app_lang_module_t *lang; nxt_common_app_conf_t *app_conf; - app_conf = data; + app_conf = process->data.app; lang = nxt_app_lang_module(task->thread->runtime, &app_conf->type); if (nxt_slow_path(lang == NULL)) { @@ -332,14 +590,37 @@ nxt_app_start(nxt_task_t *task, void *data) } } - if (nxt_app->pre_init != NULL) { - ret = nxt_app->pre_init(task, data); + if (nxt_slow_path(nxt_app_set_environment(app_conf->environment) + != NXT_OK)) + { + nxt_alert(task, "failed to set environment"); + return NXT_ERROR; + } + + if (nxt_app->setup != NULL) { + ret = nxt_app->setup(task, process, app_conf); if (nxt_slow_path(ret != NXT_OK)) { return ret; } } +#if (NXT_HAVE_ISOLATION_ROOTFS) + if (process->isolation.rootfs != NULL) { + if (process->isolation.mounts != NULL) { + ret = nxt_app_prepare_rootfs(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + } + + ret = nxt_process_change_root(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } +#endif + if (app_conf->working_directory != NULL && app_conf->working_directory[0] != 0) { @@ -353,23 +634,13 @@ nxt_app_start(nxt_task_t *task, void *data) } } - if (nxt_slow_path(nxt_app_set_environment(app_conf->environment) - != NXT_OK)) - { - nxt_alert(task, "failed to set environment"); - return NXT_ERROR; - } - - ret = nxt_app->init(task, data); + init = nxt_process_init(process); - if (nxt_slow_path(ret != NXT_OK)) { - nxt_debug(task, "application init failed"); + init->start = nxt_app->start; - } else { - nxt_debug(task, "application init done"); - } + process->state = NXT_PROCESS_STATE_CREATED; - return ret; + return NXT_OK; } @@ -429,6 +700,465 @@ nxt_app_set_environment(nxt_conf_value_t *environment) } +static nxt_int_t +nxt_app_set_isolation(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ +#if (NXT_HAVE_CLONE) + if (nxt_slow_path(nxt_app_set_isolation_namespaces(task, isolation, process) + != NXT_OK)) + { + return NXT_ERROR; + } +#endif + +#if (NXT_HAVE_CLONE_NEWUSER) + if (nxt_slow_path(nxt_app_set_isolation_creds(task, isolation, process) + != NXT_OK)) + { + return NXT_ERROR; + } +#endif + +#if (NXT_HAVE_ISOLATION_ROOTFS) + if (nxt_slow_path(nxt_app_set_isolation_rootfs(task, isolation, process) + != NXT_OK)) + { + return NXT_ERROR; + } +#endif + +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) + if (nxt_slow_path(nxt_app_set_isolation_new_privs(task, isolation, process) + != NXT_OK)) + { + return NXT_ERROR; + } +#endif + + return NXT_OK; +} + + +#if (NXT_HAVE_CLONE) + +static nxt_int_t +nxt_app_set_isolation_namespaces(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ + nxt_int_t ret; + nxt_conf_value_t *obj; + + static nxt_str_t nsname = nxt_string("namespaces"); + + obj = nxt_conf_get_object_member(isolation, &nsname, NULL); + if (obj != NULL) { + ret = nxt_app_clone_flags(task, obj, &process->isolation.clone); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + + return NXT_OK; +} + +#endif + + +#if (NXT_HAVE_ISOLATION_ROOTFS) + +static nxt_int_t +nxt_app_set_isolation_rootfs(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ + nxt_str_t str; + nxt_conf_value_t *obj; + + static nxt_str_t rootfs_name = nxt_string("rootfs"); + + obj = nxt_conf_get_object_member(isolation, &rootfs_name, NULL); + if (obj != NULL) { + nxt_conf_get_string(obj, &str); + + if (nxt_slow_path(str.length <= 1 || str.start[0] != '/')) { + nxt_log(task, NXT_LOG_ERR, "rootfs requires an absolute path other " + "than \"/\" but given \"%V\"", &str); + + return NXT_ERROR; + } + + if (str.start[str.length - 1] == '/') { + str.length--; + } + + process->isolation.rootfs = nxt_mp_alloc(process->mem_pool, + str.length + 1); + + if (nxt_slow_path(process->isolation.rootfs == NULL)) { + return NXT_ERROR; + } + + nxt_memcpy(process->isolation.rootfs, str.start, str.length); + + process->isolation.rootfs[str.length] = '\0'; + } + + return NXT_OK; +} + +#endif + + +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) + +static nxt_int_t +nxt_app_set_isolation_new_privs(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ + nxt_conf_value_t *obj; + + static nxt_str_t new_privs_name = nxt_string("new_privs"); + + obj = nxt_conf_get_object_member(isolation, &new_privs_name, NULL); + if (obj != NULL) { + process->isolation.new_privs = nxt_conf_get_boolean(obj); + } + + return NXT_OK; +} + +#endif + + +#if (NXT_HAVE_CLONE_NEWUSER) + +static nxt_int_t +nxt_app_set_isolation_creds(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ + nxt_int_t ret; + nxt_clone_t *clone; + nxt_conf_value_t *array; + + static nxt_str_t uidname = nxt_string("uidmap"); + static nxt_str_t gidname = nxt_string("gidmap"); + + clone = &process->isolation.clone; + + array = nxt_conf_get_object_member(isolation, &uidname, NULL); + if (array != NULL) { + ret = nxt_app_isolation_credential_map(task, process->mem_pool, array, + &clone->uidmap); + + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + + array = nxt_conf_get_object_member(isolation, &gidname, NULL); + if (array != NULL) { + ret = nxt_app_isolation_credential_map(task, process->mem_pool, array, + &clone->gidmap); + + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_app_isolation_credential_map(nxt_task_t *task, nxt_mp_t *mp, + nxt_conf_value_t *map_array, nxt_clone_credential_map_t *map) +{ + nxt_int_t ret; + nxt_uint_t i; + nxt_conf_value_t *obj; + + static nxt_conf_map_t nxt_clone_map_entry_conf[] = { + { + nxt_string("container"), + NXT_CONF_MAP_INT, + offsetof(nxt_clone_map_entry_t, container), + }, + + { + nxt_string("host"), + NXT_CONF_MAP_INT, + offsetof(nxt_clone_map_entry_t, host), + }, + + { + nxt_string("size"), + NXT_CONF_MAP_INT, + offsetof(nxt_clone_map_entry_t, size), + }, + }; + + map->size = nxt_conf_array_elements_count(map_array); + + if (map->size == 0) { + return NXT_OK; + } + + map->map = nxt_mp_alloc(mp, map->size * sizeof(nxt_clone_map_entry_t)); + if (nxt_slow_path(map->map == NULL)) { + return NXT_ERROR; + } + + for (i = 0; i < map->size; i++) { + obj = nxt_conf_get_array_element(map_array, i); + + ret = nxt_conf_map_object(mp, obj, nxt_clone_map_entry_conf, + nxt_nitems(nxt_clone_map_entry_conf), + map->map + i); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "clone map entry map error"); + return NXT_ERROR; + } + } + + return NXT_OK; +} + +#endif + +#if (NXT_HAVE_CLONE) + +static nxt_int_t +nxt_app_clone_flags(nxt_task_t *task, nxt_conf_value_t *namespaces, + nxt_clone_t *clone) +{ + uint32_t index; + nxt_str_t name; + nxt_int_t flag; + nxt_conf_value_t *value; + + index = 0; + + for ( ;; ) { + value = nxt_conf_next_object_member(namespaces, &name, &index); + + if (value == NULL) { + break; + } + + flag = 0; + +#if (NXT_HAVE_CLONE_NEWUSER) + if (nxt_str_eq(&name, "credential", 10)) { + flag = CLONE_NEWUSER; + } +#endif + +#if (NXT_HAVE_CLONE_NEWPID) + if (nxt_str_eq(&name, "pid", 3)) { + flag = CLONE_NEWPID; + } +#endif + +#if (NXT_HAVE_CLONE_NEWNET) + if (nxt_str_eq(&name, "network", 7)) { + flag = CLONE_NEWNET; + } +#endif + +#if (NXT_HAVE_CLONE_NEWUTS) + if (nxt_str_eq(&name, "uname", 5)) { + flag = CLONE_NEWUTS; + } +#endif + +#if (NXT_HAVE_CLONE_NEWNS) + if (nxt_str_eq(&name, "mount", 5)) { + flag = CLONE_NEWNS; + } +#endif + +#if (NXT_HAVE_CLONE_NEWCGROUP) + if (nxt_str_eq(&name, "cgroup", 6)) { + flag = CLONE_NEWCGROUP; + } +#endif + + if (!flag) { + nxt_alert(task, "unknown namespace flag: \"%V\"", &name); + return NXT_ERROR; + } + + if (nxt_conf_get_boolean(value)) { + clone->flags |= flag; + } + } + + return NXT_OK; +} + +#endif + + +#if (NXT_HAVE_ISOLATION_ROOTFS) + +static nxt_int_t +nxt_app_prepare_lang_mounts(nxt_task_t *task, nxt_process_t *process, + nxt_array_t *lang_mounts) +{ + u_char *p; + size_t i, n, rootfs_len, len; + nxt_mp_t *mp; + nxt_array_t *mounts; + const u_char *rootfs; + nxt_fs_mount_t *mnt, *lang_mnt; + + rootfs = process->isolation.rootfs; + rootfs_len = nxt_strlen(rootfs); + mp = process->mem_pool; + + /* copy to init mem pool */ + mounts = nxt_array_copy(mp, NULL, lang_mounts); + if (mounts == NULL) { + return NXT_ERROR; + } + + n = mounts->nelts; + mnt = mounts->elts; + lang_mnt = lang_mounts->elts; + + for (i = 0; i < n; i++) { + len = nxt_strlen(lang_mnt[i].dst); + + mnt[i].dst = nxt_mp_alloc(mp, rootfs_len + len + 1); + if (mnt[i].dst == NULL) { + return NXT_ERROR; + } + + p = nxt_cpymem(mnt[i].dst, rootfs, rootfs_len); + p = nxt_cpymem(p, lang_mnt[i].dst, len); + *p = '\0'; + } + + process->isolation.mounts = mounts; + + return NXT_OK; +} + + + +static nxt_int_t +nxt_app_prepare_rootfs(nxt_task_t *task, nxt_process_t *process) +{ + size_t i, n; + nxt_int_t ret, hasproc; + struct stat st; + nxt_array_t *mounts; + const u_char *dst; + nxt_fs_mount_t *mnt; + + hasproc = 0; + +#if (NXT_HAVE_CLONE_NEWPID) && (NXT_HAVE_CLONE_NEWNS) + nxt_fs_mount_t mount; + + if (nxt_is_clone_flag_set(process->isolation.clone.flags, NEWPID) + && nxt_is_clone_flag_set(process->isolation.clone.flags, NEWNS)) + { + /* + * This mount point will automatically be gone when the namespace is + * destroyed. + */ + + mount.fstype = (u_char *) "proc"; + mount.src = (u_char *) "proc"; + mount.dst = (u_char *) "/proc"; + mount.data = (u_char *) ""; + mount.flags = 0; + + ret = nxt_fs_mkdir_all(mount.dst, S_IRWXU | S_IRWXG | S_IRWXO); + if (nxt_fast_path(ret == NXT_OK)) { + ret = nxt_fs_mount(task, &mount); + if (nxt_fast_path(ret == NXT_OK)) { + hasproc = 1; + } + + } else { + nxt_log(task, NXT_LOG_WARN, "mkdir(%s) %E", mount.dst, nxt_errno); + } + } +#endif + + mounts = process->isolation.mounts; + + n = mounts->nelts; + mnt = mounts->elts; + + for (i = 0; i < n; i++) { + dst = mnt[i].dst; + + if (nxt_slow_path(nxt_memcmp(mnt[i].fstype, "bind", 4) == 0 + && stat((const char *) mnt[i].src, &st) != 0)) + { + nxt_log(task, NXT_LOG_WARN, "host path not found: %s", mnt[i].src); + continue; + } + + if (hasproc && nxt_memcmp(mnt[i].fstype, "proc", 4) == 0 + && nxt_memcmp(mnt[i].dst, "/proc", 5) == 0) + { + continue; + } + + ret = nxt_fs_mkdir_all(dst, S_IRWXU | S_IRWXG | S_IRWXO); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "mkdir(%s) %E", dst, nxt_errno); + goto undo; + } + + ret = nxt_fs_mount(task, &mnt[i]); + if (nxt_slow_path(ret != NXT_OK)) { + goto undo; + } + } + + return NXT_OK; + +undo: + + n = i + 1; + + for (i = 0; i < n; i++) { + nxt_fs_unmount(mnt[i].dst); + } + + return NXT_ERROR; +} + +#endif + + +static u_char * +nxt_cstr_dup(nxt_mp_t *mp, u_char *dst, u_char *src) +{ + u_char *p; + size_t len; + + len = nxt_strlen(src); + + if (dst == NULL) { + dst = nxt_mp_alloc(mp, len + 1); + if (nxt_slow_path(dst == NULL)) { + return NULL; + } + } + + p = nxt_cpymem(dst, src, len); + *p = '\0'; + + return dst; +} + + nxt_app_lang_module_t * nxt_app_lang_module(nxt_runtime_t *rt, nxt_str_t *name) { @@ -539,7 +1269,7 @@ nxt_unit_default_init(nxt_task_t *task, nxt_unit_init_t *init) nxt_fd_blocking(task, main_port->pair[1]); - init->ready_stream = my_port->process->init->stream; + init->ready_stream = my_port->process->stream; init->read_port.id.pid = my_port->pid; init->read_port.id.id = my_port->id; diff --git a/src/nxt_application.h b/src/nxt_application.h index e7177887..3144dc3f 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -27,6 +27,8 @@ typedef enum { typedef struct nxt_app_module_s nxt_app_module_t; +typedef nxt_int_t (*nxt_application_setup_t)(nxt_task_t *task, + nxt_process_t *process, nxt_common_app_conf_t *conf); typedef struct { @@ -34,12 +36,10 @@ typedef struct { u_char *version; char *file; nxt_app_module_t *module; + nxt_array_t *mounts; /* of nxt_fs_mount_t */ } nxt_app_lang_module_t; -typedef struct nxt_common_app_conf_s nxt_common_app_conf_t; - - typedef struct { char *executable; nxt_conf_value_t *arguments; @@ -54,9 +54,7 @@ typedef struct { typedef struct { - char *root; - nxt_str_t script; - nxt_str_t index; + nxt_conf_value_t *targets; nxt_conf_value_t *options; } nxt_php_app_conf_t; @@ -101,6 +99,8 @@ struct nxt_common_app_conf_s { nxt_ruby_app_conf_t ruby; nxt_java_app_conf_t java; } u; + + nxt_conf_value_t *self; }; @@ -111,10 +111,11 @@ struct nxt_app_module_s { nxt_str_t type; const char *version; - nxt_int_t (*pre_init)(nxt_task_t *task, - nxt_common_app_conf_t *conf); - nxt_int_t (*init)(nxt_task_t *task, - nxt_common_app_conf_t *conf); + const nxt_fs_mount_t *mounts; + nxt_uint_t nmounts; + + nxt_application_setup_t setup; + nxt_process_start_t start; }; diff --git a/src/nxt_array.c b/src/nxt_array.c index 82019f92..6fe9ad6a 100644 --- a/src/nxt_array.c +++ b/src/nxt_array.c @@ -109,3 +109,42 @@ nxt_array_remove(nxt_array_t *array, void *elt) array->nelts--; } + + +nxt_array_t * +nxt_array_copy(nxt_mp_t *mp, nxt_array_t *dst, nxt_array_t *src) +{ + void *data; + uint32_t i, size; + + size = src->size; + + if (dst == NULL) { + dst = nxt_array_create(mp, src->nelts, size); + if (nxt_slow_path(dst == NULL)) { + return NULL; + } + } + + nxt_assert(size == dst->size); + + if (dst->nalloc >= src->nelts) { + nxt_memcpy(dst->elts, src->elts, src->nelts * size); + + } else { + nxt_memcpy(dst->elts, src->elts, dst->nelts * size); + + for (i = dst->nelts; i < src->nelts; i++) { + data = nxt_array_add(dst); + if (nxt_slow_path(data == NULL)) { + return NULL; + } + + nxt_memcpy(data, src->elts + (i * size), size); + } + } + + dst->nelts = src->nelts; + + return dst; +} diff --git a/src/nxt_array.h b/src/nxt_array.h index 8d2b14f1..5762ec27 100644 --- a/src/nxt_array.h +++ b/src/nxt_array.h @@ -24,7 +24,8 @@ NXT_EXPORT void nxt_array_destroy(nxt_array_t *array); NXT_EXPORT void *nxt_array_add(nxt_array_t *array); NXT_EXPORT void *nxt_array_zero_add(nxt_array_t *array); NXT_EXPORT void nxt_array_remove(nxt_array_t *array, void *elt); - +NXT_EXPORT nxt_array_t *nxt_array_copy(nxt_mp_t *mp, nxt_array_t *dst, + nxt_array_t *src); #define \ nxt_array_last(array) \ diff --git a/src/nxt_capability.c b/src/nxt_capability.c index dfa7a834..24fd55d0 100644 --- a/src/nxt_capability.c +++ b/src/nxt_capability.c @@ -39,6 +39,7 @@ nxt_capability_set(nxt_task_t *task, nxt_capabilities_t *cap) if (geteuid() == 0) { cap->setid = 1; + cap->chroot = 1; return NXT_OK; } @@ -91,6 +92,10 @@ nxt_capability_specific_set(nxt_task_t *task, nxt_capabilities_t *cap) return NXT_ERROR; } + if ((val->effective & (1 << CAP_SYS_CHROOT)) != 0) { + cap->chroot = 1; + } + if ((val->effective & (1 << CAP_SETUID)) == 0) { return NXT_OK; } diff --git a/src/nxt_capability.h b/src/nxt_capability.h index 60bbd5f8..1575d409 100644 --- a/src/nxt_capability.h +++ b/src/nxt_capability.h @@ -7,7 +7,8 @@ #define _NXT_CAPABILITY_INCLUDED_ typedef struct { - uint8_t setid; /* 1 bit */ + uint8_t setid; /* 1 bit */ + uint8_t chroot; /* 1 bit */ } nxt_capabilities_t; diff --git a/src/nxt_cert.c b/src/nxt_cert.c index ee258646..9e825d80 100644 --- a/src/nxt_cert.c +++ b/src/nxt_cert.c @@ -797,12 +797,11 @@ nxt_cert_info_delete(nxt_str_t *name) nxt_array_t * -nxt_cert_store_load(nxt_task_t *task) +nxt_cert_store_load(nxt_task_t *task, nxt_mp_t *mp) { DIR *dir; size_t size, alloc; u_char *buf, *p; - nxt_mp_t *mp; nxt_str_t name; nxt_int_t ret; nxt_file_t file; @@ -818,14 +817,8 @@ nxt_cert_store_load(nxt_task_t *task) return NULL; } - mp = nxt_mp_create(1024, 128, 256, 32); - if (nxt_slow_path(mp == NULL)) { - return NULL; - } - certs = nxt_array_create(mp, 16, sizeof(nxt_cert_item_t)); if (nxt_slow_path(certs == NULL)) { - nxt_mp_destroy(mp); return NULL; } @@ -933,7 +926,7 @@ nxt_cert_store_release(nxt_array_t *certs) nxt_fd_close(items[i].fd); } - nxt_mp_destroy(certs->mem_pool); + nxt_array_destroy(certs); } diff --git a/src/nxt_cert.h b/src/nxt_cert.h index 319d5d3c..dbaddcf9 100644 --- a/src/nxt_cert.h +++ b/src/nxt_cert.h @@ -19,7 +19,7 @@ nxt_conf_value_t *nxt_cert_info_get(nxt_str_t *name); nxt_conf_value_t *nxt_cert_info_get_all(nxt_mp_t *mp); nxt_int_t nxt_cert_info_delete(nxt_str_t *name); -nxt_array_t *nxt_cert_store_load(nxt_task_t *task); +nxt_array_t *nxt_cert_store_load(nxt_task_t *task, nxt_mp_t *mem_pool); void nxt_cert_store_release(nxt_array_t *certs); void nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp, diff --git a/src/nxt_clone.h b/src/nxt_clone.h index dcccf1db..e89fd82d 100644 --- a/src/nxt_clone.h +++ b/src/nxt_clone.h @@ -3,8 +3,8 @@ * Copyright (C) NGINX, Inc. */ -#ifndef _NXT_CLONE_INCLUDED_ -#define _NXT_CLONE_INCLUDED_ +#ifndef _NXT_CLONE_H_INCLUDED_ +#define _NXT_CLONE_H_INCLUDED_ #if (NXT_HAVE_CLONE_NEWUSER) @@ -36,10 +36,14 @@ typedef struct { pid_t nxt_clone(nxt_int_t flags); +#define nxt_is_clone_flag_set(flags, test) \ + ((flags & CLONE_##test) == CLONE_##test) + + #if (NXT_HAVE_CLONE_NEWUSER) -#define NXT_CLONE_USER(flags) \ - ((flags & CLONE_NEWUSER) == CLONE_NEWUSER) +#define NXT_CLONE_MNT(flags) \ + ((flags & CLONE_NEWNS) == CLONE_NEWNS) NXT_EXPORT nxt_int_t nxt_clone_credential_map(nxt_task_t *task, pid_t pid, nxt_credential_t *creds, nxt_clone_t *clone); @@ -50,4 +54,5 @@ NXT_EXPORT nxt_int_t nxt_clone_vldt_credential_gidmap(nxt_task_t *task, #endif -#endif /* _NXT_CLONE_INCLUDED_ */ + +#endif /* _NXT_CLONE_H_INCLUDED_ */ diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 201a3a14..149af39a 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -118,7 +118,7 @@ NXT_EXPORT double nxt_conf_get_number(nxt_conf_value_t *value); NXT_EXPORT uint8_t nxt_conf_get_boolean(nxt_conf_value_t *value); // FIXME reimplement and reorder functions below -nxt_uint_t nxt_conf_object_members_count(nxt_conf_value_t *value); +NXT_EXPORT nxt_uint_t nxt_conf_object_members_count(nxt_conf_value_t *value); nxt_conf_value_t *nxt_conf_create_object(nxt_mp_t *mp, nxt_uint_t count); void nxt_conf_set_member(nxt_conf_value_t *object, nxt_str_t *name, nxt_conf_value_t *value, uint32_t index); diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index bc03bdfb..c4f78608 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -23,13 +23,24 @@ typedef enum { NXT_CONF_VLDT_OBJECT = 1 << NXT_CONF_OBJECT, } nxt_conf_vldt_type_t; +#define NXT_CONF_VLDT_ANY_TYPE (NXT_CONF_VLDT_NULL \ + |NXT_CONF_VLDT_BOOLEAN \ + |NXT_CONF_VLDT_NUMBER \ + |NXT_CONF_VLDT_STRING \ + |NXT_CONF_VLDT_ARRAY \ + |NXT_CONF_VLDT_OBJECT) + + +typedef nxt_int_t (*nxt_conf_vldt_handler_t)(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, + void *data); + typedef struct { - nxt_str_t name; - nxt_conf_vldt_type_t type; - nxt_int_t (*validator)(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data); - void *data; + nxt_str_t name; + nxt_conf_vldt_type_t type; + nxt_conf_vldt_handler_t validator; + void *data; } nxt_conf_vldt_object_t; @@ -74,6 +85,16 @@ static nxt_int_t nxt_conf_vldt_routes_member(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_route(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_encoded_patterns_sets( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_match_encoded_patterns_set( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_encoded_patterns_set_member( + nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_match_encoded_patterns( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_match_encoded_pattern( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_match_patterns(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, @@ -106,6 +127,14 @@ static nxt_int_t nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_php(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_php_targets_exclusive( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, @@ -324,12 +353,12 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[] = { { nxt_string("uri"), NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, - &nxt_conf_vldt_match_patterns, + &nxt_conf_vldt_match_encoded_patterns, NULL }, { nxt_string("arguments"), NXT_CONF_VLDT_OBJECT | NXT_CONF_VLDT_ARRAY, - &nxt_conf_vldt_match_patterns_sets, + &nxt_conf_vldt_match_encoded_patterns_sets, NULL }, { nxt_string("headers"), @@ -546,6 +575,24 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_app_isolation_members[] = { #endif +#if (NXT_HAVE_ISOLATION_ROOTFS) + + { nxt_string("rootfs"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + +#endif + +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) + + { nxt_string("new_privs"), + NXT_CONF_VLDT_BOOLEAN, + NULL, + NULL }, + +#endif + NXT_CONF_VLDT_END }; @@ -630,6 +677,24 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_php_target_members[] = { + { nxt_string("root"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + { nxt_string("script"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + { nxt_string("index"), + NXT_CONF_VLDT_STRING, + NULL, + NULL } +}; + + static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[] = { { nxt_string("file"), NXT_CONF_VLDT_STRING, @@ -650,7 +715,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[] = { }; -static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { +static nxt_conf_vldt_object_t nxt_conf_vldt_php_common_members[] = { + { nxt_string("options"), + NXT_CONF_VLDT_OBJECT, + &nxt_conf_vldt_object, + (void *) &nxt_conf_vldt_php_options_members }, + + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members) +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_php_notargets_members[] = { { nxt_string("root"), NXT_CONF_VLDT_STRING, NULL, @@ -666,12 +741,32 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { NULL, NULL }, - { nxt_string("options"), + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_php_common_members) +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { + { nxt_string("root"), + NXT_CONF_VLDT_ANY_TYPE, + &nxt_conf_vldt_php_targets_exclusive, + (void *) "root" }, + + { nxt_string("script"), + NXT_CONF_VLDT_ANY_TYPE, + &nxt_conf_vldt_php_targets_exclusive, + (void *) "script" }, + + { nxt_string("index"), + NXT_CONF_VLDT_ANY_TYPE, + &nxt_conf_vldt_php_targets_exclusive, + (void *) "index" }, + + { nxt_string("targets"), NXT_CONF_VLDT_OBJECT, - &nxt_conf_vldt_object, - (void *) &nxt_conf_vldt_php_options_members }, + &nxt_conf_vldt_php_targets, + NULL }, - NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members) + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_php_common_members) }; @@ -755,7 +850,7 @@ nxt_conf_validate(nxt_conf_validation_t *vldt) } -#define NXT_CONF_VLDT_ANY_TYPE \ +#define NXT_CONF_VLDT_ANY_TYPE_STR \ "either a null, a boolean, an integer, " \ "a number, a string, an array, or an object" @@ -768,7 +863,7 @@ nxt_conf_vldt_type(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_str_t expected; nxt_bool_t serial; nxt_uint_t value_type, n, t; - u_char buf[nxt_length(NXT_CONF_VLDT_ANY_TYPE)]; + u_char buf[nxt_length(NXT_CONF_VLDT_ANY_TYPE_STR)]; static nxt_str_t type_name[] = { nxt_string("a null"), @@ -1032,79 +1127,94 @@ static nxt_int_t nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) { - u_char *p; - nxt_str_t pass, first, second; + nxt_str_t pass; + nxt_int_t ret; + nxt_str_t segments[3]; - nxt_conf_get_string(value, &pass); + static nxt_str_t targets_str = nxt_string("targets"); - p = nxt_memchr(pass.start, '/', pass.length); + nxt_conf_get_string(value, &pass); - if (p != NULL) { - first.length = p - pass.start; - first.start = pass.start; + ret = nxt_http_pass_segments(vldt->pool, &pass, segments, 3); - if (pass.length - first.length == 1) { - goto error; + if (ret != NXT_OK) { + if (ret == NXT_DECLINED) { + return nxt_conf_vldt_error(vldt, "Request \"pass\" value \"%V\" " + "is invalid.", &pass); } - second.length = pass.length - first.length - 1; - second.start = p + 1; - - } else { - first = pass; - second.length = 0; + return NXT_ERROR; } - if (nxt_str_eq(&first, "applications", 12)) { + if (nxt_str_eq(&segments[0], "applications", 12)) { - if (second.length == 0) { + if (segments[1].length == 0) { goto error; } - value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + value = nxt_conf_get_object_member(vldt->conf, &segments[0], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } - value = nxt_conf_get_object_member(value, &second, NULL); + value = nxt_conf_get_object_member(value, &segments[1], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } + if (segments[2].length > 0) { + value = nxt_conf_get_object_member(value, &targets_str, NULL); + + if (value == NULL) { + goto error; + } + + value = nxt_conf_get_object_member(value, &segments[2], NULL); + + if (value == NULL) { + goto error; + } + } + return NXT_OK; } - if (nxt_str_eq(&first, "upstreams", 9)) { + if (nxt_str_eq(&segments[0], "upstreams", 9)) { - if (second.length == 0) { + if (segments[1].length == 0 || segments[2].length != 0) { goto error; } - value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + value = nxt_conf_get_object_member(vldt->conf, &segments[0], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } - value = nxt_conf_get_object_member(value, &second, NULL); + value = nxt_conf_get_object_member(value, &segments[1], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } return NXT_OK; } - if (nxt_str_eq(&first, "routes", 6)) { - value = nxt_conf_get_object_member(vldt->conf, &first, NULL); + if (nxt_str_eq(&segments[0], "routes", 6)) { + + if (segments[2].length != 0) { + goto error; + } + + value = nxt_conf_get_object_member(vldt->conf, &segments[0], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } - if (second.length == 0) { + if (segments[1].length == 0) { if (nxt_conf_type(value) != NXT_CONF_ARRAY) { goto error; } @@ -1116,9 +1226,9 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, goto error; } - value = nxt_conf_get_object_member(value, &second, NULL); + value = nxt_conf_get_object_member(value, &segments[1], NULL); - if (nxt_slow_path(value == NULL)) { + if (value == NULL) { goto error; } @@ -1297,6 +1407,109 @@ nxt_conf_vldt_match_pattern(nxt_conf_validation_t *vldt, } +static nxt_int_t nxt_conf_vldt_match_encoded_patterns_sets( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_match_encoded_patterns_set); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_match_encoded_patterns_set(vldt, value); +} + + +static nxt_int_t nxt_conf_vldt_match_encoded_patterns_set( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern for " + "\"arguments\" must be an object."); + } + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_match_encoded_patterns_set_member); +} + + +static nxt_int_t +nxt_conf_vldt_match_encoded_patterns_set_member(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value) +{ + u_char *p, *end; + + if (nxt_slow_path(name->length == 0)) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern objects must " + "not contain empty member names."); + } + + p = nxt_mp_nget(vldt->pool, name->length); + if (nxt_slow_path(p == NULL)) { + return NXT_ERROR; + } + + end = nxt_decode_uri(p, name->start, name->length); + if (nxt_slow_path(end == NULL)) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern for " + "\"arguments\" is encoded but is invalid."); + } + + return nxt_conf_vldt_match_encoded_patterns(vldt, value, NULL); +} + + +static nxt_int_t +nxt_conf_vldt_match_encoded_patterns(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + if (nxt_conf_type(value) == NXT_CONF_ARRAY) { + return nxt_conf_vldt_array_iterator(vldt, value, + &nxt_conf_vldt_match_encoded_pattern); + } + + /* NXT_CONF_STRING */ + + return nxt_conf_vldt_match_encoded_pattern(vldt, value); +} + + +static nxt_int_t +nxt_conf_vldt_match_encoded_pattern(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value) +{ + u_char *p, *end; + nxt_int_t ret; + nxt_str_t pattern; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern for \"uri\" " + "must be a string."); + } + + ret = nxt_conf_vldt_match_pattern(vldt, value); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } + + nxt_conf_get_string(value, &pattern); + + p = nxt_mp_nget(vldt->pool, pattern.length); + if (nxt_slow_path(p == NULL)) { + return NXT_ERROR; + } + + end = nxt_decode_uri(p, pattern.start, pattern.length); + if (nxt_slow_path(end == NULL)) { + return nxt_conf_vldt_error(vldt, "The \"match\" pattern for \"uri\" " + "is encoded but is invalid."); + } + + return NXT_OK; +} + + static nxt_int_t nxt_conf_vldt_match_addrs(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) @@ -1488,13 +1701,17 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, static nxt_str_t type_str = nxt_string("type"); - static void *members[] = { - nxt_conf_vldt_external_members, - nxt_conf_vldt_python_members, - nxt_conf_vldt_php_members, - nxt_conf_vldt_perl_members, - nxt_conf_vldt_ruby_members, - nxt_conf_vldt_java_members, + static struct { + nxt_conf_vldt_handler_t validator; + nxt_conf_vldt_object_t *members; + + } types[] = { + { nxt_conf_vldt_object, nxt_conf_vldt_external_members }, + { nxt_conf_vldt_object, nxt_conf_vldt_python_members }, + { nxt_conf_vldt_php, NULL }, + { nxt_conf_vldt_object, nxt_conf_vldt_perl_members }, + { nxt_conf_vldt_object, nxt_conf_vldt_ruby_members }, + { nxt_conf_vldt_object, nxt_conf_vldt_java_members }, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT); @@ -1528,7 +1745,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, &type); } - return nxt_conf_vldt_object(vldt, value, members[lang->type]); + return types[lang->type].validator(vldt, value, types[lang->type].members); } @@ -1940,6 +2157,70 @@ nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) static nxt_int_t +nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_conf_value_t *targets; + + static nxt_str_t targets_str = nxt_string("targets"); + + targets = nxt_conf_get_object_member(value, &targets_str, NULL); + + if (targets != NULL) { + return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_php_members); + } + + return nxt_conf_vldt_object(vldt, value, + nxt_conf_vldt_php_notargets_members); +} + + +static nxt_int_t +nxt_conf_vldt_php_targets_exclusive(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " + "with the \"targets\" object.", data); +} + + +static nxt_int_t +nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_uint_t n; + + n = nxt_conf_object_members_count(value); + + if (n > 254) { + return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " + "contain more than 254 members."); + } + + return nxt_conf_vldt_object_iterator(vldt, value, + &nxt_conf_vldt_php_target); +} + + +static nxt_int_t +nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, + "The PHP target name must not be empty."); + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"%V\" PHP target must be " + "an object.", name); + } + + return nxt_conf_vldt_object(vldt, value, &nxt_conf_vldt_php_target_members); +} + + +static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value) { diff --git a/src/nxt_controller.c b/src/nxt_controller.c index f9b2cf26..a61c127d 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -39,14 +39,21 @@ typedef struct { } nxt_controller_response_t; +static nxt_int_t nxt_controller_prefork(nxt_task_t *task, + nxt_process_t *process, nxt_mp_t *mp); +static nxt_int_t nxt_controller_start(nxt_task_t *task, + nxt_process_data_t *data); static void nxt_controller_process_new_port_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); static void nxt_controller_send_current_conf(nxt_task_t *task); static void nxt_controller_router_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); +static void nxt_controller_remove_pid_handler(nxt_task_t *task, + nxt_port_recv_msg_t *msg); static nxt_int_t nxt_controller_conf_default(void); static void nxt_controller_conf_init_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); +static void nxt_controller_flush_requests(nxt_task_t *task); static nxt_int_t nxt_controller_conf_send(nxt_task_t *task, nxt_conf_value_t *conf, nxt_port_rpc_handler_t handler, void *data); @@ -75,12 +82,15 @@ static void nxt_controller_process_request(nxt_task_t *task, nxt_controller_request_t *req); static void nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, nxt_str_t *path); +static nxt_bool_t nxt_controller_check_postpone_request(nxt_task_t *task); #if (NXT_TLS) static void nxt_controller_process_cert(nxt_task_t *task, nxt_controller_request_t *req, nxt_str_t *path); static void nxt_controller_process_cert_save(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_bool_t nxt_controller_cert_in_use(nxt_str_t *name); +static void nxt_controller_cert_cleanup(nxt_task_t *task, void *obj, + void *data); #endif static void nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); @@ -103,6 +113,7 @@ static nxt_uint_t nxt_controller_listening; static nxt_uint_t nxt_controller_router_ready; static nxt_controller_conf_t nxt_controller_conf; static nxt_queue_t nxt_controller_waiting_requests; +static nxt_bool_t nxt_controller_waiting_init_conf; static const nxt_event_conn_state_t nxt_controller_conn_read_state; @@ -111,21 +122,117 @@ static const nxt_event_conn_state_t nxt_controller_conn_write_state; static const nxt_event_conn_state_t nxt_controller_conn_close_state; -nxt_port_handlers_t nxt_controller_process_port_handlers = { - .quit = nxt_worker_process_quit_handler, +static const nxt_port_handlers_t nxt_controller_process_port_handlers = { + .quit = nxt_signal_quit_handler, .new_port = nxt_controller_process_new_port_handler, .change_file = nxt_port_change_log_file_handler, .mmap = nxt_port_mmap_handler, .process_ready = nxt_controller_router_ready_handler, .data = nxt_port_data_handler, - .remove_pid = nxt_port_remove_pid_handler, + .remove_pid = nxt_controller_remove_pid_handler, .rpc_ready = nxt_port_rpc_handler, .rpc_error = nxt_port_rpc_handler, }; -nxt_int_t -nxt_controller_start(nxt_task_t *task, void *data) +const nxt_process_init_t nxt_controller_process = { + .name = "controller", + .type = NXT_PROCESS_CONTROLLER, + .prefork = nxt_controller_prefork, + .restart = 1, + .setup = nxt_process_core_setup, + .start = nxt_controller_start, + .port_handlers = &nxt_controller_process_port_handlers, + .signals = nxt_process_signals, +}; + + +static nxt_int_t +nxt_controller_prefork(nxt_task_t *task, nxt_process_t *process, nxt_mp_t *mp) +{ + ssize_t n; + nxt_int_t ret; + nxt_str_t *conf; + nxt_file_t file; + nxt_runtime_t *rt; + nxt_file_info_t fi; + nxt_controller_init_t ctrl_init; + + nxt_log(task, NXT_LOG_INFO, "controller started"); + + rt = task->thread->runtime; + + nxt_memzero(&ctrl_init, sizeof(nxt_controller_init_t)); + + conf = &ctrl_init.conf; + + nxt_memzero(&file, sizeof(nxt_file_t)); + + file.name = (nxt_file_name_t *) rt->conf; + + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + + if (ret == NXT_OK) { + ret = nxt_file_info(&file, &fi); + + if (nxt_fast_path(ret == NXT_OK && nxt_is_file(&fi))) { + conf->length = nxt_file_size(&fi); + conf->start = nxt_mp_alloc(mp, conf->length); + if (nxt_slow_path(conf->start == NULL)) { + nxt_file_close(task, &file); + return NXT_ERROR; + } + + n = nxt_file_read(&file, conf->start, conf->length, 0); + + if (nxt_slow_path(n != (ssize_t) conf->length)) { + conf->start = NULL; + conf->length = 0; + + nxt_alert(task, "failed to restore previous configuration: " + "cannot read the file"); + } + } + + nxt_file_close(task, &file); + } + +#if (NXT_TLS) + ctrl_init.certs = nxt_cert_store_load(task, mp); + + nxt_mp_cleanup(mp, nxt_controller_cert_cleanup, task, ctrl_init.certs, rt); +#endif + + process->data.controller = ctrl_init; + + return NXT_OK; +} + + +#if (NXT_TLS) + +static void +nxt_controller_cert_cleanup(nxt_task_t *task, void *obj, void *data) +{ + pid_t main_pid; + nxt_array_t *certs; + nxt_runtime_t *rt; + + certs = obj; + rt = data; + + main_pid = rt->port_by_type[NXT_PROCESS_MAIN]->pid; + + if (nxt_pid == main_pid && certs != NULL) { + nxt_cert_store_release(certs); + } +} + +#endif + + +static nxt_int_t +nxt_controller_start(nxt_task_t *task, nxt_process_data_t *data) { nxt_mp_t *mp; nxt_int_t ret; @@ -144,15 +251,13 @@ nxt_controller_start(nxt_task_t *task, void *data) nxt_queue_init(&nxt_controller_waiting_requests); - init = data; + init = &data->controller; #if (NXT_TLS) - if (init->certs != NULL) { nxt_cert_info_init(task, init->certs); nxt_cert_store_release(init->certs); } - #endif json = &init->conf; @@ -167,8 +272,6 @@ nxt_controller_start(nxt_task_t *task, void *data) } conf = nxt_conf_json_parse_str(mp, json); - nxt_free(json->start); - if (nxt_slow_path(conf == NULL)) { nxt_alert(task, "failed to restore previous configuration: " "file is corrupted or not enough memory"); @@ -245,6 +348,8 @@ nxt_controller_send_current_conf(nxt_task_t *task) nxt_controller_conf_init_handler, NULL); if (nxt_fast_path(rc == NXT_OK)) { + nxt_controller_waiting_init_conf = 1; + return; } @@ -266,6 +371,8 @@ nxt_controller_send_current_conf(nxt_task_t *task) } nxt_controller_listening = 1; + + nxt_controller_flush_requests(task); } @@ -288,6 +395,28 @@ nxt_controller_router_ready_handler(nxt_task_t *task, } +static void +nxt_controller_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + nxt_pid_t pid; + nxt_process_t *process; + nxt_runtime_t *rt; + + rt = task->thread->runtime; + + nxt_assert(nxt_buf_used_size(msg->buf) == sizeof(pid)); + + nxt_memcpy(&pid, msg->buf->mem.pos, sizeof(pid)); + + process = nxt_runtime_process_find(rt, pid); + if (process != NULL && nxt_process_type(process) == NXT_PROCESS_ROUTER) { + nxt_controller_router_ready = 0; + } + + nxt_port_remove_pid_handler(task, msg); +} + + static nxt_int_t nxt_controller_conf_default(void) { @@ -322,6 +451,8 @@ nxt_controller_conf_init_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, { nxt_runtime_t *rt; + nxt_controller_waiting_init_conf = 0; + if (msg->port_msg.type != NXT_PORT_MSG_RPC_READY) { nxt_alert(task, "failed to apply previous configuration"); @@ -343,6 +474,25 @@ nxt_controller_conf_init_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_controller_listening = 1; } + + nxt_controller_flush_requests(task); +} + + +static void +nxt_controller_flush_requests(nxt_task_t *task) +{ + nxt_queue_t queue; + nxt_controller_request_t *req; + + nxt_queue_init(&queue); + nxt_queue_add(&queue, &nxt_controller_waiting_requests); + + nxt_queue_init(&nxt_controller_waiting_requests); + + nxt_queue_each(req, &queue, nxt_controller_request_t, link) { + nxt_controller_process_request(task, req); + } nxt_queue_loop; } @@ -361,9 +511,8 @@ nxt_controller_conf_send(nxt_task_t *task, nxt_conf_value_t *conf, router_port = rt->port_by_type[NXT_PROCESS_ROUTER]; - if (nxt_slow_path(router_port == NULL || !nxt_controller_router_ready)) { - return NXT_DECLINED; - } + nxt_assert(router_port != NULL); + nxt_assert(nxt_controller_router_ready); controller_port = rt->port_by_type[NXT_PROCESS_CONTROLLER]; @@ -961,7 +1110,7 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, if (post || nxt_str_eq(&req->parser.method, "PUT", 3)) { - if (!nxt_queue_is_empty(&nxt_controller_waiting_requests)) { + if (nxt_controller_check_postpone_request(task)) { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1058,10 +1207,6 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, if (nxt_slow_path(rc != NXT_OK)) { nxt_mp_destroy(mp); - if (rc == NXT_DECLINED) { - goto no_router; - } - /* rc == NXT_ERROR */ goto alloc_fail; } @@ -1076,7 +1221,7 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, if (nxt_str_eq(&req->parser.method, "DELETE", 6)) { - if (!nxt_queue_is_empty(&nxt_controller_waiting_requests)) { + if (nxt_controller_check_postpone_request(task)) { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1143,10 +1288,6 @@ nxt_controller_process_config(nxt_task_t *task, nxt_controller_request_t *req, if (nxt_slow_path(rc != NXT_OK)) { nxt_mp_destroy(mp); - if (rc == NXT_DECLINED) { - goto no_router; - } - /* rc == NXT_ERROR */ goto alloc_fail; } @@ -1193,16 +1334,27 @@ alloc_fail: resp.offset = -1; nxt_controller_response(task, req, &resp); - return; +} -no_router: - resp.status = 500; - resp.title = (u_char *) "Router process isn't available."; - resp.offset = -1; +static nxt_bool_t +nxt_controller_check_postpone_request(nxt_task_t *task) +{ + nxt_port_t *router_port; + nxt_runtime_t *rt; - nxt_controller_response(task, req, &resp); - return; + if (!nxt_queue_is_empty(&nxt_controller_waiting_requests) + || nxt_controller_waiting_init_conf + || !nxt_controller_router_ready) + { + return 1; + } + + rt = task->thread->runtime; + + router_port = rt->port_by_type[NXT_PROCESS_ROUTER]; + + return (router_port == NULL); } @@ -1469,7 +1621,6 @@ static void nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { - nxt_queue_t queue; nxt_controller_request_t *req; nxt_controller_response_t resp; @@ -1502,14 +1653,7 @@ nxt_controller_conf_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, nxt_controller_response(task, req, &resp); - nxt_queue_init(&queue); - nxt_queue_add(&queue, &nxt_controller_waiting_requests); - - nxt_queue_init(&nxt_controller_waiting_requests); - - nxt_queue_each(req, &queue, nxt_controller_request_t, link) { - nxt_controller_process_request(task, req); - } nxt_queue_loop; + nxt_controller_flush_requests(task); } diff --git a/src/nxt_external.c b/src/nxt_external.c index e498a938..6370a9c4 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -9,8 +9,7 @@ #include <nxt_unit.h> -static nxt_int_t nxt_external_init(nxt_task_t *task, - nxt_common_app_conf_t *conf); +static nxt_int_t nxt_external_start(nxt_task_t *task, nxt_process_data_t *data); nxt_app_module_t nxt_external_module = { @@ -19,7 +18,9 @@ nxt_app_module_t nxt_external_module = { nxt_string("external"), "*", NULL, - nxt_external_init, + 0, + NULL, + nxt_external_start, }; @@ -58,7 +59,7 @@ nxt_external_fd_no_cloexec(nxt_task_t *task, nxt_socket_t fd) static nxt_int_t -nxt_external_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_external_start(nxt_task_t *task, nxt_process_data_t *data) { char **argv; u_char buf[256]; @@ -71,9 +72,11 @@ nxt_external_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_port_t *my_port, *main_port; nxt_runtime_t *rt; nxt_conf_value_t *value; + nxt_common_app_conf_t *conf; nxt_external_app_conf_t *c; rt = task->thread->runtime; + conf = data->app; main_port = rt->port_by_type[NXT_PROCESS_MAIN]; my_port = nxt_runtime_port_find(rt, nxt_pid, 0); @@ -99,7 +102,7 @@ nxt_external_init(nxt_task_t *task, nxt_common_app_conf_t *conf) "%PI,%ud,%d;" "%PI,%ud,%d;" "%d,%z,%Z", - NXT_VERSION, my_port->process->init->stream, + NXT_VERSION, my_port->process->stream, main_port->pid, main_port->id, main_port->pair[1], my_port->pid, my_port->id, my_port->pair[0], 2, conf->shm_limit); diff --git a/src/nxt_fs.c b/src/nxt_fs.c new file mode 100644 index 00000000..fe271802 --- /dev/null +++ b/src/nxt_fs.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_main.h> + +#if (NXT_HAVE_FREEBSD_NMOUNT) +#include <sys/param.h> +#include <sys/uio.h> +#endif + + +static nxt_int_t nxt_fs_mkdir(const u_char *dir, mode_t mode); + + +#if (NXT_HAVE_LINUX_MOUNT) + +nxt_int_t +nxt_fs_mount(nxt_task_t *task, nxt_fs_mount_t *mnt) +{ + int rc; + + rc = mount((const char *) mnt->src, (const char *) mnt->dst, + (const char *) mnt->fstype, mnt->flags, mnt->data); + + if (nxt_slow_path(rc < 0)) { + nxt_alert(task, "mount(\"%s\", \"%s\", \"%s\", %d, \"%s\") %E", + mnt->src, mnt->dst, mnt->fstype, mnt->flags, mnt->data, + nxt_errno); + + return NXT_ERROR; + } + + return NXT_OK; +} + + +#elif (NXT_HAVE_FREEBSD_NMOUNT) + +nxt_int_t +nxt_fs_mount(nxt_task_t *task, nxt_fs_mount_t *mnt) +{ + const char *fstype; + uint8_t is_bind, is_proc; + struct iovec iov[8]; + char errmsg[256]; + + is_bind = nxt_strncmp(mnt->fstype, "bind", 4) == 0; + is_proc = nxt_strncmp(mnt->fstype, "proc", 4) == 0; + + if (nxt_slow_path(!is_bind && !is_proc)) { + nxt_alert(task, "mount type \"%s\" not implemented.", mnt->fstype); + return NXT_ERROR; + } + + if (is_bind) { + fstype = "nullfs"; + + } else { + fstype = "procfs"; + } + + iov[0].iov_base = (void *) "fstype"; + iov[0].iov_len = 7; + iov[1].iov_base = (void *) fstype; + iov[1].iov_len = strlen(fstype) + 1; + iov[2].iov_base = (void *) "fspath"; + iov[2].iov_len = 7; + iov[3].iov_base = (void *) mnt->dst; + iov[3].iov_len = nxt_strlen(mnt->dst) + 1; + iov[4].iov_base = (void *) "target"; + iov[4].iov_len = 7; + iov[5].iov_base = (void *) mnt->src; + iov[5].iov_len = nxt_strlen(mnt->src) + 1; + iov[6].iov_base = (void *) "errmsg"; + iov[6].iov_len = 7; + iov[7].iov_base = (void *) errmsg; + iov[7].iov_len = sizeof(errmsg); + + if (nxt_slow_path(nmount(iov, 8, 0) < 0)) { + nxt_alert(task, "nmount(%p, 8, 0) %s", errmsg); + return NXT_ERROR; + } + + return NXT_OK; +} + +#endif + + +#if (NXT_HAVE_LINUX_UMOUNT2) + +void +nxt_fs_unmount(const u_char *path) +{ + if (nxt_slow_path(umount2((const char *) path, MNT_DETACH) < 0)) { + nxt_thread_log_error(NXT_LOG_WARN, "umount2(%s, MNT_DETACH) %E", + path, nxt_errno); + } +} + +#elif (NXT_HAVE_UNMOUNT) + +void +nxt_fs_unmount(const u_char *path) +{ + if (nxt_slow_path(unmount((const char *) path, MNT_FORCE) < 0)) { + nxt_thread_log_error(NXT_LOG_WARN, "unmount(%s) %E", path, nxt_errno); + } +} + +#endif + + +nxt_int_t +nxt_fs_mkdir_all(const u_char *dir, mode_t mode) +{ + char *start, *end, *dst; + size_t dirlen; + char path[PATH_MAX]; + + dirlen = nxt_strlen(dir); + + nxt_assert(dirlen < PATH_MAX && dirlen > 1 && dir[0] == '/'); + + dst = path; + start = end = (char *) dir; + + while (*start != '\0') { + if (*start == '/') { + *dst++ = *start++; + } + + end = strchr(start, '/'); + if (end == NULL) { + end = ((char *)dir + dirlen); + } + + dst = nxt_cpymem(dst, start, end - start); + *dst = '\0'; + + if (nxt_slow_path(nxt_fs_mkdir((u_char *) path, mode) != NXT_OK + && nxt_errno != EEXIST)) + { + return NXT_ERROR; + } + + start = end; + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_fs_mkdir(const u_char *dir, mode_t mode) +{ + if (nxt_fast_path(mkdir((const char *) dir, mode) == 0)) { + return NXT_OK; + } + + return NXT_ERROR; +} diff --git a/src/nxt_fs.h b/src/nxt_fs.h new file mode 100644 index 00000000..85c78b27 --- /dev/null +++ b/src/nxt_fs.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_FS_H_INCLUDED_ +#define _NXT_FS_H_INCLUDED_ + + +#ifdef MS_BIND +#define NXT_MS_BIND MS_BIND +#else +#define NXT_MS_BIND 0 +#endif + +#ifdef MS_REC +#define NXT_MS_REC MS_BIND +#else +#define NXT_MS_REC 0 +#endif + + +typedef struct { + u_char *src; + u_char *dst; + u_char *fstype; + nxt_int_t flags; + u_char *data; +} nxt_fs_mount_t; + + +nxt_int_t nxt_fs_mkdir_all(const u_char *dir, mode_t mode); +nxt_int_t nxt_fs_mount(nxt_task_t *task, nxt_fs_mount_t *mnt); +void nxt_fs_unmount(const u_char *path); + + +#endif /* _NXT_FS_H_INCLUDED_ */ diff --git a/src/nxt_http.h b/src/nxt_http.h index 841f5b40..68051e69 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -175,6 +175,7 @@ struct nxt_http_request_s { nxt_http_status_t status:16; uint8_t pass_count; /* 8 bits */ + uint8_t app_target; nxt_http_protocol_t protocol:8; /* 2 bits */ uint8_t logged; /* 1 bit */ uint8_t header_sent; /* 1 bit */ @@ -201,6 +202,7 @@ struct nxt_http_action_s { } u; nxt_str_t name; + nxt_int_t target; }; @@ -277,7 +279,10 @@ nxt_http_routes_t *nxt_http_routes_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *routes_conf); nxt_http_action_t *nxt_http_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_str_t *name); -void nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf); +nxt_int_t nxt_http_routes_resolve(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf); +nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, + nxt_str_t *segments, nxt_uint_t n); nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_str_t *name); void nxt_http_routes_cleanup(nxt_task_t *task, nxt_http_routes_t *routes); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 050587f7..cc1ae17d 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -341,6 +341,8 @@ nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&r->server_name, "localhost"); } + r->app_target = action->target; + nxt_router_process_http_request(task, r, action->u.application); return NULL; diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index ca43c060..a8a6b181 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -39,6 +39,13 @@ typedef enum { } nxt_http_route_pattern_case_t; +typedef enum { + NXT_HTTP_ROUTE_ENCODING_NONE = 0, + NXT_HTTP_ROUTE_ENCODING_URI, + NXT_HTTP_ROUTE_ENCODING_URI_PLUS +} nxt_http_route_encoding_t; + + typedef struct { nxt_conf_value_t *pass; nxt_conf_value_t *ret; @@ -181,29 +188,33 @@ static nxt_int_t nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, nxt_http_action_t *action); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, - nxt_bool_t case_sensitive); + nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); static nxt_http_route_ruleset_t *nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, - nxt_bool_t case_sensitive); + nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); static nxt_http_route_rule_t *nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *rule_cv, nxt_str_t *name, - nxt_bool_t case_sensitive); + nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); static nxt_http_route_addr_rule_t *nxt_http_route_addr_rule_create( nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv); static nxt_http_route_rule_t *nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, - nxt_http_route_pattern_case_t pattern_case); + nxt_http_route_pattern_case_t pattern_case, + nxt_http_route_encoding_t encoding); static int nxt_http_pattern_compare(const void *one, const void *two); static int nxt_http_addr_pattern_compare(const void *one, const void *two); static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, - nxt_http_route_pattern_case_t pattern_case); + nxt_http_route_pattern_case_t pattern_case, + nxt_http_route_encoding_t encoding); +static nxt_int_t nxt_http_route_decode_str(nxt_str_t *str, + nxt_http_route_encoding_t encoding); static u_char *nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, nxt_http_route_pattern_case_t pattern_case); -static void nxt_http_route_resolve(nxt_task_t *task, +static nxt_int_t nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route); -static void nxt_http_action_resolve(nxt_task_t *task, +static nxt_int_t nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_action_t *action); static void nxt_http_route_find(nxt_http_routes_t *routes, nxt_str_t *name, nxt_http_action_t *action); @@ -463,7 +474,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.scheme != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.scheme, 1, - NXT_HTTP_ROUTE_PATTERN_NOCASE); + NXT_HTTP_ROUTE_PATTERN_NOCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); if (rule == NULL) { return NULL; } @@ -475,7 +487,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.host != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.host, 1, - NXT_HTTP_ROUTE_PATTERN_LOWCASE); + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); if (rule == NULL) { return NULL; } @@ -488,7 +501,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.uri != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.uri, 1, - NXT_HTTP_ROUTE_PATTERN_NOCASE); + NXT_HTTP_ROUTE_PATTERN_NOCASE, + NXT_HTTP_ROUTE_ENCODING_URI); if (rule == NULL) { return NULL; } @@ -501,7 +515,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.method != NULL) { rule = nxt_http_route_rule_create(task, mp, mtcf.method, 1, - NXT_HTTP_ROUTE_PATTERN_UPCASE); + NXT_HTTP_ROUTE_PATTERN_UPCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); if (rule == NULL) { return NULL; } @@ -514,7 +529,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.headers != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.headers, - NXT_HTTP_ROUTE_HEADER, 0); + NXT_HTTP_ROUTE_HEADER, 0, + NXT_HTTP_ROUTE_ENCODING_NONE); if (table == NULL) { return NULL; } @@ -525,7 +541,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.arguments != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.arguments, - NXT_HTTP_ROUTE_ARGUMENT, 1); + NXT_HTTP_ROUTE_ARGUMENT, 1, + NXT_HTTP_ROUTE_ENCODING_URI_PLUS); if (table == NULL) { return NULL; } @@ -536,7 +553,8 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (mtcf.cookies != NULL) { table = nxt_http_route_table_create(task, mp, mtcf.cookies, - NXT_HTTP_ROUTE_COOKIE, 1); + NXT_HTTP_ROUTE_COOKIE, 1, + NXT_HTTP_ROUTE_ENCODING_NONE); if (table == NULL) { return NULL; } @@ -699,7 +717,7 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, static nxt_http_route_table_t * nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, - nxt_bool_t case_sensitive) + nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding) { size_t size; uint32_t i, n; @@ -722,8 +740,8 @@ nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, table->object = NXT_HTTP_ROUTE_TABLE; if (!array) { - ruleset = nxt_http_route_ruleset_create(task, mp, table_cv, - object, case_sensitive); + ruleset = nxt_http_route_ruleset_create(task, mp, table_cv, object, + case_sensitive, encoding); if (nxt_slow_path(ruleset == NULL)) { return NULL; } @@ -736,8 +754,8 @@ nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, for (i = 0; i < n; i++) { ruleset_cv = nxt_conf_get_array_element(table_cv, i); - ruleset = nxt_http_route_ruleset_create(task, mp, ruleset_cv, - object, case_sensitive); + ruleset = nxt_http_route_ruleset_create(task, mp, ruleset_cv, object, + case_sensitive, encoding); if (nxt_slow_path(ruleset == NULL)) { return NULL; } @@ -752,7 +770,7 @@ nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, static nxt_http_route_ruleset_t * nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *ruleset_cv, nxt_http_route_object_t object, - nxt_bool_t case_sensitive) + nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding) { size_t size; uint32_t i, n, next; @@ -778,7 +796,7 @@ nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, rule_cv = nxt_conf_next_object_member(ruleset_cv, &name, &next); rule = nxt_http_route_rule_name_create(task, mp, rule_cv, &name, - case_sensitive); + case_sensitive, encoding); if (nxt_slow_path(rule == NULL)) { return NULL; } @@ -793,15 +811,18 @@ nxt_http_route_ruleset_create(nxt_task_t *task, nxt_mp_t *mp, static nxt_http_route_rule_t * nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, - nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive) + nxt_conf_value_t *rule_cv, nxt_str_t *name, nxt_bool_t case_sensitive, + nxt_http_route_encoding_t encoding) { - u_char c, *p; + u_char c, *p, *src, *start, *end, plus; + uint8_t d0, d1; uint32_t hash; nxt_uint_t i; nxt_http_route_rule_t *rule; rule = nxt_http_route_rule_create(task, mp, rule_cv, case_sensitive, - NXT_HTTP_ROUTE_PATTERN_NOCASE); + NXT_HTTP_ROUTE_PATTERN_NOCASE, + encoding); if (nxt_slow_path(rule == NULL)) { return NULL; } @@ -813,18 +834,65 @@ nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, return NULL; } + hash = NXT_HTTP_FIELD_HASH_INIT; rule->u.name.start = p; - hash = NXT_HTTP_FIELD_HASH_INIT; + if (encoding == NXT_HTTP_ROUTE_ENCODING_NONE) { + for (i = 0; i < name->length; i++) { + c = name->start[i]; + *p++ = c; - for (i = 0; i < name->length; i++) { - c = name->start[i]; - *p++ = c; + c = case_sensitive ? c : nxt_lowcase(c); + hash = nxt_http_field_hash_char(hash, c); + } + + goto end; + } + + plus = (encoding == NXT_HTTP_ROUTE_ENCODING_URI_PLUS) ? ' ' : '+'; + + start = name->start; + end = start + name->length; + + for (src = start; src < end; src++) { + c = *src; + + switch (c) { + case '%': + if (nxt_slow_path(end - src <= 2)) { + return NULL; + } + + d0 = nxt_hex2int[src[1]]; + d1 = nxt_hex2int[src[2]]; + src += 2; + + if (nxt_slow_path((d0 | d1) >= 16)) { + return NULL; + } + + c = (d0 << 4) + d1; + *p++ = c; + break; + + case '+': + c = plus; + *p++ = c; + break; + + default: + *p++ = c; + break; + } c = case_sensitive ? c : nxt_lowcase(c); hash = nxt_http_field_hash_char(hash, c); } + rule->u.name.length = p - rule->u.name.start; + +end: + rule->u.name.hash = nxt_http_field_hash_end(hash) & 0xFFFF; return rule; @@ -834,7 +902,8 @@ nxt_http_route_rule_name_create(nxt_task_t *task, nxt_mp_t *mp, static nxt_http_route_rule_t * nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_bool_t case_sensitive, - nxt_http_route_pattern_case_t pattern_case) + nxt_http_route_pattern_case_t pattern_case, + nxt_http_route_encoding_t encoding) { size_t size; uint32_t i, n; @@ -860,7 +929,7 @@ nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, if (string) { pattern[0].case_sensitive = case_sensitive; ret = nxt_http_route_pattern_create(task, mp, cv, &pattern[0], - pattern_case); + pattern_case, encoding); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -875,7 +944,7 @@ nxt_http_route_rule_create(nxt_task_t *task, nxt_mp_t *mp, value = nxt_conf_get_array_element(cv, i); ret = nxt_http_route_pattern_create(task, mp, value, &pattern[i], - pattern_case); + pattern_case, encoding); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -972,22 +1041,22 @@ nxt_http_addr_pattern_compare(const void *one, const void *two) static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *cv, nxt_http_route_pattern_t *pattern, - nxt_http_route_pattern_case_t pattern_case) + nxt_http_route_pattern_case_t pattern_case, + nxt_http_route_encoding_t encoding) { u_char *start; - nxt_str_t test; + nxt_str_t test, test2; + nxt_int_t ret; nxt_uint_t n, length; nxt_http_route_pattern_type_t type; - /* Suppress warning about uninitialized variable. */ - length = 0; - type = NXT_HTTP_ROUTE_PATTERN_EXACT; nxt_conf_get_string(cv, &test); pattern->negative = 0; pattern->any = 1; + pattern->min_length = 0; if (test.length != 0) { @@ -1000,7 +1069,6 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, } if (test.length != 0) { - if (test.start[0] == '*') { test.start++; test.length--; @@ -1026,18 +1094,45 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, length = test.length - 1; for (n = 1; n < length; n++) { - if (test.start[n] == '*') { - test.length = n; - type = NXT_HTTP_ROUTE_PATTERN_MIDDLE; - break; + if (test.start[n] != '*') { + continue; + } + + test.length = n; + + test2.start = &test.start[n + 1]; + test2.length = length - n; + + ret = nxt_http_route_decode_str(&test2, encoding); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; } + + type = NXT_HTTP_ROUTE_PATTERN_MIDDLE; + + pattern->length2 = test2.length; + pattern->min_length += test2.length; + + start = nxt_http_route_pattern_copy(mp, &test2, + pattern_case); + if (nxt_slow_path(start == NULL)) { + return NXT_ERROR; + } + + pattern->start2 = start; + break; } } + + ret = nxt_http_route_decode_str(&test, encoding); + if (nxt_slow_path(ret != NXT_OK)) { + return ret; + } } } pattern->type = type; - pattern->min_length = test.length; + pattern->min_length += test.length; pattern->length1 = test.length; start = nxt_http_route_pattern_copy(mp, &test, pattern_case); @@ -1047,20 +1142,43 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, pattern->start1 = start; - if (type == NXT_HTTP_ROUTE_PATTERN_MIDDLE) { - length -= test.length; - pattern->length2 = length; - pattern->min_length += length; + return NXT_OK; +} - test.start = &test.start[test.length + 1]; - test.length = length; - start = nxt_http_route_pattern_copy(mp, &test, pattern_case); - if (nxt_slow_path(start == NULL)) { +static nxt_int_t +nxt_http_route_decode_str(nxt_str_t *str, nxt_http_route_encoding_t encoding) +{ + u_char *start, *end; + + switch (encoding) { + case NXT_HTTP_ROUTE_ENCODING_NONE: + break; + + case NXT_HTTP_ROUTE_ENCODING_URI: + start = str->start; + + end = nxt_decode_uri(start, start, str->length); + if (nxt_slow_path(end == NULL)) { return NXT_ERROR; } - pattern->start2 = start; + str->length = end - start; + break; + + case NXT_HTTP_ROUTE_ENCODING_URI_PLUS: + start = str->start; + + end = nxt_decode_uri_plus(start, start, str->length); + if (nxt_slow_path(end == NULL)) { + return NXT_ERROR; + } + + str->length = end - start; + break; + + default: + nxt_unreachable(); } return NXT_OK; @@ -1097,9 +1215,10 @@ nxt_http_route_pattern_copy(nxt_mp_t *mp, nxt_str_t *test, } -void +nxt_int_t nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) { + nxt_int_t ret; nxt_http_route_t **route, **end; nxt_http_routes_t *routes; @@ -1110,75 +1229,141 @@ nxt_http_routes_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) end = route + routes->items; while (route < end) { - nxt_http_route_resolve(task, tmcf, *route); + ret = nxt_http_route_resolve(task, tmcf, *route); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } route++; } } + + return NXT_OK; } -static void +static nxt_int_t nxt_http_route_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_route_t *route) { + nxt_int_t ret; nxt_http_route_match_t **match, **end; match = &route->match[0]; end = match + route->items; while (match < end) { - nxt_http_action_resolve(task, tmcf, &(*match)->action); + ret = nxt_http_action_resolve(task, tmcf, &(*match)->action); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } match++; } + + return NXT_OK; } -static void +static nxt_int_t nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_action_t *action) { - nxt_str_t name; + nxt_str_t *targets; + nxt_int_t ret; + nxt_uint_t i; + nxt_str_t segments[3]; if (action->handler != NULL) { if (action->handler == nxt_http_static_handler && action->u.fallback != NULL) { - nxt_http_action_resolve(task, tmcf, action->u.fallback); + return nxt_http_action_resolve(task, tmcf, action->u.fallback); } - return; + return NXT_OK; } - name = action->name; - - if (nxt_str_start(&name, "applications/", 13)) { - name.length -= 13; - name.start += 13; + ret = nxt_http_pass_segments(tmcf->mem_pool, &action->name, segments, 3); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } - nxt_router_listener_application(tmcf, &name, action); + if (nxt_str_eq(&segments[0], "applications", 12)) { + nxt_router_listener_application(tmcf, &segments[1], action); nxt_router_app_use(task, action->u.application, 1); - } else if (nxt_str_start(&name, "upstreams/", 10)) { - name.length -= 10; - name.start += 10; - - nxt_upstream_find(tmcf->router_conf->upstreams, &name, action); + if (segments[2].length != 0) { + targets = action->u.application->targets; - } else if (nxt_str_start(&name, "routes", 6)) { + for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++); - if (name.length == 6) { - name.length = 0; - name.start = NULL; + action->target = i; - } else if (name.start[6] == '/') { - name.length -= 7; - name.start += 7; + } else { + action->target = 0; } - nxt_http_route_find(tmcf->router_conf->routes, &name, action); + } else if (nxt_str_eq(&segments[0], "upstreams", 9)) { + nxt_upstream_find(tmcf->router_conf->upstreams, &segments[1], action); + + } else if (nxt_str_eq(&segments[0], "routes", 6)) { + nxt_http_route_find(tmcf->router_conf->routes, &segments[1], action); } + + return NXT_OK; +} + + +nxt_int_t +nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, nxt_str_t *segments, + nxt_uint_t n) +{ + u_char *p; + nxt_str_t rest; + + if (nxt_slow_path(nxt_str_dup(mp, &rest, pass) == NULL)) { + return NXT_ERROR; + } + + nxt_memzero(segments, n * sizeof(nxt_str_t)); + + do { + p = nxt_memchr(rest.start, '/', rest.length); + + if (p != NULL) { + n--; + + if (n == 0) { + return NXT_DECLINED; + } + + segments->length = p - rest.start; + segments->start = rest.start; + + rest.length -= segments->length + 1; + rest.start = p + 1; + + } else { + n = 0; + *segments = rest; + } + + if (segments->length == 0) { + return NXT_DECLINED; + } + + p = nxt_decode_uri(segments->start, segments->start, segments->length); + if (p == NULL) { + return NXT_DECLINED; + } + + segments->length = p - segments->start; + segments++; + + } while (n); + + return NXT_OK; } @@ -1244,6 +1429,8 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_listener_application(tmcf, name, action); nxt_router_app_use(task, action->u.application, 1); + action->target = 0; + return action; } @@ -1677,7 +1864,8 @@ static nxt_array_t * nxt_http_route_arguments_parse(nxt_http_request_t *r) { size_t name_length; - u_char c, *p, *start, *end, *name; + u_char c, *p, *dst, *dst_start, *start, *end, *name; + uint8_t d0, d1; uint32_t hash; nxt_bool_t valid; nxt_array_t *args; @@ -1697,39 +1885,81 @@ nxt_http_route_arguments_parse(nxt_http_request_t *r) name = NULL; name_length = 0; + dst_start = nxt_mp_nget(r->mem_pool, r->args->length); + if (nxt_slow_path(dst_start == NULL)) { + return NULL; + } + start = r->args->start; end = start + r->args->length; - for (p = start; p < end; p++) { + for (p = start, dst = dst_start; p < end; p++, dst++) { c = *p; + *dst = c; - if (c == '=') { - name_length = p - start; - name = start; - start = p + 1; + switch (c) { + case '=': + if (name != NULL) { + break; + } + + name_length = dst - dst_start; valid = (name_length != 0); + name = dst_start; + dst_start = dst + 1; - } else if (c == '&') { + continue; + + case '&': if (valid) { nv = nxt_http_route_argument(args, name, name_length, hash, - start, p); + dst_start, dst); if (nxt_slow_path(nv == NULL)) { return NULL; } } hash = NXT_HTTP_FIELD_HASH_INIT; + name_length = 0; valid = 1; name = NULL; - start = p + 1; + dst_start = dst + 1; + + continue; + + case '+': + c = ' '; + *dst = ' '; + + break; + + case '%': + if (nxt_slow_path(end - p <= 2)) { + break; + } + + d0 = nxt_hex2int[p[1]]; + d1 = nxt_hex2int[p[2]]; + + if (nxt_slow_path((d0 | d1) >= 16)) { + break; + } + + p += 2; + c = (d0 << 4) + d1; + *dst = c; + + break; + } - } else if (name == NULL) { + if (name == NULL) { hash = nxt_http_field_hash_char(hash, c); } } if (valid) { - nv = nxt_http_route_argument(args, name, name_length, hash, start, p); + nv = nxt_http_route_argument(args, name, name_length, hash, dst_start, + dst); if (nxt_slow_path(nv == NULL)) { return NULL; } diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index 46ae57a7..ee18be1b 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -76,7 +76,7 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&extension, ".html"); } else { - nxt_str_null(&index); + nxt_str_set(&index, ""); nxt_str_null(&extension); } diff --git a/src/nxt_java.c b/src/nxt_java.c index 004907d6..c7471509 100644 --- a/src/nxt_java.c +++ b/src/nxt_java.c @@ -26,10 +26,12 @@ #include "java/nxt_jni_URLClassLoader.h" #include "nxt_jars.h" +#include "nxt_java_mounts.h" -static nxt_int_t nxt_java_pre_init(nxt_task_t *task, +static nxt_int_t nxt_java_setup(nxt_task_t *task, nxt_process_t *process, nxt_common_app_conf_t *conf); -static nxt_int_t nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static nxt_int_t nxt_java_start(nxt_task_t *task, + nxt_process_data_t *data); static void nxt_java_request_handler(nxt_unit_request_info_t *req); static void nxt_java_websocket_handler(nxt_unit_websocket_frame_t *ws); static void nxt_java_close_handler(nxt_unit_request_info_t *req); @@ -49,8 +51,10 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("java"), NXT_STRING(NXT_JAVA_VERSION), - nxt_java_pre_init, - nxt_java_init, + nxt_java_mounts, + nxt_nitems(nxt_java_mounts), + nxt_java_setup, + nxt_java_start, }; typedef struct { @@ -60,22 +64,69 @@ typedef struct { static nxt_int_t -nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_java_setup(nxt_task_t *task, nxt_process_t *process, + nxt_common_app_conf_t *conf) { + char *path, *relpath, *p, *rootfs; + size_t jars_dir_len, rootfs_len; const char *unit_jars; + rootfs = (char *) process->isolation.rootfs; + rootfs_len = 0; + unit_jars = conf->u.java.unit_jars; if (unit_jars == NULL) { - unit_jars = NXT_JARS; + if (rootfs != NULL) { + unit_jars = "/"; + } else { + unit_jars = NXT_JARS; + } } - nxt_java_modules = realpath(unit_jars, NULL); - if (nxt_java_modules == NULL) { - nxt_alert(task, "realpath(%s) failed: %E", unit_jars, nxt_errno); + relpath = strdup(unit_jars); + if (nxt_slow_path(relpath == NULL)) { return NXT_ERROR; } + if (rootfs != NULL) { + jars_dir_len = strlen(unit_jars); + rootfs_len = strlen(rootfs); + + path = nxt_malloc(jars_dir_len + rootfs_len + 1); + if (nxt_slow_path(path == NULL)) { + free(relpath); + return NXT_ERROR; + } + + p = nxt_cpymem(path, process->isolation.rootfs, rootfs_len); + p = nxt_cpymem(p, relpath, jars_dir_len); + *p = '\0'; + + free(relpath); + + } else { + path = relpath; + } + + nxt_java_modules = realpath(path, NULL); + if (nxt_java_modules == NULL) { + nxt_alert(task, "realpath(\"%s\") failed %E", path, nxt_errno); + goto free; + } + + if (rootfs != NULL && strlen(path) > rootfs_len) { + nxt_java_modules = path + rootfs_len; + } + + nxt_debug(task, "JAVA MODULES: %s", nxt_java_modules); + return NXT_OK; + +free: + + nxt_free(path); + + return NXT_ERROR; } @@ -83,6 +134,7 @@ static char ** nxt_java_module_jars(const char *jars[], int jar_count) { char **res, *jurl; + uint8_t pathsep; nxt_int_t modules_len, jlen, i; const char **jar; @@ -93,9 +145,13 @@ nxt_java_module_jars(const char *jars[], int jar_count) modules_len = nxt_strlen(nxt_java_modules); + pathsep = nxt_java_modules[modules_len - 1] == '/'; + for (i = 0, jar = jars; *jar != NULL; jar++) { - jlen = nxt_length("file:") + modules_len + nxt_length("/") - + nxt_strlen(*jar) + 1; + jlen = nxt_length("file:") + modules_len + + (!pathsep ? nxt_length("/") : 0) + + nxt_strlen(*jar) + 1; + jurl = nxt_malloc(jlen); if (jurl == NULL) { return NULL; @@ -105,7 +161,11 @@ nxt_java_module_jars(const char *jars[], int jar_count) jurl = nxt_cpymem(jurl, "file:", nxt_length("file:")); jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len); - *jurl++ = '/'; + + if (!pathsep) { + *jurl++ = '/'; + } + jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar)); *jurl++ = '\0'; } @@ -115,24 +175,26 @@ nxt_java_module_jars(const char *jars[], int jar_count) static nxt_int_t -nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_java_start(nxt_task_t *task, nxt_process_data_t *data) { - jint rc; - char *opt, *real_path; - char **classpath_arr, **unit_jars, **system_jars; - JavaVM *jvm; - JNIEnv *env; - jobject cl, classpath; - nxt_str_t str; - nxt_int_t opt_len, real_path_len; - nxt_uint_t i, unit_jars_count, classpath_count, system_jars_count; - JavaVMOption *jvm_opt; - JavaVMInitArgs jvm_args; - nxt_unit_ctx_t *ctx; - nxt_unit_init_t java_init; - nxt_java_data_t data; - nxt_conf_value_t *value; - nxt_java_app_conf_t *c; + jint rc; + char *opt, *real_path; + char **classpath_arr, **unit_jars, **system_jars; + JavaVM *jvm; + JNIEnv *env; + jobject cl, classpath; + nxt_str_t str; + nxt_int_t opt_len, real_path_len; + nxt_uint_t i, unit_jars_count, classpath_count; + nxt_uint_t system_jars_count; + JavaVMOption *jvm_opt; + JavaVMInitArgs jvm_args; + nxt_unit_ctx_t *ctx; + nxt_unit_init_t java_init; + nxt_java_data_t java_data; + nxt_conf_value_t *value; + nxt_java_app_conf_t *c; + nxt_common_app_conf_t *app_conf; //setenv("ASAN_OPTIONS", "handle_segv=0", 1); @@ -140,7 +202,8 @@ nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) jvm_args.nOptions = 0; jvm_args.ignoreUnrecognized = 0; - c = &conf->u.java; + app_conf = data->app; + c = &app_conf->u.java; if (c->options != NULL) { jvm_args.nOptions += nxt_conf_array_elements_count(c->options); @@ -338,8 +401,8 @@ nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) goto env_failed; } - data.env = env; - data.ctx = nxt_java_startContext(env, c->webapp, classpath); + java_data.env = env; + java_data.ctx = nxt_java_startContext(env, c->webapp, classpath); if ((*env)->ExceptionCheck(env)) { nxt_alert(task, "Unhandled exception in application start"); @@ -353,8 +416,8 @@ nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) java_init.callbacks.websocket_handler = nxt_java_websocket_handler; java_init.callbacks.close_handler = nxt_java_close_handler; java_init.request_data_size = sizeof(nxt_java_request_data_t); - java_init.data = &data; - java_init.shm_limit = conf->shm_limit; + java_init.data = &java_data; + java_init.shm_limit = app_conf->shm_limit; ctx = nxt_unit_init(&java_init); if (nxt_slow_path(ctx == NULL)) { @@ -367,7 +430,7 @@ nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) /* TODO report error */ } - nxt_java_stopContext(env, data.ctx); + nxt_java_stopContext(env, java_data.ctx); if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); diff --git a/src/nxt_main.h b/src/nxt_main.h index b310c4fa..5914fbd1 100644 --- a/src/nxt_main.h +++ b/src/nxt_main.h @@ -59,6 +59,7 @@ typedef uint16_t nxt_port_id_t; #include <nxt_process_type.h> #include <nxt_capability.h> #include <nxt_credential.h> +#include <nxt_fs.h> #include <nxt_process.h> #include <nxt_utf8.h> #include <nxt_file_name.h> diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index eed37752..a16e44d3 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -14,6 +14,8 @@ #include <nxt_cert.h> #endif +#include <sys/mount.h> + typedef struct { nxt_socket_t socket; @@ -36,20 +38,11 @@ extern nxt_port_handlers_t nxt_router_process_port_handlers; static nxt_int_t nxt_main_process_port_create(nxt_task_t *task, nxt_runtime_t *rt); static void nxt_main_process_title(nxt_task_t *task); -static nxt_int_t nxt_main_start_controller_process(nxt_task_t *task, - nxt_runtime_t *rt); -static nxt_int_t nxt_main_create_controller_process(nxt_task_t *task, - nxt_runtime_t *rt, nxt_process_init_t *init); -static nxt_int_t nxt_main_create_router_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init); -static nxt_int_t nxt_main_start_router_process(nxt_task_t *task, - nxt_runtime_t *rt); -static nxt_int_t nxt_main_start_discovery_process(nxt_task_t *task, - nxt_runtime_t *rt); -static nxt_int_t nxt_main_start_worker_process(nxt_task_t *task, - nxt_runtime_t *rt, nxt_common_app_conf_t *app_conf, uint32_t stream); -static nxt_int_t nxt_main_create_worker_process(nxt_task_t *task, - nxt_runtime_t *rt, nxt_process_init_t *init); +static nxt_int_t nxt_main_process_create(nxt_task_t *task, + const nxt_process_init_t init); +static nxt_int_t nxt_main_start_process(nxt_task_t *task, + nxt_process_t *process); +static nxt_process_t *nxt_main_process_new(nxt_task_t *task, nxt_runtime_t *rt); static void nxt_main_process_sigterm_handler(nxt_task_t *task, void *obj, void *data); static void nxt_main_process_sigquit_handler(nxt_task_t *task, void *obj, @@ -60,8 +53,7 @@ static void nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data); static void nxt_main_process_signal_handler(nxt_task_t *task, void *obj, void *data); -static void nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid); -static void nxt_main_stop_worker_processes(nxt_task_t *task, nxt_runtime_t *rt); +static void nxt_main_cleanup_process(nxt_task_t *task, nxt_pid_t pid); static void nxt_main_port_socket_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); static nxt_int_t nxt_main_listening_socket(nxt_sockaddr_t *sa, @@ -73,29 +65,6 @@ static void nxt_main_port_conf_store_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); static void nxt_main_port_access_log_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); -static nxt_process_init_t *nxt_process_init_create(nxt_task_t *task, - nxt_process_type_t type, const nxt_str_t *name); -static nxt_int_t nxt_process_init_name_set(nxt_process_init_t *init, - nxt_process_type_t type, const nxt_str_t *name); -static nxt_int_t nxt_process_init_creds_set(nxt_task_t *task, - nxt_process_init_t *init, nxt_str_t *user, nxt_str_t *group); - -static nxt_int_t nxt_init_isolation(nxt_task_t *task, - nxt_conf_value_t *isolation, nxt_process_init_t *init); -#if (NXT_HAVE_CLONE) -static nxt_int_t nxt_init_clone_flags(nxt_task_t *task, - nxt_conf_value_t *namespaces, nxt_process_init_t *init); -#endif - -#if (NXT_HAVE_CLONE_NEWUSER) -static nxt_int_t nxt_init_isolation_creds(nxt_task_t *task, - nxt_conf_value_t *isolation, nxt_process_init_t *init); -static nxt_int_t nxt_init_vldt_isolation_creds(nxt_task_t *task, - nxt_process_init_t *init); -static nxt_int_t nxt_init_isolation_credential_map(nxt_task_t *task, - nxt_mp_t *mem_pool, nxt_conf_value_t *map_array, - nxt_clone_credential_map_t *map); -#endif const nxt_sig_event_t nxt_main_process_signals[] = { nxt_event_signal(SIGHUP, nxt_main_process_signal_handler), @@ -108,54 +77,6 @@ const nxt_sig_event_t nxt_main_process_signals[] = { }; -static const nxt_port_handlers_t nxt_app_process_port_handlers = { - .new_port = nxt_port_new_port_handler, - .change_file = nxt_port_change_log_file_handler, - .mmap = nxt_port_mmap_handler, - .remove_pid = nxt_port_remove_pid_handler, -}; - - -static const nxt_port_handlers_t nxt_discovery_process_port_handlers = { - .quit = nxt_worker_process_quit_handler, - .new_port = nxt_port_new_port_handler, - .change_file = nxt_port_change_log_file_handler, - .mmap = nxt_port_mmap_handler, - .data = nxt_port_data_handler, - .remove_pid = nxt_port_remove_pid_handler, - .rpc_ready = nxt_port_rpc_handler, - .rpc_error = nxt_port_rpc_handler, -}; - - -static const nxt_port_handlers_t *nxt_process_port_handlers[NXT_PROCESS_MAX] = -{ - NULL, - &nxt_discovery_process_port_handlers, - &nxt_controller_process_port_handlers, - &nxt_router_process_port_handlers, - &nxt_app_process_port_handlers -}; - - -static const nxt_process_start_t nxt_process_starts[NXT_PROCESS_MAX] = { - NULL, - nxt_discovery_start, - nxt_controller_start, - nxt_router_start, - nxt_app_start -}; - - -static const nxt_process_restart_t nxt_process_restarts[NXT_PROCESS_MAX] = { - NULL, - NULL, - &nxt_main_create_controller_process, - &nxt_main_create_router_process, - NULL -}; - - static nxt_bool_t nxt_exiting; @@ -172,11 +93,11 @@ nxt_main_process_start(nxt_thread_t *thr, nxt_task_t *task, nxt_main_process_title(task); /* - * The dicsovery process will send a message processed by + * The discovery process will send a message processed by * nxt_main_port_modules_handler() which starts the controller * and router processes. */ - return nxt_main_start_discovery_process(task, rt); + return nxt_main_process_create(task, nxt_discovery_process); } @@ -275,21 +196,9 @@ static nxt_conf_map_t nxt_python_app_conf[] = { static nxt_conf_map_t nxt_php_app_conf[] = { { - nxt_string("root"), - NXT_CONF_MAP_CSTRZ, - offsetof(nxt_common_app_conf_t, u.php.root), - }, - - { - nxt_string("script"), - NXT_CONF_MAP_STR, - offsetof(nxt_common_app_conf_t, u.php.script), - }, - - { - nxt_string("index"), - NXT_CONF_MAP_STR, - offsetof(nxt_common_app_conf_t, u.php.index), + nxt_string("targets"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.php.targets), }, { @@ -362,48 +271,71 @@ nxt_port_main_data_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) static void -nxt_port_main_start_worker_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +nxt_port_main_start_process_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) { - u_char *start, ch; + u_char *start, *p, ch; size_t type_len; - nxt_mp_t *mp; nxt_int_t ret; nxt_buf_t *b; nxt_port_t *port; nxt_runtime_t *rt; + nxt_process_t *process; nxt_app_type_t idx; nxt_conf_value_t *conf; - nxt_common_app_conf_t app_conf; + nxt_process_init_t *init; + nxt_common_app_conf_t *app_conf; ret = NXT_ERROR; - mp = nxt_mp_create(1024, 128, 256, 32); + rt = task->thread->runtime; - if (nxt_slow_path(mp == NULL)) { + process = nxt_main_process_new(task, rt); + if (nxt_slow_path(process == NULL)) { return; } - b = nxt_buf_chk_make_plain(mp, msg->buf, msg->size); + init = nxt_process_init(process); + + *init = nxt_app_process; + b = nxt_buf_chk_make_plain(process->mem_pool, msg->buf, msg->size); if (b == NULL) { - return; + goto failed; } - nxt_debug(task, "main start worker: %*s", b->mem.free - b->mem.pos, + nxt_debug(task, "main start process: %*s", b->mem.free - b->mem.pos, b->mem.pos); - nxt_memzero(&app_conf, sizeof(nxt_common_app_conf_t)); + app_conf = nxt_mp_zalloc(process->mem_pool, sizeof(nxt_common_app_conf_t)); + if (nxt_slow_path(app_conf == NULL)) { + goto failed; + } start = b->mem.pos; - app_conf.name.start = start; - app_conf.name.length = nxt_strlen(start); - app_conf.shm_limit = 100 * 1024 * 1024; + app_conf->name.start = start; + app_conf->name.length = nxt_strlen(start); + + init->name = (const char *) start; + + process->name = nxt_mp_alloc(process->mem_pool, app_conf->name.length + + sizeof("\"\" application") + 1); - start += app_conf.name.length + 1; + if (nxt_slow_path(process->name == NULL)) { + goto failed; + } + + p = (u_char *) process->name; + *p++ = '"'; + p = nxt_cpymem(p, init->name, app_conf->name.length); + p = nxt_cpymem(p, "\" application", 13); + *p = '\0'; - conf = nxt_conf_json_parse(mp, start, b->mem.free, NULL); + app_conf->shm_limit = 100 * 1024 * 1024; + start += app_conf->name.length + 1; + + conf = nxt_conf_json_parse(process->mem_pool, start, b->mem.free, NULL); if (conf == NULL) { nxt_alert(task, "router app configuration parsing error"); @@ -412,44 +344,45 @@ nxt_port_main_start_worker_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) rt = task->thread->runtime; - app_conf.user.start = (u_char*)rt->user_cred.user; - app_conf.user.length = nxt_strlen(rt->user_cred.user); + app_conf->user.start = (u_char*)rt->user_cred.user; + app_conf->user.length = nxt_strlen(rt->user_cred.user); + + ret = nxt_conf_map_object(process->mem_pool, conf, nxt_common_app_conf, + nxt_nitems(nxt_common_app_conf), app_conf); - ret = nxt_conf_map_object(mp, conf, nxt_common_app_conf, - nxt_nitems(nxt_common_app_conf), &app_conf); if (ret != NXT_OK) { nxt_alert(task, "failed to map common app conf received from router"); goto failed; } - for (type_len = 0; type_len != app_conf.type.length; type_len++) { - ch = app_conf.type.start[type_len]; + for (type_len = 0; type_len != app_conf->type.length; type_len++) { + ch = app_conf->type.start[type_len]; if (ch == ' ' || nxt_isdigit(ch)) { break; } } - idx = nxt_app_parse_type(app_conf.type.start, type_len); + idx = nxt_app_parse_type(app_conf->type.start, type_len); if (nxt_slow_path(idx >= nxt_nitems(nxt_app_maps))) { nxt_alert(task, "invalid app type %d received from router", (int) idx); goto failed; } - ret = nxt_conf_map_object(mp, conf, nxt_app_maps[idx].map, - nxt_app_maps[idx].size, &app_conf); + ret = nxt_conf_map_object(process->mem_pool, conf, nxt_app_maps[idx].map, + nxt_app_maps[idx].size, app_conf); if (nxt_slow_path(ret != NXT_OK)) { nxt_alert(task, "failed to map app conf received from router"); goto failed; } - if (app_conf.limits != NULL) { - ret = nxt_conf_map_object(mp, app_conf.limits, + if (app_conf->limits != NULL) { + ret = nxt_conf_map_object(process->mem_pool, app_conf->limits, nxt_common_app_limits_conf, nxt_nitems(nxt_common_app_limits_conf), - &app_conf); + app_conf); if (nxt_slow_path(ret != NXT_OK)) { nxt_alert(task, "failed to map app limits received from router"); @@ -457,38 +390,91 @@ nxt_port_main_start_worker_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) } } - ret = nxt_main_start_worker_process(task, task->thread->runtime, - &app_conf, msg->port_msg.stream); + app_conf->self = conf; + + process->stream = msg->port_msg.stream; + process->data.app = app_conf; + + ret = nxt_main_start_process(task, process); + if (nxt_fast_path(ret == NXT_OK || ret == NXT_AGAIN)) { + return; + } failed: - if (ret == NXT_ERROR) { - port = nxt_runtime_port_find(task->thread->runtime, msg->port_msg.pid, - msg->port_msg.reply_port); - if (nxt_fast_path(port != NULL)) { - nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_ERROR, - -1, msg->port_msg.stream, 0, NULL); - } + nxt_process_use(task, process, -1); + + port = nxt_runtime_port_find(rt, msg->port_msg.pid, + msg->port_msg.reply_port); + + if (nxt_fast_path(port != NULL)) { + nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_ERROR, + -1, msg->port_msg.stream, 0, NULL); } +} - nxt_mp_destroy(mp); + +static void +nxt_main_process_created_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + nxt_port_t *port; + nxt_process_t *process; + nxt_runtime_t *rt; + + rt = task->thread->runtime; + + process = nxt_runtime_process_find(rt, msg->port_msg.pid); + if (nxt_slow_path(process == NULL)) { + return; + } + + nxt_assert(process->state == NXT_PROCESS_STATE_CREATING); + + port = nxt_runtime_port_find(rt, msg->port_msg.pid, + msg->port_msg.reply_port); + + + if (nxt_slow_path(port == NULL)) { + return; + } + +#if (NXT_HAVE_CLONE && NXT_HAVE_CLONE_NEWUSER) + if (nxt_is_clone_flag_set(process->isolation.clone.flags, NEWUSER)) { + if (nxt_slow_path(nxt_clone_credential_map(task, process->pid, + process->user_cred, + &process->isolation.clone) + != NXT_OK)) + { + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_ERROR, + -1, msg->port_msg.stream, 0, NULL); + return; + } + } + +#endif + + process->state = NXT_PROCESS_STATE_CREATED; + + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_RPC_READY_LAST, + -1, msg->port_msg.stream, 0, NULL); } static nxt_port_handlers_t nxt_main_process_port_handlers = { - .data = nxt_port_main_data_handler, - .process_ready = nxt_port_process_ready_handler, - .start_worker = nxt_port_main_start_worker_handler, - .socket = nxt_main_port_socket_handler, - .modules = nxt_main_port_modules_handler, - .conf_store = nxt_main_port_conf_store_handler, + .data = nxt_port_main_data_handler, + .process_created = nxt_main_process_created_handler, + .process_ready = nxt_port_process_ready_handler, + .start_process = nxt_port_main_start_process_handler, + .socket = nxt_main_port_socket_handler, + .modules = nxt_main_port_modules_handler, + .conf_store = nxt_main_port_conf_store_handler, #if (NXT_TLS) - .cert_get = nxt_cert_store_get_handler, - .cert_delete = nxt_cert_store_delete_handler, + .cert_get = nxt_cert_store_get_handler, + .cert_delete = nxt_cert_store_delete_handler, #endif - .access_log = nxt_main_port_access_log_handler, - .rpc_ready = nxt_port_rpc_handler, - .rpc_error = nxt_port_rpc_handler, + .access_log = nxt_main_port_access_log_handler, + .rpc_ready = nxt_port_rpc_handler, + .rpc_error = nxt_port_rpc_handler, }; @@ -509,16 +495,17 @@ nxt_main_process_port_create(nxt_task_t *task, nxt_runtime_t *rt) ret = nxt_port_socket_init(task, port, 0); if (nxt_slow_path(ret != NXT_OK)) { + nxt_port_use(task, port, -1); return ret; } /* * A main process port. A write port is not closed - * since it should be inherited by worker processes. + * since it should be inherited by processes. */ nxt_port_enable(task, port, &nxt_main_process_port_handlers); - process->ready = 1; + process->state = NXT_PROCESS_STATE_READY; return NXT_OK; } @@ -551,234 +538,68 @@ nxt_main_process_title(nxt_task_t *task) static nxt_int_t -nxt_main_start_controller_process(nxt_task_t *task, nxt_runtime_t *rt) +nxt_main_process_create(nxt_task_t *task, const nxt_process_init_t init) { - nxt_process_init_t *init; + nxt_int_t ret; + nxt_runtime_t *rt; + nxt_process_t *process; + nxt_process_init_t *pinit; - static const nxt_str_t name = nxt_string("controller"); + rt = task->thread->runtime; - init = nxt_process_init_create(task, NXT_PROCESS_CONTROLLER, &name); - if (nxt_slow_path(init == NULL)) { + process = nxt_main_process_new(task, rt); + if (nxt_slow_path(process == NULL)) { return NXT_ERROR; } - return nxt_main_create_controller_process(task, rt, init);; -} - - -static nxt_int_t -nxt_main_create_controller_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init) -{ - ssize_t n; - nxt_int_t ret; - nxt_str_t *conf; - nxt_file_t file; - nxt_file_info_t fi; - nxt_controller_init_t ctrl_init; - - nxt_memzero(&ctrl_init, sizeof(nxt_controller_init_t)); - - conf = &ctrl_init.conf; - - nxt_memzero(&file, sizeof(nxt_file_t)); - - file.name = (nxt_file_name_t *) rt->conf; - - ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); - - if (ret == NXT_OK) { - ret = nxt_file_info(&file, &fi); - - if (nxt_fast_path(ret == NXT_OK && nxt_is_file(&fi))) { - conf->length = nxt_file_size(&fi); - conf->start = nxt_malloc(conf->length); - - if (nxt_slow_path(conf->start == NULL)) { - nxt_file_close(task, &file); - return NXT_ERROR; - } - - n = nxt_file_read(&file, conf->start, conf->length, 0); - - if (nxt_slow_path(n != (ssize_t) conf->length)) { - nxt_free(conf->start); - conf->start = NULL; - - nxt_alert(task, "failed to restore previous configuration: " - "cannot read the file"); - } - } - - nxt_file_close(task, &file); - } - -#if (NXT_TLS) - ctrl_init.certs = nxt_cert_store_load(task); -#endif - - init->data = &ctrl_init; + process->name = init.name; + process->user_cred = &rt->user_cred; - ret = nxt_main_create_worker_process(task, rt, init); - - if (ret == NXT_OK) { - if (conf->start != NULL) { - nxt_free(conf->start); - } + pinit = nxt_process_init(process); + *pinit = init; -#if (NXT_TLS) - if (ctrl_init.certs != NULL) { - nxt_cert_store_release(ctrl_init.certs); - } -#endif + ret = nxt_main_start_process(task, process); + if (nxt_slow_path(ret == NXT_ERROR)) { + nxt_process_use(task, process, -1); } return ret; } -static nxt_int_t -nxt_main_start_discovery_process(nxt_task_t *task, nxt_runtime_t *rt) +static nxt_process_t * +nxt_main_process_new(nxt_task_t *task, nxt_runtime_t *rt) { - nxt_process_init_t *init; - - static const nxt_str_t name = nxt_string("discovery"); + nxt_process_t *process; - init = nxt_process_init_create(task, NXT_PROCESS_DISCOVERY, &name); - if (nxt_slow_path(init == NULL)) { - return NXT_ERROR; + process = nxt_runtime_process_new(rt); + if (nxt_slow_path(process == NULL)) { + return NULL; } - return nxt_main_create_worker_process(task, rt, init); -} - - -static nxt_int_t -nxt_main_start_router_process(nxt_task_t *task, nxt_runtime_t *rt) -{ - nxt_process_init_t *init; - - static const nxt_str_t name = nxt_string("router"); - - init = nxt_process_init_create(task, NXT_PROCESS_ROUTER, &name); - if (nxt_slow_path(init == NULL)) { - return NXT_ERROR; + process->mem_pool = nxt_mp_create(1024, 128, 256, 32); + if (process->mem_pool == NULL) { + nxt_process_use(task, process, -1); + return NULL; } - return nxt_main_create_router_process(task, rt, init); -} - - -static nxt_int_t -nxt_main_create_router_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init) -{ - nxt_main_stop_worker_processes(task, rt); - - return nxt_main_create_worker_process(task, rt, init); + return process; } static nxt_int_t -nxt_main_start_worker_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_common_app_conf_t *app_conf, uint32_t stream) +nxt_main_start_process(nxt_task_t *task, nxt_process_t *process) { - nxt_int_t cap_setid; + nxt_mp_t *tmp_mp; nxt_int_t ret; + nxt_pid_t pid; + nxt_port_t *port; nxt_process_init_t *init; - init = nxt_process_init_create(task, NXT_PROCESS_WORKER, &app_conf->name); - if (nxt_slow_path(init == NULL)) { - return NXT_ERROR; - } - - cap_setid = rt->capabilities.setid; - - if (app_conf->isolation != NULL) { - ret = nxt_init_isolation(task, app_conf->isolation, init); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - } - -#if (NXT_HAVE_CLONE_NEWUSER) - if (NXT_CLONE_USER(init->isolation.clone.flags)) { - cap_setid = 1; - } -#endif - - if (cap_setid) { - ret = nxt_process_init_creds_set(task, init, &app_conf->user, - &app_conf->group); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - - } else { - if (!nxt_str_eq(&app_conf->user, (u_char *) rt->user_cred.user, - nxt_strlen(rt->user_cred.user))) - { - nxt_alert(task, "cannot set user \"%V\" for app \"%V\": " - "missing capabilities", &app_conf->user, &app_conf->name); - goto fail; - } - - if (app_conf->group.length > 0 - && !nxt_str_eq(&app_conf->group, (u_char *) rt->group, - nxt_strlen(rt->group))) - { - nxt_alert(task, "cannot set group \"%V\" for app \"%V\": " - "missing capabilities", &app_conf->group, - &app_conf->name); - goto fail; - } - } - - init->data = app_conf; - init->stream = stream; - -#if (NXT_HAVE_CLONE_NEWUSER) - ret = nxt_init_vldt_isolation_creds(task, init); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } -#endif - - return nxt_main_create_worker_process(task, rt, init); - -fail: - - nxt_mp_destroy(init->mem_pool); - - return NXT_ERROR; -} - - -nxt_int_t -nxt_main_create_worker_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init) -{ - nxt_int_t ret; - nxt_pid_t pid; - nxt_port_t *port; - nxt_process_t *process; - - /* - * TODO: remove process, init, ports from array on memory and fork failures. - */ - - process = nxt_runtime_process_new(rt); - if (nxt_slow_path(process == NULL)) { - nxt_mp_destroy(init->mem_pool); - - return NXT_ERROR; - } - - process->init = init; + init = nxt_process_init(process); port = nxt_port_new(task, 0, 0, init->type); if (nxt_slow_path(port == NULL)) { - nxt_process_use(task, process, -1); return NXT_ERROR; } @@ -786,10 +607,24 @@ nxt_main_create_worker_process(nxt_task_t *task, nxt_runtime_t *rt, nxt_process_use(task, process, -1); + ret = NXT_ERROR; + tmp_mp = NULL; + ret = nxt_port_socket_init(task, port, 0); if (nxt_slow_path(ret != NXT_OK)) { - nxt_port_use(task, port, -1); - return ret; + goto fail; + } + + tmp_mp = nxt_mp_create(1024, 128, 256, 32); + if (tmp_mp == NULL) { + goto fail; + } + + if (init->prefork) { + ret = init->prefork(task, process, tmp_mp); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } } pid = nxt_process_create(task, process); @@ -798,15 +633,13 @@ nxt_main_create_worker_process(nxt_task_t *task, nxt_runtime_t *rt, case -1: nxt_port_close(task, port); - nxt_port_use(task, port, -1); - - return NXT_ERROR; + break; case 0: - /* A worker process, return to the event engine work queue loop. */ - nxt_port_use(task, port, -1); + /* The child process: return to the event engine work queue loop. */ - return NXT_AGAIN; + ret = NXT_AGAIN; + break; default: /* The main process created a new process. */ @@ -814,35 +647,22 @@ nxt_main_create_worker_process(nxt_task_t *task, nxt_runtime_t *rt, nxt_port_read_close(port); nxt_port_write_enable(task, port); - nxt_port_use(task, port, -1); - - return NXT_OK; + ret = NXT_OK; + break; } -} +fail: -void -nxt_main_stop_all_processes(nxt_task_t *task, nxt_runtime_t *rt) -{ - nxt_port_t *port; - nxt_process_t *process; - - nxt_runtime_process_each(rt, process) { - - if (nxt_pid != process->pid) { - nxt_process_port_each(process, port) { - - (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, - -1, 0, 0, NULL); + nxt_port_use(task, port, -1); - } nxt_process_port_loop; - } + if (nxt_fast_path(tmp_mp != NULL)) { + nxt_mp_destroy(tmp_mp); + } - } nxt_runtime_process_loop; + return ret; } - static void nxt_main_process_sigterm_handler(nxt_task_t *task, void *obj, void *data) { @@ -1019,7 +839,7 @@ nxt_main_process_sigchld_handler(nxt_task_t *task, void *obj, void *data) pid, WEXITSTATUS(status)); } - nxt_main_cleanup_worker_process(task, pid); + nxt_main_cleanup_process(task, pid); } } @@ -1033,98 +853,83 @@ nxt_main_process_signal_handler(nxt_task_t *task, void *obj, void *data) static void -nxt_main_cleanup_worker_process(nxt_task_t *task, nxt_pid_t pid) +nxt_main_cleanup_process(nxt_task_t *task, nxt_pid_t pid) { - nxt_buf_t *buf; - nxt_port_t *port; - nxt_runtime_t *rt; - nxt_process_t *process; - nxt_process_type_t ptype; - nxt_process_init_t *init; - nxt_process_restart_t restart; + int stream; + nxt_int_t ret; + nxt_buf_t *buf; + nxt_port_t *port; + const char *name; + nxt_runtime_t *rt; + nxt_process_t *process; + nxt_process_init_t init; rt = task->thread->runtime; process = nxt_runtime_process_find(rt, pid); + if (!process) { + return; + } - if (process) { - init = process->init; - process->init = NULL; - - ptype = nxt_process_type(process); - restart = nxt_process_restarts[ptype]; - - if (process->ready) { - init->stream = 0; - } +#if (NXT_HAVE_ISOLATION_ROOTFS) + if (process->isolation.rootfs != NULL && process->isolation.mounts) { + (void) nxt_process_unmount_all(task, process); + } +#endif - nxt_process_close_ports(task, process); + name = process->name; + stream = process->stream; + init = *((nxt_process_init_t *) nxt_process_init(process)); - if (nxt_exiting) { - nxt_mp_destroy(init->mem_pool); + if (process->state == NXT_PROCESS_STATE_READY) { + process->stream = 0; + } - if (rt->nprocesses <= 2) { - nxt_runtime_quit(task, 0); - } + nxt_process_close_ports(task, process); - return; + if (nxt_exiting) { + if (rt->nprocesses <= 1) { + nxt_runtime_quit(task, 0); } - nxt_runtime_process_each(rt, process) { - - if (process->pid == nxt_pid - || process->pid == pid - || nxt_queue_is_empty(&process->ports)) - { - continue; - } - - port = nxt_process_port_first(process); - - if (nxt_proc_remove_notify_matrix[ptype][port->type] == 0) { - continue; - } - - buf = nxt_buf_mem_ts_alloc(task, task->thread->engine->mem_pool, - sizeof(pid)); - if (nxt_slow_path(buf == NULL)) { - continue; - } - - buf->mem.free = nxt_cpymem(buf->mem.free, &pid, sizeof(pid)); - - nxt_port_socket_write(task, port, NXT_PORT_MSG_REMOVE_PID, - -1, init->stream, 0, buf); - } nxt_runtime_process_loop; + return; + } - if (restart != NULL) { - restart(task, rt, init); + nxt_runtime_process_each(rt, process) { - } else { - nxt_mp_destroy(init->mem_pool); + if (process->pid == nxt_pid + || process->pid == pid + || nxt_queue_is_empty(&process->ports)) + { + continue; } - } -} + port = nxt_process_port_first(process); -static void -nxt_main_stop_worker_processes(nxt_task_t *task, nxt_runtime_t *rt) -{ - nxt_port_t *port; - nxt_process_t *process; + if (nxt_proc_remove_notify_matrix[init.type][port->type] == 0) { + continue; + } - nxt_runtime_process_each(rt, process) { + buf = nxt_buf_mem_ts_alloc(task, task->thread->engine->mem_pool, + sizeof(pid)); - nxt_process_port_each(process, port) { + if (nxt_slow_path(buf == NULL)) { + continue; + } - if (port->type == NXT_PROCESS_WORKER) { - (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, - -1, 0, 0, NULL); - } + buf->mem.free = nxt_cpymem(buf->mem.free, &pid, sizeof(pid)); - } nxt_process_port_loop; + nxt_port_socket_write(task, port, NXT_PORT_MSG_REMOVE_PID, -1, + stream, 0, buf); } nxt_runtime_process_loop; + + if (init.restart) { + ret = nxt_main_process_create(task, init); + if (nxt_slow_path(ret == NXT_ERROR)) { + nxt_alert(task, "failed to restart %s", name); + } + } } @@ -1335,19 +1140,50 @@ static nxt_conf_map_t nxt_app_lang_module_map[] = { }; +static nxt_conf_map_t nxt_app_lang_mounts_map[] = { + { + nxt_string("src"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_fs_mount_t, src), + }, + { + nxt_string("dst"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_fs_mount_t, dst), + }, + { + nxt_string("fstype"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_fs_mount_t, fstype), + }, + { + nxt_string("flags"), + NXT_CONF_MAP_INT, + offsetof(nxt_fs_mount_t, flags), + }, + { + nxt_string("data"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_fs_mount_t, data), + }, +}; + + static void nxt_main_port_modules_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) { - uint32_t index; + uint32_t index, jindex, nmounts; nxt_mp_t *mp; nxt_int_t ret; nxt_buf_t *b; nxt_port_t *port; nxt_runtime_t *rt; - nxt_conf_value_t *conf, *root, *value; + nxt_fs_mount_t *mnt; + nxt_conf_value_t *conf, *root, *value, *mounts; nxt_app_lang_module_t *lang; static nxt_str_t root_path = nxt_string("/"); + static nxt_str_t mounts_name = nxt_string("mounts"); rt = task->thread->runtime; @@ -1404,7 +1240,7 @@ nxt_main_port_modules_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) break; } - lang = nxt_array_add(rt->languages); + lang = nxt_array_zero_add(rt->languages); if (lang == NULL) { goto fail; } @@ -1418,8 +1254,48 @@ nxt_main_port_modules_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) goto fail; } - nxt_debug(task, "lang %d %s \"%s\"", - lang->type, lang->version, lang->file); + mounts = nxt_conf_get_object_member(value, &mounts_name, NULL); + if (mounts == NULL) { + nxt_alert(task, "missing mounts from discovery message."); + goto fail; + } + + if (nxt_conf_type(mounts) != NXT_CONF_ARRAY) { + nxt_alert(task, "invalid mounts type from discovery message."); + goto fail; + } + + nmounts = nxt_conf_array_elements_count(mounts); + + lang->mounts = nxt_array_create(rt->mem_pool, nmounts, + sizeof(nxt_fs_mount_t)); + + if (lang->mounts == NULL) { + goto fail; + } + + for (jindex = 0; /* */; jindex++) { + value = nxt_conf_get_array_element(mounts, jindex); + if (value == NULL) { + break; + } + + mnt = nxt_array_zero_add(lang->mounts); + if (mnt == NULL) { + goto fail; + } + + ret = nxt_conf_map_object(rt->mem_pool, value, + nxt_app_lang_mounts_map, + nxt_nitems(nxt_app_lang_mounts_map), mnt); + + if (ret != NXT_OK) { + goto fail; + } + } + + nxt_debug(task, "lang %d %s \"%s\" (%d mounts)", + lang->type, lang->version, lang->file, lang->mounts->nelts); } qsort(rt->languages->elts, rt->languages->nelts, @@ -1429,10 +1305,15 @@ fail: nxt_mp_destroy(mp); - ret = nxt_main_start_controller_process(task, rt); - + ret = nxt_main_process_create(task, nxt_controller_process); if (ret == NXT_OK) { - (void) nxt_main_start_router_process(task, rt); + ret = nxt_main_process_create(task, nxt_router_process); + } + + if (nxt_slow_path(ret == NXT_ERROR)) { + nxt_exiting = 1; + + nxt_runtime_quit(task, 1); } } @@ -1544,362 +1425,3 @@ nxt_main_port_access_log_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) msg->port_msg.stream, 0, NULL); } } - - -static nxt_int_t -nxt_init_isolation(nxt_task_t *task, nxt_conf_value_t *isolation, - nxt_process_init_t *init) -{ -#if (NXT_HAVE_CLONE) - nxt_int_t ret; - nxt_conf_value_t *obj; - - static nxt_str_t nsname = nxt_string("namespaces"); - - obj = nxt_conf_get_object_member(isolation, &nsname, NULL); - if (obj != NULL) { - ret = nxt_init_clone_flags(task, obj, init); - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } - } -#endif - -#if (NXT_HAVE_CLONE_NEWUSER) - ret = nxt_init_isolation_creds(task, isolation, init); - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } -#endif - - return NXT_OK; -} - - -#if (NXT_HAVE_CLONE_NEWUSER) - -static nxt_int_t -nxt_init_isolation_creds(nxt_task_t *task, nxt_conf_value_t *isolation, - nxt_process_init_t *init) -{ - nxt_int_t ret; - nxt_clone_t *clone; - nxt_conf_value_t *array; - - static nxt_str_t uidname = nxt_string("uidmap"); - static nxt_str_t gidname = nxt_string("gidmap"); - - clone = &init->isolation.clone; - - array = nxt_conf_get_object_member(isolation, &uidname, NULL); - if (array != NULL) { - ret = nxt_init_isolation_credential_map(task, init->mem_pool, array, - &clone->uidmap); - - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } - } - - array = nxt_conf_get_object_member(isolation, &gidname, NULL); - if (array != NULL) { - ret = nxt_init_isolation_credential_map(task, init->mem_pool, array, - &clone->gidmap); - - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } - } - - return NXT_OK; -} - - -static nxt_int_t -nxt_init_vldt_isolation_creds(nxt_task_t *task, nxt_process_init_t *init) -{ - nxt_int_t ret; - nxt_clone_t *clone; - - clone = &init->isolation.clone; - - if (clone->uidmap.size == 0 && clone->gidmap.size == 0) { - return NXT_OK; - } - - if (!NXT_CLONE_USER(clone->flags)) { - if (nxt_slow_path(clone->uidmap.size > 0)) { - nxt_log(task, NXT_LOG_ERR, "\"uidmap\" is set but " - "\"isolation.namespaces.credential\" is false or unset"); - - return NXT_ERROR; - } - - if (nxt_slow_path(clone->gidmap.size > 0)) { - nxt_log(task, NXT_LOG_ERR, "\"gidmap\" is set but " - "\"isolation.namespaces.credential\" is false or unset"); - - return NXT_ERROR; - } - - return NXT_OK; - } - - ret = nxt_clone_vldt_credential_uidmap(task, &clone->uidmap, - init->user_cred); - - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } - - return nxt_clone_vldt_credential_gidmap(task, &clone->gidmap, - init->user_cred); -} - - -static nxt_int_t -nxt_init_isolation_credential_map(nxt_task_t *task, nxt_mp_t *mem_pool, - nxt_conf_value_t *map_array, nxt_clone_credential_map_t *map) -{ - nxt_int_t ret; - nxt_uint_t i; - nxt_conf_value_t *obj; - - static nxt_conf_map_t nxt_clone_map_entry_conf[] = { - { - nxt_string("container"), - NXT_CONF_MAP_INT, - offsetof(nxt_clone_map_entry_t, container), - }, - - { - nxt_string("host"), - NXT_CONF_MAP_INT, - offsetof(nxt_clone_map_entry_t, host), - }, - - { - nxt_string("size"), - NXT_CONF_MAP_INT, - offsetof(nxt_clone_map_entry_t, size), - }, - }; - - map->size = nxt_conf_array_elements_count(map_array); - - if (map->size == 0) { - return NXT_OK; - } - - map->map = nxt_mp_alloc(mem_pool, - map->size * sizeof(nxt_clone_map_entry_t)); - if (nxt_slow_path(map->map == NULL)) { - return NXT_ERROR; - } - - for (i = 0; i < map->size; i++) { - obj = nxt_conf_get_array_element(map_array, i); - - ret = nxt_conf_map_object(mem_pool, obj, nxt_clone_map_entry_conf, - nxt_nitems(nxt_clone_map_entry_conf), - map->map + i); - if (nxt_slow_path(ret != NXT_OK)) { - nxt_alert(task, "clone map entry map error"); - return NXT_ERROR; - } - } - - return NXT_OK; -} - -#endif - -#if (NXT_HAVE_CLONE) - -static nxt_int_t -nxt_init_clone_flags(nxt_task_t *task, nxt_conf_value_t *namespaces, - nxt_process_init_t *init) -{ - uint32_t index; - nxt_str_t name; - nxt_int_t flag; - nxt_conf_value_t *value; - - index = 0; - - for ( ;; ) { - value = nxt_conf_next_object_member(namespaces, &name, &index); - - if (value == NULL) { - break; - } - - flag = 0; - -#if (NXT_HAVE_CLONE_NEWUSER) - if (nxt_str_eq(&name, "credential", 10)) { - flag = CLONE_NEWUSER; - } -#endif - -#if (NXT_HAVE_CLONE_NEWPID) - if (nxt_str_eq(&name, "pid", 3)) { - flag = CLONE_NEWPID; - } -#endif - -#if (NXT_HAVE_CLONE_NEWNET) - if (nxt_str_eq(&name, "network", 7)) { - flag = CLONE_NEWNET; - } -#endif - -#if (NXT_HAVE_CLONE_NEWUTS) - if (nxt_str_eq(&name, "uname", 5)) { - flag = CLONE_NEWUTS; - } -#endif - -#if (NXT_HAVE_CLONE_NEWNS) - if (nxt_str_eq(&name, "mount", 5)) { - flag = CLONE_NEWNS; - } -#endif - -#if (NXT_HAVE_CLONE_NEWCGROUP) - if (nxt_str_eq(&name, "cgroup", 6)) { - flag = CLONE_NEWCGROUP; - } -#endif - - if (!flag) { - nxt_alert(task, "unknown namespace flag: \"%V\"", &name); - return NXT_ERROR; - } - - if (nxt_conf_get_boolean(value)) { - init->isolation.clone.flags |= flag; - } - } - - return NXT_OK; -} - -#endif - - -static nxt_process_init_t * -nxt_process_init_create(nxt_task_t *task, nxt_process_type_t type, - const nxt_str_t *name) -{ - nxt_mp_t *mp; - nxt_int_t ret; - nxt_runtime_t *rt; - nxt_process_init_t *init; - - mp = nxt_mp_create(1024, 128, 256, 32); - if (nxt_slow_path(mp == NULL)) { - return NULL; - } - - init = nxt_mp_zalloc(mp, sizeof(nxt_process_init_t)); - if (nxt_slow_path(init == NULL)) { - goto fail; - } - - init->mem_pool = mp; - - ret = nxt_process_init_name_set(init, type, name); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - - rt = task->thread->runtime; - - init->type = type; - init->start = nxt_process_starts[type]; - init->port_handlers = nxt_process_port_handlers[type]; - init->signals = nxt_worker_process_signals; - init->user_cred = &rt->user_cred; - init->data = &rt; - - return init; - -fail: - - nxt_mp_destroy(mp); - - return NULL; -} - - -static nxt_int_t -nxt_process_init_name_set(nxt_process_init_t *init, nxt_process_type_t type, - const nxt_str_t *name) -{ - u_char *str, *end; - size_t size; - const char *fmt; - - size = name->length + 1; - - if (type == NXT_PROCESS_WORKER) { - size += nxt_length("\"\" application"); - fmt = "\"%V\" application%Z"; - - } else { - fmt = "%V%Z"; - } - - str = nxt_mp_alloc(init->mem_pool, size); - if (nxt_slow_path(str == NULL)) { - return NXT_ERROR; - } - - end = str + size; - - nxt_sprintf(str, end, fmt, name); - - init->name = (char *) str; - - return NXT_OK; -} - - -static nxt_int_t -nxt_process_init_creds_set(nxt_task_t *task, nxt_process_init_t *init, - nxt_str_t *user, nxt_str_t *group) -{ - char *str; - - init->user_cred = nxt_mp_zalloc(init->mem_pool, sizeof(nxt_credential_t)); - - if (nxt_slow_path(init->user_cred == NULL)) { - return NXT_ERROR; - } - - str = nxt_mp_zalloc(init->mem_pool, user->length + 1); - if (nxt_slow_path(str == NULL)) { - return NXT_ERROR; - } - - nxt_memcpy(str, user->start, user->length); - str[user->length] = '\0'; - - init->user_cred->user = str; - - if (group->start != NULL) { - str = nxt_mp_zalloc(init->mem_pool, group->length + 1); - if (nxt_slow_path(str == NULL)) { - return NXT_ERROR; - } - - nxt_memcpy(str, group->start, group->length); - str[group->length] = '\0'; - - } else { - str = NULL; - } - - return nxt_credential_get(task, init->mem_pool, init->user_cred, str); -} diff --git a/src/nxt_main_process.h b/src/nxt_main_process.h index b0570a84..f9c974d8 100644 --- a/src/nxt_main_process.h +++ b/src/nxt_main_process.h @@ -19,26 +19,17 @@ typedef enum { } nxt_socket_error_t; -typedef struct { - nxt_str_t conf; -#if (NXT_TLS) - nxt_array_t *certs; -#endif -} nxt_controller_init_t; - - nxt_int_t nxt_main_process_start(nxt_thread_t *thr, nxt_task_t *task, nxt_runtime_t *runtime); -void nxt_main_stop_all_processes(nxt_task_t *task, nxt_runtime_t *runtime); -nxt_int_t nxt_controller_start(nxt_task_t *task, void *data); -nxt_int_t nxt_router_start(nxt_task_t *task, void *data); -nxt_int_t nxt_discovery_start(nxt_task_t *task, void *data); -nxt_int_t nxt_app_start(nxt_task_t *task, void *data); +NXT_EXPORT extern const nxt_process_init_t nxt_discovery_process; +NXT_EXPORT extern const nxt_process_init_t nxt_controller_process; +NXT_EXPORT extern const nxt_process_init_t nxt_router_process; +NXT_EXPORT extern const nxt_process_init_t nxt_app_process; extern const nxt_sig_event_t nxt_main_process_signals[]; -extern const nxt_sig_event_t nxt_worker_process_signals[]; +extern const nxt_sig_event_t nxt_process_signals[]; #endif /* _NXT_MAIN_PROCESS_H_INCLUDED_ */ diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index f5053652..7ae8484d 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -29,8 +29,28 @@ #define NXT_PHP7 1 #endif +/* PHP 8 */ +#ifndef TSRMLS_CC +#define TSRMLS_CC +#define TSRMLS_DC +#define TSRMLS_D void +#define TSRMLS_C +#endif + + +typedef struct { + nxt_str_t root; + nxt_str_t index; + nxt_str_t script_name; + nxt_str_t script_dirname; + nxt_str_t script_filename; +} nxt_php_target_t; + + typedef struct { char *cookie; + nxt_str_t *root; + nxt_str_t *index; nxt_str_t path_info; nxt_str_t script_name; nxt_str_t script_filename; @@ -52,27 +72,28 @@ typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS); #endif -static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static nxt_int_t nxt_php_start(nxt_task_t *task, nxt_process_data_t *data); +static nxt_int_t nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target, + nxt_conf_value_t *conf); +static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, + int type); +static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, + int type); +static void nxt_php_disable(nxt_task_t *task, const char *type, + nxt_str_t *value, char **ptr, nxt_php_disable_t disable); +static nxt_int_t nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir); static void nxt_php_str_trim_trail(nxt_str_t *str, u_char t); static void nxt_php_str_trim_lead(nxt_str_t *str, u_char t); -static nxt_int_t nxt_php_dirname(const nxt_str_t *file, nxt_str_t *dir); nxt_inline u_char *nxt_realpath(const void *c); -nxt_inline void nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, - const nxt_str_t *dirname); -static void nxt_php_script_request_handler(nxt_unit_request_info_t *req); -static void nxt_php_path_request_handler(nxt_unit_request_info_t *req); -static nxt_int_t nxt_php_request_init(nxt_php_run_ctx_t *ctx, +static void nxt_php_request_handler(nxt_unit_request_info_t *req); +static void nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r); +static void nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r); +nxt_inline void nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir); static int nxt_php_startup(sapi_module_struct *sapi_module); -static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, - int type); -static nxt_int_t nxt_php_alter_option(nxt_str_t *name, nxt_str_t *value, - int type); -static void nxt_php_disable(nxt_task_t *task, const char *type, - nxt_str_t *value, char **ptr, nxt_php_disable_t disable); static int nxt_php_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC); static void *nxt_php_hash_str_find_ptr(const HashTable *ht, const nxt_str_t *str); @@ -210,13 +231,6 @@ static sapi_module_struct nxt_php_sapi_module = }; -static nxt_str_t nxt_php_root; -static nxt_str_t nxt_php_script_name; -static nxt_str_t nxt_php_script_dirname; -static nxt_str_t nxt_php_script_filename; -static nxt_str_t nxt_php_index = nxt_string("index.php"); - - static uint32_t compat[] = { NXT_VERNUM, NXT_DEBUG, }; @@ -228,10 +242,15 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("php"), PHP_VERSION, NULL, - nxt_php_init, + 0, + NULL, + nxt_php_start, }; +static nxt_php_target_t *nxt_php_targets; +static nxt_int_t nxt_php_last_target = -1; + static nxt_task_t *nxt_php_task; #if defined(ZTS) && PHP_VERSION_ID < 70400 static void ***tsrm_ls; @@ -239,19 +258,20 @@ static void ***tsrm_ls; static nxt_int_t -nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_php_start(nxt_task_t *task, nxt_process_data_t *data) { - u_char *p, *tmp; - nxt_str_t ini_path; - nxt_str_t *root, *script_filename, *script_dirname, *script_name; - nxt_str_t *index; - nxt_int_t ret; - nxt_port_t *my_port, *main_port; - nxt_runtime_t *rt; - nxt_unit_ctx_t *unit_ctx; - nxt_unit_init_t php_init; - nxt_conf_value_t *value; - nxt_php_app_conf_t *c; + u_char *p; + uint32_t next; + nxt_str_t ini_path, name; + nxt_int_t ret; + nxt_uint_t n; + nxt_port_t *my_port, *main_port; + nxt_runtime_t *rt; + nxt_unit_ctx_t *unit_ctx; + nxt_unit_init_t php_init; + nxt_conf_value_t *value; + nxt_php_app_conf_t *c; + nxt_common_app_conf_t *conf; static nxt_str_t file_str = nxt_string("file"); static nxt_str_t user_str = nxt_string("user"); @@ -259,107 +279,36 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_php_task = task; + conf = data->app; c = &conf->u.php; - if (c->root == NULL) { - nxt_alert(task, "php root is empty"); - return NXT_ERROR; - } - - root = &nxt_php_root; - script_filename = &nxt_php_script_filename; - script_dirname = &nxt_php_script_dirname; - script_name = &nxt_php_script_name; - index = &nxt_php_index; + n = (c->targets != NULL) ? nxt_conf_object_members_count(c->targets) : 1; - root->start = nxt_realpath(c->root); - if (nxt_slow_path(root->start == NULL)) { - nxt_alert(task, "root realpath(%s) failed %E", c->root, nxt_errno); + nxt_php_targets = nxt_zalloc(sizeof(nxt_php_target_t) * n); + if (nxt_slow_path(nxt_php_targets == NULL)) { return NXT_ERROR; } - root->length = nxt_strlen(root->start); - - nxt_php_str_trim_trail(root, '/'); - - if (c->script.length > 0) { - nxt_php_str_trim_lead(&c->script, '/'); - - tmp = nxt_malloc(root->length + 1 + c->script.length + 1); - if (nxt_slow_path(tmp == NULL)) { - return NXT_ERROR; - } - - p = tmp; - - p = nxt_cpymem(p, root->start, root->length); - *p++ = '/'; - - p = nxt_cpymem(p, c->script.start, c->script.length); - *p = '\0'; - - script_filename->start = nxt_realpath(tmp); - if (nxt_slow_path(script_filename->start == NULL)) { - nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno); - return NXT_ERROR; - } - - nxt_free(tmp); - - script_filename->length = nxt_strlen(script_filename->start); - - if (!nxt_str_start(script_filename, root->start, root->length)) { - nxt_alert(task, "script is not under php root"); - return NXT_ERROR; - } + if (c->targets != NULL) { + next = 0; - ret = nxt_php_dirname(script_filename, script_dirname); - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; - } + for (n = 0; /* void */; n++) { + value = nxt_conf_next_object_member(c->targets, &name, &next); + if (value == NULL) { + break; + } - script_name->length = c->script.length + 1; - script_name->start = nxt_malloc(script_name->length); - if (nxt_slow_path(script_name->start == NULL)) { - return NXT_ERROR; + ret = nxt_php_set_target(task, &nxt_php_targets[n], value); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } } - script_name->start[0] = '/'; - nxt_memcpy(script_name->start + 1, c->script.start, c->script.length); - - nxt_log_error(NXT_LOG_INFO, task->log, - "(ABS_MODE) php script \"%V\" root: \"%V\"", - script_name, root); - } else { - nxt_log_error(NXT_LOG_INFO, task->log, - "(non ABS_MODE) php root: \"%V\"", root); - } - - if (c->index.length > 0) { - index->length = c->index.length; - index->start = nxt_malloc(index->length); - if (nxt_slow_path(index->start == NULL)) { - return NXT_ERROR; - } - - nxt_memcpy(index->start, c->index.start, c->index.length); - } - - nxt_memzero(&php_init, sizeof(nxt_unit_init_t)); - - if (nxt_php_script_filename.start != NULL) { - if (nxt_slow_path(chdir((char *) script_dirname->start) != 0)) { - nxt_alert(task, "failed to chdir(%V) %E", script_dirname, - nxt_errno); - + ret = nxt_php_set_target(task, &nxt_php_targets[0], conf->self); + if (nxt_slow_path(ret != NXT_OK)) { return NXT_ERROR; } - - php_init.callbacks.request_handler = nxt_php_script_request_handler; - - } else { - php_init.callbacks.request_handler = nxt_php_path_request_handler; } #ifdef ZTS @@ -430,13 +379,17 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) return NXT_ERROR; } + nxt_memzero(&php_init, sizeof(nxt_unit_init_t)); + + php_init.callbacks.request_handler = nxt_php_request_handler; + php_init.ready_port.id.pid = main_port->pid; php_init.ready_port.id.id = main_port->id; php_init.ready_port.out_fd = main_port->pair[1]; nxt_fd_blocking(task, main_port->pair[1]); - php_init.ready_stream = my_port->process->init->stream; + php_init.ready_stream = my_port->process->stream; php_init.read_port.id.pid = my_port->pid; php_init.read_port.id.id = my_port->id; @@ -462,6 +415,125 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) } +static nxt_int_t +nxt_php_set_target(nxt_task_t *task, nxt_php_target_t *target, + nxt_conf_value_t *conf) +{ + u_char *tmp, *p; + nxt_str_t str; + nxt_int_t ret; + nxt_conf_value_t *value; + + static nxt_str_t root_str = nxt_string("root"); + static nxt_str_t script_str = nxt_string("script"); + static nxt_str_t index_str = nxt_string("index"); + + value = nxt_conf_get_object_member(conf, &root_str, NULL); + + if (value == NULL) { + nxt_alert(task, "no php root specified"); + return NXT_ERROR; + } + + nxt_conf_get_string(value, &str); + + tmp = nxt_malloc(str.length + 1); + if (nxt_slow_path(tmp == NULL)) { + return NXT_ERROR; + } + + p = tmp; + + p = nxt_cpymem(p, str.start, str.length); + *p = '\0'; + + p = nxt_realpath(tmp); + if (nxt_slow_path(p == NULL)) { + nxt_alert(task, "root realpath(%s) failed %E", tmp, nxt_errno); + return NXT_ERROR; + } + + nxt_free(tmp); + + target->root.length = nxt_strlen(p); + target->root.start = p; + + nxt_php_str_trim_trail(&target->root, '/'); + + value = nxt_conf_get_object_member(conf, &script_str, NULL); + + if (value != NULL) { + nxt_conf_get_string(value, &str); + + nxt_php_str_trim_lead(&str, '/'); + + tmp = nxt_malloc(target->root.length + 1 + str.length + 1); + if (nxt_slow_path(tmp == NULL)) { + return NXT_ERROR; + } + + p = tmp; + + p = nxt_cpymem(p, target->root.start, target->root.length); + *p++ = '/'; + + p = nxt_cpymem(p, str.start, str.length); + *p = '\0'; + + p = nxt_realpath(tmp); + if (nxt_slow_path(p == NULL)) { + nxt_alert(task, "script realpath(%s) failed %E", tmp, nxt_errno); + return NXT_ERROR; + } + + nxt_free(tmp); + + target->script_filename.length = nxt_strlen(p); + target->script_filename.start = p; + + if (!nxt_str_start(&target->script_filename, + target->root.start, target->root.length)) + { + nxt_alert(task, "script is not under php root"); + return NXT_ERROR; + } + + ret = nxt_php_dirname(&target->script_filename, + &target->script_dirname); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + target->script_name.length = target->script_filename.length + - target->root.length; + target->script_name.start = target->script_filename.start + + target->root.length; + + } else { + value = nxt_conf_get_object_member(conf, &index_str, NULL); + + if (value != NULL) { + nxt_conf_get_string(value, &str); + + tmp = nxt_malloc(str.length); + if (nxt_slow_path(tmp == NULL)) { + return NXT_ERROR; + } + + nxt_memcpy(tmp, str.start, str.length); + + target->index.length = str.length; + target->index.start = tmp; + + } else { + nxt_str_set(&target->index, "index.php"); + } + } + + return NXT_OK; +} + + static void nxt_php_set_options(nxt_task_t *task, nxt_conf_value_t *options, int type) { @@ -686,56 +758,44 @@ nxt_realpath(const void *c) static void -nxt_php_script_request_handler(nxt_unit_request_info_t *req) +nxt_php_request_handler(nxt_unit_request_info_t *req) { - zend_file_handle file_handle; - nxt_php_run_ctx_t ctx; + nxt_php_target_t *target; + nxt_php_run_ctx_t ctx; + nxt_unit_request_t *r; + + r = req->request; + target = &nxt_php_targets[r->app_target]; nxt_memzero(&ctx, sizeof(ctx)); ctx.req = req; - ctx.script_filename = nxt_php_script_filename; - ctx.script_dirname = nxt_php_script_dirname; - ctx.script_name = nxt_php_script_name; + ctx.root = &target->root; + ctx.index = &target->index; - nxt_memzero(&file_handle, sizeof(file_handle)); - - file_handle.type = ZEND_HANDLE_FILENAME; - file_handle.filename = (char *) ctx.script_filename.start; - - if (nxt_slow_path(nxt_php_request_init(&ctx, req->request) != NXT_OK)) { - nxt_unit_request_done(req, NXT_UNIT_ERROR); + if (target->script_filename.length == 0) { + nxt_php_dynamic_request(&ctx, r); return; } - php_execute_script(&file_handle TSRMLS_CC); + ctx.script_filename = target->script_filename; + ctx.script_dirname = target->script_dirname; + ctx.script_name = target->script_name; - if (ctx.chdir) { - nxt_php_vcwd_chdir(ctx.req, &nxt_php_script_dirname); - } + ctx.chdir = (r->app_target != nxt_php_last_target); - php_request_shutdown(NULL); + nxt_php_execute(&ctx, r); - nxt_unit_request_done(req, NXT_UNIT_OK); + nxt_php_last_target = ctx.chdir ? -1 : r->app_target; } static void -nxt_php_path_request_handler(nxt_unit_request_info_t *req) +nxt_php_dynamic_request(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) { - u_char *p; - nxt_str_t path, script_name; - nxt_int_t ret; - zend_file_handle file_handle; - nxt_php_run_ctx_t run_ctx, *ctx; - nxt_unit_request_t *r; - - nxt_memzero(&run_ctx, sizeof(run_ctx)); - - ctx = &run_ctx; - ctx->req = req; - - r = req->request; + u_char *p; + nxt_str_t path, script_name; + nxt_int_t ret; path.length = r->path_length; path.start = nxt_unit_sptr_get(&r->path); @@ -750,26 +810,26 @@ nxt_php_path_request_handler(nxt_unit_request_info_t *req) ctx->path_info.length = r->path_length - path.length; } else if (path.start[path.length - 1] == '/') { - script_name = nxt_php_index; + script_name = *ctx->index; } else { if (nxt_slow_path(path.length < 4 || nxt_memcmp(path.start + (path.length - 4), ".php", 4))) { - nxt_unit_request_done(req, NXT_UNIT_ERROR); + nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR); return; } } - ctx->script_filename.length = nxt_php_root.length + ctx->script_filename.length = ctx->root->length + path.length + script_name.length; p = nxt_malloc(ctx->script_filename.length + 1); if (nxt_slow_path(p == NULL)) { - nxt_unit_request_done(req, NXT_UNIT_ERROR); + nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR); return; } @@ -777,9 +837,9 @@ nxt_php_path_request_handler(nxt_unit_request_info_t *req) ctx->script_filename.start = p; ctx->script_name.length = path.length + script_name.length; - ctx->script_name.start = p + nxt_php_root.length; + ctx->script_name.start = p + ctx->root->length; - p = nxt_cpymem(p, nxt_php_root.start, nxt_php_root.length); + p = nxt_cpymem(p, ctx->root->start, ctx->root->length); p = nxt_cpymem(p, path.start, path.length); if (script_name.length > 0) { @@ -788,50 +848,33 @@ nxt_php_path_request_handler(nxt_unit_request_info_t *req) *p = '\0'; - nxt_memzero(&file_handle, sizeof(file_handle)); - - file_handle.type = ZEND_HANDLE_FILENAME; - file_handle.filename = (char *) ctx->script_filename.start; + ctx->chdir = 1; ret = nxt_php_dirname(&ctx->script_filename, &ctx->script_dirname); if (nxt_slow_path(ret != NXT_OK)) { - nxt_unit_request_done(req, NXT_UNIT_ERROR); + nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR); nxt_free(ctx->script_filename.start); return; } - if (nxt_slow_path(nxt_php_request_init(ctx, req->request) != NXT_OK)) { - nxt_unit_request_done(req, NXT_UNIT_ERROR); - goto cleanup; - } - - nxt_php_vcwd_chdir(ctx->req, &ctx->script_dirname); - - php_execute_script(&file_handle TSRMLS_CC); - - php_request_shutdown(NULL); - - nxt_unit_request_done(req, NXT_UNIT_OK); - -cleanup: + nxt_php_execute(ctx, r); nxt_free(ctx->script_filename.start); nxt_free(ctx->script_dirname.start); -} - -static int -nxt_php_startup(sapi_module_struct *sapi_module) -{ - return php_module_startup(sapi_module, &nxt_php_unit_module, 1); + nxt_php_last_target = -1; } -static nxt_int_t -nxt_php_request_init(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) +static void +nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) { nxt_unit_field_t *f; + zend_file_handle file_handle; + + nxt_unit_req_debug(ctx->req, "PHP execute script %s", + ctx->script_filename.start); SG(server_context) = ctx; SG(options) |= SAPI_OPTION_NO_CHDIR; @@ -860,20 +903,6 @@ nxt_php_request_init(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) SG(request_info).path_translated = NULL; - nxt_unit_req_debug(ctx->req, "handle.filename = '%s'", - ctx->script_filename.start); - - if (nxt_php_script_filename.start != NULL) { - nxt_unit_req_debug(ctx->req, "run script %.*s in absolute mode", - (int) nxt_php_script_filename.length, - (char *) nxt_php_script_filename.start); - - } else { - nxt_unit_req_debug(ctx->req, "run script %.*s", - (int) ctx->script_filename.length, - (char *) ctx->script_filename.start); - } - #ifdef NXT_PHP7 if (nxt_slow_path(php_request_startup() == FAILURE)) { #else @@ -881,23 +910,45 @@ nxt_php_request_init(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) #endif nxt_unit_req_debug(ctx->req, "php_request_startup() failed"); - return NXT_ERROR; + nxt_unit_request_done(ctx->req, NXT_UNIT_ERROR); + return; } - return NXT_OK; + if (ctx->chdir) { + ctx->chdir = 0; + nxt_php_vcwd_chdir(ctx->req, ctx->script_dirname.start); + } + + nxt_memzero(&file_handle, sizeof(file_handle)); + + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = (char *) ctx->script_filename.start; + + php_execute_script(&file_handle TSRMLS_CC); + + php_request_shutdown(NULL); + + nxt_unit_request_done(ctx->req, NXT_UNIT_OK); } nxt_inline void -nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, const nxt_str_t *dir) +nxt_php_vcwd_chdir(nxt_unit_request_info_t *req, u_char *dir) { - if (nxt_slow_path(VCWD_CHDIR((char *) dir->start) != 0)) { + if (nxt_slow_path(VCWD_CHDIR((char *) dir) != 0)) { nxt_unit_req_alert(req, "VCWD_CHDIR(%s) failed (%d: %s)", - dir->start, errno, strerror(errno)); + dir, errno, strerror(errno)); } } +static int +nxt_php_startup(sapi_module_struct *sapi_module) +{ + return php_module_startup(sapi_module, &nxt_php_unit_module, 1); +} + + #ifdef NXT_PHP7 static size_t nxt_php_unbuffered_write(const char *str, size_t str_length TSRMLS_DC) @@ -1061,19 +1112,16 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) * available. */ - if (nxt_php_script_name.start != NULL) { - /* ABS_MODE */ - nxt_php_set_str(req, "PHP_SELF", &nxt_php_script_name, - track_vars_array TSRMLS_CC); - - } else { + if (ctx->path_info.length != 0) { nxt_php_set_sptr(req, "PHP_SELF", &r->path, r->path_length, track_vars_array TSRMLS_CC); - } - if (ctx->path_info.length != 0) { nxt_php_set_str(req, "PATH_INFO", &ctx->path_info, track_vars_array TSRMLS_CC); + + } else { + nxt_php_set_str(req, "PHP_SELF", &ctx->script_name, + track_vars_array TSRMLS_CC); } /* @@ -1100,7 +1148,7 @@ nxt_php_register_variables(zval *track_vars_array TSRMLS_DC) * as defined in the server's configuration file. */ - nxt_php_set_str(req, "DOCUMENT_ROOT", &nxt_php_root, + nxt_php_set_str(req, "DOCUMENT_ROOT", ctx->root, track_vars_array TSRMLS_CC); nxt_php_set_sptr(req, "REQUEST_METHOD", &r->method, r->method_length, diff --git a/src/nxt_port.c b/src/nxt_port.c index 70cf33e6..7232c465 100644 --- a/src/nxt_port.c +++ b/src/nxt_port.c @@ -296,7 +296,9 @@ nxt_port_process_ready_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) return; } - process->ready = 1; + nxt_assert(process->state != NXT_PROCESS_STATE_READY); + + process->state = NXT_PROCESS_STATE_READY; nxt_assert(!nxt_queue_is_empty(&process->ports)); diff --git a/src/nxt_port.h b/src/nxt_port.h index c6f15238..0e8707f3 100644 --- a/src/nxt_port.h +++ b/src/nxt_port.h @@ -14,7 +14,7 @@ struct nxt_port_handlers_s { nxt_port_handler_t rpc_error; /* Main process RPC requests. */ - nxt_port_handler_t start_worker; + nxt_port_handler_t start_process; nxt_port_handler_t socket; nxt_port_handler_t modules; nxt_port_handler_t conf_store; @@ -27,7 +27,8 @@ struct nxt_port_handlers_s { nxt_port_handler_t new_port; nxt_port_handler_t mmap; - /* New process ready. */ + /* New process */ + nxt_port_handler_t process_created; nxt_port_handler_t process_ready; /* Process exit/crash notification. */ @@ -53,76 +54,76 @@ struct nxt_port_handlers_s { #define nxt_port_handler_idx(name) \ ( offsetof(nxt_port_handlers_t, name) / sizeof(nxt_port_handler_t) ) +#define nxt_msg_last(handler) \ + (handler | NXT_PORT_MSG_LAST) typedef enum { - NXT_PORT_MSG_LAST = 0x100, - NXT_PORT_MSG_CLOSE_FD = 0x200, - NXT_PORT_MSG_SYNC = 0x400, - - NXT_PORT_MSG_MASK = 0xFF, - - _NXT_PORT_MSG_RPC_READY = nxt_port_handler_idx(rpc_ready), - _NXT_PORT_MSG_RPC_ERROR = nxt_port_handler_idx(rpc_error), - - _NXT_PORT_MSG_START_WORKER = nxt_port_handler_idx(start_worker), - _NXT_PORT_MSG_SOCKET = nxt_port_handler_idx(socket), - _NXT_PORT_MSG_MODULES = nxt_port_handler_idx(modules), - _NXT_PORT_MSG_CONF_STORE = nxt_port_handler_idx(conf_store), - _NXT_PORT_MSG_CERT_GET = nxt_port_handler_idx(cert_get), - _NXT_PORT_MSG_CERT_DELETE = nxt_port_handler_idx(cert_delete), - _NXT_PORT_MSG_ACCESS_LOG = nxt_port_handler_idx(access_log), - - _NXT_PORT_MSG_CHANGE_FILE = nxt_port_handler_idx(change_file), - _NXT_PORT_MSG_NEW_PORT = nxt_port_handler_idx(new_port), - _NXT_PORT_MSG_MMAP = nxt_port_handler_idx(mmap), - - _NXT_PORT_MSG_PROCESS_READY = nxt_port_handler_idx(process_ready), - _NXT_PORT_MSG_REMOVE_PID = nxt_port_handler_idx(remove_pid), - _NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit), - - _NXT_PORT_MSG_REQ_HEADERS = nxt_port_handler_idx(req_headers), - _NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame), - - _NXT_PORT_MSG_DATA = nxt_port_handler_idx(data), - - _NXT_PORT_MSG_OOSM = nxt_port_handler_idx(oosm), - _NXT_PORT_MSG_SHM_ACK = nxt_port_handler_idx(shm_ack), - - NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t) - / sizeof(nxt_port_handler_t), - - NXT_PORT_MSG_RPC_READY = _NXT_PORT_MSG_RPC_READY, - NXT_PORT_MSG_RPC_READY_LAST = _NXT_PORT_MSG_RPC_READY | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_RPC_ERROR = _NXT_PORT_MSG_RPC_ERROR | NXT_PORT_MSG_LAST, - - NXT_PORT_MSG_START_WORKER = _NXT_PORT_MSG_START_WORKER - | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_SOCKET = _NXT_PORT_MSG_SOCKET | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_MODULES = _NXT_PORT_MSG_MODULES | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_CONF_STORE = _NXT_PORT_MSG_CONF_STORE | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_CERT_GET = _NXT_PORT_MSG_CERT_GET | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_CERT_DELETE = _NXT_PORT_MSG_CERT_DELETE | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_ACCESS_LOG = _NXT_PORT_MSG_ACCESS_LOG | NXT_PORT_MSG_LAST, - - NXT_PORT_MSG_CHANGE_FILE = _NXT_PORT_MSG_CHANGE_FILE | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_NEW_PORT = _NXT_PORT_MSG_NEW_PORT | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_MMAP = _NXT_PORT_MSG_MMAP | NXT_PORT_MSG_LAST - | NXT_PORT_MSG_CLOSE_FD | NXT_PORT_MSG_SYNC, - - NXT_PORT_MSG_PROCESS_READY = _NXT_PORT_MSG_PROCESS_READY - | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_QUIT = _NXT_PORT_MSG_QUIT | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_REMOVE_PID = _NXT_PORT_MSG_REMOVE_PID | NXT_PORT_MSG_LAST, - - NXT_PORT_MSG_REQ_HEADERS = _NXT_PORT_MSG_REQ_HEADERS, - NXT_PORT_MSG_WEBSOCKET = _NXT_PORT_MSG_WEBSOCKET, - NXT_PORT_MSG_WEBSOCKET_LAST = _NXT_PORT_MSG_WEBSOCKET | NXT_PORT_MSG_LAST, - - NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA, - NXT_PORT_MSG_DATA_LAST = _NXT_PORT_MSG_DATA | NXT_PORT_MSG_LAST, - - NXT_PORT_MSG_OOSM = _NXT_PORT_MSG_OOSM | NXT_PORT_MSG_LAST, - NXT_PORT_MSG_SHM_ACK = _NXT_PORT_MSG_SHM_ACK | NXT_PORT_MSG_LAST, + NXT_PORT_MSG_LAST = 0x100, + NXT_PORT_MSG_CLOSE_FD = 0x200, + NXT_PORT_MSG_SYNC = 0x400, + + NXT_PORT_MSG_MASK = 0xFF, + + _NXT_PORT_MSG_RPC_READY = nxt_port_handler_idx(rpc_ready), + _NXT_PORT_MSG_RPC_ERROR = nxt_port_handler_idx(rpc_error), + + _NXT_PORT_MSG_START_PROCESS = nxt_port_handler_idx(start_process), + _NXT_PORT_MSG_SOCKET = nxt_port_handler_idx(socket), + _NXT_PORT_MSG_MODULES = nxt_port_handler_idx(modules), + _NXT_PORT_MSG_CONF_STORE = nxt_port_handler_idx(conf_store), + _NXT_PORT_MSG_CERT_GET = nxt_port_handler_idx(cert_get), + _NXT_PORT_MSG_CERT_DELETE = nxt_port_handler_idx(cert_delete), + _NXT_PORT_MSG_ACCESS_LOG = nxt_port_handler_idx(access_log), + + _NXT_PORT_MSG_CHANGE_FILE = nxt_port_handler_idx(change_file), + _NXT_PORT_MSG_NEW_PORT = nxt_port_handler_idx(new_port), + _NXT_PORT_MSG_MMAP = nxt_port_handler_idx(mmap), + + _NXT_PORT_MSG_PROCESS_CREATED = nxt_port_handler_idx(process_created), + _NXT_PORT_MSG_PROCESS_READY = nxt_port_handler_idx(process_ready), + _NXT_PORT_MSG_REMOVE_PID = nxt_port_handler_idx(remove_pid), + _NXT_PORT_MSG_QUIT = nxt_port_handler_idx(quit), + + _NXT_PORT_MSG_REQ_HEADERS = nxt_port_handler_idx(req_headers), + _NXT_PORT_MSG_WEBSOCKET = nxt_port_handler_idx(websocket_frame), + + _NXT_PORT_MSG_DATA = nxt_port_handler_idx(data), + + _NXT_PORT_MSG_OOSM = nxt_port_handler_idx(oosm), + _NXT_PORT_MSG_SHM_ACK = nxt_port_handler_idx(shm_ack), + + NXT_PORT_MSG_MAX = sizeof(nxt_port_handlers_t) + / sizeof(nxt_port_handler_t), + + NXT_PORT_MSG_RPC_READY = _NXT_PORT_MSG_RPC_READY, + NXT_PORT_MSG_RPC_READY_LAST = nxt_msg_last(_NXT_PORT_MSG_RPC_READY), + NXT_PORT_MSG_RPC_ERROR = nxt_msg_last(_NXT_PORT_MSG_RPC_ERROR), + NXT_PORT_MSG_START_PROCESS = nxt_msg_last(_NXT_PORT_MSG_START_PROCESS), + NXT_PORT_MSG_SOCKET = nxt_msg_last(_NXT_PORT_MSG_SOCKET), + NXT_PORT_MSG_MODULES = nxt_msg_last(_NXT_PORT_MSG_MODULES), + NXT_PORT_MSG_CONF_STORE = nxt_msg_last(_NXT_PORT_MSG_CONF_STORE), + NXT_PORT_MSG_CERT_GET = nxt_msg_last(_NXT_PORT_MSG_CERT_GET), + NXT_PORT_MSG_CERT_DELETE = nxt_msg_last(_NXT_PORT_MSG_CERT_DELETE), + NXT_PORT_MSG_ACCESS_LOG = nxt_msg_last(_NXT_PORT_MSG_ACCESS_LOG), + NXT_PORT_MSG_CHANGE_FILE = nxt_msg_last(_NXT_PORT_MSG_CHANGE_FILE), + NXT_PORT_MSG_NEW_PORT = nxt_msg_last(_NXT_PORT_MSG_NEW_PORT), + NXT_PORT_MSG_MMAP = nxt_msg_last(_NXT_PORT_MSG_MMAP) + | NXT_PORT_MSG_CLOSE_FD | NXT_PORT_MSG_SYNC, + + NXT_PORT_MSG_PROCESS_CREATED = nxt_msg_last(_NXT_PORT_MSG_PROCESS_CREATED), + NXT_PORT_MSG_PROCESS_READY = nxt_msg_last(_NXT_PORT_MSG_PROCESS_READY), + NXT_PORT_MSG_QUIT = nxt_msg_last(_NXT_PORT_MSG_QUIT), + NXT_PORT_MSG_REMOVE_PID = nxt_msg_last(_NXT_PORT_MSG_REMOVE_PID), + + NXT_PORT_MSG_REQ_HEADERS = _NXT_PORT_MSG_REQ_HEADERS, + NXT_PORT_MSG_WEBSOCKET = _NXT_PORT_MSG_WEBSOCKET, + NXT_PORT_MSG_WEBSOCKET_LAST = nxt_msg_last(_NXT_PORT_MSG_WEBSOCKET), + + NXT_PORT_MSG_DATA = _NXT_PORT_MSG_DATA, + NXT_PORT_MSG_DATA_LAST = nxt_msg_last(_NXT_PORT_MSG_DATA), + + NXT_PORT_MSG_OOSM = nxt_msg_last(_NXT_PORT_MSG_OOSM), + NXT_PORT_MSG_SHM_ACK = nxt_msg_last(_NXT_PORT_MSG_SHM_ACK), } nxt_port_msg_type_t; diff --git a/src/nxt_port_memory.c b/src/nxt_port_memory.c index 33d3777e..f4d2125c 100644 --- a/src/nxt_port_memory.c +++ b/src/nxt_port_memory.c @@ -174,7 +174,7 @@ complete_buf: if (process != NULL && !nxt_queue_is_empty(&process->ports)) { port = nxt_process_port_first(process); - if (port->type == NXT_PROCESS_WORKER) { + if (port->type == NXT_PROCESS_APP) { (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_SHM_ACK, -1, 0, 0, NULL); } diff --git a/src/nxt_port_rpc.c b/src/nxt_port_rpc.c index 77e8af45..37f2d902 100644 --- a/src/nxt_port_rpc.c +++ b/src/nxt_port_rpc.c @@ -8,7 +8,7 @@ #include <nxt_port_rpc.h> -static nxt_atomic_t nxt_stream_ident = 1; +static volatile uint32_t *nxt_stream_ident; typedef struct nxt_port_rpc_reg_s nxt_port_rpc_reg_t; @@ -30,6 +30,29 @@ nxt_port_rpc_remove_from_peers(nxt_task_t *task, nxt_port_t *port, nxt_port_rpc_reg_t *reg); +nxt_int_t +nxt_port_rpc_init(void) +{ + void *p; + + if (nxt_stream_ident != NULL) { + return NXT_OK; + } + + p = nxt_mem_mmap(NULL, sizeof(*nxt_stream_ident), PROT_READ | PROT_WRITE, + MAP_ANON | MAP_SHARED, -1, 0); + + if (nxt_slow_path(p == MAP_FAILED)) { + return NXT_ERROR; + } + + nxt_stream_ident = p; + *nxt_stream_ident = 1; + + return NXT_OK; +} + + static nxt_int_t nxt_rpc_reg_test(nxt_lvlhsh_query_t *lhq, void *data) { @@ -105,8 +128,7 @@ nxt_port_rpc_register_handler_ex(nxt_task_t *task, nxt_port_t *port, nxt_assert(port->pair[0] != -1); - stream = - (uint32_t) nxt_atomic_fetch_add(&nxt_stream_ident, 1) & 0x3FFFFFFF; + stream = nxt_atomic_fetch_add(nxt_stream_ident, 1); reg = nxt_mp_zalloc(port->mem_pool, sizeof(nxt_port_rpc_reg_t) + ex_size); diff --git a/src/nxt_port_rpc.h b/src/nxt_port_rpc.h index 8011e474..c07683fb 100644 --- a/src/nxt_port_rpc.h +++ b/src/nxt_port_rpc.h @@ -11,6 +11,8 @@ typedef void (*nxt_port_rpc_handler_t)(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); +nxt_int_t nxt_port_rpc_init(void); + uint32_t nxt_port_rpc_register_handler(nxt_task_t *task, nxt_port_t *port, nxt_port_rpc_handler_t ready_handler, nxt_port_rpc_handler_t error_handler, nxt_pid_t peer, void *data); diff --git a/src/nxt_port_socket.c b/src/nxt_port_socket.c index 9c7da970..4e3eaef6 100644 --- a/src/nxt_port_socket.c +++ b/src/nxt_port_socket.c @@ -993,6 +993,12 @@ nxt_port_error_handler(nxt_task_t *task, void *obj, void *data) nxt_queue_each(msg, &port->messages, nxt_port_send_msg_t, link) { + if (msg->fd != -1 && msg->close_fd != 0) { + nxt_fd_close(msg->fd); + + msg->fd = -1; + } + for (b = msg->buf; b != NULL; b = next) { next = b->next; b->next = NULL; diff --git a/src/nxt_process.c b/src/nxt_process.c index f5959edf..c4c44d14 100644 --- a/src/nxt_process.c +++ b/src/nxt_process.c @@ -13,9 +13,39 @@ #include <signal.h> -static void nxt_process_start(nxt_task_t *task, nxt_process_t *process); -static nxt_int_t nxt_process_worker_setup(nxt_task_t *task, - nxt_process_t *process, int parentfd); +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) +#include <sys/prctl.h> +#endif + +#if (NXT_HAVE_PIVOT_ROOT) +#include <mntent.h> +#endif + +static nxt_int_t nxt_process_setup(nxt_task_t *task, nxt_process_t *process); +static nxt_int_t nxt_process_child_fixup(nxt_task_t *task, + nxt_process_t *process); +static nxt_int_t nxt_process_send_created(nxt_task_t *task, + nxt_process_t *process); +static nxt_int_t nxt_process_send_ready(nxt_task_t *task, + nxt_process_t *process); +static void nxt_process_created_ok(nxt_task_t *task, nxt_port_recv_msg_t *msg, + void *data); +static void nxt_process_created_error(nxt_task_t *task, + nxt_port_recv_msg_t *msg, void *data); + +#if (NXT_HAVE_ISOLATION_ROOTFS) +static nxt_int_t nxt_process_chroot(nxt_task_t *task, const char *path); +#endif + +#if (NXT_HAVE_PIVOT_ROOT) +static nxt_int_t nxt_process_pivot_root(nxt_task_t *task, const char *rootfs); +static nxt_int_t nxt_process_private_mount(nxt_task_t *task, + const char *rootfs); +#endif + +#if (NXT_HAVE_PIVOT_ROOT) +static int nxt_pivot_root(const char *new_root, const char *old_root); +#endif /* A cached process pid. */ nxt_pid_t nxt_pid; @@ -47,62 +77,58 @@ nxt_bool_t nxt_proc_remove_notify_matrix[NXT_PROCESS_MAX][NXT_PROCESS_MAX] = { static nxt_int_t -nxt_process_worker_setup(nxt_task_t *task, nxt_process_t *process, int parentfd) +nxt_process_child_fixup(nxt_task_t *task, nxt_process_t *process) { - pid_t rpid, pid; - ssize_t n; - nxt_int_t parent_status; nxt_process_t *p; nxt_runtime_t *rt; nxt_process_init_t *init; nxt_process_type_t ptype; - pid = getpid(); - rpid = 0; - rt = task->thread->runtime; - init = process->init; + init = nxt_process_init(process); - /* Setup the worker process. */ - - n = read(parentfd, &rpid, sizeof(rpid)); - if (nxt_slow_path(n == -1 || n != sizeof(rpid))) { - nxt_alert(task, "failed to read real pid"); - return NXT_ERROR; - } - - if (nxt_slow_path(rpid == 0)) { - nxt_alert(task, "failed to get real pid from parent"); - return NXT_ERROR; - } + nxt_pid = nxt_getpid(); - nxt_pid = rpid; + process->pid = nxt_pid; /* Clean inherited cached thread tid. */ task->thread->tid = 0; - process->pid = nxt_pid; +#if (NXT_HAVE_CLONE && NXT_HAVE_CLONE_NEWPID) + if (nxt_is_clone_flag_set(process->isolation.clone.flags, NEWPID)) { + ssize_t pidsz; + char procpid[10]; - if (nxt_pid != pid) { - nxt_debug(task, "app \"%s\" real pid %d", init->name, nxt_pid); - nxt_debug(task, "app \"%s\" isolated pid: %d", init->name, pid); - } + nxt_debug(task, "%s isolated pid is %d", process->name, nxt_pid); - n = read(parentfd, &parent_status, sizeof(parent_status)); - if (nxt_slow_path(n == -1 || n != sizeof(parent_status))) { - nxt_alert(task, "failed to read parent status"); - return NXT_ERROR; - } + pidsz = readlink("/proc/self", procpid, sizeof(procpid)); + + if (nxt_slow_path(pidsz < 0 || pidsz >= (ssize_t) sizeof(procpid))) { + nxt_alert(task, "failed to read real pid from /proc/self"); + return NXT_ERROR; + } + + procpid[pidsz] = '\0'; + + nxt_pid = (nxt_pid_t) strtol(procpid, NULL, 10); + + nxt_assert(nxt_pid > 0 && nxt_errno != ERANGE); - if (nxt_slow_path(parent_status != NXT_OK)) { - return parent_status; + process->pid = nxt_pid; + task->thread->tid = nxt_pid; + + nxt_debug(task, "%s real pid is %d", process->name, nxt_pid); } +#endif + ptype = init->type; nxt_port_reset_next_id(); nxt_event_engine_thread_adopt(task->thread->engine); + rt = task->thread->runtime; + /* Remove not ready processes. */ nxt_runtime_process_each(rt, p) { @@ -114,7 +140,7 @@ nxt_process_worker_setup(nxt_task_t *task, nxt_process_t *process, int parentfd) continue; } - if (!p->ready) { + if (p->state != NXT_PROCESS_STATE_READY) { nxt_debug(task, "remove not ready process %PI", p->pid); nxt_process_close_ports(task, p); @@ -127,12 +153,6 @@ nxt_process_worker_setup(nxt_task_t *task, nxt_process_t *process, int parentfd) } nxt_runtime_process_loop; - nxt_runtime_process_add(task, process); - - nxt_process_start(task, process); - - process->ready = 1; - return NXT_OK; } @@ -140,48 +160,36 @@ nxt_process_worker_setup(nxt_task_t *task, nxt_process_t *process, int parentfd) nxt_pid_t nxt_process_create(nxt_task_t *task, nxt_process_t *process) { - int pipefd[2]; nxt_int_t ret; nxt_pid_t pid; - nxt_process_init_t *init; - - if (nxt_slow_path(pipe(pipefd) == -1)) { - nxt_alert(task, "failed to create process pipe for passing rpid"); - return -1; - } - - init = process->init; #if (NXT_HAVE_CLONE) - pid = nxt_clone(SIGCHLD | init->isolation.clone.flags); + pid = nxt_clone(SIGCHLD | process->isolation.clone.flags); if (nxt_slow_path(pid < 0)) { - nxt_alert(task, "clone() failed while creating \"%s\" %E", - init->name, nxt_errno); - goto cleanup; + nxt_alert(task, "clone() failed for %s %E", process->name, nxt_errno); + return pid; } #else pid = fork(); if (nxt_slow_path(pid < 0)) { - nxt_alert(task, "fork() failed while creating \"%s\" %E", - init->name, nxt_errno); - goto cleanup; + nxt_alert(task, "fork() failed for %s %E", process->name, nxt_errno); + return pid; } #endif if (pid == 0) { /* Child. */ - if (nxt_slow_path(close(pipefd[1]) == -1)) { - nxt_alert(task, "failed to close writer pipe fd"); - } - - ret = nxt_process_worker_setup(task, process, pipefd[0]); + ret = nxt_process_child_fixup(task, process); if (nxt_slow_path(ret != NXT_OK)) { - exit(1); + nxt_process_quit(task, 1); + return -1; } - if (nxt_slow_path(close(pipefd[0]) == -1)) { - nxt_alert(task, "failed to close writer pipe fd"); + nxt_runtime_process_add(task, process); + + if (nxt_slow_path(nxt_process_setup(task, process) != NXT_OK)) { + nxt_process_quit(task, 1); } /* @@ -193,169 +201,686 @@ nxt_process_create(nxt_task_t *task, nxt_process_t *process) /* Parent. */ - /* - * At this point, the child process is blocked reading the - * pipe fd to get its real pid (rpid). - * - * If anything goes wrong now, we need to terminate the child - * process by sending a NXT_ERROR in the pipe. - */ - #if (NXT_HAVE_CLONE) - nxt_debug(task, "clone(\"%s\"): %PI", init->name, pid); + nxt_debug(task, "clone(%s): %PI", process->name, pid); #else - nxt_debug(task, "fork(\"%s\"): %PI", init->name, pid); + nxt_debug(task, "fork(%s): %PI", process->name, pid); #endif - if (nxt_slow_path(write(pipefd[1], &pid, sizeof(pid)) == -1)) { - nxt_alert(task, "failed to write real pid"); - goto fail; + process->pid = pid; + + nxt_runtime_process_add(task, process); + + return pid; +} + + +static nxt_int_t +nxt_process_setup(nxt_task_t *task, nxt_process_t *process) +{ + nxt_int_t ret; + nxt_port_t *port, *main_port; + nxt_thread_t *thread; + nxt_runtime_t *rt; + nxt_process_init_t *init; + nxt_event_engine_t *engine; + const nxt_event_interface_t *interface; + + init = nxt_process_init(process); + + nxt_debug(task, "%s setup", process->name); + + nxt_process_title(task, "unit: %s", process->name); + + thread = task->thread; + rt = thread->runtime; + + nxt_random_init(&thread->random); + + rt->type = init->type; + + engine = thread->engine; + + /* Update inherited main process event engine and signals processing. */ + engine->signals->sigev = init->signals; + + interface = nxt_service_get(rt->services, "engine", rt->engine); + if (nxt_slow_path(interface == NULL)) { + return NXT_ERROR; } -#if (NXT_HAVE_CLONE && NXT_HAVE_CLONE_NEWUSER) - if (NXT_CLONE_USER(init->isolation.clone.flags)) { - ret = nxt_clone_credential_map(task, pid, init->user_cred, - &init->isolation.clone); + if (nxt_event_engine_change(engine, interface, rt->batch) != NXT_OK) { + return NXT_ERROR; + } + + ret = nxt_runtime_thread_pool_create(thread, rt, rt->auxiliary_threads, + 60000 * 1000000LL); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + + nxt_port_read_close(main_port); + nxt_port_write_enable(task, main_port); + + port = nxt_process_port_first(process); + + nxt_port_write_close(port); + + nxt_port_enable(task, port, init->port_handlers); + + ret = init->setup(task, process); + + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + switch (process->state) { + + case NXT_PROCESS_STATE_CREATED: + ret = nxt_process_send_created(task, process); + break; + + case NXT_PROCESS_STATE_READY: + ret = nxt_process_send_ready(task, process); + if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + break; } + + ret = init->start(task, &process->data); + break; + + default: + nxt_assert(0); } -#endif - ret = NXT_OK; + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "%s failed to start", process->name); + } - if (nxt_slow_path(write(pipefd[1], &ret, sizeof(ret)) == -1)) { - nxt_alert(task, "failed to write status"); - goto fail; + return ret; +} + + +static nxt_int_t +nxt_process_send_created(nxt_task_t *task, nxt_process_t *process) +{ + uint32_t stream; + nxt_int_t ret; + nxt_port_t *my_port, *main_port; + nxt_runtime_t *rt; + + nxt_assert(process->state == NXT_PROCESS_STATE_CREATED); + + rt = task->thread->runtime; + + my_port = nxt_process_port_first(process); + main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + + nxt_assert(my_port != NULL && main_port != NULL); + + stream = nxt_port_rpc_register_handler(task, my_port, + nxt_process_created_ok, + nxt_process_created_error, + main_port->pid, process); + + if (nxt_slow_path(stream == 0)) { + return NXT_ERROR; } - process->pid = pid; + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_PROCESS_CREATED, + -1, stream, my_port->id, NULL); - nxt_runtime_process_add(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "%s failed to send CREATED message", process->name); + nxt_port_rpc_cancel(task, my_port, stream); + return NXT_ERROR; + } + + nxt_debug(task, "%s created", process->name); + + return NXT_OK; +} - goto cleanup; + +static void +nxt_process_created_ok(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) +{ + nxt_int_t ret; + nxt_process_t *process; + nxt_process_init_t *init; + + process = data; + init = nxt_process_init(process); + + ret = nxt_process_apply_creds(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + + nxt_log(task, NXT_LOG_INFO, "%s started", process->name); + + ret = init->start(task, &process->data); fail: - ret = NXT_ERROR; + nxt_process_quit(task, ret == NXT_OK ? 0 : 1); +} + + +static void +nxt_process_created_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, + void *data) +{ + nxt_process_t *process; + nxt_process_init_t *init; + + process = data; + init = nxt_process_init(process); + + nxt_alert(task, "%s failed to start", init->name); + + nxt_process_quit(task, 1); +} + + +nxt_int_t +nxt_process_core_setup(nxt_task_t *task, nxt_process_t *process) +{ + nxt_int_t ret; - if (nxt_slow_path(write(pipefd[1], &ret, sizeof(ret)) == -1)) { - nxt_alert(task, "failed to write status"); + ret = nxt_process_apply_creds(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; } - waitpid(pid, NULL, 0); + process->state = NXT_PROCESS_STATE_READY; - pid = -1; + return NXT_OK; +} -cleanup: - if (nxt_slow_path(close(pipefd[0]) != 0)) { - nxt_alert(task, "failed to close pipe: %E", nxt_errno); +#if (NXT_HAVE_CLONE_NEWUSER) + +nxt_int_t +nxt_process_vldt_isolation_creds(nxt_task_t *task, nxt_process_t *process) +{ + nxt_int_t ret; + nxt_clone_t *clone; + nxt_credential_t *creds; + + clone = &process->isolation.clone; + creds = process->user_cred; + + if (clone->uidmap.size == 0 && clone->gidmap.size == 0) { + return NXT_OK; } - if (nxt_slow_path(close(pipefd[1]) != 0)) { - nxt_alert(task, "failed to close pipe: %E", nxt_errno); + if (!nxt_is_clone_flag_set(clone->flags, NEWUSER)) { + if (nxt_slow_path(clone->uidmap.size > 0)) { + nxt_log(task, NXT_LOG_ERR, "\"uidmap\" is set but " + "\"isolation.namespaces.credential\" is false or unset"); + + return NXT_ERROR; + } + + if (nxt_slow_path(clone->gidmap.size > 0)) { + nxt_log(task, NXT_LOG_ERR, "\"gidmap\" is set but " + "\"isolation.namespaces.credential\" is false or unset"); + + return NXT_ERROR; + } + + return NXT_OK; } - return pid; + ret = nxt_clone_vldt_credential_uidmap(task, &clone->uidmap, creds); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + return nxt_clone_vldt_credential_gidmap(task, &clone->gidmap, creds); } +#endif -static void -nxt_process_start(nxt_task_t *task, nxt_process_t *process) + +nxt_int_t +nxt_process_creds_set(nxt_task_t *task, nxt_process_t *process, nxt_str_t *user, + nxt_str_t *group) { - nxt_int_t ret, cap_setid; - nxt_port_t *port, *main_port; - nxt_thread_t *thread; - nxt_runtime_t *rt; - nxt_process_init_t *init; - nxt_event_engine_t *engine; - const nxt_event_interface_t *interface; + char *str; - init = process->init; + process->user_cred = nxt_mp_zalloc(process->mem_pool, + sizeof(nxt_credential_t)); - nxt_log(task, NXT_LOG_INFO, "%s started", init->name); + if (nxt_slow_path(process->user_cred == NULL)) { + return NXT_ERROR; + } - nxt_process_title(task, "unit: %s", init->name); + str = nxt_mp_zalloc(process->mem_pool, user->length + 1); + if (nxt_slow_path(str == NULL)) { + return NXT_ERROR; + } - thread = task->thread; - rt = thread->runtime; + nxt_memcpy(str, user->start, user->length); + str[user->length] = '\0'; - nxt_random_init(&thread->random); + process->user_cred->user = str; + + if (group->start != NULL) { + str = nxt_mp_zalloc(process->mem_pool, group->length + 1); + if (nxt_slow_path(str == NULL)) { + return NXT_ERROR; + } + + nxt_memcpy(str, group->start, group->length); + str[group->length] = '\0'; + + } else { + str = NULL; + } + + return nxt_credential_get(task, process->mem_pool, process->user_cred, str); +} + + +nxt_int_t +nxt_process_apply_creds(nxt_task_t *task, nxt_process_t *process) +{ + nxt_int_t ret, cap_setid; + nxt_runtime_t *rt; + + rt = task->thread->runtime; cap_setid = rt->capabilities.setid; -#if (NXT_HAVE_CLONE_NEWUSER) - if (!cap_setid && NXT_CLONE_USER(init->isolation.clone.flags)) { +#if (NXT_HAVE_CLONE && NXT_HAVE_CLONE_NEWUSER) + if (!cap_setid + && nxt_is_clone_flag_set(process->isolation.clone.flags, NEWUSER)) { cap_setid = 1; } #endif if (cap_setid) { - ret = nxt_credential_setgids(task, init->user_cred); + ret = nxt_credential_setgids(task, process->user_cred); if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + return NXT_ERROR; } - ret = nxt_credential_setuid(task, init->user_cred); + ret = nxt_credential_setuid(task, process->user_cred); if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + return NXT_ERROR; } } - rt->type = init->type; +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) + if (nxt_slow_path(process->isolation.new_privs == 0 + && prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0)) + { + nxt_alert(task, "failed to set no_new_privs %E", nxt_errno); + return NXT_ERROR; + } +#endif - engine = thread->engine; + return NXT_OK; +} - /* Update inherited main process event engine and signals processing. */ - engine->signals->sigev = init->signals; - interface = nxt_service_get(rt->services, "engine", rt->engine); - if (nxt_slow_path(interface == NULL)) { +#if (NXT_HAVE_ISOLATION_ROOTFS) + + +#if (NXT_HAVE_PIVOT_ROOT) && (NXT_HAVE_CLONE_NEWNS) + + +nxt_int_t +nxt_process_change_root(nxt_task_t *task, nxt_process_t *process) +{ + char *rootfs; + nxt_int_t ret; + + rootfs = (char *) process->isolation.rootfs; + + nxt_debug(task, "change root: %s", rootfs); + + if (NXT_CLONE_MNT(process->isolation.clone.flags)) { + ret = nxt_process_pivot_root(task, rootfs); + } else { + ret = nxt_process_chroot(task, rootfs); + } + + if (nxt_fast_path(ret == NXT_OK)) { + if (nxt_slow_path(chdir("/") < 0)) { + nxt_alert(task, "chdir(\"/\") %E", nxt_errno); + return NXT_ERROR; + } + } + + return ret; +} + + +#else + + +nxt_int_t +nxt_process_change_root(nxt_task_t *task, nxt_process_t *process) +{ + char *rootfs; + + rootfs = (char *) process->isolation.rootfs; + + nxt_debug(task, "change root: %s", rootfs); + + if (nxt_fast_path(nxt_process_chroot(task, rootfs) == NXT_OK)) { + if (nxt_slow_path(chdir("/") < 0)) { + nxt_alert(task, "chdir(\"/\") %E", nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; + } + + return NXT_ERROR; +} + + +#endif + + +#endif + + +#if (NXT_HAVE_ISOLATION_ROOTFS) + +static nxt_int_t +nxt_process_chroot(nxt_task_t *task, const char *path) +{ + if (nxt_slow_path(chroot(path) < 0)) { + nxt_alert(task, "chroot(%s) %E", path, nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; +} + + +void +nxt_process_unmount_all(nxt_task_t *task, nxt_process_t *process) +{ + size_t i, n; + nxt_array_t *mounts; + nxt_fs_mount_t *mnt; + + nxt_debug(task, "unmount all (%s)", process->name); + + mounts = process->isolation.mounts; + n = mounts->nelts; + mnt = mounts->elts; + + for (i = 0; i < n; i++) { + nxt_fs_unmount(mnt[i].dst); + } +} + +#endif + + +#if (NXT_HAVE_PIVOT_ROOT) && (NXT_HAVE_CLONE_NEWNS) + +/* + * pivot_root(2) can only be safely used with containers, otherwise it can + * umount(2) the global root filesystem and screw up the machine. + */ + +static nxt_int_t +nxt_process_pivot_root(nxt_task_t *task, const char *path) +{ + /* + * This implementation makes use of a kernel trick that works for ages + * and now documented in Linux kernel 5. + * https://lore.kernel.org/linux-man/87r24piwhm.fsf@x220.int.ebiederm.org/T/ + */ + + if (nxt_slow_path(mount("", "/", "", MS_SLAVE|MS_REC, "") != 0)) { + nxt_alert(task, "failed to make / a slave mount %E", nxt_errno); + return NXT_ERROR; + } + + if (nxt_slow_path(nxt_process_private_mount(task, path) != NXT_OK)) { + return NXT_ERROR; + } + + if (nxt_slow_path(mount(path, path, "bind", MS_BIND|MS_REC, "") != 0)) { + nxt_alert(task, "error bind mounting rootfs %E", nxt_errno); + return NXT_ERROR; + } + + if (nxt_slow_path(chdir(path) != 0)) { + nxt_alert(task, "failed to chdir(%s) %E", path, nxt_errno); + return NXT_ERROR; + } + + if (nxt_slow_path(nxt_pivot_root(".", ".") != 0)) { + nxt_alert(task, "failed to pivot_root %E", nxt_errno); + return NXT_ERROR; + } + + /* + * Make oldroot a slave mount to avoid unmounts getting propagated to the + * host. + */ + if (nxt_slow_path(mount("", ".", "", MS_SLAVE | MS_REC, NULL) != 0)) { + nxt_alert(task, "failed to bind mount rootfs %E", nxt_errno); + return NXT_ERROR; + } + + if (nxt_slow_path(umount2(".", MNT_DETACH) != 0)) { + nxt_alert(task, "failed to umount old root directory %E", nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; +} + + +static nxt_int_t +nxt_process_private_mount(nxt_task_t *task, const char *rootfs) +{ + char *parent_mnt; + FILE *procfile; + u_char **mounts; + size_t len; + uint8_t *shared; + nxt_int_t ret, index, nmounts; + struct mntent *ent; + + static const char *mount_path = "/proc/self/mounts"; + + ret = NXT_ERROR; + ent = NULL; + shared = NULL; + procfile = NULL; + parent_mnt = NULL; + + nmounts = 256; + + mounts = nxt_malloc(nmounts * sizeof(uintptr_t)); + if (nxt_slow_path(mounts == NULL)) { goto fail; } - if (nxt_event_engine_change(engine, interface, rt->batch) != NXT_OK) { + shared = nxt_malloc(nmounts); + if (nxt_slow_path(shared == NULL)) { goto fail; } - ret = nxt_runtime_thread_pool_create(thread, rt, rt->auxiliary_threads, - 60000 * 1000000LL); - if (nxt_slow_path(ret != NXT_OK)) { + procfile = setmntent(mount_path, "r"); + if (nxt_slow_path(procfile == NULL)) { + nxt_alert(task, "failed to open %s %E", mount_path, nxt_errno); + goto fail; } - main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + index = 0; - nxt_port_read_close(main_port); - nxt_port_write_enable(task, main_port); +again: - port = nxt_process_port_first(process); + for ( ; index < nmounts; index++) { + ent = getmntent(procfile); + if (ent == NULL) { + nmounts = index; + break; + } - nxt_port_write_close(port); + mounts[index] = (u_char *) strdup(ent->mnt_dir); + shared[index] = hasmntopt(ent, "shared") != NULL; + } - ret = init->start(task, init->data); + if (ent != NULL) { + /* there are still entries to be read */ - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + nmounts *= 2; + mounts = nxt_realloc(mounts, nmounts); + if (nxt_slow_path(mounts == NULL)) { + goto fail; + } + + shared = nxt_realloc(shared, nmounts); + if (nxt_slow_path(shared == NULL)) { + goto fail; + } + + goto again; } - nxt_port_enable(task, port, init->port_handlers); + for (index = 0; index < nmounts; index++) { + if (nxt_strcmp(mounts[index], rootfs) == 0) { + parent_mnt = (char *) rootfs; + break; + } + } - ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_PROCESS_READY, - -1, init->stream, 0, NULL); + if (parent_mnt == NULL) { + len = nxt_strlen(rootfs); - if (nxt_slow_path(ret != NXT_OK)) { - nxt_log(task, NXT_LOG_ERR, "failed to send READY message to main"); + parent_mnt = nxt_malloc(len + 1); + if (parent_mnt == NULL) { + goto fail; + } - goto fail; + nxt_memcpy(parent_mnt, rootfs, len); + parent_mnt[len] = '\0'; + + if (parent_mnt[len - 1] == '/') { + parent_mnt[len - 1] = '\0'; + len--; + } + + for ( ;; ) { + for (index = 0; index < nmounts; index++) { + if (nxt_strcmp(mounts[index], parent_mnt) == 0) { + goto found; + } + } + + if (len == 1 && parent_mnt[0] == '/') { + nxt_alert(task, "parent mount not found"); + goto fail; + } + + /* parent dir */ + while (parent_mnt[len - 1] != '/' && len > 0) { + len--; + } + + if (nxt_slow_path(len == 0)) { + nxt_alert(task, "parent mount not found"); + goto fail; + } + + if (len == 1) { + parent_mnt[len] = '\0'; /* / */ + } else { + parent_mnt[len - 1] = '\0'; /* /<path> */ + } + } } - return; +found: + + if (shared[index]) { + if (nxt_slow_path(mount("", parent_mnt, "", MS_PRIVATE, "") != 0)) { + nxt_alert(task, "mount(\"\", \"%s\", MS_PRIVATE) %E", parent_mnt, + nxt_errno); + + goto fail; + } + } + + ret = NXT_OK; fail: - exit(1); + if (procfile != NULL) { + endmntent(procfile); + } + + if (mounts != NULL) { + for (index = 0; index < nmounts; index++) { + nxt_free(mounts[index]); + } + + nxt_free(mounts); + } + + if (shared != NULL) { + nxt_free(shared); + } + + if (parent_mnt != NULL && parent_mnt != rootfs) { + nxt_free(parent_mnt); + } + + return ret; +} + + +static int +nxt_pivot_root(const char *new_root, const char *old_root) +{ + return syscall(__NR_pivot_root, new_root, old_root); +} + +#endif + + +static nxt_int_t +nxt_process_send_ready(nxt_task_t *task, nxt_process_t *process) +{ + nxt_int_t ret; + nxt_port_t *main_port; + nxt_runtime_t *rt; + + rt = task->thread->runtime; + + main_port = rt->port_by_type[NXT_PROCESS_MAIN]; + + nxt_assert(main_port != NULL); + + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_PROCESS_READY, + -1, process->stream, 0, NULL); + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_alert(task, "%s failed to send READY message", process->name); + return NXT_ERROR; + } + + nxt_debug(task, "%s sent ready", process->name); + + return NXT_OK; } @@ -625,3 +1150,50 @@ nxt_process_connected_port_find(nxt_process_t *process, nxt_port_t *port) return res; } + + +void +nxt_process_quit(nxt_task_t *task, nxt_uint_t exit_status) +{ + nxt_uint_t n; + nxt_queue_t *listen; + nxt_runtime_t *rt; + nxt_queue_link_t *link, *next; + nxt_listen_event_t *lev; + nxt_listen_socket_t *ls; + + rt = task->thread->runtime; + + nxt_debug(task, "close listen connections"); + + listen = &task->thread->engine->listen_connections; + + for (link = nxt_queue_first(listen); + link != nxt_queue_tail(listen); + link = next) + { + next = nxt_queue_next(link); + lev = nxt_queue_link_data(link, nxt_listen_event_t, link); + nxt_queue_remove(link); + + nxt_fd_event_close(task->thread->engine, &lev->socket); + } + + if (rt->listen_sockets != NULL) { + + ls = rt->listen_sockets->elts; + n = rt->listen_sockets->nelts; + + while (n != 0) { + nxt_socket_close(task, ls->socket); + ls->socket = -1; + + ls++; + n--; + } + + rt->listen_sockets->nelts = 0; + } + + nxt_runtime_quit(task, exit_status); +} diff --git a/src/nxt_process.h b/src/nxt_process.h index 3f7155c8..d3311722 100644 --- a/src/nxt_process.h +++ b/src/nxt_process.h @@ -8,68 +8,132 @@ #define _NXT_PROCESS_H_INCLUDED_ #if (NXT_HAVE_CLONE) +#include <unistd.h> #include <nxt_clone.h> #endif +#if (NXT_HAVE_CLONE) +/* + * Old glibc wrapper for getpid(2) returns a cached pid invalidated only by + * fork(2) calls. As we use clone(2) for container, it returns the wrong pid. + */ +#define nxt_getpid() \ + syscall(__NR_getpid) +#else +#define nxt_getpid() \ + getpid() +#endif + typedef pid_t nxt_pid_t; -typedef struct nxt_process_init_s nxt_process_init_t; -typedef nxt_int_t (*nxt_process_start_t)(nxt_task_t *task, void *data); -typedef nxt_int_t (*nxt_process_restart_t)(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init); +typedef struct nxt_common_app_conf_s nxt_common_app_conf_t; -struct nxt_process_init_s { - nxt_mp_t *mem_pool; - nxt_process_start_t start; - const char *name; - nxt_credential_t *user_cred; - const nxt_port_handlers_t *port_handlers; - const nxt_sig_event_t *signals; - - nxt_process_type_t type; +typedef struct { + nxt_runtime_t *rt; +} nxt_discovery_init_t; - void *data; - uint32_t stream; - union { -#if (NXT_HAVE_CLONE) - nxt_clone_t clone; +typedef struct { + nxt_str_t conf; +#if (NXT_TLS) + nxt_array_t *certs; #endif - } isolation; -}; +} nxt_controller_init_t; + + +typedef union { + void *discovery; + nxt_controller_init_t controller; + void *router; + nxt_common_app_conf_t *app; +} nxt_process_data_t; + + +typedef enum { + NXT_PROCESS_STATE_CREATING = 0, + NXT_PROCESS_STATE_CREATED, + NXT_PROCESS_STATE_READY, +} nxt_process_state_t; typedef struct nxt_port_mmap_s nxt_port_mmap_t; -typedef struct nxt_port_mmaps_s nxt_port_mmaps_t; -struct nxt_port_mmaps_s { + +typedef struct { nxt_thread_mutex_t mutex; uint32_t size; uint32_t cap; nxt_port_mmap_t *elts; -}; +} nxt_port_mmaps_t; + +typedef struct { + u_char *rootfs; + nxt_array_t *mounts; /* of nxt_mount_t */ + +#if (NXT_HAVE_CLONE) + nxt_clone_t clone; +#endif + +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) + uint8_t new_privs; /* 1 bit */ +#endif +} nxt_process_isolation_t; typedef struct { - nxt_pid_t pid; - nxt_queue_t ports; /* of nxt_port_t */ - nxt_bool_t ready; - nxt_bool_t registered; - nxt_int_t use_count; + nxt_pid_t pid; + const char *name; + nxt_queue_t ports; /* of nxt_port_t */ + nxt_process_state_t state; + nxt_bool_t registered; + nxt_int_t use_count; + + nxt_port_mmaps_t incoming; + nxt_port_mmaps_t outgoing; + + nxt_thread_mutex_t cp_mutex; + nxt_lvlhsh_t connected_ports; /* of nxt_port_t */ + + uint32_t stream; - nxt_process_init_t *init; + nxt_mp_t *mem_pool; + nxt_credential_t *user_cred; - nxt_port_mmaps_t incoming; - nxt_port_mmaps_t outgoing; + nxt_process_data_t data; - nxt_thread_mutex_t cp_mutex; - nxt_lvlhsh_t connected_ports; /* of nxt_port_t */ + nxt_process_isolation_t isolation; } nxt_process_t; +typedef nxt_int_t (*nxt_process_prefork_t)(nxt_task_t *task, + nxt_process_t *process, nxt_mp_t *mp); +typedef nxt_int_t (*nxt_process_postfork_t)(nxt_task_t *task, + nxt_process_t *process, nxt_mp_t *mp); +typedef nxt_int_t (*nxt_process_setup_t)(nxt_task_t *task, + nxt_process_t *process); +typedef nxt_int_t (*nxt_process_start_t)(nxt_task_t *task, + nxt_process_data_t *data); + + +typedef struct { + const char *name; + nxt_process_type_t type; + + nxt_process_prefork_t prefork; + + nxt_process_setup_t setup; + nxt_process_start_t start; + + uint8_t restart; /* 1-bit */ + + const nxt_port_handlers_t *port_handlers; + const nxt_sig_event_t *signals; +} nxt_process_init_t; + + extern nxt_bool_t nxt_proc_conn_matrix[NXT_PROCESS_MAX][NXT_PROCESS_MAX]; extern nxt_bool_t nxt_proc_remove_notify_matrix[NXT_PROCESS_MAX][NXT_PROCESS_MAX]; @@ -84,6 +148,9 @@ NXT_EXPORT void nxt_nanosleep(nxt_nsec_t ns); NXT_EXPORT void nxt_process_arguments(nxt_task_t *task, char **orig_argv, char ***orig_envp); +#define nxt_process_init(process) \ + (nxt_pointer_to(process, sizeof(nxt_process_t))) + #define nxt_process_port_remove(port) \ nxt_queue_remove(&port->link) @@ -113,11 +180,24 @@ void nxt_process_connected_port_remove(nxt_process_t *process, nxt_port_t *nxt_process_connected_port_find(nxt_process_t *process, nxt_port_t *port); -void nxt_worker_process_quit_handler(nxt_task_t *task, - nxt_port_recv_msg_t *msg); +void nxt_process_quit(nxt_task_t *task, nxt_uint_t exit_status); +void nxt_signal_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg); + +nxt_int_t nxt_process_core_setup(nxt_task_t *task, nxt_process_t *process); +nxt_int_t nxt_process_creds_set(nxt_task_t *task, nxt_process_t *process, + nxt_str_t *user, nxt_str_t *group); +nxt_int_t nxt_process_apply_creds(nxt_task_t *task, nxt_process_t *process); + +#if (NXT_HAVE_CLONE_NEWUSER) +nxt_int_t nxt_process_vldt_isolation_creds(nxt_task_t *task, + nxt_process_t *process); +#endif -void nxt_init_destroy(nxt_runtime_t *rt, nxt_process_init_t *init); +nxt_int_t nxt_process_change_root(nxt_task_t *task, nxt_process_t *process); +#if (NXT_HAVE_ISOLATION_ROOTFS) +void nxt_process_unmount_all(nxt_task_t *task, nxt_process_t *process); +#endif #if (NXT_HAVE_SETPROCTITLE) diff --git a/src/nxt_process_type.h b/src/nxt_process_type.h index 5ff06d63..14deda19 100644 --- a/src/nxt_process_type.h +++ b/src/nxt_process_type.h @@ -13,7 +13,7 @@ typedef enum { NXT_PROCESS_DISCOVERY, NXT_PROCESS_CONTROLLER, NXT_PROCESS_ROUTER, - NXT_PROCESS_WORKER, + NXT_PROCESS_APP, NXT_PROCESS_MAX, } nxt_process_type_t; diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 14211f3f..b9033a75 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -18,6 +18,7 @@ #include <nxt_unit_field.h> #include <nxt_unit_request.h> #include <nxt_unit_response.h> +#include <nxt_python_mounts.h> /* * According to "PEP 3333 / A Note On String Types" @@ -38,11 +39,17 @@ */ +#define _NXT_PYTHON_MOUNTS(major, minor) \ + nxt_python ## major ## minor ## _mounts + +#define NXT_PYTHON_MOUNTS(major, minor) _NXT_PYTHON_MOUNTS(major, minor) + #if PY_MAJOR_VERSION == 3 #define NXT_PYTHON_BYTES_TYPE "bytestring" #define PyString_FromStringAndSize(str, size) \ PyUnicode_DecodeLatin1((str), (size), "strict") + #else #define NXT_PYTHON_BYTES_TYPE "string" @@ -65,7 +72,8 @@ typedef struct { PyObject_HEAD } nxt_py_error_t; -static nxt_int_t nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static nxt_int_t nxt_python_start(nxt_task_t *task, + nxt_process_data_t *data); static nxt_int_t nxt_python_init_strings(void); static void nxt_python_request_handler(nxt_unit_request_info_t *req); static void nxt_python_atexit(void); @@ -115,8 +123,10 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("python"), PY_VERSION, + NXT_PYTHON_MOUNTS(PY_MAJOR_VERSION, PY_MINOR_VERSION), + nxt_nitems(NXT_PYTHON_MOUNTS(PY_MAJOR_VERSION, PY_MINOR_VERSION)), NULL, - nxt_python_init, + nxt_python_start, }; @@ -211,7 +221,7 @@ static nxt_python_string_t nxt_python_strings[] = { static nxt_int_t -nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) { int rc; char *nxt_py_module; @@ -219,6 +229,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) PyObject *obj, *pypath, *module; nxt_unit_ctx_t *unit_ctx; nxt_unit_init_t python_init; + nxt_common_app_conf_t *app_conf; nxt_python_app_conf_t *c; #if PY_MAJOR_VERSION == 3 char *path; @@ -229,7 +240,8 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) static const char bin_python[] = "/bin/python"; #endif - c = &conf->u.python; + app_conf = data->app; + c = &app_conf->u.python; if (c->module.length == 0) { nxt_alert(task, "python module is empty"); @@ -410,7 +422,7 @@ nxt_python_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_unit_default_init(task, &python_init); python_init.callbacks.request_handler = nxt_python_request_handler; - python_init.shm_limit = conf->shm_limit; + python_init.shm_limit = data->app->shm_limit; unit_ctx = nxt_unit_init(&python_init); if (nxt_slow_path(unit_ctx == NULL)) { diff --git a/src/nxt_router.c b/src/nxt_router.c index 93b750a0..788199c7 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -27,6 +27,7 @@ typedef struct { uint32_t requests; nxt_conf_value_t *limits_value; nxt_conf_value_t *processes_value; + nxt_conf_value_t *targets_value; } nxt_router_app_conf_t; @@ -74,6 +75,9 @@ struct nxt_port_select_state_s { typedef struct nxt_port_select_state_s nxt_port_select_state_t; +static nxt_int_t nxt_router_prefork(nxt_task_t *task, nxt_process_t *process, + nxt_mp_t *mp); +static nxt_int_t nxt_router_start(nxt_task_t *task, nxt_process_data_t *data); static void nxt_router_greet_controller(nxt_task_t *task, nxt_port_t *controller_port); @@ -267,8 +271,8 @@ static const nxt_str_t *nxt_app_msg_prefix[] = { }; -nxt_port_handlers_t nxt_router_process_port_handlers = { - .quit = nxt_worker_process_quit_handler, +static const nxt_port_handlers_t nxt_router_process_port_handlers = { + .quit = nxt_signal_quit_handler, .new_port = nxt_router_new_port_handler, .change_file = nxt_port_change_log_file_handler, .mmap = nxt_port_mmap_handler, @@ -281,8 +285,29 @@ nxt_port_handlers_t nxt_router_process_port_handlers = { }; -nxt_int_t -nxt_router_start(nxt_task_t *task, void *data) +const nxt_process_init_t nxt_router_process = { + .name = "router", + .type = NXT_PROCESS_ROUTER, + .prefork = nxt_router_prefork, + .restart = 1, + .setup = nxt_process_core_setup, + .start = nxt_router_start, + .port_handlers = &nxt_router_process_port_handlers, + .signals = nxt_process_signals, +}; + + +static nxt_int_t +nxt_router_prefork(nxt_task_t *task, nxt_process_t *process, nxt_mp_t *mp) +{ + nxt_runtime_stop_app_processes(task, task->thread->runtime); + + return NXT_OK; +} + + +static nxt_int_t +nxt_router_start(nxt_task_t *task, nxt_process_data_t *data) { nxt_int_t ret; nxt_port_t *controller_port; @@ -291,6 +316,8 @@ nxt_router_start(nxt_task_t *task, void *data) rt = task->thread->runtime; + nxt_log(task, NXT_LOG_INFO, "router started"); + #if (NXT_TLS) rt->tls = nxt_service_get(rt->services, "SSL/TLS", "OpenSSL"); if (nxt_slow_path(rt->tls == NULL)) { @@ -381,8 +408,8 @@ nxt_router_start_app_process_handler(nxt_task_t *task, nxt_port_t *port, goto failed; } - ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_START_WORKER, -1, - stream, port->id, b); + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_START_PROCESS, + -1, stream, port->id, b); if (nxt_slow_path(ret != NXT_OK)) { nxt_port_rpc_cancel(task, port, stream); @@ -861,7 +888,7 @@ nxt_router_new_port_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) } if (msg->u.new_port == NULL - || msg->u.new_port->type != NXT_PROCESS_WORKER) + || msg->u.new_port->type != NXT_PROCESS_APP) { msg->port_msg.type = _NXT_PORT_MSG_RPC_ERROR; } @@ -943,8 +970,10 @@ nxt_router_remove_pid_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) nxt_queue_each(engine, &nxt_router->engines, nxt_event_engine_t, link0) { - nxt_port_post(task, engine->port, nxt_router_app_process_remove_pid, - msg->u.data); + if (nxt_fast_path(engine->port != NULL)) { + nxt_port_post(task, engine->port, nxt_router_app_process_remove_pid, + msg->u.data); + } } nxt_queue_loop; @@ -1272,6 +1301,12 @@ static nxt_conf_map_t nxt_router_app_conf[] = { NXT_CONF_MAP_PTR, offsetof(nxt_router_app_conf_t, processes_value), }, + + { + nxt_string("targets"), + NXT_CONF_MAP_PTR, + offsetof(nxt_router_app_conf_t, targets_value), + }, }; @@ -1423,12 +1458,13 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, { u_char *p; size_t size; - nxt_mp_t *mp; - uint32_t next; + nxt_mp_t *mp, *app_mp; + uint32_t next, next_target; nxt_int_t ret; - nxt_str_t name, path; + nxt_str_t name, path, target; nxt_app_t *app, *prev; - nxt_str_t *t; + nxt_str_t *t, *s, *targets; + nxt_uint_t n, i; nxt_router_t *router; nxt_app_joint_t *app_joint; nxt_conf_value_t *conf, *http, *value, *websocket; @@ -1501,13 +1537,20 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, size = nxt_conf_json_length(application, NULL); - app = nxt_malloc(sizeof(nxt_app_t) + name.length + size); - if (app == NULL) { + app_mp = nxt_mp_create(4096, 128, 1024, 64); + if (nxt_slow_path(app_mp == NULL)) { goto fail; } + app = nxt_mp_get(app_mp, sizeof(nxt_app_t) + name.length + size); + if (app == NULL) { + goto app_fail; + } + nxt_memzero(app, sizeof(nxt_app_t)); + app->mem_pool = app_mp; + app->name.start = nxt_pointer_to(app, sizeof(nxt_app_t)); app->conf.start = nxt_pointer_to(app, sizeof(nxt_app_t) + name.length); @@ -1522,7 +1565,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, prev = nxt_router_app_find(&router->apps, &name); if (prev != NULL && nxt_strstr_eq(&app->conf, &prev->conf)) { - nxt_free(app); + nxt_mp_destroy(app_mp); nxt_queue_remove(&prev->link); nxt_queue_insert_tail(&tmcf->previous, &prev->link); @@ -1538,6 +1581,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, apcf.requests = 0; apcf.limits_value = NULL; apcf.processes_value = NULL; + apcf.targets_value = NULL; app_joint = nxt_malloc(sizeof(nxt_app_joint_t)); if (nxt_slow_path(app_joint == NULL)) { @@ -1587,6 +1631,30 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, apcf.spare_processes = apcf.processes; } + if (apcf.targets_value != NULL) { + n = nxt_conf_object_members_count(apcf.targets_value); + + targets = nxt_mp_get(app_mp, sizeof(nxt_str_t) * n); + if (nxt_slow_path(targets == NULL)) { + goto app_fail; + } + + next_target = 0; + + for (i = 0; i < n; i++) { + (void) nxt_conf_next_object_member(apcf.targets_value, + &target, &next_target); + + s = nxt_str_dup(app_mp, &targets[i], &target); + if (nxt_slow_path(s == NULL)) { + goto app_fail; + } + } + + } else { + targets = NULL; + } + nxt_debug(task, "application type: %V", &apcf.type); nxt_debug(task, "application processes: %D", apcf.processes); nxt_debug(task, "application request timeout: %M", apcf.timeout); @@ -1628,6 +1696,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, app->max_pending_responses = 2; app->max_requests = apcf.requests; + app->targets = targets; + engine = task->thread->engine; app->engine = engine; @@ -1793,6 +1863,11 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } } + ret = nxt_http_routes_resolve(task, tmcf); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + value = nxt_conf_get_path(conf, &access_log_path); if (value != NULL) { @@ -1827,8 +1902,6 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, tmcf->router_conf->access_log = access_log; } - nxt_http_routes_resolve(task, tmcf); - nxt_queue_add(&tmcf->deleting, &router->sockets); nxt_queue_init(&router->sockets); @@ -1836,7 +1909,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, app_fail: - nxt_free(app); + nxt_mp_destroy(app_mp); fail: @@ -1844,7 +1917,7 @@ fail: nxt_queue_remove(&app->link); nxt_thread_mutex_destroy(&app->mutex); - nxt_free(app); + nxt_mp_destroy(app->mem_pool); } nxt_queue_loop; @@ -2353,8 +2426,8 @@ nxt_router_app_rpc_create(nxt_task_t *task, goto fail; } - ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_START_WORKER, -1, - stream, router_port->id, b); + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_START_PROCESS, + -1, stream, router_port->id, b); if (nxt_slow_path(ret != NXT_OK)) { nxt_port_rpc_cancel(task, router_port, stream); @@ -4535,7 +4608,7 @@ nxt_router_free_app(nxt_task_t *task, void *obj, void *data) nxt_assert(nxt_queue_is_empty(&app->idle_ports)); nxt_thread_mutex_destroy(&app->mutex); - nxt_free(app); + nxt_mp_destroy(app->mem_pool); app_joint->app = NULL; @@ -4989,6 +5062,8 @@ nxt_router_prepare_msg(nxt_task_t *task, nxt_http_request_t *r, req = (nxt_unit_request_t *) out->mem.free; out->mem.free += req_size; + req->app_target = r->app_target; + req->content_length = content_length; p = (u_char *) (req->fields + fields_count); diff --git a/src/nxt_router.h b/src/nxt_router.h index 08142ce3..6004a459 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -133,8 +133,11 @@ struct nxt_app_s { nxt_nsec_t res_timeout; nxt_msec_t idle_timeout; + nxt_str_t *targets; + nxt_app_type_t type:8; + nxt_mp_t *mem_pool; nxt_queue_link_t link; nxt_str_t conf; diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index bcd156ee..5aa061dd 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -21,6 +21,7 @@ static nxt_int_t nxt_runtime_thread_pools(nxt_thread_t *thr, nxt_runtime_t *rt); static void nxt_runtime_start(nxt_task_t *task, void *obj, void *data); static void nxt_runtime_initial_start(nxt_task_t *task, nxt_uint_t status); static void nxt_runtime_close_idle_connections(nxt_event_engine_t *engine); +static void nxt_runtime_stop_all_processes(nxt_task_t *task, nxt_runtime_t *rt); static void nxt_runtime_exit(nxt_task_t *task, void *obj, void *data); static nxt_int_t nxt_runtime_event_engine_change(nxt_task_t *task, nxt_runtime_t *rt); @@ -83,6 +84,7 @@ nxt_runtime_create(nxt_task_t *task) lang->version = (u_char *) ""; lang->file = NULL; lang->module = &nxt_external_module; + lang->mounts = NULL; listen_sockets = nxt_array_create(mp, 1, sizeof(nxt_listen_socket_t)); if (nxt_slow_path(listen_sockets == NULL)) { @@ -118,6 +120,10 @@ nxt_runtime_create(nxt_task_t *task) goto fail; } + if (nxt_port_rpc_init() != NXT_OK) { + goto fail; + } + nxt_work_queue_add(&task->thread->engine->fast_work_queue, nxt_runtime_start, task, rt, NULL); @@ -434,7 +440,7 @@ nxt_runtime_quit(nxt_task_t *task, nxt_uint_t status) } if (rt->type == NXT_PROCESS_MAIN) { - nxt_main_stop_all_processes(task, rt); + nxt_runtime_stop_all_processes(task, rt); done = 0; } } @@ -474,6 +480,50 @@ nxt_runtime_close_idle_connections(nxt_event_engine_t *engine) } +void +nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt) +{ + nxt_port_t *port; + nxt_process_t *process; + nxt_process_init_t *init; + + nxt_runtime_process_each(rt, process) { + + init = nxt_process_init(process); + + if (init->type == NXT_PROCESS_APP) { + + nxt_process_port_each(process, port) { + + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, + 0, 0, NULL); + + } nxt_process_port_loop; + } + + } nxt_runtime_process_loop; +} + + +static void +nxt_runtime_stop_all_processes(nxt_task_t *task, nxt_runtime_t *rt) +{ + nxt_port_t *port; + nxt_process_t *process; + + nxt_runtime_process_each(rt, process) { + + nxt_process_port_each(process, port) { + + (void) nxt_port_socket_write(task, port, NXT_PORT_MSG_QUIT, -1, 0, + 0, NULL); + + } nxt_process_port_loop; + + } nxt_runtime_process_loop; +} + + static void nxt_runtime_exit(nxt_task_t *task, void *obj, void *data) { @@ -521,6 +571,10 @@ nxt_runtime_exit(nxt_task_t *task, void *obj, void *data) } nxt_runtime_process_loop; + if (rt->port_by_type[rt->type] != NULL) { + nxt_port_use(task, rt->port_by_type[rt->type], -1); + } + nxt_thread_mutex_destroy(&rt->processes_mutex); status = rt->status; @@ -1302,7 +1356,9 @@ nxt_runtime_process_new(nxt_runtime_t *rt) /* TODO: memory failures. */ - process = nxt_mp_zalloc(rt->mem_pool, sizeof(nxt_process_t)); + process = nxt_mp_zalloc(rt->mem_pool, + sizeof(nxt_process_t) + sizeof(nxt_process_init_t)); + if (nxt_slow_path(process == NULL)) { return NULL; } @@ -1343,8 +1399,9 @@ nxt_runtime_process_release(nxt_runtime_t *rt, nxt_process_t *process) nxt_thread_mutex_destroy(&process->outgoing.mutex); nxt_thread_mutex_destroy(&process->cp_mutex); - if (process->init != NULL) { - nxt_mp_destroy(process->init->mem_pool); + /* processes from nxt_runtime_process_get() have no memory pool */ + if (process->mem_pool != NULL) { + nxt_mp_destroy(process->mem_pool); } nxt_mp_free(rt->mem_pool, process); diff --git a/src/nxt_runtime.h b/src/nxt_runtime.h index a364c38c..d29b6b4d 100644 --- a/src/nxt_runtime.h +++ b/src/nxt_runtime.h @@ -110,6 +110,7 @@ nxt_port_t *nxt_runtime_process_port_create(nxt_task_t *task, nxt_runtime_t *rt, nxt_pid_t pid, nxt_port_id_t id, nxt_process_type_t type); void nxt_runtime_port_remove(nxt_task_t *task, nxt_port_t *port); +void nxt_runtime_stop_app_processes(nxt_task_t *task, nxt_runtime_t *rt); NXT_EXPORT nxt_port_t *nxt_runtime_port_find(nxt_runtime_t *rt, nxt_pid_t pid, nxt_port_id_t port_id); diff --git a/src/nxt_signal_handlers.c b/src/nxt_signal_handlers.c new file mode 100644 index 00000000..69ae2bc4 --- /dev/null +++ b/src/nxt_signal_handlers.c @@ -0,0 +1,67 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_main.h> +#include <nxt_runtime.h> +#include <nxt_port.h> +#include <nxt_main_process.h> +#include <nxt_router.h> + + +static void nxt_signal_handler(nxt_task_t *task, void *obj, void *data); +static void nxt_signal_sigterm_handler(nxt_task_t *task, void *obj, void *data); +static void nxt_signal_sigquit_handler(nxt_task_t *task, void *obj, void *data); + + +const nxt_sig_event_t nxt_process_signals[] = { + nxt_event_signal(SIGHUP, nxt_signal_handler), + nxt_event_signal(SIGINT, nxt_signal_sigterm_handler), + nxt_event_signal(SIGQUIT, nxt_signal_sigquit_handler), + nxt_event_signal(SIGTERM, nxt_signal_sigterm_handler), + nxt_event_signal(SIGCHLD, nxt_signal_handler), + nxt_event_signal(SIGUSR1, nxt_signal_handler), + nxt_event_signal(SIGUSR2, nxt_signal_handler), + nxt_event_signal_end, +}; + + +static void +nxt_signal_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_trace(task, "signal signo:%d (%s) recevied, ignored", + (int) (uintptr_t) obj, data); +} + + +void +nxt_signal_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) +{ + nxt_process_quit(task, 0); +} + + +static void +nxt_signal_sigterm_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_debug(task, "sigterm handler signo:%d (%s)", + (int) (uintptr_t) obj, data); + + /* A fast exit. */ + + nxt_runtime_quit(task, 0); +} + + +static void +nxt_signal_sigquit_handler(nxt_task_t *task, void *obj, void *data) +{ + nxt_debug(task, "sigquit handler signo:%d (%s)", + (int) (uintptr_t) obj, data); + + /* A graceful exit. */ + + nxt_process_quit(task, 0); +} diff --git a/src/nxt_string.c b/src/nxt_string.c index 54f96abc..ab568990 100644 --- a/src/nxt_string.c +++ b/src/nxt_string.c @@ -475,7 +475,7 @@ nxt_strvers_match(u_char *version, u_char *prefix, size_t length) } -static const uint8_t nxt_hex2int[256] +const uint8_t nxt_hex2int[256] nxt_aligned(32) = { 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, @@ -551,6 +551,47 @@ nxt_decode_uri(u_char *dst, u_char *src, size_t length) } +u_char * +nxt_decode_uri_plus(u_char *dst, u_char *src, size_t length) +{ + u_char *end, ch; + uint8_t d0, d1; + + nxt_prefetch(&nxt_hex2int['0']); + + end = src + length; + + while (src < end) { + ch = *src++; + + switch (ch) { + case '%': + if (nxt_slow_path(end - src < 2)) { + return NULL; + } + + d0 = nxt_hex2int[*src++]; + d1 = nxt_hex2int[*src++]; + + if (nxt_slow_path((d0 | d1) >= 16)) { + return NULL; + } + + ch = (d0 << 4) + d1; + break; + + case '+': + ch = ' '; + break; + } + + *dst++ = ch; + } + + return dst; +} + + uintptr_t nxt_encode_uri(u_char *dst, u_char *src, size_t length) { diff --git a/src/nxt_string.h b/src/nxt_string.h index 7863c60e..3f9192e2 100644 --- a/src/nxt_string.h +++ b/src/nxt_string.h @@ -174,10 +174,13 @@ NXT_EXPORT nxt_bool_t nxt_strvers_match(u_char *version, u_char *prefix, size_t length); NXT_EXPORT u_char *nxt_decode_uri(u_char *dst, u_char *src, size_t length); +NXT_EXPORT u_char *nxt_decode_uri_plus(u_char *dst, u_char *src, size_t length); NXT_EXPORT uintptr_t nxt_encode_uri(u_char *dst, u_char *src, size_t length); NXT_EXPORT uintptr_t nxt_encode_complex_uri(u_char *dst, u_char *src, size_t length); NXT_EXPORT nxt_bool_t nxt_is_complex_uri_encoded(u_char *s, size_t length); +extern const uint8_t nxt_hex2int[256]; + #endif /* _NXT_STRING_H_INCLUDED_ */ diff --git a/src/nxt_unit.c b/src/nxt_unit.c index 67244420..9f6eab95 100644 --- a/src/nxt_unit.c +++ b/src/nxt_unit.c @@ -4248,7 +4248,7 @@ nxt_unit_send_port(nxt_unit_ctx_t *ctx, nxt_unit_port_id_t *dst, m.new_port.id = new_port->id; m.new_port.pid = new_port->pid; - m.new_port.type = NXT_PROCESS_WORKER; + m.new_port.type = NXT_PROCESS_APP; m.new_port.max_size = 16 * 1024; m.new_port.max_share = 64 * 1024; diff --git a/src/nxt_unit_request.h b/src/nxt_unit_request.h index 52017a42..fede00d2 100644 --- a/src/nxt_unit_request.h +++ b/src/nxt_unit_request.h @@ -21,6 +21,7 @@ struct nxt_unit_request_s { uint8_t local_length; uint8_t tls; uint8_t websocket_handshake; + uint8_t app_target; uint32_t server_name_length; uint32_t target_length; uint32_t path_length; diff --git a/src/nxt_unix.h b/src/nxt_unix.h index 151dd555..609f7e95 100644 --- a/src/nxt_unix.h +++ b/src/nxt_unix.h @@ -238,6 +238,9 @@ #include <sys/random.h> /* getentropy(). */ #endif +#if (NXT_HAVE_ISOLATION_ROOTFS) +#include <sys/mount.h> +#endif #if (NXT_TEST_BUILD) #include <nxt_test_build.h> diff --git a/src/nxt_worker_process.c b/src/nxt_worker_process.c deleted file mode 100644 index 754e2ea8..00000000 --- a/src/nxt_worker_process.c +++ /dev/null @@ -1,118 +0,0 @@ - -/* - * Copyright (C) Igor Sysoev - * Copyright (C) NGINX, Inc. - */ - -#include <nxt_main.h> -#include <nxt_runtime.h> -#include <nxt_port.h> -#include <nxt_main_process.h> -#include <nxt_router.h> - - -static void nxt_worker_process_quit(nxt_task_t *task); -static void nxt_worker_process_signal_handler(nxt_task_t *task, void *obj, - void *data); -static void nxt_worker_process_sigterm_handler(nxt_task_t *task, void *obj, - void *data); -static void nxt_worker_process_sigquit_handler(nxt_task_t *task, void *obj, - void *data); - - -const nxt_sig_event_t nxt_worker_process_signals[] = { - nxt_event_signal(SIGHUP, nxt_worker_process_signal_handler), - nxt_event_signal(SIGINT, nxt_worker_process_sigterm_handler), - nxt_event_signal(SIGQUIT, nxt_worker_process_sigquit_handler), - nxt_event_signal(SIGTERM, nxt_worker_process_sigterm_handler), - nxt_event_signal(SIGCHLD, nxt_worker_process_signal_handler), - nxt_event_signal(SIGUSR1, nxt_worker_process_signal_handler), - nxt_event_signal(SIGUSR2, nxt_worker_process_signal_handler), - nxt_event_signal_end, -}; - - -static void -nxt_worker_process_quit(nxt_task_t *task) -{ - nxt_uint_t n; - nxt_queue_t *listen; - nxt_runtime_t *rt; - nxt_queue_link_t *link, *next; - nxt_listen_event_t *lev; - nxt_listen_socket_t *ls; - - rt = task->thread->runtime; - - nxt_debug(task, "close listen connections"); - - listen = &task->thread->engine->listen_connections; - - for (link = nxt_queue_first(listen); - link != nxt_queue_tail(listen); - link = next) - { - next = nxt_queue_next(link); - lev = nxt_queue_link_data(link, nxt_listen_event_t, link); - nxt_queue_remove(link); - - nxt_fd_event_close(task->thread->engine, &lev->socket); - } - - if (rt->listen_sockets != NULL) { - - ls = rt->listen_sockets->elts; - n = rt->listen_sockets->nelts; - - while (n != 0) { - nxt_socket_close(task, ls->socket); - ls->socket = -1; - - ls++; - n--; - } - - rt->listen_sockets->nelts = 0; - } - - nxt_runtime_quit(task, 0); -} - - -static void -nxt_worker_process_signal_handler(nxt_task_t *task, void *obj, void *data) -{ - nxt_trace(task, "signal signo:%d (%s) recevied, ignored", - (int) (uintptr_t) obj, data); -} - - -void -nxt_worker_process_quit_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) -{ - nxt_worker_process_quit(task); -} - - -static void -nxt_worker_process_sigterm_handler(nxt_task_t *task, void *obj, void *data) -{ - nxt_debug(task, "sigterm handler signo:%d (%s)", - (int) (uintptr_t) obj, data); - - /* A fast exit. */ - - nxt_runtime_quit(task, 0); -} - - -static void -nxt_worker_process_sigquit_handler(nxt_task_t *task, void *obj, void *data) -{ - nxt_debug(task, "sigquit handler signo:%d (%s)", - (int) (uintptr_t) obj, data); - - /* A graceful exit. */ - - nxt_worker_process_quit(task); -} diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 548e6daa..14e107e4 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -95,8 +95,8 @@ static int nxt_perl_psgi_result_array(PerlInterpreter *my_perl, static void nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, nxt_unit_request_info_t *req); -static nxt_int_t nxt_perl_psgi_init(nxt_task_t *task, - nxt_common_app_conf_t *conf); +static nxt_int_t nxt_perl_psgi_start(nxt_task_t *task, + nxt_process_data_t *conf); static void nxt_perl_psgi_request_handler(nxt_unit_request_info_t *req); static void nxt_perl_psgi_atexit(void); @@ -118,8 +118,14 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_perl_psgi_compat, nxt_string("perl"), PERL_VERSION_STRING, + +#if (NXT_HAVE_ISOLATION_ROOTFS) NULL, - nxt_perl_psgi_init, + 0, +#endif + + NULL, + nxt_perl_psgi_start, }; @@ -1134,14 +1140,17 @@ nxt_perl_psgi_result_cb(PerlInterpreter *my_perl, SV *result, static nxt_int_t -nxt_perl_psgi_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_perl_psgi_start(nxt_task_t *task, nxt_process_data_t *data) { int rc; nxt_unit_ctx_t *unit_ctx; nxt_unit_init_t perl_init; PerlInterpreter *my_perl; + nxt_common_app_conf_t *conf; nxt_perl_psgi_module_t module; + conf = data->app; + my_perl = nxt_perl_psgi_interpreter_init(task, conf->u.perl.script, &module.app); diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 417e2d8d..489ddcf4 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -7,6 +7,7 @@ #include <nxt_unit.h> #include <nxt_unit_request.h> +#include <nxt_ruby_mounts.h> #define NXT_RUBY_RACK_API_VERSION_MAJOR 1 @@ -28,7 +29,8 @@ typedef struct { } nxt_ruby_rack_init_t; -static nxt_int_t nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static nxt_int_t nxt_ruby_start(nxt_task_t *task, + nxt_process_data_t *data); static VALUE nxt_ruby_init_basic(VALUE arg); static nxt_int_t nxt_ruby_init_io(nxt_task_t *task); static VALUE nxt_ruby_rack_init(nxt_ruby_rack_init_t *rack_init); @@ -77,22 +79,29 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("ruby"), ruby_version, +#if (NXT_HAVE_ISOLATION_ROOTFS) + nxt_ruby_mounts, + nxt_nitems(nxt_ruby_mounts), +#endif NULL, - nxt_ruby_init, + nxt_ruby_start, }; static nxt_int_t -nxt_ruby_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +nxt_ruby_start(nxt_task_t *task, nxt_process_data_t *data) { - int state, rc; - VALUE res; - nxt_unit_ctx_t *unit_ctx; - nxt_unit_init_t ruby_unit_init; - nxt_ruby_rack_init_t rack_init; + int state, rc; + VALUE res; + nxt_unit_ctx_t *unit_ctx; + nxt_unit_init_t ruby_unit_init; + nxt_ruby_rack_init_t rack_init; + nxt_common_app_conf_t *conf; static char *argv[2] = { (char *) "NGINX_Unit", (char *) "-e0" }; + conf = data->app; + RUBY_INIT_STACK ruby_init(); ruby_options(2, argv); diff --git a/test/go/ns_inspect/app.go b/test/go/ns_inspect/app.go index d9b561c9..4d19a796 100644 --- a/test/go/ns_inspect/app.go +++ b/test/go/ns_inspect/app.go @@ -21,10 +21,11 @@ type ( } Output struct { - PID int - UID int - GID int - NS NS + PID int + UID int + GID int + NS NS + FileExists bool } ) @@ -64,6 +65,18 @@ func handler(w http.ResponseWriter, r *http.Request) { CGROUP: getns("cgroup"), }, } + + err := r.ParseForm() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if fname := r.Form.Get("file"); fname != "" { + _, err = os.Stat(fname); + out.FileExists = err == nil + } + data, err := json.Marshal(out) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/test/php/targets/1.php b/test/php/targets/1.php new file mode 100644 index 00000000..09f7ae2c --- /dev/null +++ b/test/php/targets/1.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 1'); +echo '1'; +?> diff --git a/test/php/targets/2/2.php b/test/php/targets/2/2.php new file mode 100644 index 00000000..0c5d27c6 --- /dev/null +++ b/test/php/targets/2/2.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 1'); +echo '2'; +?> diff --git a/test/php/targets/index.php b/test/php/targets/index.php new file mode 100644 index 00000000..88239d5f --- /dev/null +++ b/test/php/targets/index.php @@ -0,0 +1,4 @@ +<?php +header('Content-Length: 5'); +echo 'index'; +?> diff --git a/test/python/ns_inspect/wsgi.py b/test/python/ns_inspect/wsgi.py new file mode 100644 index 00000000..fa1222e4 --- /dev/null +++ b/test/python/ns_inspect/wsgi.py @@ -0,0 +1,31 @@ +import json +import os + +try: + # Python 3 + from urllib.parse import parse_qs +except ImportError: + # Python 2 + from urlparse import parse_qs + + +def application(environ, start_response): + ret = { + 'FileExists': False, + } + + d = parse_qs(environ['QUERY_STRING']) + + ret['FileExists'] = os.path.exists(d.get('path')[0]) + + out = json.dumps(ret) + + start_response( + '200', + [ + ('Content-Type', 'application/json'), + ('Content-Length', str(len(out))), + ], + ) + + return out.encode('utf-8') diff --git a/test/run.py b/test/run.py index 59e06bcb..384663f9 100755 --- a/test/run.py +++ b/test/run.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 - -import unittest -import sys import os +import sys +import unittest if __name__ == '__main__': loader = unittest.TestLoader() diff --git a/test/test_access_log.py b/test/test_access_log.py index 898d8b24..3ef8f7a0 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,12 +1,11 @@ -import os -import re import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestAccessLog(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def load(self, script): super().load(script) diff --git a/test/test_configuration.py b/test/test_configuration.py index daba874b..0329ef5e 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,9 +1,10 @@ import unittest + from unit.control import TestControl class TestConfiguration(TestControl): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_json_empty(self): self.assertIn('error', self.conf(''), 'empty') diff --git a/test/test_go_application.py b/test/test_go_application.py index c9d4ba77..b9b78e2b 100644 --- a/test/test_go_application.py +++ b/test/test_go_application.py @@ -2,7 +2,7 @@ from unit.applications.lang.go import TestApplicationGo class TestGoApplication(TestApplicationGo): - prerequisites = {'modules': ['go']} + prerequisites = {'modules': {'go': 'all'}} def test_go_application_variables(self): self.load('variables') diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index 7884274d..61d39617 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -1,13 +1,13 @@ -import pwd import grp -import json +import pwd import unittest + from unit.applications.lang.go import TestApplicationGo from unit.feature.isolation import TestFeatureIsolation class TestGoIsolation(TestApplicationGo): - prerequisites = {'modules': ['go'], 'features': ['isolation']} + prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} isolation = TestFeatureIsolation() @@ -281,6 +281,52 @@ class TestGoIsolation(TestApplicationGo): '%s match' % ns, ) + def test_go_isolation_rootfs_container(self): + if not self.isolation_key('unprivileged_userns_clone'): + print('unprivileged clone is not available') + raise unittest.SkipTest() + + if not self.isolation_key('mnt'): + print('mnt namespace is not supported') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'mount': True, 'credential': True}, + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + + def test_go_isolation_rootfs_container_priv(self): + if not self.is_su: + print("requires root") + raise unittest.SkipTest() + + if not self.isolation_key('mnt'): + print('mnt namespace is not supported') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'mount': True}, + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + if __name__ == '__main__': TestGoIsolation.main() diff --git a/test/test_go_isolation_rootfs.py b/test/test_go_isolation_rootfs.py new file mode 100644 index 00000000..0039ff87 --- /dev/null +++ b/test/test_go_isolation_rootfs.py @@ -0,0 +1,34 @@ +import os +import unittest + +from unit.applications.lang.go import TestApplicationGo + + +class TestGoIsolationRootfs(TestApplicationGo): + prerequisites = {'modules': {'go': 'all'}} + + def test_go_isolation_rootfs_chroot(self): + if not self.is_su: + print("requires root") + raise unittest.SkipTest() + + if os.uname().sysname == 'Darwin': + print('chroot tests not supported on OSX') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('ns_inspect', isolation=isolation) + + obj = self.getjson(url='/?file=/go/app')['body'] + + self.assertEqual(obj['FileExists'], True, 'app relative to rootfs') + + obj = self.getjson(url='/?file=/bin/sh')['body'] + self.assertEqual(obj['FileExists'], False, 'file should not exists') + + +if __name__ == '__main__': + TestGoIsolationRootfs.main() diff --git a/test/test_http_header.py b/test/test_http_header.py index b773bd68..ea4520c1 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.python import TestApplicationPython class TestHTTPHeader(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_http_header_value_leading_sp(self): self.load('custom_header') diff --git a/test/test_java_application.py b/test/test_java_application.py index 7bd351a4..0cb18c25 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,18 +1,19 @@ import io import os import time -import unittest + from unit.applications.lang.java import TestApplicationJava class TestJavaApplication(TestApplicationJava): - prerequisites = {'modules': ['java']} + prerequisites = {'modules': {'java': 'all'}} def test_java_conf_error(self): self.skip_alerts.extend( [ r'realpath.*failed', r'failed to apply new conf', + r'application setup failed', ] ) self.assertIn( diff --git a/test/test_java_isolation_rootfs.py b/test/test_java_isolation_rootfs.py new file mode 100644 index 00000000..4d39bdc3 --- /dev/null +++ b/test/test_java_isolation_rootfs.py @@ -0,0 +1,85 @@ +import os +import subprocess +import unittest + +from unit.applications.lang.java import TestApplicationJava + + +class TestJavaIsolationRootfs(TestApplicationJava): + prerequisites = {'modules': {'java': 'all'}} + + def setUp(self): + if not self.is_su: + return + + super().setUp() + + os.makedirs(self.testdir + '/jars') + os.makedirs(self.testdir + '/tmp') + os.chmod(self.testdir + '/tmp', 0o777) + + try: + process = subprocess.Popen( + [ + "mount", + "--bind", + self.pardir + "/build", + self.testdir + "/jars", + ], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except: + self.fail('Cann\'t run mount process.') + + def tearDown(self): + if not self.is_su: + return + + try: + process = subprocess.Popen( + ["umount", "--lazy", self.testdir + "/jars"], + stderr=subprocess.STDOUT, + ) + + process.communicate() + + except: + self.fail('Cann\'t run mount process.') + + # super teardown must happen after unmount to avoid deletion of /build + super().tearDown() + + def test_java_isolation_rootfs_chroot_war(self): + if not self.is_su: + print('require root') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('empty_war', isolation=isolation) + + self.assertIn( + 'success', + self.conf( + '"/"', '/config/applications/empty_war/working_directory', + ), + ) + + self.assertIn( + 'success', self.conf('"/jars"', 'applications/empty_war/unit_jars') + ) + self.assertIn( + 'success', + self.conf('"/java/empty.war"', 'applications/empty_war/webapp'), + ) + + self.assertEqual(self.get()['status'], 200, 'war') + + +if __name__ == '__main__': + TestJavaIsolationRootfs.main() diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 7ea04620..d78f7263 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -1,12 +1,13 @@ -import time import struct +import time import unittest + from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket class TestJavaWebsockets(TestApplicationJava): - prerequisites = {'modules': ['java']} + prerequisites = {'modules': {'java': 'any'}} ws = TestApplicationWebsocket() diff --git a/test/test_node_application.py b/test/test_node_application.py index 174af15d..e46cc6a1 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.node import TestApplicationNode class TestNodeApplication(TestApplicationNode): - prerequisites = {'modules': ['node']} + prerequisites = {'modules': {'node': 'all'}} def test_node_application_basic(self): self.load('basic') diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 4ce727db..1928d8c9 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -1,12 +1,13 @@ -import time import struct +import time import unittest + from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket class TestNodeWebsockets(TestApplicationNode): - prerequisites = {'modules': ['node']} + prerequisites = {'modules': {'node': 'any'}} ws = TestApplicationWebsocket() diff --git a/test/test_perl_application.py b/test/test_perl_application.py index cc4eb915..dbf6abf7 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.perl import TestApplicationPerl class TestPerlApplication(TestApplicationPerl): - prerequisites = {'modules': ['perl']} + prerequisites = {'modules': {'perl': 'all'}} def test_perl_application(self): self.load('variables') diff --git a/test/test_php_application.py b/test/test_php_application.py index 48e1e815..1259d22d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,11 +1,11 @@ import os -import re import shutil import unittest + from unit.applications.lang.php import TestApplicationPHP class TestPHPApplication(TestApplicationPHP): - prerequisites = {'modules': ['php']} + prerequisites = {'modules': {'php': 'all'}} def before_disable_functions(self): body = self.get()['body'] diff --git a/test/test_php_basic.py b/test/test_php_basic.py index 5fde3e00..16483c4a 100644 --- a/test/test_php_basic.py +++ b/test/test_php_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPHPBasic(TestControl): - prerequisites = {'modules': ['php']} + prerequisites = {'modules': {'php': 'any'}} conf_app = { "app": { diff --git a/test/test_php_isolation.py b/test/test_php_isolation.py new file mode 100644 index 00000000..1b70ef02 --- /dev/null +++ b/test/test_php_isolation.py @@ -0,0 +1,57 @@ +import unittest + +from unit.applications.lang.php import TestApplicationPHP +from unit.feature.isolation import TestFeatureIsolation + + +class TestPHPIsolation(TestApplicationPHP): + prerequisites = {'modules': {'php': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_php_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.current_dir, + } + + self.load('phpinfo', isolation=isolation) + + self.assertIn( + 'success', self.conf('"/php/phpinfo"', 'applications/phpinfo/root') + ) + self.assertIn( + 'success', + self.conf( + '"/php/phpinfo"', 'applications/phpinfo/working_directory' + ), + ) + + self.assertEqual(self.get()['status'], 200, 'empty rootfs') + + +if __name__ == '__main__': + TestPHPIsolation.main() diff --git a/test/test_php_targets.py b/test/test_php_targets.py new file mode 100644 index 00000000..9c1ba2a6 --- /dev/null +++ b/test/test_php_targets.py @@ -0,0 +1,129 @@ +import unittest +from unit.applications.lang.php import TestApplicationPHP + +class TestPHPTargets(TestApplicationPHP): + prerequisites = {'modules': {'php': 'any'}} + + def test_php_application_targets(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": {"*:7080": {"pass": "routes"}}, + "routes": [ + { + "match": {"uri": "/1"}, + "action": {"pass": "applications/targets/1"}, + }, + { + "match": {"uri": "/2"}, + "action": {"pass": "applications/targets/2"}, + }, + {"action": {"pass": "applications/targets/default"}}, + ], + "applications": { + "targets": { + "type": "php", + "processes": {"spare": 0}, + "targets": { + "1": { + "script": "1.php", + "root": self.current_dir + "/php/targets", + }, + "2": { + "script": "2.php", + "root": self.current_dir + + "/php/targets/2", + }, + "default": { + "index": "index.php", + "root": self.current_dir + "/php/targets", + }, + }, + } + }, + } + ), + ) + + self.assertEqual(self.get(url='/1')['body'], '1') + self.assertEqual(self.get(url='/2')['body'], '2') + self.assertEqual(self.get(url='/blah')['status'], 503) # TODO 404 + self.assertEqual(self.get(url='/')['body'], 'index') + + self.assertIn( + 'success', + self.conf( + "\"1.php\"", 'applications/targets/targets/default/index' + ), + 'change targets index', + ) + self.assertEqual(self.get(url='/')['body'], '1') + + self.assertIn( + 'success', + self.conf_delete('applications/targets/targets/default/index'), + 'remove targets index', + ) + self.assertEqual(self.get(url='/')['body'], 'index') + + def test_php_application_targets_error(self): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/targets/default"} + }, + "applications": { + "targets": { + "type": "php", + "processes": {"spare": 0}, + "targets": { + "default": { + "index": "index.php", + "root": self.current_dir + "/php/targets", + }, + }, + } + }, + } + ), + 'initial configuration', + ) + self.assertEqual(self.get()['status'], 200) + + self.assertIn( + 'error', + self.conf( + {"pass": "applications/targets/blah"}, 'listeners/*:7080' + ), + 'invalid targets pass', + ) + self.assertIn( + 'error', + self.conf( + '"' + self.current_dir + '/php/targets\"', + 'applications/targets/root', + ), + 'invalid root', + ) + self.assertIn( + 'error', + self.conf('"index.php"', 'applications/targets/index'), + 'invalid index', + ) + self.assertIn( + 'error', + self.conf('"index.php"', 'applications/targets/script'), + 'invalid script', + ) + self.assertIn( + 'error', + self.conf_delete('applications/targets/default/root'), + 'root remove', + ) + + +if __name__ == '__main__': + TestPHPTargets.main() diff --git a/test/test_proxy.py b/test/test_proxy.py index 74bd0873..feec1ac4 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -1,12 +1,13 @@ import re -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestProxy(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} SERVER_PORT = 7999 @@ -521,88 +522,35 @@ Content-Length: 10 self.assertEqual(len(resp['body']), 10, 'body gt Content-Length 15') def test_proxy_invalid(self): - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'blah'}}], 'routes'), - 'proxy invalid', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": '/blah'}}], 'routes'), - 'proxy invalid 2', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'unix:/blah'}}], 'routes'), - 'proxy unix invalid 2', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://blah'}}], 'routes'), - 'proxy unix invalid 3', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://127.0.0.1'}}], 'routes'), - 'proxy ipv4 invalid', - ) - self.assertIn( - 'error', - self.conf([{"action": {"proxy": 'http://127.0.0.1:'}}], 'routes'), - 'proxy ipv4 invalid 2', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:blah'}}], 'routes' - ), - 'proxy ipv4 invalid 3', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:-1'}}], 'routes' - ), - 'proxy ipv4 invalid 4', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://127.0.0.1:7080b'}}], 'routes' - ), - 'proxy ipv4 invalid 5', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[]'}}], 'routes' - ), - 'proxy ipv6 invalid', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[]:7080'}}], 'routes' - ), - 'proxy ipv6 invalid 2', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[:]:7080'}}], 'routes' - ), - 'proxy ipv6 invalid 3', - ) - self.assertIn( - 'error', - self.conf( - [{"action": {"proxy": 'http://[::7080'}}], 'routes' - ), - 'proxy ipv6 invalid 4', - ) + def check_proxy(proxy): + self.assertIn( + 'error', + self.conf([{"action": {"proxy": proxy}}], 'routes'), + 'proxy invalid', + ) + + check_proxy('blah') + check_proxy('/blah') + check_proxy('unix:/blah') + check_proxy('http://blah') + check_proxy('http://127.0.0.1') + check_proxy('http://127.0.0.1:') + check_proxy('http://127.0.0.1:blah') + check_proxy('http://127.0.0.1:-1') + check_proxy('http://127.0.0.1:7080b') + check_proxy('http://[]') + check_proxy('http://[]:7080') + check_proxy('http://[:]:7080') + check_proxy('http://[::7080') - @unittest.skip('not yet') def test_proxy_loop(self): + self.skip_alerts.extend( + [ + r'socket.*failed', + r'accept.*failed', + r'new connections are not accepted', + ] + ) self.conf( { "listeners": { @@ -625,6 +573,7 @@ Content-Length: 10 ) self.get_http10(no_recv=True) + self.get_http10(read_timeout=1) if __name__ == '__main__': TestProxy.main() diff --git a/test/test_python_application.py b/test/test_python_application.py index 8d435b48..8bd3f750 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,14 +1,14 @@ -import re -import os import grp import pwd +import re import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestPythonApplication(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'all'}} def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: diff --git a/test/test_python_basic.py b/test/test_python_basic.py index 3233fca2..d6445ac2 100644 --- a/test/test_python_basic.py +++ b/test/test_python_basic.py @@ -2,7 +2,7 @@ from unit.control import TestControl class TestPythonBasic(TestControl): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} conf_app = { "app": { diff --git a/test/test_python_environment.py b/test/test_python_environment.py index f808f795..a03b96e6 100644 --- a/test/test_python_environment.py +++ b/test/test_python_environment.py @@ -2,7 +2,7 @@ from unit.applications.lang.python import TestApplicationPython class TestPythonEnvironment(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_python_environment_name_null(self): self.load('environment') diff --git a/test/test_python_isolation.py b/test/test_python_isolation.py new file mode 100644 index 00000000..1bed64ba --- /dev/null +++ b/test/test_python_isolation.py @@ -0,0 +1,79 @@ +import unittest + +from unit.applications.lang.python import TestApplicationPython +from unit.feature.isolation import TestFeatureIsolation + + +class TestPythonIsolation(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_python_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.testdir, + } + + self.load('empty', isolation=isolation) + + self.assertEqual(self.get()['status'], 200, 'python rootfs') + + self.load('ns_inspect', isolation=isolation) + + self.assertEqual( + self.getjson(url='/?path=' + self.testdir)['body']['FileExists'], + False, + 'testdir does not exists in rootfs', + ) + + self.assertEqual( + self.getjson(url='/?path=/proc/self')['body']['FileExists'], + False, + 'no /proc/self', + ) + + self.assertEqual( + self.getjson(url='/?path=/dev/pts')['body']['FileExists'], + False, + 'no /dev/pts', + ) + + self.assertEqual( + self.getjson(url='/?path=/sys/kernel')['body']['FileExists'], + False, + 'no /sys/kernel', + ) + + ret = self.getjson(url='/?path=/app/python/ns_inspect') + + self.assertEqual( + ret['body']['FileExists'], True, 'application exists in rootfs', + ) + + +if __name__ == '__main__': + TestPythonIsolation.main() diff --git a/test/test_python_isolation_chroot.py b/test/test_python_isolation_chroot.py new file mode 100644 index 00000000..7761128e --- /dev/null +++ b/test/test_python_isolation_chroot.py @@ -0,0 +1,57 @@ +import unittest + +from unit.applications.lang.python import TestApplicationPython +from unit.feature.isolation import TestFeatureIsolation + + +class TestPythonIsolation(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + def test_python_isolation_chroot(self): + if not self.is_su: + print('requires root') + raise unittest.SkipTest() + + isolation = { + 'rootfs': self.testdir, + } + + self.load('empty', isolation=isolation) + + self.assertEqual(self.get()['status'], 200, 'python chroot') + + self.load('ns_inspect', isolation=isolation) + + self.assertEqual( + self.getjson(url='/?path=' + self.testdir)['body']['FileExists'], + False, + 'testdir does not exists in rootfs', + ) + + self.assertEqual( + self.getjson(url='/?path=/proc/self')['body']['FileExists'], + False, + 'no /proc/self', + ) + + self.assertEqual( + self.getjson(url='/?path=/dev/pts')['body']['FileExists'], + False, + 'no /dev/pts', + ) + + self.assertEqual( + self.getjson(url='/?path=/sys/kernel')['body']['FileExists'], + False, + 'no /sys/kernel', + ) + + ret = self.getjson(url='/?path=/app/python/ns_inspect') + + self.assertEqual( + ret['body']['FileExists'], True, 'application exists in rootfs', + ) + + +if __name__ == '__main__': + TestPythonIsolation.main() diff --git a/test/test_python_procman.py b/test/test_python_procman.py index a2e6126c..8613f58e 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -1,12 +1,13 @@ import re -import time import subprocess +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestPythonProcman(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() diff --git a/test/test_respawn.py b/test/test_respawn.py new file mode 100644 index 00000000..f1c71a20 --- /dev/null +++ b/test/test_respawn.py @@ -0,0 +1,95 @@ +import re +import subprocess +import time + +from unit.applications.lang.python import TestApplicationPython + + +class TestRespawn(TestApplicationPython): + prerequisites = {'modules': {'python': 'any'}} + + PATTERN_ROUTER = 'unit: router' + PATTERN_CONTROLLER = 'unit: controller' + + def setUp(self): + super().setUp() + + self.app_name = "app-" + self.testdir.split('/')[-1] + + self.load('empty', self.app_name) + + self.assertIn( + 'success', + self.conf('1', 'applications/' + self.app_name + '/processes') + ) + + def pid_by_name(self, name): + output = subprocess.check_output(['ps', 'ax']).decode() + m = re.search('\s*(\d+).*' + name, output) + return m if m is None else m.group(1) + + def kill_pids(self, *pids): + subprocess.call(['kill', '-9'] + list(pids)) + + def wait_for_process(self, process): + for i in range(50): + found = self.pid_by_name(process) + + if found is not None: + break + + time.sleep(0.1) + + return found + + def smoke_test(self): + for _ in range(5): + self.assertIn( + 'success', + self.conf('1', 'applications/' + self.app_name + '/processes') + ) + self.assertEqual(self.get()['status'], 200) + + # Check if the only one router, controller, + # and application processes running. + + output = subprocess.check_output(['ps', 'ax']).decode() + self.assertEqual(len(re.findall(self.PATTERN_ROUTER, output)), 1) + self.assertEqual(len(re.findall(self.PATTERN_CONTROLLER, output)), 1) + self.assertEqual(len(re.findall(self.app_name, output)), 1) + + def test_respawn_router(self): + pid = self.pid_by_name(self.PATTERN_ROUTER) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.PATTERN_ROUTER)) + + self.smoke_test() + + def test_respawn_controller(self): + pid = self.pid_by_name(self.PATTERN_CONTROLLER) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.PATTERN_CONTROLLER)) + + self.assertEqual(self.get()['status'], 200) + + self.smoke_test() + + def test_respawn_application(self): + pid = self.pid_by_name(self.app_name) + + self.kill_pids(pid) + self.skip_alerts.append(r'process %s exited on signal 9' % pid) + + self.assertIsNotNone(self.wait_for_process(self.app_name)) + + self.smoke_test() + + +if __name__ == '__main__': + TestRespawn.main() diff --git a/test/test_return.py b/test/test_return.py index fcb51745..a89d97e6 100644 --- a/test/test_return.py +++ b/test/test_return.py @@ -1,5 +1,5 @@ import re -import unittest + from unit.applications.proto import TestApplicationProto diff --git a/test/test_routing.py b/test/test_routing.py index ad793662..3cf4009c 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,9 +1,11 @@ +# -*- coding: utf-8 -*- import unittest + from unit.applications.proto import TestApplicationProto class TestRouting(TestApplicationProto): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() @@ -179,6 +181,61 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/blah')['status'], 200, '/blah') self.assertEqual(self.get(url='/BLAH')['status'], 404, '/BLAH') + def test_routes_pass_encode(self): + def check_pass(path, name): + self.assertIn( + 'success', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/" + path} + }, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + ) + + self.assertEqual(self.get()['status'], 200) + + check_pass("%25", "%") + check_pass("blah%2Fblah", "blah/blah") + check_pass("%2Fblah%2F%2Fblah%2F", "/blah//blah/") + check_pass("%20blah%252Fblah%7E", " blah%2Fblah~") + + def check_pass_error(path, name): + self.assertIn( + 'error', + self.conf( + { + "listeners": { + "*:7080": {"pass": "applications/" + path} + }, + "applications": { + name: { + "type": "python", + "processes": {"spare": 0}, + "path": self.current_dir + '/python/empty', + "working_directory": self.current_dir + + '/python/empty', + "module": "wsgi", + } + }, + } + ), + ) + + check_pass_error("%", "%") + check_pass_error("%1", "%1") + def test_routes_absent(self): self.conf( { @@ -1069,6 +1126,33 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/?Foo=bar')['status'], 404, 'case') self.assertEqual(self.get(url='/?foo=Bar')['status'], 404, 'case 2') + def test_routes_match_arguments_chars(self): + chars = ( + " !\"%23$%25%26'()*%2B,-./0123456789:;<%3D>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + ) + + chars_enc = "" + for h1 in ["2", "3", "4", "5", "6", "7"]: + for h2 in ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", + "B", "C", "D", "E", "F", + ]: + chars_enc += "%" + h1 + h2 + chars_enc = chars_enc[:-3] + + def check_args(args, query): + self.route_match({"arguments": args}) + self.assertEqual(self.get(url='/?' + query)['status'], 200) + + check_args({chars: chars}, chars + '=' + chars) + check_args({chars: chars}, chars + '=' + chars_enc) + check_args({chars: chars}, chars_enc + '=' + chars) + check_args({chars: chars}, chars_enc + '=' + chars_enc) + check_args({chars_enc: chars_enc}, chars + '=' + chars) + check_args({chars_enc: chars_enc}, chars + '=' + chars_enc) + check_args({chars_enc: chars_enc}, chars_enc + '=' + chars) + check_args({chars_enc: chars_enc}, chars_enc + '=' + chars_enc) + def test_routes_match_arguments_empty(self): self.route_match({"arguments": {}}) self.assertEqual(self.get()['status'], 200, 'arguments empty') @@ -1076,43 +1160,113 @@ class TestRouting(TestApplicationProto): self.route_match({"arguments": []}) self.assertEqual(self.get()['status'], 200, 'arguments empty 2') - def test_routes_match_arguments_invalid(self): - self.route_match_invalid({"arguments": ["var"]}) - self.route_match_invalid({"arguments": [{"var1": {}}]}) - self.route_match_invalid({"arguments": {"": "bar"}}) - - @unittest.skip('not yet') def test_routes_match_arguments_space(self): - self.route_match({"arguments": {"foo": "bar "}}) - - self.assertEqual(self.get(url='/?foo=bar &')['status'], 200, 'sp') - # FAIL - self.assertEqual(self.get(url='/?foo=bar+&')['status'], 200, 'sp 2') - # FAIL - self.assertEqual(self.get(url='/?foo=bar%20&')['status'], 200, 'sp 3') - - @unittest.skip('not yet') - def test_routes_match_arguments_plus(self): - self.route_match({"arguments": [{"foo": "bar+"}]}) - - self.assertEqual(self.get(url='/?foo=bar+&')['status'], 200, 'plus') - # FAIL + self.route_match({"arguments": {"+fo o%20": "%20b+a r"}}) + self.assertEqual(self.get(url='/? fo o = b a r&')['status'], 200) + self.assertEqual(self.get(url='/?+fo+o+=+b+a+r&')['status'], 200) self.assertEqual( - self.get(url='/?foo=bar%2B&')['status'], 200, 'plus 2' - ) - - @unittest.skip('not yet') - def test_routes_match_arguments_hex(self): - self.route_match({"arguments": [{"foo": "bar"}]}) - - self.assertEqual( - self.get(url='/?%66%6F%6f=%62%61%72&')['status'], 200, 'hex' - ) - - def test_routes_match_arguments_chars(self): - self.route_match({"arguments": {"foo": "-._()[],;"}}) - - self.assertEqual(self.get(url='/?foo=-._()[],;')['status'], 200, 'chs') + self.get(url='/?%20fo%20o%20=%20b%20a%20r&')['status'], 200 + ) + + self.route_match({"arguments": {"%20foo": " bar"}}) + self.assertEqual(self.get(url='/? foo= bar')['status'], 200) + self.assertEqual(self.get(url='/?+foo=+bar')['status'], 200) + self.assertEqual(self.get(url='/?%20foo=%20bar')['status'], 200) + self.assertEqual(self.get(url='/?+foo= bar')['status'], 200) + self.assertEqual(self.get(url='/?%20foo=+bar')['status'], 200) + + def test_routes_match_arguments_equal(self): + self.route_match({"arguments": {"=": "="}}) + self.assertEqual(self.get(url='/?%3D=%3D')['status'], 200) + self.assertEqual(self.get(url='/?%3D==')['status'], 200) + self.assertEqual(self.get(url='/?===')['status'], 404) + self.assertEqual(self.get(url='/?%3D%3D%3D')['status'], 404) + self.assertEqual(self.get(url='/?==%3D')['status'], 404) + + def test_routes_match_arguments_enc(self): + self.route_match({"arguments": {"Ю": "н"}}) + self.assertEqual(self.get(url='/?%D0%AE=%D0%BD')['status'], 200) + self.assertEqual(self.get(url='/?%d0%ae=%d0%Bd')['status'], 200) + + def test_routes_match_arguments_hash(self): + self.route_match({"arguments": {"#": "#"}}) + self.assertEqual(self.get(url='/?%23=%23')['status'], 200) + self.assertEqual(self.get(url='/?%23=%23#')['status'], 200) + self.assertEqual(self.get(url='/?#=#')['status'], 404) + self.assertEqual(self.get(url='/?%23=#')['status'], 404) + + def test_routes_match_arguments_wildcard(self): + self.route_match({"arguments": {"foo": "*"}}) + self.assertEqual(self.get(url='/?foo')['status'], 200) + self.assertEqual(self.get(url='/?foo=')['status'], 200) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?blah=foo')['status'], 404) + + self.route_match({"arguments": {"foo": "%25*"}}) + self.assertEqual(self.get(url='/?foo=%xx')['status'], 200) + + self.route_match({"arguments": {"foo": "%2A*"}}) + self.assertEqual(self.get(url='/?foo=*xx')['status'], 200) + self.assertEqual(self.get(url='/?foo=xx')['status'], 404) + + self.route_match({"arguments": {"foo": "*%2A"}}) + self.assertEqual(self.get(url='/?foo=xx*')['status'], 200) + self.assertEqual(self.get(url='/?foo=xx*x')['status'], 404) + + self.route_match({"arguments": {"foo": "1*2"}}) + self.assertEqual(self.get(url='/?foo=12')['status'], 200) + self.assertEqual(self.get(url='/?foo=1blah2')['status'], 200) + self.assertEqual(self.get(url='/?foo=1%2A2')['status'], 200) + self.assertEqual(self.get(url='/?foo=x12')['status'], 404) + + self.route_match({"arguments": {"foo": "bar*", "%25": "%25"}}) + self.assertEqual(self.get(url='/?foo=barxx&%=%')['status'], 200) + self.assertEqual(self.get(url='/?foo=barxx&x%=%')['status'], 404) + + def test_routes_match_arguments_negative(self): + self.route_match({"arguments": {"foo": "!%25"}}) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=%')['status'], 404) + + self.route_match({"arguments": {"foo": "%21blah"}}) + self.assertEqual(self.get(url='/?foo=%21blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=bar')['status'], 404) + + self.route_match({"arguments": {"foo": "!!%21*a"}}) + self.assertEqual(self.get(url='/?foo=blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!blah')['status'], 200) + self.assertEqual(self.get(url='/?foo=!!a')['status'], 404) + self.assertEqual(self.get(url='/?foo=!!bla')['status'], 404) + + def test_routes_match_arguments_percent(self): + self.route_match({"arguments": {"%25": "%25"}}) + self.assertEqual(self.get(url='/?%=%')['status'], 200) + self.assertEqual(self.get(url='/?%25=%25')['status'], 200) + self.assertEqual(self.get(url='/?%25=%')['status'], 200) + + self.route_match({"arguments": {"%251": "%252"}}) + self.assertEqual(self.get(url='/?%1=%2')['status'], 200) + self.assertEqual(self.get(url='/?%251=%252')['status'], 200) + self.assertEqual(self.get(url='/?%251=%2')['status'], 200) + + self.route_match({"arguments": {"%25%21%251": "%25%24%252"}}) + self.assertEqual(self.get(url='/?%!%1=%$%2')['status'], 200) + self.assertEqual(self.get(url='/?%25!%251=%25$%252')['status'], 200) + self.assertEqual(self.get(url='/?%25!%1=%$%2')['status'], 200) + + def test_routes_match_arguments_ampersand(self): + self.route_match({"arguments": {"foo": "&"}}) + self.assertEqual(self.get(url='/?foo=%26')['status'], 200) + self.assertEqual(self.get(url='/?foo=%26&')['status'], 200) + self.assertEqual(self.get(url='/?foo=%26%26')['status'], 404) + self.assertEqual(self.get(url='/?foo=&')['status'], 404) + + self.route_match({"arguments": {"&": ""}}) + self.assertEqual(self.get(url='/?%26=')['status'], 200) + self.assertEqual(self.get(url='/?%26=&')['status'], 200) + self.assertEqual(self.get(url='/?%26=%26')['status'], 404) + self.assertEqual(self.get(url='/?&=')['status'], 404) def test_routes_match_arguments_complex(self): self.route_match({"arguments": {"foo": ""}}) @@ -1147,6 +1301,14 @@ class TestRouting(TestApplicationProto): self.assertEqual( self.get(url='/?foo=bar&blah')['status'], 404, 'multiple 3' ) + self.assertEqual( + self.get(url='/?foo=bar&blah=tes')['status'], 404, 'multiple 4' + ) + self.assertEqual( + self.get(url='/?foo=b%61r&bl%61h=t%65st')['status'], + 200, + 'multiple 5', + ) def test_routes_match_arguments_multiple_rules(self): self.route_match({"arguments": {"foo": ["bar", "blah"]}}) @@ -1193,6 +1355,22 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get(url='/?var2=val2')['status'], 404, 'arr 7') self.assertEqual(self.get(url='/?var3=foo')['status'], 200, 'arr 8') + def test_routes_match_arguments_invalid(self): + # TODO remove it after controller fixed + self.skip_alerts.append(r'failed to apply new conf') + + self.route_match_invalid({"arguments": ["var"]}) + self.route_match_invalid({"arguments": [{"var1": {}}]}) + self.route_match_invalid({"arguments": {"": "bar"}}) + self.route_match_invalid({"arguments": {"foo": "*ba*r"}}) + self.route_match_invalid({"arguments": {"foo": "%"}}) + self.route_match_invalid({"arguments": {"foo": "%1G"}}) + self.route_match_invalid({"arguments": {"%": "bar"}}) + self.route_match_invalid({"arguments": {"foo": "%0"}}) + self.route_match_invalid({"arguments": {"foo": "%%1F"}}) + self.route_match_invalid({"arguments": {"%%1F": ""}}) + self.route_match_invalid({"arguments": {"%7%F": ""}}) + def test_routes_match_cookies(self): self.route_match({"cookies": {"foO": "bar"}}) @@ -1748,5 +1926,6 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get()['status'], 200, 'proxy') + if __name__ == '__main__': TestRouting.main() diff --git a/test/test_routing_tls.py b/test/test_routing_tls.py index 36bd9057..a9b8f88d 100644 --- a/test/test_routing_tls.py +++ b/test/test_routing_tls.py @@ -2,7 +2,7 @@ from unit.applications.tls import TestApplicationTLS class TestRoutingTLS(TestApplicationTLS): - prerequisites = {'modules': ['openssl']} + prerequisites = {'modules': {'openssl': 'any'}} def test_routes_match_scheme_tls(self): self.certificate() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index bdaabe51..4709df6c 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -1,9 +1,10 @@ import unittest + from unit.applications.lang.ruby import TestApplicationRuby class TestRubyApplication(TestApplicationRuby): - prerequisites = {'modules': ['ruby']} + prerequisites = {'modules': {'ruby': 'all'}} def test_ruby_application(self): self.load('variables') diff --git a/test/test_ruby_isolation.py b/test/test_ruby_isolation.py new file mode 100644 index 00000000..9bac162e --- /dev/null +++ b/test/test_ruby_isolation.py @@ -0,0 +1,71 @@ +import os +import shutil +import unittest + +from unit.applications.lang.ruby import TestApplicationRuby +from unit.feature.isolation import TestFeatureIsolation + + +class TestRubyIsolation(TestApplicationRuby): + prerequisites = {'modules': {'ruby': 'any'}, 'features': ['isolation']} + + isolation = TestFeatureIsolation() + + @classmethod + def setUpClass(cls, complete_check=True): + unit = super().setUpClass(complete_check=False) + + TestFeatureIsolation().check(cls.available, unit.testdir) + + return unit if not complete_check else unit.complete() + + def test_ruby_isolation_rootfs(self): + isolation_features = self.available['features']['isolation'].keys() + + if 'mnt' not in isolation_features: + print('requires mnt ns') + raise unittest.SkipTest() + + if not self.is_su: + if 'user' not in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + if not 'unprivileged_userns_clone' in isolation_features: + print('requires unprivileged userns or root') + raise unittest.SkipTest() + + os.mkdir(self.testdir + '/ruby') + + shutil.copytree( + self.current_dir + '/ruby/status_int', + self.testdir + '/ruby/status_int', + ) + isolation = { + 'namespaces': {'credential': not self.is_su, 'mount': True}, + 'rootfs': self.testdir, + } + + self.load('status_int', isolation=isolation) + + self.assertIn( + 'success', + self.conf( + '"/ruby/status_int/config.ru"', + 'applications/status_int/script', + ), + ) + + self.assertIn( + 'success', + self.conf( + '"/ruby/status_int"', + 'applications/status_int/working_directory', + ), + ) + + self.assertEqual(self.get()['status'], 200, 'status int') + + +if __name__ == '__main__': + TestRubyIsolation.main() diff --git a/test/test_settings.py b/test/test_settings.py index 9de3a928..6600358d 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,11 +1,12 @@ -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython class TestSettings(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_settings_header_read_timeout(self): self.load('empty') diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index c51e43ee..ca5e2678 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -1,5 +1,5 @@ import os -import unittest + from unit.applications.proto import TestApplicationProto @@ -125,18 +125,23 @@ class TestStatic(TestApplicationProto): self.assertEqual(resp['status'], 200, 'fallback proxy status') self.assertEqual(resp['body'], '', 'fallback proxy') - @unittest.skip('not yet') - def test_fallback_proxy_cycle(self): + def test_fallback_proxy_loop(self): + self.skip_alerts.extend( + [ + r'open.*/blah/index.html.*failed', + r'accept.*failed', + r'socket.*failed', + r'new connections are not accepted', + ] + ) + self.action_update( - { - "share": "/blah", - "fallback": {"proxy": "http://127.0.0.1:7080"}, - } + {"share": "/blah", "fallback": {"proxy": "http://127.0.0.1:7080"}} ) - self.assertNotEqual(self.get()['status'], 200, 'fallback cycle') + self.get(no_recv=True) self.assertIn('success', self.conf_delete('listeners/*:7081')) - self.assertNotEqual(self.get()['status'], 200, 'fallback cycle 2') + self.get(read_timeout=1) def test_fallback_invalid(self): def check_error(conf): diff --git a/test/test_static.py b/test/test_static.py index b2489aa0..bee5db28 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -1,6 +1,7 @@ import os import socket import unittest + from unit.applications.proto import TestApplicationProto diff --git a/test/test_tls.py b/test/test_tls.py index d9dcf237..a0434174 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -1,14 +1,14 @@ import io -import os import re import ssl import subprocess import unittest + from unit.applications.tls import TestApplicationTLS class TestTLS(TestApplicationTLS): - prerequisites = {'modules': ['python', 'openssl']} + prerequisites = {'modules': {'python': 'any', 'openssl': 'any'}} def findall(self, pattern): with open(self.testdir + '/unit.log', 'r', errors='ignore') as f: diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py index 7045318a..2f74fbde 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -1,11 +1,11 @@ import os import re -import unittest + from unit.applications.lang.python import TestApplicationPython class TestUpstreamsRR(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() diff --git a/test/test_usr1.py b/test/test_usr1.py index 155303ea..d1db652f 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -1,11 +1,11 @@ import os -import unittest from subprocess import call + from unit.applications.lang.python import TestApplicationPython class TestUSR1(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def test_usr1_access_log(self): self.load('empty') diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index e0f83c0a..83bde4d8 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -1,5 +1,6 @@ import os import subprocess + from unit.applications.proto import TestApplicationProto @@ -18,26 +19,36 @@ class TestApplicationGo(TestApplicationProto): return unit if not complete_check else unit.complete() - def prepare_env(self, script, name): + def prepare_env(self, script, name, static=False): if not os.path.exists(self.testdir + '/go'): os.mkdir(self.testdir + '/go') env = os.environ.copy() env['GOPATH'] = self.pardir + '/build/go' - try: - process = subprocess.Popen( - [ - 'go', - 'build', - '-o', - self.testdir + '/go/' + name, - self.current_dir + '/go/' + script + '/' + name + '.go', - ], - env=env, - stderr=subprocess.STDOUT, - ) + if static: + args = [ + 'go', + 'build', + '-tags', + 'netgo', + '-ldflags', + '-extldflags "-static"', + '-o', + self.testdir + '/go/' + name, + self.current_dir + '/go/' + script + '/' + name + '.go', + ] + else: + args = [ + 'go', + 'build', + '-o', + self.testdir + '/go/' + name, + self.current_dir + '/go/' + script + '/' + name + '.go', + ] + try: + process = subprocess.Popen(args, env=env) process.communicate() except: @@ -46,21 +57,28 @@ class TestApplicationGo(TestApplicationProto): return process def load(self, script, name='app', **kwargs): - self.prepare_env(script, name) - - self._load_conf( - { - "listeners": {"*:7080": {"pass": "applications/" + script}}, - "applications": { - script: { - "type": "external", - "processes": {"spare": 0}, - "working_directory": self.current_dir - + "/go/" - + script, - "executable": self.testdir + "/go/" + name, - } + static_build = False + + wdir = self.current_dir + "/go/" + script + executable = self.testdir + "/go/" + name + + if 'isolation' in kwargs and 'rootfs' in kwargs['isolation']: + wdir = "/go/" + executable = "/go/" + name + static_build = True + + self.prepare_env(script, name, static=static_build) + + conf = { + "listeners": {"*:7080": {"pass": "applications/" + script}}, + "applications": { + script: { + "type": "external", + "processes": {"spare": 0}, + "working_directory": wdir, + "executable": executable, }, }, - **kwargs - ) + } + + self._load_conf(conf, **kwargs) diff --git a/test/unit/applications/lang/java.py b/test/unit/applications/lang/java.py index a8a09ce5..c2c6dc51 100644 --- a/test/unit/applications/lang/java.py +++ b/test/unit/applications/lang/java.py @@ -1,7 +1,8 @@ -import os import glob +import os import shutil import subprocess + from unit.applications.proto import TestApplicationProto diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index d818298f..cf2a99f6 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,5 +1,7 @@ import os import shutil +from urllib.parse import quote + from unit.applications.proto import TestApplicationProto @@ -33,7 +35,9 @@ class TestApplicationNode(TestApplicationProto): self._load_conf( { - "listeners": {"*:7080": {"pass": "applications/" + script}}, + "listeners": { + "*:7080": {"pass": "applications/" + quote(script, '')} + }, "applications": { script: { "type": "external", diff --git a/test/unit/applications/lang/python.py b/test/unit/applications/lang/python.py index fdda024a..31a04107 100644 --- a/test/unit/applications/lang/python.py +++ b/test/unit/applications/lang/python.py @@ -1,3 +1,6 @@ +import shutil +import os + from unit.applications.proto import TestApplicationProto @@ -8,7 +11,21 @@ class TestApplicationPython(TestApplicationProto): if name is None: name = script - script_path = self.current_dir + '/python/' + script + if script[0] == '/': + script_path = script + else: + script_path = self.current_dir + '/python/' + script + + if kwargs.get('isolation') and kwargs['isolation'].get('rootfs'): + rootfs = kwargs['isolation']['rootfs'] + + if not os.path.exists(rootfs + '/app/python/'): + os.makedirs(rootfs + '/app/python/') + + if not os.path.exists(rootfs + '/app/python/' + name): + shutil.copytree(script_path, rootfs + '/app/python/' + name) + + script_path = '/app/python/' + name self._load_conf( { diff --git a/test/unit/applications/proto.py b/test/unit/applications/proto.py index ae1af354..244cb5be 100644 --- a/test/unit/applications/proto.py +++ b/test/unit/applications/proto.py @@ -1,5 +1,6 @@ import re import time + from unit.control import TestControl diff --git a/test/unit/applications/tls.py b/test/unit/applications/tls.py index 9213974a..e6a846b2 100644 --- a/test/unit/applications/tls.py +++ b/test/unit/applications/tls.py @@ -2,6 +2,7 @@ import os import re import ssl import subprocess + from unit.applications.proto import TestApplicationProto diff --git a/test/unit/applications/websockets.py b/test/unit/applications/websockets.py index fc15e8e4..e0dd2c0d 100644 --- a/test/unit/applications/websockets.py +++ b/test/unit/applications/websockets.py @@ -1,10 +1,11 @@ -import re -import random import base64 -import struct -import select import hashlib import itertools +import random +import re +import select +import struct + from unit.applications.proto import TestApplicationProto GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" diff --git a/test/unit/control.py b/test/unit/control.py index 0b344ed5..029072b5 100644 --- a/test/unit/control.py +++ b/test/unit/control.py @@ -1,4 +1,5 @@ import json + from unit.http import TestHTTP diff --git a/test/unit/feature/isolation.py b/test/unit/feature/isolation.py index 3f474993..4f33d04a 100644 --- a/test/unit/feature/isolation.py +++ b/test/unit/feature/isolation.py @@ -1,6 +1,5 @@ import os -import json -from unit.applications.proto import TestApplicationProto + from unit.applications.lang.go import TestApplicationGo from unit.applications.lang.java import TestApplicationJava from unit.applications.lang.node import TestApplicationNode @@ -8,6 +7,7 @@ from unit.applications.lang.perl import TestApplicationPerl from unit.applications.lang.php import TestApplicationPHP from unit.applications.lang.python import TestApplicationPython from unit.applications.lang.ruby import TestApplicationRuby +from unit.applications.proto import TestApplicationProto class TestFeatureIsolation(TestApplicationProto): diff --git a/test/unit/http.py b/test/unit/http.py index 13384dc8..de3bb2a4 100644 --- a/test/unit/http.py +++ b/test/unit/http.py @@ -1,11 +1,12 @@ import binascii import io +import json import os import re -import time -import json -import socket import select +import socket +import time + from unit.main import TestUnit diff --git a/test/unit/main.py b/test/unit/main.py index 4507f71a..408cf31c 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -1,17 +1,17 @@ +import argparse +import atexit +import fcntl import os +import platform import re -import sys -import stat -import time -import fcntl -import atexit import shutil import signal -import argparse -import platform +import stat +import subprocess +import sys import tempfile +import time import unittest -import subprocess from multiprocessing import Process @@ -52,9 +52,22 @@ class TestUnit(unittest.TestCase): type = self.application_type for module in self.prerequisites['modules']: if module in self.available['modules']: - for version in self.available['modules'][module]: - self.application_type = type + ' ' + version + prereq_version = self.prerequisites['modules'][module] + available_versions = self.available['modules'][module] + + if prereq_version == 'all': + for version in available_versions: + self.application_type = type + ' ' + version + self.application_version = version + super().run(result) + elif prereq_version == 'any': + self.application_type = type + ' ' + available_versions[0] super().run(result) + else: + for version in available_versions: + if version.startswith(prereq_version): + self.application_type = type + ' ' + version + super().run(result) @classmethod def main(cls): @@ -90,7 +103,7 @@ class TestUnit(unittest.TestCase): break if m is None: - unit.stop() + unit._print_log() exit("Unit is writing log too long") # discover available modules from unit.log @@ -153,7 +166,7 @@ class TestUnit(unittest.TestCase): self._run() def _run(self): - build_dir = self.pardir + '/build' + build_dir = os.path.join(self.pardir, 'build') self.unitd = build_dir + '/unitd' if not os.path.isfile(self.unitd): @@ -186,6 +199,7 @@ class TestUnit(unittest.TestCase): atexit.register(self.stop) if not self.waitforfiles(self.testdir + '/control.unit.sock'): + self._print_log() exit("Could not start unit") self.skip_alerts = [ @@ -398,4 +412,3 @@ class TestUnit(unittest.TestCase): data = f.read() print(data) - @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.17.0 -NXT_VERNUM=11700 +NXT_VERSION=1.18.0 +NXT_VERNUM=11800 |