You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qtermwidget-packaging/lib/kptydevice.cpp

423 lines
11 KiB

/*
* This file is a part of QTerminal - http://gitorious.org/qterminal
*
* This file was un-linked from KDE and modified
* by Maxim Bourmistrov <maxim@unixconn.com>
*
*/
/*
This file is part of the KDE libraries
Copyright (C) 2007 Oswald Buddenhagen <ossi@kde.org>
Copyright (C) 2010 KDE e.V. <kde-ev-board@kde.org>
Author Adriaan de Groot <groot@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kptydevice.h"
#include "kpty_p.h"
#include <QSocketNotifier>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#ifdef HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#if defined(Q_OS_FREEBSD) || defined(Q_OS_MAC)
// "the other end's output queue size" - kinda braindead, huh?
# define PTY_BYTES_AVAILABLE TIOCOUTQ
#elif defined(TIOCINQ)
// "our end's input queue size"
# define PTY_BYTES_AVAILABLE TIOCINQ
#else
// likewise. more generic ioctl (theoretically)
# define PTY_BYTES_AVAILABLE FIONREAD
#endif
//////////////////
// private data //
//////////////////
// Lifted from Qt. I don't think they would mind. ;)
// Re-lift again from Qt whenever a proper replacement for pthread_once appears
static void qt_ignore_sigpipe()
{
static QBasicAtomicInt atom = Q_BASIC_ATOMIC_INITIALIZER(0);
if (atom.testAndSetRelaxed(0, 1)) {
struct sigaction noaction;
memset(&noaction, 0, sizeof(noaction));
noaction.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &noaction, 0);
}
}
#define NO_INTR(ret,func) do { ret = func; } while (ret < 0 && errno == EINTR)
bool KPtyDevicePrivate::_k_canRead()
{
Q_Q(KPtyDevice);
qint64 readBytes = 0;
#ifdef Q_OS_IRIX // this should use a config define, but how to check it?
size_t available;
#else
int available;
#endif
if (!::ioctl(q->masterFd(), PTY_BYTES_AVAILABLE, (char *) &available)) {
#ifdef Q_OS_SOLARIS
// A Pty is a STREAMS module, and those can be activated
// with 0 bytes available. This happens either when ^C is
// pressed, or when an application does an explicit write(a,b,0)
// which happens in experiments fairly often. When 0 bytes are
// available, you must read those 0 bytes to clear the STREAMS
// module, but we don't want to hit the !readBytes case further down.
if (!available) {
char c;
// Read the 0-byte STREAMS message
NO_INTR(readBytes, read(q->masterFd(), &c, 0));
// Should return 0 bytes read; -1 is error
if (readBytes < 0) {
readNotifier->setEnabled(false);
emit q->readEof();
return false;
}
return true;
}
#endif
char *ptr = readBuffer.reserve(available);
#ifdef Q_OS_SOLARIS
// Even if available > 0, it is possible for read()
// to return 0 on Solaris, due to 0-byte writes in the stream.
// Ignore them and keep reading until we hit *some* data.
// In Solaris it is possible to have 15 bytes available
// and to (say) get 0, 0, 6, 0 and 9 bytes in subsequent reads.
// Because the stream is set to O_NONBLOCK in finishOpen(),
// an EOF read will return -1.
readBytes = 0;
while (!readBytes)
#endif
// Useless block braces except in Solaris
{
NO_INTR(readBytes, read(q->masterFd(), ptr, available));
}
if (readBytes < 0) {
readBuffer.unreserve(available);
q->setErrorString("Error reading from PTY");
return false;
}
readBuffer.unreserve(available - readBytes); // *should* be a no-op
}
if (!readBytes) {
readNotifier->setEnabled(false);
emit q->readEof();
return false;
} else {
if (!emittedReadyRead) {
emittedReadyRead = true;
emit q->readyRead();
emittedReadyRead = false;
}
return true;
}
}
bool KPtyDevicePrivate::_k_canWrite()
{
Q_Q(KPtyDevice);
writeNotifier->setEnabled(false);
if (writeBuffer.isEmpty())
return false;
qt_ignore_sigpipe();
int wroteBytes;
NO_INTR(wroteBytes,
write(q->masterFd(),
writeBuffer.readPointer(), writeBuffer.readSize()));
if (wroteBytes < 0) {
q->setErrorString("Error writing to PTY");
return false;
}
writeBuffer.free(wroteBytes);
if (!emittedBytesWritten) {
emittedBytesWritten = true;
emit q->bytesWritten(wroteBytes);
emittedBytesWritten = false;
}
if (!writeBuffer.isEmpty())
writeNotifier->setEnabled(true);
return true;
}
#ifndef timeradd
// Lifted from GLIBC
# define timeradd(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
if ((result)->tv_usec >= 1000000) { \
++(result)->tv_sec; \
(result)->tv_usec -= 1000000; \
} \
} while (0)
# define timersub(a, b, result) \
do { \
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
if ((result)->tv_usec < 0) { \
--(result)->tv_sec; \
(result)->tv_usec += 1000000; \
} \
} while (0)
#endif
bool KPtyDevicePrivate::doWait(int msecs, bool reading)
{
Q_Q(KPtyDevice);
#ifndef __linux__
struct timeval etv;
#endif
struct timeval tv, *tvp;
if (msecs < 0)
tvp = 0;
else {
tv.tv_sec = msecs / 1000;
tv.tv_usec = (msecs % 1000) * 1000;
#ifndef __linux__
gettimeofday(&etv, 0);
timeradd(&tv, &etv, &etv);
#endif
tvp = &tv;
}
while (reading ? readNotifier->isEnabled() : !writeBuffer.isEmpty()) {
fd_set rfds;
fd_set wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
if (readNotifier->isEnabled())
FD_SET(q->masterFd(), &rfds);
if (!writeBuffer.isEmpty())
FD_SET(q->masterFd(), &wfds);
#ifndef __linux__
if (tvp) {
gettimeofday(&tv, 0);
timersub(&etv, &tv, &tv);
if (tv.tv_sec < 0)
tv.tv_sec = tv.tv_usec = 0;
}
#endif
switch (select(q->masterFd() + 1, &rfds, &wfds, 0, tvp)) {
case -1:
if (errno == EINTR)
break;
return false;
case 0:
q->setErrorString("PTY operation timed out");
return false;
default:
if (FD_ISSET(q->masterFd(), &rfds)) {
bool canRead = _k_canRead();
if (reading && canRead)
return true;
}
if (FD_ISSET(q->masterFd(), &wfds)) {
bool canWrite = _k_canWrite();
if (!reading)
return canWrite;
}
break;
}
}
return false;
}
void KPtyDevicePrivate::finishOpen(QIODevice::OpenMode mode)
{
Q_Q(KPtyDevice);
q->QIODevice::open(mode);
fcntl(q->masterFd(), F_SETFL, O_NONBLOCK);
readBuffer.clear();
readNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Read, q);
writeNotifier = new QSocketNotifier(q->masterFd(), QSocketNotifier::Write, q);
QObject::connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_k_canRead()));
QObject::connect(writeNotifier, SIGNAL(activated(int)), q, SLOT(_k_canWrite()));
readNotifier->setEnabled(true);
}
/////////////////////////////
// public member functions //
/////////////////////////////
KPtyDevice::KPtyDevice(QObject *parent) :
QIODevice(parent),
KPty(new KPtyDevicePrivate(this))
{
}
KPtyDevice::~KPtyDevice()
{
close();
}
bool KPtyDevice::open(OpenMode mode)
{
Q_D(KPtyDevice);
if (masterFd() >= 0)
return true;
if (!KPty::open()) {
setErrorString("Error opening PTY");
return false;
}
d->finishOpen(mode);
return true;
}
bool KPtyDevice::open(int fd, OpenMode mode)
{
Q_D(KPtyDevice);
if (!KPty::open(fd)) {
setErrorString("Error opening PTY");
return false;
}
d->finishOpen(mode);
return true;
}
void KPtyDevice::close()
{
Q_D(KPtyDevice);
if (masterFd() < 0)
return;
delete d->readNotifier;
delete d->writeNotifier;
QIODevice::close();
KPty::close();
}
bool KPtyDevice::isSequential() const
{
return true;
}
bool KPtyDevice::canReadLine() const
{
Q_D(const KPtyDevice);
return QIODevice::canReadLine() || d->readBuffer.canReadLine();
}
bool KPtyDevice::atEnd() const
{
Q_D(const KPtyDevice);
return QIODevice::atEnd() && d->readBuffer.isEmpty();
}
qint64 KPtyDevice::bytesAvailable() const
{
Q_D(const KPtyDevice);
return QIODevice::bytesAvailable() + d->readBuffer.size();
}
qint64 KPtyDevice::bytesToWrite() const
{
Q_D(const KPtyDevice);
return d->writeBuffer.size();
}
bool KPtyDevice::waitForReadyRead(int msecs)
{
Q_D(KPtyDevice);
return d->doWait(msecs, true);
}
bool KPtyDevice::waitForBytesWritten(int msecs)
{
Q_D(KPtyDevice);
return d->doWait(msecs, false);
}
void KPtyDevice::setSuspended(bool suspended)
{
Q_D(KPtyDevice);
d->readNotifier->setEnabled(!suspended);
}
bool KPtyDevice::isSuspended() const
{
Q_D(const KPtyDevice);
return !d->readNotifier->isEnabled();
}
// protected
qint64 KPtyDevice::readData(char *data, qint64 maxlen)
{
Q_D(KPtyDevice);
return d->readBuffer.read(data, (int)qMin<qint64>(maxlen, KMAXINT));
}
// protected
qint64 KPtyDevice::readLineData(char *data, qint64 maxlen)
{
Q_D(KPtyDevice);
return d->readBuffer.readLine(data, (int)qMin<qint64>(maxlen, KMAXINT));
}
// protected
qint64 KPtyDevice::writeData(const char *data, qint64 len)
{
Q_D(KPtyDevice);
Q_ASSERT(len <= KMAXINT);
d->writeBuffer.write(data, len);
d->writeNotifier->setEnabled(true);
return len;
}