class ConStream : public st::Stream
{
public:
ConStream();
~ConStream() override;
bool open(
const std::wstring& aFname,
const std::wstring& aCmdLine,
const std::wstring& aLog);
// Console does not have remainder.
st::Pos remainder() override { return 0; }
// Only when we bumped into end.
size_t eof() override { return fIsEof; }
size_t readNe (void* aBuf, size_t aSize) override;
size_t writeNe(const void* aBuf, size_t aSize) override;
void flush() override;
bool isOpen() override { return fIsOpen; }
bool readln(std::string& s);
void endInput();
// Shall we implement close?
//void close() override {}
// Implemented capabilities
unsigned long caps() const override
{ return st::cRead | st::cWrite | st::cMultiplex | st::cRealtime
| st::cSizeUnaware; }
private:
st::AsyncFile fLog;
bool fIsOpen = false;
bool fIsEof = false;
bool fFoundCr = false;
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE hStdOutInside, hStdOutOutside;
HANDLE hStdInInside, hStdInOutside;
//HANDLE hStdErrInside, hStdErrOutside;
//bool readln(std::string& s);
};
ConStream::ConStream()
{
SECURITY_ATTRIBUTES sa;
// Security attributes
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = nullptr;
sa.bInheritHandle = true; // Help says the handle will be inherited
SECURITY_DESCRIPTOR sd;
if (isWinNT()) { // security initialization for Windows NT
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, true, nullptr, false);
sa.lpSecurityDescriptor = &sd;
}
// Stdout pipe
// read = outside, write = inside
CreatePipe(&hStdOutOutside, &hStdOutInside, &sa, 0);
SetHandleInformation(hStdOutOutside, HANDLE_FLAG_INHERIT, 0);
// Stderr pipe
// read = outside, write = inside
//CreatePipe(&hStdErrOutside, &hStdErrInside, &sa, 1024*64); // enough for stderr?
//SetHandleInformation(hStdErrOutside, HANDLE_FLAG_INHERIT, 0);
// Stdin pipe
// read = inside, write = outside
CreatePipe(&hStdInInside, &hStdInOutside, &sa, 1024*10);
SetHandleInformation(hStdInOutside, HANDLE_FLAG_INHERIT, 0);
}
void ConStream::endInput()
{
if (hStdInOutside != INVALID_HANDLE_VALUE) {
CloseHandle(hStdInOutside);
hStdInOutside = INVALID_HANDLE_VALUE;
}
}
ConStream::~ConStream()
{
if (fIsOpen) {
if (WaitForSingleObject(pi.hProcess, 2000) == WAIT_TIMEOUT) {
TerminateProcess(pi.hProcess, 0);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
CloseHandle(hStdInInside);
endInput();
//CloseHandle(hStdErrOutside);
//CloseHandle(hStdErrInside);
CloseHandle(hStdOutOutside);
CloseHandle(hStdOutInside);
}
bool ConStream::open(
const std::wstring& aFname,
const std::wstring& aCmdLine,
const std::wstring& aLog)
{
if (fIsOpen)
return false;
// Fill startup info
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
size_t n = aFname.length() + aCmdLine.length() + 10;
si.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdOutput = hStdOutInside;
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.hStdInput = hStdInInside;
si.wShowWindow = SW_HIDE;
std::wstring ws;
ws.reserve(n);
ws += L'"';
ws += aFname;
ws += L"\" ";
ws += aCmdLine;
fIsOpen = CreateProcess(
nullptr, // lpApplicationName
&*ws.begin(), // lpCommandLine
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
TRUE, // bInheritHandles
0, // dwCreationFlags
nullptr, // lpEnvironment
nullptr, // lpCurrentDirectory
&si, // lpStartupInfo
&pi ); // lpProcessInformation
if (fIsOpen && !aLog.empty())
fLog.open(aLog.c_str(), st::File::omNew);
return fIsOpen;
}
void ConStream::flush()
{
}
size_t ConStream::readNe (void* aBuf, size_t aSize)
{
if (!fIsOpen)
return 0;
DWORD nRead;
if (!ReadFile(hStdOutOutside, aBuf, static_cast<DWORD>(aSize), &nRead, nullptr))
return 0;
if (fLog.isOpen())
fLog.write(aBuf, nRead);
return nRead;
}
// Implemented write
size_t ConStream::writeNe(const void* aBuf, size_t aSize)
{
if (!fIsOpen)
return 0;
DWORD nWritten;
if (!WriteFile(hStdInOutside, aBuf, static_cast<DWORD>(aSize), &nWritten, nullptr))
return 0;
return nWritten;
}
bool ConStream::readln(std::string& s)
{
if (!fIsOpen)
return false;
s.clear();
enum { SZ = 256, TIME = 30 };
char buf[SZ + 2];
DWORD nRead, nm1, nm2;
while (true) {
if (!PeekNamedPipe(hStdOutOutside, buf, SZ, &nRead, &nm1, &nm2))
throwWinError();
// Read nothing — maybe, the program has finished?
if (nRead == 0) {
if (WaitForSingleObject(pi.hProcess, TIME) == WAIT_OBJECT_0) {
// Try again
if (!PeekNamedPipe(hStdOutOutside, buf, SZ, &nRead, &nm1, &nm2))
throwWinError();
if (nRead == 0)
return !s.empty();
// otherwise fall out
}
}
const char* start = buf;
const char* end = buf + nRead;
if (fFoundCr && *start == '\n') {
++start;
fFoundCr = false;
}
for (const char* p = start; p != end; ++p) {
switch (*p) {
case '\r':
fFoundCr = true;
// fall through
case '\n':
s.append(start, p);
// Read until CR
++p;
skipRead(p - buf);
return true;
default: ;
}
}
// Copy the entire buffer and go on
s.append(start, end);
skipRead(end - buf);
}
}