Skip to content

Commit ac18700

Browse files
committed
refactor: rewrite exec command with temporary TTY support
- Change exec to return int instead of noreturn void - Introduce exec_container_option struct for exec configuration - Add temporary PTY and console socket support (to be rewritten) - Add container monitor for signal and I/O forwarding - Use fork to manage exec process lifecycle NOTE: current TTY implementation is temporary and has limitations Signed-off-by: ComixHe <ComixHe1895@outlook.com>
1 parent ffe59c5 commit ac18700

9 files changed

Lines changed: 151 additions & 91 deletions

File tree

src/linyaps_box/app.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ try {
4949
return 0;
5050
},
5151
[](const command::exec_options &options) -> int {
52-
command::exec(options);
53-
__builtin_unreachable();
52+
return command::exec(options);
5453
},
5554
[](const command::kill_options &options) {
5655
command::kill(options);

src/linyaps_box/command/exec.cpp

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include "linyaps_box/runtime.h"
99
#include "linyaps_box/status_directory.h"
1010

11-
void linyaps_box::command::exec(const struct exec_options &options)
11+
auto linyaps_box::command::exec(const struct exec_options &options) -> int
1212
{
1313
std::unique_ptr<status_directory> dir =
1414
std::make_unique<impl::status_directory>(options.global_.get().root);
@@ -20,12 +20,17 @@ void linyaps_box::command::exec(const struct exec_options &options)
2020
throw std::runtime_error("container not found");
2121
}
2222

23-
config::process_t proc;
24-
proc.cwd = options.cwd.value_or("/");
25-
proc.args = options.command;
26-
proc.terminal = isatty(STDIN_FILENO) == 1 && isatty(STDOUT_FILENO) == 1;
27-
proc.no_new_privileges = options.no_new_privs;
28-
proc.env = options.envs.value_or(std::vector<std::string>{});
23+
exec_container_option option;
24+
option.proc.cwd = options.cwd.value_or("/");
25+
option.proc.args = options.command;
26+
option.proc.terminal = options.tty;
27+
option.proc.no_new_privileges = options.no_new_privs;
28+
option.proc.env = options.envs.value_or(std::vector<std::string>{});
29+
option.preserve_fds = options.preserve_fds;
30+
31+
if (option.proc.terminal && options.console_socket) {
32+
option.console_socket = unixSocketClient::connect(options.console_socket.value());
33+
}
2934

3035
#ifdef LINYAPS_BOX_ENABLE_CAP
3136
if (options.caps) {
@@ -45,14 +50,14 @@ void linyaps_box::command::exec(const struct exec_options &options)
4550
});
4651
};
4752

48-
transform_cap(proc.capabilities.effective);
49-
transform_cap(proc.capabilities.ambient);
50-
transform_cap(proc.capabilities.bounding);
51-
transform_cap(proc.capabilities.permitted);
53+
transform_cap(option.proc.capabilities.effective);
54+
transform_cap(option.proc.capabilities.ambient);
55+
transform_cap(option.proc.capabilities.bounding);
56+
transform_cap(option.proc.capabilities.permitted);
5257
}
5358
#endif
5459

5560
// TODO: support exec fully
5661

57-
container->second.exec(proc);
62+
return container->second.exec(std::move(option));
5863
}

src/linyaps_box/command/exec.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
namespace linyaps_box::command {
1010

11-
[[noreturn]] void exec(const exec_options &options);
11+
[[nodiscard]] auto exec(const exec_options &options) -> int;
1212

1313
} // namespace linyaps_box::command

src/linyaps_box/command/options.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,24 +96,27 @@ linyaps_box::command::options linyaps_box::command::parse(int argc, char *argv[]
9696
"for example `1000` for UID=1000 "
9797
"or `1000:1000` for UID=1000 and GID=1000")
9898
->type_name("UID[:GID]");
99-
cmd_exec->add_option("--cwd", exec_opt.cwd, "Current working directory.");
99+
cmd_exec->add_option("--cwd", exec_opt.cwd, "Current working directory.")->type_name("PATH");
100100
cmd_exec->add_option("--env", exec_opt.envs, "Environment variables to set")
101-
->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)
102-
->check(
103-
[](const std::string &str) {
104-
if (str.find('=') == std::string::npos) {
105-
return "invalid argument, env must be in the format of KEY=VALUE";
106-
}
107-
return "";
108-
},
109-
"env_check");
101+
->type_name("ENV")
102+
->take_all()
103+
->check([](const std::string &str) {
104+
if (str.find('=') == std::string::npos) {
105+
return "invalid argument, env must be in the format of KEY=VALUE";
106+
}
107+
return "";
108+
});
110109
cmd_exec->add_option("--console-socket",
111110
exec_opt.console_socket,
112111
"Path to an unix socket that will receive the master end of the console's "
113112
"pseudoterminal")
114113
->type_name("SOCKET")
115114
->check(socket_check);
116-
cmd_exec->add_flag("--tty", exec_opt.tty, "Allocate a pseudo-TTY")->default_val(false);
115+
cmd_exec->add_flag("-t,--tty", exec_opt.tty, "Allocate a pseudo-TTY")->take_last();
116+
cmd_exec->add_option("--preserve-fds",
117+
exec_opt.preserve_fds,
118+
"Pass N additional file descriptors to the container")
119+
->type_name("N");
117120
// TODO: enable capabilities and no_new_privs support after rewrite exec,
118121
// cmd_exec->add_option("-c,--cap", options.exec.caps, "Set capabilities")
119122
// ->check(

src/linyaps_box/command/options.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ struct list_options
3636
struct exec_options
3737
{
3838
explicit exec_options(global_options &global)
39-
: no_new_privs(false)
40-
, global_(global)
39+
: global_(global)
4140
{
4241
}
4342

44-
bool no_new_privs;
45-
bool tty;
43+
bool no_new_privs{ false };
44+
bool tty{ false };
45+
int preserve_fds{ 0 };
4646
std::reference_wrapper<global_options> global_;
4747
std::vector<std::string> command;
4848
std::string user;
@@ -64,8 +64,8 @@ struct run_options
6464
std::string ID;
6565
std::string bundle;
6666
std::string config;
67-
std::string console_socket;
68-
int preserve_fds{};
67+
std::optional<std::string> console_socket;
68+
int preserve_fds{ 0 };
6969
};
7070

7171
struct kill_options

src/linyaps_box/command/run.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ auto linyaps_box::command::run(const struct run_options &options) -> int
2222
run_container_options_t run_options;
2323
run_options.preserve_fds = options.preserve_fds;
2424

25-
if (container.get_config().process.terminal && !options.console_socket.empty()) {
26-
run_options.console_socket = unixSocketClient::connect(options.console_socket);
25+
if (container.get_config().process.terminal && options.console_socket) {
26+
run_options.console_socket = unixSocketClient::connect(options.console_socket.value());
2727
}
2828

2929
return container.run(std::move(run_options));

src/linyaps_box/container_ref.cpp

Lines changed: 100 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
#include "linyaps_box/container_ref.h"
66

7+
#include "linyaps_box/container_monitor.h"
8+
#include "linyaps_box/terminal.h"
9+
#include "linyaps_box/utils/file.h"
710
#include "linyaps_box/utils/log.h"
11+
#include "linyaps_box/utils/process.h"
12+
#include "linyaps_box/utils/session.h"
13+
#include "linyaps_box/utils/socket.h"
14+
#include "linyaps_box/utils/terminal.h"
815

916
#include <csignal> // IWYU pragma: keep
1017
#include <utility>
@@ -38,73 +45,110 @@ void linyaps_box::container_ref::kill(int signal) const
3845
throw std::system_error(errno, std::system_category(), std::move(ss).str());
3946
}
4047

41-
void linyaps_box::container_ref::exec(const linyaps_box::config::process_t &process)
48+
auto linyaps_box::container_ref::exec(exec_container_option option) -> int
4249
{
4350
auto target = std::to_string(this->status().PID);
4451

45-
std::vector<const char *> argv{
46-
"nsenter",
47-
"--target",
48-
target.c_str(),
49-
"--user",
50-
"--mount",
51-
"--pid",
52-
"--no-fork",
53-
// FIXME:
54-
// Old nsenter command do not support --wdns,
55-
// so we have to implement nsenter by ourself in the future.
56-
"--preserve-credentials",
57-
};
52+
// TODO: support detach later
53+
utils::prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);
5854

59-
for (const auto &arg : process.args) {
60-
argv.push_back(arg.c_str());
55+
std::optional<unixSocketClient> recv_socketpair;
56+
if (option.proc.terminal && !option.console_socket) {
57+
auto [socket1, socket2] = utils::socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
58+
option.console_socket = unixSocketClient{ std::move(socket1) };
59+
recv_socketpair = unixSocketClient{ std::move(socket2) };
6160
}
62-
argv.push_back(nullptr);
6361

64-
std::vector<const char *> c_env;
65-
c_env.reserve(process.env.size());
66-
for (const auto &env : process.env) {
67-
c_env.push_back(env.c_str());
62+
auto child = fork();
63+
if (child < 0) {
64+
throw std::system_error(errno, std::system_category(), "fork");
6865
}
69-
c_env.push_back(nullptr);
70-
71-
LINYAPS_BOX_DEBUG() << [&argv]() -> std::string {
72-
auto result = std::accumulate(argv.cbegin(),
73-
argv.cend() - 1,
74-
std::string{ "args:[" },
75-
[](std::string init, const std::string &val) {
76-
init += val;
77-
init.push_back(' ');
78-
return init;
79-
});
80-
result.push_back(']');
81-
result.insert(0, "execvp nsenter with arguments: ");
82-
return result;
83-
}();
8466

85-
// FIXME:
86-
// We only handle the command arguments for now
87-
// here are some other fields in process we need to consider:
88-
// terminal
89-
// console.height
90-
// console.width
91-
// cwd
92-
// env
93-
// rlimits
94-
// apparmor_profile
95-
// capabilities
96-
// no_new_privileges
97-
// oom_score_adj
98-
99-
::execvpe("nsenter", const_cast<char **>(argv.data()), const_cast<char **>(c_env.data()));
67+
if (child == 0) {
68+
// TODO: create terminal after rewrite exec. it should be created in the container namespace
69+
if (option.console_socket) {
70+
utils::setsid();
71+
auto [master, slave] = create_pty_pair();
72+
73+
slave.setup_stdio();
74+
// TODO: use fchown after we implement exec option `--user`
75+
slave.set_size({});
76+
77+
option.console_socket->send_fd(std::move(master).take());
78+
option.console_socket.reset();
79+
}
80+
81+
std::vector<const char *> argv{
82+
"nsenter",
83+
"--target",
84+
target.c_str(),
85+
"--user",
86+
"--mount",
87+
"--pid",
88+
// FIXME:
89+
// Old nsenter command do not support --wdns,
90+
// so we have to implement nsenter by ourself in the future.
91+
"--preserve-credentials",
92+
};
93+
94+
for (const auto &arg : option.proc.args) {
95+
argv.push_back(arg.c_str());
96+
}
97+
argv.push_back(nullptr);
98+
99+
std::vector<const char *> c_env;
100+
c_env.reserve(option.proc.env.size());
101+
for (const auto &env : option.proc.env) {
102+
c_env.push_back(env.c_str());
103+
}
104+
c_env.push_back(nullptr);
100105

101-
std::stringstream ss;
102-
ss << "execvp nsenter with arguments:";
103-
for (const auto &arg : argv) {
104-
ss << " " << arg;
106+
// FIXME:
107+
// We only handle the command arguments for now
108+
// here are some other fields in process we need to consider:
109+
// terminal
110+
// console.height
111+
// console.width
112+
// cwd
113+
// env
114+
// rlimits
115+
// apparmor_profile
116+
// capabilities
117+
// no_new_privileges
118+
// oom_score_adj
119+
120+
::execvpe("nsenter", const_cast<char **>(argv.data()), const_cast<char **>(c_env.data()));
121+
_exit(EXIT_FAILURE);
105122
}
106123

107-
throw std::system_error(errno, std::system_category(), std::move(ss).str());
124+
auto in = utils::file_descriptor{ utils::fileno(stdin), false };
125+
auto out = utils::file_descriptor{ utils::fileno(stdout), false };
126+
127+
container_monitor monitor{ child };
128+
129+
[&recv_socketpair, &monitor, &in, &out]() {
130+
if (!recv_socketpair) {
131+
return;
132+
}
133+
134+
LINYAPS_BOX_DEBUG() << "Container requires a terminal";
135+
136+
std::string payload;
137+
auto master = terminal_master{ recv_socketpair->recv_fd(payload) };
138+
139+
recv_socketpair->release();
140+
141+
in.set_nonblock(true);
142+
out.set_nonblock(true);
143+
144+
monitor.enable_io_forwarding(std::move(master), in, out);
145+
}();
146+
147+
if (!monitor.enable_signal_forwarding()) {
148+
return 0;
149+
}
150+
151+
return monitor.wait_container_exit();
108152
}
109153

110154
const linyaps_box::status_directory &linyaps_box::container_ref::status_dir() const

src/linyaps_box/container_ref.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,17 @@
77
#include "linyaps_box/config.h"
88
#include "linyaps_box/container_status.h"
99
#include "linyaps_box/status_directory.h"
10+
#include "linyaps_box/unixsocket.h"
1011

1112
namespace linyaps_box {
1213

14+
struct exec_container_option
15+
{
16+
int preserve_fds;
17+
config::process_t proc;
18+
std::optional<unixSocketClient> console_socket;
19+
};
20+
1321
class container_ref
1422
{
1523
public:
@@ -23,7 +31,7 @@ class container_ref
2331

2432
[[nodiscard]] auto status() const -> container_status_t;
2533
void kill(int signal) const;
26-
[[noreturn]] void exec(const config::process_t &process);
34+
[[nodiscard]] auto exec(exec_container_option option) -> int;
2735

2836
protected:
2937
[[nodiscard]] auto status_dir() const -> const status_directory &;

src/linyaps_box/status_directory.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <vector>
1111

1212
namespace linyaps_box {
13+
// TODO: place container status into a directory
1314
class status_directory : public virtual interface
1415
{
1516
protected:

0 commit comments

Comments
 (0)