diff --git a/src/linyaps_box/app.cpp b/src/linyaps_box/app.cpp index 269eec0..79d1a22 100644 --- a/src/linyaps_box/app.cpp +++ b/src/linyaps_box/app.cpp @@ -49,8 +49,7 @@ try { return 0; }, [](const command::exec_options &options) -> int { - command::exec(options); - __builtin_unreachable(); + return command::exec(options); }, [](const command::kill_options &options) { command::kill(options); diff --git a/src/linyaps_box/command/exec.cpp b/src/linyaps_box/command/exec.cpp index 4b9fdf0..f0833ba 100644 --- a/src/linyaps_box/command/exec.cpp +++ b/src/linyaps_box/command/exec.cpp @@ -8,7 +8,7 @@ #include "linyaps_box/runtime.h" #include "linyaps_box/status_directory.h" -void linyaps_box::command::exec(const struct exec_options &options) +auto linyaps_box::command::exec(const struct exec_options &options) -> int { std::unique_ptr dir = std::make_unique(options.global_.get().root); @@ -20,12 +20,17 @@ void linyaps_box::command::exec(const struct exec_options &options) throw std::runtime_error("container not found"); } - config::process_t proc; - proc.cwd = options.cwd.value_or("/"); - proc.args = options.command; - proc.terminal = isatty(STDIN_FILENO) == 1 && isatty(STDOUT_FILENO) == 1; - proc.no_new_privileges = options.no_new_privs; - proc.env = options.envs.value_or(std::vector{}); + exec_container_option option; + option.proc.cwd = options.cwd.value_or("/"); + option.proc.args = options.command; + option.proc.terminal = options.tty; + option.proc.no_new_privileges = options.no_new_privs; + option.proc.env = options.envs.value_or(std::vector{}); + option.preserve_fds = options.preserve_fds; + + if (option.proc.terminal && options.console_socket) { + option.console_socket = unixSocketClient::connect(options.console_socket.value()); + } #ifdef LINYAPS_BOX_ENABLE_CAP if (options.caps) { @@ -45,14 +50,14 @@ void linyaps_box::command::exec(const struct exec_options &options) }); }; - transform_cap(proc.capabilities.effective); - transform_cap(proc.capabilities.ambient); - transform_cap(proc.capabilities.bounding); - transform_cap(proc.capabilities.permitted); + transform_cap(option.proc.capabilities.effective); + transform_cap(option.proc.capabilities.ambient); + transform_cap(option.proc.capabilities.bounding); + transform_cap(option.proc.capabilities.permitted); } #endif // TODO: support exec fully - container->second.exec(proc); + return container->second.exec(std::move(option)); } diff --git a/src/linyaps_box/command/exec.h b/src/linyaps_box/command/exec.h index 57ebef0..1113188 100644 --- a/src/linyaps_box/command/exec.h +++ b/src/linyaps_box/command/exec.h @@ -8,6 +8,6 @@ namespace linyaps_box::command { -[[noreturn]] void exec(const exec_options &options); +[[nodiscard]] auto exec(const exec_options &options) -> int; } // namespace linyaps_box::command diff --git a/src/linyaps_box/command/options.cpp b/src/linyaps_box/command/options.cpp index 4b2dad4..bbd5aa3 100644 --- a/src/linyaps_box/command/options.cpp +++ b/src/linyaps_box/command/options.cpp @@ -96,24 +96,27 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[] "for example `1000` for UID=1000 " "or `1000:1000` for UID=1000 and GID=1000") ->type_name("UID[:GID]"); - cmd_exec->add_option("--cwd", exec_opt.cwd, "Current working directory."); + cmd_exec->add_option("--cwd", exec_opt.cwd, "Current working directory.")->type_name("PATH"); cmd_exec->add_option("--env", exec_opt.envs, "Environment variables to set") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) - ->check( - [](const std::string &str) { - if (str.find('=') == std::string::npos) { - return "invalid argument, env must be in the format of KEY=VALUE"; - } - return ""; - }, - "env_check"); + ->type_name("ENV") + ->take_all() + ->check([](const std::string &str) { + if (str.find('=') == std::string::npos) { + return "invalid argument, env must be in the format of KEY=VALUE"; + } + return ""; + }); cmd_exec->add_option("--console-socket", exec_opt.console_socket, "Path to an unix socket that will receive the master end of the console's " "pseudoterminal") ->type_name("SOCKET") ->check(socket_check); - cmd_exec->add_flag("--tty", exec_opt.tty, "Allocate a pseudo-TTY")->default_val(false); + cmd_exec->add_flag("-t,--tty", exec_opt.tty, "Allocate a pseudo-TTY")->take_last(); + cmd_exec->add_option("--preserve-fds", + exec_opt.preserve_fds, + "Pass N additional file descriptors to the container") + ->type_name("N"); // TODO: enable capabilities and no_new_privs support after rewrite exec, // cmd_exec->add_option("-c,--cap", options.exec.caps, "Set capabilities") // ->check( diff --git a/src/linyaps_box/command/options.h b/src/linyaps_box/command/options.h index dd34324..dcb0a3f 100644 --- a/src/linyaps_box/command/options.h +++ b/src/linyaps_box/command/options.h @@ -36,13 +36,13 @@ struct list_options struct exec_options { explicit exec_options(global_options &global) - : no_new_privs(false) - , global_(global) + : global_(global) { } - bool no_new_privs; - bool tty; + bool no_new_privs{ false }; + bool tty{ false }; + int preserve_fds{ 0 }; std::reference_wrapper global_; std::vector command; std::string user; @@ -64,8 +64,8 @@ struct run_options std::string ID; std::string bundle; std::string config; - std::string console_socket; - int preserve_fds{}; + std::optional console_socket; + int preserve_fds{ 0 }; }; struct kill_options diff --git a/src/linyaps_box/command/run.cpp b/src/linyaps_box/command/run.cpp index acd7330..8bf89aa 100644 --- a/src/linyaps_box/command/run.cpp +++ b/src/linyaps_box/command/run.cpp @@ -22,8 +22,8 @@ auto linyaps_box::command::run(const struct run_options &options) -> int run_container_options_t run_options; run_options.preserve_fds = options.preserve_fds; - if (container.get_config().process.terminal && !options.console_socket.empty()) { - run_options.console_socket = unixSocketClient::connect(options.console_socket); + if (container.get_config().process.terminal && options.console_socket) { + run_options.console_socket = unixSocketClient::connect(options.console_socket.value()); } return container.run(std::move(run_options)); diff --git a/src/linyaps_box/container_ref.cpp b/src/linyaps_box/container_ref.cpp index a96fa44..2c9a6d6 100644 --- a/src/linyaps_box/container_ref.cpp +++ b/src/linyaps_box/container_ref.cpp @@ -4,7 +4,14 @@ #include "linyaps_box/container_ref.h" +#include "linyaps_box/container_monitor.h" +#include "linyaps_box/terminal.h" +#include "linyaps_box/utils/file.h" #include "linyaps_box/utils/log.h" +#include "linyaps_box/utils/process.h" +#include "linyaps_box/utils/session.h" +#include "linyaps_box/utils/socket.h" +#include "linyaps_box/utils/terminal.h" #include // IWYU pragma: keep #include @@ -38,73 +45,110 @@ void linyaps_box::container_ref::kill(int signal) const throw std::system_error(errno, std::system_category(), std::move(ss).str()); } -void linyaps_box::container_ref::exec(const linyaps_box::config::process_t &process) +auto linyaps_box::container_ref::exec(exec_container_option option) -> int { auto target = std::to_string(this->status().PID); - std::vector argv{ - "nsenter", - "--target", - target.c_str(), - "--user", - "--mount", - "--pid", - "--no-fork", - // FIXME: - // Old nsenter command do not support --wdns, - // so we have to implement nsenter by ourself in the future. - "--preserve-credentials", - }; + // TODO: support detach later + utils::prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); - for (const auto &arg : process.args) { - argv.push_back(arg.c_str()); + std::optional recv_socketpair; + if (option.proc.terminal && !option.console_socket) { + auto [socket1, socket2] = utils::socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + option.console_socket = unixSocketClient{ std::move(socket1) }; + recv_socketpair = unixSocketClient{ std::move(socket2) }; } - argv.push_back(nullptr); - std::vector c_env; - c_env.reserve(process.env.size()); - for (const auto &env : process.env) { - c_env.push_back(env.c_str()); + auto child = fork(); + if (child < 0) { + throw std::system_error(errno, std::system_category(), "fork"); } - c_env.push_back(nullptr); - - LINYAPS_BOX_DEBUG() << [&argv]() -> std::string { - auto result = std::accumulate(argv.cbegin(), - argv.cend() - 1, - std::string{ "args:[" }, - [](std::string init, const std::string &val) { - init += val; - init.push_back(' '); - return init; - }); - result.push_back(']'); - result.insert(0, "execvp nsenter with arguments: "); - return result; - }(); - // FIXME: - // We only handle the command arguments for now - // here are some other fields in process we need to consider: - // terminal - // console.height - // console.width - // cwd - // env - // rlimits - // apparmor_profile - // capabilities - // no_new_privileges - // oom_score_adj - - ::execvpe("nsenter", const_cast(argv.data()), const_cast(c_env.data())); + if (child == 0) { + // TODO: create terminal after rewrite exec. it should be created in the container namespace + if (option.console_socket) { + utils::setsid(); + auto [master, slave] = create_pty_pair(); + + slave.setup_stdio(); + // TODO: use fchown after we implement exec option `--user` + slave.set_size({}); + + option.console_socket->send_fd(std::move(master).take()); + option.console_socket.reset(); + } + + std::vector argv{ + "nsenter", + "--target", + target.c_str(), + "--user", + "--mount", + "--pid", + // FIXME: + // Old nsenter command do not support --wdns, + // so we have to implement nsenter by ourself in the future. + "--preserve-credentials", + }; + + for (const auto &arg : option.proc.args) { + argv.push_back(arg.c_str()); + } + argv.push_back(nullptr); + + std::vector c_env; + c_env.reserve(option.proc.env.size()); + for (const auto &env : option.proc.env) { + c_env.push_back(env.c_str()); + } + c_env.push_back(nullptr); - std::stringstream ss; - ss << "execvp nsenter with arguments:"; - for (const auto &arg : argv) { - ss << " " << arg; + // FIXME: + // We only handle the command arguments for now + // here are some other fields in process we need to consider: + // terminal + // console.height + // console.width + // cwd + // env + // rlimits + // apparmor_profile + // capabilities + // no_new_privileges + // oom_score_adj + + ::execvpe("nsenter", const_cast(argv.data()), const_cast(c_env.data())); + _exit(EXIT_FAILURE); } - throw std::system_error(errno, std::system_category(), std::move(ss).str()); + auto in = utils::file_descriptor{ utils::fileno(stdin), false }; + auto out = utils::file_descriptor{ utils::fileno(stdout), false }; + + container_monitor monitor{ child }; + + [&recv_socketpair, &monitor, &in, &out]() { + if (!recv_socketpair) { + return; + } + + LINYAPS_BOX_DEBUG() << "Container requires a terminal"; + + std::string payload; + auto master = terminal_master{ recv_socketpair->recv_fd(payload) }; + + recv_socketpair->release(); + + in.set_nonblock(true); + out.set_nonblock(true); + + monitor.enable_io_forwarding(std::move(master), in, out); + }(); + + if (!monitor.enable_signal_forwarding()) { + return 0; + } + + return monitor.wait_container_exit(); } const linyaps_box::status_directory &linyaps_box::container_ref::status_dir() const diff --git a/src/linyaps_box/container_ref.h b/src/linyaps_box/container_ref.h index dfa69b2..b03df9e 100644 --- a/src/linyaps_box/container_ref.h +++ b/src/linyaps_box/container_ref.h @@ -7,9 +7,17 @@ #include "linyaps_box/config.h" #include "linyaps_box/container_status.h" #include "linyaps_box/status_directory.h" +#include "linyaps_box/unixsocket.h" namespace linyaps_box { +struct exec_container_option +{ + int preserve_fds; + config::process_t proc; + std::optional console_socket; +}; + class container_ref { public: @@ -23,7 +31,7 @@ class container_ref [[nodiscard]] auto status() const -> container_status_t; void kill(int signal) const; - [[noreturn]] void exec(const config::process_t &process); + [[nodiscard]] auto exec(exec_container_option option) -> int; protected: [[nodiscard]] auto status_dir() const -> const status_directory &; diff --git a/src/linyaps_box/status_directory.h b/src/linyaps_box/status_directory.h index 10d53f0..6dfe1e5 100644 --- a/src/linyaps_box/status_directory.h +++ b/src/linyaps_box/status_directory.h @@ -10,6 +10,7 @@ #include namespace linyaps_box { +// TODO: place container status into a directory class status_directory : public virtual interface { protected: