13 #include "includes/process.h"
14 #include "includes/subprocess.h"
15 #include "includes/assert.h"
17 // shorter than using namespace ...
18 using namespace linuxdeploy::subprocess;
20 int process::pid() const {
24 int process::stdout_fd() const {
28 int process::stderr_fd() const {
32 process::process(std::initializer_list<std::string> args, const subprocess_env_map_t& env)
33 : process(std::vector<std::string>(args), env) {}
35 process::process(const std::vector<std::string>& args, const subprocess_env_map_t& env) {
37 util::assert::assert_not_empty(args);
39 // pipes for both stdout and stderr
40 // the order is, as seen from the child: [read, write]
41 int stdout_pipe_fds[2];
42 int stderr_pipe_fds[2];
44 // FIXME: for debugging of #150
45 auto create_pipe = [](int fds[]) {
46 const auto rv = pipe(fds);
49 const auto error = errno;
50 throw std::logic_error("failed to create pipe: " + std::string(strerror(error)));
54 // create actual pipes
55 create_pipe(stdout_pipe_fds);
56 create_pipe(stderr_pipe_fds);
58 // create child process
62 throw std::runtime_error{"fork() failed"};
65 if (child_pid_ == 0) {
66 // we're in the child process
68 // first step: close the read end of both pipes
69 close_pipe_fd_(stdout_pipe_fds[READ_END_]);
70 close_pipe_fd_(stderr_pipe_fds[READ_END_]);
72 auto connect_fd = [](int fd, int fileno) {
74 if (dup2(fd, fileno) == -1) {
75 const auto error = errno;
77 throw std::logic_error{"failed to connect pipes: " + std::string(strerror(error))};
86 connect_fd(stdout_pipe_fds[WRITE_END_], STDOUT_FILENO);
87 connect_fd(stderr_pipe_fds[WRITE_END_], STDERR_FILENO);
89 // now, we also have to close the write end of both pipes
90 close_pipe_fd_(stdout_pipe_fds[WRITE_END_]);
91 close_pipe_fd_(stderr_pipe_fds[WRITE_END_]);
93 // prepare arguments for exec*
94 auto exec_args = make_args_vector_(args);
95 auto exec_env = make_env_vector_(env);
98 execvpe(args.front().c_str(), exec_args.data(), exec_env.data());
100 // only reached if exec* fails
102 // clean up memory if exec should ever return
103 // prevents memleaks if the exception below would be handled by a caller
104 auto deleter = [](char* ptr) {
109 std::for_each(exec_args.begin(), exec_args.end(), deleter);
110 std::for_each(exec_env.begin(), exec_env.end(), deleter);
112 throw std::runtime_error{"exec() failed: " + std::string(strerror(errno))};
117 // we do not intend to write to the processes
118 close_pipe_fd_(stdout_pipe_fds[WRITE_END_]);
119 close_pipe_fd_(stderr_pipe_fds[WRITE_END_]);
121 // store file descriptors
122 stdout_fd_ = stdout_pipe_fds[READ_END_];
123 stderr_fd_ = stderr_pipe_fds[READ_END_];
126 int process::close() {
128 close_pipe_fd_(stdout_fd_);
131 close_pipe_fd_(stderr_fd_);
137 if (waitpid(child_pid_, &status, 0) == -1) {
138 throw std::logic_error{"waitpid() failed"};
142 exit_code_ = check_waitpid_status_(status);
149 process::~process() {
153 std::vector<char*> process::make_args_vector_(const std::vector<std::string>& args) {
154 std::vector<char*> rv{};
155 rv.reserve(args.size());
157 for (const auto& arg : args) {
158 rv.emplace_back(strdup(arg.c_str()));
161 // execv* want a nullptr-terminated array
162 rv.emplace_back(nullptr);
167 std::vector<char*> process::make_env_vector_(const subprocess_env_map_t& env) {
168 std::vector<char*> rv;
170 // first, copy existing environment
171 // we cannot reserve space in the vector unfortunately, as we don't know the size of environ before the iteration
172 if (environ != nullptr) {
173 for (auto** current_env_var = environ; *current_env_var != nullptr; ++current_env_var) {
174 rv.emplace_back(strdup(*current_env_var));
178 // add own environment variables, overwriting existing ones if necessary
179 for (const auto& env_var : env) {
180 const auto& key = env_var.first;
181 const auto& value = env_var.second;
183 auto predicate = [&key](char* existing_env_var) {
184 char* equal_sign = strstr(existing_env_var, "=");
186 if (equal_sign == nullptr) {
187 throw std::runtime_error{"no equal sign in environment variable"};
190 return strncmp(existing_env_var, key.c_str(), std::distance(equal_sign, existing_env_var)) == 0;
193 // delete existing env var, if any
194 rv.erase(std::remove_if(rv.begin(), rv.end(), predicate), rv.end());
197 std::ostringstream oss;
202 rv.emplace_back(strdup(oss.str().c_str()));
205 // exec*e want a nullptr-terminated array
206 rv.emplace_back(nullptr);
211 void process::kill(int signal) const {
212 if (::kill(child_pid_, signal) != 0) {
213 throw std::logic_error{"failed to kill child process"};
216 if (waitpid(child_pid_, nullptr, 0)) {
217 throw std::logic_error{"failed to wait for killed child"};
221 bool process::is_running() {
227 auto result = waitpid(child_pid_, &status, WNOHANG);
233 if (result == child_pid_) {
234 // TODO: extract the following lines from both this method and close() to eliminate duplicate code
235 close_pipe_fd_(stdout_fd_);
238 close_pipe_fd_(stderr_fd_);
242 exit_code_ = check_waitpid_status_(status);
248 // TODO: check errno == ECHILD
249 throw std::logic_error{"waitpid() failed: " + std::string(strerror(errno))};
252 // can only happen if waitpid() returns an unknown process ID
253 throw std::logic_error{"unknown error occured"};
256 int process::check_waitpid_status_(int status) {
257 if (WIFSIGNALED(status) != 0) {
258 // TODO: consider treating child exit caused by signals separately
259 return WTERMSIG(status);
260 } else if (WIFEXITED(status) != 0) {
261 return WEXITSTATUS(status);
264 throw std::logic_error{"unknown child process state"};
267 void process::close_pipe_fd_(int fd) {
268 const auto rv = ::close(fd);
271 const auto error = errno;
272 throw std::logic_error("failed to close pipe fd: " + std::string(strerror(error)));