Implement GetExternalCommandOutput() on windows

This commit is contained in:
Adrian Ebeling 2019-01-20 13:37:48 +01:00
parent f45b0f9c77
commit 4f79a96660

View File

@ -58,9 +58,171 @@ void FreeUnusedMemory() {}
// TODO Wait for debugger to attach
void TraceMe() {}
class Utf8To16 {
public:
Utf8To16(const char *utf8String);
operator const wchar_t * () const {return m_str.c_str();}
operator const std::wstring & () const {return m_str;}
const std::wstring &asWstring() const {return m_str;}
const wchar_t *asWchar_t() const {return m_str.c_str();}
private:
std::wstring m_str;
};
Utf8To16::Utf8To16(const char *utf8Str) {
const std::size_t inSize = ::strlen(utf8Str);
// find out how many utf16 characters we need
const int requiredU16Chars =
MultiByteToWideChar(CP_UTF8, 0, utf8Str, inSize, nullptr, 0);
m_str.resize(requiredU16Chars);
// finally, do the conversion
MultiByteToWideChar(CP_UTF8, 0, utf8Str, inSize, &m_str[0], m_str.size());
}
void closeHandleIfValid(HANDLE &f_handle) {
if(f_handle != INVALID_HANDLE_VALUE) {
CloseHandle(f_handle);
f_handle = INVALID_HANDLE_VALUE;
}
}
std::string readAllFromPipe(HANDLE pipe) {
std::string ret;
const int BUFFER_SIZE = 1024;
CHAR buf[BUFFER_SIZE];
DWORD bytesRead = 0;
bool moreToRead = true;
while (moreToRead) {
if (ReadFile(pipe, buf, BUFFER_SIZE, &bytesRead, NULL)) {
ret.append(buf, bytesRead);
} else {
const DWORD err = GetLastError();
if(err == ERROR_BROKEN_PIPE) {
// child process terminated (this is not an error)
} else {
LOG_S(ERROR) << "Error while reading from child process: " << err;
}
moreToRead = false;
}
}
return ret;
}
std::string GetExternalCommandOutput(const std::vector<std::string> &command,
std::string_view input) {
return "";
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(saAttr);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = nullptr;
enum { READ_END, WRITE_END, NUM_HANDLES };
HANDLE stdIn[NUM_HANDLES];
HANDLE stdOut[NUM_HANDLES];
HANDLE stdErr[NUM_HANDLES];
// create anonymous pipes for the child process
if (!CreatePipe(&stdIn[READ_END], &stdIn[WRITE_END], &saAttr, 0) ||
!CreatePipe(&stdOut[READ_END], &stdOut[WRITE_END], &saAttr, 0) ||
!CreatePipe(&stdErr[READ_END], &stdErr[WRITE_END], &saAttr, 0)) {
LOG_S(ERROR) << "Error creating pipes for child process";
return "";
}
// the child is not supposed to gain access to the pipes' parent end
if(!SetHandleInformation(stdIn[WRITE_END], HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(stdOut[READ_END], HANDLE_FLAG_INHERIT, 0) ||
!SetHandleInformation(stdErr[READ_END], HANDLE_FLAG_INHERIT, 0))
{
LOG_S(ERROR) << "Error in SetHandleInformation: " << GetLastError();
return "";
}
// set up STARTUPINFO structure. It tells CreateProcess to use the pipes
// we just created as stdin and stdout for the new process.
STARTUPINFOW siStartInfo;
memset(&siStartInfo, 0, sizeof(siStartInfo));
siStartInfo.cb = sizeof(siStartInfo);
siStartInfo.hStdInput = stdIn[READ_END];
siStartInfo.hStdOutput = stdOut[WRITE_END];
siStartInfo.hStdError = stdErr[WRITE_END];
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION process;
memset(&process, 0, sizeof(process));
// fold the command line parts into a single string, quoting the arguments
std::string cmdString;
for (int i = 0; i < command.size(); i++) {
cmdString += ((i > 0) ? " \"" : "\"") + command[i] + "\"";
}
std::wstring cmdString_u16 = Utf8To16(cmdString.c_str());
if(!CreateProcessW(NULL, // app name: we pass it through lpCommandLine
&cmdString_u16[0],
NULL, // security attrs
NULL, // thread security attrs
TRUE, // handles are inherited
CREATE_UNICODE_ENVIRONMENT, // creation flags
NULL, // environment
NULL, // cwd
&siStartInfo, // in: stdin, stdout, stderr pipes
&process // out: info about the new process
)) {
LOG_S(ERROR) << "Error creating process " << GetLastError();
return "";
}
// we need to close our handles to the write end of these pipe. Otherwise,
// ReadFile() will not return when the child process terminates.
closeHandleIfValid(stdOut[WRITE_END]);
closeHandleIfValid(stdErr[WRITE_END]);
closeHandleIfValid(stdIn[READ_END]);
// write input data to process' stdin
DWORD numBytesOut = 0;
WriteFile(stdIn[WRITE_END], input.data(), input.size(), &numBytesOut, NULL);
closeHandleIfValid(stdIn[WRITE_END]); // close the handle to signal data end
// wait for the process to finish
if(process.hProcess != INVALID_HANDLE_VALUE) {
DWORD res = WaitForSingleObject(process.hProcess, INFINITE);
if(res != WAIT_OBJECT_0) {
LOG_S(ERROR) << "Error waiting for process to finish: " << res;
return "";
}
}
// Process return code is not used at the moment, but if it's required later,
// here's the necessary code for it:
// DWORD retCode;
// GetExitCodeProcess(process.hProcess, &retCode);
// read all stdout/stderr that the process has produced:
std::string output = readAllFromPipe(stdOut[READ_END]);
std::string err = readAllFromPipe(stdErr[READ_END]);
if (err.size() > 0) {
LOG_S(ERROR) << "Stderr from child process:\n" << err;
}
// close all the handles
for(int i=0; i<NUM_HANDLES; i++) {
closeHandleIfValid(stdIn[i]);
closeHandleIfValid(stdOut[i]);
closeHandleIfValid(stdErr[i]);
}
closeHandleIfValid(process.hThread);
closeHandleIfValid(process.hProcess);
return output;
}
void SpawnThread(void *(*fn)(void *), void *arg) {