Checkpoint work on libpconsole.
This commit is contained in:
parent
a53fcbbea6
commit
40c7e87528
@ -3,24 +3,12 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
class AgentMsg
|
||||
struct AgentMsg
|
||||
{
|
||||
public:
|
||||
enum Type {
|
||||
InputRecord,
|
||||
WindowSize,
|
||||
SetAutoShutDownFlag
|
||||
StartProcess,
|
||||
SetSize
|
||||
};
|
||||
|
||||
Type type;
|
||||
union {
|
||||
INPUT_RECORD inputRecord;
|
||||
struct {
|
||||
unsigned short cols;
|
||||
unsigned short rows;
|
||||
} windowSize;
|
||||
BOOL flag;
|
||||
} u;
|
||||
};
|
||||
|
||||
#endif // AGENTMSG_H
|
||||
|
80
Shared/Buffer.h
Normal file
80
Shared/Buffer.h
Normal file
@ -0,0 +1,80 @@
|
||||
#ifndef BUFFER_H
|
||||
#define BUFFER_H
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
||||
struct WriteBuffer
|
||||
{
|
||||
private:
|
||||
std::stringstream ss;
|
||||
public:
|
||||
void putInt(int i);
|
||||
void putWString(const std::wstring &str);
|
||||
void putWString(const wchar_t *str);
|
||||
std::string str() const;
|
||||
};
|
||||
|
||||
inline void WriteBuffer::putInt(int i)
|
||||
{
|
||||
ss.write((const char*)&i, sizeof(i));
|
||||
}
|
||||
|
||||
inline void WriteBuffer::putWString(const std::wstring &str)
|
||||
{
|
||||
putInt(str.size());
|
||||
ss.write((const char*)str.c_str(), sizeof(wchar_t) * str.size());
|
||||
}
|
||||
|
||||
inline void WriteBuffer::putWString(const wchar_t *str)
|
||||
{
|
||||
int len = wcslen(str);
|
||||
putInt(len);
|
||||
ss.write((const char*)str, sizeof(wchar_t) * len);
|
||||
}
|
||||
|
||||
inline std::string WriteBuffer::str() const
|
||||
{
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
struct ReadBuffer
|
||||
{
|
||||
private:
|
||||
std::stringstream ss;
|
||||
public:
|
||||
ReadBuffer(const std::string &packet);
|
||||
int getInt();
|
||||
std::wstring getWString();
|
||||
void assertEof();
|
||||
};
|
||||
|
||||
inline ReadBuffer::ReadBuffer(const std::string &packet) : ss(packet)
|
||||
{
|
||||
}
|
||||
|
||||
inline int ReadBuffer::getInt()
|
||||
{
|
||||
int i;
|
||||
ss.read((char*)&i, sizeof(i));
|
||||
return i;
|
||||
}
|
||||
|
||||
inline std::wstring ReadBuffer::getWString()
|
||||
{
|
||||
int len = getInt();
|
||||
wchar_t *tmp = new wchar_t[len];
|
||||
ss.read((char*)tmp, sizeof(wchar_t) * len);
|
||||
std::wstring ret(tmp, len);
|
||||
delete [] tmp;
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline void ReadBuffer::assertEof()
|
||||
{
|
||||
ss.peek();
|
||||
assert(ss.eof());
|
||||
}
|
||||
|
||||
#endif /* BUFFER_H */
|
@ -2,6 +2,7 @@
|
||||
#define PCONSOLE_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef PCONSOLE
|
||||
#define PCONSOLE_API __declspec(dllexport)
|
||||
@ -44,20 +45,6 @@ typedef void (*pconsole_process_exit_cb)(pconsole_t *pconsole,
|
||||
*/
|
||||
PCONSOLE_API pconsole_t *pconsole_open(int cols, int rows);
|
||||
|
||||
/*
|
||||
* Sets the I/O callback, which may be NULL. The callback is called:
|
||||
* - after new data is available for reading, AND
|
||||
* - after the output queue size decreases
|
||||
*/
|
||||
PCONSOLE_API void pconsole_set_io_cb(pconsole_t *pconsole, pconsole_io_cb cb);
|
||||
|
||||
/*
|
||||
* Sets the process exit callback, which may be NULL. The callback is
|
||||
* called when a process started with pconsole_start_process exits.
|
||||
*/
|
||||
PCONSOLE_API void pconsole_set_process_exit_cb(pconsole_t *pconsole,
|
||||
pconsole_process_exit_cb cb);
|
||||
|
||||
/*
|
||||
* Start a child process. Either (but not both) of program and cmdline may
|
||||
* be NULL. cwd and env may be NULL. env is a pointer to a NULL-terminated
|
||||
@ -66,43 +53,33 @@ PCONSOLE_API void pconsole_set_process_exit_cb(pconsole_t *pconsole,
|
||||
* This function never modifies the cmdline, unlike CreateProcess.
|
||||
*
|
||||
* Only one child process may be started. After the child process exits, the
|
||||
* agent will flush and close its output buffer.
|
||||
* agent will flush and close the data pipe.
|
||||
*/
|
||||
PCONSOLE_API int pconsole_start_process(pconsole_t *pconsole,
|
||||
PCONSOLE_API int pconsole_start_process(pconsole_t *pc,
|
||||
const wchar_t *program,
|
||||
const wchar_t *cmdline,
|
||||
const wchar_t *cwd,
|
||||
const wchar_t *const *env);
|
||||
|
||||
/*
|
||||
* Reads pty-like input. Returns -1 if no data available, 0 if the pipe
|
||||
* is closed, and the amount of data read otherwise.
|
||||
*/
|
||||
PCONSOLE_API int pconsole_read(pconsole_t *pconsole, void *buffer, int size);
|
||||
PCONSOLE_API int pconsole_get_exit_code(pconsole_t *pc);
|
||||
|
||||
PCONSOLE_API int pconsole_flush_and_close(pconsole_t *pc);
|
||||
|
||||
/*
|
||||
* Write input to the Win32 console. This input will be translated into
|
||||
* INPUT_RECORD objects. (TODO: What about Ctrl-C and ESC?)
|
||||
* Returns an overlapped-mode pipe handle that can be read and written
|
||||
* like a Unix terminal.
|
||||
*/
|
||||
PCONSOLE_API int pconsole_write(pconsole_t *pconsole,
|
||||
const void *buffer,
|
||||
int size);
|
||||
PCONSOLE_API HANDLE pconsole_get_data_pipe(pconsole_t *pc);
|
||||
|
||||
/*
|
||||
* Change the size of the Windows console.
|
||||
*/
|
||||
PCONSOLE_API int pconsole_set_size(pconsole_t *pconsole, int cols, int rows);
|
||||
|
||||
/*
|
||||
* Gets the amount of data queued for output to the pconsole. Use this API to
|
||||
* limit the size of the output buffer.
|
||||
*/
|
||||
PCONSOLE_API int pconsole_get_output_queue_size(pconsole_t *pconsole);
|
||||
PCONSOLE_API int pconsole_set_size(pconsole_t *pc, int cols, int rows);
|
||||
|
||||
/*
|
||||
* Closes the pconsole.
|
||||
*/
|
||||
PCONSOLE_API void pconsole_close(pconsole_t *pconsole);
|
||||
PCONSOLE_API void pconsole_close(pconsole_t *pc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -21,3 +21,5 @@ $(LIBRARY) : $(OBJECTS)
|
||||
.PHONY : clean
|
||||
clean :
|
||||
rm -f $(LIBRARY) *.o *.d
|
||||
|
||||
-include $(OBJECTS:.o=.d)
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include "../Shared/DebugClient.h"
|
||||
#include "../Shared/AgentMsg.h"
|
||||
#include "../Shared/Buffer.h"
|
||||
|
||||
// TODO: Error handling, handle out-of-memory.
|
||||
|
||||
@ -14,34 +16,13 @@
|
||||
static volatile LONG consoleCounter;
|
||||
const int bufSize = 4096;
|
||||
|
||||
static WINAPI DWORD serviceThread(void *threadParam);
|
||||
|
||||
struct pconsole_s {
|
||||
pconsole_s();
|
||||
HANDLE controlPipe;
|
||||
HANDLE dataPipe;
|
||||
int agentPid;
|
||||
|
||||
char dataWriteBuffer[bufSize];
|
||||
int dataWriteAmount;
|
||||
char dataReadBuffer[bufSize];
|
||||
int dataReadStart;
|
||||
int dataReadAmount;
|
||||
OVERLAPPED dataReadOver;
|
||||
OVERLAPPED dataWriteOver;
|
||||
HANDLE dataReadEvent;
|
||||
HANDLE dataWriteEvent;
|
||||
bool dataReadPending;
|
||||
bool dataWritePending;
|
||||
|
||||
DWORD serviceThreadId;
|
||||
HANDLE ioEvent;
|
||||
bool ioCallbackFlag;
|
||||
pconsole_io_cb ioCallback;
|
||||
|
||||
CRITICAL_SECTION lock;
|
||||
};
|
||||
|
||||
pconsole_s::pconsole_s() : dataPipe(NULL), agentPid(-1)
|
||||
pconsole_s::pconsole_s() : controlPipe(NULL), dataPipe(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
@ -90,18 +71,11 @@ static bool pathExists(const std::wstring &path)
|
||||
return GetFileAttributes(path.c_str()) != 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
PCONSOLE_API
|
||||
pconsole_s *pconsole_open(int cols, int rows)
|
||||
static std::wstring findAgentProgram()
|
||||
{
|
||||
BOOL success;
|
||||
|
||||
pconsole_s *pconsole = new pconsole_s;
|
||||
|
||||
// Look for the Agent executable.
|
||||
std::wstring progDir = dirname(getModuleFileName(getCurrentModule()));
|
||||
std::wstring agentProgram;
|
||||
if (pathExists(progDir + L"\\"AGENT_EXE)) {
|
||||
agentProgram = progDir + L"\\"AGENT_EXE;
|
||||
return progDir + L"\\"AGENT_EXE;
|
||||
} else {
|
||||
// The development directory structure looks like this:
|
||||
// root/
|
||||
@ -109,39 +83,65 @@ pconsole_s *pconsole_open(int cols, int rows)
|
||||
// pconsole-agent.exe
|
||||
// libpconsole/
|
||||
// pconsole.dll
|
||||
agentProgram = dirname(progDir) + L"\\agent\\"AGENT_EXE;
|
||||
std::wstring agentProgram = dirname(progDir) + L"\\agent\\"AGENT_EXE;
|
||||
if (!pathExists(agentProgram)) {
|
||||
assert(false);
|
||||
}
|
||||
return agentProgram;
|
||||
}
|
||||
}
|
||||
|
||||
// Start a named pipe server.
|
||||
std::wstringstream serverNameStream;
|
||||
serverNameStream << L"\\\\.\\pipe\\pconsole-" << GetCurrentProcessId()
|
||||
<< L"-" << InterlockedIncrement(&consoleCounter);
|
||||
std::wstring serverName = serverNameStream.str();
|
||||
pconsole->dataPipe = CreateNamedPipe(serverName.c_str(),
|
||||
/*dwOpenMode=*/PIPE_ACCESS_DUPLEX |
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE |
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
/*dwPipeMode=*/0,
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/0,
|
||||
/*nInBufferSize=*/0,
|
||||
/*nDefaultTimeOut=*/3000,
|
||||
NULL);
|
||||
if (pconsole->dataPipe == INVALID_HANDLE_VALUE)
|
||||
return NULL;
|
||||
// Call ConnectNamedPipe and block, even for an overlapped pipe. If the
|
||||
// pipe is overlapped, create a temporary event for use connecting.
|
||||
static bool connectNamedPipe(HANDLE handle, bool overlapped)
|
||||
{
|
||||
OVERLAPPED over, *pover = NULL;
|
||||
if (overlapped) {
|
||||
pover = &over;
|
||||
memset(&over, 0, sizeof(over));
|
||||
over.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
assert(over.hEvent != NULL);
|
||||
}
|
||||
bool success = ConnectNamedPipe(handle, pover);
|
||||
if (overlapped && !success && GetLastError() == ERROR_IO_PENDING) {
|
||||
DWORD actual;
|
||||
success = GetOverlappedResult(handle, pover, &actual, TRUE);
|
||||
}
|
||||
if (!success && GetLastError() == ERROR_PIPE_CONNECTED)
|
||||
success = TRUE;
|
||||
if (overlapped)
|
||||
CloseHandle(over.hEvent);
|
||||
return success;
|
||||
}
|
||||
|
||||
static HANDLE createNamedPipe(const std::wstring &name, bool overlapped)
|
||||
{
|
||||
return CreateNamedPipe(name.c_str(),
|
||||
/*dwOpenMode=*/
|
||||
PIPE_ACCESS_DUPLEX |
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE |
|
||||
(overlapped ? FILE_FLAG_OVERLAPPED : 0),
|
||||
/*dwPipeMode=*/0,
|
||||
/*nMaxInstances=*/1,
|
||||
/*nOutBufferSize=*/0,
|
||||
/*nInBufferSize=*/0,
|
||||
/*nDefaultTimeOut=*/3000,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void startAgentProcess(std::wstring &controlPipeName,
|
||||
std::wstring &dataPipeName,
|
||||
int cols, int rows)
|
||||
{
|
||||
bool success;
|
||||
|
||||
std::wstring agentProgram = findAgentProgram();
|
||||
std::wstringstream agentCmdLineStream;
|
||||
agentCmdLineStream << L"\"" << agentProgram << L"\" "
|
||||
<< serverName << " "
|
||||
<< controlPipeName << dataPipeName << " "
|
||||
<< cols << " " << rows;
|
||||
std::wstring agentCmdLine = agentCmdLineStream.str();
|
||||
|
||||
Trace("Starting agent");
|
||||
//Trace("Starting Agent: [%s]", agentCmdLine.toStdString().c_str());
|
||||
|
||||
// Get a non-interactive window station for the agent.
|
||||
// TODO: review security w.r.t. windowstation and desktop.
|
||||
HWINSTA originalStation = GetProcessWindowStation();
|
||||
@ -163,40 +163,50 @@ pconsole_s *pconsole_open(int cols, int rows)
|
||||
STARTUPINFO sui;
|
||||
memset(&sui, 0, sizeof(sui));
|
||||
sui.cb = sizeof(sui);
|
||||
// TODO: Put this back.
|
||||
sui.lpDesktop = (LPWSTR)startupDesktop.c_str();
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
std::vector<wchar_t> cmdline(agentCmdLine.size() + 1);
|
||||
agentCmdLine.copy(&cmdline[0], agentCmdLine.size());
|
||||
cmdline[agentCmdLine.size()] = L'\0';
|
||||
success = CreateProcess(
|
||||
agentProgram.c_str(),
|
||||
&cmdline[0],
|
||||
NULL, NULL,
|
||||
/*bInheritHandles=*/FALSE,
|
||||
/*dwCreationFlags=*/CREATE_NEW_CONSOLE,
|
||||
NULL, NULL,
|
||||
&sui, &pi);
|
||||
success = CreateProcess(agentProgram.c_str(),
|
||||
&cmdline[0],
|
||||
NULL, NULL,
|
||||
/*bInheritHandles=*/FALSE,
|
||||
/*dwCreationFlags=*/CREATE_NEW_CONSOLE,
|
||||
NULL, NULL,
|
||||
&sui, &pi);
|
||||
if (!success) {
|
||||
// qFatal("Could not start agent subprocess.");
|
||||
assert(false);
|
||||
}
|
||||
pconsole->agentPid = pi.dwProcessId;
|
||||
//qDebug("New child process: PID %d", (int)m_agentProcess->dwProcessId);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
SetProcessWindowStation(originalStation);
|
||||
CloseDesktop(desktop);
|
||||
CloseWindowStation(station);
|
||||
}
|
||||
|
||||
// Connect the named pipe.
|
||||
OVERLAPPED over;
|
||||
memset(&over, 0, sizeof(over));
|
||||
over.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
assert(over.hEvent != NULL);
|
||||
success = ConnectNamedPipe(pconsole->dataPipe, &over);
|
||||
if (!success && GetLastError() == ERROR_IO_PENDING) {
|
||||
DWORD actual;
|
||||
success = GetOverlappedResult(pconsole->dataPipe, &over, &actual, TRUE);
|
||||
}
|
||||
if (!success && GetLastError() == ERROR_PIPE_CONNECTED)
|
||||
success = TRUE;
|
||||
PCONSOLE_API pconsole_s *pconsole_open(int cols, int rows)
|
||||
{
|
||||
pconsole_s *pc = new pconsole_s;
|
||||
|
||||
// Start pipes.
|
||||
std::wstringstream pipeName;
|
||||
pipeName << L"\\\\.\\pipe\\pconsole-" << GetCurrentProcessId()
|
||||
<< L"-" << InterlockedIncrement(&consoleCounter);
|
||||
std::wstring controlPipeName = pipeName.str() + L"-control";
|
||||
std::wstring dataPipeName = pipeName.str() + L"-data";
|
||||
pc->controlPipe = createNamedPipe(controlPipeName, false);
|
||||
pc->dataPipe = createNamedPipe(dataPipeName, true);
|
||||
|
||||
// Start the agent.
|
||||
startAgentProcess(controlPipeName, dataPipeName, cols, rows);
|
||||
|
||||
// Connect the pipes.
|
||||
bool success;
|
||||
success = connectNamedPipe(pc->controlPipe, false);
|
||||
assert(success);
|
||||
success = connectNamedPipe(pc->dataPipe, true);
|
||||
assert(success);
|
||||
|
||||
// TODO: Review security w.r.t. the named pipe. Ensure that we're really
|
||||
@ -207,351 +217,54 @@ pconsole_s *pconsole_open(int cols, int rows)
|
||||
// run the agent (and therefore the Win7 conhost) under the logged in user
|
||||
// for an SSH connection.
|
||||
|
||||
// TODO: error handling?
|
||||
CloseHandle(over.hEvent);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
SetProcessWindowStation(originalStation);
|
||||
CloseDesktop(desktop);
|
||||
CloseWindowStation(station);
|
||||
|
||||
// Create events.
|
||||
pconsole->ioEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
pconsole->dataReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
pconsole->dataWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
|
||||
InitializeCriticalSection(&pconsole->lock);
|
||||
|
||||
CreateThread(NULL, 0, serviceThread, pconsole, 0, &pconsole->serviceThreadId);
|
||||
|
||||
return pconsole;
|
||||
return pc;
|
||||
}
|
||||
|
||||
PCONSOLE_API void pconsole_set_io_cb(pconsole_t *pc, pconsole_io_cb cb)
|
||||
static void writePacket(pconsole_s *pc, const WriteBuffer &packet)
|
||||
{
|
||||
EnterCriticalSection(&pc->lock);
|
||||
pc->ioCallback = cb;
|
||||
LeaveCriticalSection(&pc->lock);
|
||||
std::string payload = packet.str();
|
||||
int payloadSize = payload.size();
|
||||
DWORD actual;
|
||||
BOOL success = WriteFile(pc->controlPipe, &payloadSize, sizeof(int), &actual, NULL);
|
||||
assert(success && actual == sizeof(int));
|
||||
success = WriteFile(pc->controlPipe, payload.c_str(), payloadSize, &actual, NULL);
|
||||
assert(success && actual == payloadSize);
|
||||
}
|
||||
|
||||
/*
|
||||
PCONSOLE_API void pconsole_set_process_exit_cb(pconsole_t *pconsole,
|
||||
pconsole_process_exit_cb cb)
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
*/
|
||||
|
||||
PCONSOLE_API int pconsole_start_process(pconsole_t *pconsole,
|
||||
PCONSOLE_API int pconsole_start_process(pconsole_t *pc,
|
||||
const wchar_t *program,
|
||||
const wchar_t *cmdline,
|
||||
const wchar_t *cwd,
|
||||
const wchar_t *const *env)
|
||||
{
|
||||
#if 0
|
||||
int ret = -1;
|
||||
|
||||
if (!FreeConsole())
|
||||
Trace("FreeConsole failed");
|
||||
if (!AttachConsole(console->agentPid))
|
||||
Trace("AttachConsole to pid %d failed", console->agentPid);
|
||||
|
||||
HANDLE conout1, conout2, conin;
|
||||
|
||||
{
|
||||
// TODO: Should the permissions be more restrictive?
|
||||
// TODO: Is this code necessary or even desirable? If I
|
||||
// don't change these handles, then are the old values
|
||||
// invalid? If so, what happens if I create a console
|
||||
// subprocess?
|
||||
// The handle must be inheritable. See comment below.
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.bInheritHandle = TRUE;
|
||||
conout1 = CreateFile(L"CONOUT$",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
&sa,
|
||||
OPEN_EXISTING,
|
||||
0, NULL);
|
||||
conout2 = CreateFile(L"CONOUT$",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
&sa,
|
||||
OPEN_EXISTING,
|
||||
0, NULL);
|
||||
conin = CreateFile(L"CONIN$",
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
&sa,
|
||||
OPEN_EXISTING,
|
||||
0, NULL);
|
||||
assert(conin != NULL);
|
||||
assert(conout1 != NULL);
|
||||
assert(conout2 != NULL);
|
||||
/*
|
||||
BOOL success;
|
||||
success = SetStdHandle(STD_OUTPUT_HANDLE, conout1);
|
||||
Q_ASSERT(success);
|
||||
success = SetStdHandle(STD_ERROR_HANDLE, conout2);
|
||||
Q_ASSERT(success);
|
||||
success = SetStdHandle(STD_INPUT_HANDLE, conin);
|
||||
Q_ASSERT(success);
|
||||
*/
|
||||
WriteBuffer packet;
|
||||
packet.putInt(AgentMsg::StartProcess);
|
||||
packet.putWString(program ? program : L"");
|
||||
packet.putWString(cmdline ? cmdline : L"");
|
||||
packet.putWString(cwd ? cwd : L"");
|
||||
int envCount = 0;
|
||||
if (env != NULL) {
|
||||
while (env[envCount] != NULL)
|
||||
envCount++;
|
||||
}
|
||||
|
||||
{
|
||||
wchar_t *cmdlineCopy = NULL;
|
||||
if (cmdline != NULL) {
|
||||
cmdlineCopy = new wchar_t[wcslen(cmdline) + 1];
|
||||
wcscpy(cmdlineCopy, cmdline);
|
||||
}
|
||||
STARTUPINFO sui;
|
||||
memset(&sui, 0, sizeof(sui));
|
||||
sui.cb = sizeof(sui);
|
||||
sui.dwFlags = STARTF_USESTDHANDLES;
|
||||
sui.hStdInput = conin;
|
||||
sui.hStdOutput = conout1;
|
||||
sui.hStdError = conout2;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
BOOL success = CreateProcess(
|
||||
program,
|
||||
cmdlineCopy,
|
||||
NULL,
|
||||
NULL,
|
||||
FALSE,
|
||||
0,
|
||||
NULL,
|
||||
cwd,
|
||||
&sui,
|
||||
&pi);
|
||||
delete [] cmdlineCopy;
|
||||
if (success) {
|
||||
ret = pi.dwProcessId;
|
||||
Trace("Started shell pid %d", (int)pi.dwProcessId);
|
||||
} else {
|
||||
Trace("Could not start shell");
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(conout1);
|
||||
CloseHandle(conout2);
|
||||
CloseHandle(conin);
|
||||
|
||||
FreeConsole();
|
||||
|
||||
/*
|
||||
// Now that the shell is started, tell the agent to shutdown when the
|
||||
// console has no more programs using it.
|
||||
AgentMsg msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.type = AgentMsg::SetAutoShutDownFlag;
|
||||
msg.u.flag = TRUE;
|
||||
writeMsg(msg);
|
||||
*/
|
||||
return ret;
|
||||
#endif
|
||||
return 0;
|
||||
packet.putInt(envCount);
|
||||
for (int envIndex = 0; envIndex < envCount; ++envIndex)
|
||||
packet.putWString(env[envIndex]);
|
||||
writePacket(pc, packet);
|
||||
}
|
||||
|
||||
// The lock should be acquired by the caller.
|
||||
static void completeRead(pconsole_s *pc, DWORD amount)
|
||||
PCONSOLE_API int pconsole_set_size(pconsole_s *pc, int cols, int rows)
|
||||
{
|
||||
pc->dataReadAmount += amount;
|
||||
pc->ioCallbackFlag = true;
|
||||
SetEvent(pc->ioEvent);
|
||||
WriteBuffer packet;
|
||||
packet.putInt(AgentMsg::SetSize);
|
||||
packet.putInt(cols);
|
||||
packet.putInt(rows);
|
||||
writePacket(pc, packet);
|
||||
}
|
||||
|
||||
// The lock should be acquired by the caller.
|
||||
static void pumpReadIo(pconsole_s *pc)
|
||||
PCONSOLE_API void pconsole_close(pconsole_s *pc)
|
||||
{
|
||||
while (!pc->dataReadPending && pc->dataReadAmount < bufSize / 2) {
|
||||
if (pc->dataReadStart > 0) {
|
||||
if (pc->dataReadAmount > 0) {
|
||||
memmove(pc->dataReadBuffer,
|
||||
pc->dataReadBuffer + pc->dataReadStart,
|
||||
pc->dataReadAmount);
|
||||
}
|
||||
pc->dataReadStart = 0;
|
||||
}
|
||||
memset(&pc->dataReadOver, 0, sizeof(pc->dataReadOver));
|
||||
pc->dataReadOver.hEvent = pc->dataReadEvent;
|
||||
DWORD amount;
|
||||
BOOL ret = ReadFile(pc->dataPipe,
|
||||
pc->dataReadBuffer + pc->dataReadAmount,
|
||||
bufSize - pc->dataReadAmount,
|
||||
&amount,
|
||||
&pc->dataReadOver);
|
||||
if (ret) {
|
||||
completeRead(pc, amount);
|
||||
} else if (GetLastError() == ERROR_IO_PENDING) {
|
||||
pc->dataReadPending = true;
|
||||
} else {
|
||||
// TODO: The pipe is broken.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The lock should be acquired by the caller.
|
||||
static void completeWrite(pconsole_s *pc, DWORD amount)
|
||||
{
|
||||
if (amount < pc->dataWriteAmount) {
|
||||
memmove(pc->dataWriteBuffer,
|
||||
pc->dataWriteBuffer + amount,
|
||||
pc->dataWriteAmount - amount);
|
||||
}
|
||||
pc->dataWriteAmount -= amount;
|
||||
pc->ioCallbackFlag = true;
|
||||
SetEvent(pc->ioEvent);
|
||||
}
|
||||
|
||||
// The lock should be acquired by the caller.
|
||||
static void pumpWriteIo(pconsole_s *pc)
|
||||
{
|
||||
while (!pc->dataWritePending && pc->dataWriteAmount > 0) {
|
||||
memset(&pc->dataWriteOver, 0, sizeof(pc->dataWriteOver));
|
||||
pc->dataWriteOver.hEvent = pc->dataWriteEvent;
|
||||
DWORD amount;
|
||||
BOOL ret = WriteFile(pc->dataPipe,
|
||||
pc->dataWriteBuffer,
|
||||
pc->dataWriteAmount,
|
||||
&amount,
|
||||
&pc->dataWriteOver);
|
||||
if (ret) {
|
||||
completeWrite(pc, amount);
|
||||
} else if (GetLastError() == ERROR_IO_PENDING) {
|
||||
pc->dataWritePending = true;
|
||||
} else {
|
||||
// TODO: The pipe is broken.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WINAPI DWORD serviceThread(void *threadParam)
|
||||
{
|
||||
pconsole_s *pc = (pconsole_s*)threadParam;
|
||||
HANDLE events[] = { pc->dataReadEvent, pc->dataWriteEvent, pc->ioEvent };
|
||||
while (true) {
|
||||
EnterCriticalSection(&pc->lock);
|
||||
|
||||
if (pc->dataReadPending) {
|
||||
DWORD amount;
|
||||
BOOL ret = GetOverlappedResult(pc->dataPipe, &pc->dataReadOver,
|
||||
&amount, FALSE);
|
||||
if (!ret && GetLastError() == ERROR_IO_INCOMPLETE) {
|
||||
// Keep waiting.
|
||||
} else if (!ret) {
|
||||
// TODO: Something is wrong.
|
||||
} else {
|
||||
completeRead(pc, amount);
|
||||
pc->dataReadPending = false;
|
||||
ResetEvent(pc->dataReadEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (pc->dataWritePending) {
|
||||
DWORD amount;
|
||||
BOOL ret = GetOverlappedResult(pc->dataPipe, &pc->dataWriteOver,
|
||||
&amount, FALSE);
|
||||
if (!ret && GetLastError() == ERROR_IO_INCOMPLETE) {
|
||||
// Keep waiting.
|
||||
} else if (!ret) {
|
||||
// TODO: Something is wrong.
|
||||
} else {
|
||||
completeWrite(pc, amount);
|
||||
pc->dataWritePending = false;
|
||||
ResetEvent(pc->dataWriteEvent);
|
||||
}
|
||||
}
|
||||
|
||||
pumpReadIo(pc);
|
||||
pumpWriteIo(pc);
|
||||
|
||||
ResetEvent(pc->ioEvent);
|
||||
pconsole_io_cb iocb = pc->ioCallbackFlag ? pc->ioCallback : NULL;
|
||||
pc->ioCallbackFlag = false;
|
||||
|
||||
LeaveCriticalSection(&pc->lock);
|
||||
|
||||
if (iocb) {
|
||||
// Should this callback happen with the lock acquired? With the
|
||||
// lock unacquired, then a library user could change the callback
|
||||
// function and still see a call to the old callback function.
|
||||
// With the lock acquired, a deadlock could happen if the user
|
||||
// acquires a lock in the callback. In practice, I expect that
|
||||
// the callback routine will be NULL until it is initialized, and
|
||||
// it will only be initialized once.
|
||||
iocb(pc);
|
||||
}
|
||||
|
||||
WaitForMultipleObjects(sizeof(events) / sizeof(events[0]), events,
|
||||
FALSE, INFINITE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
PCONSOLE_API int pconsole_read(pconsole_s *pconsole,
|
||||
void *buffer,
|
||||
int size)
|
||||
{
|
||||
int ret;
|
||||
EnterCriticalSection(&pconsole->lock);
|
||||
int amount = std::min(size, pconsole->dataReadAmount);
|
||||
if (amount == 0) {
|
||||
ret = -1;
|
||||
} else {
|
||||
memcpy(buffer, pconsole->dataReadBuffer + pconsole->dataReadStart, amount);
|
||||
pconsole->dataReadStart += amount;
|
||||
pconsole->dataReadAmount -= amount;
|
||||
ret = amount;
|
||||
pumpReadIo(pconsole);
|
||||
}
|
||||
LeaveCriticalSection(&pconsole->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
PCONSOLE_API int pconsole_write(pconsole_s *pconsole,
|
||||
const void *buffer,
|
||||
int size)
|
||||
{
|
||||
int ret;
|
||||
EnterCriticalSection(&pconsole->lock);
|
||||
int amount = std::min(size, bufSize - pconsole->dataWriteAmount);
|
||||
if (amount == 0) {
|
||||
ret = -1;
|
||||
} else {
|
||||
memcpy(pconsole->dataWriteBuffer + pconsole->dataWriteAmount,
|
||||
buffer,
|
||||
amount);
|
||||
pconsole->dataWriteAmount += amount;
|
||||
ret = amount;
|
||||
pumpWriteIo(pconsole);
|
||||
}
|
||||
LeaveCriticalSection(&pconsole->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
PCONSOLE_API int pconsole_set_size(pconsole_s *pconsole, int cols, int rows)
|
||||
{
|
||||
// TODO: implement
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
PCONSOLE_API int pconsole_get_output_queue_size(pconsole_s *pconsole)
|
||||
{
|
||||
// TODO: replace this API...
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
PCONSOLE_API void pconsole_close(pconsole_s *pconsole)
|
||||
{
|
||||
CloseHandle(pconsole->dataPipe);
|
||||
//CloseHandle(pconsole->cancelEvent);
|
||||
CloseHandle(pconsole->dataReadEvent);
|
||||
CloseHandle(pconsole->dataWriteEvent);
|
||||
delete pconsole;
|
||||
CloseHandle(pc->controlPipe);
|
||||
CloseHandle(pc->dataPipe);
|
||||
delete pc;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user