diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 9173598..0000000 --- a/.clang-format +++ /dev/null @@ -1 +0,0 @@ -BreakBeforeBraces: Stroustrup \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7a15c88..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build* -.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index d4b5583..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -cmake_minimum_required(VERSION 3.9) - -project(subprocess CXX) - -enable_testing() -add_subdirectory(test) \ No newline at end of file diff --git a/subprocess.hpp b/subprocess.hpp index 48f081c..b22a00a 100755 --- a/subprocess.hpp +++ b/subprocess.hpp @@ -34,35 +34,28 @@ Documentation for C++ subprocessing libraray. #ifndef SUBPROCESS_HPP #define SUBPROCESS_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include extern "C" { -#ifdef _MSC_VER -#include -#include -#else -#include -#include -#endif -#include -#include -#include + #include + #include + #include + #include + #include } /*! @@ -116,7 +109,7 @@ class CalledProcessError: public std::runtime_error { public: CalledProcessError(const std::string& error_msg): - std::runtime_error(error_msg) + std::runtime_error(error_msg) {} }; @@ -135,7 +128,7 @@ class OSError: public std::runtime_error { public: OSError(const std::string& err_msg, int err_code): - std::runtime_error( err_msg + " : " + std::strerror(err_code) ) + std::runtime_error( err_msg + " : " + std::strerror(err_code) ) {} }; @@ -144,170 +137,56 @@ public: namespace util { -/*! - * Function: split - * Parameters: - * [in] str : Input string which needs to be split based upon the - * delimiters provided. - * [in] deleims : Delimiter characters based upon which the string needs - * to be split. Default constructed to ' '(space) and '\t'(tab) - * [out] vector : Vector of strings split at deleimiter. - */ -static inline std::vector -split(const std::string& str, const std::string& delims=" \t") -{ - std::vector res; - size_t init = 0; + /*! + * Function: split + * Parameters: + * [in] str : Input string which needs to be split based upon the + * delimiters provided. + * [in] deleims : Delimiter characters based upon which the string needs + * to be split. Default constructed to ' '(space) and '\t'(tab) + * [out] vector : Vector of strings split at deleimiter. + */ + static inline std::vector + split(const std::string& str, const std::string& delims=" \t") + { + std::vector res; + size_t init = 0; - while (true) { - auto pos = str.find_first_of(delims, init); - if (pos == std::string::npos) { - res.emplace_back(str.substr(init, str.length())); - break; - } - res.emplace_back(str.substr(init, pos - init)); - pos++; - init = pos; - } - - return res; -} - - -/*! - * Function: join - * Parameters: - * [in] vec : Vector of strings which needs to be joined to form - * a single string with words seperated by a seperator char. - * [in] sep : String used to seperate 2 words in the joined string. - * Default constructed to ' ' (space). - * [out] string: Joined string. - */ -static inline -std::string join(const std::vector& vec, - const std::string& sep = " ") -{ - std::string res; - for (auto& elem : vec) res.append(elem + sep); - res.erase(--res.end()); - return res; -} - - -#ifdef _MSC_VER -template bool is_ready(std::shared_future 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'\\'); + while (true) { + auto pos = str.find_first_of(delims, init); + if (pos == std::string::npos) { + res.emplace_back(str.substr(init, str.length())); 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); - } + res.emplace_back(str.substr(init, pos - init)); + pos++; + init = pos; } - 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); + return res; } - int os_fhandle = _open_osfhandle((intptr_t)h, md); - if (os_fhandle == -1) { - CloseHandle(h); - throw OSError("_open_osfhandle", 0); + + /*! + * Function: join + * Parameters: + * [in] vec : Vector of strings which needs to be joined to form + * a single string with words seperated by a seperator char. + * [in] sep : String used to seperate 2 words in the joined string. + * Default constructed to ' ' (space). + * [out] string: Joined string. + */ + static inline + std::string join(const std::vector& vec, + const std::string& sep = " ") + { + std::string res; + for (auto& elem : vec) res.append(elem + sep); + res.erase(--res.end()); + return res; } - FILE *fp = _fdopen(os_fhandle, mode); - if (fp == 0) { - _close(os_fhandle); - throw OSError("_fdopen", 0); - } - return fp; -} -#else /*! * Function: set_clo_on_exec * Sets/Resets the FD_CLOEXEC flag on the provided file descriptor @@ -377,126 +256,92 @@ FILE *file_from_handle(HANDLE h, const char *mode) } -#endif + /*! + * Function: read_atmost_n + * Reads at the most `read_upto` bytes from the + * file descriptor `fd` before returning. + * Parameters: + * [in] fd : The file descriptor from which it needs to read. + * [in] buf : The buffer into which it needs to write the data. + * [in] read_upto: Max number of bytes which must be read from `fd`. + * [out] int : Number of bytes written to `buf` or read from `fd` + * OR -1 in case of error. + * NOTE: In case of EINTR while reading from socket, this API + * will retry to read from `fd`, but only till the EINTR counter + * reaches 50 after which it will return with whatever data it read. + */ + static inline + int read_atmost_n(int fd, char* buf, size_t read_upto) + { + int rbytes = 0; + int eintr_cnter = 0; -/*! - * Function: read_atmost_n - * Reads at the most `read_upto` bytes from the - * file descriptor `fd` before returning. - * Parameters: - * [in] fd : The file descriptor from which it needs to read. - * [in] buf : The buffer into which it needs to write the data. - * [in] read_upto: Max number of bytes which must be read from `fd`. - * [out] int : Number of bytes written to `buf` or read from `fd` - * OR -1 in case of error. - * NOTE: In case of EINTR while reading from socket, this API - * will retry to read from `fd`, but only till the EINTR counter - * reaches 50 after which it will return with whatever data it read. - */ -static inline -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 eintr_cnter = 0; - - while (1) { - int read_bytes = read(fd, buf + rbytes, read_upto - rbytes); - if (read_bytes == -1) { - if (errno == EINTR) { - if (eintr_cnter >= 50) - return -1; - eintr_cnter++; - continue; + while (1) { + int read_bytes = read(fd, buf + rbytes, read_upto - rbytes); + if (read_bytes == -1) { + if (errno == EINTR) { + if (eintr_cnter >= 50) return -1; + eintr_cnter++; + continue; + } + 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& 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; } - return -1; } buf.erase(buf.begin()+total_bytes_read, buf.end()); // remove extra nulls return total_bytes_read; } - /*! - * 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: - * 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 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); - } - - 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 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 @@ -525,7 +370,7 @@ template static inline int read_all(FILE *fp, Buffer &buf) return std::make_pair(ret, status); } -#endif + }; // end namespace util @@ -666,23 +511,16 @@ struct input input(int fd): rd_ch_(fd) {} // FILE pointer. - input(FILE* fp):input(_fileno(fp)) { assert(fp); } + input (FILE* fp):input(fileno(fp)) { assert(fp); } input(const char* filename) { -#ifdef _MSC_VER -#else 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; -#endif } input(IOTYPE typ) { assert (typ == PIPE && "STDOUT/STDERR not allowed"); -#ifdef _MSC_VER -#else std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); -#endif } int rd_ch_ = -1; @@ -700,26 +538,20 @@ struct input * Eg: output{PIPE} * OR output{"output.txt"} */ -struct output{ +struct output +{ 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) { -#ifdef _MSC_VER -#else 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; -#endif } output(IOTYPE typ) { - assert(typ == PIPE && "STDOUT/STDERR not allowed"); -#ifdef _MSC_VER -#else + assert (typ == PIPE && "STDOUT/STDERR not allowed"); std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); -#endif } int rd_ch_ = -1; @@ -739,30 +571,21 @@ struct error { 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) - { -#ifdef _MSC_VER -#else + error(const char* filename) { 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; -#endif } - error(IOTYPE typ) - { -#ifdef _MSC_VER -#else - assert((typ == PIPE || typ == STDOUT) && "STDERR not aloowed"); + error(IOTYPE typ) { + assert ((typ == PIPE || typ == STDOUT) && "STDERR not aloowed"); if (typ == PIPE) { std::tie(rd_ch_, wr_ch_) = util::pipe_cloexec(); } else { // Need to defer it till we have checked all arguments deferred_ = true; } -#endif } bool deferred_ = false; @@ -889,7 +712,7 @@ struct has_type> { template struct has_type> { static constexpr bool value = - std::is_same::type>::value ? true : has_type>::value; + std::is_same::type>::value ? true : has_type>::value; }; //---- @@ -923,8 +746,6 @@ private: Popen* popen_ = nullptr; }; -#ifdef _MSC_VER -#else /*! * A helper class to Popen. * This takes care of all the fork-exec logic @@ -946,7 +767,6 @@ private: Popen* parent_ = nullptr; int err_wr_pipe_ = -1; }; -#endif // Fwd Decl. class Streams; @@ -1006,8 +826,6 @@ public: void cleanup_fds() { -#ifdef _MSC_VER -#else if (write_to_child_ != -1 && read_from_parent_ != -1) { close(write_to_child_); } @@ -1017,30 +835,23 @@ public: if (err_write_ != -1 && err_read_ != -1) { close(err_read_); } -#endif } void close_parent_fds() { -#ifdef _MSC_VER -#else if (write_to_child_ != -1) close(write_to_child_); if (read_from_child_ != -1) close(read_from_child_); if (err_read_ != -1) close(err_read_); -#endif } void close_child_fds() { -#ifdef _MSC_VER -#else if (write_to_parent_ != -1) close(write_to_parent_); if (read_from_parent_ != -1) close(read_from_parent_); if (err_write_ != -1) close(err_write_); -#endif } -FILE* input() { return input_.get(); } + FILE* input() { return input_.get(); } FILE* output() { return output_.get(); } FILE* error() { return error_.get(); } @@ -1052,7 +863,7 @@ FILE* input() { return input_.get(); } void set_err_buf_cap(size_t cap) { comm_.set_err_buf_cap(cap); } 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); } int send(const std::vector& msg) @@ -1071,14 +882,6 @@ public:// Yes they are public std::shared_ptr output_ = nullptr; std::shared_ptr 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 int bufsiz_ = 0; @@ -1136,10 +939,7 @@ class Popen { public: friend struct detail::ArgumentDeducer; -#ifdef _MSC_VER -#else friend class detail::Child; -#endif template Popen(const std::string& cmd_args, Args&& ...args): @@ -1166,16 +966,6 @@ public: if (!defer_process_start_) execute_process(); } - template - Popen(std::vector vargs_, Args &&... args) : vargs_(vargs_) - { - this->init_args(std::forward(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); int pid() const noexcept { return child_pid_; } @@ -1207,7 +997,7 @@ public: return res; } - std::pair communicate(const std::vector&msg) + std::pair communicate(const std::vector& msg) { auto res = stream_.communicate(msg); retcode_ = wait(); @@ -1238,11 +1028,6 @@ private: private: detail::Streams stream_; -#ifdef _MSC_VER - HANDLE hProcess_; - std::shared_future hExited_; -#endif - bool defer_process_start_ = false; bool close_fds_ = false; bool has_preexec_fn_ = false; @@ -1303,10 +1088,7 @@ inline void Popen::start_process() noexcept(false) inline int Popen::wait() noexcept(false) { -#ifdef _MSC_VER - return this->hExited_.get(); -#else -int ret, status; + int ret, status; std::tie(ret, status) = util::wait_for_child_exit(pid()); if (ret == -1) { if (errno != ECHILD) throw OSError("waitpid failed", errno); @@ -1317,19 +1099,13 @@ int ret, status; else return 255; return 0; -#endif } inline int Popen::poll() noexcept(false) { int status; if (!child_created_) return -1; // TODO: ?? -#ifdef _MSC_VER - if (!util::is_ready(this->hExited_)) - return 0; - return this->hExited_.get(); -#else // Returns zero if child is still running int ret = waitpid(child_pid_, &status, WNOHANG); if (ret == 0) return -1; @@ -1358,133 +1134,17 @@ inline int Popen::poll() noexcept(false) } return retcode_; -#endif } 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); else ::kill(child_pid_, sig_num); -#endif } 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> 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(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; std::tie(err_rd_pipe, err_wr_pipe) = util::pipe_cloexec(); @@ -1512,7 +1172,7 @@ inline void Popen::execute_process() noexcept(false) child_created_ = true; -if (child_pid_ == 0) + if (child_pid_ == 0) { // Close descriptors belonging to parent stream_.close_parent_fds(); @@ -1553,7 +1213,6 @@ if (child_pid_ == 0) } } -#endif } namespace detail { @@ -1618,9 +1277,6 @@ namespace detail { } - -#ifdef _MSC_VER -#else inline void Child::execute_child() { int sys_ret = -1; auto& stream = parent_->stream_; @@ -1718,79 +1374,31 @@ namespace detail { } -#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 - + inline void Streams::setup_comm_channels() + { if (write_to_child_ != -1) input(fdopen(write_to_child_, "wb")); if (read_from_child_ != -1) output(fdopen(read_from_child_, "rb")); if (err_read_ != -1) error(fdopen(err_read_, "rb")); - auto handles = {input(), output(), error()}; + auto handles = {input(), output(), error()}; - for (auto& h : handles) { - if (h == nullptr) continue; - switch (bufsiz_) { - case 0: - setvbuf(h, nullptr, _IONBF, BUFSIZ); - break; - case 1: - setvbuf(h, nullptr, _IONBF, BUFSIZ); - break; - default: - setvbuf(h, nullptr, _IOFBF, bufsiz_); - }; + for (auto& h : handles) { + if (h == nullptr) continue; + switch (bufsiz_) { + case 0: + setvbuf(h, nullptr, _IONBF, BUFSIZ); + break; + case 1: + setvbuf(h, nullptr, _IONBF, BUFSIZ); + break; + default: + 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; return std::fwrite(msg, sizeof(char), length, stream_->input()); } @@ -1825,61 +1433,61 @@ inline int Communication::send(const char *msg, size_t length) // Close the input stream stream_->input_.reset(); } else if (stream_->output()) { - // Read till EOF - // ATTN: This could be blocking, if the process - // at the other end screws up, we get screwed as well - obuf.add_cap(out_buf_cap_); + // Read till EOF + // ATTN: This could be blocking, if the process + // at the other end screws up, we get screwed as well + obuf.add_cap(out_buf_cap_); - int rbytes = util::read_all( - stream_->output(), + int rbytes = util::read_all( + fileno(stream_->output()), obuf.buf); - if (rbytes == -1) { - throw OSError("read to obuf failed", errno); - } + if (rbytes == -1) { + throw OSError("read to obuf failed", errno); + } - obuf.length = rbytes; - // Close the output stream - stream_->output_.reset(); + obuf.length = rbytes; + // Close the output stream + stream_->output_.reset(); - } else if (stream_->error()) { - // Same screwness applies here as well - ebuf.add_cap(err_buf_cap_); + } else if (stream_->error()) { + // Same screwness applies here as well + ebuf.add_cap(err_buf_cap_); - int rbytes = util::read_atmost_n( - stream_->error(), + int rbytes = util::read_atmost_n( + fileno(stream_->error()), ebuf.buf.data(), ebuf.buf.size()); - if (rbytes == -1) { - throw OSError("read to ebuf failed", errno); - } + if (rbytes == -1) { + throw OSError("read to ebuf failed", errno); + } - ebuf.length = rbytes; - // Close the error stream - stream_->error_.reset(); + ebuf.length = rbytes; + // Close the error stream + stream_->error_.reset(); + } + return std::make_pair(std::move(obuf), std::move(ebuf)); } - return std::make_pair(std::move(obuf), std::move(ebuf)); + + return communicate_threaded(msg, length); } - return communicate_threaded(msg, length); -} + inline std::pair + Communication::communicate_threaded(const char* msg, size_t length) + { + OutBuffer obuf; + ErrBuffer ebuf; + std::future out_fut, err_fut; + const int length_conv = length; -inline std::pair -Communication::communicate_threaded(const char* msg, size_t length) -{ - OutBuffer obuf; - ErrBuffer ebuf; - std::future out_fut, err_fut; - const int length_conv = length; - - if (stream_->output()) { + if (stream_->output()) { obuf.add_cap(out_buf_cap_); out_fut = std::async(std::launch::async, [&obuf, this] { - return util::read_all(this->stream_->output(), obuf.buf); + return util::read_all(fileno(this->stream_->output()), obuf.buf); }); } if (stream_->error()) { @@ -1887,7 +1495,7 @@ Communication::communicate_threaded(const char* msg, size_t length) err_fut = std::async(std::launch::async, [&ebuf, this] { - return util::read_all(this->stream_->error(), ebuf.buf); + return util::read_all(fileno(this->stream_->error()), ebuf.buf); }); } if (stream_->input()) { @@ -1899,11 +1507,8 @@ Communication::communicate_threaded(const char* msg, size_t length) } } } -#ifdef _MSC_VER - fclose(stream_->input()); -#endif - stream_->input_.reset(); - } + stream_->input_.reset(); + } if (out_fut.valid()) { int res = out_fut.get(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index f7dffa7..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ - - -add_executable(test_subprocess test_subprocess.cc) - -add_test( - NAME test_subprocess - COMMAND $ -) \ No newline at end of file diff --git a/test/test_subprocess.cc b/test/test_subprocess.cc index cb3141a..a1fc111 100755 --- a/test/test_subprocess.cc +++ b/test/test_subprocess.cc @@ -1,27 +1,19 @@ -#include "../subprocess.hpp" #include +#include "../subprocess.hpp" using namespace subprocess; void test_exename() { -#ifdef _MSC_VER - auto ret = call({"--version"}, executable{"cmake"}, shell{false}); -#else auto ret = call({"-l"}, executable{"ls"}, shell{false}); -#endif std::cout << ret << std::endl; } void test_input() { -#if 0 - auto p = Popen({"cmake", "--version"}, output{PIPE}, input{PIPE}); -#else 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)); -#endif auto res = p.communicate(nullptr, 0); std::cout << res.first.buf.data() << std::endl; } @@ -30,8 +22,7 @@ void test_piping() { auto cat = Popen({"cat", "../subprocess.hpp"}, 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; std::cout << res.buf.data() << std::endl; } @@ -54,10 +45,7 @@ void test_sleep() while (p.poll() == -1) { std::cout << "Waiting..." << std::endl; -#ifdef _MSC_VER -#else sleep(1); -#endif } std::cout << "Sleep ended: ret code = " << p.retcode() << std::endl; @@ -68,7 +56,7 @@ void test_read_all() Popen p = Popen({"echo","12345678"}, output{PIPE}); std::vector buf(6); - int rbytes = util::read_all(p.output(), buf); + int rbytes = util::read_all(fileno(p.output()), buf); std::string out(buf.begin(), buf.end());