| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtNetwork module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qlocalsocket_p.h" |
| |
| #include <private/qthread_p.h> |
| #include <qcoreapplication.h> |
| #include <qdebug.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| void QLocalSocketPrivate::init() |
| { |
| Q_Q(QLocalSocket); |
| memset(&overlapped, 0, sizeof(overlapped)); |
| overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| dataReadNotifier = new QWinEventNotifier(overlapped.hEvent, q); |
| q->connect(dataReadNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_notified())); |
| } |
| |
| void QLocalSocketPrivate::setErrorString(const QString &function) |
| { |
| Q_Q(QLocalSocket); |
| BOOL windowsError = GetLastError(); |
| QLocalSocket::LocalSocketState currentState = state; |
| |
| // If the connectToServer fails due to WaitNamedPipe() time-out, assume ConnectionError |
| if (state == QLocalSocket::ConnectingState && windowsError == ERROR_SEM_TIMEOUT) |
| windowsError = ERROR_NO_DATA; |
| |
| switch (windowsError) { |
| case ERROR_PIPE_NOT_CONNECTED: |
| case ERROR_BROKEN_PIPE: |
| case ERROR_NO_DATA: |
| error = QLocalSocket::ConnectionError; |
| errorString = QLocalSocket::tr("%1: Connection error").arg(function); |
| state = QLocalSocket::UnconnectedState; |
| break; |
| case ERROR_FILE_NOT_FOUND: |
| error = QLocalSocket::ServerNotFoundError; |
| errorString = QLocalSocket::tr("%1: Invalid name").arg(function); |
| state = QLocalSocket::UnconnectedState; |
| break; |
| default: |
| error = QLocalSocket::UnknownSocketError; |
| errorString = QLocalSocket::tr("%1: Unknown error %2").arg(function).arg(windowsError); |
| #if defined QLOCALSOCKET_DEBUG |
| qWarning() << "QLocalSocket error not handled:" << errorString; |
| #endif |
| state = QLocalSocket::UnconnectedState; |
| } |
| |
| if (currentState != state) { |
| q->emit stateChanged(state); |
| if (state == QLocalSocket::UnconnectedState) |
| q->emit disconnected(); |
| } |
| emit q->error(error); |
| } |
| |
| QLocalSocketPrivate::QLocalSocketPrivate() : QIODevicePrivate(), |
| handle(INVALID_HANDLE_VALUE), |
| pipeWriter(0), |
| readBufferMaxSize(0), |
| actualReadBufferSize(0), |
| error(QLocalSocket::UnknownSocketError), |
| readSequenceStarted(false), |
| pendingReadyRead(false), |
| pipeClosed(false), |
| state(QLocalSocket::UnconnectedState) |
| { |
| } |
| |
| QLocalSocketPrivate::~QLocalSocketPrivate() |
| { |
| destroyPipeHandles(); |
| CloseHandle(overlapped.hEvent); |
| } |
| |
| void QLocalSocketPrivate::destroyPipeHandles() |
| { |
| if (handle != INVALID_HANDLE_VALUE) { |
| DisconnectNamedPipe(handle); |
| CloseHandle(handle); |
| } |
| } |
| |
| void QLocalSocket::connectToServer(const QString &name, OpenMode openMode) |
| { |
| Q_D(QLocalSocket); |
| if (state() == ConnectedState || state() == ConnectingState) |
| return; |
| |
| d->error = QLocalSocket::UnknownSocketError; |
| d->errorString = QString(); |
| d->state = ConnectingState; |
| emit stateChanged(d->state); |
| if (name.isEmpty()) { |
| d->error = QLocalSocket::ServerNotFoundError; |
| setErrorString(QLocalSocket::tr("%1: Invalid name").arg(QLatin1String("QLocalSocket::connectToServer"))); |
| d->state = UnconnectedState; |
| emit error(d->error); |
| emit stateChanged(d->state); |
| return; |
| } |
| |
| QString pipePath = QLatin1String("\\\\.\\pipe\\"); |
| if (name.startsWith(pipePath)) |
| d->fullServerName = name; |
| else |
| d->fullServerName = pipePath + name; |
| // Try to open a named pipe |
| HANDLE localSocket; |
| forever { |
| DWORD permissions = (openMode & QIODevice::ReadOnly) ? GENERIC_READ : 0; |
| permissions |= (openMode & QIODevice::WriteOnly) ? GENERIC_WRITE : 0; |
| localSocket = CreateFile((const wchar_t *)d->fullServerName.utf16(), // pipe name |
| permissions, |
| 0, // no sharing |
| NULL, // default security attributes |
| OPEN_EXISTING, // opens existing pipe |
| FILE_FLAG_OVERLAPPED, |
| NULL); // no template file |
| |
| if (localSocket != INVALID_HANDLE_VALUE) |
| break; |
| DWORD error = GetLastError(); |
| // It is really an error only if it is not ERROR_PIPE_BUSY |
| if (ERROR_PIPE_BUSY != error) { |
| break; |
| } |
| |
| // All pipe instances are busy, so wait until connected or up to 5 seconds. |
| if (!WaitNamedPipe((const wchar_t *)d->fullServerName.utf16(), 5000)) |
| break; |
| } |
| |
| if (localSocket == INVALID_HANDLE_VALUE) { |
| d->setErrorString(QLatin1String("QLocalSocket::connectToServer")); |
| d->fullServerName = QString(); |
| return; |
| } |
| |
| // we have a valid handle |
| d->serverName = name; |
| if (setSocketDescriptor((quintptr)localSocket, ConnectedState, openMode)) { |
| d->handle = localSocket; |
| emit connected(); |
| } |
| } |
| |
| // This is reading from the buffer |
| qint64 QLocalSocket::readData(char *data, qint64 maxSize) |
| { |
| Q_D(QLocalSocket); |
| |
| if (d->pipeClosed && d->actualReadBufferSize == 0) |
| return -1; // signal EOF |
| |
| qint64 readSoFar; |
| // If startAsyncRead() read data, copy it to its destination. |
| if (maxSize == 1 && d->actualReadBufferSize > 0) { |
| *data = d->readBuffer.getChar(); |
| d->actualReadBufferSize--; |
| readSoFar = 1; |
| } else { |
| qint64 bytesToRead = qMin(qint64(d->actualReadBufferSize), maxSize); |
| readSoFar = 0; |
| while (readSoFar < bytesToRead) { |
| const char *ptr = d->readBuffer.readPointer(); |
| int bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar, |
| qint64(d->readBuffer.nextDataBlockSize())); |
| memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); |
| readSoFar += bytesToReadFromThisBlock; |
| d->readBuffer.free(bytesToReadFromThisBlock); |
| d->actualReadBufferSize -= bytesToReadFromThisBlock; |
| } |
| } |
| |
| if (d->pipeClosed) { |
| if (d->actualReadBufferSize == 0) |
| QTimer::singleShot(0, this, SLOT(_q_pipeClosed())); |
| } else { |
| if (!d->readSequenceStarted) |
| d->startAsyncRead(); |
| d->checkReadyRead(); |
| } |
| |
| return readSoFar; |
| } |
| |
| /*! |
| \internal |
| Schedules or cancels a readyRead() emission depending on actual data availability |
| */ |
| void QLocalSocketPrivate::checkReadyRead() |
| { |
| if (actualReadBufferSize > 0) { |
| if (!pendingReadyRead) { |
| Q_Q(QLocalSocket); |
| QTimer::singleShot(0, q, SLOT(_q_emitReadyRead())); |
| pendingReadyRead = true; |
| } |
| } else { |
| pendingReadyRead = false; |
| } |
| } |
| |
| /*! |
| \internal |
| Reads data from the socket into the readbuffer |
| */ |
| void QLocalSocketPrivate::startAsyncRead() |
| { |
| do { |
| DWORD bytesToRead = checkPipeState(); |
| if (pipeClosed) |
| return; |
| |
| if (bytesToRead == 0) { |
| // There are no bytes in the pipe but we need to |
| // start the overlapped read with some buffer size. |
| bytesToRead = initialReadBufferSize; |
| } |
| |
| 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); |
| |
| readSequenceStarted = true; |
| if (ReadFile(handle, ptr, bytesToRead, NULL, &overlapped)) { |
| completeAsyncRead(); |
| } else { |
| switch (GetLastError()) { |
| case ERROR_IO_PENDING: |
| // This is not an error. We're getting notified, when data arrives. |
| return; |
| case ERROR_MORE_DATA: |
| // This is not an error. The synchronous read succeeded. |
| // We're connected to a message mode pipe and the message |
| // didn't fit into the pipe's system buffer. |
| completeAsyncRead(); |
| break; |
| 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. |
| pipeClosed = true; |
| Q_Q(QLocalSocket); |
| emit q->readChannelFinished(); |
| return; |
| } |
| default: |
| setErrorString(QLatin1String("QLocalSocketPrivate::startAsyncRead")); |
| return; |
| } |
| } |
| } while (!readSequenceStarted); |
| } |
| |
| /*! |
| \internal |
| Sets the correct size of the read buffer after a read operation. |
| Returns false, if an error occurred or the connection dropped. |
| */ |
| bool QLocalSocketPrivate::completeAsyncRead() |
| { |
| ResetEvent(overlapped.hEvent); |
| readSequenceStarted = false; |
| |
| DWORD bytesRead; |
| if (!GetOverlappedResult(handle, &overlapped, &bytesRead, TRUE)) { |
| switch (GetLastError()) { |
| 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_PIPE_NOT_CONNECTED: |
| return false; |
| default: |
| setErrorString(QLatin1String("QLocalSocketPrivate::completeAsyncRead")); |
| return false; |
| } |
| } |
| |
| actualReadBufferSize += bytesRead; |
| readBuffer.truncate(actualReadBufferSize); |
| return true; |
| } |
| |
| qint64 QLocalSocket::writeData(const char *data, qint64 maxSize) |
| { |
| Q_D(QLocalSocket); |
| if (!d->pipeWriter) { |
| d->pipeWriter = new QWindowsPipeWriter(d->handle, this); |
| connect(d->pipeWriter, SIGNAL(canWrite()), this, SLOT(_q_canWrite())); |
| connect(d->pipeWriter, SIGNAL(bytesWritten(qint64)), this, SIGNAL(bytesWritten(qint64))); |
| d->pipeWriter->start(); |
| } |
| return d->pipeWriter->write(data, maxSize); |
| } |
| |
| void QLocalSocket::abort() |
| { |
| close(); |
| } |
| |
| /*! |
| \internal |
| Returns the number of available bytes in the pipe. |
| Sets QLocalSocketPrivate::pipeClosed to true if the connection is broken. |
| */ |
| DWORD QLocalSocketPrivate::checkPipeState() |
| { |
| Q_Q(QLocalSocket); |
| DWORD bytes; |
| if (PeekNamedPipe(handle, NULL, 0, NULL, &bytes, NULL)) { |
| return bytes; |
| } else { |
| if (!pipeClosed) { |
| pipeClosed = true; |
| emit q->readChannelFinished(); |
| if (actualReadBufferSize == 0) |
| QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); |
| } |
| } |
| return 0; |
| } |
| |
| void QLocalSocketPrivate::_q_pipeClosed() |
| { |
| Q_Q(QLocalSocket); |
| q->close(); |
| } |
| |
| qint64 QLocalSocket::bytesAvailable() const |
| { |
| Q_D(const QLocalSocket); |
| qint64 available = QIODevice::bytesAvailable(); |
| available += (qint64) d->actualReadBufferSize; |
| return available; |
| } |
| |
| qint64 QLocalSocket::bytesToWrite() const |
| { |
| Q_D(const QLocalSocket); |
| return (d->pipeWriter) ? d->pipeWriter->bytesToWrite() : 0; |
| } |
| |
| bool QLocalSocket::canReadLine() const |
| { |
| Q_D(const QLocalSocket); |
| if (state() != ConnectedState) |
| return false; |
| return (QIODevice::canReadLine() |
| || d->readBuffer.indexOf('\n', d->actualReadBufferSize) != -1); |
| } |
| |
| void QLocalSocket::close() |
| { |
| Q_D(QLocalSocket); |
| if (state() == UnconnectedState) |
| return; |
| |
| QIODevice::close(); |
| d->state = ClosingState; |
| emit stateChanged(d->state); |
| if (!d->pipeClosed) |
| emit readChannelFinished(); |
| d->serverName = QString(); |
| d->fullServerName = QString(); |
| |
| if (state() != UnconnectedState && bytesToWrite() > 0) { |
| disconnectFromServer(); |
| return; |
| } |
| d->readSequenceStarted = false; |
| d->pendingReadyRead = false; |
| d->pipeClosed = false; |
| d->destroyPipeHandles(); |
| d->handle = INVALID_HANDLE_VALUE; |
| ResetEvent(d->overlapped.hEvent); |
| d->state = UnconnectedState; |
| emit stateChanged(d->state); |
| emit disconnected(); |
| if (d->pipeWriter) { |
| delete d->pipeWriter; |
| d->pipeWriter = 0; |
| } |
| } |
| |
| bool QLocalSocket::flush() |
| { |
| Q_D(QLocalSocket); |
| if (d->pipeWriter) |
| return d->pipeWriter->waitForWrite(0); |
| return false; |
| } |
| |
| void QLocalSocket::disconnectFromServer() |
| { |
| Q_D(QLocalSocket); |
| |
| // Are we still connected? |
| if (!isValid()) { |
| // If we have unwritten data, the pipeWriter is still present. |
| // It must be destroyed before close() to prevent an infinite loop. |
| delete d->pipeWriter; |
| d->pipeWriter = 0; |
| } |
| |
| flush(); |
| if (d->pipeWriter && d->pipeWriter->bytesToWrite() != 0) { |
| d->state = QLocalSocket::ClosingState; |
| emit stateChanged(d->state); |
| } else { |
| close(); |
| } |
| } |
| |
| QLocalSocket::LocalSocketError QLocalSocket::error() const |
| { |
| Q_D(const QLocalSocket); |
| return d->error; |
| } |
| |
| bool QLocalSocket::setSocketDescriptor(quintptr socketDescriptor, |
| LocalSocketState socketState, OpenMode openMode) |
| { |
| Q_D(QLocalSocket); |
| d->readBuffer.clear(); |
| d->actualReadBufferSize = 0; |
| QIODevice::open(openMode); |
| d->handle = (int*)socketDescriptor; |
| d->state = socketState; |
| emit stateChanged(d->state); |
| if (d->state == ConnectedState && openMode.testFlag(QIODevice::ReadOnly)) { |
| d->startAsyncRead(); |
| d->checkReadyRead(); |
| } |
| return true; |
| } |
| |
| void QLocalSocketPrivate::_q_canWrite() |
| { |
| Q_Q(QLocalSocket); |
| if (state == QLocalSocket::ClosingState) |
| q->close(); |
| } |
| |
| void QLocalSocketPrivate::_q_notified() |
| { |
| Q_Q(QLocalSocket); |
| if (!completeAsyncRead()) { |
| pipeClosed = true; |
| emit q->readChannelFinished(); |
| if (actualReadBufferSize == 0) |
| QTimer::singleShot(0, q, SLOT(_q_pipeClosed())); |
| return; |
| } |
| startAsyncRead(); |
| pendingReadyRead = false; |
| emit q->readyRead(); |
| } |
| |
| void QLocalSocketPrivate::_q_emitReadyRead() |
| { |
| if (pendingReadyRead) { |
| Q_Q(QLocalSocket); |
| pendingReadyRead = false; |
| emit q->readyRead(); |
| } |
| } |
| |
| quintptr QLocalSocket::socketDescriptor() const |
| { |
| Q_D(const QLocalSocket); |
| return (quintptr)d->handle; |
| } |
| |
| qint64 QLocalSocket::readBufferSize() const |
| { |
| Q_D(const QLocalSocket); |
| return d->readBufferMaxSize; |
| } |
| |
| void QLocalSocket::setReadBufferSize(qint64 size) |
| { |
| Q_D(QLocalSocket); |
| d->readBufferMaxSize = size; |
| } |
| |
| bool QLocalSocket::waitForConnected(int msecs) |
| { |
| Q_UNUSED(msecs); |
| return (state() == ConnectedState); |
| } |
| |
| bool QLocalSocket::waitForDisconnected(int msecs) |
| { |
| Q_D(QLocalSocket); |
| if (state() == UnconnectedState) |
| return false; |
| if (!openMode().testFlag(QIODevice::ReadOnly)) { |
| qWarning("QLocalSocket::waitForDisconnected isn't supported for write only pipes."); |
| return false; |
| } |
| QIncrementalSleepTimer timer(msecs); |
| forever { |
| d->checkPipeState(); |
| if (d->pipeClosed) |
| close(); |
| if (state() == UnconnectedState) |
| return true; |
| Sleep(timer.nextSleepTime()); |
| if (timer.hasTimedOut()) |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool QLocalSocket::isValid() const |
| { |
| Q_D(const QLocalSocket); |
| if (d->handle == INVALID_HANDLE_VALUE) |
| return false; |
| |
| return PeekNamedPipe(d->handle, NULL, 0, NULL, NULL, NULL); |
| } |
| |
| bool QLocalSocket::waitForReadyRead(int msecs) |
| { |
| Q_D(QLocalSocket); |
| |
| if (bytesAvailable() > 0) |
| return true; |
| |
| if (d->state != QLocalSocket::ConnectedState) |
| return false; |
| |
| // We already know that the pipe is gone, but did not enter the event loop yet. |
| if (d->pipeClosed) { |
| close(); |
| return false; |
| } |
| |
| Q_ASSERT(d->readSequenceStarted); |
| DWORD result = WaitForSingleObject(d->overlapped.hEvent, msecs == -1 ? INFINITE : msecs); |
| switch (result) { |
| case WAIT_OBJECT_0: |
| d->_q_notified(); |
| // We just noticed that the pipe is gone. |
| if (d->pipeClosed) { |
| close(); |
| return false; |
| } |
| return true; |
| case WAIT_TIMEOUT: |
| return false; |
| } |
| |
| qWarning("QLocalSocket::waitForReadyRead WaitForSingleObject failed with error code %d.", int(GetLastError())); |
| return false; |
| } |
| |
| bool QLocalSocket::waitForBytesWritten(int msecs) |
| { |
| Q_D(const QLocalSocket); |
| if (!d->pipeWriter) |
| return false; |
| |
| // Wait for the pipe writer to acknowledge that it has |
| // written. This will succeed if either the pipe writer has |
| // already written the data, or if it manages to write data |
| // within the given timeout. |
| return d->pipeWriter->waitForWrite(msecs); |
| } |
| |
| QT_END_NAMESPACE |