kyua: Add FreeBSD Jail execution environment support

A new Kyua concept is added -- "execution environment". A test can be
configured to be run within a specific environment. The test case
lifecycle is extended respectively:
- execenv init (creates a jail or does nothing for default
  execenv="host")
- test exec
- cleanup exec (optional)
- execenv cleanup (removes a jail or does nothing for default
  execenv="host")

The following new functionality is provided, from bottom to top:

1 ATF based tests

- The new "execenv" metadata property can be set to explicitly ask for
  an execution environment: "host" or "jail". If it's not defined, as
  all existing tests do, then it implicitly means "host".

- The new "execenv.jail.params" metadata property can be optionally
  defined to ask Kyua to use specific jail(8) parameters during creation
  of a temporary jail. An example is "vnet allow.raw_sockets".

  Kyua implicitly adds "children.max" to "execenv_jail_params"
  parameters with the maximum possible value. A test case can override
  it.

2 Kyuafile

- The same new metadata properties can be defined on Kyuafile level:
  "execenv" and "execenv_jail_params".

- Note that historically ATF uses dotted style of metadata naming, while
  Kyua uses underscore style. Hence "execenv.jail.params" vs.
  "execenv_jail_params".

3 kyua.conf, kyua CLI

- The new "execenvs" engine configuration variable can be set to a list
  of execution environments to run only tests designed for. Tests of not
  listed environments are skipped.

- By default, this variable lists all execution environments supported
  by a Kyua binary, e.g. execenvs="host jail".

- This variable can be changed via "kyua.conf" or via kyua CLI's "-v"
  parameter. For example, "kyua -v execenvs=host test" will run only
  host-based tests and skip jail-based ones.

- Current value of this variable can be examined with "kyua config".

[markj] This feature has not landed upstream yet.
See the discussion in https://github.com/freebsd/kyua/pull/224 .
Having the ability to automatically jail tests allows many network tests
to run in parallel, giving a drastic speedup.  So, let's import the
feature and start using it in main.

Signed-off-by:  Igor Ostapenko <pm@igoro.pro>
Reviewed by:    markj, kp
Tested by:      markj, kp
MFC after:	3 months
Differential Revision:  https://reviews.freebsd.org/D45865
This commit is contained in:
Igor Ostapenko 2024-07-16 21:41:12 +03:00 committed by Mark Johnston
parent 75e1fea68a
commit 257e70f1d5
40 changed files with 1792 additions and 28 deletions

View file

@ -10,3 +10,4 @@
* The FreeBSD Foundation
* Google Inc.
* Igor Ostapenko <pm@igoro.pro>

View file

@ -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());
}

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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);
}

View file

@ -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") {

View file

@ -35,6 +35,7 @@
#include <stdexcept>
#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;
}

View file

@ -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));
}

View file

@ -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)

View file

@ -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);
}

View file

@ -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)

View file

@ -34,6 +34,7 @@ extern "C" {
#include <cstdlib>
#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);
}

View file

@ -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;

View file

@ -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(

View file

@ -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;

View file

@ -35,6 +35,7 @@ extern "C" {
#include <cstdlib>
#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);
}

View file

@ -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

View file

@ -42,6 +42,7 @@ all_body() {
cat >"${HOME}/.kyua/kyua.conf" <<EOF
syntax(2)
architecture = "my-architecture"
execenvs = "my-env1 my-env2"
parallelism = 256
platform = "my-platform"
unprivileged_user = "$(id -u -n)"
@ -51,6 +52,7 @@ EOF
cat >expout <<EOF
architecture = my-architecture
execenvs = my-env1 my-env2
parallelism = 256
platform = my-platform
test_suites.suite1.the_variable = value1

View file

@ -97,6 +97,8 @@ CONTENTS STRIPPED BY TEST
allowed_architectures is empty
allowed_platforms is empty
description is empty
execenv is empty
execenv_jail_params is empty
has_cleanup = false
is_exclusive = false
required_configs is empty
@ -135,6 +137,8 @@ Test case metadata
allowed_architectures is empty
allowed_platforms is empty
description is empty
execenv is empty
execenv_jail_params is empty
has_cleanup = false
is_exclusive = false
required_configs is empty
@ -211,6 +215,8 @@ CONTENTS STRIPPED BY TEST
allowed_architectures is empty
allowed_platforms is empty
description is empty
execenv is empty
execenv_jail_params is empty
has_cleanup = false
is_exclusive = false
required_configs is empty
@ -249,6 +255,8 @@ Test case metadata
allowed_architectures is empty
allowed_platforms is empty
description is empty
execenv is empty
execenv_jail_params is empty
has_cleanup = false
is_exclusive = false
required_configs is empty

View file

@ -251,6 +251,8 @@ Metadata:
allowed_architectures is empty
allowed_platforms is empty
description is empty
execenv is empty
execenv_jail_params is empty
has_cleanup = false
is_exclusive = false
required_configs is empty

View file

@ -27,6 +27,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "cli/main.hpp"
#include "os/freebsd/main.hpp"
/// Program entry point.
@ -46,5 +47,7 @@
int
main(const int argc, const char* const* const argv)
{
freebsd::main(argc, argv);
return cli::main(argc, argv);
}

View file

@ -30,6 +30,7 @@
#include <memory>
#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.

View file

@ -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&);

View file

@ -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', "

View file

@ -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', "

View file

@ -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'}})}",

View file

@ -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

View file

@ -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)

View file

@ -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)
);
}

View file

@ -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)

View file

@ -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 <iostream>
#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

View file

@ -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;
}

View file

@ -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)

View file

@ -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 <limits.h>
#include <unistd.h>
#include <sys/stat.h>
// FreeBSD sysctl facility
#include <sys/sysctl.h>
// FreeBSD Jail syscalls
#include <sys/param.h>
#include <sys/jail.h>
// FreeBSD Jail library
#include <jail.h>
}
#include <fstream>
#include <iostream>
#include <regex>
#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" + <program path> + "_" + <test case name>.
/// 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

View file

@ -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)

View file

@ -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;
}

View file

@ -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

View file

@ -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;
};

View file

@ -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"