SSH: Derive SshRemoteProcess from QIODevice.

Now it looks even more like QProcess. Things like process channels are
still missing.

Change-Id: I3f30cd00ed4a054d02e83add9a6f4162b48f8345
Reviewed-by: Christian Kandeler <christian.kandeler@nokia.com>
This commit is contained in:
Christian Kandeler
2011-11-15 17:13:02 +01:00
parent d9dde0d5e8
commit 4c76e40617
17 changed files with 171 additions and 91 deletions

View File

@@ -821,13 +821,13 @@ void SftpChannelPrivate::handleOpenSuccessInternal()
m_sftpState = SubsystemRequested;
}
void SftpChannelPrivate::handleOpenFailureInternal()
void SftpChannelPrivate::handleOpenFailureInternal(const QString &reason)
{
if (channelState() != SessionRequested) {
throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
"Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE packet.");
}
emit initializationFailed(tr("Server could not start session."));
emit initializationFailed(tr("Server could not start session: %1").arg(reason));
}
void SftpChannelPrivate::sendReadRequest(const SftpDownload::Ptr &job,

View File

@@ -74,7 +74,7 @@ private:
SftpJobId createJob(const AbstractSftpOperation::Ptr &job);
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data);

View File

@@ -84,7 +84,7 @@ void AbstractSshChannel::requestSessionStart()
setChannelState(SessionRequested);
m_timeoutTimer->start(ReplyTimeout);
} catch (Botan::Exception &e) {
m_errorString = QString::fromAscii(e.what());
qDebug("Botan error: %s", e.what());
closeChannel();
}
}
@@ -95,7 +95,7 @@ void AbstractSshChannel::sendData(const QByteArray &data)
m_sendBuffer += data;
flushSendBuffer();
} catch (Botan::Exception &e) {
m_errorString = QString::fromAscii(e.what());
qDebug("Botan error: %s", e.what());
closeChannel();
}
}
@@ -163,8 +163,7 @@ void AbstractSshChannel::handleOpenFailure(const QString &reason)
#ifdef CREATOR_SSH_DEBUG
qDebug("Channel open request failed for channel %u", m_localChannel);
#endif
m_errorString = reason;
handleOpenFailureInternal();
handleOpenFailureInternal(reason);
}
void AbstractSshChannel::handleChannelEof()

View File

@@ -58,9 +58,6 @@ public:
ChannelState channelState() const { return m_state; }
void setChannelState(ChannelState state);
void setError(const QString &error) { m_errorString = error; }
QString errorString() const { return m_errorString; }
quint32 localChannelId() const { return m_localChannel; }
quint32 remoteChannel() const { return m_remoteChannel; }
@@ -101,7 +98,7 @@ protected:
private:
virtual void handleOpenSuccessInternal() = 0;
virtual void handleOpenFailureInternal() = 0;
virtual void handleOpenFailureInternal(const QString &reason) = 0;
virtual void handleChannelDataInternal(const QByteArray &data) = 0;
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data) = 0;
@@ -119,7 +116,6 @@ private:
quint32 m_remoteMaxPacketSize;
ChannelState m_state;
QByteArray m_sendBuffer;
QString m_errorString;
};
} // namespace Internal

View File

@@ -42,6 +42,8 @@
#include <QtCore/QTimer>
#include <cstring>
/*!
\class Utils::SshRemoteProcess
@@ -49,17 +51,10 @@
Objects are created via SshConnection::createRemoteProcess.
The process is started via the start() member function.
A closeChannel() function is provided, but rarely useful, because
\list
\i a) when the process ends, the channel is closed automatically, and
\i b) closing a channel will not necessarily kill the remote process.
\endlist
Therefore, the only sensible use case for calling closeChannel() is to
get rid of an SshRemoteProces object before the process is actually started.
If the process needs a pseudo terminal, you can request one
via requestTerminal() before calling start().
Note that this class does not support QIODevice's waitFor*() functions, i.e. it has
no synchronous mode.
*/
namespace Utils {
@@ -99,12 +94,63 @@ SshRemoteProcess::~SshRemoteProcess()
delete d;
}
bool SshRemoteProcess::atEnd() const
{
return QIODevice::atEnd() && d->m_stdout.isEmpty();
}
qint64 SshRemoteProcess::bytesAvailable() const
{
return QIODevice::bytesAvailable() + d->m_stdout.count();
}
bool SshRemoteProcess::canReadLine() const
{
return QIODevice::canReadLine() || d->m_stdout.contains('\n'); // TODO: Not cross-platform?
}
QByteArray SshRemoteProcess::readAllStandardOutput()
{
return readAll();
}
QByteArray SshRemoteProcess::readAllStandardError()
{
const QByteArray data = d->m_stderr;
d->m_stderr.clear();
return data;
}
void SshRemoteProcess::close()
{
d->closeChannel();
QIODevice::close();
}
qint64 SshRemoteProcess::readData(char *data, qint64 maxlen)
{
const qint64 bytesRead = qMin(qint64(d->m_stdout.count()), maxlen);
memcpy(data, d->m_stdout.constData(), bytesRead);
d->m_stdout.remove(0, bytesRead);
return bytesRead;
}
qint64 SshRemoteProcess::writeData(const char *data, qint64 len)
{
if (isRunning()) {
d->sendData(QByteArray(data, len));
return len;
}
return 0;
}
void SshRemoteProcess::init()
{
connect(d, SIGNAL(started()), this, SIGNAL(started()),
Qt::QueuedConnection);
connect(d, SIGNAL(readyReadStandardOutput()), this, SIGNAL(readyReadStandardOutput()),
Qt::QueuedConnection);
connect(d, SIGNAL(readyReadStandardOutput()), this, SIGNAL(readyRead()), Qt::QueuedConnection);
connect(d, SIGNAL(readyReadStandardError()), this,
SIGNAL(readyReadStandardError()), Qt::QueuedConnection);
connect(d, SIGNAL(closed(int)), this, SIGNAL(closed(int)), Qt::QueuedConnection);
@@ -129,6 +175,7 @@ void SshRemoteProcess::start()
#ifdef CREATOR_SSH_DEBUG
qDebug("process start requested, channel id = %u", d->localChannelId());
#endif
QIODevice::open(QIODevice::ReadWrite);
d->requestSessionStart();
}
}
@@ -140,36 +187,19 @@ void SshRemoteProcess::sendSignal(const QByteArray &signal)
d->m_sendFacility.sendChannelSignalPacket(d->remoteChannel(),
signal);
} catch (Botan::Exception &e) {
d->setError(QString::fromAscii(e.what()));
setErrorString(QString::fromAscii(e.what()));
d->closeChannel();
}
}
void SshRemoteProcess::closeChannel()
{
d->closeChannel();
}
void SshRemoteProcess::sendInput(const QByteArray &data)
{
if (isRunning())
d->sendData(data);
}
bool SshRemoteProcess::isRunning() const
{
return d->m_procState == Internal::SshRemoteProcessPrivate::Running;
}
QString SshRemoteProcess::errorString() const { return d->errorString(); }
int SshRemoteProcess::exitCode() const { return d->m_exitCode; }
QByteArray SshRemoteProcess::exitSignal() const { return d->m_signal; }
QByteArray SshRemoteProcess::readAllStandardOutput() { return d->readAllStandardOutput(); }
QByteArray SshRemoteProcess::readAllStandardError() { return d->readAllStandardError(); }
namespace Internal {
SshRemoteProcessPrivate::SshRemoteProcessPrivate(const QByteArray &command,
@@ -214,20 +244,6 @@ void SshRemoteProcessPrivate::setProcState(ProcessState newState)
}
}
QByteArray SshRemoteProcessPrivate::readAllStandardOutput()
{
const QByteArray data = m_stdout;
m_stdout.clear();
return data;
}
QByteArray SshRemoteProcessPrivate::readAllStandardError()
{
const QByteArray data = m_stderr;
m_stderr.clear();
return data;
}
void SshRemoteProcessPrivate::closeHook()
{
if (m_wasRunning) {
@@ -256,9 +272,10 @@ void SshRemoteProcessPrivate::handleOpenSuccessInternal()
m_timeoutTimer->start(ReplyTimeout);
}
void SshRemoteProcessPrivate::handleOpenFailureInternal()
void SshRemoteProcessPrivate::handleOpenFailureInternal(const QString &reason)
{
setProcState(StartFailed);
m_proc->setErrorString(reason);
}
void SshRemoteProcessPrivate::handleChannelSuccess()
@@ -313,9 +330,9 @@ void SshRemoteProcessPrivate::handleExitSignal(const SshChannelExitSignal &signa
#ifdef CREATOR_SSH_DEBUG
qDebug("Exit due to signal %s", signal.signal.data());
#endif
setError(signal.error);
m_signal = signal.signal;
m_procState = Exited;
m_proc->setErrorString(tr("Process killed by signal"));
}
} // namespace Internal

View File

@@ -35,7 +35,7 @@
#include <utils/utils_global.h>
#include <QtCore/QObject>
#include <QtCore/QProcess>
#include <QtCore/QSharedPointer>
QT_BEGIN_NAMESPACE
@@ -50,7 +50,8 @@ class SshRemoteProcessPrivate;
class SshSendFacility;
} // namespace Internal
class QTCREATOR_UTILS_EXPORT SshRemoteProcess : public QObject
// TODO: ProcessChannel
class QTCREATOR_UTILS_EXPORT SshRemoteProcess : public QIODevice
{
Q_OBJECT
@@ -77,6 +78,13 @@ public:
~SshRemoteProcess();
// QIODevice stuff
bool atEnd() const;
qint64 bytesAvailable() const;
bool canReadLine() const;
void close();
bool isSequential() const { return true; }
/*
* Note that this is of limited value in practice, because servers are
* usually configured to ignore such requests for security reasons.
@@ -85,10 +93,8 @@ public:
void requestTerminal(const SshPseudoTerminal &terminal);
void start();
void closeChannel();
bool isRunning() const;
QString errorString() const;
int exitCode() const;
QByteArray exitSignal() const;
@@ -99,8 +105,6 @@ public:
void sendSignal(const QByteArray &signal);
void kill() { sendSignal(KillSignal); }
void sendInput(const QByteArray &data); // Should usually have a trailing newline.
signals:
void started();
@@ -118,6 +122,10 @@ private:
Internal::SshSendFacility &sendFacility);
SshRemoteProcess(quint32 channelId, Internal::SshSendFacility &sendFacility);
// QIODevice stuff
qint64 readData(char *data, qint64 maxlen);
qint64 writeData(const char *data, qint64 len);
void init();
Internal::SshRemoteProcessPrivate *d;

View File

@@ -60,9 +60,6 @@ public:
virtual void closeHook();
QByteArray readAllStandardOutput();
QByteArray readAllStandardError();
signals:
void started();
void readyReadStandardOutput();
@@ -76,7 +73,7 @@ private:
SshRemoteProcess *proc);
virtual void handleOpenSuccessInternal();
virtual void handleOpenFailureInternal();
virtual void handleOpenFailureInternal(const QString &reason);
virtual void handleChannelDataInternal(const QByteArray &data);
virtual void handleChannelExtendedDataInternal(quint32 type,
const QByteArray &data);

View File

@@ -341,7 +341,7 @@ void RemoteGdbProcess::sendInput(const QByteArray &data)
if (!isdigit(data.at(pos)))
break;
m_lastSeqNr = data.left(pos);
m_gdbProc->sendInput(data);
m_gdbProc->write(data);
}
void RemoteGdbProcess::handleAppOutput()

View File

@@ -95,7 +95,7 @@ qint64 SshIODevice::writeData (const char * data, qint64 maxSize)
startupbuffer += QByteArray::fromRawData(data, maxSize);
return maxSize;
}
proc->sendInput(QByteArray::fromRawData(data, maxSize));
proc->write(data, maxSize);
return maxSize;
}
qint64 SshIODevice::readData (char * data, qint64 maxSize)
@@ -128,7 +128,7 @@ qint64 SshIODevice::readData (char * data, qint64 maxSize)
void SshIODevice::processStarted()
{
proc = runner->process();
proc->sendInput(startupbuffer);
proc->write(startupbuffer);
}
void SshIODevice::outputAvailable(const QByteArray &output)

View File

@@ -89,7 +89,7 @@ void MaddeDeviceTester::stopTest()
case QtTest:
case MadDeveloperTest:
case QmlToolingTest:
m_processRunner->process()->closeChannel();
m_processRunner->process()->close();
break;
}

View File

@@ -447,7 +447,7 @@ void MaemoPublisherFremantleFree::prepareToSendFile()
emit progressReport(tr("Uploading file %1 ...")
.arg(QDir::toNativeSeparators(nextFilePath)));
QFileInfo info(nextFilePath);
m_uploader->process()->sendInput("C0644 " + QByteArray::number(info.size())
m_uploader->process()->write("C0644 " + QByteArray::number(info.size())
+ ' ' + info.fileName().toUtf8() + '\n');
}
@@ -473,13 +473,13 @@ void MaemoPublisherFremantleFree::sendFile()
tr("Upload failed."));
return;
}
m_uploader->process()->sendInput(data);
m_uploader->process()->write(data);
bytesToSend -= data.size();
QCoreApplication::processEvents();
if (m_state == Inactive)
return;
}
m_uploader->process()->sendInput(QByteArray(1, '\0'));
m_uploader->process()->write(QByteArray(1, '\0'));
}
void MaemoPublisherFremantleFree::handleScpStdOut(const QByteArray &output)

View File

@@ -380,11 +380,11 @@ void MaemoRemoteMounter::setState(State newState)
m_utfsServerTimer->stop();
if (m_mountProcess) {
disconnect(m_mountProcess.data(), 0, this, 0);
m_mountProcess->closeChannel();
m_mountProcess->close();
}
if (m_unmountProcess) {
disconnect(m_unmountProcess.data(), 0, this, 0);
m_unmountProcess->closeChannel();
m_unmountProcess->close();
}
}
m_state = newState;

View File

@@ -106,7 +106,7 @@ void GenericLinuxDeviceTester::stopTest()
d->portsGatherer.stop();
break;
case RunningUname:
d->process->closeChannel();
d->process->close();
break;
case Inactive:
break;

View File

@@ -115,7 +115,7 @@ void RemoteLinuxCustomCommandDeployService::stopDeployment()
QTC_ASSERT(d->state == Running, return);
disconnect(d->runner, 0, this, 0);
d->runner->process()->closeChannel();
d->runner->process()->close();
d->state = Inactive;
handleDeploymentDone();
}

View File

@@ -35,6 +35,7 @@
#include <utils/ssh/sshpseudoterminal.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QtCore/QTimer>
#include <iostream>
@@ -75,9 +76,10 @@ void RemoteProcessTest::run()
void RemoteProcessTest::handleConnectionError()
{
std::cerr << "Error: Connection failure ("
<< qPrintable(m_remoteRunner->lastConnectionErrorString()) << ")."
<< std::endl;
const QString error = m_state == TestingIoDevice
? m_sshConnection->errorString() : m_remoteRunner->lastConnectionErrorString();
std::cerr << "Error: Connection failure (" << qPrintable(error) << ")." << std::endl;
qApp->quit();
}
@@ -92,6 +94,11 @@ void RemoteProcessTest::handleProcessStarted()
Utils::SshRemoteProcessRunner * const killer
= new Utils::SshRemoteProcessRunner(this);
killer->run("pkill -9 sleep", m_sshParams);
} else if (m_state == TestingIoDevice) {
connect(m_catProcess.data(), SIGNAL(readyRead()), SLOT(handleReadyRead()));
m_textStream = new QTextStream(m_catProcess.data());
*m_textStream << testString();
m_textStream->flush();
}
}
}
@@ -198,10 +205,16 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
qApp->quit();
return;
}
std::cout << "Ok.\nAll tests succeeded." << std::endl;
qApp->quit();
std::cout << "Ok.\nTesting I/O device functionality... " << std::flush;
m_state = TestingIoDevice;
m_sshConnection = Utils::SshConnection::create(m_sshParams);
connect(m_sshConnection.data(), SIGNAL(connected()), SLOT(handleConnected()));
connect(m_sshConnection.data(), SIGNAL(error(Utils::SshError)),
SLOT(handleConnectionError()));
m_sshConnection->connectToHost();
break;
}
case TestingIoDevice:
case Inactive:
Q_ASSERT(false);
}
@@ -216,16 +229,23 @@ void RemoteProcessTest::handleProcessClosed(int exitStatus)
qApp->quit();
break;
case SshRemoteProcess::KilledBySignal:
if (m_state != TestingCrash) {
std::cerr << "Error: Unexpected crash." << std::endl;
qApp->quit();
return;
}
switch (m_state) {
case TestingCrash:
std::cout << "Ok.\nTesting remote process with terminal... " << std::flush;
m_state = TestingTerminal;
m_started = false;
m_timeoutTimer->start();
m_remoteRunner->runInTerminal("top -n 1", SshPseudoTerminal(), m_sshParams);
break;
case TestingIoDevice:
std::cout << "Ok.\nAll tests succeeded." << std::endl;
qApp->quit();
break;
default:
std::cerr << "Error: Unexpected crash." << std::endl;
qApp->quit();
return;
}
}
}
@@ -234,3 +254,35 @@ void RemoteProcessTest::handleTimeout()
std::cerr << "Error: Timeout waiting for progress." << std::endl;
qApp->quit();
}
void RemoteProcessTest::handleConnected()
{
Q_ASSERT(m_state == TestingIoDevice);
m_catProcess = m_sshConnection->createRemoteProcess(QString::fromLocal8Bit("cat").toUtf8());
connect(m_catProcess.data(), SIGNAL(started()), SLOT(handleProcessStarted()));
connect(m_catProcess.data(), SIGNAL(closed(int)), SLOT(handleProcessClosed(int)));
m_started = false;
m_timeoutTimer->start();
m_catProcess->start();
}
QString RemoteProcessTest::testString() const
{
return QLatin1String("x");
}
void RemoteProcessTest::handleReadyRead()
{
Q_ASSERT(m_state == TestingIoDevice);
const QString &data = QString::fromUtf8(m_catProcess->readAll());
if (data != testString()) {
std::cerr << "Testing of QIODevice functionality failed: Expected '"
<< qPrintable(testString()) << "', got '" << qPrintable(data) << "'." << std::endl;
qApp->exit(1);
}
Utils::SshRemoteProcessRunner * const killer = new Utils::SshRemoteProcessRunner(this);
killer->run("pkill -9 cat", m_sshParams);
}

View File

@@ -35,9 +35,11 @@
#include <utils/ssh/sshremoteprocessrunner.h>
QT_FORWARD_DECLARE_CLASS(QTimer);
#include <QtCore/QObject>
QT_FORWARD_DECLARE_CLASS(QTextStream)
QT_FORWARD_DECLARE_CLASS(QTimer)
class RemoteProcessTest : public QObject
{
Q_OBJECT
@@ -53,13 +55,22 @@ private slots:
void handleProcessStderr(const QByteArray &output);
void handleProcessClosed(int exitStatus);
void handleTimeout();
void handleReadyRead();
void handleConnected();
private:
enum State { Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal };
enum State {
Inactive, TestingSuccess, TestingFailure, TestingCrash, TestingTerminal, TestingIoDevice
};
QString testString() const;
const Utils::SshConnectionParameters m_sshParams;
QTimer * const m_timeoutTimer;
QTextStream *m_textStream;
Utils::SshRemoteProcessRunner * const m_remoteRunner;
Utils::SshRemoteProcess::Ptr m_catProcess;
Utils::SshConnection::Ptr m_sshConnection;
QByteArray m_remoteStdout;
QByteArray m_remoteStderr;
State m_state;

View File

@@ -115,5 +115,5 @@ void Shell::handleChannelClosed(int exitStatus)
void Shell::handleStdin()
{
m_shell->sendInput(m_stdin->readLine());
m_shell->write(m_stdin->readLine());
}