diff --git a/src/external/process.cpp b/src/external/process.cpp new file mode 100644 index 00000000..7e029b91 --- /dev/null +++ b/src/external/process.cpp @@ -0,0 +1,64 @@ +// +// tiny-process-library C++ library - https://gitlab.com/eidheim/tiny-process-library +// Commit: 6166ba5dce461438cefb57e847832aca25d510d7 +// +// The MIT License (MIT) +// +// Copyright (c) 2015-2020 Ole Christian Eidheim +// + +#include "process.hpp" + +namespace TinyProcessLib { + +Process::Process(const std::vector &arguments, const string_type &path, + std::function read_stdout, + std::function read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + std::function read_stdout, + std::function read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path); + async_read(); +} + +Process::Process(const std::vector &arguments, const string_type &path, + const environment_type &environment, + std::function read_stdout, + std::function read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path, &environment); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + const environment_type &environment, + std::function read_stdout, + std::function read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path, &environment); + async_read(); +} + +Process::~Process() noexcept { + close_fds(); +} + +Process::id_type Process::get_id() const noexcept { + return data.id; +} + +bool Process::write(const std::string &str) { + return write(str.c_str(), str.size()); +} + +} // namespace TinyProcessLib diff --git a/src/external/process.hpp b/src/external/process.hpp new file mode 100644 index 00000000..57a0793e --- /dev/null +++ b/src/external/process.hpp @@ -0,0 +1,193 @@ +// +// tiny-process-library C++ library - https://gitlab.com/eidheim/tiny-process-library +// Commit: 6166ba5dce461438cefb57e847832aca25d510d7 +// +// The MIT License (MIT) +// +// Copyright (c) 2015-2020 Ole Christian Eidheim +// + +#ifndef TINY_PROCESS_LIBRARY_HPP_ +#define TINY_PROCESS_LIBRARY_HPP_ +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +namespace TinyProcessLib { +/// Additional parameters to Process constructors. +struct Config { + /// Buffer size for reading stdout and stderr. Default is 131072 (128 kB). + std::size_t buffer_size = 131072; + /// Set to true to inherit file descriptors from parent process. Default is false. + /// On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false. + bool inherit_file_descriptors = false; + + /// If set, invoked when process stdout is closed. + /// This call goes after last call to read_stdout(). + std::function on_stdout_close = nullptr; + /// If set, invoked when process stderr is closed. + /// This call goes after last call to read_stderr(). + std::function on_stderr_close = nullptr; + + /// On Windows only: controls how the process is started, mimics STARTUPINFO's wShowWindow. + /// See: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-startupinfoa + /// and https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-showwindow + enum class ShowWindow { + hide = 0, + show_normal = 1, + show_minimized = 2, + maximize = 3, + show_maximized = 3, + show_no_activate = 4, + show = 5, + minimize = 6, + show_min_no_active = 7, + show_na = 8, + restore = 9, + show_default = 10, + force_minimize = 11 + }; + /// On Windows only: controls how the window is shown. + ShowWindow show_window{ShowWindow::show_default}; + + /// Set to true to break out of flatpak sandbox by prepending all commands with `/usr/bin/flatpak-spawn --host` + /// which will execute the command line on the host system. + /// Requires the flatpak `org.freedesktop.Flatpak` portal to be opened for the current sandbox. + /// See https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-spawn. + bool flatpak_spawn_host = false; +}; + +/// Platform independent class for creating processes. +/// Note on Windows: it seems not possible to specify which pipes to redirect. +/// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, +/// the stdout, stderr and stdin are sent to the parent process instead. +class Process { +public: +#ifdef _WIN32 + typedef unsigned long id_type; // Process id type + typedef void *fd_type; // File descriptor type +#ifdef UNICODE + typedef std::wstring string_type; +#else + typedef std::string string_type; +#endif +#else + typedef pid_t id_type; + typedef int fd_type; + typedef std::string string_type; +#endif + typedef std::unordered_map environment_type; + +private: + class Data { + public: + Data() noexcept; + id_type id; +#ifdef _WIN32 + void *handle{nullptr}; +#endif + int exit_status{-1}; + }; + +public: + /// Starts a process with the environment of the calling process. + Process(const std::vector &arguments, const string_type &path = string_type(), + std::function read_stdout = nullptr, + std::function read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with the environment of the calling process. + Process(const string_type &command, const string_type &path = string_type(), + std::function read_stdout = nullptr, + std::function read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + + /// Starts a process with specified environment. + Process(const std::vector &arguments, + const string_type &path, + const environment_type &environment, + std::function read_stdout = nullptr, + std::function read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with specified environment. + Process(const string_type &command, + const string_type &path, + const environment_type &environment, + std::function read_stdout = nullptr, + std::function read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; +#ifndef _WIN32 + /// Starts a process with the environment of the calling process. + /// Supported on Unix-like systems only. + /// Since the command line is not known to the Process object itself, + /// this overload does not support the flatpak_spawn_host configuration. + Process(const std::function &function, + std::function read_stdout = nullptr, + std::function read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}); +#endif + ~Process() noexcept; + + /// Get the process id of the started process. + id_type get_id() const noexcept; + /// Wait until process is finished, and return exit status. + int get_exit_status() noexcept; + /// If process is finished, returns true and sets the exit status. Returns false otherwise. + bool try_get_exit_status(int &exit_status) noexcept; + /// Write to stdin. + bool write(const char *bytes, size_t n); + /// Write to stdin. Convenience function using write(const char *, size_t). + bool write(const std::string &str); + /// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. + void close_stdin() noexcept; + + /// Kill the process. force=true is only supported on Unix-like systems. + void kill(bool force = false) noexcept; + /// Kill a given process id. Use kill(bool force) instead if possible. force=true is only supported on Unix-like systems. + static void kill(id_type id, bool force = false) noexcept; +#ifndef _WIN32 + /// Send the signal signum to the process. + void signal(int signum) noexcept; +#endif + +private: + Data data; + bool closed; + std::mutex close_mutex; + std::function read_stdout; + std::function read_stderr; +#ifndef _WIN32 + std::thread stdout_stderr_thread; +#else + std::thread stdout_thread, stderr_thread; +#endif + bool open_stdin; + std::mutex stdin_mutex; + + Config config; + + std::unique_ptr stdout_fd, stderr_fd, stdin_fd; + + id_type open(const std::vector &arguments, const string_type &path, const environment_type *environment = nullptr) noexcept; + id_type open(const string_type &command, const string_type &path, const environment_type *environment = nullptr) noexcept; +#ifndef _WIN32 + id_type open(const std::function &function) noexcept; +#endif + void async_read() noexcept; + void close_fds() noexcept; +}; + +} // namespace TinyProcessLib + +#endif // TINY_PROCESS_LIBRARY_HPP_ diff --git a/src/external/process_unix.cpp b/src/external/process_unix.cpp new file mode 100644 index 00000000..c8af82b2 --- /dev/null +++ b/src/external/process_unix.cpp @@ -0,0 +1,530 @@ +// +// tiny-process-library C++ library - https://gitlab.com/eidheim/tiny-process-library +// Commit: 6166ba5dce461438cefb57e847832aca25d510d7 +// +// The MIT License (MIT) +// +// Copyright (c) 2015-2020 Ole Christian Eidheim +// + +#include "process.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TinyProcessLib { + +static int portable_execvpe(const char *file, char *const argv[], char *const envp[]) { +#ifdef __GLIBC__ + // Prefer native implementation. + return execvpe(file, argv, envp); +#else + if(!file || !*file) { + errno = ENOENT; + return -1; + } + + if(strchr(file, '/') != nullptr) { + // If file contains a slash, no search is needed. + return execve(file, argv, envp); + } + + const char *path = getenv("PATH"); + char cspath[PATH_MAX + 1] = {}; + if(!path) { + // If env variable is not set, use static path string. + confstr(_CS_PATH, cspath, sizeof(cspath)); + path = cspath; + } + + const size_t path_len = strlen(path); + const size_t file_len = strlen(file); + + if(file_len > NAME_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + // Indicates whether we encountered EACCESS at least once. + bool eacces = false; + + const char *curr = nullptr; + const char *next = nullptr; + + for(curr = path; *curr; curr = *next ? next + 1 : next) { + next = strchr(curr, ':'); + if(!next) { + next = path + path_len; + } + + const size_t sz = (next - curr); + if(sz > PATH_MAX) { + // Path is too long. Proceed to next path in list. + continue; + } + + char exe_path[PATH_MAX + 1 + NAME_MAX + 1]; // 1 byte for slash + 1 byte for \0 + memcpy(exe_path, curr, sz); + exe_path[sz] = '/'; + memcpy(exe_path + sz + 1, file, file_len); + exe_path[sz + 1 + file_len] = '\0'; + + execve(exe_path, argv, envp); + + switch(errno) { + case EACCES: + eacces = true; + case ENOENT: + case ESTALE: + case ENOTDIR: + case ENODEV: + case ETIMEDOUT: + // The errors above indicate that the executable was not found. + // The list of errors replicates one from glibc. + // In this case we proceed to next path in list. + break; + + default: + // Other errors indicate that executable was found but failed + // to execute. In this case we return error. + return -1; + } + } + + if(eacces) { + // If search failed, and at least one iteration reported EACCESS, it means + // that the needed executable exists but does not have suitable permissions. + // In this case we report EACEESS for user. + errno = EACCES; + } + // Otherwise we just keep last encountered errno. + return -1; +#endif +} + +Process::Data::Data() noexcept : id(-1) { +} + +Process::Process(const std::function &function, + std::function read_stdout, + std::function read_stderr, + bool open_stdin, const Config &config) + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + if(config.flatpak_spawn_host) + throw std::invalid_argument("Cannot break out of a flatpak sandbox with this overload."); + open(function); + async_read(); +} + +Process::id_type Process::open(const std::function &function) noexcept { + if(open_stdin) + stdin_fd = std::unique_ptr(new fd_type); + if(read_stdout) + stdout_fd = std::unique_ptr(new fd_type); + if(read_stderr) + stderr_fd = std::unique_ptr(new fd_type); + + int stdin_p[2], stdout_p[2], stderr_p[2]; + + if(stdin_fd && pipe(stdin_p) != 0) + return -1; + if(stdout_fd && pipe(stdout_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + return -1; + } + if(stderr_fd && pipe(stderr_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + return -1; + } + + id_type pid = fork(); + + if(pid < 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + return pid; + } + else if(pid == 0) { + if(stdin_fd) + dup2(stdin_p[0], 0); + if(stdout_fd) + dup2(stdout_p[1], 1); + if(stderr_fd) + dup2(stderr_p[1], 2); + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + + if(!config.inherit_file_descriptors) { + // Optimization on some systems: using 8 * 1024 (Debian's default _SC_OPEN_MAX) as fd_max limit + int fd_max = std::min(8192, static_cast(sysconf(_SC_OPEN_MAX))); // Truncation is safe + if(fd_max < 0) + fd_max = 8192; + for(int fd = 3; fd < fd_max; fd++) + close(fd); + } + + setpgid(0, 0); + // TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe + // TODO: One solution is: echo "command;exit"|script -q /dev/null + + if(function) + function(); + + _exit(EXIT_FAILURE); + } + + if(stdin_fd) + close(stdin_p[0]); + if(stdout_fd) + close(stdout_p[1]); + if(stderr_fd) + close(stderr_p[1]); + + if(stdin_fd) + *stdin_fd = stdin_p[1]; + if(stdout_fd) + *stdout_fd = stdout_p[0]; + if(stderr_fd) + *stderr_fd = stderr_p[0]; + + closed = false; + data.id = pid; + return pid; +} + +Process::id_type Process::open(const std::vector &arguments, const string_type &path, const environment_type *environment) noexcept { + return open([this, &arguments, &path, &environment] { + if(arguments.empty()) + exit(127); + + std::vector argv_ptrs; + + if(config.flatpak_spawn_host) { + // break out of sandbox, execute on host + argv_ptrs.reserve(arguments.size() + 3); + argv_ptrs.emplace_back("/usr/bin/flatpak-spawn"); + argv_ptrs.emplace_back("--host"); + } + else + argv_ptrs.reserve(arguments.size() + 1); + + for(auto &argument : arguments) + argv_ptrs.emplace_back(argument.c_str()); + argv_ptrs.emplace_back(nullptr); + + if(!path.empty()) { + if(chdir(path.c_str()) != 0) + exit(1); + } + + if(!environment) + execvp(argv_ptrs[0], const_cast(argv_ptrs.data())); + else { + std::vector env_strs; + std::vector env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + + portable_execvpe(argv_ptrs[0], const_cast(argv_ptrs.data()), const_cast(env_ptrs.data())); + } + }); +} + +Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept { + return open([this, &command, &path, &environment] { + auto command_c_str = command.c_str(); + std::string cd_path_and_command; + if(!path.empty()) { + auto path_escaped = path; + size_t pos = 0; + // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 + while((pos = path_escaped.find('\'', pos)) != std::string::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + command_c_str = cd_path_and_command.c_str(); + } + + if(!environment) { + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execl("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr); + else + execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + } + else { + std::vector env_strs; + std::vector env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execle("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + else + execle("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + } + }); +} + +void Process::async_read() noexcept { + if(data.id <= 0 || (!stdout_fd && !stderr_fd)) + return; + + stdout_stderr_thread = std::thread([this] { + std::vector pollfds; + std::bitset<2> fd_is_stdout; + if(stdout_fd) { + fd_is_stdout.set(pollfds.size()); + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stdout_fd, F_SETFL, fcntl(*stdout_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stdout_fd : -1; + pollfds.back().events = POLLIN; + } + if(stderr_fd) { + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stderr_fd, F_SETFL, fcntl(*stderr_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stderr_fd : -1; + pollfds.back().events = POLLIN; + } + auto buffer = std::unique_ptr(new char[config.buffer_size]); + bool any_open = !pollfds.empty(); + while(any_open && (poll(pollfds.data(), static_cast(pollfds.size()), -1) > 0 || errno == EINTR)) { + any_open = false; + for(size_t i = 0; i < pollfds.size(); ++i) { + if(pollfds[i].fd >= 0) { + if(pollfds[i].revents & POLLIN) { + const ssize_t n = read(pollfds[i].fd, buffer.get(), config.buffer_size); + if(n > 0) { + if(fd_is_stdout[i]) + read_stdout(buffer.get(), static_cast(n)); + else + read_stderr(buffer.get(), static_cast(n)); + } + else if(n == 0 || (n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + } + else if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + any_open = true; + } + } + } + }); +} + +int Process::get_exit_status() noexcept { + if(data.id <= 0) + return -1; + + int exit_status; + id_type pid; + do { + pid = waitpid(data.id, &exit_status, 0); + } while(pid < 0 && errno == EINTR); + + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, return previously sampled exit status (or -1) + return data.exit_status; + } + else { + // Store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard lock(close_mutex); + closed = true; + } + close_fds(); + + return exit_status; +} + +bool Process::try_get_exit_status(int &exit_status) noexcept { + if(data.id <= 0) { + exit_status = -1; + return true; + } + + const id_type pid = waitpid(data.id, &exit_status, WNOHANG); + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, set previously sampled exit status (or -1) + exit_status = data.exit_status; + return true; + } + else if(pid <= 0) { + // Process still running (p==0) or error + return false; + } + else { + // store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard lock(close_mutex); + closed = true; + } + close_fds(); + + return true; +} + +void Process::close_fds() noexcept { + if(stdout_stderr_thread.joinable()) + stdout_stderr_thread.join(); + + if(stdin_fd) + close_stdin(); + if(stdout_fd) { + if(data.id > 0) + close(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + if(data.id > 0) + close(*stderr_fd); + stderr_fd.reset(); + } +} + +bool Process::write(const char *bytes, size_t n) { + if(!open_stdin) + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + + std::lock_guard lock(stdin_mutex); + if(stdin_fd) { + while(n != 0) { + const ssize_t ret = ::write(*stdin_fd, bytes, n); + if(ret < 0) { + if(errno == EINTR) + continue; + else + return false; + } + bytes += static_cast(ret); + n -= static_cast(ret); + } + return true; + } + return false; +} + +void Process::close_stdin() noexcept { + std::lock_guard lock(stdin_mutex); + if(stdin_fd) { + if(data.id > 0) + close(*stdin_fd); + stdin_fd.reset(); + } +} + +void Process::kill(bool force) noexcept { + std::lock_guard lock(close_mutex); + if(data.id > 0 && !closed) { + if(force) { + ::kill(-data.id, SIGTERM); + ::kill(data.id, SIGTERM); // Based on comment in https://gitlab.com/eidheim/tiny-process-library/-/merge_requests/29#note_1146144166 + } + else { + ::kill(-data.id, SIGINT); + ::kill(data.id, SIGINT); + } + } +} + +void Process::kill(id_type id, bool force) noexcept { + if(id <= 0) + return; + + if(force) { + ::kill(-id, SIGTERM); + ::kill(id, SIGTERM); + } + else { + ::kill(-id, SIGINT); + ::kill(id, SIGINT); + } +} + +void Process::signal(int signum) noexcept { + std::lock_guard lock(close_mutex); + if(data.id > 0 && !closed) { + ::kill(-data.id, signum); + ::kill(data.id, signum); + } +} + +} // namespace TinyProcessLib diff --git a/src/external/subprocess.hpp b/src/external/subprocess.hpp deleted file mode 100644 index 1871d834..00000000 --- a/src/external/subprocess.hpp +++ /dev/null @@ -1,170 +0,0 @@ -// -// subprocess C++ library - https://github.com/tsaarni/cpp-subprocess -// -// The MIT License (MIT) -// -// Copyright (c) 2015 Tero Saarni -// - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace subprocess -{ - -class popen -{ -public: - - popen(const std::string& cmd, std::vector argv) - : in_filebuf(nullptr), out_filebuf(nullptr), err_filebuf(nullptr), in_stream(nullptr), out_stream(nullptr), err_stream(nullptr) - { - if (pipe(in_pipe) == -1 || - pipe(out_pipe) == -1 || - pipe(err_pipe) == -1 ) - { - throw std::system_error(errno, std::system_category()); - } - - run(cmd, argv); - } - - popen(const std::string& cmd, std::vector argv, std::ostream& pipe_stdout) - : in_filebuf(nullptr), out_filebuf(nullptr), err_filebuf(nullptr), in_stream(nullptr), out_stream(nullptr), err_stream(nullptr) - { - auto filebuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(pipe_stdout.rdbuf()); - out_pipe[READ] = -1; - out_pipe[WRITE] = filebuf->fd(); - - if (pipe(in_pipe) == -1 || - pipe(err_pipe) == -1 ) - { - throw std::system_error(errno, std::system_category()); - } - - run(cmd, argv); - } - - ~popen() - { - delete in_filebuf; - delete in_stream; - if (out_filebuf != nullptr) delete out_filebuf; - if (out_stream != nullptr) delete out_stream; - delete err_filebuf; - delete err_stream; - } - - std::ostream& stdin() { return *in_stream; }; - - std::istream& stdout() - { - if (out_stream == nullptr) throw std::system_error(EBADF, std::system_category()); - return *out_stream; - }; - - std::istream& stderr() { return *err_stream; }; - - int wait() - { - int status = 0; - waitpid(pid, &status, 0); - return WEXITSTATUS(status); - }; - - void close() - { - in_filebuf->close(); - } - - -private: - - enum ends_of_pipe { READ = 0, WRITE = 1 }; - - struct raii_char_str - { - raii_char_str(std::string s) : buf(s.c_str(), s.c_str() + s.size() + 1) { }; - operator char*() const { return &buf[0]; }; - mutable std::vector buf; - }; - - void run(const std::string& cmd, std::vector argv) - { - argv.insert(argv.begin(), cmd); - - pid = ::fork(); - - if (pid == 0) child(argv); - - ::close(in_pipe[READ]); - ::close(out_pipe[WRITE]); - ::close(err_pipe[WRITE]); - - in_filebuf = new __gnu_cxx::stdio_filebuf(in_pipe[WRITE], std::ios_base::out, 1); - in_stream = new std::ostream(in_filebuf); - - if (out_pipe[READ] != -1) - { - out_filebuf = new __gnu_cxx::stdio_filebuf(out_pipe[READ], std::ios_base::in, 1); - out_stream = new std::istream(out_filebuf); - } - - err_filebuf = new __gnu_cxx::stdio_filebuf(err_pipe[READ], std::ios_base::in, 1); - err_stream = new std::istream(err_filebuf); - } - - void child(const std::vector& argv) - { - if (dup2(in_pipe[READ], STDIN_FILENO) == -1 || - dup2(out_pipe[WRITE], STDOUT_FILENO) == -1 || - dup2(err_pipe[WRITE], STDERR_FILENO) == -1 ) - { - std::perror("subprocess: dup2() failed"); - return; - } - - ::close(in_pipe[READ]); - ::close(in_pipe[WRITE]); - if (out_pipe[READ] != -1) ::close(out_pipe[READ]); - ::close(out_pipe[WRITE]); - ::close(err_pipe[READ]); - ::close(err_pipe[WRITE]); - - std::vector real_args(argv.begin(), argv.end()); - std::vector cargs(real_args.begin(), real_args.end()); - cargs.push_back(nullptr); - - if (execvp(cargs[0], &cargs[0]) == -1) - { - std::perror("subprocess: execvp() failed"); - return; - } - } - - pid_t pid; - - int in_pipe[2]; - int out_pipe[2]; - int err_pipe[2]; - - __gnu_cxx::stdio_filebuf* in_filebuf; - __gnu_cxx::stdio_filebuf* out_filebuf; - __gnu_cxx::stdio_filebuf* err_filebuf; - - std::ostream* in_stream; - std::istream* out_stream; - std::istream* err_stream; -}; - -} // namespace: subprocess diff --git a/src/robot_dart/gui/magnum/gs/camera.cpp b/src/robot_dart/gui/magnum/gs/camera.cpp index 57262982..fb8aebba 100644 --- a/src/robot_dart/gui/magnum/gs/camera.cpp +++ b/src/robot_dart/gui/magnum/gs/camera.cpp @@ -4,7 +4,7 @@ #include "robot_dart/robot_dart_simu.hpp" #include "robot_dart/utils.hpp" -#include +#include #include @@ -261,7 +261,9 @@ namespace robot_dart { "-vb", "20M", video_fname}; - _ffmpeg_process = new subprocess::popen(ffmpeg, args); + args.insert(args.begin(), ffmpeg); + _ffmpeg_process = new TinyProcessLib::Process( + args, "", [](const char*, size_t) {}, [](const char*, size_t) {}, true); } void Camera::draw(Magnum::SceneGraph::DrawableGroup3D& drawables, Magnum::GL::AbstractFramebuffer& framebuffer, Magnum::PixelFormat format, RobotDARTSimu* simu, const DebugDrawData& debug_data, bool draw_debug) @@ -372,15 +374,14 @@ namespace robot_dart { Corrade::Containers::StridedArrayView2D dst{Corrade::Containers::arrayCast(Corrade::Containers::arrayView(data)), {std::size_t(image.size().y()), std::size_t(image.size().x())}}; Corrade::Utility::copy(src, dst); - _ffmpeg_process->stdin().write(reinterpret_cast(data.data()), data.size()); - _ffmpeg_process->stdin().flush(); + _ffmpeg_process->write(reinterpret_cast(data.data()), data.size()); } } void Camera::_clean_up_subprocess() { if (_ffmpeg_process) { - _ffmpeg_process->close(); + _ffmpeg_process->close_stdin(); delete _ffmpeg_process; } diff --git a/src/robot_dart/gui/magnum/gs/camera.hpp b/src/robot_dart/gui/magnum/gs/camera.hpp index 853aa870..d48d07c1 100644 --- a/src/robot_dart/gui/magnum/gs/camera.hpp +++ b/src/robot_dart/gui/magnum/gs/camera.hpp @@ -12,8 +12,8 @@ #include #include -namespace subprocess { - class popen; +namespace TinyProcessLib { + class Process; } namespace robot_dart { @@ -90,7 +90,7 @@ namespace robot_dart { bool _recording_video = false; Corrade::Containers::Optional _image, _depth_image; - subprocess::popen* _ffmpeg_process = nullptr; + TinyProcessLib::Process* _ffmpeg_process = nullptr; void _clean_up_subprocess(); }; diff --git a/waf_tools/magnum.py b/waf_tools/magnum.py index b5204083..55781efd 100644 --- a/waf_tools/magnum.py +++ b/waf_tools/magnum.py @@ -239,26 +239,27 @@ def fatal(required, msg): egl_found = False glx_found = False if 'TARGET_HEADLESS' in magnum_config or 'TARGET_EGL' in magnum_config: - # TARGET_HEADLESS requires EGL - egl_inc = get_directory('EGL/egl.h', includes_check) + if conf.env['DEST_OS'] != 'darwin': # EGL is available only in Linux + # TARGET_HEADLESS requires EGL + egl_inc = get_directory('EGL/egl.h', includes_check) - magnum_includes = magnum_includes + [egl_inc] + magnum_includes = magnum_includes + [egl_inc] - libs_egl = ['EGL'] - for lib_egl in libs_egl: - try: - lib_dir = get_directory('lib'+lib_egl+'.so', libs_check) - egl_found = True + libs_egl = ['EGL'] + for lib_egl in libs_egl: + try: + lib_dir = get_directory('lib'+lib_egl+'.so', libs_check) + egl_found = True - magnum_libpaths = magnum_libpaths + [lib_dir] - magnum_libs.append(lib_egl) - break - except: - egl_found = False + magnum_libpaths = magnum_libpaths + [lib_dir] + magnum_libs.append(lib_egl) + break + except: + egl_found = False - if not egl_found: - fatal(required, 'Not found') - return + if not egl_found: + fatal(required, 'Not found') + return else: # we need GLX glx_inc = get_directory('GL/glx.h', includes_check) diff --git a/wscript b/wscript index fa10f9cb..8f7630a9 100644 --- a/wscript +++ b/wscript @@ -276,6 +276,11 @@ def build_robot_dart(bld): magnum_files.append(ffile) else: files.append(ffile) + # External (TinyProcessLib) + for root, dirnames, filenames in os.walk(bld.path.abspath()+'/src/external/'): + for filename in fnmatch.filter(filenames, '*.cpp'): + ffile = os.path.join(root, filename) + magnum_files.append(ffile) files = [f[len(bld.path.abspath())+1:] for f in files] robot_dart_srcs = " ".join(files)