parent
39ba4f5de2
commit
eda153e728
8 changed files with 1021 additions and 23 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,9 +1,6 @@
|
|||
[submodule "external/type_safe"]
|
||||
path = external/type_safe
|
||||
url = https://github.com/foonathan/type_safe
|
||||
[submodule "external/tiny-process-library"]
|
||||
path = external/tiny-process-library
|
||||
url = https://gitlab.com/eidheim/tiny-process-library.git
|
||||
[submodule "external/cxxopts"]
|
||||
path = external/cxxopts
|
||||
url = https://github.com/jarro2783/cxxopts
|
||||
|
|
|
|||
24
external/external.cmake
vendored
24
external/external.cmake
vendored
|
|
@ -16,27 +16,13 @@ endif()
|
|||
#
|
||||
# install the tiny-process-library
|
||||
#
|
||||
message(STATUS "Installing tiny-process-library via submodule")
|
||||
execute_process(COMMAND git submodule update --init -- external/tiny-process-library
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
find_package(Threads REQUIRED QUIET)
|
||||
|
||||
# create a target here instead of using the one provided
|
||||
set(tiny_process_dir ${CMAKE_CURRENT_SOURCE_DIR}/external/tiny-process-library)
|
||||
if(WIN32)
|
||||
add_library(_cppast_tiny_process EXCLUDE_FROM_ALL
|
||||
${tiny_process_dir}/process.hpp
|
||||
${tiny_process_dir}/process.cpp
|
||||
${tiny_process_dir}/process_win.cpp)
|
||||
else()
|
||||
add_library(_cppast_tiny_process EXCLUDE_FROM_ALL
|
||||
${tiny_process_dir}/process.hpp
|
||||
${tiny_process_dir}/process.cpp
|
||||
${tiny_process_dir}/process_unix.cpp)
|
||||
endif()
|
||||
target_include_directories(_cppast_tiny_process PUBLIC ${tiny_process_dir})
|
||||
target_link_libraries(_cppast_tiny_process PUBLIC Threads::Threads)
|
||||
set_target_properties(_cppast_tiny_process PROPERTIES CXX_STANDARD 11)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/external/tpl)
|
||||
add_library(_cppast_tiny_process INTERFACE)
|
||||
target_include_directories(_cppast_tiny_process INTERFACE ${tiny_process_dir})
|
||||
target_link_libraries(_cppast_tiny_process INTERFACE tiny-process-library::tiny-process-library Threads::Threads)
|
||||
|
||||
#
|
||||
# install cxxopts, if needed
|
||||
|
|
@ -114,7 +100,7 @@ function(_cppast_find_llvm_config)
|
|||
find_program(LLVM_CONFIG_BINARY "llvm-config" "${LLVM_DOWNLOAD_DIR}/bin" NO_DEFAULT_PATH)
|
||||
else()
|
||||
find_program(llvm_config_binary_no_suffix llvm-config)
|
||||
find_program(llvm_config_binary_suffix NAMES llvm-config-7 llvm-config-6.0 llvm-config-5.0 llvm-config-4.0)
|
||||
find_program(llvm_config_binary_suffix NAMES llvm-config-10 llvm-config-9 llvm-config-8 llvm-config-7 llvm-config-6.0 llvm-config-5.0 llvm-config-4.0)
|
||||
|
||||
if(NOT llvm_config_binary_no_suffix)
|
||||
set(LLVM_CONFIG_BINARY ${llvm_config_binary_suffix} CACHE INTERNAL "")
|
||||
|
|
|
|||
1
external/tiny-process-library
vendored
1
external/tiny-process-library
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit a6773276efdbc322f1abb0159301f5502647eb52
|
||||
66
external/tpl/CMakeLists.txt
vendored
Normal file
66
external/tpl/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
project(tiny-process-library)
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
option(BUILD_TESTING "set ON to build library tests" ON)
|
||||
else()
|
||||
option(BUILD_TESTING "set ON to build library tests" OFF)
|
||||
endif()
|
||||
|
||||
add_library(tiny-process-library process.cpp)
|
||||
add_library(tiny-process-library::tiny-process-library ALIAS tiny-process-library)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_definitions(tiny-process-library PRIVATE /D_CRT_SECURE_NO_WARNINGS)
|
||||
else()
|
||||
target_compile_options(tiny-process-library PRIVATE -std=c++11 -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
target_sources(tiny-process-library PRIVATE process_win.cpp)
|
||||
#If compiled using MSYS2, use sh to run commands
|
||||
if(MSYS)
|
||||
target_compile_definitions(tiny-process-library PUBLIC MSYS_PROCESS_USE_SH)
|
||||
endif()
|
||||
else()
|
||||
target_sources(tiny-process-library PRIVATE process_unix.cpp)
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
target_link_libraries(tiny-process-library ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_include_directories(tiny-process-library PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
# if tiny-process-library is not a sub-project:
|
||||
if(CMAKE_SOURCE_DIR STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
if(MSVC)
|
||||
add_definitions(/D_CRT_SECURE_NO_WARNINGS)
|
||||
else()
|
||||
add_compile_options(-std=c++11 -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
add_executable(examples examples.cpp)
|
||||
target_link_libraries(examples tiny-process-library)
|
||||
|
||||
install(TARGETS tiny-process-library
|
||||
EXPORT ${PROJECT_NAME}-config
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
|
||||
install(EXPORT ${PROJECT_NAME}-config
|
||||
FILE ${PROJECT_NAME}-config.cmake
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION lib/cmake/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
install(FILES process.hpp DESTINATION include)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
55
external/tpl/process.cpp
vendored
Normal file
55
external/tpl/process.cpp
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "process.hpp"
|
||||
|
||||
namespace TinyProcessLib {
|
||||
|
||||
Process::Process(const std::vector<string_type> &arguments, const string_type &path,
|
||||
std::function<void(const char *bytes, size_t n)> read_stdout,
|
||||
std::function<void(const char *bytes, size_t n)> 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<void(const char *bytes, size_t n)> read_stdout,
|
||||
std::function<void(const char *bytes, size_t n)> 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<string_type> &arguments, const string_type &path,
|
||||
const environment_type &environment,
|
||||
std::function<void(const char *bytes, size_t n)> read_stdout,
|
||||
std::function<void(const char *bytes, size_t n)> 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<void(const char *bytes, size_t n)> read_stdout,
|
||||
std::function<void(const char *bytes, size_t n)> 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
|
||||
169
external/tpl/process.hpp
vendored
Normal file
169
external/tpl/process.hpp
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#ifndef TINY_PROCESS_LIBRARY_HPP_
|
||||
#define TINY_PROCESS_LIBRARY_HPP_
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#ifndef _WIN32
|
||||
#include <sys/wait.h>
|
||||
#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;
|
||||
|
||||
/// 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};
|
||||
};
|
||||
|
||||
/// 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<string_type, string_type> 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<string_type> &arguments, const string_type &path = string_type(),
|
||||
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
|
||||
std::function<void(const char *bytes, size_t n)> 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<void(const char *bytes, size_t n)> read_stdout = nullptr,
|
||||
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
|
||||
bool open_stdin = false,
|
||||
const Config &config = {}) noexcept;
|
||||
|
||||
/// Starts a process with specified environment.
|
||||
Process(const std::vector<string_type> &arguments,
|
||||
const string_type &path,
|
||||
const environment_type &environment,
|
||||
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
|
||||
std::function<void(const char *bytes, size_t n)> 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<void(const char *bytes, size_t n)> read_stdout = nullptr,
|
||||
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
|
||||
bool open_stdin = false,
|
||||
const Config &config = {}) noexcept; /// Starts a process with specified environment.
|
||||
#ifndef _WIN32
|
||||
/// Starts a process with the environment of the calling process.
|
||||
/// Supported on Unix-like systems only.
|
||||
Process(const std::function<void()> &function,
|
||||
std::function<void(const char *bytes, size_t n)> read_stdout = nullptr,
|
||||
std::function<void(const char *bytes, size_t n)> read_stderr = nullptr,
|
||||
bool open_stdin = false,
|
||||
const Config &config = {}) noexcept;
|
||||
#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<void(const char *bytes, size_t n)> read_stdout;
|
||||
std::function<void(const char *bytes, size_t n)> 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<fd_type> stdout_fd, stderr_fd, stdin_fd;
|
||||
|
||||
id_type open(const std::vector<string_type> &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<void()> &function) noexcept;
|
||||
#endif
|
||||
void async_read() noexcept;
|
||||
void close_fds() noexcept;
|
||||
};
|
||||
|
||||
} // namespace TinyProcessLib
|
||||
|
||||
#endif // TINY_PROCESS_LIBRARY_HPP_
|
||||
376
external/tpl/process_unix.cpp
vendored
Normal file
376
external/tpl/process_unix.cpp
vendored
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
#include "process.hpp"
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <set>
|
||||
#include <signal.h>
|
||||
#include <stdexcept>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace TinyProcessLib {
|
||||
|
||||
Process::Data::Data() noexcept : id(-1) {}
|
||||
|
||||
Process::Process(const std::function<void()> &function,
|
||||
std::function<void(const char *, size_t)> read_stdout,
|
||||
std::function<void(const char *, size_t)> 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(function);
|
||||
async_read();
|
||||
}
|
||||
|
||||
Process::id_type Process::open(const std::function<void()> &function) noexcept {
|
||||
if(open_stdin)
|
||||
stdin_fd = std::unique_ptr<fd_type>(new fd_type);
|
||||
if(read_stdout)
|
||||
stdout_fd = std::unique_ptr<fd_type>(new fd_type);
|
||||
if(read_stderr)
|
||||
stderr_fd = std::unique_ptr<fd_type>(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<int>(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<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept {
|
||||
return open([&arguments, &path, &environment] {
|
||||
if(arguments.empty())
|
||||
exit(127);
|
||||
|
||||
std::vector<const char *> argv_ptrs;
|
||||
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)
|
||||
execv(arguments[0].c_str(), const_cast<char *const *>(argv_ptrs.data()));
|
||||
else {
|
||||
std::vector<std::string> env_strs;
|
||||
std::vector<const char *> 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);
|
||||
|
||||
execve(arguments[0].c_str(), const_cast<char *const *>(argv_ptrs.data()), const_cast<char *const *>(env_ptrs.data()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept {
|
||||
return open([&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)
|
||||
execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr);
|
||||
else {
|
||||
std::vector<std::string> env_strs;
|
||||
std::vector<const char *> 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);
|
||||
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<pollfd> 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<char[]>(new char[config.buffer_size]);
|
||||
bool any_open = !pollfds.empty();
|
||||
while(any_open && (poll(pollfds.data(), static_cast<nfds_t>(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<size_t>(n));
|
||||
else
|
||||
read_stderr(buffer.get(), static_cast<size_t>(n));
|
||||
}
|
||||
else if(n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
pollfds[i].fd = -1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
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<std::mutex> lock(close_mutex);
|
||||
closed = true;
|
||||
}
|
||||
close_fds();
|
||||
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
bool Process::try_get_exit_status(int &exit_status) noexcept {
|
||||
if(data.id <= 0)
|
||||
return false;
|
||||
|
||||
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<std::mutex> 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<std::mutex> lock(stdin_mutex);
|
||||
if(stdin_fd) {
|
||||
if(::write(*stdin_fd, bytes, n) >= 0) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Process::close_stdin() noexcept {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> lock(close_mutex);
|
||||
if(data.id > 0 && !closed) {
|
||||
if(force)
|
||||
::kill(-data.id, SIGTERM);
|
||||
else
|
||||
::kill(-data.id, SIGINT);
|
||||
}
|
||||
}
|
||||
|
||||
void Process::kill(id_type id, bool force) noexcept {
|
||||
if(id <= 0)
|
||||
return;
|
||||
|
||||
if(force)
|
||||
::kill(-id, SIGTERM);
|
||||
else
|
||||
::kill(-id, SIGINT);
|
||||
}
|
||||
|
||||
void Process::signal(int signum) noexcept {
|
||||
std::lock_guard<std::mutex> lock(close_mutex);
|
||||
if(data.id > 0 && !closed) {
|
||||
::kill(-data.id, signum);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace TinyProcessLib
|
||||
350
external/tpl/process_win.cpp
vendored
Normal file
350
external/tpl/process_win.cpp
vendored
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#include "process.hpp"
|
||||
// clang-format off
|
||||
#include <windows.h>
|
||||
// clang-format on
|
||||
#include <tlhelp32.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace TinyProcessLib {
|
||||
|
||||
Process::Data::Data() noexcept : id(0) {}
|
||||
|
||||
// Simple HANDLE wrapper to close it automatically from the destructor.
|
||||
class Handle {
|
||||
public:
|
||||
Handle() noexcept : handle(INVALID_HANDLE_VALUE) {}
|
||||
~Handle() noexcept {
|
||||
close();
|
||||
}
|
||||
void close() noexcept {
|
||||
if(handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
}
|
||||
HANDLE detach() noexcept {
|
||||
HANDLE old_handle = handle;
|
||||
handle = INVALID_HANDLE_VALUE;
|
||||
return old_handle;
|
||||
}
|
||||
operator HANDLE() const noexcept { return handle; }
|
||||
HANDLE *operator&() noexcept { return &handle; }
|
||||
|
||||
private:
|
||||
HANDLE handle;
|
||||
};
|
||||
|
||||
//Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj
|
||||
std::mutex create_process_mutex;
|
||||
|
||||
Process::id_type Process::open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept {
|
||||
string_type command;
|
||||
for(auto &argument : arguments)
|
||||
#ifdef UNICODE
|
||||
command += (command.empty() ? L"" : L" ") + argument;
|
||||
#else
|
||||
command += (command.empty() ? "" : " ") + argument;
|
||||
#endif
|
||||
return open(command, path, environment);
|
||||
}
|
||||
|
||||
//Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx.
|
||||
Process::id_type Process::open(const string_type &command, const string_type &path, const environment_type *environment) noexcept {
|
||||
if(open_stdin)
|
||||
stdin_fd = std::unique_ptr<fd_type>(new fd_type(nullptr));
|
||||
if(read_stdout)
|
||||
stdout_fd = std::unique_ptr<fd_type>(new fd_type(nullptr));
|
||||
if(read_stderr)
|
||||
stderr_fd = std::unique_ptr<fd_type>(new fd_type(nullptr));
|
||||
|
||||
Handle stdin_rd_p;
|
||||
Handle stdin_wr_p;
|
||||
Handle stdout_rd_p;
|
||||
Handle stdout_wr_p;
|
||||
Handle stderr_rd_p;
|
||||
Handle stderr_wr_p;
|
||||
|
||||
SECURITY_ATTRIBUTES security_attributes;
|
||||
|
||||
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
security_attributes.bInheritHandle = TRUE;
|
||||
security_attributes.lpSecurityDescriptor = nullptr;
|
||||
|
||||
std::lock_guard<std::mutex> lock(create_process_mutex);
|
||||
if(stdin_fd) {
|
||||
if(!CreatePipe(&stdin_rd_p, &stdin_wr_p, &security_attributes, 0) ||
|
||||
!SetHandleInformation(stdin_wr_p, HANDLE_FLAG_INHERIT, 0))
|
||||
return 0;
|
||||
}
|
||||
if(stdout_fd) {
|
||||
if(!CreatePipe(&stdout_rd_p, &stdout_wr_p, &security_attributes, 0) ||
|
||||
!SetHandleInformation(stdout_rd_p, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(stderr_fd) {
|
||||
if(!CreatePipe(&stderr_rd_p, &stderr_wr_p, &security_attributes, 0) ||
|
||||
!SetHandleInformation(stderr_rd_p, HANDLE_FLAG_INHERIT, 0)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION process_info;
|
||||
STARTUPINFO startup_info;
|
||||
|
||||
ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(STARTUPINFO));
|
||||
startup_info.cb = sizeof(STARTUPINFO);
|
||||
startup_info.hStdInput = stdin_rd_p;
|
||||
startup_info.hStdOutput = stdout_wr_p;
|
||||
startup_info.hStdError = stderr_wr_p;
|
||||
if(stdin_fd || stdout_fd || stderr_fd)
|
||||
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
if(config.show_window != Config::ShowWindow::show_default) {
|
||||
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
|
||||
startup_info.wShowWindow = static_cast<WORD>(config.show_window);
|
||||
}
|
||||
|
||||
auto process_command = command;
|
||||
#ifdef MSYS_PROCESS_USE_SH
|
||||
size_t pos = 0;
|
||||
while((pos = process_command.find('\\', pos)) != string_type::npos) {
|
||||
process_command.replace(pos, 1, "\\\\\\\\");
|
||||
pos += 4;
|
||||
}
|
||||
pos = 0;
|
||||
while((pos = process_command.find('\"', pos)) != string_type::npos) {
|
||||
process_command.replace(pos, 1, "\\\"");
|
||||
pos += 2;
|
||||
}
|
||||
process_command.insert(0, "sh -c \"");
|
||||
process_command += "\"";
|
||||
#endif
|
||||
|
||||
string_type environment_str;
|
||||
if(environment) {
|
||||
#ifdef UNICODE
|
||||
for(const auto &e : *environment)
|
||||
environment_str += e.first + L'=' + e.second + L'\0';
|
||||
environment_str += L'\0';
|
||||
#else
|
||||
for(const auto &e : *environment)
|
||||
environment_str += e.first + '=' + e.second + '\0';
|
||||
environment_str += '\0';
|
||||
#endif
|
||||
}
|
||||
BOOL bSuccess = CreateProcess(nullptr, process_command.empty() ? nullptr : &process_command[0], nullptr, nullptr,
|
||||
stdin_fd || stdout_fd || stderr_fd || config.inherit_file_descriptors, // Cannot be false when stdout, stderr or stdin is used
|
||||
stdin_fd || stdout_fd || stderr_fd ? CREATE_NO_WINDOW : 0, // CREATE_NO_WINDOW cannot be used when stdout or stderr is redirected to parent process
|
||||
environment_str.empty() ? nullptr : &environment_str[0],
|
||||
path.empty() ? nullptr : path.c_str(),
|
||||
&startup_info, &process_info);
|
||||
|
||||
if(!bSuccess)
|
||||
return 0;
|
||||
else
|
||||
CloseHandle(process_info.hThread);
|
||||
|
||||
if(stdin_fd)
|
||||
*stdin_fd = stdin_wr_p.detach();
|
||||
if(stdout_fd)
|
||||
*stdout_fd = stdout_rd_p.detach();
|
||||
if(stderr_fd)
|
||||
*stderr_fd = stderr_rd_p.detach();
|
||||
|
||||
closed = false;
|
||||
data.id = process_info.dwProcessId;
|
||||
data.handle = process_info.hProcess;
|
||||
return process_info.dwProcessId;
|
||||
}
|
||||
|
||||
void Process::async_read() noexcept {
|
||||
if(data.id == 0)
|
||||
return;
|
||||
|
||||
if(stdout_fd) {
|
||||
stdout_thread = std::thread([this]() {
|
||||
DWORD n;
|
||||
std::unique_ptr<char[]> buffer(new char[config.buffer_size]);
|
||||
for(;;) {
|
||||
BOOL bSuccess = ReadFile(*stdout_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr);
|
||||
if(!bSuccess || n == 0)
|
||||
break;
|
||||
read_stdout(buffer.get(), static_cast<size_t>(n));
|
||||
}
|
||||
});
|
||||
}
|
||||
if(stderr_fd) {
|
||||
stderr_thread = std::thread([this]() {
|
||||
DWORD n;
|
||||
std::unique_ptr<char[]> buffer(new char[config.buffer_size]);
|
||||
for(;;) {
|
||||
BOOL bSuccess = ReadFile(*stderr_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr);
|
||||
if(!bSuccess || n == 0)
|
||||
break;
|
||||
read_stderr(buffer.get(), static_cast<size_t>(n));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
int Process::get_exit_status() noexcept {
|
||||
if(data.id == 0)
|
||||
return -1;
|
||||
|
||||
if(!data.handle)
|
||||
return data.exit_status;
|
||||
|
||||
WaitForSingleObject(data.handle, INFINITE);
|
||||
|
||||
DWORD exit_status;
|
||||
if(!GetExitCodeProcess(data.handle, &exit_status))
|
||||
data.exit_status = -1; // Store exit status for future calls
|
||||
else
|
||||
data.exit_status = static_cast<int>(exit_status); // Store exit status for future calls
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(close_mutex);
|
||||
CloseHandle(data.handle);
|
||||
data.handle = nullptr;
|
||||
closed = true;
|
||||
}
|
||||
close_fds();
|
||||
|
||||
return data.exit_status;
|
||||
}
|
||||
|
||||
bool Process::try_get_exit_status(int &exit_status) noexcept {
|
||||
if(data.id == 0)
|
||||
return false;
|
||||
|
||||
if(!data.handle) {
|
||||
exit_status = data.exit_status;
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD wait_status = WaitForSingleObject(data.handle, 0);
|
||||
if(wait_status == WAIT_TIMEOUT)
|
||||
return false;
|
||||
|
||||
DWORD exit_status_tmp;
|
||||
if(!GetExitCodeProcess(data.handle, &exit_status_tmp))
|
||||
exit_status = -1;
|
||||
else
|
||||
exit_status = static_cast<int>(exit_status_tmp);
|
||||
data.exit_status = exit_status; // Store exit status for future calls
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(close_mutex);
|
||||
CloseHandle(data.handle);
|
||||
data.handle = nullptr;
|
||||
closed = true;
|
||||
}
|
||||
close_fds();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Process::close_fds() noexcept {
|
||||
if(stdout_thread.joinable())
|
||||
stdout_thread.join();
|
||||
if(stderr_thread.joinable())
|
||||
stderr_thread.join();
|
||||
|
||||
if(stdin_fd)
|
||||
close_stdin();
|
||||
if(stdout_fd) {
|
||||
if(*stdout_fd != nullptr)
|
||||
CloseHandle(*stdout_fd);
|
||||
stdout_fd.reset();
|
||||
}
|
||||
if(stderr_fd) {
|
||||
if(*stderr_fd != nullptr)
|
||||
CloseHandle(*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<std::mutex> lock(stdin_mutex);
|
||||
if(stdin_fd) {
|
||||
DWORD written;
|
||||
BOOL bSuccess = WriteFile(*stdin_fd, bytes, static_cast<DWORD>(n), &written, nullptr);
|
||||
if(!bSuccess || written == 0) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Process::close_stdin() noexcept {
|
||||
std::lock_guard<std::mutex> lock(stdin_mutex);
|
||||
if(stdin_fd) {
|
||||
if(*stdin_fd != nullptr)
|
||||
CloseHandle(*stdin_fd);
|
||||
stdin_fd.reset();
|
||||
}
|
||||
}
|
||||
|
||||
//Based on http://stackoverflow.com/a/1173396
|
||||
void Process::kill(bool /*force*/) noexcept {
|
||||
std::lock_guard<std::mutex> lock(close_mutex);
|
||||
if(data.id > 0 && !closed) {
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if(snapshot) {
|
||||
PROCESSENTRY32 process;
|
||||
ZeroMemory(&process, sizeof(process));
|
||||
process.dwSize = sizeof(process);
|
||||
if(Process32First(snapshot, &process)) {
|
||||
do {
|
||||
if(process.th32ParentProcessID == data.id) {
|
||||
HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID);
|
||||
if(process_handle) {
|
||||
TerminateProcess(process_handle, 2);
|
||||
CloseHandle(process_handle);
|
||||
}
|
||||
}
|
||||
} while(Process32Next(snapshot, &process));
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
TerminateProcess(data.handle, 2);
|
||||
}
|
||||
}
|
||||
|
||||
//Based on http://stackoverflow.com/a/1173396
|
||||
void Process::kill(id_type id, bool /*force*/) noexcept {
|
||||
if(id == 0)
|
||||
return;
|
||||
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if(snapshot) {
|
||||
PROCESSENTRY32 process;
|
||||
ZeroMemory(&process, sizeof(process));
|
||||
process.dwSize = sizeof(process);
|
||||
if(Process32First(snapshot, &process)) {
|
||||
do {
|
||||
if(process.th32ParentProcessID == id) {
|
||||
HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID);
|
||||
if(process_handle) {
|
||||
TerminateProcess(process_handle, 2);
|
||||
CloseHandle(process_handle);
|
||||
}
|
||||
}
|
||||
} while(Process32Next(snapshot, &process));
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id);
|
||||
if(process_handle)
|
||||
TerminateProcess(process_handle, 2);
|
||||
}
|
||||
|
||||
} // namespace TinyProcessLib
|
||||
Loading…
Add table
Add a link
Reference in a new issue