| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtCore module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwindowspipereader_p.h" |
| #include "qiodevice_p.h" |
| #include <qelapsedtimer.h> |
| #include <qscopedvaluerollback.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| QWindowsPipeReader::Overlapped::Overlapped(QWindowsPipeReader *reader) |
| : pipeReader(reader) |
| { |
| } |
| |
| void QWindowsPipeReader::Overlapped::clear() |
| { |
| ZeroMemory(this, sizeof(OVERLAPPED)); |
| } |
| |
| |
| QWindowsPipeReader::QWindowsPipeReader(QObject *parent) |
| : QObject(parent), |
| handle(INVALID_HANDLE_VALUE), |
| overlapped(nullptr), |
| readBufferMaxSize(0), |
| actualReadBufferSize(0), |
| stopped(true), |
| readSequenceStarted(false), |
| notifiedCalled(false), |
| pipeBroken(false), |
| readyReadPending(false), |
| inReadyRead(false) |
| { |
| connect(this, &QWindowsPipeReader::_q_queueReadyRead, |
| this, &QWindowsPipeReader::emitPendingReadyRead, Qt::QueuedConnection); |
| } |
| |
| QWindowsPipeReader::~QWindowsPipeReader() |
| { |
| stop(); |
| delete overlapped; |
| } |
| |
| /*! |
| Sets the handle to read from. The handle must be valid. |
| */ |
| void QWindowsPipeReader::setHandle(HANDLE hPipeReadEnd) |
| { |
| readBuffer.clear(); |
| actualReadBufferSize = 0; |
| handle = hPipeReadEnd; |
| pipeBroken = false; |
| } |
| |
| /*! |
| Stops the asynchronous read sequence. |
| If the read sequence is running then the I/O operation is canceled. |
| */ |
| void QWindowsPipeReader::stop() |
| { |
| stopped = true; |
| if (readSequenceStarted) { |
| overlapped->pipeReader = nullptr; |
| if (!CancelIoEx(handle, overlapped)) { |
| const DWORD dwError = GetLastError(); |
| if (dwError != ERROR_NOT_FOUND) { |
| qErrnoWarning(dwError, "QWindowsPipeReader: CancelIoEx on handle %p failed.", |
| handle); |
| } |
| } |
| overlapped = nullptr; // The object will be deleted in the I/O callback. |
| readSequenceStarted = false; |
| } |
| } |
| |
| /*! |
| Returns the number of bytes we've read so far. |
| */ |
| qint64 QWindowsPipeReader::bytesAvailable() const |
| { |
| return actualReadBufferSize; |
| } |
| |
| /*! |
| Copies at most \c{maxlen} bytes from the internal read buffer to \c{data}. |
| */ |
| qint64 QWindowsPipeReader::read(char *data, qint64 maxlen) |
| { |
| if (pipeBroken && actualReadBufferSize == 0) |
| return 0; // signal EOF |
| |
| qint64 readSoFar; |
| // If startAsyncRead() has read data, copy it to its destination. |
| if (maxlen == 1 && actualReadBufferSize > 0) { |
| *data = readBuffer.getChar(); |
| actualReadBufferSize--; |
| readSoFar = 1; |
| } else { |
| readSoFar = readBuffer.read(data, qMin(actualReadBufferSize, maxlen)); |
| actualReadBufferSize -= readSoFar; |
| } |
| |
| if (!pipeBroken) { |
| if (!readSequenceStarted && !stopped) |
| startAsyncRead(); |
| if (readSoFar == 0) |
| return -2; // signal EWOULDBLOCK |
| } |
| |
| return readSoFar; |
| } |
| |
| bool QWindowsPipeReader::canReadLine() const |
| { |
| return readBuffer.indexOf('\n', actualReadBufferSize) >= 0; |
| } |
| |
| /*! |
| \internal |
| Will be called whenever the read operation completes. |
| */ |
| void QWindowsPipeReader::notified(DWORD errorCode, DWORD numberOfBytesRead) |
| { |
| notifiedCalled = true; |
| readSequenceStarted = false; |
| |
| switch (errorCode) { |
| case ERROR_SUCCESS: |
| break; |
| case ERROR_MORE_DATA: |
| // This is not an error. We're connected to a message mode |
| // pipe and the message didn't fit into the pipe's system |
| // buffer. We will read the remaining data in the next call. |
| break; |
| case ERROR_BROKEN_PIPE: |
| case ERROR_PIPE_NOT_CONNECTED: |
| pipeBroken = true; |
| break; |
| case ERROR_OPERATION_ABORTED: |
| if (stopped) |
| break; |
| Q_FALLTHROUGH(); |
| default: |
| emit winError(errorCode, QLatin1String("QWindowsPipeReader::notified")); |
| pipeBroken = true; |
| break; |
| } |
| |
| // After the reader was stopped, the only reason why this function can be called is the |
| // completion of a cancellation. No signals should be emitted, and no new read sequence should |
| // be started in this case. |
| if (stopped) |
| return; |
| |
| if (pipeBroken) { |
| emit pipeClosed(); |
| return; |
| } |
| |
| actualReadBufferSize += numberOfBytesRead; |
| readBuffer.truncate(actualReadBufferSize); |
| startAsyncRead(); |
| if (!readyReadPending) { |
| readyReadPending = true; |
| emit _q_queueReadyRead(QWindowsPipeReader::QPrivateSignal()); |
| } |
| } |
| |
| /*! |
| \internal |
| Reads data from the pipe into the readbuffer. |
| */ |
| void QWindowsPipeReader::startAsyncRead() |
| { |
| const DWORD minReadBufferSize = 4096; |
| qint64 bytesToRead = qMax(checkPipeState(), minReadBufferSize); |
| if (pipeBroken) |
| return; |
| |
| if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { |
| bytesToRead = readBufferMaxSize - readBuffer.size(); |
| if (bytesToRead <= 0) { |
| // Buffer is full. User must read data from the buffer |
| // before we can read more from the pipe. |
| return; |
| } |
| } |
| |
| char *ptr = readBuffer.reserve(bytesToRead); |
| |
| stopped = false; |
| readSequenceStarted = true; |
| if (!overlapped) |
| overlapped = new Overlapped(this); |
| overlapped->clear(); |
| if (!ReadFileEx(handle, ptr, bytesToRead, overlapped, &readFileCompleted)) { |
| readSequenceStarted = false; |
| |
| const DWORD dwError = GetLastError(); |
| switch (dwError) { |
| case ERROR_BROKEN_PIPE: |
| case ERROR_PIPE_NOT_CONNECTED: |
| // It may happen, that the other side closes the connection directly |
| // after writing data. Then we must set the appropriate socket state. |
| pipeBroken = true; |
| emit pipeClosed(); |
| break; |
| default: |
| emit winError(dwError, QLatin1String("QWindowsPipeReader::startAsyncRead")); |
| break; |
| } |
| } |
| } |
| |
| /*! |
| \internal |
| Called when ReadFileEx finished the read operation. |
| */ |
| void QWindowsPipeReader::readFileCompleted(DWORD errorCode, DWORD numberOfBytesTransfered, |
| OVERLAPPED *overlappedBase) |
| { |
| Overlapped *overlapped = static_cast<Overlapped *>(overlappedBase); |
| if (overlapped->pipeReader) |
| overlapped->pipeReader->notified(errorCode, numberOfBytesTransfered); |
| else |
| delete overlapped; |
| } |
| |
| /*! |
| \internal |
| Returns the number of available bytes in the pipe. |
| Sets QWindowsPipeReader::pipeBroken to true if the connection is broken. |
| */ |
| DWORD QWindowsPipeReader::checkPipeState() |
| { |
| DWORD bytes; |
| if (PeekNamedPipe(handle, nullptr, 0, nullptr, &bytes, nullptr)) |
| return bytes; |
| if (!pipeBroken) { |
| pipeBroken = true; |
| emit pipeClosed(); |
| } |
| return 0; |
| } |
| |
| bool QWindowsPipeReader::waitForNotification(int timeout) |
| { |
| QElapsedTimer t; |
| t.start(); |
| notifiedCalled = false; |
| int msecs = timeout; |
| while (SleepEx(msecs == -1 ? INFINITE : msecs, TRUE) == WAIT_IO_COMPLETION) { |
| if (notifiedCalled) |
| return true; |
| |
| // Some other I/O completion routine was called. Wait some more. |
| msecs = qt_subtract_from_timeout(timeout, t.elapsed()); |
| if (!msecs) |
| break; |
| } |
| return notifiedCalled; |
| } |
| |
| void QWindowsPipeReader::emitPendingReadyRead() |
| { |
| if (readyReadPending) { |
| readyReadPending = false; |
| QScopedValueRollback<bool> guard(inReadyRead, true); |
| emit readyRead(); |
| } |
| } |
| |
| /*! |
| Waits for the completion of the asynchronous read operation. |
| Returns \c true, if we've emitted the readyRead signal (non-recursive case) |
| or readyRead will be emitted by the event loop (recursive case). |
| */ |
| bool QWindowsPipeReader::waitForReadyRead(int msecs) |
| { |
| if (readyReadPending) { |
| if (!inReadyRead) |
| emitPendingReadyRead(); |
| return true; |
| } |
| |
| if (!readSequenceStarted) |
| return false; |
| |
| if (!waitForNotification(msecs)) |
| return false; |
| |
| if (readyReadPending) { |
| if (!inReadyRead) |
| emitPendingReadyRead(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /*! |
| Waits until the pipe is closed. |
| */ |
| bool QWindowsPipeReader::waitForPipeClosed(int msecs) |
| { |
| const int sleepTime = 10; |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| forever { |
| waitForReadyRead(0); |
| checkPipeState(); |
| if (pipeBroken) |
| return true; |
| if (stopWatch.hasExpired(msecs - sleepTime)) |
| return false; |
| Sleep(sleepTime); |
| } |
| } |
| |
| QT_END_NAMESPACE |