WIP: Add windows compatibility (#30)

* add package files

* add windows compat to subprocess.hpp

* add test modifications

* repair test_read_all
This commit is contained in:
xoviat 2019-05-02 01:32:17 -05:00 committed by Arun Muralidharan
parent de5f791d04
commit 5d92f48492
6 changed files with 640 additions and 216 deletions

1
.clang-format Normal file
View File

@ -0,0 +1 @@
BreakBeforeBraces: Stroustrup

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build*
.vscode

6
CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.9)
project(subprocess CXX)
enable_testing()
add_subdirectory(test)

View File

@ -34,28 +34,35 @@ Documentation for C++ subprocessing libraray.
#ifndef SUBPROCESS_HPP #ifndef SUBPROCESS_HPP
#define SUBPROCESS_HPP #define SUBPROCESS_HPP
#include <map>
#include <algorithm> #include <algorithm>
#include <iostream>
#include <string>
#include <cstdlib>
#include <cassert> #include <cassert>
#include <cstring> #include <codecvt>
#include <cstdio>
#include <csignal> #include <csignal>
#include <future> #include <cstdio>
#include <vector> #include <cstdlib>
#include <sstream> #include <cstring>
#include <memory>
#include <initializer_list>
#include <exception> #include <exception>
#include <future>
#include <initializer_list>
#include <iostream>
#include <locale>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
extern "C" { extern "C" {
#include <unistd.h> #ifdef _MSC_VER
#include <fcntl.h> #include <Windows.h>
#include <sys/types.h> #include <io.h>
#include <sys/wait.h> #else
#include <signal.h> #include <sys/wait.h>
#include <unistd.h>
#endif
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
} }
/*! /*!
@ -137,7 +144,7 @@ public:
namespace util namespace util
{ {
/*! /*!
* Function: split * Function: split
* Parameters: * Parameters:
* [in] str : Input string which needs to be split based upon the * [in] str : Input string which needs to be split based upon the
@ -146,9 +153,9 @@ namespace util
* to be split. Default constructed to ' '(space) and '\t'(tab) * to be split. Default constructed to ' '(space) and '\t'(tab)
* [out] vector<string> : Vector of strings split at deleimiter. * [out] vector<string> : Vector of strings split at deleimiter.
*/ */
static inline std::vector<std::string> static inline std::vector<std::string>
split(const std::string& str, const std::string& delims=" \t") split(const std::string& str, const std::string& delims=" \t")
{ {
std::vector<std::string> res; std::vector<std::string> res;
size_t init = 0; size_t init = 0;
@ -164,10 +171,10 @@ namespace util
} }
return res; return res;
} }
/*! /*!
* Function: join * Function: join
* Parameters: * Parameters:
* [in] vec : Vector of strings which needs to be joined to form * [in] vec : Vector of strings which needs to be joined to form
@ -176,17 +183,131 @@ namespace util
* Default constructed to ' ' (space). * Default constructed to ' ' (space).
* [out] string: Joined string. * [out] string: Joined string.
*/ */
static inline static inline
std::string join(const std::vector<std::string>& vec, std::string join(const std::vector<std::string>& vec,
const std::string& sep = " ") const std::string& sep = " ")
{ {
std::string res; std::string res;
for (auto& elem : vec) res.append(elem + sep); for (auto& elem : vec) res.append(elem + sep);
res.erase(--res.end()); res.erase(--res.end());
return res; return res;
}
#ifdef _MSC_VER
template <typename R> bool is_ready(std::shared_future<R> const &f)
{
return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}
void quote_argument(const std::wstring &argument, std::wstring &commandLine,
bool Force)
{
//
// Unless we're told otherwise, don't quote unless we actually
// need to do so --- hopefully avoid problems if programs won't
// parse quotes properly
//
if (Force == false && argument.empty() == false &&
argument.find_first_of(L" \t\n\v\"") == argument.npos) {
commandLine.append(argument);
}
else {
commandLine.push_back(L'"');
for (auto It = argument.begin();; ++It) {
unsigned NumberBackslashes = 0;
while (It != argument.end() && *It == L'\\') {
++It;
++NumberBackslashes;
} }
if (It == argument.end()) {
//
// Escape all backslashes, but let the terminating
// double quotation mark we add below be interpreted
// as a metacharacter.
//
commandLine.append(NumberBackslashes * 2, L'\\');
break;
}
else if (*It == L'"') {
//
// Escape all backslashes and the following
// double quotation mark.
//
commandLine.append(NumberBackslashes * 2 + 1, L'\\');
commandLine.push_back(*It);
}
else {
//
// Backslashes aren't special here.
//
commandLine.append(NumberBackslashes, L'\\');
commandLine.push_back(*It);
}
}
commandLine.push_back(L'"');
}
}
std::string get_last_error()
{
DWORD errorMessageID = ::GetLastError();
if (errorMessageID == 0)
return std::string();
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer, 0, NULL);
std::string message(messageBuffer, size);
LocalFree(messageBuffer);
return message;
}
FILE *file_from_handle(HANDLE h, const char *mode)
{
int md;
if (mode == "w") {
md = _O_WRONLY;
}
else if (mode == "r") {
md = _O_RDONLY;
}
else {
throw OSError("file_from_handle", 0);
}
int os_fhandle = _open_osfhandle((intptr_t)h, md);
if (os_fhandle == -1) {
CloseHandle(h);
throw OSError("_open_osfhandle", 0);
}
FILE *fp = _fdopen(os_fhandle, mode);
if (fp == 0) {
_close(os_fhandle);
throw OSError("_fdopen", 0);
}
return fp;
}
#else
/*! /*!
* Function: set_clo_on_exec * Function: set_clo_on_exec
* Sets/Resets the FD_CLOEXEC flag on the provided file descriptor * Sets/Resets the FD_CLOEXEC flag on the provided file descriptor
@ -256,7 +377,9 @@ namespace util
} }
/*! #endif
/*!
* Function: read_atmost_n * Function: read_atmost_n
* Reads at the most `read_upto` bytes from the * Reads at the most `read_upto` bytes from the
* file descriptor `fd` before returning. * file descriptor `fd` before returning.
@ -270,9 +393,13 @@ namespace util
* will retry to read from `fd`, but only till the EINTR counter * will retry to read from `fd`, but only till the EINTR counter
* reaches 50 after which it will return with whatever data it read. * reaches 50 after which it will return with whatever data it read.
*/ */
static inline static inline
int read_atmost_n(int fd, char* buf, size_t read_upto) int read_atmost_n(FILE *fp, char *buf, size_t read_upto)
{ {
#ifdef _MSC_VER
return (int)fread(buf, 1, read_upto, fp);
#else
int fd = _fileno(fp);
int rbytes = 0; int rbytes = 0;
int eintr_cnter = 0; int eintr_cnter = 0;
@ -280,63 +407,13 @@ namespace util
int read_bytes = read(fd, buf + rbytes, read_upto - rbytes); int read_bytes = read(fd, buf + rbytes, read_upto - rbytes);
if (read_bytes == -1) { if (read_bytes == -1) {
if (errno == EINTR) { if (errno == EINTR) {
if (eintr_cnter >= 50) return -1; if (eintr_cnter >= 50)
return -1;
eintr_cnter++; eintr_cnter++;
continue; continue;
} }
return -1; return -1;
} }
if (read_bytes == 0) return rbytes;
rbytes += read_bytes;
}
return rbytes;
}
/*!
* Function: read_all
* Reads all the available data from `fd` into
* `buf`. Internally calls read_atmost_n.
* Parameters:
* [in] fd : The file descriptor from which to read from.
* [in] buf : The buffer of type `class Buffer` into which
* the read data is written to.
* [out] int: Number of bytes read OR -1 in case of failure.
*
* NOTE: `class Buffer` is a exposed public class. See below.
*/
static inline int read_all(int fd, std::vector<char>& buf)
{
auto buffer = buf.data();
int total_bytes_read = 0;
int fill_sz = buf.size();
while (1) {
const int rd_bytes = read_atmost_n(fd, buffer, fill_sz);
if (rd_bytes == -1) { // Read finished
if (total_bytes_read == 0) return -1;
break;
} else if (rd_bytes == fill_sz) { // Buffer full
const auto orig_sz = buf.size();
const auto new_sz = orig_sz * 2;
buf.resize(new_sz);
fill_sz = new_sz - orig_sz;
//update the buffer pointer
buffer = buf.data();
total_bytes_read += rd_bytes;
buffer += total_bytes_read;
} else { // Partial data ? Continue reading
total_bytes_read += rd_bytes;
fill_sz -= rd_bytes;
break;
}
}
buf.erase(buf.begin()+total_bytes_read, buf.end()); // remove extra nulls buf.erase(buf.begin()+total_bytes_read, buf.end()); // remove extra nulls
return total_bytes_read; return total_bytes_read;
} }
@ -367,10 +444,88 @@ namespace util
return std::make_pair(ret, status); return std::make_pair(ret, status);
} }
rbytes += read_bytes;
}
return rbytes;
#endif
}
/*!
* Function: read_all
* Reads all the available data from `fd` into
* `buf`. Internally calls read_atmost_n.
* Parameters:
* [in] fd : The file descriptor from which to read from.
* [in] buf : The buffer of type `class Buffer` into which
* the read data is written to.
* [out] int: Number of bytes read OR -1 in case of failure.
*
* NOTE: `class Buffer` is a exposed public class. See below.
*/
template <typename Buffer> static inline int read_all(FILE *fp, Buffer &buf)
{
auto buffer = buf.data();
int total_bytes_read = 0;
int fill_sz = buf.size();
while (1) {
const int rd_bytes = read_atmost_n(fp, buffer, fill_sz);
if (rd_bytes == -1) { // Read finished
if (total_bytes_read == 0)
return -1;
break;
}
else if (rd_bytes == fill_sz) { // Buffer full
const auto orig_sz = buf.size();
const auto new_sz = orig_sz * 2;
buf.resize(new_sz);
fill_sz = new_sz - orig_sz;
//update the buffer pointer
buffer = buf.data();
buffer += rd_bytes;
total_bytes_read += rd_bytes;
} else { // Partial data ? Continue reading
total_bytes_read += rd_bytes;
fill_sz -= rd_bytes;
break;
}
}
return total_bytes_read;
}
#ifndef _MSC_VER
/*!
* Function: wait_for_child_exit
* Waits for the process with pid `pid` to exit
* and returns its status.
* Parameters:
* [in] pid : The pid of the process.
* [out] pair<int, int>:
* pair.first : Return code of the waitpid call.
* pair.second : Exit status of the process.
*
* NOTE: This is a blocking call as in, it will loop
* till the child is exited.
*/
static inline
std::pair<int, int> wait_for_child_exit(int pid)
{
int status = 0;
int ret = -1;
while (1) {
ret = waitpid(pid, &status, WNOHANG);
if (ret == -1) break;
if (ret == 0) continue;
return std::make_pair(ret, status); return std::make_pair(ret, status);
} }
return std::make_pair(ret, status);
}
#endif
}; // end namespace util }; // end namespace util
@ -511,16 +666,23 @@ struct input
input(int fd): rd_ch_(fd) {} input(int fd): rd_ch_(fd) {}
// FILE pointer. // FILE pointer.
input (FILE* fp):input(fileno(fp)) { assert(fp); } input(FILE* fp):input(_fileno(fp)) { assert(fp); }
input(const char* filename) { input(const char* filename) {
#ifdef _MSC_VER
#else
int fd = open(filename, O_RDONLY); int fd = open(filename, O_RDONLY);
if (fd == -1) throw OSError("File not found: ", errno); if (fd == -1)
throw OSError("File not found: ", errno);
rd_ch_ = fd; rd_ch_ = fd;
#endif
} }
input(IOTYPE typ) { input(IOTYPE typ) {
assert (typ == PIPE && "STDOUT/STDERR not allowed"); assert (typ == PIPE && "STDOUT/STDERR not allowed");
#ifdef _MSC_VER
#else
std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec();
#endif
} }
int rd_ch_ = -1; int rd_ch_ = -1;
@ -538,20 +700,26 @@ struct input
* Eg: output{PIPE} * Eg: output{PIPE}
* OR output{"output.txt"} * OR output{"output.txt"}
*/ */
struct output struct output{
{
output(int fd): wr_ch_(fd) {} output(int fd): wr_ch_(fd) {}
output (FILE* fp):output(fileno(fp)) { assert(fp); } output(FILE* fp):output(_fileno(fp)) { assert(fp); }
output(const char* filename) { output(const char* filename) {
#ifdef _MSC_VER
#else
int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640); int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640);
if (fd == -1) throw OSError("File not found: ", errno); if (fd == -1)
throw OSError("File not found: ", errno);
wr_ch_ = fd; wr_ch_ = fd;
#endif
} }
output(IOTYPE typ) { output(IOTYPE typ) {
assert (typ == PIPE && "STDOUT/STDERR not allowed"); assert(typ == PIPE && "STDOUT/STDERR not allowed");
#ifdef _MSC_VER
#else
std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec();
#endif
} }
int rd_ch_ = -1; int rd_ch_ = -1;
@ -571,21 +739,30 @@ struct error
{ {
error(int fd): wr_ch_(fd) {} error(int fd): wr_ch_(fd) {}
error(FILE* fp):error(fileno(fp)) { assert(fp); } error(FILE* fp) : error(_fileno(fp)) { assert(fp); }
error(const char* filename) { error(const char* filename)
{
#ifdef _MSC_VER
#else
int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640); int fd = open(filename, O_APPEND | O_CREAT | O_RDWR, 0640);
if (fd == -1) throw OSError("File not found: ", errno); if (fd == -1)
throw OSError("File not found: ", errno);
wr_ch_ = fd; wr_ch_ = fd;
#endif
} }
error(IOTYPE typ) { error(IOTYPE typ)
assert ((typ == PIPE || typ == STDOUT) && "STDERR not aloowed"); {
#ifdef _MSC_VER
#else
assert((typ == PIPE || typ == STDOUT) && "STDERR not aloowed");
if (typ == PIPE) { if (typ == PIPE) {
std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec();
} else { } else {
// Need to defer it till we have checked all arguments // Need to defer it till we have checked all arguments
deferred_ = true; deferred_ = true;
} }
#endif
} }
bool deferred_ = false; bool deferred_ = false;
@ -746,6 +923,8 @@ private:
Popen* popen_ = nullptr; Popen* popen_ = nullptr;
}; };
#ifdef _MSC_VER
#else
/*! /*!
* A helper class to Popen. * A helper class to Popen.
* This takes care of all the fork-exec logic * This takes care of all the fork-exec logic
@ -767,6 +946,7 @@ private:
Popen* parent_ = nullptr; Popen* parent_ = nullptr;
int err_wr_pipe_ = -1; int err_wr_pipe_ = -1;
}; };
#endif
// Fwd Decl. // Fwd Decl.
class Streams; class Streams;
@ -826,6 +1006,8 @@ public:
void cleanup_fds() void cleanup_fds()
{ {
#ifdef _MSC_VER
#else
if (write_to_child_ != -1 && read_from_parent_ != -1) { if (write_to_child_ != -1 && read_from_parent_ != -1) {
close(write_to_child_); close(write_to_child_);
} }
@ -835,23 +1017,30 @@ public:
if (err_write_ != -1 && err_read_ != -1) { if (err_write_ != -1 && err_read_ != -1) {
close(err_read_); close(err_read_);
} }
#endif
} }
void close_parent_fds() void close_parent_fds()
{ {
#ifdef _MSC_VER
#else
if (write_to_child_ != -1) close(write_to_child_); if (write_to_child_ != -1) close(write_to_child_);
if (read_from_child_ != -1) close(read_from_child_); if (read_from_child_ != -1) close(read_from_child_);
if (err_read_ != -1) close(err_read_); if (err_read_ != -1) close(err_read_);
#endif
} }
void close_child_fds() void close_child_fds()
{ {
#ifdef _MSC_VER
#else
if (write_to_parent_ != -1) close(write_to_parent_); if (write_to_parent_ != -1) close(write_to_parent_);
if (read_from_parent_ != -1) close(read_from_parent_); if (read_from_parent_ != -1) close(read_from_parent_);
if (err_write_ != -1) close(err_write_); if (err_write_ != -1) close(err_write_);
#endif
} }
FILE* input() { return input_.get(); } FILE* input() { return input_.get(); }
FILE* output() { return output_.get(); } FILE* output() { return output_.get(); }
FILE* error() { return error_.get(); } FILE* error() { return error_.get(); }
@ -863,7 +1052,7 @@ public:
void set_err_buf_cap(size_t cap) { comm_.set_err_buf_cap(cap); } void set_err_buf_cap(size_t cap) { comm_.set_err_buf_cap(cap); }
public: /* Communication forwarding API's */ public: /* Communication forwarding API's */
int send(const char* msg, size_t length) int send(const char* msg, size_t length)
{ return comm_.send(msg, length); } { return comm_.send(msg, length); }
int send(const std::vector<char>& msg) int send(const std::vector<char>& msg)
@ -882,6 +1071,14 @@ public:// Yes they are public
std::shared_ptr<FILE> output_ = nullptr; std::shared_ptr<FILE> output_ = nullptr;
std::shared_ptr<FILE> error_ = nullptr; std::shared_ptr<FILE> error_ = nullptr;
#ifdef _MSC_VER
HANDLE g_hChildStd_IN_Rd = nullptr;
HANDLE g_hChildStd_IN_Wr = nullptr;
HANDLE g_hChildStd_OUT_Rd = nullptr;
HANDLE g_hChildStd_OUT_Wr = nullptr;
HANDLE g_hChildStd_ERR_Rd = nullptr;
HANDLE g_hChildStd_ERR_Wr = nullptr;
#endif
// Buffer size for the IO streams // Buffer size for the IO streams
int bufsiz_ = 0; int bufsiz_ = 0;
@ -939,7 +1136,10 @@ class Popen
{ {
public: public:
friend struct detail::ArgumentDeducer; friend struct detail::ArgumentDeducer;
#ifdef _MSC_VER
#else
friend class detail::Child; friend class detail::Child;
#endif
template <typename... Args> template <typename... Args>
Popen(const std::string& cmd_args, Args&& ...args): Popen(const std::string& cmd_args, Args&& ...args):
@ -966,6 +1166,16 @@ public:
if (!defer_process_start_) execute_process(); if (!defer_process_start_) execute_process();
} }
template <typename... Args>
Popen(std::vector<std::string> vargs_, Args &&... args) : vargs_(vargs_)
{
this->init_args(std::forward<Args>(args)...);
// Setup the communication channels of the Popen class
this->stream_.setup_comm_channels();
if (!this->defer_process_start_)
this->execute_process();
}
void start_process() noexcept(false); void start_process() noexcept(false);
int pid() const noexcept { return child_pid_; } int pid() const noexcept { return child_pid_; }
@ -997,7 +1207,7 @@ public:
return res; return res;
} }
std::pair<OutBuffer, ErrBuffer> communicate(const std::vector<char>& msg) std::pair<OutBuffer, ErrBuffer> communicate(const std::vector<char>&msg)
{ {
auto res = stream_.communicate(msg); auto res = stream_.communicate(msg);
retcode_ = wait(); retcode_ = wait();
@ -1028,6 +1238,11 @@ private:
private: private:
detail::Streams stream_; detail::Streams stream_;
#ifdef _MSC_VER
HANDLE hProcess_;
std::shared_future<int> hExited_;
#endif
bool defer_process_start_ = false; bool defer_process_start_ = false;
bool close_fds_ = false; bool close_fds_ = false;
bool has_preexec_fn_ = false; bool has_preexec_fn_ = false;
@ -1088,7 +1303,10 @@ inline void Popen::start_process() noexcept(false)
inline int Popen::wait() noexcept(false) inline int Popen::wait() noexcept(false)
{ {
int ret, status; #ifdef _MSC_VER
return this->hExited_.get();
#else
int ret, status;
std::tie(ret, status) = util::wait_for_child_exit(pid()); std::tie(ret, status) = util::wait_for_child_exit(pid());
if (ret == -1) { if (ret == -1) {
if (errno != ECHILD) throw OSError("waitpid failed", errno); if (errno != ECHILD) throw OSError("waitpid failed", errno);
@ -1099,13 +1317,19 @@ inline int Popen::wait() noexcept(false)
else return 255; else return 255;
return 0; return 0;
#endif
} }
inline int Popen::poll() noexcept(false) inline int Popen::poll() noexcept(false)
{ {
int status; int status;
if (!child_created_) return -1; // TODO: ?? if (!child_created_) return -1; // TODO: ??
#ifdef _MSC_VER
if (!util::is_ready<int>(this->hExited_))
return 0;
return this->hExited_.get();
#else
// Returns zero if child is still running // Returns zero if child is still running
int ret = waitpid(child_pid_, &status, WNOHANG); int ret = waitpid(child_pid_, &status, WNOHANG);
if (ret == 0) return -1; if (ret == 0) return -1;
@ -1134,17 +1358,133 @@ inline int Popen::poll() noexcept(false)
} }
return retcode_; return retcode_;
#endif
} }
inline void Popen::kill(int sig_num) inline void Popen::kill(int sig_num)
{ {
#ifdef _MSC_VER
if (!TerminateProcess(this->hProcess_, (UINT)sig_num)) {
throw OSError("TerminateProcess", 0);
}
#else
if (session_leader_) killpg(child_pid_, sig_num); if (session_leader_) killpg(child_pid_, sig_num);
else ::kill(child_pid_, sig_num); else ::kill(child_pid_, sig_num);
#endif
} }
inline void Popen::execute_process() noexcept(false) inline void Popen::execute_process() noexcept(false)
{ {
#ifdef _MSC_VER
if (this->shell_) {
throw OSError("Shell", 0);
/*
auto new_cmd = util::join(vargs_);
this->vargs_.clear();
this->vargs_.insert(vargs_.begin(), {"/bin/sh", "-c"});
this->vargs_.push_back(new_cmd);
this->populate_c_argv();
*/
}
if (exe_name_.length()) {
this->vargs_.insert(this->vargs_.begin(), this->exe_name_);
this->populate_c_argv();
}
this->exe_name_ = vargs_[0];
// Create a child process that uses the previously created pipes for STDIN and
// STDOUT.
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::wstring argument;
std::wstring commandLine;
for (auto arg : this->vargs_) {
argument = converter.from_bytes(arg);
util::quote_argument(argument, commandLine, true);
commandLine += L" ";
}
// std::wcout << commandLine << std::endl;
// CreateProcessW can modify szCmdLine so we allocate needed memory
wchar_t *szCmdline = new wchar_t[commandLine.size() + 1];
wcscpy_s(szCmdline, commandLine.size() + 1, commandLine.c_str());
PROCESS_INFORMATION piProcInfo;
STARTUPINFOW siStartInfo;
BOOL bSuccess = FALSE;
// Set up members of the PROCESS_INFORMATION structure.
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
// Set up members of the STARTUPINFOW structure.
// This structure specifies the STDIN and STDOUT handles for redirection.
ZeroMemory(&siStartInfo, sizeof(STARTUPINFOW));
siStartInfo.cb = sizeof(STARTUPINFOW);
siStartInfo.hStdError = this->stream_.g_hChildStd_OUT_Wr;
/* siStartInfo.hStdError = this->stream_.g_hChildStd_ERR_Wr; */
siStartInfo.hStdOutput = this->stream_.g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = this->stream_.g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// TOOD: Another thread may be required
// Create the child process.
bSuccess = CreateProcessW(NULL,
szCmdline, // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFOW pointer
&piProcInfo); // receives PROCESS_INFORMATION
// If an error occurs, exit the application.
if (!bSuccess)
throw OSError("CreateProcess failed", 0);
CloseHandle(piProcInfo.hThread);
this->hProcess_ = piProcInfo.hProcess;
this->hExited_ =
std::shared_future<int>(std::async(std::launch::async, [this] {
WaitForSingleObject(this->hProcess_, INFINITE);
CloseHandle(this->stream_.g_hChildStd_ERR_Wr);
CloseHandle(this->stream_.g_hChildStd_OUT_Wr);
CloseHandle(this->stream_.g_hChildStd_IN_Rd);
DWORD exit_code;
if (FALSE == GetExitCodeProcess(this->hProcess_, &exit_code))
throw OSError("GetExitCodeProcess", 0);
CloseHandle(this->hProcess_);
return (int)exit_code;
}));
// char buf[1024];
// while (fgets(buf, sizeof(buf), this->stream_.output_.get()) != NULL) {
// buf[strlen(buf) - 1] = '\0'; // eat the newline fgets() stores
// printf("%s\n", buf);
// }
// else {
// // Close handles to the child process and its primary thread.
// // Some applications might keep these handles to monitor the status
// // of the child process, for example.
// CloseHandle(piProcInfo.hProcess);
// }
#else
int err_rd_pipe, err_wr_pipe; int err_rd_pipe, err_wr_pipe;
std::tie(err_rd_pipe, err_wr_pipe) = util::pipe_cloexec(); std::tie(err_rd_pipe, err_wr_pipe) = util::pipe_cloexec();
@ -1172,7 +1512,7 @@ inline void Popen::execute_process() noexcept(false)
child_created_ = true; child_created_ = true;
if (child_pid_ == 0) if (child_pid_ == 0)
{ {
// Close descriptors belonging to parent // Close descriptors belonging to parent
stream_.close_parent_fds(); stream_.close_parent_fds();
@ -1213,6 +1553,7 @@ inline void Popen::execute_process() noexcept(false)
} }
} }
#endif
} }
namespace detail { namespace detail {
@ -1277,6 +1618,9 @@ namespace detail {
} }
#ifdef _MSC_VER
#else
inline void Child::execute_child() { inline void Child::execute_child() {
int sys_ret = -1; int sys_ret = -1;
auto& stream = parent_->stream_; auto& stream = parent_->stream_;
@ -1374,8 +1718,55 @@ namespace detail {
} }
inline void Streams::setup_comm_channels() #endif
{
inline void Streams::setup_comm_channels()
{
#ifdef _MSC_VER
SECURITY_ATTRIBUTES saAttr;
// Set the bInheritHandle flag so pipe handles are inherited.
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDIN.
if (!CreatePipe(&this->g_hChildStd_IN_Rd, &this->g_hChildStd_IN_Wr, &saAttr,
0))
throw OSError("Stdin CreatePipe", 0);
// Ensure the write handle to the pipe for STDIN is not inherited.
if (!SetHandleInformation(this->g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
throw OSError("Stdin SetHandleInformation", 0);
this->input(util::file_from_handle(this->g_hChildStd_IN_Wr, "w"));
this->write_to_child_ = _fileno(this->input());
// Create a pipe for the child process's STDOUT.
if (!CreatePipe(&this->g_hChildStd_OUT_Rd, &this->g_hChildStd_OUT_Wr, &saAttr,
0))
throw OSError("StdoutRd CreatePipe", 0);
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!SetHandleInformation(this->g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
throw OSError("Stdout SetHandleInformation", 0);
this->output(util::file_from_handle(this->g_hChildStd_OUT_Rd, "r"));
this->read_from_child_ = _fileno(this->output());
// Create a pipe for the child process's STDERR.
if (!CreatePipe(&this->g_hChildStd_ERR_Rd, &this->g_hChildStd_ERR_Wr, &saAttr,
0))
throw OSError("StdERRRd CreatePipe", 0);
// Ensure the read handle to the pipe for STDERR is not inherited.
if (!SetHandleInformation(this->g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0))
throw OSError("StdERR SetHandleInformation", 0);
this->error(util::file_from_handle(this->g_hChildStd_ERR_Rd, "r"));
this->err_read_ = _fileno(this->error());
#else
if (write_to_child_ != -1) input(fdopen(write_to_child_, "wb")); if (write_to_child_ != -1) input(fdopen(write_to_child_, "wb"));
if (read_from_child_ != -1) output(fdopen(read_from_child_, "rb")); if (read_from_child_ != -1) output(fdopen(read_from_child_, "rb"));
if (err_read_ != -1) error(fdopen(err_read_, "rb")); if (err_read_ != -1) error(fdopen(err_read_, "rb"));
@ -1395,10 +1786,11 @@ namespace detail {
setvbuf(h, nullptr, _IOFBF, bufsiz_); setvbuf(h, nullptr, _IOFBF, bufsiz_);
}; };
} }
} #endif
}
inline int Communication::send(const char* msg, size_t length) inline int Communication::send(const char *msg, size_t length)
{ {
if (stream_->input() == nullptr) return -1; if (stream_->input() == nullptr) return -1;
return std::fwrite(msg, sizeof(char), length, stream_->input()); return std::fwrite(msg, sizeof(char), length, stream_->input());
} }
@ -1439,7 +1831,7 @@ namespace detail {
obuf.add_cap(out_buf_cap_); obuf.add_cap(out_buf_cap_);
int rbytes = util::read_all( int rbytes = util::read_all(
fileno(stream_->output()), stream_->output(),
obuf.buf); obuf.buf);
if (rbytes == -1) { if (rbytes == -1) {
@ -1455,7 +1847,7 @@ namespace detail {
ebuf.add_cap(err_buf_cap_); ebuf.add_cap(err_buf_cap_);
int rbytes = util::read_atmost_n( int rbytes = util::read_atmost_n(
fileno(stream_->error()), stream_->error(),
ebuf.buf.data(), ebuf.buf.data(),
ebuf.buf.size()); ebuf.buf.size());
@ -1471,12 +1863,12 @@ namespace detail {
} }
return communicate_threaded(msg, length); return communicate_threaded(msg, length);
} }
inline std::pair<OutBuffer, ErrBuffer> inline std::pair<OutBuffer, ErrBuffer>
Communication::communicate_threaded(const char* msg, size_t length) Communication::communicate_threaded(const char* msg, size_t length)
{ {
OutBuffer obuf; OutBuffer obuf;
ErrBuffer ebuf; ErrBuffer ebuf;
std::future<int> out_fut, err_fut; std::future<int> out_fut, err_fut;
@ -1487,7 +1879,7 @@ namespace detail {
out_fut = std::async(std::launch::async, out_fut = std::async(std::launch::async,
[&obuf, this] { [&obuf, this] {
return util::read_all(fileno(this->stream_->output()), obuf.buf); return util::read_all(this->stream_->output(), obuf.buf);
}); });
} }
if (stream_->error()) { if (stream_->error()) {
@ -1495,7 +1887,7 @@ namespace detail {
err_fut = std::async(std::launch::async, err_fut = std::async(std::launch::async,
[&ebuf, this] { [&ebuf, this] {
return util::read_all(fileno(this->stream_->error()), ebuf.buf); return util::read_all(this->stream_->error(), ebuf.buf);
}); });
} }
if (stream_->input()) { if (stream_->input()) {
@ -1507,6 +1899,9 @@ namespace detail {
} }
} }
} }
#ifdef _MSC_VER
fclose(stream_->input());
#endif
stream_->input_.reset(); stream_->input_.reset();
} }

8
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,8 @@
add_executable(test_subprocess test_subprocess.cc)
add_test(
NAME test_subprocess
COMMAND $<TARGET_FILE:test_subprocess>
)

View File

@ -1,19 +1,27 @@
#include <iostream>
#include "../subprocess.hpp" #include "../subprocess.hpp"
#include <iostream>
using namespace subprocess; using namespace subprocess;
void test_exename() void test_exename()
{ {
#ifdef _MSC_VER
auto ret = call({"--version"}, executable{"cmake"}, shell{false});
#else
auto ret = call({"-l"}, executable{"ls"}, shell{false}); auto ret = call({"-l"}, executable{"ls"}, shell{false});
#endif
std::cout << ret << std::endl; std::cout << ret << std::endl;
} }
void test_input() void test_input()
{ {
#if 0
auto p = Popen({"cmake", "--version"}, output{PIPE}, input{PIPE});
#else
auto p = Popen({"grep", "f"}, output{PIPE}, input{PIPE}); auto p = Popen({"grep", "f"}, output{PIPE}, input{PIPE});
const char* msg = "one\ntwo\nthree\nfour\nfive\n"; const char *msg = "one\ntwo\nthree\nfour\nfive\n";
p.send(msg, strlen(msg)); p.send(msg, strlen(msg));
#endif
auto res = p.communicate(nullptr, 0); auto res = p.communicate(nullptr, 0);
std::cout << res.first.buf.data() << std::endl; std::cout << res.first.buf.data() << std::endl;
} }
@ -22,7 +30,8 @@ void test_piping()
{ {
auto cat = Popen({"cat", "../subprocess.hpp"}, output{PIPE}); auto cat = Popen({"cat", "../subprocess.hpp"}, output{PIPE});
auto grep = Popen({"grep", "template"}, input{cat.output()}, output{PIPE}); auto grep = Popen({"grep", "template"}, input{cat.output()}, output{PIPE});
auto cut = Popen({"cut", "-d,", "-f", "1"}, input{grep.output()}, output{PIPE}); auto cut =
Popen({"cut", "-d,", "-f", "1"}, input{grep.output()}, output{PIPE});
auto res = cut.communicate().first; auto res = cut.communicate().first;
std::cout << res.buf.data() << std::endl; std::cout << res.buf.data() << std::endl;
} }
@ -45,7 +54,10 @@ void test_sleep()
while (p.poll() == -1) { while (p.poll() == -1) {
std::cout << "Waiting..." << std::endl; std::cout << "Waiting..." << std::endl;
#ifdef _MSC_VER
#else
sleep(1); sleep(1);
#endif
} }
std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl; std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl;
@ -56,7 +68,7 @@ void test_read_all()
Popen p = Popen({"echo","12345678"}, output{PIPE}); Popen p = Popen({"echo","12345678"}, output{PIPE});
std::vector<char> buf(6); std::vector<char> buf(6);
int rbytes = util::read_all(fileno(p.output()), buf); int rbytes = util::read_all(p.output(), buf);
std::string out(buf.begin(), buf.end()); std::string out(buf.begin(), buf.end());