Get pconsole.exe and pconsole.dll building simultaneously.

* Remove obsolete comments and APIs.

 * In pconsole.exe, use two threads to handle pconsole<->pty communication.
   I use blocking I/O for the pty.  For the Win32 pipe to the pconsole
   agent, I use overlapped I/O and emulate blocking I/O.

   I'm not sure I *have* to open the pipe in overlapped mode.  When I last
   tried using non-overlapped I/O, I had a problem where a pending read
   would block writes (or vice versa).  Maybe setting the overlapped
   parameter to {Read,Write}File would be sufficient.
This commit is contained in:
Ryan Prichard 2012-02-16 03:49:17 -08:00
parent 40c7e87528
commit 27f0c4d156
3 changed files with 128 additions and 90 deletions

View File

@ -15,29 +15,9 @@ extern "C" {
#endif
typedef struct pconsole_s pconsole_t;
typedef void (*pconsole_io_cb)(pconsole_t *pconsole);
typedef void (*pconsole_process_exit_cb)(pconsole_t *pconsole,
int pid,
int exitcode);
/*
* pconsole API.
*
* This library provides non-blocking I/O APIs that communicate with a pconsole
* agent process over Win32 named pipes.
*
* Most I/O is transferred with the pconsole_read and pconsole_write functions.
* The format is the same as a Unix pty (i.e. escape sequences).
*
* A pconsole_t instance has an internal write buffer that grows on-demand to
* an arbitrary size. A pconsole client should use the
* pconsole_get_output_queue_size API to limit the size of the output buffer.
* (TODO: This isn't a practical concern, though, is it?) The internal read
* buffer is statically-sized -- if the client does not read available data
* using pconsole_read, then the pipe will eventually block.
*
* The library uses callbacks to notify the client. A callback function is
* always called on a worker thread that the pconsole library handles.
*/
/*

View File

@ -14,7 +14,6 @@
#define AGENT_EXE L"pconsole-agent.exe"
static volatile LONG consoleCounter;
const int bufSize = 4096;
struct pconsole_s {
pconsole_s();
@ -186,9 +185,9 @@ static void startAgentProcess(std::wstring &controlPipeName,
CloseWindowStation(station);
}
PCONSOLE_API pconsole_s *pconsole_open(int cols, int rows)
PCONSOLE_API pconsole_t *pconsole_open(int cols, int rows)
{
pconsole_s *pc = new pconsole_s;
pconsole_t *pc = new pconsole_t;
// Start pipes.
std::wstringstream pipeName;
@ -220,7 +219,7 @@ PCONSOLE_API pconsole_s *pconsole_open(int cols, int rows)
return pc;
}
static void writePacket(pconsole_s *pc, const WriteBuffer &packet)
static void writePacket(pconsole_t *pc, const WriteBuffer &packet)
{
std::string payload = packet.str();
int payloadSize = payload.size();
@ -253,7 +252,12 @@ PCONSOLE_API int pconsole_start_process(pconsole_t *pc,
writePacket(pc, packet);
}
PCONSOLE_API int pconsole_set_size(pconsole_s *pc, int cols, int rows)
PCONSOLE_API HANDLE pconsole_get_data_pipe(pconsole_t *pc)
{
return pc->dataPipe;
}
PCONSOLE_API int pconsole_set_size(pconsole_t *pc, int cols, int rows)
{
WriteBuffer packet;
packet.putInt(AgentMsg::SetSize);
@ -262,7 +266,7 @@ PCONSOLE_API int pconsole_set_size(pconsole_s *pc, int cols, int rows)
writePacket(pc, packet);
}
PCONSOLE_API void pconsole_close(pconsole_s *pc)
PCONSOLE_API void pconsole_close(pconsole_t *pc)
{
CloseHandle(pc->controlPipe);
CloseHandle(pc->dataPipe);

View File

@ -8,8 +8,10 @@
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <pconsole.h>
static int signalWriteFd;
@ -25,6 +27,7 @@ static termios setRawTerminalMode()
exit(1);
}
/*
// This code makes the terminal output non-blocking.
int flags = fcntl(STDOUT_FILENO, F_GETFL, 0);
if (flags == -1) {
@ -35,6 +38,7 @@ static termios setRawTerminalMode()
perror("fcntl F_SETFL on stdout failed");
exit(1);
}
*/
termios buf;
if (tcgetattr(STDIN_FILENO, &buf) < 0) {
@ -47,7 +51,7 @@ static termios setRawTerminalMode()
buf.c_cflag &= ~(CSIZE | PARENB);
buf.c_cflag |= CS8;
buf.c_oflag &= ~OPOST;
buf.c_cc[VMIN] = 0;
buf.c_cc[VMIN] = 1; // blocking read
buf.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) {
fprintf(stderr, "tcsetattr failed\n");
@ -64,18 +68,118 @@ static void restoreTerminalMode(termios original)
}
}
static void terminalResized(int signo)
static void writeToSignalFd()
{
char dummy = 0;
write(signalWriteFd, &dummy, 1);
}
static void pconsoleIo(pconsole_t *pconsole)
static void terminalResized(int signo)
{
char dummy = 0;
write(signalWriteFd, &dummy, 1);
writeToSignalFd();
}
// Create a manual reset, initially unset event.
static HANDLE createEvent()
{
return CreateEvent(NULL, TRUE, FALSE, NULL);
}
// Connect pconsole overlapped I/O to Cygwin blocking STDOUT_FILENO.
class OutputHandler {
public:
OutputHandler(HANDLE pconsole);
pthread_t getThread() { return thread; }
private:
static void *threadProc(void *pvthis);
HANDLE pconsole;
pthread_t thread;
};
OutputHandler::OutputHandler(HANDLE pconsole) : pconsole(pconsole)
{
pthread_create(&thread, NULL, OutputHandler::threadProc, this);
}
// TODO: See whether we can make the pipe non-overlapped if we still use
// an OVERLAPPED structure in the ReadFile/WriteFile calls.
void *OutputHandler::threadProc(void *pvthis)
{
OutputHandler *pthis = (OutputHandler*)pvthis;
HANDLE event = createEvent();
OVERLAPPED over;
const int bufferSize = 4096;
char *buffer = new char[bufferSize];
while (true) {
DWORD amount;
memset(&over, 0, sizeof(over));
over.hEvent = event;
BOOL ret = ReadFile(pthis->pconsole,
buffer, bufferSize,
&amount,
&over);
if (!ret && GetLastError() == ERROR_IO_PENDING)
ret = GetOverlappedResult(pthis->pconsole, &over, &amount, TRUE);
if (!ret || amount == 0)
break;
// TODO: partial writes?
int written = write(STDOUT_FILENO, buffer, amount);
if (written != amount)
break;
}
delete [] buffer;
CloseHandle(event);
return NULL;
}
// Connect Cygwin non-blocking STDIN_FILENO to pconsole overlapped I/O.
class InputHandler {
public:
InputHandler(HANDLE pconsole);
pthread_t getThread() { return thread; }
private:
static void *threadProc(void *pvthis);
HANDLE pconsole;
pthread_t thread;
};
InputHandler::InputHandler(HANDLE pconsole) : pconsole(pconsole)
{
pthread_create(&thread, NULL, InputHandler::threadProc, this);
}
void *InputHandler::threadProc(void *pvthis)
{
InputHandler *pthis = (InputHandler*)pvthis;
HANDLE event = createEvent();
const int bufferSize = 4096;
char *buffer = new char[bufferSize];
while (true) {
int amount = read(STDIN_FILENO, buffer, bufferSize);
if (amount <= 0)
break;
DWORD written;
OVERLAPPED over;
memset(&over, 0, sizeof(over));
over.hEvent = event;
BOOL ret = WriteFile(pthis->pconsole,
buffer, amount,
&written,
&over);
if (!ret && GetLastError() == ERROR_IO_PENDING)
ret = GetOverlappedResult(pthis->pconsole, &over, &written, TRUE);
// TODO: partial writes?
if (!ret || written != amount)
break;
}
delete [] buffer;
CloseHandle(event);
return NULL;
}
int main()
{
winsize sz;
@ -87,8 +191,6 @@ int main()
exit(1);
}
pconsole_set_io_cb(pconsole, pconsoleIo);
{
struct sigaction resizeSigAct;
memset(&resizeSigAct, 0, sizeof(resizeSigAct));
@ -100,12 +202,6 @@ int main()
termios mode = setRawTerminalMode();
int signalReadFd;
const int bufSize = 4096;
char writeBuf[bufSize];
int writeBufAmount;
char buf[bufSize];
int amount;
{
int pipeFd[2];
if (pipe2(pipeFd, O_NONBLOCK) != 0) {
@ -116,18 +212,14 @@ int main()
signalWriteFd = pipeFd[1];
}
while (true) {
fd_set readfds;
fd_set writefds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(signalReadFd, &readfds);
if (pconsole_get_output_queue_size(pconsole) < bufSize)
FD_SET(STDIN_FILENO, &readfds);
if (writeBufAmount > 0)
FD_SET(STDOUT_FILENO, &writefds);
OutputHandler outputHandler(pconsole_get_data_pipe(pconsole));
InputHandler inputHandler(pconsole_get_data_pipe(pconsole));
if (select(signalReadFd + 1, &readfds, &writefds, NULL, NULL) < 0) {
while (true) {
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(signalReadFd, &readfds);
if (select(signalReadFd + 1, &readfds, NULL, NULL, NULL) < 0) {
perror("select failed");
exit(1);
}
@ -143,50 +235,12 @@ int main()
}
// Discard any data in the signal pipe.
amount = read(signalReadFd, buf, bufSize);
char tmpBuf[256];
int amount = read(signalReadFd, tmpBuf, sizeof(tmpBuf));
if (amount == 0 || amount < 0 && errno != EAGAIN) {
perror("error reading internal signal fd");
exit(1);
}
// Read from the pty and write to the pconsole.
amount = bufSize - pconsole_get_output_queue_size(pconsole);
if (amount > 0) {
amount = read(STDIN_FILENO, buf, amount);
if (amount == 0) {
break;
} else if (amount < 0 && errno != EAGAIN) {
perror("error reading from pty");
exit(1);
} else if (amount > 0) {
pconsole_write(pconsole, buf, amount);
}
}
// Read from the pconsole.
amount = bufSize - writeBufAmount;
if (amount > 0) {
amount = pconsole_read(pconsole, buf + writeBufAmount, amount);
if (amount == 0)
break;
else if (amount > 0)
writeBufAmount += amount;
}
// Write to the pty.
if (writeBufAmount > 0) {
amount = write(STDOUT_FILENO, writeBuf, writeBufAmount);
if (amount == 0) {
break;
} else if (amount < 0 && errno != EAGAIN) {
perror("error writing to pty");
} else if (amount > 0) {
int remaining = writeBufAmount - amount;
if (amount < writeBufAmount)
memmove(writeBuf, writeBuf + amount, remaining);
writeBufAmount = remaining;
}
}
}
// TODO: Get the pconsole child exit code and exit with it.