MatN work for versatile appimage creation for all types of os
[goodguy/cinelerra.git] / cinelerra-5.1 / tools / makeappimagetool / process.cpp
1 // system headers
2 #include <algorithm>
3 #include <iostream>
4 #include <memory>
5 #include <sstream>
6 #include <stdexcept>
7 #include <utility>
8 #include <unistd.h>
9 #include <memory.h>
10 #include <wait.h>
11
12 // local headers
13 #include "includes/process.h"
14 #include "includes/subprocess.h"
15 #include "includes/assert.h"
16
17 // shorter than using namespace ...
18 using namespace linuxdeploy::subprocess;
19
20 int process::pid() const {
21     return child_pid_;
22 }
23
24 int process::stdout_fd() const {
25     return stdout_fd_;
26 }
27
28 int process::stderr_fd() const {
29     return stderr_fd_;
30 }
31
32 process::process(std::initializer_list<std::string> args, const subprocess_env_map_t& env)
33     : process(std::vector<std::string>(args), env) {}
34
35 process::process(const std::vector<std::string>& args, const subprocess_env_map_t& env) {
36     // preconditions
37     util::assert::assert_not_empty(args);
38
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];
43
44     // FIXME: for debugging of #150
45     auto create_pipe = [](int fds[]) {
46         const auto rv = pipe(fds);
47
48         if (rv != 0) {
49             const auto error = errno;
50             throw std::logic_error("failed to create pipe: " + std::string(strerror(error)));
51         }
52     };
53
54     // create actual pipes
55     create_pipe(stdout_pipe_fds);
56     create_pipe(stderr_pipe_fds);
57
58     // create child process
59     child_pid_ = fork();
60
61     if (child_pid_ < 0) {
62         throw std::runtime_error{"fork() failed"};
63     }
64
65     if (child_pid_ == 0) {
66         // we're in the child process
67
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_]);
71
72         auto connect_fd = [](int fd, int fileno) {
73             for (;;) {
74                 if (dup2(fd, fileno) == -1) {
75                     const auto error = errno;
76                     if (error != EINTR) {
77                         throw std::logic_error{"failed to connect pipes: " + std::string(strerror(error))};
78                     }
79                     continue;
80                 }
81
82                 break;
83             }
84         };
85
86         connect_fd(stdout_pipe_fds[WRITE_END_], STDOUT_FILENO);
87         connect_fd(stderr_pipe_fds[WRITE_END_], STDERR_FILENO);
88
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_]);
92
93         // prepare arguments for exec*
94         auto exec_args = make_args_vector_(args);
95         auto exec_env = make_env_vector_(env);
96
97         // call subprocess
98         execvpe(args.front().c_str(), exec_args.data(), exec_env.data());
99
100         // only reached if exec* fails
101
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) {
105             free(ptr);
106             ptr = nullptr;
107         };
108
109         std::for_each(exec_args.begin(), exec_args.end(), deleter);
110         std::for_each(exec_env.begin(), exec_env.end(), deleter);
111
112         throw std::runtime_error{"exec() failed: " + std::string(strerror(errno))};
113     }
114
115     // parent code
116
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_]);
120
121     // store file descriptors
122     stdout_fd_ = stdout_pipe_fds[READ_END_];
123     stderr_fd_ = stderr_pipe_fds[READ_END_];
124 }
125
126 int process::close() {
127     if (!exited_) {
128         close_pipe_fd_(stdout_fd_);
129         stdout_fd_ = -1;
130
131         close_pipe_fd_(stderr_fd_);
132         stderr_fd_ = -1;
133
134         {
135             int status;
136
137             if (waitpid(child_pid_, &status, 0) == -1) {
138                 throw std::logic_error{"waitpid() failed"};
139             }
140
141             exited_ = true;
142             exit_code_ = check_waitpid_status_(status);
143         }
144     }
145
146     return exit_code_;
147 }
148
149 process::~process() {
150     (void) close();
151 }
152
153 std::vector<char*> process::make_args_vector_(const std::vector<std::string>& args) {
154     std::vector<char*> rv{};
155     rv.reserve(args.size());
156
157     for (const auto& arg : args) {
158         rv.emplace_back(strdup(arg.c_str()));
159     }
160
161     // execv* want a nullptr-terminated array
162     rv.emplace_back(nullptr);
163
164     return rv;
165 }
166
167 std::vector<char*> process::make_env_vector_(const subprocess_env_map_t& env) {
168     std::vector<char*> rv;
169
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));
175         }
176     }
177
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;
182
183         auto predicate = [&key](char* existing_env_var) {
184             char* equal_sign = strstr(existing_env_var, "=");
185
186             if (equal_sign == nullptr) {
187                 throw std::runtime_error{"no equal sign in environment variable"};
188             }
189
190             return strncmp(existing_env_var, key.c_str(), std::distance(equal_sign, existing_env_var)) == 0;
191         };
192
193         // delete existing env var, if any
194         rv.erase(std::remove_if(rv.begin(), rv.end(), predicate), rv.end());
195
196         // insert new value
197         std::ostringstream oss;
198         oss << key;
199         oss << "=";
200         oss << value;
201
202         rv.emplace_back(strdup(oss.str().c_str()));
203     }
204
205     // exec*e want a nullptr-terminated array
206     rv.emplace_back(nullptr);
207
208     return rv;
209 }
210
211 void process::kill(int signal) const {
212     if (::kill(child_pid_, signal) != 0) {
213         throw std::logic_error{"failed to kill child process"};
214     }
215
216     if (waitpid(child_pid_, nullptr, 0)) {
217         throw std::logic_error{"failed to wait for killed child"};
218     }
219 }
220
221 bool process::is_running() {
222     if (exited_) {
223         return false;
224     }
225
226     int status;
227     auto result = waitpid(child_pid_, &status, WNOHANG);
228
229     if (result == 0) {
230         return true;
231     }
232
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_);
236         stdout_fd_ = -1;
237
238         close_pipe_fd_(stderr_fd_);
239         stderr_fd_ = -1;
240
241         exited_ = true;
242         exit_code_ = check_waitpid_status_(status);
243
244         return false;
245     }
246
247     if (result < 0) {
248         // TODO: check errno == ECHILD
249         throw std::logic_error{"waitpid() failed: " + std::string(strerror(errno))};
250     }
251
252     // can only happen if waitpid() returns an unknown process ID
253     throw std::logic_error{"unknown error occured"};
254 }
255
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);
262     }
263
264     throw std::logic_error{"unknown child process state"};
265 }
266
267 void process::close_pipe_fd_(int fd) {
268     const auto rv = ::close(fd);
269
270     if (rv != 0) {
271         const auto error = errno;
272         throw std::logic_error("failed to close pipe fd: " + std::string(strerror(error)));
273     }
274 }