blob: 71dad33e33cf381ddeaae20bf3269cf41470d99d [file] [log] [blame]
/****************************************************************************
**
** 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