From 399d68dd1aa0b70c6cb58a5bc63cd50595294fa3 Mon Sep 17 00:00:00 2001 From: Justin Karneges Date: Fri, 23 Mar 2007 22:30:09 +0000 Subject: [PATCH] go back to polling for windows, corrected windows console usage svn path=/trunk/kdesupport/qca/; revision=645946 --- include/QtCrypto/qpipe.h | 23 +- src/support/qpipe.cpp | 807 ++++++++++++++++++++++++++++++++++----- 2 files changed, 725 insertions(+), 105 deletions(-) diff --git a/include/QtCrypto/qpipe.h b/include/QtCrypto/qpipe.h index 4984a2b0..82801866 100644 --- a/include/QtCrypto/qpipe.h +++ b/include/QtCrypto/qpipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2006 Justin Karneges + * Copyright (C) 2003-2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -36,6 +36,8 @@ #ifdef QPIPE_SECURE # include +#else +# define QCA_EXPORT #endif // defs adapted qprocess_p.h @@ -48,6 +50,10 @@ typedef int Q_PIPE_ID; #define INVALID_Q_PIPE_ID -1 #endif +// Note: for Windows console, I/O must be in UTF-8. Reads are guaranteed to +// to completely decode (no partial characters). Likewise, writes must +// not contain partial characters. + namespace QCA { // unbuffered direct pipe @@ -55,7 +61,12 @@ class QCA_EXPORT QPipeDevice : public QObject { Q_OBJECT public: - enum Type { Read, Write }; + enum Type + { + Read, + Write + }; + QPipeDevice(QObject *parent = 0); ~QPipeDevice(); @@ -75,6 +86,7 @@ public: int bytesAvailable() const; // bytes available to read int read(char *data, int maxsize); // return number read, 0 = EOF, -1 = error int write(const char *data, int size); // return number taken, ptr must stay valid. -1 on error + int writeResult(int *written) const; // 0 = success (wrote all), -1 = error (see written) signals: void notify(); // can read or can write, depending on type @@ -90,7 +102,12 @@ class QCA_EXPORT QPipeEnd : public QObject { Q_OBJECT public: - enum Error { ErrorEOF, ErrorBroken }; + enum Error + { + ErrorEOF, + ErrorBroken + }; + QPipeEnd(QObject *parent = 0); ~QPipeEnd(); diff --git a/src/support/qpipe.cpp b/src/support/qpipe.cpp index 0be3f002..463bbb83 100644 --- a/src/support/qpipe.cpp +++ b/src/support/qpipe.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2006 Justin Karneges + * Copyright (C) 2003-2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -17,10 +17,18 @@ * */ +// Note: if we ever enable the threaded backend, we need to protect: +// QPipeDevice read and bytesAvailable +// QPipeEnd finalize + +// Note: we never use the return value for QPipeWriter::stop, but I don't +// think this matters much + #include "qpipe.h" +#include + #ifdef Q_OS_UNIX -# include # include # include # include @@ -28,6 +36,11 @@ # include #endif +#define USE_POLL + +#define CONSOLE_CHAREXPAND 5 +#define PIPEWRITER_POLL 1000 +#define PIPEREADER_POLL 100 #define PIPEWRITER_BLOCK 8192 #define PIPEEND_BLOCK 8192 #define PIPEEND_READBUF 16384 @@ -51,8 +64,7 @@ static void ignore_sigpipe() } #endif -#ifdef Q_OS_UNIX -static bool setBlocking(Q_PIPE_ID pipe, bool b) +static bool pipe_set_blocking(Q_PIPE_ID pipe, bool b) { #ifdef Q_OS_WIN DWORD flags = 0; @@ -73,7 +85,6 @@ static bool setBlocking(Q_PIPE_ID pipe, bool b) return true; #endif } -#endif // returns number of bytes available static int pipe_read_avail(Q_PIPE_ID pipe) @@ -81,8 +92,8 @@ static int pipe_read_avail(Q_PIPE_ID pipe) int bytesAvail = 0; #ifdef Q_OS_WIN DWORD i = 0; - PeekNamedPipe(pipe, 0, 0, 0, &i, 0); - bytesAvail = (int)i; + if(PeekNamedPipe(pipe, 0, 0, 0, &i, 0)) + bytesAvail = (int)i; #endif #ifdef Q_OS_UNIX size_t nbytes = 0; @@ -144,8 +155,6 @@ static int pipe_read(Q_PIPE_ID pipe, char *data, int max, bool *eof) return bytesRead; } -// this function only works with blocking pipes on windows. -// for unix, both blocking and non-blocking should work. // returns number of bytes actually written. // for blocking pipes, this should always be 'size'. // -1 on error. @@ -155,7 +164,7 @@ static int pipe_write(Q_PIPE_ID pipe, const char *data, int size) DWORD written; if(!WriteFile(pipe, data, size, &written, 0)) return -1; - return written; + return (int)written; #endif #ifdef Q_OS_UNIX ignore_sigpipe(); @@ -172,11 +181,259 @@ static int pipe_write(Q_PIPE_ID pipe, const char *data, int size) #endif } +// Windows Console functions + #ifdef Q_OS_WIN + +static bool pipe_is_a_console(Q_PIPE_ID pipe) +{ + DWORD mode; + if(GetConsoleMode(pipe, &mode)) + return true; + return false; +} + +// returns the number of keypress events in the console input queue, +// or -1 if there is an error (don't forget this!!) +static int pipe_read_avail_console(Q_PIPE_ID pipe) +{ + DWORD count, i; + INPUT_RECORD *rec; + int n, total; + + // how many events are there? + if(!GetNumberOfConsoleInputEvents(pipe, &count)) + return -1; + + // peek them all + rec = (INPUT_RECORD *)malloc(count * sizeof(INPUT_RECORD)); + BOOL ret; + QT_WA( + ret = PeekConsoleInputW(pipe, rec, count, &i); + , + ret = PeekConsoleInputA(pipe, rec, count, &i); + ) + if(!ret) + { + free(rec); + return -1; + } + count = i; // use only the amount returned + + // see which ones are normal keypress events + total = 0; + for(n = 0; n < (int)count; ++n) + { + if(rec[n].EventType == KEY_EVENT) + { + KEY_EVENT_RECORD *ke = &rec[n].Event.KeyEvent; + if(ke->bKeyDown && ke->uChar.AsciiChar != 0) + total += ke->wRepeatCount; + } + } + + free(rec); + return total; +} + +// pass dec to keep a long-running decoder, else 0 +static int pipe_read_console(Q_PIPE_ID pipe, ushort *data, int max, bool *eof, QTextDecoder *dec = 0) +{ + int n, size, count; + bool own_decoder; + + if(eof) + *eof = false; + if(max < 1) + return 0; + + count = pipe_read_avail_console(pipe); + if(count == -1) + return -1; + if(count == 0) + return 0; + + if(dec) + { + own_decoder = false; + } + else + { + QT_WA( + dec = 0; + , + dec = QTextCodec::codecForLocale()->makeDecoder(); + ) + own_decoder = true; + } + + size = 0; + for(n = 0; n < count && size < max; ++n) + { + bool use_uni = true; + quint16 uni = 0; + quint8 ansi = 0; + + BOOL ret; + DWORD i; + QT_WA( + ret = ReadConsoleW(pipe, &uni, 1, &i, NULL); + , + ret = ReadConsoleA(pipe, &ansi, 1, &i, NULL); + use_uni = false; + ) + if(!ret) + { + // if the first read is an error, then report error + if(n == 0) + { + delete dec; + return -1; + } + // if we have some data, don't count this as an error. + // we'll probably get it again next time around... + else + break; + } + + QString substr; + if(use_uni) + substr = QChar(uni); + else + substr = dec->toUnicode((const char *)&ansi, 1); + + for(int k = 0; k < substr.length() && size < max; ++k) + { + QChar c = substr[k]; + if(c == QChar(0x1A)) // EOF? + { + if(eof) + *eof = true; + break; + } + data[size++] = substr[k].unicode(); + } + } + if(own_decoder) + delete dec; + + return size; +} + +static int pipe_write_console(Q_PIPE_ID pipe, const ushort *data, int size) +{ + DWORD i; + BOOL ret; + QT_WA( + ret = WriteConsoleW(pipe, data, size, &i, NULL); + , + // Note: we lose security by converting to QString here, but + // who really cares if we're writing to a *display* ? :) + QByteArray out = QString::fromUtf16(data, size).toLocal8Bit(); + ret = WriteConsoleA(pipe, out.data(), out.size(), &i, NULL); + if(ret) + { + // convert number of bytes to number of unicode chars + i = (DWORD)QString::fromLocal8Bit(out.mid(0, i)).length(); + } + ) + if(!ret) + return -1; + return (int)i; +} +#endif + +#ifdef Q_OS_WIN + +// Here is the multi-backend stuff for windows. QPipeWriter and QPipeReader +// define a common interface, and then subclasses (like QPipeWriterThread) +// are used by QPipeDevice. The base classes inherit from QThread, even +// if threads aren't used, so that I can define signals without dealing +// with multiple QObject inheritance in the thread subclasses (it is also +// possible that I'm missing something obvious and don't need to do this). + +// Note: +// QPipeWriterThread and QPipeReaderThread require the pipes to be in +// blocking mode. QPipeWriterPoll and QPipeReaderPoll require the pipes +// to be in non-blocking mode. + //---------------------------------------------------------------------------- // QPipeWriter //---------------------------------------------------------------------------- class QPipeWriter : public QThread +{ + Q_OBJECT +public: + QPipeWriter(QObject *parent = 0) : QThread(parent) + { + } + + virtual ~QPipeWriter() + { + } + + // start + virtual void start() = 0; + + // stop, and return number of bytes written so far + virtual int stop() = 0; + + // data pointer needs to remain until canWrite is emitted + virtual int write(const char *data, int size) = 0; + +signals: + // result values: + // = 0 : success + // = -1 : error + void canWrite(int result, int bytesWritten); + +protected: + virtual void run() + { + // implement a default to satisfy the polling subclass + } +}; + +//---------------------------------------------------------------------------- +// QPipeReader +//---------------------------------------------------------------------------- +class QPipeReader : public QThread +{ + Q_OBJECT +public: + QPipeReader(QObject *parent = 0) : QThread(parent) + { + } + + virtual ~QPipeReader() + { + } + + // start + virtual void start() = 0; + + // to be called after every read + virtual void resume() = 0; + +signals: + // result values: + // >= 0 : readAhead + // = -1 : atEnd + // = -2 : atError + // = -3 : data available, but no readAhead + void canRead(int result); + +protected: + virtual void run() + { + // implement a default to satisfy the polling subclass + } +}; + +//---------------------------------------------------------------------------- +// QPipeWriterThread +//---------------------------------------------------------------------------- +class QPipeWriterThread : public QPipeWriter { Q_OBJECT public: @@ -187,15 +444,27 @@ public: const char *data; int size; - QPipeWriter(Q_PIPE_ID id, QObject *parent = 0) : QThread(parent) + QPipeWriterThread(Q_PIPE_ID id, QObject *parent = 0) : QPipeWriter(parent) { do_quit = false; data = 0; - connect(this, SIGNAL(canWrite_p()), SIGNAL(canWrite())); + connect(this, SIGNAL(canWrite_p(int, int)), SIGNAL(canWrite(int, int))); DuplicateHandle(GetCurrentProcess(), id, GetCurrentProcess(), &pipe, 0, false, DUPLICATE_SAME_ACCESS); } - ~QPipeWriter() + virtual ~QPipeWriterThread() + { + stop(); + CloseHandle(pipe); + } + + virtual void start() + { + pipe_set_blocking(pipe, true); + QThread::start(); + } + + virtual int stop() { if(isRunning()) { @@ -205,12 +474,13 @@ public: m.unlock(); if(!wait(100)) terminate(); + do_quit = false; + data = 0; } - CloseHandle(pipe); + return size; } - // data pointer needs to remain until canWrite is emitted - int write(const char *_data, int _size) + virtual int write(const char *_data, int _size) { if(!isRunning()) return -1; @@ -218,6 +488,7 @@ public: QMutexLocker locker(&m); if(data) return 0; + data = _data; size = _size; w.wakeOne(); @@ -245,22 +516,20 @@ protected: m.unlock(); - if(internalWrite(p, len) != 1) - break; + int ret = internalWrite(p, len); m.lock(); data = 0; + size = ret; m.unlock(); - emit canWrite_p(); + emit canWrite_p(ret < len ? -1 : 0, ret); } } -signals: - void canWrite(); - void canWrite_p(); - private: + // attempts to write len bytes. value returned is number of bytes written. + // any return value less than len means a write error was encountered int internalWrite(const char *p, int len) { int total = 0; @@ -277,8 +546,8 @@ private: int ret = pipe_write(pipe, p + total, qMin(PIPEWRITER_BLOCK, len - total)); if(ret == -1) { - // from qt - if(GetLastError() == 0xE8 /*NT_STATUS_INVALID_USER_BUFFER*/) + // from qt, don't know why + if(GetLastError() == 0xE8) // NT_STATUS_INVALID_USER_BUFFER { // give the os a rest msleep(100); @@ -286,18 +555,96 @@ private: } // on any other error, end thread - return -1; + return total; } total += ret; } - return 1; + return total; + } + +signals: + void canWrite_p(int result, int bytesWritten); +}; + +//---------------------------------------------------------------------------- +// QPipeWriterPoll +//---------------------------------------------------------------------------- +class QPipeWriterPoll : public QPipeWriter +{ + Q_OBJECT +public: + Q_PIPE_ID pipe; + const char *data; + int size; + QTimer timer; + int total; + + QPipeWriterPoll(Q_PIPE_ID id, QObject *parent = 0) : QPipeWriter(parent), timer(this) + { + pipe = id; + data = 0; + connect(&timer, SIGNAL(timeout()), SLOT(tryNextWrite())); + } + + virtual ~QPipeWriterPoll() + { + } + + virtual void start() + { + pipe_set_blocking(pipe, false); + } + + // return number of bytes written + virtual int stop() + { + timer.stop(); + data = 0; + return total; + } + + // data pointer needs to remain until canWrite is emitted + virtual int write(const char *_data, int _size) + { + total = 0; + data = _data; + size = _size; + timer.start(0); // write at next event loop + return _size; + } + +private slots: + void tryNextWrite() + { + int written = pipe_write(pipe, data + total, size - total); + bool error = false; + if(written == -1) + { + error = true; + written = 0; // no bytes written on error + + // from qt, they don't count it as fatal + if(GetLastError() == 0xE8) // NT_STATUS_INVALID_USER_BUFFER + error = false; + } + + total += written; + if(error || total == size) + { + timer.stop(); + data = 0; + emit canWrite(error ? -1 : 0, total); + return; + } + + timer.setInterval(PIPEWRITER_POLL); } }; //---------------------------------------------------------------------------- -// QPipeReader +// QPipeReaderThread //---------------------------------------------------------------------------- -class QPipeReader : public QThread +class QPipeReaderThread : public QPipeReader { Q_OBJECT public: @@ -306,7 +653,7 @@ public: QWaitCondition w; bool do_quit, active; - QPipeReader(Q_PIPE_ID id, QObject *parent = 0) : QThread(parent) + QPipeReaderThread(Q_PIPE_ID id, QObject *parent = 0) : QPipeReader(parent) { do_quit = false; active = true; @@ -314,7 +661,7 @@ public: DuplicateHandle(GetCurrentProcess(), id, GetCurrentProcess(), &pipe, 0, false, DUPLICATE_SAME_ACCESS); } - ~QPipeReader() + virtual ~QPipeReaderThread() { if(isRunning()) { @@ -328,9 +675,16 @@ public: CloseHandle(pipe); } - void resume() + virtual void start() + { + pipe_set_blocking(pipe, true); + QThread::start(); + } + + virtual void resume() { QMutexLocker locker(&m); + pipe_set_blocking(pipe, true); active = true; w.wakeOne(); } @@ -368,11 +722,10 @@ protected: result = -2; else if(ret >= 1) // we got some data?? queue it result = c; - else // no data? not sure if this can happen - continue; m.lock(); active = false; + pipe_set_blocking(pipe, false); m.unlock(); emit canRead_p(result); @@ -383,13 +736,103 @@ protected: } signals: - // result values: - // >= 0 : readAhead - // = -1 : atEnd - // = -2 : atError - void canRead(int result); void canRead_p(int result); }; + +//---------------------------------------------------------------------------- +// QPipeReaderPoll +//---------------------------------------------------------------------------- +class QPipeReaderPoll : public QPipeReader +{ + Q_OBJECT +public: + Q_PIPE_ID pipe; + QTimer timer; + bool consoleMode; + + QPipeReaderPoll(Q_PIPE_ID id, QObject *parent = 0) : QPipeReader(parent), timer(this) + { + pipe = id; + connect(&timer, SIGNAL(timeout()), SLOT(tryRead())); + } + + virtual ~QPipeReaderPoll() + { + } + + virtual void start() + { + pipe_set_blocking(pipe, false); + consoleMode = pipe_is_a_console(pipe); + resume(); + } + + virtual void resume() + { + timer.start(0); + } + +private slots: + void tryRead() + { + if(consoleMode) + tryReadConsole(); + else + tryReadPipe(); + } + +private: + void tryReadPipe() + { + // is there data available for reading? if so, signal. + int bytes = pipe_read_avail(pipe); + if(bytes > 0) + { + timer.stop(); + emit canRead(-3); // no readAhead + return; + } + + // no data available? probe for EOF/error + unsigned char c; + bool done; + int ret = pipe_read(pipe, (char *)&c, 1, &done); + if(done || ret != 0) // eof, error, or data? + { + int result; + + if(done) // we got EOF? + result = -1; + else if(ret == -1) // we got an error? + result = -2; + else if(ret >= 1) // we got some data?? queue it + result = c; + + timer.stop(); + emit canRead(result); + return; + } + + timer.setInterval(PIPEREADER_POLL); + } + + void tryReadConsole() + { + // is there data available for reading? if so, signal. + int count = pipe_read_avail_console(pipe); + if(count > 0) + { + timer.stop(); + emit canRead(-3); // no readAhead + return; + } + + timer.setInterval(PIPEREADER_POLL); + } +}; + +// end of windows pipe writer/reader implementations + #endif //---------------------------------------------------------------------------- @@ -405,11 +848,15 @@ public: bool enabled; bool blockReadNotify; bool canWrite; + int writeResult; + int lastTaken, lastWritten; #ifdef Q_OS_WIN bool atEnd, atError, forceNotify; int readAhead; QTimer *readTimer; + QTextDecoder *dec; + bool consoleMode; QPipeWriter *pipeWriter; QPipeReader *pipeReader; #endif @@ -423,6 +870,7 @@ public: readTimer = 0; pipeWriter = 0; pipeReader = 0; + dec = 0; #endif #ifdef Q_OS_UNIX sn_read = 0; @@ -448,6 +896,9 @@ public: pipeWriter = 0; delete pipeReader; pipeReader = 0; + delete dec; + dec = 0; + consoleMode = false; #endif #ifdef Q_OS_UNIX delete sn_read; @@ -469,6 +920,7 @@ public: enabled = false; blockReadNotify = false; canWrite = true; + writeResult = -1; } void setup(Q_PIPE_ID id, QPipeDevice::Type _type) @@ -487,8 +939,28 @@ public: if(type == QPipeDevice::Read) { #ifdef Q_OS_WIN + // for windows, the blocking mode is chosen by the QPipeReader + + // console might need a decoder + if(consoleMode) + { + QT_WA( + dec = 0; + , + dec = QTextCodec::codecForLocale()->makeDecoder(); + ) + } + // pipe reader - pipeReader = new QPipeReader(pipe, this); +#ifdef USE_POLL + pipeReader = new QPipeReaderPoll(pipe, this); +#else + // console always polls, no matter what + if(consoleMode) + pipeReader = new QPipeReaderPoll(pipe, this); + else + pipeReader = new QPipeReaderThread(pipe, this); +#endif connect(pipeReader, SIGNAL(canRead(int)), this, SLOT(pr_canRead(int))); pipeReader->start(); @@ -496,14 +968,13 @@ public: readTimer = new QTimer(this); connect(readTimer, SIGNAL(timeout()), SLOT(t_timeout())); - // NOTE: now that we have pipeReader, this no longer - // polls for data. it only does delayed - // singleshot notifications. + // updated: now that we have pipeReader, this no longer + // polls for data. it only does delayed singleshot + // notifications. readTimer->setSingleShot(true); - //readTimer->start(100); #endif #ifdef Q_OS_UNIX - setBlocking(pipe, false); + pipe_set_blocking(pipe, false); // socket notifier sn_read = new QSocketNotifier(pipe, QSocketNotifier::Read, this); @@ -512,8 +983,9 @@ public: } else { + // for windows, the blocking mode is chosen by the QPipeWriter #ifdef Q_OS_UNIX - setBlocking(pipe, false); + pipe_set_blocking(pipe, false); // socket notifier sn_write = new QSocketNotifier(pipe, QSocketNotifier::Write, this); @@ -541,42 +1013,28 @@ public slots: emit q->notify(); return; } - - // NOTE: disabling this since we now have pipeReader - // is there data available for reading? if so, signal. - /*int bytes = pipe_read_avail(pipe); - if(bytes > 0) - { - blockReadNotify = true; - emit q->notify(); - return; - } - - // no data available? probe for EOF/error - unsigned char c; - bool done; - int ret = pipe_read(pipe, (char *)&c, 1, &done); - if(done || ret != 0) // eof, error, or data? - { - if(done) // we got EOF? - atEnd = true; - else if(ret == -1) // we got an error? - atError = true; - else if(ret >= 1) // we got some data?? queue it - readAhead = c; - - blockReadNotify = true; - emit q->notify(); - return; - }*/ #endif } - void pw_canWrite() + void pw_canWrite(int result, int bytesWritten) { #ifdef Q_OS_WIN + if(result == 0) + { + writeResult = 0; + lastWritten = lastTaken; // success means all bytes + } + else + { + writeResult = -1; + lastWritten = bytesWritten; + } + canWrite = true; emit q->notify(); +#else + Q_UNUSED(result); + Q_UNUSED(bytesWritten); #endif } @@ -588,7 +1046,7 @@ public slots: atEnd = true; else if(result == -2) atError = true; - else + else if(result != -3) readAhead = result; emit q->notify(); #else @@ -610,6 +1068,9 @@ public slots: void sn_write_activated(int) { #ifdef Q_OS_UNIX + writeResult = 0; + lastWritten = lastTaken; + canWrite = true; sn_write->setEnabled(false); emit q->notify(); @@ -663,6 +1124,9 @@ void QPipeDevice::take(Q_PIPE_ID id, Type t) void QPipeDevice::enable() { +#ifdef Q_OS_WIN + d->consoleMode = pipe_is_a_console(d->pipe); +#endif d->enable(); } @@ -693,10 +1157,16 @@ bool QPipeDevice::winDupHandle() int QPipeDevice::bytesAvailable() const { - int n = pipe_read_avail(d->pipe); + int n; #ifdef Q_OS_WIN + if(d->consoleMode) + n = pipe_read_avail_console(d->pipe); + else + n = pipe_read_avail(d->pipe); if(d->readAhead != -1) ++n; +#else + n = pipe_read_avail(d->pipe); #endif return n; } @@ -711,7 +1181,16 @@ int QPipeDevice::read(char *data, int maxsize) return -1; #ifdef Q_OS_WIN - // for resuming the pipeReader thread + // for windows console: + // the number of bytes in utf8 can exceed the number of actual + // characters it represents. to be safe, we'll assume that + // utf8 could outnumber characters X:1. this does mean that + // the maxsize parameter needs to be at least X to do + // anything. (X = CONSOLE_CHAREXPAND) + if(d->consoleMode && maxsize < CONSOLE_CHAREXPAND) + return -1; + + // for resuming the pipeReader bool wasBlocked = d->blockReadNotify; #endif @@ -753,7 +1232,40 @@ int QPipeDevice::read(char *data, int maxsize) // read from the pipe now bool done; - int ret = pipe_read(d->pipe, data + offset, size, &done); + int ret; + if(d->consoleMode) + { + // read a fraction of the number of characters as requested, + // to guarantee the result fits + int num = size / CONSOLE_CHAREXPAND; + +#ifdef QPIPE_SECURE + QSecureArray destbuf(num * sizeof(ushort), 0); +#else + QByteArray destbuf(num * sizeof(ushort), 0); +#endif + ushort *dest = (ushort *)destbuf.data(); + + ret = pipe_read_console(d->pipe, dest, num, &done, d->dec); + if(ret != -1) + { + // for security, encode one character at a time without + // performing a QString conversion of the whole thing + QTextCodec *codec = QTextCodec::codecForMib(106); + QTextCodec::ConverterState cstate(QTextCodec::IgnoreHeader); + int at = 0; + for(int n = 0; n < ret; ++n) + { + QChar c(dest[n]); + QByteArray out = codec->fromUnicode(&c, 1, &cstate); + memcpy(data + offset + at, out.data(), out.size()); + at += out.size(); + } + ret = at; // change ret to actual bytes + } + } + else + ret = pipe_read(d->pipe, data + offset, size, &done); if(done || ret == -1) // eof or error { // did we already have some data? if so, defer the eof/error @@ -765,8 +1277,8 @@ int QPipeDevice::read(char *data, int maxsize) else d->atError = true; - // NOTE: now that readTimer is a singleshot, we have - // to start it for forceNotify to work + // readTimer is a singleshot, so we have to start it + // for forceNotify to work d->readTimer->start(); } // otherwise, bail @@ -831,12 +1343,45 @@ int QPipeDevice::write(const char *data, int size) #ifdef Q_OS_WIN if(!d->pipeWriter) { - d->pipeWriter = new QPipeWriter(d->pipe, d); - connect(d->pipeWriter, SIGNAL(canWrite()), d, SLOT(pw_canWrite())); +#ifdef USE_POLL + d->pipeWriter = new QPipeWriterPoll(d->pipe, d); +#else + // console always polls, no matter what + if(d->consoleMode) + d->pipeWriter = new QPipeReaderPoll(d->pipe, d); + else + d->pipeWriter = new QPipeWriterThread(d->pipe, d); +#endif + connect(d->pipeWriter, SIGNAL(canWrite(int, int)), d, SLOT(pw_canWrite(int, int))); d->pipeWriter->start(); } - d->canWrite = false; - r = d->pipeWriter->write(data, size); + + if(d->consoleMode) + { + // Note: we convert to QString here, but it should not be a + // security issue (see pipe_write_console comment above) + + // for console, just write direct. we won't use pipewriter + QString out = QString::fromUtf8(QByteArray(data, size)); + r = pipe_write_console(d->pipe, out.utf16(), out.length()); + if(r == -1) + return -1; + + // convert characters to bytes + r = out.mid(0, r).toUtf8().size(); + + // simulate. we invoke the signal of pipewriter rather than our + // own slot, so that the invoke can be cancelled. + d->canWrite = false; + QMetaObject::invokeMethod(d->pipeWriter, "canWrite", Qt::QueuedConnection, Q_ARG(int, 0), Q_ARG(int, r)); + } + else + { + d->canWrite = false; + r = d->pipeWriter->write(data, size); + } + + d->lastTaken = r; if(r == -1) { close(); @@ -845,6 +1390,7 @@ int QPipeDevice::write(const char *data, int size) #endif #ifdef Q_OS_UNIX r = pipe_write(d->pipe, data, size); + d->lastTaken = r; if(r == -1) { close(); @@ -857,6 +1403,13 @@ int QPipeDevice::write(const char *data, int size) return r; } +int QPipeDevice::writeResult(int *written) const +{ + if(written) + *written = d->lastWritten; + return d->writeResult; +} + //---------------------------------------------------------------------------- // QPipeEnd //---------------------------------------------------------------------------- @@ -876,26 +1429,33 @@ public: QPipeDevice::Type type; QByteArray buf; QByteArray curWrite; + +#ifdef Q_OS_WIN + bool consoleMode; +#endif + #ifdef QPIPE_SECURE bool secure; QSecureArray sec_buf; QSecureArray sec_curWrite; #endif - QTimer readTrigger, writeTrigger, closeTrigger; + QTimer readTrigger, writeTrigger, closeTrigger, writeErrorTrigger; bool canRead, activeWrite; int lastWrite; bool closeLater; bool closing; - Private(QPipeEnd *_q) : QObject(_q), q(_q), pipe(this), readTrigger(this), writeTrigger(this), closeTrigger(this) + Private(QPipeEnd *_q) : QObject(_q), q(_q), pipe(this), readTrigger(this), writeTrigger(this), closeTrigger(this), writeErrorTrigger(this) { readTrigger.setSingleShot(true); writeTrigger.setSingleShot(true); closeTrigger.setSingleShot(true); + writeErrorTrigger.setSingleShot(true); connect(&pipe, SIGNAL(notify()), SLOT(pipe_notify())); connect(&readTrigger, SIGNAL(timeout()), SLOT(doRead())); connect(&writeTrigger, SIGNAL(timeout()), SLOT(doWrite())); connect(&closeTrigger, SIGNAL(timeout()), SLOT(doClose())); + connect(&writeErrorTrigger, SIGNAL(timeout()), SLOT(doWriteError())); reset(ResetSessionAndData); } @@ -905,6 +1465,7 @@ public: readTrigger.stop(); writeTrigger.stop(); closeTrigger.stop(); + writeErrorTrigger.stop(); canRead = false; activeWrite = false; lastWrite = 0; @@ -928,6 +1489,9 @@ public: void setup(Q_PIPE_ID id, QPipeDevice::Type _type) { type = _type; +#ifdef Q_OS_WIN + consoleMode = pipe_is_a_console(id); +#endif pipe.take(id, type); } @@ -1058,6 +1622,11 @@ public slots: } else { + int x; + int writeResult = pipe.writeResult(&x); + if(writeResult == -1) + lastWrite = x; // if error, we may have written less bytes + // remove what we just wrote bool moreData = false; #ifdef QPIPE_SECURE @@ -1078,26 +1647,32 @@ public slots: #endif curWrite.clear(); - int x = lastWrite; + x = lastWrite; lastWrite = 0; - // more to write? do it - if(moreData) + if(writeResult == 0) { - writeTrigger.start(0); - } - // done with all writing - else - { - activeWrite = false; - if(closeLater) + // more to write? do it + if(moreData) { - closeLater = false; - closeTrigger.start(0); + writeTrigger.start(0); + } + // done with all writing + else + { + activeWrite = false; + if(closeLater) + { + closeLater = false; + closeTrigger.start(0); + } } } + else + writeErrorTrigger.start(); - emit q->bytesWritten(x); + if(x > 0) + emit q->bytesWritten(x); } } @@ -1115,11 +1690,33 @@ public slots: return; } + int max; +#ifdef Q_OS_WIN + if(consoleMode) + { + // need a minimum amount for console + if(left < CONSOLE_CHAREXPAND) + { + canRead = true; + return; + } + + // don't use pipe.bytesAvailable() for console mode, + // as it is somewhat bogus. fortunately, there is + // no problem with overreading from the console. + max = qMin(left, 32); + } + else +#endif + { + max = qMin(left, pipe.bytesAvailable()); + } + int ret; #ifdef QPIPE_SECURE if(secure) { - QSecureArray a(qMin(left, pipe.bytesAvailable())); + QSecureArray a(max); ret = pipe.read(a.data(), a.size()); if(ret >= 1) { @@ -1130,7 +1727,7 @@ public slots: else #endif { - QByteArray a(qMin(left, pipe.bytesAvailable()), 0); + QByteArray a(max, 0); ret = pipe.read(a.data(), a.size()); if(ret >= 1) { @@ -1191,6 +1788,12 @@ public slots: reset(ResetSession); emit q->closed(); } + + void doWriteError() + { + reset(ResetSession); + emit q->error(QPipeEnd::ErrorBroken); + } }; QPipeEnd::QPipeEnd(QObject *parent)