Page MenuHomeFreeBSD

D42350.id129537.diff
No OneTemporary

D42350.id129537.diff

diff --git a/contrib/kyua/engine/atf.cpp b/contrib/kyua/engine/atf.cpp
--- a/contrib/kyua/engine/atf.cpp
+++ b/contrib/kyua/engine/atf.cpp
@@ -39,6 +39,7 @@
#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"
@@ -190,7 +191,9 @@
args.push_back(F("-r%s") % (control_directory / result_name));
args.push_back(test_case_name);
- process::exec(test_program.absolute_path(), args);
+
+ engine::execenv::init(test_program, test_case_name);
+ engine::execenv::exec(test_program, test_case_name, args);
}
@@ -219,7 +222,8 @@
}
args.push_back(F("%s:cleanup") % test_case_name);
- process::exec(test_program.absolute_path(), args);
+
+ engine::execenv::exec(test_program, test_case_name, args);
}
diff --git a/contrib/kyua/engine/atf_list.cpp b/contrib/kyua/engine/atf_list.cpp
--- a/contrib/kyua/engine/atf_list.cpp
+++ b/contrib/kyua/engine/atf_list.cpp
@@ -121,6 +121,10 @@
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") {
+ mdbuilder.set_string("execenv_jail", value);
} else if (name == "require.config") {
mdbuilder.set_string("required_configs", value);
} else if (name == "require.files") {
diff --git a/contrib/kyua/engine/execenv/execenv.hpp b/contrib/kyua/engine/execenv/execenv.hpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/engine/execenv/execenv.hpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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 multiplexer.
+///
+/// A test case may ask for a specific execution environment like running in
+/// a jail, what needs initialization before the test run and cleanup after.
+///
+/// By default, there is no specific execution environment, so called host
+/// environment, and no additional initialization or cleanup is done.
+
+#if !defined(ENGINE_EXECENV_EXECENV_HPP)
+#define ENGINE_EXECENV_EXECENV_HPP
+
+#include "model/test_program.hpp"
+#include "utils/defs.hpp"
+#include "utils/process/operations_fwd.hpp"
+
+namespace engine {
+namespace execenv {
+
+
+void init(const model::test_program&, const std::string&);
+
+void exec(const model::test_program&, const std::string&,
+ const utils::process::args_vector&) throw() UTILS_NORETURN;
+
+void cleanup(const model::test_program&, const std::string&);
+
+
+} // namespace execenv
+} // namespace engine
+
+#endif // !defined(ENGINE_EXECENV_EXECENV_HPP)
diff --git a/contrib/kyua/engine/execenv/execenv.cpp b/contrib/kyua/engine/execenv/execenv.cpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/engine/execenv/execenv.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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/jail.hpp"
+#include "model/metadata.hpp"
+#include "model/test_case.hpp"
+#include "model/test_program.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/operations.hpp"
+
+namespace execenv = engine::execenv;
+namespace process = utils::process;
+
+using utils::process::args_vector;
+
+
+/// Initialize execution environment.
+///
+/// It's expected to be called inside a fork which runs 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.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::init(const model::test_program& test_program,
+ const std::string& test_case_name)
+{
+ const model::test_case& test_case = test_program.find(test_case_name);
+ if (test_case.get_metadata().is_execenv_jail())
+ return execenv::jail::init(test_program, test_case_name);
+ // else if ...other env
+
+ // host environment by default
+ return;
+}
+
+
+/// Execute within an execution environment.
+///
+/// It's expected to be called inside a fork which runs interface::exec_test().
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::exec(const model::test_program& test_program,
+ const std::string& test_case_name,
+ const args_vector& args) throw()
+{
+ const model::test_case& test_case = test_program.find(test_case_name);
+ if (test_case.get_metadata().is_execenv_jail())
+ execenv::jail::exec(test_program, test_case_name, args);
+ // else if ...other env
+
+ // host environment by default
+ process::exec(test_program.absolute_path(), args);
+}
+
+
+/// Cleanup execution environment.
+///
+/// It's expected to be called inside a fork for execenv cleanup.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::cleanup(const model::test_program& test_program,
+ const std::string& test_case_name)
+{
+ const model::test_case& test_case = test_program.find(test_case_name);
+ if (test_case.get_metadata().is_execenv_jail())
+ return execenv::jail::cleanup(test_program, test_case_name);
+ // else if ...other env
+
+ // cleanup is not expected to be called for host environment
+ std::exit(EXIT_SUCCESS);
+}
diff --git a/contrib/kyua/engine/execenv/jail.hpp b/contrib/kyua/engine/execenv/jail.hpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/engine/execenv/jail.hpp
@@ -0,0 +1,55 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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/jail.hpp
+/// FreeBSD jail execution environment.
+
+#if !defined(ENGINE_EXECENV_JAIL_HPP)
+#define ENGINE_EXECENV_JAIL_HPP
+
+#include "model/test_program.hpp"
+#include "utils/defs.hpp"
+#include "utils/process/operations_fwd.hpp"
+
+namespace engine {
+namespace execenv {
+namespace jail {
+
+
+void init(const model::test_program&, const std::string&);
+
+void exec(const model::test_program&, const std::string&,
+ const utils::process::args_vector&) throw() UTILS_NORETURN;
+
+void cleanup(const model::test_program&, const std::string&);
+
+
+} // namespace jail
+} // namespace execenv
+} // namespace engine
+
+#endif // !defined(ENGINE_EXECENV_JAIL_HPP)
diff --git a/contrib/kyua/engine/execenv/jail.cpp b/contrib/kyua/engine/execenv/jail.cpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/engine/execenv/jail.cpp
@@ -0,0 +1,88 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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/jail.hpp"
+
+#include "model/metadata.hpp"
+#include "model/test_case.hpp"
+#include "model/test_program.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/jail.hpp"
+#include "utils/process/operations.hpp"
+
+namespace execenv = engine::execenv;
+namespace process = utils::process;
+
+using utils::process::args_vector;
+
+
+/// Initialize execution environment.
+///
+/// It's expected to be called inside a fork which runs interface::exec_test(),
+/// so we can fail a test fast if its execution environment setup fails.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::jail::init(const model::test_program& test_program,
+ const std::string& test_case_name)
+{
+ const model::test_case& test_case = test_program.find(test_case_name);
+
+ process::jail::create(test_program.absolute_path(), test_case_name,
+ test_case.get_metadata().execenv_jail());
+}
+
+
+/// Execute within an execution environment.
+///
+/// It's expected to be called inside a fork which runs interface::exec_test().
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::jail::exec(const model::test_program& test_program,
+ const std::string& test_case_name,
+ const args_vector& args) throw()
+{
+ process::jail::exec(test_program.absolute_path(), test_case_name,
+ args);
+}
+
+
+/// Cleanup execution environment.
+///
+/// It's expected to be called inside a fork for execenv cleanup.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+execenv::jail::cleanup(const model::test_program& test_program,
+ const std::string& test_case_name)
+{
+ process::jail::remove(test_program.absolute_path(), test_case_name);
+}
diff --git a/contrib/kyua/engine/plain.cpp b/contrib/kyua/engine/plain.cpp
--- a/contrib/kyua/engine/plain.cpp
+++ b/contrib/kyua/engine/plain.cpp
@@ -34,6 +34,7 @@
#include <cstdlib>
+#include "engine/execenv/execenv.hpp"
#include "model/test_case.hpp"
#include "model/test_program.hpp"
#include "model/test_result.hpp"
@@ -104,7 +105,9 @@
}
process::args_vector args;
- process::exec(test_program.absolute_path(), args);
+
+ engine::execenv::init(test_program, test_case_name);
+ engine::execenv::exec(test_program, test_case_name, args);
}
diff --git a/contrib/kyua/engine/scheduler.hpp b/contrib/kyua/engine/scheduler.hpp
--- a/contrib/kyua/engine/scheduler.hpp
+++ b/contrib/kyua/engine/scheduler.hpp
@@ -262,6 +262,7 @@
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/scheduler.cpp b/contrib/kyua/engine/scheduler.cpp
--- a/contrib/kyua/engine/scheduler.cpp
+++ b/contrib/kyua/engine/scheduler.cpp
@@ -40,6 +40,7 @@
#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"
@@ -87,6 +88,10 @@
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 +211,18 @@
/// 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.
+ int 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 +239,14 @@
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 int 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 +285,40 @@
};
+/// 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 +545,39 @@
};
+/// 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 */)
+ {
+ engine::execenv::cleanup(_test_program, _test_case_name);
+ }
+};
+
+
/// Obtains the right scheduler interface for a given test program.
///
/// \param name The name of the interface of the test program.
@@ -835,6 +921,22 @@
% 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 +958,8 @@
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 +969,37 @@
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 +1061,61 @@
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 +1305,7 @@
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 +1340,8 @@
_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 +1377,7 @@
// 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 +1402,6 @@
_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 +1410,21 @@
// 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) {
+ // ignored
+ }
+
+ // 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 +1463,65 @@
_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) {
+ // ignored
}
+
+ // 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) {
+ // ignored
+ }
+
INV(result);
std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
diff --git a/contrib/kyua/engine/tap.cpp b/contrib/kyua/engine/tap.cpp
--- a/contrib/kyua/engine/tap.cpp
+++ b/contrib/kyua/engine/tap.cpp
@@ -35,6 +35,7 @@
#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"
@@ -151,7 +152,9 @@
}
process::args_vector args;
- process::exec(test_program.absolute_path(), args);
+
+ engine::execenv::init(test_program, test_case_name);
+ engine::execenv::exec(test_program, test_case_name, args);
}
diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp
--- a/contrib/kyua/model/metadata.hpp
+++ b/contrib/kyua/model/metadata.hpp
@@ -69,6 +69,10 @@
const std::string& description(void) const;
bool has_cleanup(void) const;
bool is_exclusive(void) const;
+ const std::string& execenv(void) const;
+ bool has_execenv(void) const;
+ const std::string& execenv_jail(void) const;
+ bool is_execenv_jail(void) const;
const strings_set& required_configs(void) const;
const utils::units::bytes& required_disk_space(void) const;
const paths_set& required_files(void) const;
@@ -112,6 +116,8 @@
metadata_builder& set_description(const std::string&);
metadata_builder& set_has_cleanup(const bool);
metadata_builder& set_is_exclusive(const bool);
+ metadata_builder& set_execenv(const std::string&);
+ metadata_builder& set_execenv_jail(const std::string&);
metadata_builder& set_required_configs(const strings_set&);
metadata_builder& set_required_disk_space(const utils::units::bytes&);
metadata_builder& set_required_files(const paths_set&);
diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp
--- a/contrib/kyua/model/metadata.cpp
+++ b/contrib/kyua/model/metadata.cpp
@@ -249,6 +249,8 @@
tree.define< config::string_node >("description");
tree.define< config::bool_node >("has_cleanup");
tree.define< config::bool_node >("is_exclusive");
+ tree.define< config::string_node >("execenv");
+ tree.define< config::string_node >("execenv_jail");
tree.define< config::strings_set_node >("required_configs");
tree.define< bytes_node >("required_disk_space");
tree.define< paths_set_node >("required_files");
@@ -272,6 +274,8 @@
tree.set< config::string_node >("description", "");
tree.set< config::bool_node >("has_cleanup", false);
tree.set< config::bool_node >("is_exclusive", false);
+ tree.set< config::string_node >("execenv", "");
+ tree.set< config::string_node >("execenv_jail", "");
tree.set< config::strings_set_node >("required_configs",
model::strings_set());
tree.set< bytes_node >("required_disk_space", units::bytes(0));
@@ -486,13 +490,64 @@
model::metadata::is_exclusive(void) const
{
if (_pimpl->props.is_set("is_exclusive")) {
- return _pimpl->props.lookup< config::bool_node >("is_exclusive");
+ const bool is_excl =
+ _pimpl->props.lookup< config::bool_node >("is_exclusive");
+ return is_excl && !is_execenv_jail();
} else {
return get_defaults().lookup< config::bool_node >("is_exclusive");
}
}
+/// 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 whether the test has any specific execenv apart from "host" 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 != "host";
+}
+
+
+/// Returns execenv jail parameters string to run a test with.
+///
+/// \return String of jail parameters.
+const std::string&
+model::metadata::execenv_jail(void) const
+{
+ if (_pimpl->props.is_set("execenv_jail")) {
+ return _pimpl->props.lookup< config::string_node >("execenv_jail");
+ } else {
+ return get_defaults().lookup< config::string_node >("execenv_jail");
+ }
+}
+
+
+/// Returns whether the test is configured for jail execenv.
+///
+/// \return True if there is a jail execenv is set; false otherwise.
+bool
+model::metadata::is_execenv_jail(void) const
+{
+ return execenv() == "jail";
+}
+
+
/// Returns the list of configuration variables needed by the test.
///
/// \return Set of configuration variables.
@@ -920,6 +975,36 @@
}
+/// 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 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(const std::string& params)
+{
+ set< config::string_node >(_pimpl->props, "execenv_jail", params);
+ return *this;
+}
+
+
/// Sets the list of configuration variables needed by the test.
///
/// \param vars Set of configuration variables.
diff --git a/contrib/kyua/utils/process/executor.hpp b/contrib/kyua/utils/process/executor.hpp
--- a/contrib/kyua/utils/process/executor.hpp
+++ b/contrib/kyua/utils/process/executor.hpp
@@ -215,6 +215,7 @@
exit_handle wait(const exec_handle);
exit_handle wait_any(void);
+ exit_handle reap(const int);
void check_interrupt(void) const;
};
diff --git a/contrib/kyua/utils/process/executor.cpp b/contrib/kyua/utils/process/executor.cpp
--- a/contrib/kyua/utils/process/executor.cpp
+++ b/contrib/kyua/utils/process/executor.cpp
@@ -671,6 +671,34 @@
data._pimpl->state_owners,
all_exec_handles)));
}
+
+ executor::exit_handle
+ reap(const int 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)));
+ }
};
@@ -773,6 +801,13 @@
timeout,
unprivileged_user,
detail::refcnt_t(new detail::refcnt_t::element_type(0)))));
+ if (_pimpl->all_exec_handles.find(handle.pid()) != _pimpl->all_exec_handles.end()) {
+ std::string m = "spawn_post: want to insert pid=" + handle.pid() + ", but found it in the map: ";
+ for (auto it : _pimpl->all_exec_handles)
+ m += (*it).first + ", ";
+ m += ".";
+ std::cerr << m;
+ }
INV_MSG(_pimpl->all_exec_handles.find(handle.pid()) ==
_pimpl->all_exec_handles.end(),
F("PID %s already in all_exec_handles; not properly cleaned "
@@ -853,6 +888,20 @@
}
+/// 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/jail.hpp b/contrib/kyua/utils/process/jail.hpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/utils/process/jail.hpp
@@ -0,0 +1,55 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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 utils/process/jail.hpp
+/// Collection of utilities for FreeBSD jail.
+
+#if !defined(UTILS_PROCESS_JAIL_HPP)
+#define UTILS_PROCESS_JAIL_HPP
+
+#include "utils/defs.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/process/operations_fwd.hpp"
+
+namespace utils {
+namespace process {
+namespace jail {
+
+
+void create(const utils::fs::path&, const std::string&, const std::string&);
+
+void exec(const utils::fs::path&, const std::string&,
+ const args_vector&) throw() UTILS_NORETURN;
+
+void remove(const utils::fs::path&, const std::string&);
+
+
+} // namespace jail
+} // namespace process
+} // namespace utils
+
+#endif // !defined(UTILS_PROCESS_JAIL_HPP)
diff --git a/contrib/kyua/utils/process/jail.cpp b/contrib/kyua/utils/process/jail.cpp
new file mode 100644
--- /dev/null
+++ b/contrib/kyua/utils/process/jail.cpp
@@ -0,0 +1,293 @@
+// Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
+//
+// 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 "utils/process/jail.hpp"
+
+extern "C" {
+#include <unistd.h>
+#include <sys/stat.h>
+}
+
+#include <fstream>
+#include <iostream>
+#include <regex>
+
+#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 fs = utils::fs;
+namespace process = utils::process;
+namespace jail = utils::process::jail;
+
+using utils::process::args_vector;
+using utils::process::child;
+
+
+namespace {
+
+
+static std::string
+make_jail_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 =
+ 255 /* jail name max */ - 4 /* "kyua" prefix */;
+ if (name.length() > limit)
+ name.erase(0, name.length() - limit);
+
+ return "kyua" + name;
+}
+
+
+static std::vector< std::string >
+parse_jail_params_string(const std::string& str)
+{
+ std::vector< std::string > params;
+ std::string p;
+ char quote = 0;
+
+ for (const char& c : str) {
+ // whitespace delimited parameter
+ if (quote == 0) {
+ if (std::isspace(c)) {
+ if (p.empty())
+ continue;
+ params.push_back(p);
+ p = "";
+ }
+ else if (c == '"' || c == '\'') {
+ if (!p.empty())
+ params.push_back(p);
+ p = "";
+ quote = c;
+ }
+ else
+ p += c;
+ }
+
+ // quoted parameter
+ else {
+ if (c == quote) {
+ if (!p.empty())
+ params.push_back(p);
+ p = "";
+ quote = 0;
+ }
+ else
+ p += c;
+ }
+ }
+
+ // leftovers
+ if (!p.empty())
+ params.push_back(p);
+
+ return params;
+}
+
+
+/// 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);
+ }
+};
+
+
+} // anonymous namespace
+
+
+/// Create a jail based on test program path and case name.
+///
+/// A new jail will always be 'persist', thus the caller is expected to remove
+/// the jail eventually via jail::remove().
+///
+/// It's expected to be run in a subprocess.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+/// \param jail String of jail parameters.
+void
+jail::create(const fs::path& program,
+ const std::string& test_case_name,
+ const std::string& jail_params)
+{
+ args_vector av;
+
+ // creation flag
+ av.push_back("-qc");
+
+ // jail name
+ av.push_back(F("name=%s") % make_jail_name(program, test_case_name));
+
+ // some obvious defaults to ease test authors' life
+ av.push_back("children.max=16");
+
+ // test defined jail params
+ const std::vector< std::string > params = parse_jail_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
+ char err[330];
+ child->output().getline(err, 330);
+ std::cerr << err << "\n";
+ std::exit(EXIT_FAILURE);
+}
+
+
+/// Executes an external binary in a jail and replaces the current process.
+///
+/// This function must not use any of the logging features so that the output
+/// of the subprocess is not "polluted" by our own messages.
+///
+/// This function must also not affect the global state of the current process
+/// as otherwise we would not be able to use vfork(). Only state stored in the
+/// stack can be touched.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+/// \param args The arguments to pass to the binary, without the program name.
+void
+jail::exec(const fs::path& program,
+ const std::string& test_case_name,
+ const args_vector& args) throw()
+{
+ args_vector av(args);
+ av.insert(av.begin(), program.str());
+
+ // get our work dir
+ char cwd[256];
+ if (getcwd(cwd, 256) == NULL) {
+ std::cerr << "process::jail::exec: getcwd() errors: "
+ << strerror(errno) << ".\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ // prepare a script to run in a jail to change back to the work dir
+ // and exec the program
+ std::string cd_exec_path = std::string(cwd) + "/kyua_cd_exec.sh";
+ std::ofstream f(cd_exec_path);
+ if (f.fail()) {
+ std::cerr << "process::jail::exec: cannot create kyua_cd_exec.sh file: "
+ << strerror(errno) << ".\n";
+ std::exit(EXIT_FAILURE);
+ }
+ f << "#!/bin/sh\n"
+ << "cd \"$1\" && shift && exec $*";
+ f.close();
+ if (chmod(cd_exec_path.c_str(),
+ S_IRUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH) != 0) {
+ std::cerr << "process::jail::exec: chmod() errors: "
+ << strerror(errno) << ".\n";
+ std::exit(EXIT_FAILURE);
+ }
+
+ // change current work dir inside a jail back to kyua work dir
+ av.insert(av.begin(), cwd);
+ av.insert(av.begin(), cd_exec_path);
+
+ av.insert(av.begin(), make_jail_name(program, test_case_name));
+
+ process::exec(fs::path("/usr/sbin/jexec"), av);
+}
+
+
+/// Removes a jail based on test program path and case name.
+///
+/// It's expected to be run in a subprocess.
+///
+/// \param program The test program binary absolute path.
+/// \param test_case_name Name of the test case.
+void
+jail::remove(const fs::path& program,
+ const std::string& test_case_name)
+{
+ args_vector av;
+
+ // removal flag
+ av.push_back("-r");
+
+ // jail name
+ av.push_back(make_jail_name(program, test_case_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
+ char err[330];
+ child->output().getline(err, 330);
+ std::cerr << err << "\n";
+ std::exit(EXIT_FAILURE);
+}
diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile
--- a/usr.bin/kyua/Makefile
+++ b/usr.bin/kyua/Makefile
@@ -89,6 +89,7 @@
utils/process/executor.cpp \
utils/process/fdstream.cpp \
utils/process/isolation.cpp \
+ utils/process/jail.cpp \
utils/process/operations.cpp \
utils/process/status.cpp \
utils/process/system.cpp \
@@ -128,7 +129,9 @@
engine/scanner.cpp \
engine/tap.cpp \
engine/tap_parser.cpp \
- engine/scheduler.cpp
+ engine/scheduler.cpp \
+ engine/execenv/execenv.cpp \
+ engine/execenv/jail.cpp
SRCS+= store/dbtypes.cpp \
store/exceptions.cpp \

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 17, 2:23 PM (3 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14680052
Default Alt Text
D42350.id129537.diff (50 KB)

Event Timeline