chigraph  master
Systems programming language written for beginners in LLVM
Subprocess.cpp
Go to the documentation of this file.
1 
4 #include "chi/Support/Result.hpp"
5 
6 #include <cassert>
7 #include <thread>
8 
9 #include <boost/algorithm/string/replace.hpp>
10 
11 // Win32 Implementation
12 #ifdef WIN32
13 
14 #define WIN32_LEAN_AND_MEAN
15 #include "windows.h"
16 
17 namespace chi {
18 namespace {
19 // http://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror
20 // Returns the last Win32 error, in string format. Returns an empty string if there is no error.
21 std::string GetLastErrorAsString() {
22  // Get the error message, if any.
23  DWORD errorMessageID = GetLastError();
24  if (errorMessageID == 0) return std::string(); // No error message has been recorded
25 
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,
30  NULL);
31 
32  std::string message(messageBuffer, size);
33 
34  // Free the buffer.
35  LocalFree(messageBuffer);
36 
37  return message;
38 }
39 // taken from
40 // https://docs.microsoft.com/en-us/visualstudio/debugger/how-to-set-a-thread-name-in-native-code
41 #pragma pack(push, 8)
42 typedef struct tagTHREADNAME_INFO {
43  DWORD dwType; // Must be 0x1000.
44  LPCSTR szName; // Pointer to name (in user addr space).
45  DWORD dwThreadID; // Thread ID (-1=caller thread).
46  DWORD dwFlags; // Reserved for future use, must be zero.
47 } THREADNAME_INFO;
48 #pragma pack(pop)
49 
50 void SetThreadName(HANDLE thread, const char* name) {
51  THREADNAME_INFO info;
52  info.dwType = 0x1000;
53  info.szName = name;
54  info.dwThreadID = GetThreadId(thread);
55  info.dwFlags = 0;
56 #pragma warning(push)
57 #pragma warning(disable : 6320 6322)
58  __try {
59  RaiseException(0x406D1388, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
60  } __except (EXCEPTION_EXECUTE_HANDLER) {}
61 #pragma warning(pop)
62 }
63 } // namespace
64 
65 struct Subprocess::Implementation {
66  HANDLE StdIn_Write = 0;
67 
68  HANDLE StdOut_Read = 0;
69 
70  HANDLE StdErr_Read = 0;
71 
72  PROCESS_INFORMATION procInfo = {};
73 
74  std::thread stdoutThread;
75  std::thread stderrThread;
76 };
77 
79  wait();
80 
81  // close the FDs
82 
83  CloseHandle(mPimpl->StdOut_Read);
84  CloseHandle(mPimpl->StdErr_Read);
85 
86  mPimpl->StdOut_Read = nullptr;
87  mPimpl->StdErr_Read = nullptr;
88 
89  if (mPimpl->StdIn_Write != nullptr) {
90  CloseHandle(mPimpl->StdIn_Write);
91  mPimpl->StdIn_Write = nullptr;
92  }
93 
94  if (mPimpl->stdoutThread.joinable()) { mPimpl->stdoutThread.join(); }
95 
96  if (mPimpl->stderrThread.joinable()) { mPimpl->stderrThread.join(); }
97 }
98 
99 Result Subprocess::pushToStdIn(const char* data, size_t size) {
100  assert(started() && "Process must be started to push to stdin");
101  assert(!isStdInClosed() && "Cannot push to stdin after closing stdin");
102 
103  Result res;
104 
105  if (size == 0) { return {}; }
106 
107  DWORD bytesWritten;
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()}});
111  return res;
112  }
113  if (bytesWritten == 0) {
114  res.addEntry("EUKN", "No bytes were written to stdin",
115  {{"Error Message", GetLastErrorAsString()}});
116  }
117 
118  return res;
119 }
120 
121 Result Subprocess::closeStdIn() {
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");
124 
125  Result res;
126 
127  if (!CloseHandle(mPimpl->StdIn_Write)) {
128  res.addEntry("EUKN", "Failed to close process stdin",
129  {{"Error Message", GetLastErrorAsString()}});
130  }
131  mPimpl->StdIn_Write = nullptr;
132 
133  mStdInClosed = true;
134 
135  return res;
136 }
137 
138 Result Subprocess::start() {
139  assert(!started() && "Cannot start a Subprocess twice");
140 
141  Result res;
142 
143  // Create SECURITY_ATTRIBUTES struct
144  SECURITY_ATTRIBUTES secAttributes;
145  secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
146  secAttributes.bInheritHandle = true;
147  secAttributes.lpSecurityDescriptor = nullptr;
148 
149  HANDLE stdoutWrite;
150  HANDLE stderrWrite;
151  HANDLE stdinRead;
152 
153  // create stdout pipe
154  if (!CreatePipe(&mPimpl->StdOut_Read, &stdoutWrite, &secAttributes, 0)) {
155  res.addEntry("EUKN", "Failed to create stdout pipe",
156  {{"Error Message", GetLastErrorAsString()}});
157  return res;
158  }
159  // make sure the read handle isn't inhereited
160  if (!SetHandleInformation(mPimpl->StdOut_Read, HANDLE_FLAG_INHERIT, 0)) {
161  res.addEntry("EUKN", "Failed to SetHandleInformation",
162  {{"Error Message", GetLastErrorAsString()}});
163  return res;
164  }
165 
166  // create stderr pipe
167  if (!CreatePipe(&mPimpl->StdErr_Read, &stderrWrite, &secAttributes, 0)) {
168  res.addEntry("EUKN", "Failed to create stderr pipe",
169  {{"Error Message", GetLastErrorAsString()}});
170  return res;
171  }
172  // make sure the read handle isn't inhereited
173  if (!SetHandleInformation(mPimpl->StdErr_Read, HANDLE_FLAG_INHERIT, 0)) {
174  res.addEntry("EUKN", "Failed to SetHandleInformation",
175  {{"Error Message", GetLastErrorAsString()}});
176  return res;
177  }
178 
179  // create stdin pipe
180  if (!CreatePipe(&stdinRead, &mPimpl->StdIn_Write, &secAttributes, 0)) {
181  res.addEntry("EUKN", "Failed to create stdin pipe",
182  {{"Error Message", GetLastErrorAsString()}});
183  return res;
184  }
185  // make sure the read handle isn't inhereited
186  if (!SetHandleInformation(mPimpl->StdIn_Write, HANDLE_FLAG_INHERIT, 0)) {
187  res.addEntry("EUKN", "Failed to SetHandleInformation",
188  {{"Error Message", GetLastErrorAsString()}});
189  return res;
190  }
191 
192  // finally start the process
194 
195  // create commandline
196  std::wstring commandLine = L'"' + mExePath.wstring() + L'"';
197 
198  for (const auto& cmd : mArguments) {
199  std::wstring wCmd;
200  std::copy(cmd.begin(), cmd.end(), std::back_inserter(wCmd));
201 
202  boost::algorithm::replace_all(wCmd, L"\"", L"\\\"");
203 
204  commandLine += L" \"" + wCmd + L'"';
205  }
206 
207  ZeroMemory(&mPimpl->procInfo, sizeof(PROCESS_INFORMATION));
208 
209  // setup startupInfo
210  STARTUPINFOW startupInfo;
211  ZeroMemory(&startupInfo, sizeof(startupInfo));
212 
213  startupInfo.cb = sizeof(startupInfo);
214  startupInfo.hStdError = stderrWrite;
215  startupInfo.hStdOutput = stdoutWrite;
216  startupInfo.hStdInput = stdinRead;
217  startupInfo.dwFlags |= STARTF_USESTDHANDLES;
218 
219  // create the process
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()}});
224  return res;
225  }
226 
227  // Close the thread handle, we don't need it
228  CloseHandle(mPimpl->procInfo.hThread);
229 
230  // close the handles to the ends of the pipe we don't use
231  CloseHandle(stderrWrite);
232  CloseHandle(stdoutWrite);
233  CloseHandle(stdinRead);
234 
235  // start threads for listening for input
236  mPimpl->stdoutThread = std::thread([this]() {
237  while (true) {
238  DWORD bytesRead;
239 
240  std::vector<char> buffer;
241  buffer.resize(8192);
242 
243  if (!ReadFile(mPimpl->StdOut_Read, buffer.data(), buffer.size(), &bytesRead, nullptr)) {
244  return;
245  }
246 
247  if (bytesRead == 0) { return; }
248 
249  // send it
250  if (mStdOutHandler) { mStdOutHandler(buffer.data(), bytesRead); }
251  }
252  });
253 
254 #ifndef NDEBUG
255  {
256  auto threadName = mExePath.filename().string() + " stdout reader";
257 
258  // name the thread for debugging
259  SetThreadName(mPimpl->stdoutThread.native_handle(), threadName.c_str());
260  }
261 #endif
262 
263  // start threads for listening for input
264  mPimpl->stderrThread = std::thread([this]() {
265 
266  while (true) {
267  DWORD bytesRead;
268 
269  std::vector<char> buffer;
270  buffer.resize(8192);
271 
272  if (!ReadFile(mPimpl->StdErr_Read, buffer.data(), buffer.size(), &bytesRead, nullptr)) {
273  return;
274  }
275 
276  if (bytesRead == 0) { return; }
277 
278  // send it
279  if (mStdErrHandler) { mStdErrHandler(buffer.data(), bytesRead); }
280  }
281 
282  });
283 
284 #ifndef NDEBUG
285  {
286  auto threadName = mExePath.filename().string() + " stderr reader";
287 
288  // name the thread for debugging
289  SetThreadName(mPimpl->stderrThread.native_handle(), threadName.c_str());
290  }
291 #endif
292 
293  mStarted = true;
294 
295  return res;
296 }
297 
298 void Subprocess::kill() {
299  if (running()) { TerminateProcess(mPimpl->procInfo.hProcess, 9); }
300 }
301 
302 void Subprocess::wait() {
303  assert(started() && "Cannot wait for a process before it's started");
304 
305  // wait for it to complete
306  WaitForSingleObject(mPimpl->procInfo.hProcess, INFINITE);
307 }
308 
310  wait();
311 
312  DWORD exitCode = 0;
313  if (!GetExitCodeProcess(mPimpl->procInfo.hProcess, &exitCode)) {
314  return -1; // what should this be?
315  }
316 
317  return exitCode;
318 }
319 
320 bool Subprocess::running() {
321  assert(started() && "Must start a process before checking if it's running");
322 
323  if (mPimpl->procInfo.hProcess == 0) { return false; }
324  DWORD exitCode = 0;
325  if (!GetExitCodeProcess(mPimpl->procInfo.hProcess, &exitCode)) { return false; }
326 
327  return exitCode == STILL_ACTIVE;
328 }
329 
330 } // namespace chi
331 
332 // POSIX implementation
333 #else
334 
335 #include <signal.h>
336 #include <sys/wait.h>
337 #include <unistd.h>
338 
339 namespace chi {
340 
341 struct Subprocess::Implementation {
342  std::array<int, 2> stdinPipe = {{-1, -1}};
343 
344  std::array<int, 2> stdoutPipe = {{-1, -1}};
345  std::array<int, 2> stderrPipe = {{-1, -1}};
346 
347  int childPID = -1;
348 
349  std::thread stdoutThread;
350  std::thread stderrThread;
351 };
352 
354  wait();
355 
356  // close the FDs
357 
358  if (isStdInClosed()) {
359  close(mPimpl->stdinPipe[1]);
360  mPimpl->stdinPipe[1] = -1;
361  }
362 
363  close(mPimpl->stdoutPipe[0]);
364  mPimpl->stdoutPipe[0] = -1;
365 
366  close(mPimpl->stderrPipe[0]);
367  mPimpl->stderrPipe[0] = -1;
368 
369  if (mPimpl->stdoutThread.joinable()) { mPimpl->stdoutThread.join(); }
370 
371  if (mPimpl->stderrThread.joinable()) { mPimpl->stderrThread.join(); }
372 }
373 
374 Result Subprocess::pushToStdIn(const char* data, size_t size) {
375  assert(started() && "Process must be started to push to stdin");
376  assert(!isStdInClosed() && "Cannot push to stdin after closing stdin");
377 
378  Result res;
379 
380  if (write(mPimpl->stdinPipe[1], data, size) == -1) {
381  res.addEntry("EUKN", "Failed to write to stdin pipe", {{"Error message", strerror(errno)}});
382  return res;
383  }
384 
385  return res;
386 }
387 
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");
391 
392  Result res;
393 
394  if (close(mPimpl->stdinPipe[1]) == -1) {
395  res.addEntry("EUKN", "Failed to close stdin pipe", {{"Error Message", strerror(errno)}});
396  return res;
397  }
398  mPimpl->stdinPipe[1] = -1;
399 
400  mStdInClosed = true;
401 
402  return res;
403 }
404 
406  assert(!started() && "Cannot start a Subprocess twice");
407 
408  Result res;
409 
410  // create pipes
411  if (pipe(mPimpl->stdinPipe.data()) != 0) {
412  res.addEntry("EUKN", "Failed to create stdin pipe", {{"Error message", strerror(errno)}});
413  return res;
414  }
415  if (pipe(mPimpl->stdoutPipe.data()) != 0) {
416  res.addEntry("EUKN", "Failed to create stdout pipe", {{"Error message", strerror(errno)}});
417  return res;
418  }
419  if (pipe(mPimpl->stderrPipe.data()) != 0) {
420  res.addEntry("EUKN", "Failed to create stderr pipe", {{"Error message", strerror(errno)}});
421  return res;
422  }
423 
424  // fork!
425  mPimpl->childPID = fork();
426 
427  if (mPimpl->childPID == -1) {
428  res.addEntry("EUKN", "Failed to fork", {{"Error message", strerror(errno)}});
429  return res;
430  }
431 
432  // child process
433  if (mPimpl->childPID == 0) {
434  boost::filesystem::current_path(mWorkingDir);
435 
436  // make read end of stdin pipe the stdin stream, and same for the other pipes
437  dup2(mPimpl->stdinPipe[0], 0);
438  dup2(mPimpl->stdoutPipe[1], 1);
439  dup2(mPimpl->stderrPipe[1], 2);
440 
441  // close the other fds, we don't need them
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]);
448 
449  // close open fds for the process (other than 0, 1, and 2 which are the std streams)
450  // https://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor/899533#899533
451  int fd_max = static_cast<int>(sysconf(_SC_OPEN_MAX)); // truncation is safe
452  for (int fd = 3; fd < fd_max; fd++) close(fd);
453 
454  // set group
455  setpgid(0, 0);
456 
457  // make argv
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);
463 
464  // run the process
465  execvp(mExePath.c_str(), argv.data());
466 
467  _exit(EXIT_FAILURE);
468  }
469 
470  // parent process
471 
472  // close the ends of the pipes we don't use
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;
479 
480  // start threads
481  mPimpl->stdoutThread = std::thread([this] {
482 
483  std::array<char, 4096> buffer;
484  while (true) {
485  // read from stdout
486  ssize_t bytesRead = read(mPimpl->stdoutPipe[0], buffer.data(), buffer.size());
487 
488  // error
489  if (bytesRead == -1) { return; }
490 
491  // 0 is EOF
492  if (bytesRead == 0) { return; }
493 
494  if (mStdOutHandler) { mStdOutHandler(buffer.data(), bytesRead); }
495  }
496  });
497 
498  mPimpl->stderrThread = std::thread([this] {
499 
500  std::array<char, 4096> buffer;
501  while (true) {
502  // read from stdout
503  ssize_t bytesRead = read(mPimpl->stderrPipe[0], buffer.data(), buffer.size());
504 
505  // error
506  if (bytesRead == -1) { return; }
507 
508  // 0 is EOF
509  if (bytesRead == 0) { return; }
510 
511  if (mStdErrHandler) { mStdErrHandler(buffer.data(), bytesRead); }
512  }
513  });
514 
515  mStarted = true;
516 
517  return {};
518 }
519 
521  assert(started() && "Cannot kill a process if it never started");
522  ::kill(mPimpl->childPID, SIGINT);
523 }
524 
526  assert(started() && "Cannot wait for a process before it's started");
527 
528  int exit_status;
529  waitpid(mPimpl->childPID, &exit_status, 0);
530 
531  if (WIFEXITED(exit_status)) { mExitCode = WEXITSTATUS(exit_status); }
532 }
533 
535  assert(started() && "Cannot get the exit code of a process before it's started");
536 
537  if (mExitCode) { return *mExitCode; }
538 
539  int exit_status;
540  if (waitpid(mPimpl->childPID, &exit_status, 0) == -1) { return -1; }
541 
542  if (WIFEXITED(exit_status)) {
543  mExitCode = WEXITSTATUS(exit_status);
544  return *mExitCode;
545  }
546 
547  return -1;
548 }
549 
551  assert(started() && "Must start a process before checking if it's running");
552 
553  int exit_status;
554 
555  // WNOHANG makes sure it doesn't wait until the process is done
556  int status = waitpid(mPimpl->childPID, &exit_status, WNOHANG);
557  if (status == -1) {
558  return false; // error
559  }
560  if (status == 0) { return true; }
561  if (WIFEXITED(exit_status)) { mExitCode = int(WEXITSTATUS(exit_status)); }
562  return false;
563 }
564 
565 } // namespace chi
566 
567 #endif
568 
569 namespace chi {
570 
571 // Common functions
572 Subprocess::Subprocess(const boost::filesystem::path& path)
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");
576 }
577 
578 } // namespace chi
~Subprocess()
Wait for the process to exit and close all open handles.
Definition: Subprocess.cpp:353
void addEntry(const char *ec, const char *overview, nlohmann::json data)
Add a entry to the result, either a warning or an error.
Definition: Result.cpp:52
void wait()
Wait for the process to complete.
Definition: Subprocess.cpp:525
Defines the Result class and related functions.
int exitCode()
Wait and gets the exit code.
Definition: Subprocess.cpp:534
Result closeStdIn()
Close the STDIN stream (send an EOF)
Definition: Subprocess.cpp:388
Subprocess(const boost::filesystem::path &pathToExecutable)
Construct a Subprocess with a path to an executable.
Definition: Subprocess.cpp:572
Result pushToStdIn(const char *data, size_t size)
Pushes data to the stdin stream of the child process.
Definition: Subprocess.cpp:374
bool running()
Check if the process is still running.
Definition: Subprocess.cpp:550
Result start()
Start the process.
Definition: Subprocess.cpp:405
void kill()
Kill the child process On POSIX, this sends SIGINT.
Definition: Subprocess.cpp:520
The namespace where chigraph lives.
The result object, used for identifiying errors with good diagnostics.
Definition: Result.hpp:72