src/network/socket/qlocalsocket_win.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 14 May 2010 16:40:13 +0300
changeset 22 79de32ba3296
parent 18 2f34d5167611
child 33 3e2da88830cd
permissions -rw-r--r--
Revision: 201017 Kit: 201019

/****************************************************************************
**
** Copyright (C) 2010 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$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, 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.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $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);

    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 (readSoFar == 0) {
            QTimer::singleShot(0, this, SLOT(_q_pipeClosed()));
            return -1;  // signal EOF
        }
    } 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 = bytesAvailable();
        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_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 occured or the connection dropped.
 */
bool QLocalSocketPrivate::completeAsyncRead()
{
    ResetEvent(overlapped.hEvent);
    readSequenceStarted = false;

    DWORD bytesRead;
    if (!GetOverlappedResult(handle, &overlapped, &bytesRead, TRUE)) {
        if (GetLastError() != ERROR_PIPE_NOT_CONNECTED)
            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();
}

/*!
    The number of bytes available from the pipe
  */
DWORD QLocalSocketPrivate::bytesAvailable()
{
    Q_Q(QLocalSocket);
    DWORD bytes;
    if (PeekNamedPipe(handle, NULL, 0, NULL, &bytes, NULL)) {
        return bytes;
    } else {
        if (!pipeClosed) {
            pipeClosed = true;
            emit q->readChannelFinished();
            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();
        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->bytesAvailable();    // to check if PeekNamedPipe fails
        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;

    Q_ASSERT(d->readSequenceStarted);
    DWORD result = WaitForSingleObject(d->overlapped.hEvent, msecs == -1 ? INFINITE : msecs);
    switch (result) {
        case WAIT_OBJECT_0:
            d->_q_notified();
            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