diff --git a/contrib/kyua/AUTHORS b/contrib/kyua/AUTHORS index c7bd72ce776b..4cf65083015a 100644 --- a/contrib/kyua/AUTHORS +++ b/contrib/kyua/AUTHORS @@ -10,3 +10,4 @@ * The FreeBSD Foundation * Google Inc. +* Igor Ostapenko diff --git a/contrib/kyua/cli/cmd_config_test.cpp b/contrib/kyua/cli/cmd_config_test.cpp index f084f99bb90a..a5f6930ba027 100644 --- a/contrib/kyua/cli/cmd_config_test.cpp +++ b/contrib/kyua/cli/cmd_config_test.cpp @@ -61,6 +61,7 @@ fake_config(void) { config::tree user_config = engine::default_config(); user_config.set_string("architecture", "the-architecture"); + user_config.set_string("execenvs", "the-env"); user_config.set_string("parallelism", "128"); user_config.set_string("platform", "the-platform"); //user_config.set_string("unprivileged_user", ""); @@ -83,12 +84,13 @@ ATF_TEST_CASE_BODY(all) cmdline::ui_mock ui; ATF_REQUIRE_EQ(EXIT_SUCCESS, cmd.main(&ui, args, fake_config())); - ATF_REQUIRE_EQ(5, ui.out_log().size()); + ATF_REQUIRE_EQ(6, ui.out_log().size()); ATF_REQUIRE_EQ("architecture = the-architecture", ui.out_log()[0]); - ATF_REQUIRE_EQ("parallelism = 128", ui.out_log()[1]); - ATF_REQUIRE_EQ("platform = the-platform", ui.out_log()[2]); - ATF_REQUIRE_EQ("test_suites.foo.bar = first", ui.out_log()[3]); - ATF_REQUIRE_EQ("test_suites.foo.baz = second", ui.out_log()[4]); + ATF_REQUIRE_EQ("execenvs = the-env", ui.out_log()[1]); + ATF_REQUIRE_EQ("parallelism = 128", ui.out_log()[2]); + ATF_REQUIRE_EQ("platform = the-platform", ui.out_log()[3]); + ATF_REQUIRE_EQ("test_suites.foo.bar = first", ui.out_log()[4]); + ATF_REQUIRE_EQ("test_suites.foo.baz = second", ui.out_log()[5]); ATF_REQUIRE(ui.err_log().empty()); } diff --git a/contrib/kyua/doc/kyua.conf.5.in b/contrib/kyua/doc/kyua.conf.5.in index 05a9499b48c4..7188bb8888c3 100644 --- a/contrib/kyua/doc/kyua.conf.5.in +++ b/contrib/kyua/doc/kyua.conf.5.in @@ -1,4 +1,4 @@ -.\" Copyright 2012 The Kyua Authors. +.\" Copyright 2012-2024 The Kyua Authors. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -25,7 +25,7 @@ .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -.Dd February 20, 2015 +.Dd March 22, 2024 .Dt KYUA.CONF 5 .Os .Sh NAME @@ -36,6 +36,7 @@ .Pp Variables: .Va architecture , +.Va execenvs , .Va platform , .Va test_suites , .Va unprivileged_user . @@ -72,6 +73,14 @@ The following variables are internally recognized by .Bl -tag -width XX -offset indent .It Va architecture Name of the system architecture (aka processor type). +.It Va execenvs +Whitespace-separated list of execution environment names. +.Pp +Only tests which require one of the given execution environments will be run. +.Pp +See +.Xr kyuafile 5 +for the list of possible execution environments. .It Va parallelism Maximum number of test cases to execute concurrently. .It Va platform diff --git a/contrib/kyua/doc/kyuafile.5.in b/contrib/kyua/doc/kyuafile.5.in index 06cb2dbc42a8..a667f5dc2816 100644 --- a/contrib/kyua/doc/kyuafile.5.in +++ b/contrib/kyua/doc/kyuafile.5.in @@ -1,4 +1,4 @@ -.\" Copyright 2012 The Kyua Authors. +.\" Copyright 2012-2024 The Kyua Authors. .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -25,7 +25,7 @@ .\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE .\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -.Dd July 3, 2015 +.Dd March 23, 2024 .Dt KYUAFILE 5 .Os .Sh NAME @@ -173,6 +173,75 @@ Refer to the section below for clarification. .It Va description Textual description of the test. +.It Va execenv +The name of the execution environment to be used for running the test. +If empty or not defined, the +.Sq host +execution environment is meant. +The possible values are: +.Bl -tag -width xUnnnnnnn +.It host +The default environment which runs the test as a usual child process. +.It jail +The +.Fx +.Xr jail 8 +environment. +It creates a temporary jail to run the test and its optional cleanup logic +within. +.Pp +This feature requires +.Xr kyua 1 +to be running with superuser privileges. +.Pp +The difference between +.Va security.jail.children.max +and +.Va security.jail.children.cur +sysctl of the jail +.Xr kyua 1 +is running within must have a value high enough for the jail based tests +planned to be run. +For instance, the value 1 should be enough for a sequential run of simple +tests. +Otherwise, such aspects as parallel test execution and sub-jails spawned +by specific test cases should be considered. +.Pp +The formula of a temporary jail name is +.Sq kyua ++ +.Va test program path ++ +.Sq _ ++ +.Va test case name . +All non-alphanumeric characters are replaced with +.Sq _ . +.Sq kyua_usr_tests_sys_netpfil_pf_pass_block_v4 +is an example for /usr/tests/sys/netpfil/pf/pass_block:v4 test case. +.El +.It Va execenv_jail_params +Additional test-specific whitespace-separated parameters of +.Fx +.Xr jail 8 +to create a temporary jail within which the test is run. +It makes sense only if execenv is set to +.Sq jail . +.sp +.Xr kyua 1 +implicitly passes +.Sq children.max +parameter to +.Xr jail 8 +for a temporary jail with the maximum possible value according to +the jail +.Xr kyua 1 +itself is running within. +It allows tests to easily spawn their own sub-jails without additional +configuration. +It can be overridden via +.Va execenv_jail_params +if needed. .It Va is_exclusive If true, indicates that this test program cannot be executed along any other programs at the same time. @@ -360,6 +429,36 @@ test_suite('FreeBSD') plain_test_program{name='the_test', ['custom.FreeBSD-Bug-Id']='category/12345'} .Ed +.Ss FreeBSD jail execution environment +The following example configures the test to be run within a temporary jail +with +.Xr vnet 9 +support and the permission to create raw sockets: +.Bd -literal -offset indent +syntax(2) + +test_suite('FreeBSD') + +atf_test_program{name='network_test', + execenv='jail', + execenv_jail_params='vnet allow.raw_sockets', + required_user='root'} +.Ed +.Pp +A test case itself may have no requirements in superuser privileges, +but required_user='root' metadata property reminds that the jail execution +environment requires +.Xr kyua 1 +being running with root privileges, and the test is skipped otherwise with +the respective message. The combination of +.Va execenv +set to +.Sq jail +and +.Va required_user +set to +.Sq unprivileged +does not work respectively. .Ss Connecting disjoint test suites Now suppose you had various test suites on your file system and you would like to connect them together so that they could be executed and treated as diff --git a/contrib/kyua/drivers/report_junit_test.cpp b/contrib/kyua/drivers/report_junit_test.cpp index 462dca72f9be..0f009c6befd3 100644 --- a/contrib/kyua/drivers/report_junit_test.cpp +++ b/contrib/kyua/drivers/report_junit_test.cpp @@ -63,6 +63,8 @@ static const char* const default_metadata = "allowed_architectures is empty\n" "allowed_platforms is empty\n" "description is empty\n" + "execenv is empty\n" + "execenv_jail_params is empty\n" "has_cleanup = false\n" "is_exclusive = false\n" "required_configs is empty\n" @@ -80,6 +82,8 @@ static const char* const overriden_metadata = "allowed_architectures is empty\n" "allowed_platforms is empty\n" "description = Textual description\n" + "execenv is empty\n" + "execenv_jail_params is empty\n" "has_cleanup = false\n" "is_exclusive = false\n" "required_configs is empty\n" @@ -199,6 +203,8 @@ ATF_TEST_CASE_BODY(junit_metadata__overrides) .add_allowed_architecture("arch1") .add_allowed_platform("platform1") .set_description("This is a test") + .set_execenv("jail") + .set_execenv_jail_params("vnet") .set_has_cleanup(true) .set_is_exclusive(true) .add_required_config("config1") @@ -215,6 +221,8 @@ ATF_TEST_CASE_BODY(junit_metadata__overrides) + "allowed_architectures = arch1\n" + "allowed_platforms = platform1\n" + "description = This is a test\n" + + "execenv = jail\n" + + "execenv_jail_params = vnet\n" + "has_cleanup = true\n" + "is_exclusive = true\n" + "required_configs = config1\n" diff --git a/contrib/kyua/engine/atf.cpp b/contrib/kyua/engine/atf.cpp index eb63be20b0e7..f6746dd2f29f 100644 --- a/contrib/kyua/engine/atf.cpp +++ b/contrib/kyua/engine/atf.cpp @@ -39,6 +39,7 @@ extern "C" { #include "engine/atf_list.hpp" #include "engine/atf_result.hpp" #include "engine/exceptions.hpp" +#include "engine/execenv/execenv.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" @@ -54,6 +55,7 @@ extern "C" { #include "utils/stream.hpp" namespace config = utils::config; +namespace execenv = engine::execenv; namespace fs = utils::fs; namespace process = utils::process; @@ -190,7 +192,10 @@ engine::atf_interface::exec_test(const model::test_program& test_program, args.push_back(F("-r%s") % (control_directory / result_name)); args.push_back(test_case_name); - process::exec(test_program.absolute_path(), args); + + auto e = execenv::get(test_program, test_case_name); + e->init(); + e->exec(args); } @@ -219,7 +224,9 @@ engine::atf_interface::exec_cleanup( } args.push_back(F("%s:cleanup") % test_case_name); - process::exec(test_program.absolute_path(), args); + + auto e = execenv::get(test_program, test_case_name); + e->exec(args); } diff --git a/contrib/kyua/engine/atf_list.cpp b/contrib/kyua/engine/atf_list.cpp index a16b889c74f0..c9c2fed70175 100644 --- a/contrib/kyua/engine/atf_list.cpp +++ b/contrib/kyua/engine/atf_list.cpp @@ -121,6 +121,10 @@ engine::parse_atf_metadata(const model::properties_map& props) mdbuilder.set_string("has_cleanup", value); } else if (name == "require.arch") { mdbuilder.set_string("allowed_architectures", value); + } else if (name == "execenv") { + mdbuilder.set_string("execenv", value); + } else if (name == "execenv.jail.params") { + mdbuilder.set_string("execenv_jail_params", value); } else if (name == "require.config") { mdbuilder.set_string("required_configs", value); } else if (name == "require.files") { diff --git a/contrib/kyua/engine/config.cpp b/contrib/kyua/engine/config.cpp index 3f162a94fbb5..a7c418e3164c 100644 --- a/contrib/kyua/engine/config.cpp +++ b/contrib/kyua/engine/config.cpp @@ -35,6 +35,7 @@ #include #include "engine/exceptions.hpp" +#include "engine/execenv/execenv.hpp" #include "utils/config/exceptions.hpp" #include "utils/config/parser.hpp" #include "utils/config/tree.ipp" @@ -43,6 +44,7 @@ #include "utils/text/operations.ipp" namespace config = utils::config; +namespace execenv = engine::execenv; namespace fs = utils::fs; namespace passwd = utils::passwd; namespace text = utils::text; @@ -59,6 +61,7 @@ static void init_tree(config::tree& tree) { tree.define< config::string_node >("architecture"); + tree.define< config::strings_set_node >("execenvs"); tree.define< config::positive_int_node >("parallelism"); tree.define< config::string_node >("platform"); tree.define< engine::user_node >("unprivileged_user"); @@ -74,6 +77,14 @@ static void set_defaults(config::tree& tree) { tree.set< config::string_node >("architecture", KYUA_ARCHITECTURE); + + std::set< std::string > supported; + for (auto em : execenv::execenvs()) + if (em->is_supported()) + supported.insert(em->name()); + supported.insert(execenv::default_execenv_name); + tree.set< config::strings_set_node >("execenvs", supported); + // TODO(jmmv): Automatically derive this from the number of CPUs in the // machine and forcibly set to a value greater than 1. Still testing // the new parallel implementation as of 2015-02-27 though. @@ -229,6 +240,13 @@ engine::empty_config(void) { config::tree tree(false); init_tree(tree); + + // Tests of Kyua itself tend to use an empty config, i.e. default + // execution environment is used. Let's allow it. + std::set< std::string > supported; + supported.insert(engine::execenv::default_execenv_name); + tree.set< config::strings_set_node >("execenvs", supported); + return tree; } diff --git a/contrib/kyua/engine/execenv/execenv.cpp b/contrib/kyua/engine/execenv/execenv.cpp new file mode 100644 index 000000000000..b043bcda52cb --- /dev/null +++ b/contrib/kyua/engine/execenv/execenv.cpp @@ -0,0 +1,74 @@ +// Copyright 2023 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "engine/execenv/execenv.hpp" + +#include "engine/execenv/execenv_host.hpp" + +namespace execenv = engine::execenv; + +using utils::none; + + +const char* execenv::default_execenv_name = "host"; + + +/// List of registered execution environments, except default host one. +/// +/// Use register_execenv() to add an entry to this global list. +static std::vector< std::shared_ptr< execenv::manager > > + execenv_managers; + + +void +execenv::register_execenv(const std::shared_ptr< execenv::manager > manager) +{ + execenv_managers.push_back(manager); +} + + +const std::vector< std::shared_ptr< execenv::manager> > +execenv::execenvs() +{ + return execenv_managers; +} + + +std::unique_ptr< execenv::interface > +execenv::get(const model::test_program& test_program, + const std::string& test_case_name) +{ + for (auto m : execenv_managers) { + auto e = m->probe(test_program, test_case_name); + if (e != nullptr) + return e; + } + + return std::unique_ptr< execenv::interface >( + new execenv::execenv_host(test_program, test_case_name)); +} diff --git a/contrib/kyua/engine/execenv/execenv.hpp b/contrib/kyua/engine/execenv/execenv.hpp new file mode 100644 index 000000000000..e667ff205d85 --- /dev/null +++ b/contrib/kyua/engine/execenv/execenv.hpp @@ -0,0 +1,149 @@ +// Copyright 2023 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file engine/execenv/execenv.hpp +/// Execution environment subsystem interface. + +#if !defined(ENGINE_EXECENV_EXECENV_HPP) +#define ENGINE_EXECENV_EXECENV_HPP + +#include "model/test_program.hpp" +#include "utils/optional.ipp" +#include "utils/process/operations_fwd.hpp" + +using utils::process::args_vector; +using utils::optional; + +namespace engine { +namespace execenv { + + +extern const char* default_execenv_name; + + +/// Abstract interface of an execution environment. +class interface { +protected: + const model::test_program& _test_program; + const std::string& _test_case_name; + +public: + /// Constructor. + /// + /// \param program The test program. + /// \param test_case_name Name of the test case. + interface(const model::test_program& test_program, + const std::string& test_case_name) : + _test_program(test_program), + _test_case_name(test_case_name) + {} + + /// Destructor. + virtual ~interface() {} + + /// Initializes execution environment. + /// + /// It's expected to be called inside a fork which runs + /// scheduler::interface::exec_test(), so we can fail a test fast if its + /// execution environment setup fails, and test execution could use the + /// configured proc environment, if expected. + virtual void init() const = 0; + + /// Cleanups or removes execution environment. + /// + /// It's expected to be called inside a fork for execenv cleanup. + virtual void cleanup() const = 0; + + /// Executes a test within the execution environment. + /// + /// It's expected to be called inside a fork which runs + /// scheduler::interface::exec_test() or exec_cleanup(). + /// + /// \param args The arguments to pass to the binary. + virtual void exec(const args_vector& args) const UTILS_NORETURN = 0; +}; + + +/// Abstract interface of an execution environment manager. +class manager { +public: + /// Destructor. + virtual ~manager() {} + + /// Returns name of an execution environment. + virtual const std::string& name() const = 0; + + /// Returns whether this execution environment is actually supported. + /// + /// It can be compile time and/or runtime check. + virtual bool is_supported() const = 0; + + /// Returns execution environment for a test. + /// + /// It checks if the given test is designed for this execution environment. + /// + /// \param program The test program. + /// \param test_case_name Name of the test case. + /// + /// \return An execenv object if the test conforms, or none. + virtual std::unique_ptr< interface > probe( + const model::test_program& test_program, + const std::string& test_case_name) const = 0; + + // TODO: execenv related extra metadata could be provided by a manager + // not to know how exactly and where it should be added to the kyua +}; + + +/// Registers an execution environment. +/// +/// \param manager Execution environment manager. +void register_execenv(const std::shared_ptr< manager > manager); + + +/// Returns list of registered execenv managers, except default host one. +/// +/// \return A vector of pointers to execenv managers. +const std::vector< std::shared_ptr< manager> > execenvs(); + + +/// Returns execution environment for a test case. +/// +/// \param program The test program. +/// \param test_case_name Name of the test case. +/// +/// \return An execution environment of a test. +std::unique_ptr< execenv::interface > get( + const model::test_program& test_program, + const std::string& test_case_name); + + +} // namespace execenv +} // namespace engine + +#endif // !defined(ENGINE_EXECENV_EXECENV_HPP) diff --git a/contrib/kyua/engine/execenv/execenv_host.cpp b/contrib/kyua/engine/execenv/execenv_host.cpp new file mode 100644 index 000000000000..4e37fca3e7d3 --- /dev/null +++ b/contrib/kyua/engine/execenv/execenv_host.cpp @@ -0,0 +1,52 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "engine/execenv/execenv_host.hpp" + +#include "utils/fs/path.hpp" +#include "utils/process/operations.hpp" + +void +execenv::execenv_host::init() const +{ + // nothing to do +} + + +void +execenv::execenv_host::cleanup() const +{ + // nothing to do +} + + +void +execenv::execenv_host::exec(const args_vector& args) const +{ + utils::process::exec(_test_program.absolute_path(), args); +} diff --git a/contrib/kyua/engine/execenv/execenv_host.hpp b/contrib/kyua/engine/execenv/execenv_host.hpp new file mode 100644 index 000000000000..2742366cfd6f --- /dev/null +++ b/contrib/kyua/engine/execenv/execenv_host.hpp @@ -0,0 +1,63 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file engine/execenv/execenv_host.hpp +/// Default execution environment. + +#if !defined(ENGINE_EXECENV_EXECENV_HOST_HPP) +#define ENGINE_EXECENV_EXECENV_HOST_HPP + +#include "engine/execenv/execenv.hpp" + +#include "utils/process/operations_fwd.hpp" + +namespace execenv = engine::execenv; + +using utils::process::args_vector; + +namespace engine { +namespace execenv { + + +class execenv_host : public execenv::interface { +public: + execenv_host(const model::test_program& test_program, + const std::string& test_case_name) : + execenv::interface(test_program, test_case_name) + {} + + void init() const; + void cleanup() const; + void exec(const args_vector& args) const UTILS_NORETURN; +}; + + +} // namespace execenv +} // namespace engine + +#endif // !defined(ENGINE_EXECENV_EXECENV_HOST_HPP) diff --git a/contrib/kyua/engine/plain.cpp b/contrib/kyua/engine/plain.cpp index 8346e50bbecf..9a2c63f8b663 100644 --- a/contrib/kyua/engine/plain.cpp +++ b/contrib/kyua/engine/plain.cpp @@ -34,6 +34,7 @@ extern "C" { #include +#include "engine/execenv/execenv.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" @@ -47,6 +48,7 @@ extern "C" { #include "utils/sanity.hpp" namespace config = utils::config; +namespace execenv = engine::execenv; namespace fs = utils::fs; namespace process = utils::process; @@ -104,7 +106,10 @@ engine::plain_interface::exec_test( } process::args_vector args; - process::exec(test_program.absolute_path(), args); + + auto e = execenv::get(test_program, test_case_name); + e->init(); + e->exec(args); } diff --git a/contrib/kyua/engine/requirements.cpp b/contrib/kyua/engine/requirements.cpp index a7b0a90d97db..a6a4cae7511c 100644 --- a/contrib/kyua/engine/requirements.cpp +++ b/contrib/kyua/engine/requirements.cpp @@ -28,6 +28,7 @@ #include "engine/requirements.hpp" +#include "engine/execenv/execenv.hpp" #include "model/metadata.hpp" #include "model/types.hpp" #include "utils/config/nodes.ipp" @@ -100,6 +101,34 @@ check_allowed_architectures(const model::strings_set& allowed_architectures, } +/// Checks if test's execenv matches the user configuration. +/// +/// \param execenv Execution environment name a test is designed for. +/// \param user_config Runtime user configuration. +/// +/// \return Empty if the execenv is in the list or an error message otherwise. +static std::string +check_execenv(const std::string& execenv, const config::tree& user_config) +{ + std::string name = execenv; + if (name.empty()) + name = engine::execenv::default_execenv_name; // if test claims nothing + + std::set< std::string > execenvs; + try { + execenvs = user_config.lookup< config::strings_set_node >("execenvs"); + } catch (const config::unknown_key_error&) { + // okay, user config does not define it, empty set then + } + + if (execenvs.find(name) == execenvs.end()) + return F("'%s' execenv is not supported or not allowed by " + "the runtime user configuration") % name; + + return ""; +} + + /// Checks if the allowed platforms match the current architecture. /// /// \param allowed_platforms Set of allowed platforms. @@ -263,6 +292,10 @@ engine::check_reqs(const model::metadata& md, const config::tree& cfg, if (!reason.empty()) return reason; + reason = check_execenv(md.execenv(), cfg); + if (!reason.empty()) + return reason; + reason = check_allowed_platforms(md.allowed_platforms(), cfg); if (!reason.empty()) return reason; diff --git a/contrib/kyua/engine/scheduler.cpp b/contrib/kyua/engine/scheduler.cpp index e7b51d23acca..e75091a40e38 100644 --- a/contrib/kyua/engine/scheduler.cpp +++ b/contrib/kyua/engine/scheduler.cpp @@ -40,6 +40,7 @@ extern "C" { #include "engine/config.hpp" #include "engine/exceptions.hpp" +#include "engine/execenv/execenv.hpp" #include "engine/requirements.hpp" #include "model/context.hpp" #include "model/metadata.hpp" @@ -68,6 +69,7 @@ extern "C" { namespace config = utils::config; namespace datetime = utils::datetime; +namespace execenv = engine::execenv; namespace executor = utils::process::executor; namespace fs = utils::fs; namespace logging = utils::logging; @@ -87,6 +89,10 @@ using utils::optional; datetime::delta scheduler::cleanup_timeout(60, 0); +/// Timeout for the test case execenv cleanup operation. +datetime::delta scheduler::execenv_cleanup_timeout(60, 0); + + /// Timeout for the test case listing operation. /// /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose @@ -206,6 +212,18 @@ struct test_exec_data : public exec_data { /// denote that no further attempts shall be made at cleaning this up. bool needs_cleanup; + /// Whether this test case still needs to have its execenv cleanup executed. + /// + /// This is set externally when the cleanup routine is actually invoked to + /// denote that no further attempts shall be made at cleaning this up. + bool needs_execenv_cleanup; + + /// Original PID of the test case subprocess. + /// + /// This is used for the cleanup upon termination by a signal, to reap the + /// leftovers and form missing exit_handle. + pid_t pid; + /// The exit_handle for this test once it has completed. /// /// This is set externally when the test case has finished, as we need this @@ -222,12 +240,14 @@ struct test_exec_data : public exec_data { test_exec_data(const model::test_program_ptr test_program_, const std::string& test_case_name_, const std::shared_ptr< scheduler::interface > interface_, - const config::tree& user_config_) : + const config::tree& user_config_, + const pid_t pid_) : exec_data(test_program_, test_case_name_), - interface(interface_), user_config(user_config_) + interface(interface_), user_config(user_config_), pid(pid_) { const model::test_case& test_case = test_program->find(test_case_name); needs_cleanup = test_case.get_metadata().has_cleanup(); + needs_execenv_cleanup = test_case.get_metadata().has_execenv(); } }; @@ -266,6 +286,40 @@ struct cleanup_exec_data : public exec_data { }; +/// Maintenance data held while a test execenv cleanup is being executed. +/// +/// Instances of this object are related to a previous test_exec_data, as +/// cleanup routines can only exist once the test has been run. +struct execenv_exec_data : public exec_data { + /// The exit handle of the test. This is necessary so that we can return + /// the correct exit_handle to the user of the scheduler. + executor::exit_handle body_exit_handle; + + /// The final result of the test's body. This is necessary to compute the + /// right return value for a test with a cleanup routine: the body result is + /// respected if it is a "bad" result; else the result of the cleanup + /// routine is used if it has failed. + model::test_result body_result; + + /// Constructor. + /// + /// \param test_program_ Test program data for this test case. + /// \param test_case_name_ Name of the test case. + /// \param body_exit_handle_ If not none, exit handle of the body + /// corresponding to the cleanup routine represented by this exec_data. + /// \param body_result_ If not none, result of the body corresponding to the + /// cleanup routine represented by this exec_data. + execenv_exec_data(const model::test_program_ptr test_program_, + const std::string& test_case_name_, + const executor::exit_handle& body_exit_handle_, + const model::test_result& body_result_) : + exec_data(test_program_, test_case_name_), + body_exit_handle(body_exit_handle_), body_result(body_result_) + { + } +}; + + /// Shared pointer to exec_data. /// /// We require this because we want exec_data to not be copyable, and thus we @@ -492,6 +546,40 @@ class run_test_cleanup { }; +/// Functor to execute a test execenv cleanup in a child process. +class run_execenv_cleanup { + /// Test program to execute. + const model::test_program _test_program; + + /// Name of the test case to execute. + const std::string& _test_case_name; + +public: + /// Constructor. + /// + /// \param test_program Test program to execute. + /// \param test_case_name Name of the test case to execute. + run_execenv_cleanup( + const model::test_program_ptr test_program, + const std::string& test_case_name) : + _test_program(force_absolute_paths(*test_program)), + _test_case_name(test_case_name) + { + } + + /// Body of the subprocess. + /// + /// \param control_directory The testcase directory where cleanup will be + /// run from. + void + operator()(const fs::path& /* control_directory */) + { + auto e = execenv::get(_test_program, _test_case_name); + e->cleanup(); + } +}; + + /// Obtains the right scheduler interface for a given test program. /// /// \param name The name of the interface of the test program. @@ -835,6 +923,22 @@ struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { % test_data->test_case_name); } } + + const test_exec_data_vector td = tests_needing_execenv_cleanup(); + + for (test_exec_data_vector::const_iterator iter = td.begin(); + iter != td.end(); ++iter) { + const test_exec_data* test_data = *iter; + + try { + sync_execenv_cleanup(test_data); + } catch (const std::runtime_error& e) { + LW(F("Failed to run execenv cleanup routine for %s:%s on abrupt " + "termination") + % test_data->test_program->relative_path() + % test_data->test_case_name); + } + } } /// Finds any pending exec_datas that correspond to tests needing cleanup. @@ -856,6 +960,8 @@ struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { if (test_data->needs_cleanup) { tests_data.push_back(test_data); test_data->needs_cleanup = false; + if (!test_data->exit_handle) + test_data->exit_handle = generic.reap(test_data->pid); } } catch (const std::bad_cast& e) { // Do nothing for cleanup_exec_data objects. @@ -865,6 +971,37 @@ struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { return tests_data; } + /// Finds any pending exec_datas that correspond to tests needing execenv + /// cleanup. + /// + /// \return The collection of test_exec_data objects that have their + /// specific execenv property set. + test_exec_data_vector + tests_needing_execenv_cleanup(void) + { + test_exec_data_vector tests_data; + + for (exec_data_map::const_iterator iter = all_exec_data.begin(); + iter != all_exec_data.end(); ++iter) { + const exec_data_ptr data = (*iter).second; + + try { + test_exec_data* test_data = &dynamic_cast< test_exec_data& >( + *data.get()); + if (test_data->needs_execenv_cleanup) { + tests_data.push_back(test_data); + test_data->needs_execenv_cleanup = false; + if (!test_data->exit_handle) + test_data->exit_handle = generic.reap(test_data->pid); + } + } catch (const std::bad_cast& e) { + // Do nothing for other objects. + } + } + + return tests_data; + } + /// Cleans up a single test case synchronously. /// /// \param test_data The data of the previously executed test case to be @@ -926,6 +1063,61 @@ struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { return handle; } + + /// Cleans up a single test case execenv synchronously. + /// + /// \param test_data The data of the previously executed test case to be + /// cleaned up. + void + sync_execenv_cleanup(const test_exec_data* test_data) + { + // The message in this result should never be seen by the user, but use + // something reasonable just in case it leaks and we need to pinpoint + // the call site. + model::test_result result(model::test_result_broken, + "Test case died abruptly"); + + const executor::exec_handle cleanup_handle = spawn_execenv_cleanup( + test_data->test_program, test_data->test_case_name, + test_data->exit_handle.get(), result); + generic.wait(cleanup_handle); + } + + /// Forks and executes a test case execenv cleanup asynchronously. + /// + /// \param test_program The container test program. + /// \param test_case_name The name of the test case to run. + /// \param body_handle The exit handle of the test case's corresponding + /// body. The cleanup will be executed in the same context. + /// \param body_result The result of the test case's corresponding body. + /// + /// \return A handle for the background operation. Used to match the result + /// of the execution returned by wait_any() with this invocation. + executor::exec_handle + spawn_execenv_cleanup(const model::test_program_ptr test_program, + const std::string& test_case_name, + const executor::exit_handle& body_handle, + const model::test_result& body_result) + { + generic.check_interrupt(); + + LI(F("Spawning %s:%s (execenv cleanup)") + % test_program->absolute_path() % test_case_name); + + const executor::exec_handle handle = generic.spawn_followup( + run_execenv_cleanup(test_program, test_case_name), + body_handle, execenv_cleanup_timeout); + + const exec_data_ptr data(new execenv_exec_data( + test_program, test_case_name, body_handle, body_result)); + LD(F("Inserting %s into all_exec_data (execenv cleanup)") % handle.pid()); + INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(), + F("PID %s already in all_exec_data; not properly cleaned " + "up or reused too fast") % handle.pid());; + all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); + + return handle; + } }; @@ -1115,7 +1307,7 @@ scheduler::scheduler_handle::spawn_test( unprivileged_user); const exec_data_ptr data(new test_exec_data( - test_program, test_case_name, interface, user_config)); + test_program, test_case_name, interface, user_config, handle.pid())); LD(F("Inserting %s into all_exec_data") % handle.pid()); INV_MSG( _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(), @@ -1150,6 +1342,8 @@ scheduler::scheduler_handle::wait_any(void) _pimpl->generic, handle); optional< model::test_result > result; + + // test itself try { test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *data.get()); @@ -1185,6 +1379,7 @@ scheduler::scheduler_handle::wait_any(void) // if the test's body reports a skip (because actions could have // already been taken). test_data->needs_cleanup = false; + test_data->needs_execenv_cleanup = false; } } if (!result) { @@ -1209,7 +1404,6 @@ scheduler::scheduler_handle::wait_any(void) _pimpl->spawn_cleanup(test_data->test_program, test_data->test_case_name, test_data->user_config, handle, result.get()); - test_data->needs_cleanup = false; // TODO(jmmv): Chaining this call is ugly. We'd be better off by // looping over terminated processes until we got a result suitable @@ -1218,7 +1412,21 @@ scheduler::scheduler_handle::wait_any(void) // of test cases do not have cleanup routines. return wait_any(); } + + if (test_data->needs_execenv_cleanup) { + INV(test_case.get_metadata().has_execenv()); + _pimpl->spawn_execenv_cleanup(test_data->test_program, + test_data->test_case_name, + handle, result.get()); + test_data->needs_execenv_cleanup = false; + return wait_any(); + } } catch (const std::bad_cast& e) { + // ok, let's check for another type + } + + // test cleanup + try { const cleanup_exec_data* cleanup_data = &dynamic_cast< const cleanup_exec_data& >(*data.get()); LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid()); @@ -1257,7 +1465,65 @@ scheduler::scheduler_handle::wait_any(void) _pimpl->all_exec_data.erase(handle.original_pid()); handle = cleanup_data->body_exit_handle; + + const exec_data_map::iterator it = _pimpl->all_exec_data.find( + handle.original_pid()); + if (it != _pimpl->all_exec_data.end()) { + exec_data_ptr d = (*it).second; + test_exec_data* test_data = &dynamic_cast< test_exec_data& >( + *d.get()); + const model::test_case& test_case = + cleanup_data->test_program->find(cleanup_data->test_case_name); + test_data->needs_cleanup = false; + + if (test_data->needs_execenv_cleanup) { + INV(test_case.get_metadata().has_execenv()); + _pimpl->spawn_execenv_cleanup(cleanup_data->test_program, + cleanup_data->test_case_name, + handle, result.get()); + test_data->needs_execenv_cleanup = false; + return wait_any(); + } + } + } catch (const std::bad_cast& e) { + // ok, let's check for another type } + + // execenv cleanup + try { + const execenv_exec_data* execenv_data = + &dynamic_cast< const execenv_exec_data& >(*data.get()); + LD(F("Got %s from all_exec_data (execenv cleanup)") % handle.original_pid()); + + const model::test_result& body_result = execenv_data->body_result; + if (body_result.good()) { + if (!handle.status()) { + result = model::test_result(model::test_result_broken, + "Test case execenv cleanup timed out"); + } else { + if (!handle.status().get().exited() || + handle.status().get().exitstatus() != EXIT_SUCCESS) { + result = model::test_result( + model::test_result_broken, + "Test case execenv cleanup did not terminate successfully"); // ? + } else { + result = body_result; + } + } + } else { + result = body_result; + } + + LD(F("Removing %s from all_exec_data (execenv cleanup) in favor of %s") + % handle.original_pid() + % execenv_data->body_exit_handle.original_pid()); + _pimpl->all_exec_data.erase(handle.original_pid()); + + handle = execenv_data->body_exit_handle; + } catch (const std::bad_cast& e) { + // ok, it was one of the types above + } + INV(result); std::shared_ptr< result_handle::bimpl > result_handle_bimpl( diff --git a/contrib/kyua/engine/scheduler.hpp b/contrib/kyua/engine/scheduler.hpp index 24ff0b5a26fc..ee01c83b4991 100644 --- a/contrib/kyua/engine/scheduler.hpp +++ b/contrib/kyua/engine/scheduler.hpp @@ -262,6 +262,7 @@ class scheduler_handle { extern utils::datetime::delta cleanup_timeout; +extern utils::datetime::delta execenv_cleanup_timeout; extern utils::datetime::delta list_timeout; diff --git a/contrib/kyua/engine/tap.cpp b/contrib/kyua/engine/tap.cpp index 85e23857f5b7..ed35ba40433f 100644 --- a/contrib/kyua/engine/tap.cpp +++ b/contrib/kyua/engine/tap.cpp @@ -35,6 +35,7 @@ extern "C" { #include #include "engine/exceptions.hpp" +#include "engine/execenv/execenv.hpp" #include "engine/tap_parser.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" @@ -48,6 +49,7 @@ extern "C" { #include "utils/sanity.hpp" namespace config = utils::config; +namespace execenv = engine::execenv; namespace fs = utils::fs; namespace process = utils::process; @@ -151,7 +153,10 @@ engine::tap_interface::exec_test( } process::args_vector args; - process::exec(test_program.absolute_path(), args); + + auto e = execenv::get(test_program, test_case_name); + e->init(); + e->exec(args); } diff --git a/contrib/kyua/examples/kyua.conf b/contrib/kyua/examples/kyua.conf index 83418a320dc4..d7e2ef60e50a 100644 --- a/contrib/kyua/examples/kyua.conf +++ b/contrib/kyua/examples/kyua.conf @@ -43,6 +43,9 @@ syntax(2) -- Name of the system architecture (aka processor type). architecture = "x86_64" +-- List of execution environments. +execenvs = "host jail" + -- Maximum number of jobs (such as test case runs) to execute concurrently. parallelism = 16 diff --git a/contrib/kyua/integration/cmd_config_test.sh b/contrib/kyua/integration/cmd_config_test.sh index ed457e5c4b37..02e7654571e7 100644 --- a/contrib/kyua/integration/cmd_config_test.sh +++ b/contrib/kyua/integration/cmd_config_test.sh @@ -42,6 +42,7 @@ all_body() { cat >"${HOME}/.kyua/kyua.conf" <expout < +#include "engine/execenv/execenv.hpp" #include "model/exceptions.hpp" #include "model/types.hpp" #include "utils/config/exceptions.hpp" @@ -247,6 +248,8 @@ init_tree(config::tree& tree) tree.define< config::strings_set_node >("allowed_platforms"); tree.define_dynamic("custom"); tree.define< config::string_node >("description"); + tree.define< config::string_node >("execenv"); + tree.define< config::string_node >("execenv_jail_params"); tree.define< config::bool_node >("has_cleanup"); tree.define< config::bool_node >("is_exclusive"); tree.define< config::strings_set_node >("required_configs"); @@ -270,6 +273,8 @@ set_defaults(config::tree& tree) tree.set< config::strings_set_node >("allowed_platforms", model::strings_set()); tree.set< config::string_node >("description", ""); + tree.set< config::string_node >("execenv", ""); + tree.set< config::string_node >("execenv_jail_params", ""); tree.set< config::bool_node >("has_cleanup", false); tree.set< config::bool_node >("is_exclusive", false); tree.set< config::strings_set_node >("required_configs", @@ -464,6 +469,36 @@ model::metadata::description(void) const } +/// Returns execution environment name. +/// +/// \return Name of configured execution environment. +const std::string& +model::metadata::execenv(void) const +{ + if (_pimpl->props.is_set("execenv")) { + return _pimpl->props.lookup< config::string_node >("execenv"); + } else { + return get_defaults().lookup< config::string_node >("execenv"); + } +} + + +/// Returns execenv jail(8) parameters string to run a test with. +/// +/// \return String of jail parameters. +const std::string& +model::metadata::execenv_jail_params(void) const +{ + if (_pimpl->props.is_set("execenv_jail_params")) { + return _pimpl->props.lookup< config::string_node >( + "execenv_jail_params"); + } else { + return get_defaults().lookup< config::string_node >( + "execenv_jail_params"); + } +} + + /// Returns whether the test has a cleanup part or not. /// /// \return True if there is a cleanup part; false otherwise. @@ -478,6 +513,17 @@ model::metadata::has_cleanup(void) const } +/// Returns whether the test has a specific execenv apart from default one. +/// +/// \return True if there is a non-host execenv configured; false otherwise. +bool +model::metadata::has_execenv(void) const +{ + const std::string& name = execenv(); + return !name.empty() && name != engine::execenv::default_execenv_name; +} + + /// Returns whether the test is exclusive or not. /// /// \return True if the test has to be run on its own, not concurrently with any @@ -890,6 +936,36 @@ model::metadata_builder::set_description(const std::string& description) } +/// Sets execution environment name. +/// +/// \param name Execution environment name. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_execenv(const std::string& name) +{ + set< config::string_node >(_pimpl->props, "execenv", name); + return *this; +} + + +/// Sets execenv jail(8) parameters string to run the test with. +/// +/// \param params String of jail parameters. +/// +/// \return A reference to this builder. +/// +/// \throw model::error If the value is invalid. +model::metadata_builder& +model::metadata_builder::set_execenv_jail_params(const std::string& params) +{ + set< config::string_node >(_pimpl->props, "execenv_jail_params", params); + return *this; +} + + /// Sets whether the test has a cleanup part or not. /// /// \param cleanup True if the test has a cleanup part; false otherwise. diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp index c7dd4519f122..83bc5348774a 100644 --- a/contrib/kyua/model/metadata.hpp +++ b/contrib/kyua/model/metadata.hpp @@ -67,7 +67,10 @@ class metadata { const strings_set& allowed_platforms(void) const; model::properties_map custom(void) const; const std::string& description(void) const; + const std::string& execenv(void) const; + const std::string& execenv_jail_params(void) const; bool has_cleanup(void) const; + bool has_execenv(void) const; bool is_exclusive(void) const; const strings_set& required_configs(void) const; const utils::units::bytes& required_disk_space(void) const; @@ -110,6 +113,8 @@ class metadata_builder : utils::noncopyable { metadata_builder& set_allowed_platforms(const strings_set&); metadata_builder& set_custom(const model::properties_map&); metadata_builder& set_description(const std::string&); + metadata_builder& set_execenv(const std::string&); + metadata_builder& set_execenv_jail_params(const std::string&); metadata_builder& set_has_cleanup(const bool); metadata_builder& set_is_exclusive(const bool); metadata_builder& set_required_configs(const strings_set&); diff --git a/contrib/kyua/model/metadata_test.cpp b/contrib/kyua/model/metadata_test.cpp index 7b22653ec1a2..b4c3dff5b029 100644 --- a/contrib/kyua/model/metadata_test.cpp +++ b/contrib/kyua/model/metadata_test.cpp @@ -315,6 +315,8 @@ ATF_TEST_CASE_BODY(to_properties) props["allowed_platforms"] = ""; props["custom.foo"] = "bar"; props["description"] = ""; + props["execenv"] = ""; + props["execenv_jail_params"] = ""; props["has_cleanup"] = "false"; props["is_exclusive"] = "false"; props["required_configs"] = ""; @@ -406,7 +408,8 @@ ATF_TEST_CASE_BODY(output__defaults) std::ostringstream str; str << model::metadata_builder().build(); ATF_REQUIRE_EQ("metadata{allowed_architectures='', allowed_platforms='', " - "description='', has_cleanup='false', is_exclusive='false', " + "description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='false', " "required_configs='', " "required_disk_space='0', required_files='', " "required_memory='0', " @@ -428,7 +431,8 @@ ATF_TEST_CASE_BODY(output__some_values) .build(); ATF_REQUIRE_EQ( "metadata{allowed_architectures='abc', allowed_platforms='', " - "description='', has_cleanup='false', is_exclusive='true', " + "description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='true', " "required_configs='', " "required_disk_space='0', required_files='bar foo', " "required_memory='1.00K', " diff --git a/contrib/kyua/model/test_case_test.cpp b/contrib/kyua/model/test_case_test.cpp index 1a55de0fab42..1e2597d1501e 100644 --- a/contrib/kyua/model/test_case_test.cpp +++ b/contrib/kyua/model/test_case_test.cpp @@ -200,7 +200,8 @@ ATF_TEST_CASE_BODY(test_case__output) ATF_REQUIRE_EQ( "test_case{name='the-name', " "metadata=metadata{allowed_architectures='', allowed_platforms='foo', " - "custom.bar='baz', description='', has_cleanup='false', " + "custom.bar='baz', description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', " "is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " "required_memory='0', " diff --git a/contrib/kyua/model/test_program_test.cpp b/contrib/kyua/model/test_program_test.cpp index f9a8f7e59da3..ddfbc430387c 100644 --- a/contrib/kyua/model/test_program_test.cpp +++ b/contrib/kyua/model/test_program_test.cpp @@ -544,7 +544,8 @@ check_output__no_test_cases(void) "test_program{interface='plain', binary='binary/path', " "root='/the/root', test_suite='suite-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " - "description='', has_cleanup='false', is_exclusive='false', " + "description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " "required_memory='0', " "required_programs='', required_user='', timeout='300'}, " @@ -593,21 +594,23 @@ check_output__some_test_cases(void) "test_program{interface='plain', binary='binary/path', " "root='/the/root', test_suite='suite-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " - "description='', has_cleanup='false', is_exclusive='false', " + "description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " "required_memory='0', " "required_programs='', required_user='', timeout='300'}, " "test_cases=map(" "another-name=test_case{name='another-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='', " - "description='', has_cleanup='false', is_exclusive='false', " + "description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " "required_memory='0', " "required_programs='', required_user='', timeout='300'}}, " "the-name=test_case{name='the-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='foo', " - "custom.bar='baz', description='', has_cleanup='false', " - "is_exclusive='false', " + "custom.bar='baz', description='', execenv='', execenv_jail_params='', " + "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " "required_memory='0', " "required_programs='', required_user='', timeout='300'}})}", diff --git a/contrib/kyua/os/freebsd/execenv_jail.cpp b/contrib/kyua/os/freebsd/execenv_jail.cpp new file mode 100644 index 000000000000..04f44a412760 --- /dev/null +++ b/contrib/kyua/os/freebsd/execenv_jail.cpp @@ -0,0 +1,78 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "os/freebsd/execenv_jail.hpp" + +#include "model/metadata.hpp" +#include "model/test_case.hpp" +#include "os/freebsd/utils/jail.hpp" +#include "utils/fs/path.hpp" + + +namespace freebsd { + + +bool execenv_jail_supported = true; + + +static utils::jail jail = utils::jail(); + + +void +execenv_jail::init() const +{ + auto test_case = _test_program.find(_test_case_name); + + jail.create( + jail.make_name(_test_program.absolute_path(), _test_case_name), + test_case.get_metadata().execenv_jail_params() + ); +} + + +void +execenv_jail::cleanup() const +{ + jail.remove( + jail.make_name(_test_program.absolute_path(), _test_case_name) + ); +} + + +void +execenv_jail::exec(const args_vector& args) const +{ + jail.exec( + jail.make_name(_test_program.absolute_path(), _test_case_name), + _test_program.absolute_path(), + args + ); +} + + +} // namespace freebsd diff --git a/contrib/kyua/os/freebsd/execenv_jail.hpp b/contrib/kyua/os/freebsd/execenv_jail.hpp new file mode 100644 index 000000000000..e6d2c2e42497 --- /dev/null +++ b/contrib/kyua/os/freebsd/execenv_jail.hpp @@ -0,0 +1,65 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file os/freebsd/execenv_jail.hpp +/// FreeBSD jail execution environment. + +#if !defined(FREEBSD_EXECENV_JAIL_HPP) +#define FREEBSD_EXECENV_JAIL_HPP + +#include "engine/execenv/execenv.hpp" + +#include "utils/process/operations_fwd.hpp" + +namespace execenv = engine::execenv; + +using utils::process::args_vector; + + +namespace freebsd { + + +extern bool execenv_jail_supported; + + +class execenv_jail : public execenv::interface { +public: + execenv_jail(const model::test_program& test_program, + const std::string& test_case_name) : + execenv::interface(test_program, test_case_name) + {} + + void init() const; + void cleanup() const; + void exec(const args_vector& args) const UTILS_NORETURN; +}; + + +} // namespace freebsd + +#endif // !defined(FREEBSD_EXECENV_JAIL_HPP) diff --git a/contrib/kyua/os/freebsd/execenv_jail_manager.cpp b/contrib/kyua/os/freebsd/execenv_jail_manager.cpp new file mode 100644 index 000000000000..18673f6b0faa --- /dev/null +++ b/contrib/kyua/os/freebsd/execenv_jail_manager.cpp @@ -0,0 +1,63 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "os/freebsd/execenv_jail_manager.hpp" + +#include "model/metadata.hpp" +#include "model/test_case.hpp" +#include "os/freebsd/execenv_jail.hpp" + +static const std::string execenv_name = "jail"; + +const std::string& +freebsd::execenv_jail_manager::name() const +{ + return execenv_name; +} + + +bool +freebsd::execenv_jail_manager::is_supported() const +{ + return freebsd::execenv_jail_supported; +} + + +std::unique_ptr< execenv::interface > +freebsd::execenv_jail_manager::probe( + const model::test_program& test_program, + const std::string& test_case_name) const +{ + auto test_case = test_program.find(test_case_name); + if (test_case.get_metadata().execenv() != execenv_name) + return nullptr; + + return std::unique_ptr< execenv::interface >( + new freebsd::execenv_jail(test_program, test_case_name) + ); +} diff --git a/contrib/kyua/os/freebsd/execenv_jail_manager.hpp b/contrib/kyua/os/freebsd/execenv_jail_manager.hpp new file mode 100644 index 000000000000..eee9da9ed7d0 --- /dev/null +++ b/contrib/kyua/os/freebsd/execenv_jail_manager.hpp @@ -0,0 +1,54 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file os/freebsd/execenv_jail_manager.hpp +/// FreeBSD jail execution environment manager. + +#if !defined(FREEBSD_EXECENV_JAIL_MANAGER_HPP) +#define FREEBSD_EXECENV_JAIL_MANAGER_HPP + +#include "engine/execenv/execenv.hpp" + +namespace execenv = engine::execenv; + +namespace freebsd { + + +class execenv_jail_manager : public execenv::manager { +public: + const std::string& name() const; + bool is_supported() const; + std::unique_ptr< execenv::interface > probe( + const model::test_program& test_program, + const std::string& test_case_name) const; +}; + + +} // namespace freebsd + +#endif // !defined(FREEBSD_EXECENV_JAIL_MANAGER_HPP) diff --git a/contrib/kyua/os/freebsd/execenv_jail_stub.cpp b/contrib/kyua/os/freebsd/execenv_jail_stub.cpp new file mode 100644 index 000000000000..9425618e2b5a --- /dev/null +++ b/contrib/kyua/os/freebsd/execenv_jail_stub.cpp @@ -0,0 +1,75 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "os/freebsd/execenv_jail.hpp" + +#include + +#include "utils/process/operations_fwd.hpp" + +using utils::process::args_vector; + + +static inline void requires_freebsd(void) UTILS_NORETURN; + +static inline void +requires_freebsd(void) +{ + std::cerr << "execenv=\"jail\" requires FreeBSD with jail feature.\n"; + std::exit(EXIT_FAILURE); +} + + +namespace freebsd { + + +bool execenv_jail_supported = false; + + +void +execenv_jail::init() const +{ + requires_freebsd(); +} + + +void +execenv_jail::cleanup() const +{ + requires_freebsd(); +} + + +void +execenv_jail::exec(const args_vector&) const +{ + requires_freebsd(); +} + + +} // namespace freebsd diff --git a/contrib/kyua/os/freebsd/main.cpp b/contrib/kyua/os/freebsd/main.cpp new file mode 100644 index 000000000000..13e5dcf0e023 --- /dev/null +++ b/contrib/kyua/os/freebsd/main.cpp @@ -0,0 +1,54 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "os/freebsd/main.hpp" + +#include "engine/execenv/execenv.hpp" +#include "os/freebsd/execenv_jail_manager.hpp" + +namespace execenv = engine::execenv; + +/// FreeBSD related features initialization. +/// +/// \param argc The number of arguments passed on the command line. +/// \param argv NULL-terminated array containing the command line arguments. +/// +/// \return 0 on success, some other integer on error. +/// +/// \throw std::exception This throws any uncaught exception. Such exceptions +/// are bugs, but we let them propagate so that the runtime will abort and +/// dump core. +int +freebsd::main(const int, const char* const* const) +{ + execenv::register_execenv( + std::shared_ptr< execenv::manager >(new freebsd::execenv_jail_manager()) + ); + + return 0; +} diff --git a/contrib/kyua/os/freebsd/main.hpp b/contrib/kyua/os/freebsd/main.hpp new file mode 100644 index 000000000000..0581483e37e0 --- /dev/null +++ b/contrib/kyua/os/freebsd/main.hpp @@ -0,0 +1,41 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file os/freebsd/main.hpp +/// FreeBSD related features initialization. + +#if !defined(FREEBSD_MAIN_HPP) +#define FREEBSD_MAIN_HPP + +namespace freebsd { + +int main(const int argc, const char* const* const argv); + +} // namespace freebsd + +#endif // !defined(FREEBSD_MAIN_HPP) diff --git a/contrib/kyua/os/freebsd/utils/jail.cpp b/contrib/kyua/os/freebsd/utils/jail.cpp new file mode 100644 index 000000000000..b39761f28e51 --- /dev/null +++ b/contrib/kyua/os/freebsd/utils/jail.cpp @@ -0,0 +1,306 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "os/freebsd/utils/jail.hpp" + +extern "C" { +#include +#include +#include + +// FreeBSD sysctl facility +#include + +// FreeBSD Jail syscalls +#include +#include + +// FreeBSD Jail library +#include +} + +#include +#include +#include + +#include "model/metadata.hpp" +#include "model/test_case.hpp" +#include "model/test_program.hpp" +#include "utils/fs/path.hpp" +#include "utils/process/child.ipp" +#include "utils/format/macros.hpp" +#include "utils/process/operations.hpp" +#include "utils/process/status.hpp" + +namespace process = utils::process; +namespace fs = utils::fs; + +using utils::process::args_vector; +using utils::process::child; + + +static const size_t jail_name_max_len = MAXHOSTNAMELEN - 1; +static const char* jail_name_prefix = "kyua"; + + +/// Functor to run a program. +class run { + /// Program binary absolute path. + const utils::fs::path& _program; + + /// Program arguments. + const args_vector& _args; + +public: + /// Constructor. + /// + /// \param program Program binary absolute path. + /// \param args Program arguments. + run( + const utils::fs::path& program, + const args_vector& args) : + _program(program), + _args(args) + { + } + + /// Body of the subprocess. + void + operator()(void) + { + process::exec(_program, _args); + } +}; + + +namespace freebsd { +namespace utils { + + +std::vector< std::string > +jail::parse_params_string(const std::string& str) +{ + std::vector< std::string > params; + std::string p; + char quote = 0; + + std::istringstream iss(str); + while (iss >> p) { + if (p.front() == '"' || p.front() == '\'') { + quote = p.front(); + p.erase(p.begin()); + if (p.find(quote) == std::string::npos) { + std::string rest; + std::getline(iss, rest, quote); + p += rest; + iss.ignore(); + } + if (p.back() == quote) + p.erase(p.end() - 1); + } + params.push_back(p); + } + + return params; +} + + +/// Constructs a jail name based on program and test case. +/// +/// The formula is "kyua" + + "_" + . +/// All non-alphanumeric chars are replaced with "_". +/// +/// If a resulting string exceeds maximum allowed length of a jail name, +/// then it's shortened from the left side keeping the "kyua" prefix. +/// +/// \param program The test program. +/// \param test_case_name Name of the test case. +/// +/// \return A jail name string. +std::string +jail::make_name(const fs::path& program, + const std::string& test_case_name) +{ + std::string name = std::regex_replace( + program.str() + "_" + test_case_name, + std::regex(R"([^A-Za-z0-9_])"), + "_"); + + const std::string::size_type limit = + jail_name_max_len - strlen(jail_name_prefix); + if (name.length() > limit) + name.erase(0, name.length() - limit); + + return jail_name_prefix + name; +} + + +/// Create a jail with a given name and params string. +/// +/// A new jail will always be 'persist', thus the caller is expected to remove +/// the jail eventually via remove(). +/// +/// It's expected to be run in a subprocess. +/// +/// \param jail_name Name of a new jail. +/// \param jail_params String of jail parameters. +void +jail::create(const std::string& jail_name, + const std::string& jail_params) +{ + args_vector av; + + // creation flag + av.push_back("-qc"); + + // jail name + av.push_back("name=" + jail_name); + + // determine maximum allowed children.max + const char* const oid = "security.jail.children.max"; + int max; + size_t len = sizeof(max); + if (::sysctlbyname(oid, &max, &len, NULL, 0) != 0) { + std::cerr << "sysctlbyname(" << oid << ") errors: " + << strerror(errno) << ".\n"; + std::exit(EXIT_FAILURE); + } + if (len < sizeof(max)) { + std::cerr << "sysctlbyname(" << oid << ") provides less " + "data (" << len << ") than expected (" << sizeof(max) << ").\n"; + std::exit(EXIT_FAILURE); + } + if (max < 0) { + std::cerr << "sysctlbyname(" << oid << ") yields " + "abnormal " << max << ".\n"; + std::exit(EXIT_FAILURE); + } + if (max > 0) + max--; // a child jail must have less than parent's children.max + av.push_back("children.max=" + std::to_string(max)); + + // test defined jail params + const std::vector< std::string > params = parse_params_string(jail_params); + for (const std::string& p : params) + av.push_back(p); + + // it must be persist + av.push_back("persist"); + + // invoke jail + std::auto_ptr< process::child > child = child::fork_capture( + run(fs::path("/usr/sbin/jail"), av)); + process::status status = child->wait(); + + // expect success + if (status.exited() && status.exitstatus() == EXIT_SUCCESS) + return; + + // otherwise, let us know what jail thinks and fail fast + std::cerr << child->output().rdbuf(); + std::exit(EXIT_FAILURE); +} + + +/// Executes an external binary in a jail and replaces the current process. +/// +/// \param jail_name Name of the jail to run within. +/// \param program The test program binary absolute path. +/// \param args The arguments to pass to the binary, without the program name. +void +jail::exec(const std::string& jail_name, + const fs::path& program, + const args_vector& args) throw() +{ + // get work dir prepared by kyua + char cwd[PATH_MAX]; + if (::getcwd(cwd, sizeof(cwd)) == NULL) { + std::cerr << "jail::exec: getcwd() errors: " + << strerror(errno) << ".\n"; + std::exit(EXIT_FAILURE); + } + + // get jail id by its name + int jid = ::jail_getid(jail_name.c_str()); + if (jid == -1) { + std::cerr << "jail::exec: jail_getid() errors: " + << strerror(errno) << ": " << jail_errmsg << ".\n"; + std::exit(EXIT_FAILURE); + } + + // attach to the jail + if (::jail_attach(jid) == -1) { + std::cerr << "jail::exec: jail_attach() errors: " + << strerror(errno) << ".\n"; + std::exit(EXIT_FAILURE); + } + + // set back the expected work dir + if (::chdir(cwd) == -1) { + std::cerr << "jail::exec: chdir() errors: " + << strerror(errno) << ".\n"; + std::exit(EXIT_FAILURE); + } + + process::exec(program, args); +} + + +/// Removes a jail with a given name. +/// +/// It's expected to be run in a subprocess. +/// +/// \param jail_name Name of a jail to remove. +void +jail::remove(const std::string& jail_name) +{ + args_vector av; + + // removal flag + av.push_back("-r"); + + // jail name + av.push_back(jail_name); + + // invoke jail + std::auto_ptr< process::child > child = child::fork_capture( + run(fs::path("/usr/sbin/jail"), av)); + process::status status = child->wait(); + + // expect success + if (status.exited() && status.exitstatus() == EXIT_SUCCESS) + std::exit(EXIT_SUCCESS); + + // otherwise, let us know what jail thinks and fail fast + std::cerr << child->output().rdbuf(); + std::exit(EXIT_FAILURE); +} + + +} // namespace utils +} // namespace freebsd diff --git a/contrib/kyua/os/freebsd/utils/jail.hpp b/contrib/kyua/os/freebsd/utils/jail.hpp new file mode 100644 index 000000000000..5b972155cd25 --- /dev/null +++ b/contrib/kyua/os/freebsd/utils/jail.hpp @@ -0,0 +1,64 @@ +// Copyright 2024 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file os/freebsd/utils/jail.hpp +/// FreeBSD jail utilities. + +#if !defined(FREEBSD_UTILS_JAIL_HPP) +#define FREEBSD_UTILS_JAIL_HPP + +#include "utils/defs.hpp" +#include "utils/fs/path_fwd.hpp" +#include "utils/process/operations_fwd.hpp" + +namespace fs = utils::fs; + +using utils::process::args_vector; + +namespace freebsd { +namespace utils { + + +class jail { +public: + std::vector< std::string > parse_params_string(const std::string& str); + std::string make_name(const fs::path& program, + const std::string& test_case_name); + void create(const std::string& jail_name, + const std::string& jail_params); + void exec(const std::string& jail_name, + const fs::path& program, + const args_vector& args) throw() UTILS_NORETURN; + void remove(const std::string& jail_name); +}; + + +} // namespace utils +} // namespace freebsd + +#endif // !defined(FREEBSD_UTILS_JAIL_HPP) diff --git a/contrib/kyua/utils/config/nodes.ipp b/contrib/kyua/utils/config/nodes.ipp index 9e0a1228cccd..0ec3832cc690 100644 --- a/contrib/kyua/utils/config/nodes.ipp +++ b/contrib/kyua/utils/config/nodes.ipp @@ -382,9 +382,14 @@ config::base_set_node< ValueType >::push_lua(lutok::state& /* state */) const template< typename ValueType > void config::base_set_node< ValueType >::set_lua( - lutok::state& /* state */, - const int /* value_index */) + lutok::state& state, + const int value_index) { + if (state.is_string(value_index)) { + set_string(state.to_string(value_index)); + return; + } + UNREACHABLE; } diff --git a/contrib/kyua/utils/process/executor.cpp b/contrib/kyua/utils/process/executor.cpp index a00632614737..b73a86b9c1b9 100644 --- a/contrib/kyua/utils/process/executor.cpp +++ b/contrib/kyua/utils/process/executor.cpp @@ -689,6 +689,34 @@ struct utils::process::executor::executor_handle::impl : utils::noncopyable { data._pimpl->state_owners, all_exec_handles))); } + + executor::exit_handle + reap(const pid_t original_pid) + { + const exec_handles_map::iterator iter = all_exec_handles.find( + original_pid); + exec_handle& data = (*iter).second; + data._pimpl->timer.unprogram(); + + if (!fs::exists(data.stdout_file())) { + std::ofstream new_stdout(data.stdout_file().c_str()); + } + if (!fs::exists(data.stderr_file())) { + std::ofstream new_stderr(data.stderr_file().c_str()); + } + + return exit_handle(std::shared_ptr< exit_handle::impl >( + new exit_handle::impl( + data.pid(), + none, + data._pimpl->unprivileged_user, + data._pimpl->start_time, datetime::timestamp::now(), + data.control_directory(), + data.stdout_file(), + data.stderr_file(), + data._pimpl->state_owners, + all_exec_handles))); + } }; @@ -879,6 +907,20 @@ executor::executor_handle::wait_any(void) } +/// Forms exit_handle for the given PID subprocess. +/// +/// Can be used in the cases when we want to do cleanup(s) of a killed test +/// subprocess, but we do not have exit handle as we usually do after normal +/// wait mechanism. +/// +/// \return A pointer to an object describing the subprocess. +executor::exit_handle +executor::executor_handle::reap(const int pid) +{ + return _pimpl->reap(pid); +} + + /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the diff --git a/contrib/kyua/utils/process/executor.hpp b/contrib/kyua/utils/process/executor.hpp index 858ad9c815aa..01a17ff8c681 100644 --- a/contrib/kyua/utils/process/executor.hpp +++ b/contrib/kyua/utils/process/executor.hpp @@ -215,6 +215,7 @@ class executor_handle { exit_handle wait(const exec_handle); exit_handle wait_any(void); + exit_handle reap(const pid_t); void check_interrupt(void) const; }; diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile index 5160d1ed6328..f9ac12bb0f9d 100644 --- a/usr.bin/kyua/Makefile +++ b/usr.bin/kyua/Makefile @@ -127,7 +127,12 @@ SRCS+= engine/atf.cpp \ engine/scanner.cpp \ engine/tap.cpp \ engine/tap_parser.cpp \ - engine/scheduler.cpp + engine/scheduler.cpp \ + engine/execenv/execenv.cpp \ + engine/execenv/execenv_host.cpp + +SRCS+= os/freebsd/execenv_jail_manager.cpp \ + os/freebsd/main.cpp SRCS+= store/dbtypes.cpp \ store/exceptions.cpp \ @@ -160,6 +165,14 @@ SRCS+= cli/cmd_about.cpp \ cli/config.cpp \ cli/main.cpp +.if ${MK_JAIL} == "no" +SRCS+= os/freebsd/execenv_jail_stub.cpp +.else +SRCS+= os/freebsd/execenv_jail.cpp \ + os/freebsd/utils/jail.cpp +LIBADD+= jail +.endif + FILESGROUPS= DOCS MISC STORE .if ${MK_EXAMPLES} != "no"