Checkpoint work on libpconsole.

This commit is contained in:
Ryan Prichard 2012-02-10 02:09:38 -08:00
parent a53fcbbea6
commit 40c7e87528
5 changed files with 217 additions and 457 deletions

View File

@ -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
View 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 */

View File

@ -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
}

View File

@ -21,3 +21,5 @@ $(LIBRARY) : $(OBJECTS)
.PHONY : clean
clean :
rm -f $(LIBRARY) *.o *.d
-include $(OBJECTS:.o=.d)

View File

@ -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;
}