From 8f6e6086cb7bceeff6eec7cf0a2e3ff94f8d7705 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Mon, 20 Apr 2020 14:04:55 +0100 Subject: Tests: skips adjusted. --- test/test_proxy.py | 9 ++++++++- test/test_share_fallback.py | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/test/test_proxy.py b/test/test_proxy.py index 74bd0873..b539d5f6 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -601,8 +601,14 @@ Content-Length: 10 'proxy ipv6 invalid 4', ) - @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 +631,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_share_fallback.py b/test/test_share_fallback.py index c51e43ee..3f288d89 100644 --- a/test/test_share_fallback.py +++ b/test/test_share_fallback.py @@ -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): -- cgit From af9392af1fec054119c8ddcd356ade0ea3fbf913 Mon Sep 17 00:00:00 2001 From: Andrei Belov Date: Thu, 23 Apr 2020 19:55:19 +0300 Subject: Packages: added Ubuntu 20.04 "focal" support. --- pkg/deb/Makefile | 12 ++++++++++++ pkg/deb/Makefile.jsc-common | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) 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 -- cgit From 6a9a4fe0d46263d4d158803d4a82851e240e0e63 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 24 Apr 2020 05:08:56 +0100 Subject: Tests: introduced module version specification in prerequisites. --- test/test_access_log.py | 2 +- test/test_configuration.py | 2 +- test/test_go_application.py | 2 +- test/test_go_isolation.py | 2 +- test/test_http_header.py | 2 +- test/test_java_application.py | 2 +- test/test_java_websockets.py | 2 +- test/test_node_application.py | 2 +- test/test_node_websockets.py | 2 +- test/test_perl_application.py | 2 +- test/test_php_application.py | 2 +- test/test_php_basic.py | 2 +- test/test_proxy.py | 2 +- test/test_python_application.py | 2 +- test/test_python_basic.py | 2 +- test/test_python_environment.py | 2 +- test/test_python_procman.py | 2 +- test/test_routing.py | 2 +- test/test_routing_tls.py | 2 +- test/test_ruby_application.py | 2 +- test/test_settings.py | 2 +- test/test_tls.py | 2 +- test/test_upstreams_rr.py | 2 +- test/test_usr1.py | 2 +- test/unit/main.py | 16 ++++++++++++++-- 25 files changed, 38 insertions(+), 26 deletions(-) diff --git a/test/test_access_log.py b/test/test_access_log.py index 898d8b24..9287097f 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -6,7 +6,7 @@ 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..24efc914 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -3,7 +3,7 @@ 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..cf78959a 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -7,7 +7,7 @@ from unit.feature.isolation import TestFeatureIsolation class TestGoIsolation(TestApplicationGo): - prerequisites = {'modules': ['go'], 'features': ['isolation']} + prerequisites = {'modules': {'go': 'any'}, 'features': ['isolation']} isolation = TestFeatureIsolation() diff --git a/test/test_http_header.py b/test/test_http_header.py index b773bd68..00d83f94 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -3,7 +3,7 @@ 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..0b4828c7 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -6,7 +6,7 @@ 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( diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 7ea04620..0f8f62a5 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -6,7 +6,7 @@ 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..ed32357d 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -3,7 +3,7 @@ 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..14921991 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -6,7 +6,7 @@ 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..612c0792 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -3,7 +3,7 @@ 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..746d61d5 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -5,7 +5,7 @@ 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_proxy.py b/test/test_proxy.py index b539d5f6..852e970f 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -6,7 +6,7 @@ from unit.applications.lang.python import TestApplicationPython class TestProxy(TestApplicationPython): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} SERVER_PORT = 7999 diff --git a/test/test_python_application.py b/test/test_python_application.py index 8d435b48..d7d39be2 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -8,7 +8,7 @@ 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_procman.py b/test/test_python_procman.py index a2e6126c..daa2c53d 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -6,7 +6,7 @@ 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_routing.py b/test/test_routing.py index ad793662..e4f13e29 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -3,7 +3,7 @@ from unit.applications.proto import TestApplicationProto class TestRouting(TestApplicationProto): - prerequisites = {'modules': ['python']} + prerequisites = {'modules': {'python': 'any'}} def setUp(self): super().setUp() 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..90ebd6a8 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -3,7 +3,7 @@ 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_settings.py b/test/test_settings.py index 9de3a928..0b471d4e 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -5,7 +5,7 @@ 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_tls.py b/test/test_tls.py index d9dcf237..c482bf5a 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -8,7 +8,7 @@ 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..56d4cbbb 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -5,7 +5,7 @@ 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..0627ca1d 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -5,7 +5,7 @@ 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/main.py b/test/unit/main.py index 4507f71a..074d053e 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -52,9 +52,21 @@ 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 + 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): -- cgit From f291f249383936d9823e095d4f17b6ed21f4b15c Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Tue, 12 May 2020 11:15:03 +0300 Subject: Version bump. --- version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version b/version index b5283235..f1cf1dc6 100644 --- a/version +++ b/version @@ -1,5 +1,5 @@ # Copyright (C) NGINX, Inc. -NXT_VERSION=1.17.0 -NXT_VERNUM=11700 +NXT_VERSION=1.18.0 +NXT_VERNUM=11800 -- cgit From 50f9816daab6c1f9f51764bd488cea0dd11ce965 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 12 May 2020 16:25:16 +0300 Subject: Blocking config change when applying the initial router config. --- src/nxt_controller.c | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/nxt_controller.c b/src/nxt_controller.c index f9b2cf26..d17b0cc6 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -47,6 +47,7 @@ static void nxt_controller_router_ready_handler(nxt_task_t *task, 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); @@ -103,6 +104,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; @@ -245,6 +247,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; } @@ -322,6 +326,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 +349,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; } @@ -961,7 +986,9 @@ 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_queue_is_empty(&nxt_controller_waiting_requests) + || nxt_controller_waiting_init_conf) + { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1076,7 +1103,9 @@ 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_queue_is_empty(&nxt_controller_waiting_requests) + || nxt_controller_waiting_init_conf) + { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1469,7 +1498,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 +1530,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); } -- cgit From 3ec72362b93432ddd8f8fb06f8dbc0bcd8a7e95d Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Tue, 12 May 2020 16:25:24 +0300 Subject: Waiting for router instead of reporting to user on config update. --- src/nxt_controller.c | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/nxt_controller.c b/src/nxt_controller.c index d17b0cc6..ea70cf78 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -76,6 +76,7 @@ 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); @@ -270,6 +271,8 @@ nxt_controller_send_current_conf(nxt_task_t *task) } nxt_controller_listening = 1; + + nxt_controller_flush_requests(task); } @@ -386,9 +389,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]; @@ -986,9 +988,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) - || nxt_controller_waiting_init_conf) - { + if (nxt_controller_check_postpone_request(task)) { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1085,10 +1085,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; } @@ -1103,9 +1099,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) - || nxt_controller_waiting_init_conf) - { + if (nxt_controller_check_postpone_request(task)) { nxt_queue_insert_tail(&nxt_controller_waiting_requests, &req->link); return; } @@ -1172,10 +1166,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; } @@ -1222,16 +1212,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); } -- cgit From d803ec39bc75622194802d249edc11fedc8e2441 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Tue, 12 May 2020 17:59:47 +0100 Subject: Tests: added respawn tests. --- test/test_respawn.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 test/test_respawn.py diff --git a/test/test_respawn.py b/test/test_respawn.py new file mode 100644 index 00000000..e7eef004 --- /dev/null +++ b/test/test_respawn.py @@ -0,0 +1,95 @@ +import re +import time +import subprocess +import unittest +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() -- cgit From 0174c971b5ec0d604e4e9becfa41e0bc31179e57 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 14 May 2020 13:15:00 +0300 Subject: Configuration: URI encoding in the "pass" option. This is useful to escape "/" in path fragments. For example, in order to reference the application named "foo/bar": { "pass": "applications/foo%2Fbar" } --- src/nxt_conf_validation.c | 60 +++++++++---------- src/nxt_http.h | 5 +- src/nxt_http_route.c | 112 ++++++++++++++++++++++++++---------- src/nxt_router.c | 7 ++- test/unit/applications/lang/node.py | 5 +- 5 files changed, 123 insertions(+), 66 deletions(-) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index bc03bdfb..476fc97b 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -1032,79 +1032,73 @@ 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[2]; nxt_conf_get_string(value, &pass); - p = nxt_memchr(pass.start, '/', pass.length); - - if (p != NULL) { - first.length = p - pass.start; - first.start = pass.start; + ret = nxt_http_pass_segments(vldt->pool, &pass, segments, 2); - 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; } 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) { 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)) { + 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 +1110,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; } diff --git a/src/nxt_http.h b/src/nxt_http.h index 841f5b40..6f593cd2 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -277,7 +277,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_route.c b/src/nxt_http_route.c index ca43c060..bb6e9dc9 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -201,9 +201,9 @@ static nxt_int_t nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, 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); @@ -1097,9 +1097,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 +1111,128 @@ 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_int_t ret; + nxt_str_t segments[2]; 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, 2); + 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; + } else if (nxt_str_eq(&segments[0], "upstreams", 9)) { + nxt_upstream_find(tmcf->router_conf->upstreams, &segments[1], action); - nxt_upstream_find(tmcf->router_conf->upstreams, &name, action); + } else if (nxt_str_eq(&segments[0], "routes", 6)) { + nxt_http_route_find(tmcf->router_conf->routes, &segments[1], action); + } - } else if (nxt_str_start(&name, "routes", 6)) { + return NXT_OK; +} - if (name.length == 6) { - name.length = 0; - name.start = NULL; - } else if (name.start[6] == '/') { - name.length -= 7; - name.start += 7; - } +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; - nxt_http_route_find(tmcf->router_conf->routes, &name, action); + 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; } diff --git a/src/nxt_router.c b/src/nxt_router.c index 93b750a0..a699effc 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1793,6 +1793,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 +1832,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); diff --git a/test/unit/applications/lang/node.py b/test/unit/applications/lang/node.py index d818298f..1f5d5027 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,5 +1,6 @@ import os import shutil +from urllib.parse import quote from unit.applications.proto import TestApplicationProto @@ -33,7 +34,9 @@ class TestApplicationNode(TestApplicationProto): self._load_conf( { - "listeners": {"*:7080": {"pass": "applications/" + script}}, + "listeners": { + "*:7080": {"pass": "applications/" + quote(script, '')} + }, "applications": { script: { "type": "external", -- cgit From 376d758dd72ac27f2bd5bb833ba68f5c9b531880 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 14 May 2020 13:15:01 +0300 Subject: PHP: implemented "targets" option. This allows to specify multiple subsequent targets inside PHP applications. For example: { "listeners": { "*:80": { "pass": "routes" } }, "routes": [ { "match": { "uri": "/info" }, "action": { "pass": "applications/my_app/phpinfo" } }, { "match": { "uri": "/hello" }, "action": { "pass": "applications/my_app/hello" } }, { "action": { "pass": "applications/my_app/rest" } } ], "applications": { "my_app": { "type": "php", "targets": { "phpinfo": { "script": "phpinfo.php", "root": "/www/data/admin", }, "hello": { "script": "hello.php", "root": "/www/data/test", }, "rest": { "root": "/www/data/example.com", "index": "index.php" }, } } } } --- src/nxt_application.h | 6 +- src/nxt_conf.h | 2 +- src/nxt_conf_validation.c | 202 ++++++++++++++++++--- src/nxt_http.h | 2 + src/nxt_http_request.c | 2 + src/nxt_http_route.c | 21 ++- src/nxt_main_process.c | 20 +-- src/nxt_php_sapi.c | 446 +++++++++++++++++++++++++--------------------- src/nxt_router.c | 64 +++++-- src/nxt_router.h | 3 + src/nxt_unit_request.h | 1 + 11 files changed, 509 insertions(+), 260 deletions(-) diff --git a/src/nxt_application.h b/src/nxt_application.h index e7177887..972a712b 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -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; }; 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 476fc97b..0f46560d 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; @@ -106,6 +117,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, @@ -630,6 +649,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 +687,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 +713,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 +822,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 +835,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"), @@ -1034,11 +1101,13 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, { nxt_str_t pass; nxt_int_t ret; - nxt_str_t segments[2]; + nxt_str_t segments[3]; + + static nxt_str_t targets_str = nxt_string("targets"); nxt_conf_get_string(value, &pass); - ret = nxt_http_pass_segments(vldt->pool, &pass, segments, 2); + ret = nxt_http_pass_segments(vldt->pool, &pass, segments, 3); if (ret != NXT_OK) { if (ret == NXT_DECLINED) { @@ -1067,12 +1136,26 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, 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(&segments[0], "upstreams", 9)) { - if (segments[1].length == 0) { + if (segments[1].length == 0 || segments[2].length != 0) { goto error; } @@ -1092,6 +1175,11 @@ nxt_conf_vldt_pass(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, } 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 (value == NULL) { @@ -1482,13 +1570,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); @@ -1522,7 +1614,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); } @@ -1933,6 +2025,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_http.h b/src/nxt_http.h index 6f593cd2..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; }; 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 bb6e9dc9..06bc91da 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1151,8 +1151,10 @@ static nxt_int_t nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_action_t *action) { - nxt_int_t ret; - nxt_str_t segments[2]; + 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 @@ -1164,7 +1166,7 @@ nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NXT_OK; } - ret = nxt_http_pass_segments(tmcf->mem_pool, &action->name, segments, 2); + ret = nxt_http_pass_segments(tmcf->mem_pool, &action->name, segments, 3); if (nxt_slow_path(ret != NXT_OK)) { return NXT_ERROR; } @@ -1173,6 +1175,17 @@ nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_listener_application(tmcf, &segments[1], action); nxt_router_app_use(task, action->u.application, 1); + if (segments[2].length != 0) { + targets = action->u.application->targets; + + for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++); + + action->target = i; + + } else { + action->target = 0; + } + } else if (nxt_str_eq(&segments[0], "upstreams", 9)) { nxt_upstream_find(tmcf->router_conf->upstreams, &segments[1], action); @@ -1298,6 +1311,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; } diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index eed37752..c35954c0 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -275,21 +275,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), }, { @@ -457,6 +445,8 @@ nxt_port_main_start_worker_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) } } + app_conf.self = conf; + ret = nxt_main_start_worker_process(task, task->thread->runtime, &app_conf, msg->port_msg.stream); diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index f5053652..ccbdd475 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -29,8 +29,20 @@ #define NXT_PHP7 1 #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; @@ -53,26 +65,27 @@ typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS); static nxt_int_t 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); +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 +223,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, }; @@ -232,6 +238,9 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { }; +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; @@ -241,11 +250,11 @@ static void ***tsrm_ls; static nxt_int_t nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) { - u_char *p, *tmp; - nxt_str_t ini_path; - nxt_str_t *root, *script_filename, *script_dirname, *script_name; - nxt_str_t *index; + 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; @@ -261,105 +270,33 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) 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,6 +367,10 @@ 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]; @@ -462,6 +403,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 +746,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; - - nxt_memzero(&file_handle, sizeof(file_handle)); + ctx.root = &target->root; + ctx.index = &target->index; - 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 +798,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 +825,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 +836,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 +891,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 +898,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 +1100,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 +1136,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_router.c b/src/nxt_router.c index a699effc..d94a34af 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; @@ -1272,6 +1273,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 +1430,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 +1509,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 +1537,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 +1553,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 +1603,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++) { + value = 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 +1668,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; @@ -1839,7 +1881,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: @@ -1847,7 +1889,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; @@ -4538,7 +4580,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; @@ -4992,6 +5034,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_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; -- cgit From ee1e248f4b038bb9e03fd78463da580af03c28f7 Mon Sep 17 00:00:00 2001 From: Axel Duch Date: Thu, 14 May 2020 12:29:06 +0200 Subject: Router: decode uri and args. --- src/nxt_conf_validation.c | 117 ++++++++++++++++++- src/nxt_http_route.c | 278 ++++++++++++++++++++++++++++++++++++---------- src/nxt_string.c | 43 ++++++- src/nxt_string.h | 3 + 4 files changed, 381 insertions(+), 60 deletions(-) diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 0f46560d..a7a8d139 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -85,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, @@ -343,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"), @@ -1379,6 +1389,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) diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 06bc91da..5010c561 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,23 +188,27 @@ 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); @@ -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,10 +1041,12 @@ 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; @@ -988,6 +1059,7 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, pattern->negative = 0; pattern->any = 1; + pattern->min_length = 0; if (test.length != 0) { @@ -1000,7 +1072,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 +1097,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 +1145,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; +} + + +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; + } + + str->length = end - start; + break; - test.start = &test.start[test.length + 1]; - test.length = length; + case NXT_HTTP_ROUTE_ENCODING_URI_PLUS: + start = str->start; - start = nxt_http_route_pattern_copy(mp, &test, pattern_case); - if (nxt_slow_path(start == NULL)) { + end = nxt_decode_uri_plus(start, start, str->length); + if (nxt_slow_path(end == NULL)) { return NXT_ERROR; } - pattern->start2 = start; + str->length = end - start; + break; + + default: + nxt_unreachable(); } return NXT_OK; @@ -1746,7 +1867,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; @@ -1766,39 +1888,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; + + continue; - } else if (c == '&') { + 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_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..7d1e044d 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); +NXT_EXPORT const uint8_t nxt_hex2int[256]; + #endif /* _NXT_STRING_H_INCLUDED_ */ -- cgit From 26f407e24ad301e50b3abeea3a7c390577928c00 Mon Sep 17 00:00:00 2001 From: Axel Duch Date: Thu, 14 May 2020 12:29:22 +0200 Subject: Tests: decode uri and args. --- test/test_routing.py | 193 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 158 insertions(+), 35 deletions(-) diff --git a/test/test_routing.py b/test/test_routing.py index e4f13e29..9e45707e 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import unittest from unit.applications.proto import TestApplicationProto @@ -1069,6 +1071,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 +1105,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.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.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='/?%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 +1246,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 +1300,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"}}) -- cgit From cf73fb809395da2a97e4a11a1185647e1d2b1d3d Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 15 May 2020 04:20:45 +0100 Subject: Tests: test_proxy_invalid simplified. --- test/test_proxy.py | 99 +++++++++++------------------------------------------- 1 file changed, 20 insertions(+), 79 deletions(-) diff --git a/test/test_proxy.py b/test/test_proxy.py index 852e970f..c163b14d 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -521,85 +521,26 @@ 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') def test_proxy_loop(self): self.skip_alerts.extend( -- cgit From ce4a2bbd05f42d258f9bf7880060a604ac1a866e Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 15 May 2020 04:20:56 +0100 Subject: Tests: style. --- test/run.py | 5 ++--- test/test_access_log.py | 3 +-- test/test_configuration.py | 1 + test/test_go_isolation.py | 4 ++-- test/test_http_header.py | 1 + test/test_java_application.py | 2 +- test/test_java_websockets.py | 3 ++- test/test_node_application.py | 1 + test/test_node_websockets.py | 3 ++- test/test_perl_application.py | 1 + test/test_php_application.py | 2 +- test/test_proxy.py | 3 ++- test/test_python_application.py | 4 ++-- test/test_python_procman.py | 3 ++- test/test_respawn.py | 4 ++-- test/test_return.py | 2 +- test/test_routing.py | 3 ++- test/test_ruby_application.py | 1 + test/test_settings.py | 3 ++- test/test_share_fallback.py | 2 +- test/test_static.py | 1 + test/test_tls.py | 2 +- test/test_upstreams_rr.py | 2 +- test/test_usr1.py | 2 +- test/unit/applications/lang/go.py | 1 + test/unit/applications/lang/java.py | 3 ++- test/unit/applications/lang/node.py | 1 + test/unit/applications/proto.py | 1 + test/unit/applications/tls.py | 1 + test/unit/applications/websockets.py | 9 +++++---- test/unit/control.py | 1 + test/unit/feature/isolation.py | 4 ++-- test/unit/http.py | 7 ++++--- test/unit/main.py | 17 ++++++++--------- 34 files changed, 60 insertions(+), 43 deletions(-) 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 9287097f..3ef8f7a0 100644 --- a/test/test_access_log.py +++ b/test/test_access_log.py @@ -1,7 +1,6 @@ -import os -import re import time import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_configuration.py b/test/test_configuration.py index 24efc914..0329ef5e 100644 --- a/test/test_configuration.py +++ b/test/test_configuration.py @@ -1,4 +1,5 @@ import unittest + from unit.control import TestControl diff --git a/test/test_go_isolation.py b/test/test_go_isolation.py index cf78959a..e6aade9b 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -1,7 +1,7 @@ -import pwd import grp -import json +import pwd import unittest + from unit.applications.lang.go import TestApplicationGo from unit.feature.isolation import TestFeatureIsolation diff --git a/test/test_http_header.py b/test/test_http_header.py index 00d83f94..ea4520c1 100644 --- a/test/test_http_header.py +++ b/test/test_http_header.py @@ -1,4 +1,5 @@ import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_java_application.py b/test/test_java_application.py index 0b4828c7..3051ddea 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -1,7 +1,7 @@ import io import os import time -import unittest + from unit.applications.lang.java import TestApplicationJava diff --git a/test/test_java_websockets.py b/test/test_java_websockets.py index 0f8f62a5..d78f7263 100644 --- a/test/test_java_websockets.py +++ b/test/test_java_websockets.py @@ -1,6 +1,7 @@ -import time import struct +import time import unittest + from unit.applications.lang.java import TestApplicationJava from unit.applications.websockets import TestApplicationWebsocket diff --git a/test/test_node_application.py b/test/test_node_application.py index ed32357d..e46cc6a1 100644 --- a/test/test_node_application.py +++ b/test/test_node_application.py @@ -1,4 +1,5 @@ import unittest + from unit.applications.lang.node import TestApplicationNode diff --git a/test/test_node_websockets.py b/test/test_node_websockets.py index 14921991..1928d8c9 100644 --- a/test/test_node_websockets.py +++ b/test/test_node_websockets.py @@ -1,6 +1,7 @@ -import time import struct +import time import unittest + from unit.applications.lang.node import TestApplicationNode from unit.applications.websockets import TestApplicationWebsocket diff --git a/test/test_perl_application.py b/test/test_perl_application.py index 612c0792..dbf6abf7 100644 --- a/test/test_perl_application.py +++ b/test/test_perl_application.py @@ -1,4 +1,5 @@ import unittest + from unit.applications.lang.perl import TestApplicationPerl diff --git a/test/test_php_application.py b/test/test_php_application.py index 746d61d5..1259d22d 100644 --- a/test/test_php_application.py +++ b/test/test_php_application.py @@ -1,7 +1,7 @@ import os -import re import shutil import unittest + from unit.applications.lang.php import TestApplicationPHP class TestPHPApplication(TestApplicationPHP): diff --git a/test/test_proxy.py b/test/test_proxy.py index c163b14d..feec1ac4 100644 --- a/test/test_proxy.py +++ b/test/test_proxy.py @@ -1,7 +1,8 @@ import re -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_python_application.py b/test/test_python_application.py index d7d39be2..8bd3f750 100644 --- a/test/test_python_application.py +++ b/test/test_python_application.py @@ -1,9 +1,9 @@ -import re -import os import grp import pwd +import re import time import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_python_procman.py b/test/test_python_procman.py index daa2c53d..8613f58e 100644 --- a/test/test_python_procman.py +++ b/test/test_python_procman.py @@ -1,7 +1,8 @@ import re -import time import subprocess +import time import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_respawn.py b/test/test_respawn.py index e7eef004..f1c71a20 100644 --- a/test/test_respawn.py +++ b/test/test_respawn.py @@ -1,7 +1,7 @@ import re -import time import subprocess -import unittest +import time + from unit.applications.lang.python import TestApplicationPython 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 9e45707e..5c4de519 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- - import unittest + from unit.applications.proto import TestApplicationProto @@ -1871,5 +1871,6 @@ class TestRouting(TestApplicationProto): self.assertEqual(self.get()['status'], 200, 'proxy') + if __name__ == '__main__': TestRouting.main() diff --git a/test/test_ruby_application.py b/test/test_ruby_application.py index 90ebd6a8..4709df6c 100644 --- a/test/test_ruby_application.py +++ b/test/test_ruby_application.py @@ -1,4 +1,5 @@ import unittest + from unit.applications.lang.ruby import TestApplicationRuby diff --git a/test/test_settings.py b/test/test_settings.py index 0b471d4e..6600358d 100644 --- a/test/test_settings.py +++ b/test/test_settings.py @@ -1,6 +1,7 @@ -import time import socket +import time import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_share_fallback.py b/test/test_share_fallback.py index 3f288d89..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 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 c482bf5a..a0434174 100644 --- a/test/test_tls.py +++ b/test/test_tls.py @@ -1,9 +1,9 @@ import io -import os import re import ssl import subprocess import unittest + from unit.applications.tls import TestApplicationTLS diff --git a/test/test_upstreams_rr.py b/test/test_upstreams_rr.py index 56d4cbbb..2f74fbde 100644 --- a/test/test_upstreams_rr.py +++ b/test/test_upstreams_rr.py @@ -1,6 +1,6 @@ import os import re -import unittest + from unit.applications.lang.python import TestApplicationPython diff --git a/test/test_usr1.py b/test/test_usr1.py index 0627ca1d..d1db652f 100644 --- a/test/test_usr1.py +++ b/test/test_usr1.py @@ -1,6 +1,6 @@ import os -import unittest from subprocess import call + from unit.applications.lang.python import TestApplicationPython diff --git a/test/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index e0f83c0a..9070beb6 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 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 1f5d5027..cf2a99f6 100644 --- a/test/unit/applications/lang/node.py +++ b/test/unit/applications/lang/node.py @@ -1,6 +1,7 @@ import os import shutil from urllib.parse import quote + from unit.applications.proto import TestApplicationProto 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 074d053e..42918c9d 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 @@ -410,4 +410,3 @@ class TestUnit(unittest.TestCase): data = f.read() print(data) - -- cgit From ea841400f543fa4139adca926fefdd8565c227d2 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 15 May 2020 04:21:10 +0100 Subject: Tests: added test for encoding in the "pass" option. --- test/test_routing.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/test_routing.py b/test/test_routing.py index 5c4de519..3cf4009c 100644 --- a/test/test_routing.py +++ b/test/test_routing.py @@ -181,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( { -- cgit From 82471c1dd33fc13a18a334113b2c220cc06f3d5b Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Fri, 15 May 2020 04:21:25 +0100 Subject: Tests: added tests for "targets" option. --- test/php/targets/1.php | 4 ++ test/php/targets/2/2.php | 4 ++ test/php/targets/index.php | 4 ++ test/test_php_targets.py | 129 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 test/php/targets/1.php create mode 100644 test/php/targets/2/2.php create mode 100644 test/php/targets/index.php create mode 100644 test/test_php_targets.py 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 @@ + 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 @@ + 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 @@ + 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() -- cgit From 79f5e531fe6451eccff1c4dc14826c30f922ead1 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 15 May 2020 17:08:37 +0300 Subject: Router: removed two unused assignments. This should resolve some static analyzers warnings. --- src/nxt_http_route.c | 3 --- src/nxt_router.c | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 5010c561..a8a6b181 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -1050,9 +1050,6 @@ nxt_http_route_pattern_create(nxt_task_t *task, nxt_mp_t *mp, 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); diff --git a/src/nxt_router.c b/src/nxt_router.c index d94a34af..4093db8d 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -1614,8 +1614,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, next_target = 0; for (i = 0; i < n; i++) { - value = nxt_conf_next_object_member(apcf.targets_value, - &target, &next_target); + (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)) { -- cgit From d0de6df83987a7e8e25fab9ba5f274b993c7f094 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Fri, 15 May 2020 21:32:07 +0300 Subject: Fixed global constant declaration (appeared in 9af10e099d09). This fixes building with GCC 10, which is default to -fno-common. See: https://gcc.gnu.org/gcc-10/porting_to.html --- src/nxt_string.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nxt_string.h b/src/nxt_string.h index 7d1e044d..3f9192e2 100644 --- a/src/nxt_string.h +++ b/src/nxt_string.h @@ -180,7 +180,7 @@ 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); -NXT_EXPORT const uint8_t nxt_hex2int[256]; +extern const uint8_t nxt_hex2int[256]; #endif /* _NXT_STRING_H_INCLUDED_ */ -- cgit From 140b81208e83569913aa81f964eb64e15940d897 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 20 May 2020 11:18:03 +0300 Subject: PHP: building with PHP 8 (development version). --- auto/modules/php | 6 +++++- src/nxt_php_sapi.c | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) 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/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index ccbdd475..d3c23c31 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -29,6 +29,14 @@ #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; -- cgit From b2e6ef7bebff632fc64e10bdb03b5da02817d172 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Wed, 20 May 2020 11:18:03 +0300 Subject: Static: fixed potential undefined behavior in memcpy(). According to the C standard, pointer arguments passed to memcpy() calls shall still have valid values. NULL is considered as invalid. Found with GCC Static Analyzer. --- src/nxt_http_static.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } -- cgit From fa4d4b61200b6f465edbe24ebcdce1a7a8675d39 Mon Sep 17 00:00:00 2001 From: Andrei Zeliankou Date: Wed, 20 May 2020 23:06:56 +0100 Subject: Tests: print unit.log in case of errors. Thanks to hongzhidao. --- test/unit/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/main.py b/test/unit/main.py index 42918c9d..d415f58f 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -102,7 +102,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 @@ -198,6 +198,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 = [ -- cgit From 89b1e88f8f0b1d66a19ac9657c9568ef5fb0ff27 Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 May 2020 12:40:49 +0300 Subject: Closing unsent file descriptors from port queue. After a process exits, all ports linked to it from other processes should be closed. All unsent file descriptors in port queue, marked as "close after send", should be closed to avoid resource leakage. --- src/nxt_port_socket.c | 6 ++++++ 1 file changed, 6 insertions(+) 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; -- cgit From de368f033dd2d22312269010f20f7f6388ecd95f Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 May 2020 12:40:54 +0300 Subject: Added NULL check for engine->port. This is required to handle REMOVE_PID messages if router engine initialization is incomplete. --- src/nxt_router.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nxt_router.c b/src/nxt_router.c index 4093db8d..b4cba08b 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -944,8 +944,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; -- cgit From aacf11152c314efb1895b6d44ba72dc9f1801c7d Mon Sep 17 00:00:00 2001 From: Max Romanov Date: Thu, 28 May 2020 12:41:00 +0300 Subject: Moving nxt_stream_ident to shared memory. This aims to avoid stream id clashes after router restart. --- src/nxt_port_rpc.c | 28 +++++++++++++++++++++++++--- src/nxt_port_rpc.h | 2 ++ src/nxt_runtime.c | 4 ++++ 3 files changed, 31 insertions(+), 3 deletions(-) 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 -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_runtime.c b/src/nxt_runtime.c index bcd156ee..ea01f06f 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -118,6 +118,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); -- cgit From e9e5ddd5a5d9ce99768833137eac2551a710becf Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Mon, 9 Mar 2020 16:28:25 +0000 Subject: Refactor of process management. The process abstraction has changed to: setup(task, process) start(task, process_data) prefork(task, process, mp) The prefork() occurs in the main process right before fork. The file src/nxt_main_process.c is completely free of process specific logic. The creation of a process now supports a PROCESS_CREATED state. The The setup() function of each process can set its state to either created or ready. If created, a MSG_PROCESS_CREATED is sent to main process, where external setup can be done (required for rootfs under container). The core processes (discovery, controller and router) doesn't need external setup, then they all proceeds to their start() function straight away. In the case of applications, the load of the module happens at the process setup() time and The module's init() function has changed to be the start() of the process. The module API has changed to: setup(task, process, conf) start(task, data) As a direct benefit of the PROCESS_CREATED message, the clone(2) of processes using pid namespaces now doesn't need to create a pipe to make the child block until parent setup uid/gid mappings nor it needs to receive the child pid. --- auto/sources | 2 +- src/nxt_application.c | 363 +++++++++++++- src/nxt_application.h | 11 +- src/nxt_cert.c | 11 +- src/nxt_cert.h | 2 +- src/nxt_clone.h | 14 +- src/nxt_controller.c | 142 +++++- src/nxt_external.c | 11 +- src/nxt_java.c | 61 +-- src/nxt_main_process.c | 1049 ++++++++++------------------------------- src/nxt_main_process.h | 19 +- src/nxt_php_sapi.c | 32 +- src/nxt_port.c | 4 +- src/nxt_port.h | 141 +++--- src/nxt_port_memory.c | 2 +- src/nxt_process.c | 534 ++++++++++++++------- src/nxt_process.h | 137 ++++-- src/nxt_process_type.h | 2 +- src/nxt_python_wsgi.c | 13 +- src/nxt_router.c | 44 +- src/nxt_runtime.c | 60 ++- src/nxt_runtime.h | 1 + src/nxt_signal_handlers.c | 67 +++ src/nxt_unit.c | 2 +- src/nxt_worker_process.c | 118 ----- src/perl/nxt_perl_psgi.c | 11 +- src/ruby/nxt_ruby.c | 20 +- test/test_java_application.py | 1 + 28 files changed, 1543 insertions(+), 1331 deletions(-) create mode 100644 src/nxt_signal_handlers.c delete mode 100644 src/nxt_worker_process.c diff --git a/auto/sources b/auto/sources index c6b34bbc..4ac132dd 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 \ diff --git a/src/nxt_application.c b/src/nxt_application.c index bebe3907..6de82257 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -25,6 +25,8 @@ typedef struct { } 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 +36,27 @@ 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 nxt_int_t nxt_app_isolation(nxt_task_t *task, + nxt_conf_value_t *isolation, nxt_process_t *process); + +#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); +#endif + +#if (NXT_HAVE_CLONE_NEWUSER) +static nxt_int_t nxt_app_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 + +nxt_str_t nxt_server = nxt_string(NXT_SERVER); static uint32_t compat[] = { @@ -42,14 +64,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 +118,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; @@ -301,18 +362,85 @@ 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; + nxt_int_t ret; + nxt_runtime_t *rt; + nxt_common_app_conf_t *app_conf; + + rt = task->thread->runtime; + app_conf = process->data.app; + cap_setid = rt->capabilities.setid; + + if (app_conf->isolation != NULL) { + ret = nxt_app_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; + } +#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,8 +460,8 @@ nxt_app_start(nxt_task_t *task, void *data) } } - if (nxt_app->pre_init != NULL) { - ret = nxt_app->pre_init(task, data); + if (nxt_app->setup != NULL) { + ret = nxt_app->setup(task, process, app_conf); if (nxt_slow_path(ret != NXT_OK)) { return ret; @@ -360,16 +488,13 @@ nxt_app_start(nxt_task_t *task, void *data) 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 +554,206 @@ nxt_app_set_environment(nxt_conf_value_t *environment) } +static nxt_int_t +nxt_app_isolation(nxt_task_t *task, nxt_conf_value_t *isolation, + nxt_process_t *process) +{ +#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_app_clone_flags(task, obj, &process->isolation.clone); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } +#endif + +#if (NXT_HAVE_CLONE_NEWUSER) + ret = nxt_app_isolation_creds(task, isolation, process); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } +#endif + + return NXT_OK; +} + + +#if (NXT_HAVE_CLONE_NEWUSER) + +static nxt_int_t +nxt_app_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 + + + nxt_app_lang_module_t * nxt_app_lang_module(nxt_runtime_t *rt, nxt_str_t *name) { @@ -539,7 +864,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 972a712b..b4231e3b 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 { @@ -37,9 +39,6 @@ typedef struct { } 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; @@ -111,10 +110,8 @@ 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); + nxt_application_setup_t setup; + nxt_process_start_t start; }; 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..c2066ce6 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,11 @@ typedef struct { pid_t nxt_clone(nxt_int_t flags); -#if (NXT_HAVE_CLONE_NEWUSER) +#define nxt_is_clone_flag_set(flags, test) \ + ((flags & CLONE_##test) == CLONE_##test) + -#define NXT_CLONE_USER(flags) \ - ((flags & CLONE_NEWUSER) == CLONE_NEWUSER) +#if (NXT_HAVE_CLONE_NEWUSER) 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 +51,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_controller.c b/src/nxt_controller.c index ea70cf78..a61c127d 100644 --- a/src/nxt_controller.c +++ b/src/nxt_controller.c @@ -39,11 +39,17 @@ 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); @@ -83,6 +89,8 @@ static void nxt_controller_process_cert(nxt_task_t *task, 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); @@ -114,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; @@ -147,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; @@ -170,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"); @@ -295,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) { diff --git a/src/nxt_external.c b/src/nxt_external.c index e498a938..58523525 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -9,8 +9,7 @@ #include -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,7 @@ nxt_app_module_t nxt_external_module = { nxt_string("external"), "*", NULL, - nxt_external_init, + nxt_external_start, }; @@ -58,7 +57,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 +70,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 +100,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_java.c b/src/nxt_java.c index 004907d6..c4145c1d 100644 --- a/src/nxt_java.c +++ b/src/nxt_java.c @@ -27,9 +27,10 @@ #include "nxt_jars.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 +50,8 @@ 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_setup, + nxt_java_start, }; typedef struct { @@ -60,7 +61,8 @@ 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) { const char *unit_jars; @@ -115,24 +117,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 +144,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 +343,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 +358,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 +372,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_process.c b/src/nxt_main_process.c index c35954c0..0dff050b 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -36,20 +36,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 +51,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 +63,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 +75,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 +91,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); } @@ -350,48 +269,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; - start += app_conf.name.length + 1; + process->name = nxt_mp_alloc(process->mem_pool, app_conf->name.length + + sizeof("\"\" application") + 1); - conf = nxt_conf_json_parse(mp, start, b->mem.free, NULL); + 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'; + 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"); @@ -400,44 +342,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"); @@ -445,40 +388,91 @@ nxt_port_main_start_worker_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) } } - app_conf.self = conf; + app_conf->self = conf; - ret = nxt_main_start_worker_process(task, task->thread->runtime, - &app_conf, msg->port_msg.stream); + 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, }; @@ -499,16 +493,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; } @@ -541,234 +536,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);; -} + process->name = init.name; + process->user_cred = &rt->user_cred; + pinit = nxt_process_init(process); + *pinit = 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; - - ret = nxt_main_create_worker_process(task, rt, init); - - if (ret == NXT_OK) { - if (conf->start != NULL) { - nxt_free(conf->start); - } - -#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); + return process; } static nxt_int_t -nxt_main_create_router_process(nxt_task_t *task, nxt_runtime_t *rt, - nxt_process_init_t *init) +nxt_main_start_process(nxt_task_t *task, nxt_process_t *process) { - nxt_main_stop_worker_processes(task, rt); - - return nxt_main_create_worker_process(task, rt, init); -} - - -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_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; } @@ -776,10 +605,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); @@ -788,15 +631,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. */ @@ -804,35 +645,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; } -} - -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) { +fail: - (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) { @@ -1009,7 +837,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); } } @@ -1023,101 +851,80 @@ 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; + name = process->name; + stream = process->stream; + init = *((nxt_process_init_t *) nxt_process_init(process)); - ptype = nxt_process_type(process); - restart = nxt_process_restarts[ptype]; + if (process->state == NXT_PROCESS_STATE_READY) { + process->stream = 0; + } - if (process->ready) { - init->stream = 0; - } + nxt_process_close_ports(task, process); - nxt_process_close_ports(task, process); + if (nxt_exiting) { + if (rt->nprocesses <= 1) { + nxt_runtime_quit(task, 0); + } - if (nxt_exiting) { - nxt_mp_destroy(init->mem_pool); + return; + } - if (rt->nprocesses <= 2) { - nxt_runtime_quit(task, 0); - } + nxt_runtime_process_each(rt, process) { - return; + if (process->pid == nxt_pid + || process->pid == pid + || nxt_queue_is_empty(&process->ports)) + { + continue; } - nxt_runtime_process_each(rt, process) { + port = nxt_process_port_first(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[init.type][port->type] == 0) { + continue; + } - 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)); - buf = nxt_buf_mem_ts_alloc(task, task->thread->engine->mem_pool, - sizeof(pid)); - if (nxt_slow_path(buf == NULL)) { - continue; - } + if (nxt_slow_path(buf == NULL)) { + continue; + } - buf->mem.free = nxt_cpymem(buf->mem.free, &pid, sizeof(pid)); + 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; + nxt_port_socket_write(task, port, NXT_PORT_MSG_REMOVE_PID, -1, + stream, 0, buf); - if (restart != NULL) { - restart(task, rt, init); + } nxt_runtime_process_loop; - } else { - nxt_mp_destroy(init->mem_pool); + 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); } } } -static void -nxt_main_stop_worker_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) { - - if (port->type == NXT_PROCESS_WORKER) { - (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_main_port_socket_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) { @@ -1419,10 +1226,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); } } @@ -1534,362 +1346,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 d3c23c31..ddad5761 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -72,7 +72,7 @@ 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, @@ -242,7 +242,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("php"), PHP_VERSION, NULL, - nxt_php_init, + nxt_php_start, }; @@ -256,19 +256,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; - 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; + 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"); @@ -276,6 +277,7 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) nxt_php_task = task; + conf = data->app; c = &conf->u.php; n = (c->targets != NULL) ? nxt_conf_object_members_count(c->targets) : 1; @@ -385,7 +387,7 @@ nxt_php_init(nxt_task_t *task, nxt_common_app_conf_t *conf) 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; 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_process.c b/src/nxt_process.c index f5959edf..e84549b3 100644 --- a/src/nxt_process.c +++ b/src/nxt_process.c @@ -13,9 +13,18 @@ #include -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); +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); + /* A cached process pid. */ nxt_pid_t nxt_pid; @@ -47,62 +56,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. */ + nxt_pid = nxt_getpid(); - 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 = 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 +119,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 +132,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 +139,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,78 +180,24 @@ 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; - } - -#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_slow_path(ret != NXT_OK)) { - goto fail; - } - } -#endif - - ret = NXT_OK; - - if (nxt_slow_path(write(pipefd[1], &ret, sizeof(ret)) == -1)) { - nxt_alert(task, "failed to write status"); - goto fail; - } - process->pid = pid; nxt_runtime_process_add(task, process); - goto cleanup; - -fail: - - ret = NXT_ERROR; - - if (nxt_slow_path(write(pipefd[1], &ret, sizeof(ret)) == -1)) { - nxt_alert(task, "failed to write status"); - } - - waitpid(pid, NULL, 0); - - pid = -1; - -cleanup: - - if (nxt_slow_path(close(pipefd[0]) != 0)) { - nxt_alert(task, "failed to close pipe: %E", nxt_errno); - } - - if (nxt_slow_path(close(pipefd[1]) != 0)) { - nxt_alert(task, "failed to close pipe: %E", nxt_errno); - } - return pid; } -static void -nxt_process_start(nxt_task_t *task, nxt_process_t *process) +static nxt_int_t +nxt_process_setup(nxt_task_t *task, nxt_process_t *process) { - nxt_int_t ret, cap_setid; + nxt_int_t ret; nxt_port_t *port, *main_port; nxt_thread_t *thread; nxt_runtime_t *rt; @@ -272,37 +205,17 @@ nxt_process_start(nxt_task_t *task, nxt_process_t *process) nxt_event_engine_t *engine; const nxt_event_interface_t *interface; - init = process->init; + init = nxt_process_init(process); - nxt_log(task, NXT_LOG_INFO, "%s started", init->name); + nxt_debug(task, "%s setup", process->name); - nxt_process_title(task, "unit: %s", init->name); + nxt_process_title(task, "unit: %s", process->name); thread = task->thread; rt = thread->runtime; nxt_random_init(&thread->random); - cap_setid = rt->capabilities.setid; - -#if (NXT_HAVE_CLONE_NEWUSER) - if (!cap_setid && NXT_CLONE_USER(init->isolation.clone.flags)) { - cap_setid = 1; - } -#endif - - if (cap_setid) { - ret = nxt_credential_setgids(task, init->user_cred); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - - ret = nxt_credential_setuid(task, init->user_cred); - if (nxt_slow_path(ret != NXT_OK)) { - goto fail; - } - } - rt->type = init->type; engine = thread->engine; @@ -312,17 +225,17 @@ nxt_process_start(nxt_task_t *task, nxt_process_t *process) interface = nxt_service_get(rt->services, "engine", rt->engine); if (nxt_slow_path(interface == NULL)) { - goto fail; + return NXT_ERROR; } if (nxt_event_engine_change(engine, interface, rt->batch) != NXT_OK) { - goto fail; + return NXT_ERROR; } ret = nxt_runtime_thread_pool_create(thread, rt, rt->auxiliary_threads, 60000 * 1000000LL); if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + return NXT_ERROR; } main_port = rt->port_by_type[NXT_PROCESS_MAIN]; @@ -334,28 +247,282 @@ nxt_process_start(nxt_task_t *task, nxt_process_t *process) nxt_port_write_close(port); - ret = init->start(task, init->data); + nxt_port_enable(task, port, init->port_handlers); + + ret = init->setup(task, process); if (nxt_slow_path(ret != NXT_OK)) { - goto fail; + return NXT_ERROR; } - nxt_port_enable(task, port, init->port_handlers); + switch (process->state) { - ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_PROCESS_READY, - -1, init->stream, 0, NULL); + 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)) { + break; + } + + ret = init->start(task, &process->data); + break; + + default: + nxt_assert(0); + } if (nxt_slow_path(ret != NXT_OK)) { - nxt_log(task, NXT_LOG_ERR, "failed to send READY message to main"); + nxt_alert(task, "%s failed to start", process->name); + } + + 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; + } + + ret = nxt_port_socket_write(task, main_port, NXT_PORT_MSG_PROCESS_CREATED, + -1, stream, my_port->id, NULL); + + 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; +} + + +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; } - return; + nxt_log(task, NXT_LOG_INFO, "%s started", process->name); + + ret = init->start(task, &process->data); fail: - exit(1); + 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; + + ret = nxt_process_apply_creds(task, process); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + process->state = NXT_PROCESS_STATE_READY; + + return NXT_OK; +} + + +#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_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; + } + + 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 + + +nxt_int_t +nxt_process_creds_set(nxt_task_t *task, nxt_process_t *process, nxt_str_t *user, + nxt_str_t *group) +{ + char *str; + + process->user_cred = nxt_mp_zalloc(process->mem_pool, + sizeof(nxt_credential_t)); + + if (nxt_slow_path(process->user_cred == NULL)) { + return NXT_ERROR; + } + + str = nxt_mp_zalloc(process->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'; + + 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 && 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, process->user_cred); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + + ret = nxt_credential_setuid(task, process->user_cred); + if (nxt_slow_path(ret != NXT_OK)) { + return NXT_ERROR; + } + } + + return NXT_OK; +} + + +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 +792,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..45bab25e 100644 --- a/src/nxt_process.h +++ b/src/nxt_process.h @@ -8,68 +8,123 @@ #define _NXT_PROCESS_H_INCLUDED_ #if (NXT_HAVE_CLONE) +#include #include #endif -typedef pid_t nxt_pid_t; +#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); -struct nxt_process_init_s { - nxt_mp_t *mem_pool; - nxt_process_start_t start; - const char *name; - nxt_credential_t *user_cred; +typedef struct nxt_common_app_conf_s nxt_common_app_conf_t; - 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 { - 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 */ - nxt_process_init_t *init; + uint32_t stream; - nxt_port_mmaps_t incoming; - nxt_port_mmaps_t outgoing; + nxt_mp_t *mem_pool; + nxt_credential_t *user_cred; - nxt_thread_mutex_t cp_mutex; - nxt_lvlhsh_t connected_ports; /* of nxt_port_t */ + nxt_process_data_t data; + + union { +#if (NXT_HAVE_CLONE) + nxt_clone_t clone; +#endif + } 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 +139,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 +171,18 @@ 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); -void nxt_init_destroy(nxt_runtime_t *rt, nxt_process_init_t *init); +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 #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..089d15c0 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -65,7 +65,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); @@ -116,7 +117,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("python"), PY_VERSION, NULL, - nxt_python_init, + nxt_python_start, }; @@ -211,7 +212,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 +220,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 +231,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 +413,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 b4cba08b..788199c7 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -75,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); @@ -268,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, @@ -282,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; @@ -292,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)) { @@ -382,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); @@ -862,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; } @@ -2400,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); diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index ea01f06f..d7e35dec 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); @@ -438,7 +439,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; } } @@ -478,6 +479,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) { @@ -525,6 +570,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; @@ -1306,7 +1355,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; } @@ -1347,8 +1398,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 +#include +#include +#include +#include + + +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_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_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 -#include -#include -#include -#include - - -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..5e9200dc 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); @@ -119,7 +119,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("perl"), PERL_VERSION_STRING, NULL, - nxt_perl_psgi_init, + nxt_perl_psgi_start, }; @@ -1134,14 +1134,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..40f72f51 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -28,7 +28,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); @@ -78,21 +79,24 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("ruby"), ruby_version, 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/test_java_application.py b/test/test_java_application.py index 3051ddea..0cb18c25 100644 --- a/test/test_java_application.py +++ b/test/test_java_application.py @@ -13,6 +13,7 @@ class TestJavaApplication(TestApplicationJava): [ r'realpath.*failed', r'failed to apply new conf', + r'application setup failed', ] ) self.assertIn( -- cgit From e2b53e16c60ba1e3bbbe59172c184e97f889326b Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Thu, 28 May 2020 14:57:41 +0100 Subject: Added "rootfs" feature. --- auto/isolation | 130 +++++++++++++ auto/modules/java | 31 ++- auto/modules/python | 34 +++- auto/modules/ruby | 38 ++++ auto/sources | 5 + src/nxt_application.c | 481 ++++++++++++++++++++++++++++++++++++++++++---- src/nxt_application.h | 4 + src/nxt_array.c | 39 ++++ src/nxt_array.h | 3 +- src/nxt_capability.c | 5 + src/nxt_capability.h | 3 +- src/nxt_clone.h | 3 + src/nxt_conf_validation.c | 18 ++ src/nxt_external.c | 2 + src/nxt_fs.c | 163 ++++++++++++++++ src/nxt_fs.h | 36 ++++ src/nxt_java.c | 72 ++++++- src/nxt_main.h | 1 + src/nxt_main_process.c | 89 ++++++++- src/nxt_php_sapi.c | 2 + src/nxt_process.c | 358 ++++++++++++++++++++++++++++++++++ src/nxt_process.h | 53 +++-- src/nxt_python_wsgi.c | 9 + src/nxt_runtime.c | 1 + src/nxt_unix.h | 3 + src/perl/nxt_perl_psgi.c | 6 + src/ruby/nxt_ruby.c | 5 + 27 files changed, 1520 insertions(+), 74 deletions(-) create mode 100644 src/nxt_fs.c create mode 100644 src/nxt_fs.h 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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/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 4ac132dd..2075ca0f 100644 --- a/auto/sources +++ b/auto/sources @@ -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/src/nxt_application.c b/src/nxt_application.c index 6de82257..566bf256 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -17,11 +17,16 @@ #include +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) +#include +#endif + typedef struct { nxt_app_type_t type; nxt_str_t version; nxt_str_t file; + nxt_array_t *mounts; } nxt_module_t; @@ -40,22 +45,40 @@ 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 nxt_int_t nxt_app_isolation(nxt_task_t *task, +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_isolation_creds(nxt_task_t *task, +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); @@ -154,16 +177,17 @@ 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) { - 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; @@ -206,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); @@ -225,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: @@ -245,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. @@ -328,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()); } @@ -369,17 +474,23 @@ nxt_discovery_quit(nxt_task_t *task, nxt_port_recv_msg_t *msg, 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; + 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_isolation(task, app_conf->isolation, process); + ret = nxt_app_set_isolation(task, app_conf->isolation, process); if (nxt_slow_path(ret != NXT_OK)) { return ret; } @@ -388,6 +499,25 @@ nxt_app_prefork(nxt_task_t *task, nxt_process_t *process, nxt_mp_t *mp) #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 @@ -460,6 +590,13 @@ nxt_app_setup(nxt_task_t *task, nxt_process_t *process) } } + 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); @@ -468,6 +605,22 @@ nxt_app_setup(nxt_task_t *task, nxt_process_t *process) } } +#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) { @@ -481,13 +634,6 @@ nxt_app_setup(nxt_task_t *task, nxt_process_t *process) } } - if (nxt_slow_path(nxt_app_set_environment(app_conf->environment) - != NXT_OK)) - { - nxt_alert(task, "failed to set environment"); - return NXT_ERROR; - } - init = nxt_process_init(process); init->start = nxt_app->start; @@ -555,10 +701,51 @@ nxt_app_set_environment(nxt_conf_value_t *environment) static nxt_int_t -nxt_app_isolation(nxt_task_t *task, nxt_conf_value_t *isolation, +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; @@ -571,23 +758,82 @@ nxt_app_isolation(nxt_task_t *task, nxt_conf_value_t *isolation, return NXT_ERROR; } } + + return NXT_OK; +} + #endif -#if (NXT_HAVE_CLONE_NEWUSER) - ret = nxt_app_isolation_creds(task, isolation, process); - if (nxt_slow_path(ret != NXT_OK)) { - return NXT_ERROR; + +#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_isolation_creds(nxt_task_t *task, nxt_conf_value_t *isolation, +nxt_app_set_isolation_creds(nxt_task_t *task, nxt_conf_value_t *isolation, nxt_process_t *process) { nxt_int_t ret; @@ -753,6 +999,165 @@ nxt_app_clone_flags(nxt_task_t *task, nxt_conf_value_t *namespaces, #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) diff --git a/src/nxt_application.h b/src/nxt_application.h index b4231e3b..3144dc3f 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -36,6 +36,7 @@ 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; @@ -110,6 +111,9 @@ struct nxt_app_module_s { nxt_str_t type; const char *version; + 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_clone.h b/src/nxt_clone.h index c2066ce6..e89fd82d 100644 --- a/src/nxt_clone.h +++ b/src/nxt_clone.h @@ -42,6 +42,9 @@ pid_t nxt_clone(nxt_int_t flags); #if (NXT_HAVE_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); NXT_EXPORT nxt_int_t nxt_clone_vldt_credential_uidmap(nxt_task_t *task, diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index a7a8d139..c4f78608 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -573,6 +573,24 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_app_isolation_members[] = { &nxt_conf_vldt_array_iterator, (void *) &nxt_conf_vldt_clone_gidmap }, +#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 diff --git a/src/nxt_external.c b/src/nxt_external.c index 58523525..6370a9c4 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -18,6 +18,8 @@ nxt_app_module_t nxt_external_module = { nxt_string("external"), "*", NULL, + 0, + NULL, nxt_external_start, }; 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 + +#if (NXT_HAVE_FREEBSD_NMOUNT) +#include +#include +#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_java.c b/src/nxt_java.c index c4145c1d..c7471509 100644 --- a/src/nxt_java.c +++ b/src/nxt_java.c @@ -26,6 +26,7 @@ #include "java/nxt_jni_URLClassLoader.h" #include "nxt_jars.h" +#include "nxt_java_mounts.h" static nxt_int_t nxt_java_setup(nxt_task_t *task, nxt_process_t *process, nxt_common_app_conf_t *conf); @@ -50,6 +51,8 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("java"), NXT_STRING(NXT_JAVA_VERSION), + nxt_java_mounts, + nxt_nitems(nxt_java_mounts), nxt_java_setup, nxt_java_start, }; @@ -64,20 +67,66 @@ static nxt_int_t 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; } @@ -85,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; @@ -95,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; @@ -107,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'; } 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 #include #include +#include #include #include #include diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index 0dff050b..a16e44d3 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -14,6 +14,8 @@ #include #endif +#include + typedef struct { nxt_socket_t socket; @@ -869,6 +871,12 @@ nxt_main_cleanup_process(nxt_task_t *task, nxt_pid_t pid) return; } +#if (NXT_HAVE_ISOLATION_ROOTFS) + if (process->isolation.rootfs != NULL && process->isolation.mounts) { + (void) nxt_process_unmount_all(task, process); + } +#endif + name = process->name; stream = process->stream; init = *((nxt_process_init_t *) nxt_process_init(process)); @@ -1132,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; @@ -1201,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; } @@ -1215,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, diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index ddad5761..7ae8484d 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -242,6 +242,8 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_string("php"), PHP_VERSION, NULL, + 0, + NULL, nxt_php_start, }; diff --git a/src/nxt_process.c b/src/nxt_process.c index e84549b3..c4c44d14 100644 --- a/src/nxt_process.c +++ b/src/nxt_process.c @@ -13,6 +13,14 @@ #include +#if (NXT_HAVE_PR_SET_NO_NEW_PRIVS) +#include +#endif + +#if (NXT_HAVE_PIVOT_ROOT) +#include +#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); @@ -25,6 +33,19 @@ static void nxt_process_created_ok(nxt_task_t *task, nxt_port_recv_msg_t *msg, 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; @@ -495,10 +516,347 @@ nxt_process_apply_creds(nxt_task_t *task, nxt_process_t *process) } } +#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 + + return NXT_OK; +} + + +#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; + } + + shared = nxt_malloc(nmounts); + if (nxt_slow_path(shared == NULL)) { + goto fail; + } + + 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; + } + + index = 0; + +again: + + for ( ; index < nmounts; index++) { + ent = getmntent(procfile); + if (ent == NULL) { + nmounts = index; + break; + } + + mounts[index] = (u_char *) strdup(ent->mnt_dir); + shared[index] = hasmntopt(ent, "shared") != NULL; + } + + if (ent != NULL) { + /* there are still entries to be read */ + + 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; + } + + for (index = 0; index < nmounts; index++) { + if (nxt_strcmp(mounts[index], rootfs) == 0) { + parent_mnt = (char *) rootfs; + break; + } + } + + if (parent_mnt == NULL) { + len = nxt_strlen(rootfs); + + parent_mnt = nxt_malloc(len + 1); + if (parent_mnt == NULL) { + 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'; /* / */ + } + } + } + +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: + + 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) { diff --git a/src/nxt_process.h b/src/nxt_process.h index 45bab25e..d3311722 100644 --- a/src/nxt_process.h +++ b/src/nxt_process.h @@ -69,33 +69,42 @@ typedef struct { 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; - 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_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_port_mmaps_t incoming; + nxt_port_mmaps_t outgoing; - nxt_thread_mutex_t cp_mutex; - nxt_lvlhsh_t connected_ports; /* of nxt_port_t */ + nxt_thread_mutex_t cp_mutex; + nxt_lvlhsh_t connected_ports; /* of nxt_port_t */ - uint32_t stream; + uint32_t stream; - nxt_mp_t *mem_pool; - nxt_credential_t *user_cred; + nxt_mp_t *mem_pool; + nxt_credential_t *user_cred; - nxt_process_data_t data; + nxt_process_data_t data; - union { -#if (NXT_HAVE_CLONE) - nxt_clone_t clone; -#endif - } isolation; + nxt_process_isolation_t isolation; } nxt_process_t; @@ -184,6 +193,12 @@ nxt_int_t nxt_process_vldt_isolation_creds(nxt_task_t *task, nxt_process_t *process); #endif +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) #define nxt_process_title(task, fmt, ...) \ diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 089d15c0..b9033a75 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -18,6 +18,7 @@ #include #include #include +#include /* * 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" @@ -116,6 +123,8 @@ 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_start, }; diff --git a/src/nxt_runtime.c b/src/nxt_runtime.c index d7e35dec..5aa061dd 100644 --- a/src/nxt_runtime.c +++ b/src/nxt_runtime.c @@ -84,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)) { 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 /* getentropy(). */ #endif +#if (NXT_HAVE_ISOLATION_ROOTFS) +#include +#endif #if (NXT_TEST_BUILD) #include diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 5e9200dc..14e107e4 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -118,6 +118,12 @@ 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, + 0, +#endif + NULL, nxt_perl_psgi_start, }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index 40f72f51..489ddcf4 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -7,6 +7,7 @@ #include #include +#include #define NXT_RUBY_RACK_API_VERSION_MAJOR 1 @@ -78,6 +79,10 @@ 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_start, }; -- cgit From 08b765ae4289f399bb3642d327ccf402efca3537 Mon Sep 17 00:00:00 2001 From: Tiago Natel de Moura Date: Thu, 28 May 2020 14:59:52 +0100 Subject: Tests: Added rootfs tests. --- test/go/ns_inspect/app.go | 21 +++++++-- test/python/ns_inspect/wsgi.py | 31 +++++++++++++ test/test_go_isolation.py | 46 +++++++++++++++++++ test/test_go_isolation_rootfs.py | 34 ++++++++++++++ test/test_java_isolation_rootfs.py | 85 +++++++++++++++++++++++++++++++++++ test/test_php_isolation.py | 57 +++++++++++++++++++++++ test/test_python_isolation.py | 79 ++++++++++++++++++++++++++++++++ test/test_python_isolation_chroot.py | 57 +++++++++++++++++++++++ test/test_ruby_isolation.py | 71 +++++++++++++++++++++++++++++ test/unit/applications/lang/go.py | 75 +++++++++++++++++++------------ test/unit/applications/lang/python.py | 19 +++++++- test/unit/main.py | 3 +- 12 files changed, 543 insertions(+), 35 deletions(-) create mode 100644 test/python/ns_inspect/wsgi.py create mode 100644 test/test_go_isolation_rootfs.py create mode 100644 test/test_java_isolation_rootfs.py create mode 100644 test/test_php_isolation.py create mode 100644 test/test_python_isolation.py create mode 100644 test/test_python_isolation_chroot.py create mode 100644 test/test_ruby_isolation.py 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/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/test_go_isolation.py b/test/test_go_isolation.py index e6aade9b..61d39617 100644 --- a/test/test_go_isolation.py +++ b/test/test_go_isolation.py @@ -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_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_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_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_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/unit/applications/lang/go.py b/test/unit/applications/lang/go.py index 9070beb6..83bde4d8 100644 --- a/test/unit/applications/lang/go.py +++ b/test/unit/applications/lang/go.py @@ -19,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: @@ -47,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/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/main.py b/test/unit/main.py index d415f58f..408cf31c 100644 --- a/test/unit/main.py +++ b/test/unit/main.py @@ -58,6 +58,7 @@ class TestUnit(unittest.TestCase): 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] @@ -165,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): -- cgit From 943865fc77ea4c65c2f61c43c19bc9e4686de75f Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 28 May 2020 17:34:06 +0300 Subject: Added version 1.18.0 CHANGES. --- CHANGES | 11 +++++++++++ docs/changes.xml | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/CHANGES b/CHANGES index 3ffd06b7..5e49979f 100644 --- a/CHANGES +++ b/CHANGES @@ -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/docs/changes.xml b/docs/changes.xml index a883ccbb..f3ec7518 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -5,6 +5,53 @@ + + + + +NGINX Unit updated to 1.18.0. + + + + + + + + + + +the "rootfs" isolation option for changing root filesystem for an application. + + + + + +multiple "targets" in PHP applications. + + + + + +support for percent-encoding in the "uri" and "arguments" matching options +and in the "pass" option. + + + + + + " -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 " -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 " -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 " -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 " -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 " -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 " -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 " -ENV UNIT_VERSION 1.17.0-1~buster +ENV UNIT_VERSION 1.18.0-1~buster RUN set -x \ && apt-get update \ -- cgit From 9d8e476c4e3695019b0a1fe3696d3411a8393de6 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 28 May 2020 18:04:47 +0300 Subject: Added tag 1.18.0 for changeset a34bc498d976 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index bae952d7..7bf00581 100644 --- a/.hgtags +++ b/.hgtags @@ -24,3 +24,4 @@ b391df5f0102aa6afe660cfc863729c1b1111c9e 1.12.0 801ac82f80fb2b2333f2c03ac9c3df6b7cec130a 1.15.0 8bab088952dd9d7caa3d04fd4b3026cef26fcf7d 1.16.0 4b13438632bc37ca599113be90af64f6e2f09d83 1.17.0 +a34bc498d976affa5b50584d3d93a4a9a04d5c39 1.18.0 -- cgit