Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F102741723
D42350.id129537.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
50 KB
Referenced Files
None
Subscribers
None
D42350.id129537.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 17, 2:23 PM (7 h, 32 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
14680052
Default Alt Text
D42350.id129537.diff (50 KB)
Attached To
Mode
D42350: kyua: add jail execution environment
Attached
Detach File
Event Timeline
Log In to Comment