Ssh: Use DeviceShell in LinuxDevice

Change-Id: I165f888dbb1e7072c35ec88ce5fd8a7ae4562139
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Marcus Tillmanns
2022-05-17 10:07:27 +02:00
parent 2f18256633
commit 2d82f2173d
3 changed files with 36 additions and 101 deletions

View File

@@ -64,6 +64,8 @@ public:
DeviceShell(); DeviceShell();
virtual ~DeviceShell(); virtual ~DeviceShell();
bool start();
bool runInShell(const CommandLine &cmd, const QByteArray &stdInData = {}); bool runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
RunResult outputForRunInShell(const CommandLine &cmd, const QByteArray &stdInData = {}); RunResult outputForRunInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
@@ -76,7 +78,6 @@ protected:
virtual void startupFailed(const CommandLine &cmdLine); virtual void startupFailed(const CommandLine &cmdLine);
RunResult run(const CommandLine &cmd, const QByteArray &stdInData = {}); RunResult run(const CommandLine &cmd, const QByteArray &stdInData = {});
bool start();
void close(); void close();
private: private:

View File

@@ -109,7 +109,6 @@ public:
ContainerShell(const QString &containerId) ContainerShell(const QString &containerId)
: m_containerId(containerId) : m_containerId(containerId)
{ {
start();
} }
private: private:
@@ -464,9 +463,7 @@ void DockerDevicePrivate::startContainer()
"or restart Qt Creator.")); "or restart Qt Creator."));
}); });
if (m_shell->state() != DeviceShell::State::Succeeded) { if (!m_shell->start()) {
m_shell.reset();
DockerApi::recheckDockerDaemon();
qCWarning(dockerDeviceLog) << "Container shell failed to start"; qCWarning(dockerDeviceLog) << "Container shell failed to start";
} }
} }

View File

@@ -46,6 +46,7 @@
#include <projectexplorer/runcontrol.h> #include <projectexplorer/runcontrol.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/deviceshell.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/port.h> #include <utils/port.h>
@@ -380,7 +381,6 @@ public:
bool setupShell(); bool setupShell();
bool runInShell(const CommandLine &cmd, const QByteArray &data = {}); bool runInShell(const CommandLine &cmd, const QByteArray &data = {});
QByteArray outputForRunInShell(const QString &cmd);
QByteArray outputForRunInShell(const CommandLine &cmd); QByteArray outputForRunInShell(const CommandLine &cmd);
void attachToSharedConnection(SshConnectionHandle *connectionHandle, void attachToSharedConnection(SshConnectionHandle *connectionHandle,
const SshParameters &sshParameters); const SshParameters &sshParameters);
@@ -734,7 +734,7 @@ void SshProcessInterfacePrivate::doStart()
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
{ {
Utils::CommandLine cmd{SshSettings::sshFilePath()}; CommandLine cmd{SshSettings::sshFilePath()};
if (!m_sshParameters.x11DisplayName.isEmpty()) if (!m_sshParameters.x11DisplayName.isEmpty())
cmd.addArg("-X"); cmd.addArg("-X");
@@ -769,6 +769,25 @@ static SshParameters displayless(const SshParameters &sshParameters)
class ShellThreadHandler : public QObject class ShellThreadHandler : public QObject
{ {
class LinuxDeviceShell : public DeviceShell
{
public:
LinuxDeviceShell(const CommandLine &cmdLine)
: m_cmdLine(cmdLine)
{
}
private:
void setupShellProcess(QtcProcess *shellProcess) override
{
SshParameters::setupSshEnvironment(shellProcess);
shellProcess->setCommand(m_cmdLine);
}
private:
const CommandLine m_cmdLine;
};
public: public:
~ShellThreadHandler() ~ShellThreadHandler()
{ {
@@ -778,10 +797,6 @@ public:
void closeShell() void closeShell()
{ {
if (m_shell && m_shell->isRunning()) {
m_shell->write("exit\n");
m_shell->waitForFinished(-1);
}
m_shell.reset(); m_shell.reset();
} }
@@ -790,9 +805,6 @@ public:
{ {
closeShell(); closeShell();
setSshParameters(parameters); setSshParameters(parameters);
m_shell.reset(new QtcProcess);
SshParameters::setupSshEnvironment(m_shell.get());
const FilePath sshPath = SshSettings::sshFilePath(); const FilePath sshPath = SshSettings::sshFilePath();
CommandLine cmd { sshPath }; CommandLine cmd { sshPath };
@@ -801,93 +813,23 @@ public:
<< m_displaylessSshParameters.host()); << m_displaylessSshParameters.host());
cmd.addArg("/bin/sh"); cmd.addArg("/bin/sh");
m_shell->setCommand(cmd); m_shell.reset(new LinuxDeviceShell(cmd));
m_shell->setProcessMode(ProcessMode::Writer); connect(m_shell.get(), &DeviceShell::done, this, [this] { m_shell.reset(); });
m_shell->setWriteData("echo\n"); return m_shell->start();
m_shell->start();
auto failed = [this] {
closeShell();
qCDebug(linuxDeviceLog) << "Failed to connect to" << m_displaylessSshParameters.host();
return false;
};
QDeadlineTimer timer(30000);
if (!m_shell->waitForStarted(timer.remainingTime()))
return failed();
while (true) {
if (!m_shell->waitForReadyRead(timer.remainingTime()))
return failed();
const QByteArray output = m_shell->readAllStandardOutput();
if (output == "\n")
break; // expected output from echo
if (output.size() > 0)
return failed(); // other unidentified output
// In case of trying to run a shell using SSH_ASKPASS, it may happen
// that we receive ready read signal but for error channel, while output
// channel still is empty. In this case we wait in loop until the user
// provides the right password, otherwise we timeout after 30 seconds.
}
return true;
} }
// Call me with shell mutex locked // Call me with shell mutex locked
bool runInShell(const CommandLine &cmd, const QByteArray &data = {}) bool runInShell(const CommandLine &cmd, const QByteArray &data = {})
{ {
QTC_ASSERT(m_shell, return false); QTC_ASSERT(m_shell, return false);
QTC_CHECK(m_shell->readAllStandardOutput().isNull()); // clean possible left-overs return m_shell->runInShell(cmd, data);
QTC_CHECK(m_shell->readAllStandardError().isNull()); // clean possible left-overs
QString prefix;
if (!data.isEmpty())
prefix = "echo '" + QString::fromUtf8(data.toBase64()) + "' | base64 -d | ";
const QString suffix = " > /dev/null 2>&1\necho $?\n";
const QString command = prefix + cmd.toUserOutput() + suffix;
m_shell->write(command);
DEBUG("RUN1 " << cmd.toUserOutput());
m_shell->waitForReadyRead();
const QByteArray output = m_shell->readAllStandardOutput();
DEBUG("GOT1 " << output);
bool ok = false;
const int result = output.toInt(&ok);
LOG("Run command in shell:" << cmd.toUserOutput() << "result: " << output << " ==>" << result);
QTC_ASSERT(ok, return false);
return !result;
} }
// Call me with shell mutex locked // Call me with shell mutex locked
QByteArray outputForRunInShell(const QString &cmd) QByteArray outputForRunInShell(const CommandLine &cmd)
{ {
QTC_ASSERT(m_shell, return {}); QTC_ASSERT(m_shell, return {});
QTC_CHECK(m_shell->readAllStandardOutput().isNull()); // clean possible left-overs return m_shell->outputForRunInShell(cmd).stdOut;
QTC_CHECK(m_shell->readAllStandardError().isNull()); // clean possible left-overs
auto cleanup = qScopeGuard([this] { m_shell->readAllStandardOutput(); }); // clean on assert
const QString suffix = " 2> /dev/null \necho $? 1>&2\n";
const QString command = cmd + suffix;
m_shell->write(command);
DEBUG("RUN2 " << cmd.toUserOutput());
while (true) {
m_shell->waitForReadyRead();
const QByteArray error = m_shell->readAllStandardError();
if (!error.isNull()) {
bool ok = false;
const int result = error.toInt(&ok);
QTC_ASSERT(ok, return {});
QTC_ASSERT(!result, return {});
break;
}
}
const QByteArray output = m_shell->readAllStandardOutput();
DEBUG("GOT2 " << output);
LOG("Run command in shell:" << cmd << "output size:" << output.size());
return output;
} }
void setSshParameters(const SshParameters &sshParameters) void setSshParameters(const SshParameters &sshParameters)
@@ -970,7 +912,7 @@ private:
mutable QMutex m_mutex; mutable QMutex m_mutex;
SshParameters m_displaylessSshParameters; SshParameters m_displaylessSshParameters;
QList<SshSharedConnection *> m_connections; QList<SshSharedConnection *> m_connections;
std::unique_ptr<QtcProcess> m_shell; std::unique_ptr<LinuxDeviceShell> m_shell;
}; };
// LinuxDevice // LinuxDevice
@@ -1089,7 +1031,7 @@ public:
private: private:
void start() override { m_reader.start(); } void start() override { m_reader.start(); }
void readerFinished() { emit finished(m_reader.remoteEnvironment(), true); } void readerFinished() { emit finished(m_reader.remoteEnvironment(), true); }
void readerError() { emit finished(Utils::Environment(), false); } void readerError() { emit finished(Environment(), false); }
Internal::RemoteLinuxEnvironmentReader m_reader; Internal::RemoteLinuxEnvironmentReader m_reader;
}; };
@@ -1172,7 +1114,7 @@ bool LinuxDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray &da
return ret; return ret;
} }
QByteArray LinuxDevicePrivate::outputForRunInShell(const QString &cmd) QByteArray LinuxDevicePrivate::outputForRunInShell(const CommandLine &cmd)
{ {
QMutexLocker locker(&m_shellMutex); QMutexLocker locker(&m_shellMutex);
DEBUG(cmd); DEBUG(cmd);
@@ -1185,11 +1127,6 @@ QByteArray LinuxDevicePrivate::outputForRunInShell(const QString &cmd)
return ret; return ret;
} }
QByteArray LinuxDevicePrivate::outputForRunInShell(const CommandLine &cmd)
{
return outputForRunInShell(cmd.toUserOutput());
}
void LinuxDevicePrivate::attachToSharedConnection(SshConnectionHandle *connectionHandle, void LinuxDevicePrivate::attachToSharedConnection(SshConnectionHandle *connectionHandle,
const SshParameters &sshParameters) const SshParameters &sshParameters)
{ {
@@ -1337,7 +1274,7 @@ qint64 LinuxDevice::bytesAvailable(const FilePath &filePath) const
CommandLine cmd("df", {"-k"}); CommandLine cmd("df", {"-k"});
cmd.addArg(filePath.path()); cmd.addArg(filePath.path());
cmd.addArgs("|tail -n 1 |sed 's/ */ /g'|cut -d ' ' -f 4", CommandLine::Raw); cmd.addArgs("|tail -n 1 |sed 's/ */ /g'|cut -d ' ' -f 4", CommandLine::Raw);
const QByteArray output = d->outputForRunInShell(cmd.toUserOutput()); const QByteArray output = d->outputForRunInShell(cmd);
bool ok = false; bool ok = false;
const qint64 size = output.toLongLong(&ok); const qint64 size = output.toLongLong(&ok);
if (ok) if (ok)
@@ -1365,7 +1302,7 @@ QFileDevice::Permissions LinuxDevice::permissions(const FilePath &filePath) cons
return perm; return perm;
} }
bool LinuxDevice::setPermissions(const Utils::FilePath &filePath, QFileDevice::Permissions permissions) const bool LinuxDevice::setPermissions(const FilePath &filePath, QFileDevice::Permissions permissions) const
{ {
QTC_ASSERT(handlesFile(filePath), return false); QTC_ASSERT(handlesFile(filePath), return false);
const int flags = int(permissions); const int flags = int(permissions);
@@ -1394,7 +1331,7 @@ QByteArray LinuxDevice::fileContents(const FilePath &filePath, qint64 limit, qin
CommandLine cmd(FilePath::fromString("dd"), args, CommandLine::Raw); CommandLine cmd(FilePath::fromString("dd"), args, CommandLine::Raw);
const QByteArray output = d->outputForRunInShell(cmd); const QByteArray output = d->outputForRunInShell(cmd);
DEBUG(output << output << QByteArray::fromHex(output)); DEBUG(output << QByteArray::fromHex(output));
return output; return output;
} }