MatN work for versatile appimage creation for all types of os
[goodguy/cinelerra.git] / cinelerra-5.1 / tools / makeappimagetool / process.cpp
diff --git a/cinelerra-5.1/tools/makeappimagetool/process.cpp b/cinelerra-5.1/tools/makeappimagetool/process.cpp
new file mode 100644 (file)
index 0000000..89b92d7
--- /dev/null
@@ -0,0 +1,274 @@
+// system headers
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <stdexcept>
+#include <utility>
+#include <unistd.h>
+#include <memory.h>
+#include <wait.h>
+
+// local headers
+#include "includes/process.h"
+#include "includes/subprocess.h"
+#include "includes/assert.h"
+
+// shorter than using namespace ...
+using namespace linuxdeploy::subprocess;
+
+int process::pid() const {
+    return child_pid_;
+}
+
+int process::stdout_fd() const {
+    return stdout_fd_;
+}
+
+int process::stderr_fd() const {
+    return stderr_fd_;
+}
+
+process::process(std::initializer_list<std::string> args, const subprocess_env_map_t& env)
+    : process(std::vector<std::string>(args), env) {}
+
+process::process(const std::vector<std::string>& args, const subprocess_env_map_t& env) {
+    // preconditions
+    util::assert::assert_not_empty(args);
+
+    // pipes for both stdout and stderr
+    // the order is, as seen from the child: [read, write]
+    int stdout_pipe_fds[2];
+    int stderr_pipe_fds[2];
+
+    // FIXME: for debugging of #150
+    auto create_pipe = [](int fds[]) {
+        const auto rv = pipe(fds);
+
+        if (rv != 0) {
+            const auto error = errno;
+            throw std::logic_error("failed to create pipe: " + std::string(strerror(error)));
+        }
+    };
+
+    // create actual pipes
+    create_pipe(stdout_pipe_fds);
+    create_pipe(stderr_pipe_fds);
+
+    // create child process
+    child_pid_ = fork();
+
+    if (child_pid_ < 0) {
+        throw std::runtime_error{"fork() failed"};
+    }
+
+    if (child_pid_ == 0) {
+        // we're in the child process
+
+        // first step: close the read end of both pipes
+        close_pipe_fd_(stdout_pipe_fds[READ_END_]);
+        close_pipe_fd_(stderr_pipe_fds[READ_END_]);
+
+        auto connect_fd = [](int fd, int fileno) {
+            for (;;) {
+                if (dup2(fd, fileno) == -1) {
+                    const auto error = errno;
+                    if (error != EINTR) {
+                        throw std::logic_error{"failed to connect pipes: " + std::string(strerror(error))};
+                    }
+                    continue;
+                }
+
+                break;
+            }
+        };
+
+        connect_fd(stdout_pipe_fds[WRITE_END_], STDOUT_FILENO);
+        connect_fd(stderr_pipe_fds[WRITE_END_], STDERR_FILENO);
+
+        // now, we also have to close the write end of both pipes
+        close_pipe_fd_(stdout_pipe_fds[WRITE_END_]);
+        close_pipe_fd_(stderr_pipe_fds[WRITE_END_]);
+
+        // prepare arguments for exec*
+        auto exec_args = make_args_vector_(args);
+        auto exec_env = make_env_vector_(env);
+
+        // call subprocess
+        execvpe(args.front().c_str(), exec_args.data(), exec_env.data());
+
+        // only reached if exec* fails
+
+        // clean up memory if exec should ever return
+        // prevents memleaks if the exception below would be handled by a caller
+        auto deleter = [](char* ptr) {
+            free(ptr);
+            ptr = nullptr;
+        };
+
+        std::for_each(exec_args.begin(), exec_args.end(), deleter);
+        std::for_each(exec_env.begin(), exec_env.end(), deleter);
+
+        throw std::runtime_error{"exec() failed: " + std::string(strerror(errno))};
+    }
+
+    // parent code
+
+    // we do not intend to write to the processes
+    close_pipe_fd_(stdout_pipe_fds[WRITE_END_]);
+    close_pipe_fd_(stderr_pipe_fds[WRITE_END_]);
+
+    // store file descriptors
+    stdout_fd_ = stdout_pipe_fds[READ_END_];
+    stderr_fd_ = stderr_pipe_fds[READ_END_];
+}
+
+int process::close() {
+    if (!exited_) {
+        close_pipe_fd_(stdout_fd_);
+        stdout_fd_ = -1;
+
+        close_pipe_fd_(stderr_fd_);
+        stderr_fd_ = -1;
+
+        {
+            int status;
+
+            if (waitpid(child_pid_, &status, 0) == -1) {
+                throw std::logic_error{"waitpid() failed"};
+            }
+
+            exited_ = true;
+            exit_code_ = check_waitpid_status_(status);
+        }
+    }
+
+    return exit_code_;
+}
+
+process::~process() {
+    (void) close();
+}
+
+std::vector<char*> process::make_args_vector_(const std::vector<std::string>& args) {
+    std::vector<char*> rv{};
+    rv.reserve(args.size());
+
+    for (const auto& arg : args) {
+        rv.emplace_back(strdup(arg.c_str()));
+    }
+
+    // execv* want a nullptr-terminated array
+    rv.emplace_back(nullptr);
+
+    return rv;
+}
+
+std::vector<char*> process::make_env_vector_(const subprocess_env_map_t& env) {
+    std::vector<char*> rv;
+
+    // first, copy existing environment
+    // we cannot reserve space in the vector unfortunately, as we don't know the size of environ before the iteration
+    if (environ != nullptr) {
+        for (auto** current_env_var = environ; *current_env_var != nullptr; ++current_env_var) {
+            rv.emplace_back(strdup(*current_env_var));
+        }
+    }
+
+    // add own environment variables, overwriting existing ones if necessary
+    for (const auto& env_var : env) {
+        const auto& key = env_var.first;
+        const auto& value = env_var.second;
+
+        auto predicate = [&key](char* existing_env_var) {
+            char* equal_sign = strstr(existing_env_var, "=");
+
+            if (equal_sign == nullptr) {
+                throw std::runtime_error{"no equal sign in environment variable"};
+            }
+
+            return strncmp(existing_env_var, key.c_str(), std::distance(equal_sign, existing_env_var)) == 0;
+        };
+
+        // delete existing env var, if any
+        rv.erase(std::remove_if(rv.begin(), rv.end(), predicate), rv.end());
+
+        // insert new value
+        std::ostringstream oss;
+        oss << key;
+        oss << "=";
+        oss << value;
+
+        rv.emplace_back(strdup(oss.str().c_str()));
+    }
+
+    // exec*e want a nullptr-terminated array
+    rv.emplace_back(nullptr);
+
+    return rv;
+}
+
+void process::kill(int signal) const {
+    if (::kill(child_pid_, signal) != 0) {
+        throw std::logic_error{"failed to kill child process"};
+    }
+
+    if (waitpid(child_pid_, nullptr, 0)) {
+        throw std::logic_error{"failed to wait for killed child"};
+    }
+}
+
+bool process::is_running() {
+    if (exited_) {
+        return false;
+    }
+
+    int status;
+    auto result = waitpid(child_pid_, &status, WNOHANG);
+
+    if (result == 0) {
+        return true;
+    }
+
+    if (result == child_pid_) {
+        // TODO: extract the following lines from both this method and close() to eliminate duplicate code
+        close_pipe_fd_(stdout_fd_);
+        stdout_fd_ = -1;
+
+        close_pipe_fd_(stderr_fd_);
+        stderr_fd_ = -1;
+
+        exited_ = true;
+        exit_code_ = check_waitpid_status_(status);
+
+        return false;
+    }
+
+    if (result < 0) {
+        // TODO: check errno == ECHILD
+        throw std::logic_error{"waitpid() failed: " + std::string(strerror(errno))};
+    }
+
+    // can only happen if waitpid() returns an unknown process ID
+    throw std::logic_error{"unknown error occured"};
+}
+
+int process::check_waitpid_status_(int status) {
+    if (WIFSIGNALED(status) != 0) {
+        // TODO: consider treating child exit caused by signals separately
+        return WTERMSIG(status);
+    } else if (WIFEXITED(status) != 0) {
+        return WEXITSTATUS(status);
+    }
+
+    throw std::logic_error{"unknown child process state"};
+}
+
+void process::close_pipe_fd_(int fd) {
+    const auto rv = ::close(fd);
+
+    if (rv != 0) {
+        const auto error = errno;
+        throw std::logic_error("failed to close pipe fd: " + std::string(strerror(error)));
+    }
+}