9 #include <boost/algorithm/string/replace.hpp> 14 #define WIN32_LEAN_AND_MEAN 21 std::string GetLastErrorAsString() {
23 DWORD errorMessageID = GetLastError();
24 if (errorMessageID == 0)
return std::string();
26 LPSTR messageBuffer =
nullptr;
27 size_t size = FormatMessageA(
28 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
29 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0,
32 std::string message(messageBuffer, size);
35 LocalFree(messageBuffer);
42 typedef struct tagTHREADNAME_INFO {
50 void SetThreadName(HANDLE thread,
const char* name) {
54 info.dwThreadID = GetThreadId(thread);
57 #pragma warning(disable : 6320 6322) 59 RaiseException(0x406D1388, 0,
sizeof(info) /
sizeof(ULONG_PTR), (ULONG_PTR*)&info);
60 } __except (EXCEPTION_EXECUTE_HANDLER) {}
65 struct Subprocess::Implementation {
66 HANDLE StdIn_Write = 0;
68 HANDLE StdOut_Read = 0;
70 HANDLE StdErr_Read = 0;
72 PROCESS_INFORMATION procInfo = {};
74 std::thread stdoutThread;
75 std::thread stderrThread;
83 CloseHandle(mPimpl->StdOut_Read);
84 CloseHandle(mPimpl->StdErr_Read);
86 mPimpl->StdOut_Read =
nullptr;
87 mPimpl->StdErr_Read =
nullptr;
89 if (mPimpl->StdIn_Write !=
nullptr) {
90 CloseHandle(mPimpl->StdIn_Write);
91 mPimpl->StdIn_Write =
nullptr;
94 if (mPimpl->stdoutThread.joinable()) { mPimpl->stdoutThread.join(); }
96 if (mPimpl->stderrThread.joinable()) { mPimpl->stderrThread.join(); }
100 assert(started() &&
"Process must be started to push to stdin");
101 assert(!isStdInClosed() &&
"Cannot push to stdin after closing stdin");
105 if (size == 0) {
return {}; }
108 if (!WriteFile(mPimpl->StdIn_Write, data, size, &bytesWritten,
nullptr)) {
109 res.addEntry(
"EUKN",
"Failed to write to stdin for process",
110 {{
"Error Message", GetLastErrorAsString()}});
113 if (bytesWritten == 0) {
114 res.addEntry(
"EUKN",
"No bytes were written to stdin",
115 {{
"Error Message", GetLastErrorAsString()}});
122 assert(running() &&
"You cannot close stdin for a process you never started");
123 assert(!isStdInClosed() &&
"You cannot close stdin for a process you never started");
127 if (!CloseHandle(mPimpl->StdIn_Write)) {
128 res.addEntry(
"EUKN",
"Failed to close process stdin",
129 {{
"Error Message", GetLastErrorAsString()}});
131 mPimpl->StdIn_Write =
nullptr;
139 assert(!started() &&
"Cannot start a Subprocess twice");
144 SECURITY_ATTRIBUTES secAttributes;
145 secAttributes.nLength =
sizeof(SECURITY_ATTRIBUTES);
146 secAttributes.bInheritHandle =
true;
147 secAttributes.lpSecurityDescriptor =
nullptr;
154 if (!CreatePipe(&mPimpl->StdOut_Read, &stdoutWrite, &secAttributes, 0)) {
155 res.addEntry(
"EUKN",
"Failed to create stdout pipe",
156 {{
"Error Message", GetLastErrorAsString()}});
160 if (!SetHandleInformation(mPimpl->StdOut_Read, HANDLE_FLAG_INHERIT, 0)) {
161 res.addEntry(
"EUKN",
"Failed to SetHandleInformation",
162 {{
"Error Message", GetLastErrorAsString()}});
167 if (!CreatePipe(&mPimpl->StdErr_Read, &stderrWrite, &secAttributes, 0)) {
168 res.addEntry(
"EUKN",
"Failed to create stderr pipe",
169 {{
"Error Message", GetLastErrorAsString()}});
173 if (!SetHandleInformation(mPimpl->StdErr_Read, HANDLE_FLAG_INHERIT, 0)) {
174 res.addEntry(
"EUKN",
"Failed to SetHandleInformation",
175 {{
"Error Message", GetLastErrorAsString()}});
180 if (!CreatePipe(&stdinRead, &mPimpl->StdIn_Write, &secAttributes, 0)) {
181 res.addEntry(
"EUKN",
"Failed to create stdin pipe",
182 {{
"Error Message", GetLastErrorAsString()}});
186 if (!SetHandleInformation(mPimpl->StdIn_Write, HANDLE_FLAG_INHERIT, 0)) {
187 res.addEntry(
"EUKN",
"Failed to SetHandleInformation",
188 {{
"Error Message", GetLastErrorAsString()}});
196 std::wstring commandLine = L
'"' + mExePath.wstring() + L
'"';
198 for (
const auto& cmd : mArguments) {
200 std::copy(cmd.begin(), cmd.end(), std::back_inserter(wCmd));
202 boost::algorithm::replace_all(wCmd, L
"\"", L
"\\\"");
204 commandLine += L
" \"" + wCmd + L
'"';
207 ZeroMemory(&mPimpl->procInfo,
sizeof(PROCESS_INFORMATION));
210 STARTUPINFOW startupInfo;
211 ZeroMemory(&startupInfo,
sizeof(startupInfo));
213 startupInfo.cb =
sizeof(startupInfo);
214 startupInfo.hStdError = stderrWrite;
215 startupInfo.hStdOutput = stdoutWrite;
216 startupInfo.hStdInput = stdinRead;
217 startupInfo.dwFlags |= STARTF_USESTDHANDLES;
220 if (!CreateProcessW(
nullptr, &commandLine[0],
nullptr,
nullptr, TRUE, 0,
nullptr,
221 mWorkingDir.c_str(), &startupInfo, &mPimpl->procInfo)) {
222 res.addEntry(
"EUKN",
"Failed to CreateProcess",
223 {{
"Error Message", GetLastErrorAsString()}});
228 CloseHandle(mPimpl->procInfo.hThread);
231 CloseHandle(stderrWrite);
232 CloseHandle(stdoutWrite);
233 CloseHandle(stdinRead);
236 mPimpl->stdoutThread = std::thread([
this]() {
240 std::vector<char> buffer;
243 if (!ReadFile(mPimpl->StdOut_Read, buffer.data(), buffer.size(), &bytesRead,
nullptr)) {
247 if (bytesRead == 0) {
return; }
250 if (mStdOutHandler) { mStdOutHandler(buffer.data(), bytesRead); }
256 auto threadName = mExePath.filename().string() +
" stdout reader";
259 SetThreadName(mPimpl->stdoutThread.native_handle(), threadName.c_str());
264 mPimpl->stderrThread = std::thread([
this]() {
269 std::vector<char> buffer;
272 if (!ReadFile(mPimpl->StdErr_Read, buffer.data(), buffer.size(), &bytesRead,
nullptr)) {
276 if (bytesRead == 0) {
return; }
279 if (mStdErrHandler) { mStdErrHandler(buffer.data(), bytesRead); }
286 auto threadName = mExePath.filename().string() +
" stderr reader";
289 SetThreadName(mPimpl->stderrThread.native_handle(), threadName.c_str());
299 if (running()) { TerminateProcess(mPimpl->procInfo.hProcess, 9); }
303 assert(started() &&
"Cannot wait for a process before it's started");
306 WaitForSingleObject(mPimpl->procInfo.hProcess, INFINITE);
313 if (!GetExitCodeProcess(mPimpl->procInfo.hProcess, &exitCode)) {
321 assert(started() &&
"Must start a process before checking if it's running");
323 if (mPimpl->procInfo.hProcess == 0) {
return false; }
325 if (!GetExitCodeProcess(mPimpl->procInfo.hProcess, &exitCode)) {
return false; }
327 return exitCode == STILL_ACTIVE;
336 #include <sys/wait.h> 341 struct Subprocess::Implementation {
342 std::array<int, 2> stdinPipe = {{-1, -1}};
344 std::array<int, 2> stdoutPipe = {{-1, -1}};
345 std::array<int, 2> stderrPipe = {{-1, -1}};
349 std::thread stdoutThread;
350 std::thread stderrThread;
358 if (isStdInClosed()) {
359 close(mPimpl->stdinPipe[1]);
360 mPimpl->stdinPipe[1] = -1;
363 close(mPimpl->stdoutPipe[0]);
364 mPimpl->stdoutPipe[0] = -1;
366 close(mPimpl->stderrPipe[0]);
367 mPimpl->stderrPipe[0] = -1;
369 if (mPimpl->stdoutThread.joinable()) { mPimpl->stdoutThread.join(); }
371 if (mPimpl->stderrThread.joinable()) { mPimpl->stderrThread.join(); }
375 assert(started() &&
"Process must be started to push to stdin");
376 assert(!isStdInClosed() &&
"Cannot push to stdin after closing stdin");
380 if (write(mPimpl->stdinPipe[1], data, size) == -1) {
381 res.
addEntry(
"EUKN",
"Failed to write to stdin pipe", {{
"Error message", strerror(errno)}});
389 assert(running() &&
"You cannot close stdin for a process you never started");
390 assert(!isStdInClosed() &&
"You cannot close stdin for a process you never started");
394 if (close(mPimpl->stdinPipe[1]) == -1) {
395 res.
addEntry(
"EUKN",
"Failed to close stdin pipe", {{
"Error Message", strerror(errno)}});
398 mPimpl->stdinPipe[1] = -1;
406 assert(!started() &&
"Cannot start a Subprocess twice");
411 if (pipe(mPimpl->stdinPipe.data()) != 0) {
412 res.
addEntry(
"EUKN",
"Failed to create stdin pipe", {{
"Error message", strerror(errno)}});
415 if (pipe(mPimpl->stdoutPipe.data()) != 0) {
416 res.
addEntry(
"EUKN",
"Failed to create stdout pipe", {{
"Error message", strerror(errno)}});
419 if (pipe(mPimpl->stderrPipe.data()) != 0) {
420 res.
addEntry(
"EUKN",
"Failed to create stderr pipe", {{
"Error message", strerror(errno)}});
425 mPimpl->childPID = fork();
427 if (mPimpl->childPID == -1) {
428 res.
addEntry(
"EUKN",
"Failed to fork", {{
"Error message", strerror(errno)}});
433 if (mPimpl->childPID == 0) {
434 boost::filesystem::current_path(mWorkingDir);
437 dup2(mPimpl->stdinPipe[0], 0);
438 dup2(mPimpl->stdoutPipe[1], 1);
439 dup2(mPimpl->stderrPipe[1], 2);
442 close(mPimpl->stdinPipe[0]);
443 close(mPimpl->stdinPipe[1]);
444 close(mPimpl->stdoutPipe[0]);
445 close(mPimpl->stdoutPipe[1]);
446 close(mPimpl->stderrPipe[0]);
447 close(mPimpl->stderrPipe[1]);
451 int fd_max =
static_cast<int>(sysconf(_SC_OPEN_MAX));
452 for (
int fd = 3; fd < fd_max; fd++) close(fd);
458 std::vector<char*> argv;
459 std::string exePathStr = mExePath.string();
460 argv.push_back(&exePathStr[0]);
461 for (
auto& arg : mArguments) { argv.push_back(&arg[0]); }
462 argv.push_back(
nullptr);
465 execvp(mExePath.c_str(), argv.data());
473 close(mPimpl->stdinPipe[0]);
474 mPimpl->stdinPipe[0] = -1;
475 close(mPimpl->stdoutPipe[1]);
476 mPimpl->stdoutPipe[1] = -1;
477 close(mPimpl->stderrPipe[1]);
478 mPimpl->stderrPipe[1] = -1;
481 mPimpl->stdoutThread = std::thread([
this] {
483 std::array<char, 4096> buffer;
486 ssize_t bytesRead = read(mPimpl->stdoutPipe[0], buffer.data(), buffer.size());
489 if (bytesRead == -1) {
return; }
492 if (bytesRead == 0) {
return; }
494 if (mStdOutHandler) { mStdOutHandler(buffer.data(), bytesRead); }
498 mPimpl->stderrThread = std::thread([
this] {
500 std::array<char, 4096> buffer;
503 ssize_t bytesRead = read(mPimpl->stderrPipe[0], buffer.data(), buffer.size());
506 if (bytesRead == -1) {
return; }
509 if (bytesRead == 0) {
return; }
511 if (mStdErrHandler) { mStdErrHandler(buffer.data(), bytesRead); }
521 assert(started() &&
"Cannot kill a process if it never started");
522 ::kill(mPimpl->childPID, SIGINT);
526 assert(started() &&
"Cannot wait for a process before it's started");
529 waitpid(mPimpl->childPID, &exit_status, 0);
531 if (WIFEXITED(exit_status)) { mExitCode = WEXITSTATUS(exit_status); }
535 assert(started() &&
"Cannot get the exit code of a process before it's started");
537 if (mExitCode) {
return *mExitCode; }
540 if (waitpid(mPimpl->childPID, &exit_status, 0) == -1) {
return -1; }
542 if (WIFEXITED(exit_status)) {
543 mExitCode = WEXITSTATUS(exit_status);
551 assert(started() &&
"Must start a process before checking if it's running");
556 int status = waitpid(mPimpl->childPID, &exit_status, WNOHANG);
560 if (status == 0) {
return true; }
561 if (WIFEXITED(exit_status)) { mExitCode = int(WEXITSTATUS(exit_status)); }
573 : mPimpl{std::make_unique<Subprocess::Implementation>()}, mExePath{path} {
574 assert(boost::filesystem::is_regular_file(path) &&
575 "the path passed to subprocess is not a regular file");
~Subprocess()
Wait for the process to exit and close all open handles.
void addEntry(const char *ec, const char *overview, nlohmann::json data)
Add a entry to the result, either a warning or an error.
void wait()
Wait for the process to complete.
Defines the Result class and related functions.
int exitCode()
Wait and gets the exit code.
Result closeStdIn()
Close the STDIN stream (send an EOF)
Subprocess(const boost::filesystem::path &pathToExecutable)
Construct a Subprocess with a path to an executable.
Result pushToStdIn(const char *data, size_t size)
Pushes data to the stdin stream of the child process.
bool running()
Check if the process is still running.
Result start()
Start the process.
void kill()
Kill the child process On POSIX, this sends SIGINT.
The namespace where chigraph lives.
The result object, used for identifiying errors with good diagnostics.