├── LICENSE ├── README.md ├── SCsub ├── config.py ├── register_types.cpp ├── register_types.h ├── std_io.cpp ├── std_io.h └── tiny-process-library ├── .clang-format ├── LICENSE ├── README.md ├── examples.cpp ├── process.cpp ├── process.hpp ├── process_unix.cpp └── process_win.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Oded Streigold 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot-std_io 2 | A Godot game engine module that facilitates running external processes and communicating with them via standard I/O 3 | -------------------------------------------------------------------------------- /SCsub: -------------------------------------------------------------------------------- 1 | Import('env') 2 | 3 | env.add_source_files(env.modules_sources, "*.cpp") # Add all cpp files to the build 4 | 5 | #env.add_source_files(env.modules_sources, "tiny-process-library/*.cpp") # Add all cpp files to the build 6 | 7 | env.add_source_files(env.modules_sources, "tiny-process-library/process.cpp") # Add all cpp files to the build 8 | 9 | env.add_source_files(env.modules_sources, "tiny-process-library/process_unix.cpp") # Add all cpp files to the build 10 | 11 | 12 | 13 | env.Append(CPPPATH=["tiny-process-library"]) # this is a relative path 14 | 15 | 16 | #src_list = ["summator.cpp", "other.cpp", "etc.cpp"] 17 | #env.add_source_files(env.modules_sources, src_list) 18 | 19 | #env.Append(CPPPATH=["mylib/include"]) # this is a relative path 20 | #env.Append(CPPPATH=["#myotherlib/include"]) # this is an 'absolute' path 21 | 22 | 23 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | 3 | def can_build(env, platform): 4 | return True 5 | 6 | def configure(env): 7 | pass 8 | 9 | -------------------------------------------------------------------------------- /register_types.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // register_types.cpp 3 | // std_io 4 | // 5 | // Created by Oded Streigold on 4/18/20. 6 | // 7 | 8 | #include "register_types.h" 9 | 10 | #include "register_types.h" 11 | 12 | #include "core/class_db.h" 13 | #include "std_io.h" 14 | 15 | 16 | 17 | 18 | void register_std_io_types() { 19 | ClassDB::register_class(); 20 | } 21 | 22 | void unregister_std_io_types() { 23 | // Nothing to do here in this example. 24 | } 25 | -------------------------------------------------------------------------------- /register_types.h: -------------------------------------------------------------------------------- 1 | // 2 | // register_types.h 3 | // std_io 4 | // 5 | // Created by Oded Streigold on 4/18/20. 6 | // 7 | 8 | #ifndef register_types_h 9 | #define register_types_h 10 | 11 | void register_std_io_types(); 12 | void unregister_std_io_types(); 13 | /* yes, the word in the middle must be the same as the module folder name */ 14 | 15 | 16 | #endif /* register_types_h */ 17 | -------------------------------------------------------------------------------- /std_io.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // std_io.cpp 3 | // std_io 4 | // 5 | // Created by Oded Streigold on 4/18/20. 6 | // 7 | 8 | #include "std_io.h" 9 | 10 | #include 11 | #include 12 | 13 | 14 | 15 | int STD_IO::start_process(String path_godot_str) 16 | { 17 | std::wstring path_wstring = path_godot_str.c_str(); 18 | std::string path_string( path_wstring.begin(), path_wstring.end() ); 19 | 20 | if(this->process != nullptr) 21 | { 22 | stop_process(); 23 | } 24 | 25 | this->process = new Process(path_string, "", 26 | [this](const char *bytes, size_t n) 27 | { 28 | //cout << "Output from stdout: " << string(bytes, n); 29 | string output_str(bytes, n); 30 | 31 | String output_godot_str( output_str.c_str() ); 32 | Variant v(output_godot_str); 33 | 34 | Variant** args = new Variant*[1]; 35 | args[0] = {&v}; 36 | 37 | Variant::CallError err; 38 | this->godot_output_callback->call_func( (const Variant**)args, 1, err); 39 | 40 | delete[] args; 41 | }, 42 | [this](const char *bytes, size_t n) 43 | { 44 | //cout << "Output from stderr: " << string(bytes, n); 45 | }, true); 46 | 47 | return 0; 48 | } 49 | 50 | void STD_IO::set_stdout_callback(Ref callback ) 51 | { 52 | this->godot_output_callback = callback ; 53 | } 54 | 55 | 56 | std::string STD_IO::godot_string_std_string(String godot_string) 57 | { 58 | std::wstring std_wstring = godot_string.c_str(); 59 | std::string std_string( std_wstring.begin(), std_wstring.end() ); 60 | return std_string; 61 | } 62 | 63 | 64 | int STD_IO::send_command(String command){ 65 | if(this->process == nullptr) 66 | return -1; 67 | std::string std_str_command = godot_string_std_string(command); 68 | this->process->write(std_str_command + "\n"); 69 | return 0; 70 | 71 | } 72 | 73 | int STD_IO::stop_process(){ 74 | if(this->process == nullptr) 75 | return -1; 76 | this->process->kill(); 77 | delete(this->process); 78 | this->process = nullptr; 79 | return 0; 80 | } 81 | 82 | void STD_IO::_bind_methods() { 83 | 84 | ClassDB::bind_method(D_METHOD("set_stdout_callback", "callback"), &STD_IO::set_stdout_callback); 85 | ClassDB::bind_method(D_METHOD("start_process", "path_godot_str"), &STD_IO::start_process); 86 | ClassDB::bind_method(D_METHOD("send_command", "command"), &STD_IO::send_command); 87 | ClassDB::bind_method(D_METHOD("stop_process"), &STD_IO::stop_process); 88 | } 89 | 90 | STD_IO::STD_IO() { 91 | } 92 | -------------------------------------------------------------------------------- /std_io.h: -------------------------------------------------------------------------------- 1 | // 2 | // std_io.h 3 | // std_io 4 | // 5 | // Created by Oded Streigold on 4/18/20. 6 | // 7 | 8 | #ifndef std_io_h 9 | #define std_io_h 10 | 11 | #include "core/reference.h" 12 | #include "core/func_ref.h" 13 | #include "process.hpp" 14 | 15 | using namespace std; 16 | using namespace TinyProcessLib; 17 | 18 | class STD_IO : public Reference { 19 | GDCLASS(STD_IO, Reference); 20 | 21 | int count; 22 | Process* process = nullptr; 23 | Ref godot_output_callback; 24 | 25 | 26 | protected: 27 | static void _bind_methods(); 28 | 29 | 30 | 31 | public: 32 | 33 | std::string godot_string_std_string(String godot_string); 34 | 35 | STD_IO(); 36 | 37 | int start_process(String path); 38 | int stop_process(); 39 | int send_command(String command); 40 | void set_stdout_callback(Ref callback ); 41 | 42 | }; 43 | 44 | 45 | #endif /* std_io_h */ 46 | -------------------------------------------------------------------------------- /tiny-process-library/.clang-format: -------------------------------------------------------------------------------- 1 | IndentWidth: 2 2 | AccessModifierOffset: -2 3 | UseTab: Never 4 | ColumnLimit: 0 5 | MaxEmptyLinesToKeep: 2 6 | SpaceBeforeParens: Never 7 | BreakBeforeBraces: Custom 8 | BraceWrapping: {BeforeElse: true, BeforeCatch: true} 9 | NamespaceIndentation: None 10 | -------------------------------------------------------------------------------- /tiny-process-library/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2020 Ole Christian Eidheim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tiny-process-library/README.md: -------------------------------------------------------------------------------- 1 | # tiny-process-library 2 | A small platform independent library making it simple to create and stop new processes in C++, as well as writing to stdin and reading from stdout and stderr of a new process. 3 | 4 | This library was created for, and is used by the C++ IDE project [juCi++](https://gitlab.com/cppit/jucipp). 5 | 6 | ### Features 7 | * No external dependencies 8 | * Simple to use 9 | * Platform independent 10 | * Creating processes using executables is supported on all platforms 11 | * Creating processes using functions is only possible on Unix-like systems 12 | * Read separately from stdout and stderr using anonymous functions 13 | * Write to stdin 14 | * Kill a running process (SIGTERM is supported on Unix-like systems) 15 | * Correctly closes file descriptors/handles 16 | 17 | ### Usage 18 | See [examples.cpp](examples.cpp). 19 | 20 | ### Get, compile and run 21 | 22 | #### Unix-like systems 23 | ```sh 24 | git clone http://gitlab.com/eidheim/tiny-process-library 25 | cd tiny-process-library 26 | mkdir build 27 | cd build 28 | cmake .. 29 | make 30 | ./examples 31 | ``` 32 | 33 | #### Windows with MSYS2 (https://msys2.github.io/) 34 | ```sh 35 | git clone http://gitlab.com/eidheim/tiny-process-library 36 | cd tiny-process-library 37 | mkdir build 38 | cd build 39 | cmake -G"MSYS Makefiles" .. 40 | make 41 | ./examples 42 | ``` 43 | 44 | ### Coding style 45 | Due to poor lambda support in clang-format, a custom clang-format is used with the following patch applied: 46 | ```diff 47 | diff --git a/lib/Format/ContinuationIndenter.cpp b/lib/Format/ContinuationIndenter.cpp 48 | index bb8efd61a3..e80a487055 100644 49 | --- a/lib/Format/ContinuationIndenter.cpp 50 | +++ b/lib/Format/ContinuationIndenter.cpp 51 | @@ -276,6 +276,8 @@ LineState ContinuationIndenter::getInitialState(unsigned FirstIndent, 52 | } 53 | 54 | bool ContinuationIndenter::canBreak(const LineState &State) { 55 | + if(Style.ColumnLimit==0) 56 | + return true; 57 | const FormatToken &Current = *State.NextToken; 58 | const FormatToken &Previous = *Current.Previous; 59 | assert(&Previous == Current.Previous); 60 | @@ -325,6 +327,8 @@ bool ContinuationIndenter::canBreak(const LineState &State) { 61 | } 62 | 63 | bool ContinuationIndenter::mustBreak(const LineState &State) { 64 | + if(Style.ColumnLimit==0) 65 | + return false; 66 | const FormatToken &Current = *State.NextToken; 67 | const FormatToken &Previous = *Current.Previous; 68 | if (Current.MustBreakBefore || Current.is(TT_InlineASMColon)) 69 | ``` 70 | -------------------------------------------------------------------------------- /tiny-process-library/examples.cpp: -------------------------------------------------------------------------------- 1 | #include "process.hpp" 2 | #include 3 | 4 | using namespace std; 5 | using namespace TinyProcessLib; 6 | 7 | int main() { 8 | #if !defined(_WIN32) || defined(MSYS_PROCESS_USE_SH) 9 | //The following examples are for Unix-like systems and Windows through MSYS2 10 | 11 | 12 | cout << "Example 1a - the mandatory Hello World through a command" << endl; 13 | Process process1a("echo Hello World", "", [](const char *bytes, size_t n) { 14 | cout << "Output from stdout: " << string(bytes, n); 15 | }); 16 | auto exit_status = process1a.get_exit_status(); 17 | cout << "Example 1a process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 18 | this_thread::sleep_for(chrono::seconds(2)); 19 | 20 | 21 | cout << endl << "Example 1b - Hello World using arguments" << endl; 22 | Process process1b(vector{"/bin/echo", "Hello", "World"}, "", [](const char *bytes, size_t n) { 23 | cout << "Output from stdout: " << string(bytes, n); 24 | }); 25 | exit_status = process1b.get_exit_status(); 26 | cout << "Example 1b process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 27 | this_thread::sleep_for(chrono::seconds(2)); 28 | 29 | 30 | #ifndef _WIN32 31 | cout << endl << "Example 1c - Hello World through a function on Unix-like systems" << endl; 32 | Process process1c([] { 33 | cout << "Hello World" << endl; 34 | exit(0); 35 | }, [](const char *bytes, size_t n) { 36 | cout << "Output from stdout: " << string(bytes, n); 37 | }); 38 | exit_status = process1c.get_exit_status(); 39 | cout << "Example 1c process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 40 | this_thread::sleep_for(chrono::seconds(2)); 41 | #endif 42 | 43 | 44 | cout << endl << "Example 2 - cd into a nonexistent directory" << endl; 45 | Process process2("cd a_nonexistent_directory", "", [](const char *bytes, size_t n) { 46 | cout << "Output from stdout: " << string(bytes, n); 47 | }, [](const char *bytes, size_t n) { 48 | cout << "Output from stderr: " << string(bytes, n); 49 | //add a newline for prettier output on some platforms: 50 | if(bytes[n - 1] != '\n') 51 | cout << endl; 52 | }); 53 | exit_status = process2.get_exit_status(); 54 | cout << "Example 2 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 55 | this_thread::sleep_for(chrono::seconds(2)); 56 | 57 | 58 | cout << endl << "Example 3 - async sleep process" << endl; 59 | thread thread3([]() { 60 | Process process3("sleep 2"); 61 | auto exit_status = process3.get_exit_status(); 62 | cout << "Example 3 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 63 | }); 64 | thread3.detach(); 65 | this_thread::sleep_for(chrono::seconds(4)); 66 | 67 | 68 | cout << endl << "Example 4 - killing async sleep process after 2 seconds" << endl; 69 | auto process4 = make_shared("sleep 4"); 70 | thread thread4([process4]() { 71 | auto exit_status = process4->get_exit_status(); 72 | cout << "Example 4 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 73 | }); 74 | thread4.detach(); 75 | this_thread::sleep_for(chrono::seconds(2)); 76 | process4->kill(); 77 | this_thread::sleep_for(chrono::seconds(2)); 78 | 79 | 80 | cout << endl << "Example 5 - multiple commands, stdout and stderr" << endl; 81 | Process process5("echo Hello && ls an_incorrect_path", "", [](const char *bytes, size_t n) { 82 | cout << "Output from stdout: " << string(bytes, n); 83 | }, [](const char *bytes, size_t n) { 84 | cout << "Output from stderr: " << string(bytes, n); 85 | //add a newline for prettier output on some platforms: 86 | if(bytes[n - 1] != '\n') 87 | cout << endl; 88 | }); 89 | exit_status = process5.get_exit_status(); 90 | cout << "Example 5 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 91 | this_thread::sleep_for(chrono::seconds(2)); 92 | 93 | 94 | cout << endl << "Example 6 - run bash with input from stdin" << endl; 95 | Process process6("bash", "", [](const char *bytes, size_t n) { 96 | cout << "Output from stdout: " << string(bytes, n); 97 | }, nullptr, true); 98 | process6.write("echo Hello from bash\n"); 99 | process6.write("exit\n"); 100 | exit_status = process6.get_exit_status(); 101 | cout << "Example 6 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 102 | this_thread::sleep_for(chrono::seconds(2)); 103 | 104 | 105 | cout << endl << "Example 7 - send data to cat through stdin" << endl; 106 | Process process7("cat", "", [](const char *bytes, size_t n) { 107 | cout << "Output from stdout: " << string(bytes, n); 108 | }, nullptr, true); 109 | process7.write("Hello cat\n"); 110 | process7.close_stdin(); 111 | exit_status = process7.get_exit_status(); 112 | cout << "Example 7 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 113 | this_thread::sleep_for(chrono::seconds(2)); 114 | 115 | 116 | cout << endl << "Example 8 - demonstrates Process::try_get_exit_status" << endl; 117 | Process process8("sleep 3"); 118 | while(!process8.try_get_exit_status(exit_status)) { 119 | cout << "Example 8 process is running" << endl; 120 | this_thread::sleep_for(chrono::seconds(1)); 121 | } 122 | cout << "Example 8 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 123 | this_thread::sleep_for(chrono::seconds(2)); 124 | 125 | 126 | cout << endl << "Example 9 - launch with different environment" << endl; 127 | Process process9("printenv", "", {{"VAR1", "value1"}, {"VAR2", "second value"}}, [](const char *bytes, size_t n) { 128 | std::cout << std::string{bytes, n}; 129 | }); 130 | exit_status = process9.get_exit_status(); 131 | cout << "Example 9 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 132 | this_thread::sleep_for(chrono::seconds(2)); 133 | 134 | 135 | cout << endl << "Example 10 - launch with normal environment" << endl; 136 | Process process10("printenv", "", [](const char *bytes, size_t n) { 137 | std::cout << std::string{bytes, n}; 138 | }); 139 | exit_status = process10.get_exit_status(); 140 | cout << "Example 10 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 141 | 142 | 143 | #else 144 | //Examples for Windows without MSYS2 145 | 146 | 147 | cout << "Example 1 - the mandatory Hello World" << endl; 148 | Process process1("cmd /C echo Hello World", "", [](const char *bytes, size_t n) { 149 | cout << "Output from stdout: " << string(bytes, n); 150 | }); 151 | auto exit_status = process1.get_exit_status(); 152 | cout << "Example 1 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 153 | this_thread::sleep_for(chrono::seconds(2)); 154 | 155 | 156 | cout << endl << "Example 2 - cd into a nonexistent directory" << endl; 157 | Process process2("cmd /C cd a_nonexistent_directory", "", [](const char *bytes, size_t n) { 158 | cout << "Output from stdout: " << string(bytes, n); 159 | }, [](const char *bytes, size_t n) { 160 | cout << "Output from stderr: " << string(bytes, n); 161 | //add a newline for prettier output on some platforms: 162 | if(bytes[n - 1] != '\n') 163 | cout << endl; 164 | }); 165 | exit_status = process2.get_exit_status(); 166 | cout << "Example 2 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 167 | this_thread::sleep_for(chrono::seconds(2)); 168 | 169 | 170 | cout << endl << "Example 3 - async sleep process" << endl; 171 | thread thread3([]() { 172 | Process process3("timeout 2"); 173 | auto exit_status = process3.get_exit_status(); 174 | cout << "Example 3 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 175 | }); 176 | thread3.detach(); 177 | this_thread::sleep_for(chrono::seconds(4)); 178 | 179 | 180 | cout << endl << "Example 4 - killing async sleep process after 2 seconds" << endl; 181 | auto process4 = make_shared("timeout 4"); 182 | thread thread4([process4]() { 183 | auto exit_status = process4->get_exit_status(); 184 | cout << "Example 4 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 185 | }); 186 | thread4.detach(); 187 | this_thread::sleep_for(chrono::seconds(2)); 188 | process4->kill(); 189 | this_thread::sleep_for(chrono::seconds(2)); 190 | 191 | 192 | cout << endl << "Example 5 - demonstrates Process::try_get_exit_status" << endl; 193 | Process process5("timeout 3"); 194 | while(!process5.try_get_exit_status(exit_status)) { 195 | cout << "Example 5 process is running" << endl; 196 | this_thread::sleep_for(chrono::seconds(1)); 197 | } 198 | cout << "Example 5 process returned: " << exit_status << " (" << (exit_status == 0 ? "success" : "failure") << ")" << endl; 199 | 200 | 201 | #endif 202 | } 203 | -------------------------------------------------------------------------------- /tiny-process-library/process.cpp: -------------------------------------------------------------------------------- 1 | #include "process.hpp" 2 | 3 | namespace TinyProcessLib { 4 | 5 | Process::Process(const std::vector &arguments, const string_type &path, 6 | std::function read_stdout, 7 | std::function read_stderr, 8 | bool open_stdin, const Config &config) noexcept 9 | : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { 10 | open(arguments, path); 11 | async_read(); 12 | } 13 | 14 | Process::Process(const string_type &command, const string_type &path, 15 | std::function read_stdout, 16 | std::function read_stderr, 17 | bool open_stdin, const Config &config) noexcept 18 | : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { 19 | open(command, path); 20 | async_read(); 21 | } 22 | 23 | Process::Process(const std::vector &arguments, const string_type &path, 24 | const environment_type &environment, 25 | std::function read_stdout, 26 | std::function read_stderr, 27 | bool open_stdin, const Config &config) noexcept 28 | : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { 29 | open(arguments, path, &environment); 30 | async_read(); 31 | } 32 | 33 | Process::Process(const string_type &command, const string_type &path, 34 | const environment_type &environment, 35 | std::function read_stdout, 36 | std::function read_stderr, 37 | bool open_stdin, const Config &config) noexcept 38 | : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { 39 | open(command, path, &environment); 40 | async_read(); 41 | } 42 | 43 | Process::~Process() noexcept { 44 | close_fds(); 45 | } 46 | 47 | Process::id_type Process::get_id() const noexcept { 48 | return data.id; 49 | } 50 | 51 | bool Process::write(const std::string &data) { 52 | return write(data.c_str(), data.size()); 53 | } 54 | 55 | } // namespace TinyProcessLib 56 | -------------------------------------------------------------------------------- /tiny-process-library/process.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TINY_PROCESS_LIBRARY_HPP_ 2 | #define TINY_PROCESS_LIBRARY_HPP_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifndef _WIN32 11 | #include 12 | #endif 13 | 14 | namespace TinyProcessLib { 15 | /// Additional parameters to Process constructors. 16 | struct Config { 17 | /// Buffer size for reading stdout and stderr. Default is 131072 (128 kB). 18 | std::size_t buffer_size = 131072; 19 | /// Set to true to inherit file descriptors from parent process. Default is false. 20 | /// On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false. 21 | bool inherit_file_descriptors = false; 22 | }; 23 | 24 | /// Platform independent class for creating processes. 25 | /// Note on Windows: it seems not possible to specify which pipes to redirect. 26 | /// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, 27 | /// the stdout, stderr and stdin are sent to the parent process instead. 28 | class Process { 29 | public: 30 | #ifdef _WIN32 31 | typedef unsigned long id_type; // Process id type 32 | typedef void *fd_type; // File descriptor type 33 | #ifdef UNICODE 34 | typedef std::wstring string_type; 35 | #else 36 | typedef std::string string_type; 37 | #endif 38 | #else 39 | typedef pid_t id_type; 40 | typedef int fd_type; 41 | typedef std::string string_type; 42 | #endif 43 | typedef std::unordered_map environment_type; 44 | 45 | private: 46 | class Data { 47 | public: 48 | Data() noexcept; 49 | id_type id; 50 | #ifdef _WIN32 51 | void *handle; 52 | #else 53 | int exit_status{-1}; 54 | #endif 55 | }; 56 | 57 | public: 58 | /// Starts a process with the environment of the calling process. 59 | Process(const std::vector &arguments, const string_type &path = string_type(), 60 | std::function read_stdout = nullptr, 61 | std::function read_stderr = nullptr, 62 | bool open_stdin = false, 63 | const Config &config = {}) noexcept; 64 | /// Starts a process with the environment of the calling process. 65 | Process(const string_type &command, const string_type &path = string_type(), 66 | std::function read_stdout = nullptr, 67 | std::function read_stderr = nullptr, 68 | bool open_stdin = false, 69 | const Config &config = {}) noexcept; 70 | 71 | /// Starts a process with specified environment. 72 | Process(const std::vector &arguments, 73 | const string_type &path, 74 | const environment_type &environment, 75 | std::function read_stdout = nullptr, 76 | std::function read_stderr = nullptr, 77 | bool open_stdin = false, 78 | const Config &config = {}) noexcept; 79 | /// Starts a process with specified environment. 80 | Process(const string_type &command, 81 | const string_type &path, 82 | const environment_type &environment, 83 | std::function read_stdout = nullptr, 84 | std::function read_stderr = nullptr, 85 | bool open_stdin = false, 86 | const Config &config = {}) noexcept; /// Starts a process with specified environment. 87 | #ifndef _WIN32 88 | /// Starts a process with the environment of the calling process. 89 | /// Supported on Unix-like systems only. 90 | Process(const std::function &function, 91 | std::function read_stdout = nullptr, 92 | std::function read_stderr = nullptr, 93 | bool open_stdin = false, 94 | const Config &config = {}) noexcept; 95 | #endif 96 | ~Process() noexcept; 97 | 98 | /// Get the process id of the started process. 99 | id_type get_id() const noexcept; 100 | /// Wait until process is finished, and return exit status. 101 | int get_exit_status() noexcept; 102 | /// If process is finished, returns true and sets the exit status. Returns false otherwise. 103 | bool try_get_exit_status(int &exit_status) noexcept; 104 | /// Write to stdin. 105 | bool write(const char *bytes, size_t n); 106 | /// Write to stdin. Convenience function using write(const char *, size_t). 107 | bool write(const std::string &data); 108 | /// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. 109 | void close_stdin() noexcept; 110 | 111 | /// Kill the process. force=true is only supported on Unix-like systems. 112 | void kill(bool force = false) noexcept; 113 | /// Kill a given process id. Use kill(bool force) instead if possible. force=true is only supported on Unix-like systems. 114 | static void kill(id_type id, bool force = false) noexcept; 115 | 116 | private: 117 | Data data; 118 | bool closed; 119 | std::mutex close_mutex; 120 | std::function read_stdout; 121 | std::function read_stderr; 122 | #ifndef _WIN32 123 | std::thread stdout_stderr_thread; 124 | #else 125 | std::thread stdout_thread, stderr_thread; 126 | #endif 127 | bool open_stdin; 128 | std::mutex stdin_mutex; 129 | 130 | Config config; 131 | 132 | std::unique_ptr stdout_fd, stderr_fd, stdin_fd; 133 | 134 | id_type open(const std::vector &arguments, const string_type &path, const environment_type *environment = nullptr) noexcept; 135 | id_type open(const string_type &command, const string_type &path, const environment_type *environment = nullptr) noexcept; 136 | #ifndef _WIN32 137 | id_type open(const std::function &function) noexcept; 138 | #endif 139 | void async_read() noexcept; 140 | void close_fds() noexcept; 141 | }; 142 | 143 | } // namespace TinyProcessLib 144 | 145 | #endif // TINY_PROCESS_LIBRARY_HPP_ 146 | -------------------------------------------------------------------------------- /tiny-process-library/process_unix.cpp: -------------------------------------------------------------------------------- 1 | #include "process.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace TinyProcessLib { 13 | 14 | Process::Data::Data() noexcept : id(-1) {} 15 | 16 | Process::Process(const std::function &function, 17 | std::function read_stdout, 18 | std::function read_stderr, 19 | bool open_stdin, const Config &config) noexcept 20 | : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { 21 | open(function); 22 | async_read(); 23 | } 24 | 25 | Process::id_type Process::open(const std::function &function) noexcept { 26 | if(open_stdin) 27 | stdin_fd = std::unique_ptr(new fd_type); 28 | if(read_stdout) 29 | stdout_fd = std::unique_ptr(new fd_type); 30 | if(read_stderr) 31 | stderr_fd = std::unique_ptr(new fd_type); 32 | 33 | int stdin_p[2], stdout_p[2], stderr_p[2]; 34 | 35 | if(stdin_fd && pipe(stdin_p) != 0) 36 | return -1; 37 | if(stdout_fd && pipe(stdout_p) != 0) { 38 | if(stdin_fd) { 39 | close(stdin_p[0]); 40 | close(stdin_p[1]); 41 | } 42 | return -1; 43 | } 44 | if(stderr_fd && pipe(stderr_p) != 0) { 45 | if(stdin_fd) { 46 | close(stdin_p[0]); 47 | close(stdin_p[1]); 48 | } 49 | if(stdout_fd) { 50 | close(stdout_p[0]); 51 | close(stdout_p[1]); 52 | } 53 | return -1; 54 | } 55 | 56 | id_type pid = fork(); 57 | 58 | if(pid < 0) { 59 | if(stdin_fd) { 60 | close(stdin_p[0]); 61 | close(stdin_p[1]); 62 | } 63 | if(stdout_fd) { 64 | close(stdout_p[0]); 65 | close(stdout_p[1]); 66 | } 67 | if(stderr_fd) { 68 | close(stderr_p[0]); 69 | close(stderr_p[1]); 70 | } 71 | return pid; 72 | } 73 | else if(pid == 0) { 74 | if(stdin_fd) 75 | dup2(stdin_p[0], 0); 76 | if(stdout_fd) 77 | dup2(stdout_p[1], 1); 78 | if(stderr_fd) 79 | dup2(stderr_p[1], 2); 80 | if(stdin_fd) { 81 | close(stdin_p[0]); 82 | close(stdin_p[1]); 83 | } 84 | if(stdout_fd) { 85 | close(stdout_p[0]); 86 | close(stdout_p[1]); 87 | } 88 | if(stderr_fd) { 89 | close(stderr_p[0]); 90 | close(stderr_p[1]); 91 | } 92 | 93 | if(!config.inherit_file_descriptors) { 94 | // Optimization on some systems: using 8 * 1024 (Debian's default _SC_OPEN_MAX) as fd_max limit 95 | int fd_max = std::min(8192, static_cast(sysconf(_SC_OPEN_MAX))); // Truncation is safe 96 | if(fd_max < 0) 97 | fd_max = 8192; 98 | for(int fd = 3; fd < fd_max; fd++) 99 | close(fd); 100 | } 101 | 102 | setpgid(0, 0); 103 | //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 104 | //TODO: One solution is: echo "command;exit"|script -q /dev/null 105 | 106 | if(function) 107 | function(); 108 | 109 | _exit(EXIT_FAILURE); 110 | } 111 | 112 | if(stdin_fd) 113 | close(stdin_p[0]); 114 | if(stdout_fd) 115 | close(stdout_p[1]); 116 | if(stderr_fd) 117 | close(stderr_p[1]); 118 | 119 | if(stdin_fd) 120 | *stdin_fd = stdin_p[1]; 121 | if(stdout_fd) 122 | *stdout_fd = stdout_p[0]; 123 | if(stderr_fd) 124 | *stderr_fd = stderr_p[0]; 125 | 126 | closed = false; 127 | data.id = pid; 128 | return pid; 129 | } 130 | 131 | Process::id_type Process::open(const std::vector &arguments, const string_type &path, const environment_type *environment) noexcept { 132 | return open([&arguments, &path, &environment] { 133 | if(arguments.empty()) 134 | exit(127); 135 | 136 | std::vector argv_ptrs; 137 | argv_ptrs.reserve(arguments.size() + 1); 138 | for(auto &argument : arguments) 139 | argv_ptrs.emplace_back(argument.c_str()); 140 | argv_ptrs.emplace_back(nullptr); 141 | 142 | if(!path.empty()) { 143 | if(chdir(path.c_str()) != 0) 144 | exit(1); 145 | } 146 | 147 | if(!environment) 148 | execv(arguments[0].c_str(), const_cast(argv_ptrs.data())); 149 | else { 150 | std::vector env_strs; 151 | std::vector env_ptrs; 152 | env_strs.reserve(environment->size()); 153 | env_ptrs.reserve(environment->size() + 1); 154 | for(const auto &e : *environment) { 155 | env_strs.emplace_back(e.first + '=' + e.second); 156 | env_ptrs.emplace_back(env_strs.back().c_str()); 157 | } 158 | env_ptrs.emplace_back(nullptr); 159 | 160 | execve(arguments[0].c_str(), const_cast(argv_ptrs.data()), const_cast(env_ptrs.data())); 161 | } 162 | }); 163 | } 164 | 165 | Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept { 166 | return open([&command, &path, &environment] { 167 | if(!path.empty()) { 168 | if(chdir(path.c_str()) != 0) 169 | exit(1); 170 | } 171 | 172 | if(!environment) 173 | execl("/bin/sh", "/bin/sh", "-c", command.c_str(), nullptr); 174 | else { 175 | std::vector env_strs; 176 | std::vector env_ptrs; 177 | env_strs.reserve(environment->size()); 178 | env_ptrs.reserve(environment->size() + 1); 179 | for(const auto &e : *environment) { 180 | env_strs.emplace_back(e.first + '=' + e.second); 181 | env_ptrs.emplace_back(env_strs.back().c_str()); 182 | } 183 | env_ptrs.emplace_back(nullptr); 184 | execle("/bin/sh", "/bin/sh", "-c", command.c_str(), nullptr, env_ptrs.data()); 185 | } 186 | }); 187 | } 188 | 189 | void Process::async_read() noexcept { 190 | if(data.id <= 0 || (!stdout_fd && !stderr_fd)) 191 | return; 192 | 193 | stdout_stderr_thread = std::thread([this] { 194 | std::vector pollfds; 195 | std::bitset<2> fd_is_stdout; 196 | if(stdout_fd) { 197 | fd_is_stdout.set(pollfds.size()); 198 | pollfds.emplace_back(); 199 | pollfds.back().fd = fcntl(*stdout_fd, F_SETFL, fcntl(*stdout_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stdout_fd : -1; 200 | pollfds.back().events = POLLIN; 201 | } 202 | if(stderr_fd) { 203 | pollfds.emplace_back(); 204 | pollfds.back().fd = fcntl(*stderr_fd, F_SETFL, fcntl(*stderr_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stderr_fd : -1; 205 | pollfds.back().events = POLLIN; 206 | } 207 | auto buffer = std::unique_ptr(new char[config.buffer_size]); 208 | bool any_open = !pollfds.empty(); 209 | while(any_open && (poll(pollfds.data(), static_cast(pollfds.size()), -1) > 0 || errno == EINTR)) { 210 | any_open = false; 211 | for(size_t i = 0; i < pollfds.size(); ++i) { 212 | if(pollfds[i].fd >= 0) { 213 | if(pollfds[i].revents & POLLIN) { 214 | const ssize_t n = read(pollfds[i].fd, buffer.get(), config.buffer_size); 215 | if(n > 0) { 216 | if(fd_is_stdout[i]) 217 | read_stdout(buffer.get(), static_cast(n)); 218 | else 219 | read_stderr(buffer.get(), static_cast(n)); 220 | } 221 | else if(n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { 222 | pollfds[i].fd = -1; 223 | continue; 224 | } 225 | } 226 | if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { 227 | pollfds[i].fd = -1; 228 | continue; 229 | } 230 | any_open = true; 231 | } 232 | } 233 | } 234 | }); 235 | } 236 | 237 | int Process::get_exit_status() noexcept { 238 | if(data.id <= 0) 239 | return -1; 240 | 241 | int exit_status; 242 | id_type p; 243 | do { 244 | p = waitpid(data.id, &exit_status, 0); 245 | } while(p < 0 && errno == EINTR); 246 | 247 | if(p < 0 && errno == ECHILD) { 248 | // PID doesn't exist anymore, return previously sampled exit status (or -1) 249 | return data.exit_status; 250 | } 251 | else { 252 | // store exit status for future calls 253 | if(exit_status >= 256) 254 | exit_status = exit_status >> 8; 255 | data.exit_status = exit_status; 256 | } 257 | 258 | { 259 | std::lock_guard lock(close_mutex); 260 | closed = true; 261 | } 262 | close_fds(); 263 | 264 | return exit_status; 265 | } 266 | 267 | bool Process::try_get_exit_status(int &exit_status) noexcept { 268 | if(data.id <= 0) 269 | return false; 270 | 271 | const id_type p = waitpid(data.id, &exit_status, WNOHANG); 272 | if(p < 0 && errno == ECHILD) { 273 | // PID doesn't exist anymore, set previously sampled exit status (or -1) 274 | exit_status = data.exit_status; 275 | return true; 276 | } 277 | else if(p <= 0) { 278 | // Process still running (p==0) or error 279 | return false; 280 | } 281 | else { 282 | // store exit status for future calls 283 | if(exit_status >= 256) 284 | exit_status = exit_status >> 8; 285 | data.exit_status = exit_status; 286 | } 287 | 288 | { 289 | std::lock_guard lock(close_mutex); 290 | closed = true; 291 | } 292 | close_fds(); 293 | 294 | return true; 295 | } 296 | 297 | void Process::close_fds() noexcept { 298 | if(stdout_stderr_thread.joinable()) 299 | stdout_stderr_thread.join(); 300 | 301 | if(stdin_fd) 302 | close_stdin(); 303 | if(stdout_fd) { 304 | if(data.id > 0) 305 | close(*stdout_fd); 306 | stdout_fd.reset(); 307 | } 308 | if(stderr_fd) { 309 | if(data.id > 0) 310 | close(*stderr_fd); 311 | stderr_fd.reset(); 312 | } 313 | } 314 | 315 | bool Process::write(const char *bytes, size_t n) { 316 | if(!open_stdin) 317 | throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); 318 | 319 | std::lock_guard lock(stdin_mutex); 320 | if(stdin_fd) { 321 | if(::write(*stdin_fd, bytes, n) >= 0) { 322 | return true; 323 | } 324 | else { 325 | return false; 326 | } 327 | } 328 | return false; 329 | } 330 | 331 | void Process::close_stdin() noexcept { 332 | std::lock_guard lock(stdin_mutex); 333 | if(stdin_fd) { 334 | if(data.id > 0) 335 | close(*stdin_fd); 336 | stdin_fd.reset(); 337 | } 338 | } 339 | 340 | void Process::kill(bool force) noexcept { 341 | std::lock_guard lock(close_mutex); 342 | if(data.id > 0 && !closed) { 343 | if(force) 344 | ::kill(-data.id, SIGTERM); 345 | else 346 | ::kill(-data.id, SIGINT); 347 | } 348 | } 349 | 350 | void Process::kill(id_type id, bool force) noexcept { 351 | if(id <= 0) 352 | return; 353 | 354 | if(force) 355 | ::kill(-id, SIGTERM); 356 | else 357 | ::kill(-id, SIGINT); 358 | } 359 | 360 | } // namespace TinyProcessLib 361 | -------------------------------------------------------------------------------- /tiny-process-library/process_win.cpp: -------------------------------------------------------------------------------- 1 | #include "process.hpp" 2 | // clang-format off 3 | #include 4 | // clang-format on 5 | #include 6 | #include 7 | #include 8 | 9 | namespace TinyProcessLib { 10 | 11 | Process::Data::Data() noexcept : id(0), handle(NULL) {} 12 | 13 | // Simple HANDLE wrapper to close it automatically from the destructor. 14 | class Handle { 15 | public: 16 | Handle() noexcept : handle(INVALID_HANDLE_VALUE) {} 17 | ~Handle() noexcept { 18 | close(); 19 | } 20 | void close() noexcept { 21 | if(handle != INVALID_HANDLE_VALUE) 22 | CloseHandle(handle); 23 | } 24 | HANDLE detach() noexcept { 25 | HANDLE old_handle = handle; 26 | handle = INVALID_HANDLE_VALUE; 27 | return old_handle; 28 | } 29 | operator HANDLE() const noexcept { return handle; } 30 | HANDLE *operator&() noexcept { return &handle; } 31 | 32 | private: 33 | HANDLE handle; 34 | }; 35 | 36 | //Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj 37 | std::mutex create_process_mutex; 38 | 39 | Process::id_type Process::open(const std::vector &arguments, const string_type &path, const environment_type *environment) noexcept { 40 | string_type command; 41 | for(auto &argument : arguments) 42 | #ifdef UNICODE 43 | command += (command.empty() ? L"" : L" ") + argument; 44 | #else 45 | command += (command.empty() ? "" : " ") + argument; 46 | #endif 47 | return open(command, path, environment); 48 | } 49 | 50 | //Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. 51 | Process::id_type Process::open(const string_type &command, const string_type &path, const environment_type *environment) noexcept { 52 | if(open_stdin) 53 | stdin_fd = std::unique_ptr(new fd_type(NULL)); 54 | if(read_stdout) 55 | stdout_fd = std::unique_ptr(new fd_type(NULL)); 56 | if(read_stderr) 57 | stderr_fd = std::unique_ptr(new fd_type(NULL)); 58 | 59 | Handle stdin_rd_p; 60 | Handle stdin_wr_p; 61 | Handle stdout_rd_p; 62 | Handle stdout_wr_p; 63 | Handle stderr_rd_p; 64 | Handle stderr_wr_p; 65 | 66 | SECURITY_ATTRIBUTES security_attributes; 67 | 68 | security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); 69 | security_attributes.bInheritHandle = TRUE; 70 | security_attributes.lpSecurityDescriptor = nullptr; 71 | 72 | std::lock_guard lock(create_process_mutex); 73 | if(stdin_fd) { 74 | if(!CreatePipe(&stdin_rd_p, &stdin_wr_p, &security_attributes, 0) || 75 | !SetHandleInformation(stdin_wr_p, HANDLE_FLAG_INHERIT, 0)) 76 | return 0; 77 | } 78 | if(stdout_fd) { 79 | if(!CreatePipe(&stdout_rd_p, &stdout_wr_p, &security_attributes, 0) || 80 | !SetHandleInformation(stdout_rd_p, HANDLE_FLAG_INHERIT, 0)) { 81 | return 0; 82 | } 83 | } 84 | if(stderr_fd) { 85 | if(!CreatePipe(&stderr_rd_p, &stderr_wr_p, &security_attributes, 0) || 86 | !SetHandleInformation(stderr_rd_p, HANDLE_FLAG_INHERIT, 0)) { 87 | return 0; 88 | } 89 | } 90 | 91 | PROCESS_INFORMATION process_info; 92 | STARTUPINFO startup_info; 93 | 94 | ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); 95 | 96 | ZeroMemory(&startup_info, sizeof(STARTUPINFO)); 97 | startup_info.cb = sizeof(STARTUPINFO); 98 | startup_info.hStdInput = stdin_rd_p; 99 | startup_info.hStdOutput = stdout_wr_p; 100 | startup_info.hStdError = stderr_wr_p; 101 | if(stdin_fd || stdout_fd || stderr_fd) 102 | startup_info.dwFlags |= STARTF_USESTDHANDLES; 103 | 104 | auto process_command = command; 105 | #ifdef MSYS_PROCESS_USE_SH 106 | size_t pos = 0; 107 | while((pos = process_command.find('\\', pos)) != string_type::npos) { 108 | process_command.replace(pos, 1, "\\\\\\\\"); 109 | pos += 4; 110 | } 111 | pos = 0; 112 | while((pos = process_command.find('\"', pos)) != string_type::npos) { 113 | process_command.replace(pos, 1, "\\\""); 114 | pos += 2; 115 | } 116 | process_command.insert(0, "sh -c \""); 117 | process_command += "\""; 118 | #endif 119 | 120 | string_type environment_str; 121 | if(environment) { 122 | #ifdef UNICODE 123 | for(const auto &e : *environment) 124 | environment_str += e.first + L'=' + e.second + L'\0'; 125 | environment_str += L'\0'; 126 | #else 127 | for(const auto &e : *environment) 128 | environment_str += e.first + '=' + e.second + '\0'; 129 | environment_str += '\0'; 130 | #endif 131 | } 132 | BOOL bSuccess = CreateProcess(nullptr, process_command.empty() ? nullptr : &process_command[0], nullptr, nullptr, 133 | stdin_fd || stdout_fd || stderr_fd || config.inherit_file_descriptors, // Cannot be false when stdout, stderr or stdin is used 134 | 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 135 | environment_str.empty() ? nullptr : &environment_str[0], 136 | path.empty() ? nullptr : path.c_str(), 137 | &startup_info, &process_info); 138 | 139 | if(!bSuccess) 140 | return 0; 141 | else 142 | CloseHandle(process_info.hThread); 143 | 144 | if(stdin_fd) 145 | *stdin_fd = stdin_wr_p.detach(); 146 | if(stdout_fd) 147 | *stdout_fd = stdout_rd_p.detach(); 148 | if(stderr_fd) 149 | *stderr_fd = stderr_rd_p.detach(); 150 | 151 | closed = false; 152 | data.id = process_info.dwProcessId; 153 | data.handle = process_info.hProcess; 154 | return process_info.dwProcessId; 155 | } 156 | 157 | void Process::async_read() noexcept { 158 | if(data.id == 0) 159 | return; 160 | 161 | if(stdout_fd) { 162 | stdout_thread = std::thread([this]() { 163 | DWORD n; 164 | std::unique_ptr buffer(new char[config.buffer_size]); 165 | for(;;) { 166 | BOOL bSuccess = ReadFile(*stdout_fd, static_cast(buffer.get()), static_cast(config.buffer_size), &n, nullptr); 167 | if(!bSuccess || n == 0) 168 | break; 169 | read_stdout(buffer.get(), static_cast(n)); 170 | } 171 | }); 172 | } 173 | if(stderr_fd) { 174 | stderr_thread = std::thread([this]() { 175 | DWORD n; 176 | std::unique_ptr buffer(new char[config.buffer_size]); 177 | for(;;) { 178 | BOOL bSuccess = ReadFile(*stderr_fd, static_cast(buffer.get()), static_cast(config.buffer_size), &n, nullptr); 179 | if(!bSuccess || n == 0) 180 | break; 181 | read_stderr(buffer.get(), static_cast(n)); 182 | } 183 | }); 184 | } 185 | } 186 | 187 | int Process::get_exit_status() noexcept { 188 | if(data.id == 0) 189 | return -1; 190 | 191 | DWORD exit_status; 192 | WaitForSingleObject(data.handle, INFINITE); 193 | if(!GetExitCodeProcess(data.handle, &exit_status)) 194 | exit_status = -1; 195 | { 196 | std::lock_guard lock(close_mutex); 197 | CloseHandle(data.handle); 198 | closed = true; 199 | } 200 | close_fds(); 201 | 202 | return static_cast(exit_status); 203 | } 204 | 205 | bool Process::try_get_exit_status(int &exit_status) noexcept { 206 | if(data.id == 0) 207 | return false; 208 | 209 | DWORD wait_status = WaitForSingleObject(data.handle, 0); 210 | 211 | if(wait_status == WAIT_TIMEOUT) 212 | return false; 213 | 214 | DWORD exit_status_win; 215 | if(!GetExitCodeProcess(data.handle, &exit_status_win)) 216 | exit_status_win = -1; 217 | { 218 | std::lock_guard lock(close_mutex); 219 | CloseHandle(data.handle); 220 | closed = true; 221 | } 222 | close_fds(); 223 | 224 | exit_status = static_cast(exit_status_win); 225 | return true; 226 | } 227 | 228 | void Process::close_fds() noexcept { 229 | if(stdout_thread.joinable()) 230 | stdout_thread.join(); 231 | if(stderr_thread.joinable()) 232 | stderr_thread.join(); 233 | 234 | if(stdin_fd) 235 | close_stdin(); 236 | if(stdout_fd) { 237 | if(*stdout_fd != NULL) 238 | CloseHandle(*stdout_fd); 239 | stdout_fd.reset(); 240 | } 241 | if(stderr_fd) { 242 | if(*stderr_fd != NULL) 243 | CloseHandle(*stderr_fd); 244 | stderr_fd.reset(); 245 | } 246 | } 247 | 248 | bool Process::write(const char *bytes, size_t n) { 249 | if(!open_stdin) 250 | throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); 251 | 252 | std::lock_guard lock(stdin_mutex); 253 | if(stdin_fd) { 254 | DWORD written; 255 | BOOL bSuccess = WriteFile(*stdin_fd, bytes, static_cast(n), &written, nullptr); 256 | if(!bSuccess || written == 0) { 257 | return false; 258 | } 259 | else { 260 | return true; 261 | } 262 | } 263 | return false; 264 | } 265 | 266 | void Process::close_stdin() noexcept { 267 | std::lock_guard lock(stdin_mutex); 268 | if(stdin_fd) { 269 | if(*stdin_fd != NULL) 270 | CloseHandle(*stdin_fd); 271 | stdin_fd.reset(); 272 | } 273 | } 274 | 275 | //Based on http://stackoverflow.com/a/1173396 276 | void Process::kill(bool /*force*/) noexcept { 277 | std::lock_guard lock(close_mutex); 278 | if(data.id > 0 && !closed) { 279 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 280 | if(snapshot) { 281 | PROCESSENTRY32 process; 282 | ZeroMemory(&process, sizeof(process)); 283 | process.dwSize = sizeof(process); 284 | if(Process32First(snapshot, &process)) { 285 | do { 286 | if(process.th32ParentProcessID == data.id) { 287 | HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); 288 | if(process_handle) { 289 | TerminateProcess(process_handle, 2); 290 | CloseHandle(process_handle); 291 | } 292 | } 293 | } while(Process32Next(snapshot, &process)); 294 | } 295 | CloseHandle(snapshot); 296 | } 297 | TerminateProcess(data.handle, 2); 298 | } 299 | } 300 | 301 | //Based on http://stackoverflow.com/a/1173396 302 | void Process::kill(id_type id, bool /*force*/) noexcept { 303 | if(id == 0) 304 | return; 305 | 306 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 307 | if(snapshot) { 308 | PROCESSENTRY32 process; 309 | ZeroMemory(&process, sizeof(process)); 310 | process.dwSize = sizeof(process); 311 | if(Process32First(snapshot, &process)) { 312 | do { 313 | if(process.th32ParentProcessID == id) { 314 | HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); 315 | if(process_handle) { 316 | TerminateProcess(process_handle, 2); 317 | CloseHandle(process_handle); 318 | } 319 | } 320 | } while(Process32Next(snapshot, &process)); 321 | } 322 | CloseHandle(snapshot); 323 | } 324 | HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id); 325 | if(process_handle) 326 | TerminateProcess(process_handle, 2); 327 | } 328 | 329 | } // namespace TinyProcessLib 330 | --------------------------------------------------------------------------------