2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2012-04-24 15:49:09 +02:00
|
|
|
|
2012-07-27 13:31:13 +02:00
|
|
|
#include "linuxdevice.h"
|
2011-05-31 12:47:53 +02:00
|
|
|
|
2012-04-06 18:28:16 +02:00
|
|
|
#include "genericlinuxdeviceconfigurationwidget.h"
|
2013-06-27 17:12:08 +02:00
|
|
|
#include "linuxdevicetester.h"
|
2022-04-29 13:18:42 +02:00
|
|
|
#include "linuxprocessinterface.h"
|
2012-04-06 18:28:16 +02:00
|
|
|
#include "publickeydeploymentdialog.h"
|
2011-07-25 11:55:00 +02:00
|
|
|
#include "remotelinux_constants.h"
|
2022-07-15 12:20:23 +02:00
|
|
|
#include "remotelinuxsignaloperation.h"
|
|
|
|
#include "remotelinuxtr.h"
|
2023-07-21 01:31:04 +02:00
|
|
|
#include "sshdevicewizard.h"
|
2011-07-25 11:55:00 +02:00
|
|
|
|
2019-02-20 19:13:28 +01:00
|
|
|
#include <coreplugin/icore.h>
|
2019-01-10 12:58:04 +01:00
|
|
|
#include <coreplugin/messagemanager.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
#include <projectexplorer/devicesupport/devicemanager.h>
|
2022-05-20 17:53:20 +02:00
|
|
|
#include <projectexplorer/devicesupport/filetransfer.h>
|
|
|
|
#include <projectexplorer/devicesupport/filetransferinterface.h>
|
2023-03-10 16:29:41 +01:00
|
|
|
#include <projectexplorer/devicesupport/processlist.h>
|
2022-05-13 01:08:44 +02:00
|
|
|
#include <projectexplorer/devicesupport/sshparameters.h>
|
|
|
|
#include <projectexplorer/devicesupport/sshsettings.h>
|
2019-03-13 08:06:08 +01:00
|
|
|
|
2014-06-16 18:25:52 +04:00
|
|
|
#include <utils/algorithm.h>
|
2022-10-10 17:32:56 +02:00
|
|
|
#include <utils/devicefileaccess.h>
|
2022-05-17 10:07:27 +02:00
|
|
|
#include <utils/deviceshell.h>
|
2019-05-21 18:15:29 +02:00
|
|
|
#include <utils/environment.h>
|
2019-01-10 12:58:04 +01:00
|
|
|
#include <utils/hostosinfo.h>
|
2016-04-19 16:43:30 +02:00
|
|
|
#include <utils/port.h>
|
2023-03-23 15:32:42 +01:00
|
|
|
#include <utils/portlist.h>
|
2023-05-03 17:05:35 +02:00
|
|
|
#include <utils/process.h>
|
2022-02-24 18:42:47 +01:00
|
|
|
#include <utils/processinfo.h>
|
2012-02-15 14:47:45 -08:00
|
|
|
#include <utils/qtcassert.h>
|
2020-06-17 06:35:31 +02:00
|
|
|
#include <utils/stringutils.h>
|
2021-07-27 09:50:43 +02:00
|
|
|
#include <utils/temporaryfile.h>
|
|
|
|
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
#include <QMutex>
|
2023-01-31 09:38:37 +01:00
|
|
|
#include <QReadWriteLock>
|
2021-07-27 09:50:43 +02:00
|
|
|
#include <QRegularExpression>
|
2022-02-14 18:37:56 +01:00
|
|
|
#include <QTemporaryDir>
|
2021-07-27 09:50:43 +02:00
|
|
|
#include <QThread>
|
2022-02-14 18:37:56 +01:00
|
|
|
#include <QTimer>
|
2011-08-02 12:20:16 +02:00
|
|
|
|
2012-07-26 16:23:20 +02:00
|
|
|
using namespace ProjectExplorer;
|
2019-06-20 17:19:12 +02:00
|
|
|
using namespace Utils;
|
2012-07-26 16:23:20 +02:00
|
|
|
|
2011-05-31 12:47:53 +02:00
|
|
|
namespace RemoteLinux {
|
2011-08-02 12:20:16 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
const QByteArray s_pidMarker = "__qtc";
|
|
|
|
|
2022-10-07 13:56:02 +02:00
|
|
|
static Q_LOGGING_CATEGORY(linuxDeviceLog, "qtc.remotelinux.device", QtWarningMsg);
|
|
|
|
#define DEBUG(x) qCDebug(linuxDeviceLog) << x << '\n'
|
2021-07-27 09:50:43 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
class SshSharedConnection : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
2022-05-12 18:43:44 +02:00
|
|
|
explicit SshSharedConnection(const SshParameters &sshParameters, QObject *parent = nullptr);
|
2022-02-14 18:37:56 +01:00
|
|
|
~SshSharedConnection() override;
|
|
|
|
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters sshParameters() const { return m_sshParameters; }
|
2022-02-14 18:37:56 +01:00
|
|
|
void ref();
|
|
|
|
void deref();
|
|
|
|
void makeStale();
|
|
|
|
|
|
|
|
void connectToHost();
|
|
|
|
void disconnectFromHost();
|
|
|
|
|
|
|
|
QProcess::ProcessState state() const;
|
|
|
|
QString socketFilePath() const
|
|
|
|
{
|
2022-12-14 13:28:56 +01:00
|
|
|
QTC_ASSERT(m_masterSocketDir, return {});
|
2022-02-14 18:37:56 +01:00
|
|
|
return m_masterSocketDir->path() + "/cs";
|
|
|
|
}
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void connected(const QString &socketFilePath);
|
|
|
|
void disconnected(const ProcessResultData &result);
|
|
|
|
|
|
|
|
void autoDestructRequested();
|
|
|
|
|
|
|
|
private:
|
|
|
|
void emitConnected();
|
2022-04-22 16:02:03 +02:00
|
|
|
void emitError(QProcess::ProcessError processError, const QString &errorString);
|
|
|
|
void emitDisconnected();
|
2022-05-03 23:38:14 +02:00
|
|
|
QString fullProcessError() const;
|
2022-05-11 17:58:24 +02:00
|
|
|
QStringList connectionArgs(const FilePath &binary) const;
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters m_sshParameters;
|
2023-05-03 16:00:22 +02:00
|
|
|
std::unique_ptr<Process> m_masterProcess;
|
2022-02-14 18:37:56 +01:00
|
|
|
std::unique_ptr<QTemporaryDir> m_masterSocketDir;
|
|
|
|
QTimer m_timer;
|
|
|
|
int m_ref = 0;
|
|
|
|
bool m_stale = false;
|
2022-04-22 16:02:03 +02:00
|
|
|
QProcess::ProcessState m_state = QProcess::NotRunning;
|
2022-02-14 18:37:56 +01:00
|
|
|
};
|
|
|
|
|
2022-05-12 18:43:44 +02:00
|
|
|
SshSharedConnection::SshSharedConnection(const SshParameters &sshParameters, QObject *parent)
|
2022-02-14 18:37:56 +01:00
|
|
|
: QObject(parent), m_sshParameters(sshParameters)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SshSharedConnection::~SshSharedConnection()
|
|
|
|
{
|
|
|
|
QTC_CHECK(m_ref == 0);
|
|
|
|
disconnect();
|
|
|
|
disconnectFromHost();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshSharedConnection::ref()
|
|
|
|
{
|
|
|
|
++m_ref;
|
|
|
|
m_timer.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshSharedConnection::deref()
|
|
|
|
{
|
|
|
|
QTC_ASSERT(m_ref, return);
|
|
|
|
if (--m_ref)
|
|
|
|
return;
|
|
|
|
if (m_stale) // no one uses it
|
|
|
|
deleteLater();
|
|
|
|
// not stale, so someone may reuse it
|
|
|
|
m_timer.start(SshSettings::connectionSharingTimeout() * 1000 * 60);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshSharedConnection::makeStale()
|
|
|
|
{
|
|
|
|
m_stale = true;
|
|
|
|
if (!m_ref) // no one uses it
|
|
|
|
deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshSharedConnection::connectToHost()
|
|
|
|
{
|
|
|
|
if (state() != QProcess::NotRunning)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const FilePath sshBinary = SshSettings::sshFilePath();
|
|
|
|
if (!sshBinary.exists()) {
|
2022-07-15 12:20:23 +02:00
|
|
|
emitError(QProcess::FailedToStart, Tr::tr("Cannot establish SSH connection: ssh binary "
|
2022-02-14 18:37:56 +01:00
|
|
|
"\"%1\" does not exist.").arg(sshBinary.toUserOutput()));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_masterSocketDir.reset(new QTemporaryDir);
|
|
|
|
if (!m_masterSocketDir->isValid()) {
|
2022-07-15 12:20:23 +02:00
|
|
|
emitError(QProcess::FailedToStart,
|
|
|
|
Tr::tr("Cannot establish SSH connection: Failed to create temporary "
|
|
|
|
"directory for control socket: %1")
|
2022-02-14 18:37:56 +01:00
|
|
|
.arg(m_masterSocketDir->errorString()));
|
|
|
|
m_masterSocketDir.reset();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
m_masterProcess.reset(new Process);
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters::setupSshEnvironment(m_masterProcess.get());
|
2022-02-14 18:37:56 +01:00
|
|
|
m_timer.setSingleShot(true);
|
|
|
|
connect(&m_timer, &QTimer::timeout, this, &SshSharedConnection::autoDestructRequested);
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(m_masterProcess.get(), &Process::readyReadStandardOutput, this, [this] {
|
2023-01-05 17:55:04 +01:00
|
|
|
const QByteArray reply = m_masterProcess->readAllRawStandardOutput();
|
2022-02-14 18:37:56 +01:00
|
|
|
if (reply == "\n")
|
|
|
|
emitConnected();
|
2022-04-22 16:02:03 +02:00
|
|
|
// TODO: otherwise emitError and finish master process?
|
2022-02-14 18:37:56 +01:00
|
|
|
});
|
2022-04-22 16:02:03 +02:00
|
|
|
// TODO: in case of refused connection we are getting the following on stdErr:
|
|
|
|
// ssh: connect to host 127.0.0.1 port 22: Connection refused\r\n
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(m_masterProcess.get(), &Process::done, this, [this] {
|
2022-05-03 23:38:14 +02:00
|
|
|
const ProcessResult result = m_masterProcess->result();
|
|
|
|
const ProcessResultData resultData = m_masterProcess->resultData();
|
|
|
|
if (result == ProcessResult::StartFailed) {
|
2022-07-15 12:20:23 +02:00
|
|
|
emitError(QProcess::FailedToStart, Tr::tr("Cannot establish SSH connection.\n"
|
|
|
|
"Control process failed to start."));
|
2022-02-14 18:37:56 +01:00
|
|
|
return;
|
2022-05-03 23:38:14 +02:00
|
|
|
} else if (result == ProcessResult::FinishedWithError) {
|
|
|
|
emitError(resultData.m_error, fullProcessError());
|
2022-02-14 18:37:56 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-05-03 23:38:14 +02:00
|
|
|
emit disconnected(resultData);
|
2022-02-14 18:37:56 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
QStringList args = QStringList{"-M", "-N", "-o", "ControlPersist=no",
|
|
|
|
"-o", "PermitLocalCommand=yes", // Enable local command
|
|
|
|
"-o", "LocalCommand=echo"} // Local command is executed after successfully
|
|
|
|
// connecting to the server. "echo" will print "\n"
|
|
|
|
// on the process output if everything went fine.
|
|
|
|
<< connectionArgs(sshBinary);
|
|
|
|
if (!m_sshParameters.x11DisplayName.isEmpty()) {
|
|
|
|
args.prepend("-X");
|
|
|
|
Environment env = m_masterProcess->environment();
|
|
|
|
env.set("DISPLAY", m_sshParameters.x11DisplayName);
|
|
|
|
m_masterProcess->setEnvironment(env);
|
|
|
|
}
|
|
|
|
m_masterProcess->setCommand(CommandLine(sshBinary, args));
|
|
|
|
m_masterProcess->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshSharedConnection::disconnectFromHost()
|
|
|
|
{
|
|
|
|
m_masterProcess.reset();
|
|
|
|
m_masterSocketDir.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
QProcess::ProcessState SshSharedConnection::state() const
|
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
return m_state;
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
void SshSharedConnection::emitConnected()
|
|
|
|
{
|
|
|
|
m_state = QProcess::Running;
|
|
|
|
emit connected(socketFilePath());
|
|
|
|
}
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
void SshSharedConnection::emitError(QProcess::ProcessError error, const QString &errorString)
|
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
m_state = QProcess::NotRunning;
|
2023-03-21 11:28:54 +01:00
|
|
|
ProcessResultData resultData{-1, QProcess::CrashExit, QProcess::UnknownError, {}};
|
|
|
|
if (m_masterProcess)
|
|
|
|
resultData = m_masterProcess->resultData();
|
2022-05-03 23:38:14 +02:00
|
|
|
resultData.m_error = error;
|
|
|
|
resultData.m_errorString = errorString;
|
|
|
|
emit disconnected(resultData);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
void SshSharedConnection::emitDisconnected()
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
m_state = QProcess::NotRunning;
|
|
|
|
emit disconnected(m_masterProcess->resultData());
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2022-05-03 23:38:14 +02:00
|
|
|
QString SshSharedConnection::fullProcessError() const
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2022-05-03 23:38:14 +02:00
|
|
|
const QString errorString = m_masterProcess->exitStatus() == QProcess::CrashExit
|
|
|
|
? m_masterProcess->errorString() : QString();
|
2022-06-17 14:17:14 +02:00
|
|
|
const QString standardError = m_masterProcess->cleanedStdErr();
|
2022-05-03 23:38:14 +02:00
|
|
|
const QString errorPrefix = errorString.isEmpty() && standardError.isEmpty()
|
2022-07-15 12:20:23 +02:00
|
|
|
? Tr::tr("SSH connection failure.") : Tr::tr("SSH connection failure:");
|
2022-05-03 23:38:14 +02:00
|
|
|
QStringList allErrors {errorPrefix, errorString, standardError};
|
|
|
|
allErrors.removeAll({});
|
|
|
|
return allErrors.join('\n');
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2022-05-11 17:58:24 +02:00
|
|
|
QStringList SshSharedConnection::connectionArgs(const FilePath &binary) const
|
|
|
|
{
|
|
|
|
return m_sshParameters.connectionOptions(binary) << "-o" << ("ControlPath=" + socketFilePath())
|
|
|
|
<< m_sshParameters.host();
|
|
|
|
}
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
// SshConnectionHandle
|
|
|
|
|
|
|
|
class SshConnectionHandle : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
SshConnectionHandle(const IDevice::ConstPtr &device) : m_device(device) {}
|
|
|
|
~SshConnectionHandle() override { emit detachFromSharedConnection(); }
|
|
|
|
|
|
|
|
signals:
|
|
|
|
// direction: connection -> caller
|
|
|
|
void connected(const QString &socketFilePath);
|
|
|
|
void disconnected(const ProcessResultData &result);
|
|
|
|
// direction: caller -> connection
|
|
|
|
void detachFromSharedConnection();
|
|
|
|
|
|
|
|
private:
|
|
|
|
// Store the IDevice::ConstPtr in order to extend the lifetime of device for as long
|
|
|
|
// as this object is alive.
|
|
|
|
IDevice::ConstPtr m_device;
|
|
|
|
};
|
|
|
|
|
|
|
|
// LinuxDevicePrivate
|
|
|
|
|
|
|
|
class ShellThreadHandler;
|
2022-10-10 17:32:56 +02:00
|
|
|
class LinuxDevicePrivate;
|
|
|
|
|
|
|
|
class LinuxDeviceFileAccess : public UnixDeviceFileAccess
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
LinuxDeviceFileAccess(LinuxDevicePrivate *dev)
|
|
|
|
: m_dev(dev)
|
|
|
|
{}
|
|
|
|
|
2022-10-14 13:39:16 +02:00
|
|
|
RunResult runInShell(const CommandLine &cmdLine,
|
2022-10-10 17:32:56 +02:00
|
|
|
const QByteArray &stdInData) const override;
|
|
|
|
|
2023-01-31 09:21:39 +01:00
|
|
|
Environment deviceEnvironment() const override;
|
|
|
|
|
2022-10-10 17:32:56 +02:00
|
|
|
LinuxDevicePrivate *m_dev;
|
|
|
|
};
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-09-07 12:44:04 +02:00
|
|
|
class LinuxDeviceSettings : public DeviceSettings
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
LinuxDeviceSettings() { displayName.setDefaultValue(Tr::tr("Remote Linux Device")); }
|
|
|
|
};
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
class LinuxDevicePrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit LinuxDevicePrivate(LinuxDevice *parent);
|
|
|
|
~LinuxDevicePrivate();
|
|
|
|
|
|
|
|
bool setupShell();
|
2022-10-07 16:54:27 +02:00
|
|
|
RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
void attachToSharedConnection(SshConnectionHandle *connectionHandle,
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters &sshParameters);
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-01-31 09:38:37 +01:00
|
|
|
Environment getEnvironment();
|
|
|
|
void invalidateEnvironmentCache();
|
|
|
|
|
2023-03-15 09:30:14 +01:00
|
|
|
void checkOsType();
|
|
|
|
void queryOsType(std::function<RunResult(const CommandLine &)> run);
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
LinuxDevice *q = nullptr;
|
|
|
|
QThread m_shellThread;
|
|
|
|
ShellThreadHandler *m_handler = nullptr;
|
|
|
|
mutable QMutex m_shellMutex;
|
2022-10-10 17:32:56 +02:00
|
|
|
LinuxDeviceFileAccess m_fileAccess{this};
|
2023-01-31 09:38:37 +01:00
|
|
|
|
|
|
|
QReadWriteLock m_environmentCacheLock;
|
|
|
|
std::optional<Environment> m_environmentCache;
|
2022-02-14 18:37:56 +01:00
|
|
|
};
|
|
|
|
|
2023-01-31 09:38:37 +01:00
|
|
|
void LinuxDevicePrivate::invalidateEnvironmentCache()
|
2022-10-10 17:32:56 +02:00
|
|
|
{
|
2023-01-31 09:38:37 +01:00
|
|
|
QWriteLocker locker(&m_environmentCacheLock);
|
|
|
|
m_environmentCache.reset();
|
2022-10-10 17:32:56 +02:00
|
|
|
}
|
|
|
|
|
2023-01-31 09:38:37 +01:00
|
|
|
Environment LinuxDevicePrivate::getEnvironment()
|
2023-01-31 09:21:39 +01:00
|
|
|
{
|
2023-01-31 09:38:37 +01:00
|
|
|
QReadLocker locker(&m_environmentCacheLock);
|
|
|
|
if (m_environmentCache.has_value())
|
|
|
|
return m_environmentCache.value();
|
|
|
|
|
|
|
|
locker.unlock();
|
|
|
|
QWriteLocker writeLocker(&m_environmentCacheLock);
|
|
|
|
if (m_environmentCache.has_value())
|
|
|
|
return m_environmentCache.value();
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
Process getEnvProc;
|
2023-03-28 07:40:26 +02:00
|
|
|
getEnvProc.setCommand({q->filePath("env"), {}});
|
2023-01-31 09:21:39 +01:00
|
|
|
getEnvProc.runBlocking();
|
|
|
|
|
|
|
|
const QString remoteOutput = getEnvProc.cleanedStdOut();
|
2023-01-31 09:38:37 +01:00
|
|
|
m_environmentCache = Environment(remoteOutput.split('\n', Qt::SkipEmptyParts), q->osType());
|
|
|
|
return m_environmentCache.value();
|
|
|
|
}
|
|
|
|
|
|
|
|
RunResult LinuxDeviceFileAccess::runInShell(const CommandLine &cmdLine,
|
|
|
|
const QByteArray &stdInData) const
|
|
|
|
{
|
|
|
|
return m_dev->runInShell(cmdLine, stdInData);
|
|
|
|
}
|
|
|
|
|
|
|
|
Environment LinuxDeviceFileAccess::deviceEnvironment() const
|
|
|
|
{
|
|
|
|
return m_dev->getEnvironment();
|
2023-01-31 09:21:39 +01:00
|
|
|
}
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
// SshProcessImpl
|
|
|
|
|
|
|
|
class SshProcessInterfacePrivate : public QObject
|
|
|
|
{
|
|
|
|
public:
|
2023-03-16 10:30:46 +01:00
|
|
|
SshProcessInterfacePrivate(SshProcessInterface *sshInterface, const IDevice::ConstPtr &device);
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
void start();
|
|
|
|
|
|
|
|
void handleConnected(const QString &socketFilePath);
|
|
|
|
void handleDisconnected(const ProcessResultData &result);
|
|
|
|
|
|
|
|
void handleStarted();
|
|
|
|
void handleDone();
|
|
|
|
void handleReadyReadStandardOutput();
|
|
|
|
void handleReadyReadStandardError();
|
|
|
|
|
|
|
|
void clearForStart();
|
|
|
|
void doStart();
|
|
|
|
CommandLine fullLocalCommandLine() const;
|
|
|
|
|
|
|
|
SshProcessInterface *q = nullptr;
|
|
|
|
|
|
|
|
qint64 m_processId = 0;
|
|
|
|
// Store the IDevice::ConstPtr in order to extend the lifetime of device for as long
|
|
|
|
// as this object is alive.
|
|
|
|
IDevice::ConstPtr m_device;
|
|
|
|
std::unique_ptr<SshConnectionHandle> m_connectionHandle;
|
2023-05-03 16:00:22 +02:00
|
|
|
Process m_process;
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
QString m_socketFilePath;
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters m_sshParameters;
|
2023-03-16 15:39:05 +01:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
bool m_connecting = false;
|
2022-04-29 13:18:42 +02:00
|
|
|
bool m_killed = false;
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
ProcessResultData m_result;
|
2023-03-16 10:30:46 +01:00
|
|
|
|
|
|
|
QByteArray m_output;
|
|
|
|
QByteArray m_error;
|
|
|
|
bool m_pidParsed = false;
|
2023-03-16 15:39:05 +01:00
|
|
|
bool m_useConnectionSharing = false;
|
2022-02-14 18:37:56 +01:00
|
|
|
};
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device)
|
|
|
|
: d(new SshProcessInterfacePrivate(this, device))
|
|
|
|
{}
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
SshProcessInterface::~SshProcessInterface()
|
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
killIfRunning();
|
2022-02-14 18:37:56 +01:00
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterface::emitStarted(qint64 processId)
|
|
|
|
{
|
|
|
|
d->m_processId = processId;
|
|
|
|
emit started(processId);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterface::killIfRunning()
|
|
|
|
{
|
2022-04-29 13:18:42 +02:00
|
|
|
if (d->m_killed || d->m_process.state() != QProcess::Running)
|
|
|
|
return;
|
|
|
|
sendControlSignal(ControlSignal::Kill);
|
|
|
|
d->m_killed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 SshProcessInterface::processId() const
|
|
|
|
{
|
|
|
|
return d->m_processId;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SshProcessInterface::runInShell(const CommandLine &command, const QByteArray &data)
|
|
|
|
{
|
2023-05-03 16:00:22 +02:00
|
|
|
Process process;
|
2022-11-09 19:29:41 +01:00
|
|
|
CommandLine cmd = {d->m_device->filePath("/bin/sh"), {"-c"}};
|
|
|
|
QString tmp;
|
|
|
|
ProcessArgs::addArg(&tmp, command.executable().path());
|
|
|
|
ProcessArgs::addArgs(&tmp, command.arguments());
|
|
|
|
cmd.addArg(tmp);
|
|
|
|
process.setCommand(cmd);
|
|
|
|
process.setWriteData(data);
|
|
|
|
process.start();
|
2022-12-09 15:19:59 +01:00
|
|
|
bool isFinished = process.waitForFinished(2000); // TODO: it may freeze on some devices
|
|
|
|
// otherwise we may start producing killers for killers
|
|
|
|
QTC_CHECK(isFinished);
|
|
|
|
return isFinished;
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterface::start()
|
|
|
|
{
|
|
|
|
d->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 SshProcessInterface::write(const QByteArray &data)
|
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
return d->m_process.writeRaw(data);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2023-01-24 07:23:44 +01:00
|
|
|
void SshProcessInterface::sendControlSignal(ControlSignal controlSignal)
|
2022-11-14 15:23:01 +01:00
|
|
|
{
|
2023-01-24 07:23:44 +01:00
|
|
|
if (controlSignal == ControlSignal::CloseWriteChannel) {
|
|
|
|
d->m_process.closeWriteChannel();
|
|
|
|
return;
|
|
|
|
}
|
2023-04-04 15:35:32 +02:00
|
|
|
if (d->m_process.usesTerminal() || d->m_process.ptyData().has_value()) {
|
2022-11-14 15:23:01 +01:00
|
|
|
switch (controlSignal) {
|
2023-01-24 07:23:44 +01:00
|
|
|
case ControlSignal::Terminate: d->m_process.terminate(); break;
|
|
|
|
case ControlSignal::Kill: d->m_process.kill(); break;
|
|
|
|
case ControlSignal::Interrupt: d->m_process.interrupt(); break;
|
|
|
|
case ControlSignal::KickOff: d->m_process.kickoffProcess(); break;
|
|
|
|
case ControlSignal::CloseWriteChannel: break;
|
2022-11-14 15:23:01 +01:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
handleSendControlSignal(controlSignal);
|
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2022-04-29 13:18:42 +02:00
|
|
|
QTC_ASSERT(controlSignal != ControlSignal::KickOff, return);
|
2023-01-24 07:23:44 +01:00
|
|
|
QTC_ASSERT(controlSignal != ControlSignal::CloseWriteChannel, return);
|
2022-04-29 13:18:42 +02:00
|
|
|
const qint64 pid = processId();
|
|
|
|
QTC_ASSERT(pid, return); // TODO: try sending a signal based on process name
|
2022-08-17 14:40:26 +02:00
|
|
|
const QString args = QString::fromLatin1("-%1 -%2")
|
2022-04-29 13:18:42 +02:00
|
|
|
.arg(controlSignalToInt(controlSignal)).arg(pid);
|
2022-08-17 14:40:26 +02:00
|
|
|
const CommandLine command = { "kill", args, CommandLine::Raw };
|
2022-04-29 13:18:42 +02:00
|
|
|
// Note: This blocking call takes up to 2 ms for local remote.
|
|
|
|
runInShell(command);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
void SshProcessInterfacePrivate::handleStarted()
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0;
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
// Don't emit started() when terminal is off,
|
|
|
|
// it's being done later inside handleReadyReadStandardOutput().
|
2023-03-16 10:30:46 +01:00
|
|
|
if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData)
|
2022-02-14 18:37:56 +01:00
|
|
|
return;
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
m_pidParsed = true;
|
2023-03-16 10:30:46 +01:00
|
|
|
q->emitStarted(processId);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
void SshProcessInterfacePrivate::handleDone()
|
2022-05-25 20:08:12 +02:00
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
if (m_connectionHandle) // TODO: should it disconnect from signals first?
|
|
|
|
m_connectionHandle.release()->deleteLater();
|
|
|
|
|
|
|
|
ProcessResultData finalData = m_process.resultData();
|
2022-05-25 20:08:12 +02:00
|
|
|
if (!m_pidParsed) {
|
|
|
|
finalData.m_error = QProcess::FailedToStart;
|
2023-02-03 11:54:23 +01:00
|
|
|
finalData.m_errorString = Utils::joinStrings({finalData.m_errorString,
|
|
|
|
QString::fromLocal8Bit(m_error)}, '\n');
|
2022-05-25 20:08:12 +02:00
|
|
|
}
|
2023-03-16 10:30:46 +01:00
|
|
|
emit q->done(finalData);
|
2022-05-25 20:08:12 +02:00
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
void SshProcessInterfacePrivate::handleReadyReadStandardOutput()
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
// By default this forwards readyRead immediately, but only buffers the
|
|
|
|
// output in case the start signal is not emitted yet.
|
|
|
|
// In case the pid can be parsed now, the delayed started() is
|
|
|
|
// emitted, and any previously buffered output emitted now.
|
|
|
|
const QByteArray outputData = m_process.readAllRawStandardOutput();
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
if (m_pidParsed) {
|
2023-03-16 10:30:46 +01:00
|
|
|
emit q->readyRead(outputData, {});
|
2022-02-14 18:37:56 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_output.append(outputData);
|
|
|
|
|
|
|
|
static const QByteArray endMarker = s_pidMarker + '\n';
|
2023-03-01 08:15:58 +01:00
|
|
|
int endMarkerLength = endMarker.length();
|
|
|
|
int endMarkerOffset = m_output.indexOf(endMarker);
|
|
|
|
if (endMarkerOffset == -1) {
|
|
|
|
static const QByteArray endMarker = s_pidMarker + "\r\n";
|
|
|
|
endMarkerOffset = m_output.indexOf(endMarker);
|
|
|
|
endMarkerLength = endMarker.length();
|
|
|
|
if (endMarkerOffset == -1)
|
|
|
|
return;
|
|
|
|
}
|
2022-02-14 18:37:56 +01:00
|
|
|
const int startMarkerOffset = m_output.indexOf(s_pidMarker);
|
|
|
|
if (startMarkerOffset == endMarkerOffset) // Only theoretically possible.
|
|
|
|
return;
|
|
|
|
const int pidStart = startMarkerOffset + s_pidMarker.length();
|
|
|
|
const QByteArray pidString = m_output.mid(pidStart, endMarkerOffset - pidStart);
|
|
|
|
m_pidParsed = true;
|
|
|
|
const qint64 processId = pidString.toLongLong();
|
|
|
|
|
|
|
|
// We don't want to show output from e.g. /etc/profile.
|
2023-03-01 08:15:58 +01:00
|
|
|
m_output = m_output.mid(endMarkerOffset + endMarkerLength);
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
q->emitStarted(processId);
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2022-05-04 13:09:22 +02:00
|
|
|
if (!m_output.isEmpty() || !m_error.isEmpty())
|
2023-03-16 10:30:46 +01:00
|
|
|
emit q->readyRead(m_output, m_error);
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
m_output.clear();
|
2022-05-04 13:09:22 +02:00
|
|
|
m_error.clear();
|
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
void SshProcessInterfacePrivate::handleReadyReadStandardError()
|
2022-05-04 13:09:22 +02:00
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
// By default forwards readyRead immediately, but buffers it in
|
|
|
|
// case the start signal is not emitted yet.
|
|
|
|
const QByteArray errorData = m_process.readAllRawStandardError();
|
|
|
|
|
2022-05-04 13:09:22 +02:00
|
|
|
if (m_pidParsed) {
|
2023-03-16 10:30:46 +01:00
|
|
|
emit q->readyRead({}, errorData);
|
2022-05-04 13:09:22 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_error.append(errorData);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
SshProcessInterfacePrivate::SshProcessInterfacePrivate(SshProcessInterface *sshInterface,
|
2023-03-16 10:30:46 +01:00
|
|
|
const IDevice::ConstPtr &device)
|
2022-02-14 18:37:56 +01:00
|
|
|
: QObject(sshInterface)
|
|
|
|
, q(sshInterface)
|
2023-03-16 10:30:46 +01:00
|
|
|
, m_device(device)
|
2022-05-02 14:00:42 +02:00
|
|
|
, m_process(this)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(&m_process, &Process::started, this, &SshProcessInterfacePrivate::handleStarted);
|
|
|
|
connect(&m_process, &Process::done, this, &SshProcessInterfacePrivate::handleDone);
|
|
|
|
connect(&m_process, &Process::readyReadStandardOutput,
|
2022-02-14 18:37:56 +01:00
|
|
|
this, &SshProcessInterfacePrivate::handleReadyReadStandardOutput);
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(&m_process, &Process::readyReadStandardError,
|
2022-02-14 18:37:56 +01:00
|
|
|
this, &SshProcessInterfacePrivate::handleReadyReadStandardError);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterfacePrivate::start()
|
|
|
|
{
|
|
|
|
clearForStart();
|
2023-03-31 12:10:20 +02:00
|
|
|
m_sshParameters = m_device->sshParameters();
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
|
2023-03-31 12:10:20 +02:00
|
|
|
if (const IDevice::ConstPtr linkDevice = DeviceManager::instance()->find(linkDeviceId)) {
|
|
|
|
CommandLine cmd{linkDevice->filePath("ssh")};
|
|
|
|
if (!m_sshParameters.userName().isEmpty()) {
|
|
|
|
cmd.addArg("-l");
|
|
|
|
cmd.addArg(m_sshParameters.userName());
|
|
|
|
}
|
|
|
|
cmd.addArg(m_sshParameters.host());
|
|
|
|
|
2023-04-06 11:14:30 +02:00
|
|
|
const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off
|
|
|
|
|| q->m_setup.m_ptyData;
|
|
|
|
if (useTerminal)
|
|
|
|
cmd.addArg("-tt");
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
const CommandLine full = q->m_setup.m_commandLine;
|
2023-04-20 17:59:26 +02:00
|
|
|
if (!full.isEmpty()) { // Empty is ok in case of opening a terminal.
|
|
|
|
CommandLine inner;
|
|
|
|
const QString wd = q->m_setup.m_workingDirectory.path();
|
|
|
|
if (!wd.isEmpty())
|
|
|
|
inner.addCommandLineWithAnd({"cd", {wd}});
|
2023-04-06 11:14:30 +02:00
|
|
|
if (!useTerminal) {
|
2023-04-20 17:59:26 +02:00
|
|
|
const QString pidArg = QString("%1\\$\\$%1").arg(QString::fromLatin1(s_pidMarker));
|
|
|
|
inner.addCommandLineWithAnd({"echo", pidArg, CommandLine::Raw});
|
2023-04-06 11:14:30 +02:00
|
|
|
}
|
2023-04-20 17:59:26 +02:00
|
|
|
inner.addCommandLineWithAnd(full);
|
|
|
|
cmd.addCommandLineAsSingleArg(inner);
|
2023-03-31 12:10:20 +02:00
|
|
|
}
|
|
|
|
|
2023-04-06 11:14:30 +02:00
|
|
|
m_process.setProcessImpl(q->m_setup.m_processImpl);
|
|
|
|
m_process.setProcessMode(q->m_setup.m_processMode);
|
|
|
|
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
|
|
|
m_process.setPtyData(q->m_setup.m_ptyData);
|
|
|
|
m_process.setReaperTimeout(q->m_setup.m_reaperTimeout);
|
|
|
|
m_process.setWriteData(q->m_setup.m_writeData);
|
|
|
|
m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows);
|
|
|
|
m_process.setExtraData(q->m_setup.m_extraData);
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
m_process.setCommand(cmd);
|
|
|
|
m_process.start();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_useConnectionSharing = SshSettings::connectionSharingEnabled();
|
2023-03-16 15:39:05 +01:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
// TODO: Do we really need it for master process?
|
|
|
|
m_sshParameters.x11DisplayName
|
|
|
|
= q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString();
|
2023-03-16 15:39:05 +01:00
|
|
|
if (m_useConnectionSharing) {
|
2022-02-14 18:37:56 +01:00
|
|
|
m_connecting = true;
|
2023-03-16 10:30:46 +01:00
|
|
|
m_connectionHandle.reset(new SshConnectionHandle(m_device));
|
2022-05-02 14:00:42 +02:00
|
|
|
m_connectionHandle->setParent(this);
|
2022-02-14 18:37:56 +01:00
|
|
|
connect(m_connectionHandle.get(), &SshConnectionHandle::connected,
|
|
|
|
this, &SshProcessInterfacePrivate::handleConnected);
|
|
|
|
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
|
|
|
|
this, &SshProcessInterfacePrivate::handleDisconnected);
|
2023-03-16 10:30:46 +01:00
|
|
|
auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
|
|
|
|
QTC_ASSERT(linuxDevice, handleDone(); return);
|
|
|
|
linuxDevice->connectionAccess()
|
|
|
|
->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
|
2022-02-14 18:37:56 +01:00
|
|
|
} else {
|
|
|
|
doStart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterfacePrivate::handleConnected(const QString &socketFilePath)
|
|
|
|
{
|
|
|
|
m_connecting = false;
|
|
|
|
m_socketFilePath = socketFilePath;
|
|
|
|
doStart();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterfacePrivate::handleDisconnected(const ProcessResultData &result)
|
|
|
|
{
|
|
|
|
ProcessResultData resultData = result;
|
|
|
|
if (m_connecting)
|
|
|
|
resultData.m_error = QProcess::FailedToStart;
|
|
|
|
|
|
|
|
m_connecting = false;
|
|
|
|
if (m_connectionHandle) // TODO: should it disconnect from signals first?
|
|
|
|
m_connectionHandle.release()->deleteLater();
|
|
|
|
|
2022-05-03 23:38:14 +02:00
|
|
|
if (resultData.m_error != QProcess::UnknownError || m_process.state() != QProcess::NotRunning)
|
2022-02-14 18:37:56 +01:00
|
|
|
emit q->done(resultData); // TODO: don't emit done() on process finished afterwards
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterfacePrivate::clearForStart()
|
|
|
|
{
|
|
|
|
m_result = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
void SshProcessInterfacePrivate::doStart()
|
|
|
|
{
|
|
|
|
m_process.setProcessImpl(q->m_setup.m_processImpl);
|
|
|
|
m_process.setProcessMode(q->m_setup.m_processMode);
|
|
|
|
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
2023-03-02 14:03:54 +01:00
|
|
|
m_process.setPtyData(q->m_setup.m_ptyData);
|
2022-06-03 14:28:30 +02:00
|
|
|
m_process.setReaperTimeout(q->m_setup.m_reaperTimeout);
|
2022-04-22 16:02:03 +02:00
|
|
|
m_process.setWriteData(q->m_setup.m_writeData);
|
2023-03-14 09:09:55 +01:00
|
|
|
m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows);
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
// TODO: what about other fields from m_setup?
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters::setupSshEnvironment(&m_process);
|
2022-02-14 18:37:56 +01:00
|
|
|
if (!m_sshParameters.x11DisplayName.isEmpty()) {
|
2022-05-09 15:02:23 +02:00
|
|
|
Environment env = m_process.controlEnvironment();
|
2022-02-14 18:37:56 +01:00
|
|
|
// Note: it seems this is no-op when shared connection is used.
|
|
|
|
// In this case the display is taken from master process.
|
|
|
|
env.set("DISPLAY", m_sshParameters.x11DisplayName);
|
2022-05-09 15:02:23 +02:00
|
|
|
m_process.setControlEnvironment(env);
|
2022-02-14 18:37:56 +01:00
|
|
|
}
|
2023-03-16 10:30:46 +01:00
|
|
|
m_process.setExtraData(q->m_setup.m_extraData);
|
2022-02-14 18:37:56 +01:00
|
|
|
m_process.setCommand(fullLocalCommandLine());
|
|
|
|
m_process.start();
|
|
|
|
}
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2023-03-31 12:10:20 +02:00
|
|
|
const FilePath sshBinary = SshSettings::sshFilePath();
|
|
|
|
const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData;
|
|
|
|
const bool usePidMarker = !useTerminal;
|
|
|
|
const bool sourceProfile = m_device->extraData(Constants::SourceProfile).toBool();
|
|
|
|
const bool useX = !m_sshParameters.x11DisplayName.isEmpty();
|
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
CommandLine cmd{sshBinary};
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
if (useX)
|
2022-02-14 18:37:56 +01:00
|
|
|
cmd.addArg("-X");
|
2023-03-16 15:39:05 +01:00
|
|
|
if (useTerminal)
|
2022-02-14 18:37:56 +01:00
|
|
|
cmd.addArg("-tt");
|
|
|
|
|
|
|
|
cmd.addArg("-q");
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
cmd.addArgs(m_sshParameters.connectionOptions(sshBinary));
|
|
|
|
if (!m_socketFilePath.isEmpty())
|
|
|
|
cmd.addArgs({"-o", "ControlPath=" + m_socketFilePath});
|
2022-02-14 18:37:56 +01:00
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
cmd.addArg(m_sshParameters.host());
|
|
|
|
|
|
|
|
CommandLine commandLine = q->m_setup.m_commandLine;
|
2023-03-16 10:30:46 +01:00
|
|
|
FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path());
|
|
|
|
commandLine.setExecutable(executable);
|
|
|
|
|
|
|
|
CommandLine inner;
|
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
if (!commandLine.isEmpty() && sourceProfile) {
|
2023-03-16 10:30:46 +01:00
|
|
|
const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
|
|
|
|
for (const QString &filePath : rcFilesToSource) {
|
|
|
|
inner.addArgs({"test", "-f", filePath});
|
|
|
|
inner.addArgs("&&", CommandLine::Raw);
|
|
|
|
inner.addArgs({".", filePath});
|
|
|
|
inner.addArgs(";", CommandLine::Raw);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
const FilePath &workingDirectory = q->m_setup.m_workingDirectory;
|
2023-03-16 15:39:05 +01:00
|
|
|
if (!workingDirectory.isEmpty()) {
|
|
|
|
inner.addArgs({"cd", workingDirectory.path()});
|
2023-03-16 10:30:46 +01:00
|
|
|
inner.addArgs("&&", CommandLine::Raw);
|
|
|
|
}
|
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
if (usePidMarker)
|
2023-03-16 10:30:46 +01:00
|
|
|
inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
|
|
|
|
|
2023-03-31 12:10:20 +02:00
|
|
|
const Environment &env = q->m_setup.m_environment;
|
2023-03-16 10:30:46 +01:00
|
|
|
env.forEachEntry([&](const QString &key, const QString &value, bool) {
|
|
|
|
inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw);
|
|
|
|
});
|
|
|
|
|
2023-03-16 15:39:05 +01:00
|
|
|
if (!useTerminal && !commandLine.isEmpty())
|
2023-03-16 10:30:46 +01:00
|
|
|
inner.addArg("exec");
|
|
|
|
|
|
|
|
if (!commandLine.isEmpty())
|
|
|
|
inner.addCommandLineAsArgs(commandLine, CommandLine::Raw);
|
|
|
|
|
|
|
|
cmd.addArg(inner.arguments());
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
return cmd;
|
|
|
|
}
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
// ShellThreadHandler
|
|
|
|
|
2022-05-12 18:43:44 +02:00
|
|
|
static SshParameters displayless(const SshParameters &sshParameters)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters parameters = sshParameters;
|
2022-02-14 18:37:56 +01:00
|
|
|
parameters.x11DisplayName.clear();
|
|
|
|
return parameters;
|
|
|
|
}
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
class ShellThreadHandler : public QObject
|
2012-04-06 18:28:16 +02:00
|
|
|
{
|
2022-05-17 10:07:27 +02:00
|
|
|
class LinuxDeviceShell : public DeviceShell
|
|
|
|
{
|
|
|
|
public:
|
2022-08-10 14:38:06 +02:00
|
|
|
LinuxDeviceShell(const CommandLine &cmdLine, const FilePath &devicePath)
|
2022-05-17 10:07:27 +02:00
|
|
|
: m_cmdLine(cmdLine)
|
2022-08-10 14:38:06 +02:00
|
|
|
, m_devicePath(devicePath)
|
2022-05-17 10:07:27 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-05-03 16:00:22 +02:00
|
|
|
void setupShellProcess(Process *shellProcess) override
|
2022-05-17 10:07:27 +02:00
|
|
|
{
|
|
|
|
SshParameters::setupSshEnvironment(shellProcess);
|
|
|
|
shellProcess->setCommand(m_cmdLine);
|
|
|
|
}
|
|
|
|
|
2022-08-10 14:38:06 +02:00
|
|
|
CommandLine createFallbackCommand(const CommandLine &cmdLine) override
|
|
|
|
{
|
|
|
|
CommandLine result = cmdLine;
|
2023-03-29 13:45:42 +02:00
|
|
|
result.setExecutable(m_devicePath.withNewMappedPath(cmdLine.executable())); // Needed?
|
2022-08-10 14:38:06 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-05-17 10:07:27 +02:00
|
|
|
private:
|
|
|
|
const CommandLine m_cmdLine;
|
2022-08-10 14:38:06 +02:00
|
|
|
const FilePath m_devicePath;
|
2022-05-17 10:07:27 +02:00
|
|
|
};
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
public:
|
|
|
|
~ShellThreadHandler()
|
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
closeShell();
|
2022-02-14 18:37:56 +01:00
|
|
|
qDeleteAll(m_connections);
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
void closeShell()
|
2022-01-13 12:56:24 +01:00
|
|
|
{
|
2022-02-14 18:37:56 +01:00
|
|
|
m_shell.reset();
|
2022-01-13 12:56:24 +01:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
// Call me with shell mutex locked
|
2022-05-12 18:43:44 +02:00
|
|
|
bool start(const SshParameters ¶meters)
|
2021-07-27 09:50:43 +02:00
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
closeShell();
|
|
|
|
setSshParameters(parameters);
|
2022-04-28 15:42:09 +02:00
|
|
|
|
|
|
|
const FilePath sshPath = SshSettings::sshFilePath();
|
|
|
|
CommandLine cmd { sshPath };
|
|
|
|
cmd.addArg("-q");
|
|
|
|
cmd.addArgs(m_displaylessSshParameters.connectionOptions(sshPath)
|
|
|
|
<< m_displaylessSshParameters.host());
|
|
|
|
cmd.addArg("/bin/sh");
|
|
|
|
|
2022-08-10 14:38:06 +02:00
|
|
|
m_shell.reset(new LinuxDeviceShell(cmd, FilePath::fromString(QString("ssh://%1/").arg(parameters.userAtHost()))));
|
2022-09-21 10:59:59 +02:00
|
|
|
connect(m_shell.get(), &DeviceShell::done, this, [this] {
|
|
|
|
m_shell.release()->deleteLater();
|
|
|
|
});
|
2022-05-17 10:07:27 +02:00
|
|
|
return m_shell->start();
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
// Call me with shell mutex locked
|
2022-10-07 16:54:27 +02:00
|
|
|
RunResult runInShell(const CommandLine &cmd, const QByteArray &data = {})
|
2021-07-27 09:50:43 +02:00
|
|
|
{
|
|
|
|
QTC_ASSERT(m_shell, return {});
|
2022-10-07 16:54:27 +02:00
|
|
|
return m_shell->runInShell(cmd, data);
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-05-12 18:43:44 +02:00
|
|
|
void setSshParameters(const SshParameters &sshParameters)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
2022-04-22 16:02:03 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters displaylessSshParameters = displayless(sshParameters);
|
2021-07-27 09:50:43 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
if (m_displaylessSshParameters == displaylessSshParameters)
|
|
|
|
return;
|
2021-07-27 09:50:43 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
// If displayless sshParameters don't match the old connections' sshParameters, then stale
|
|
|
|
// old connections (don't delete, as the last deref() to each one will delete them).
|
2022-10-07 14:46:06 +02:00
|
|
|
for (SshSharedConnection *connection : std::as_const(m_connections))
|
2022-02-14 18:37:56 +01:00
|
|
|
connection->makeStale();
|
|
|
|
m_connections.clear();
|
|
|
|
m_displaylessSshParameters = displaylessSshParameters;
|
|
|
|
}
|
2021-07-27 09:50:43 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
QString attachToSharedConnection(SshConnectionHandle *connectionHandle,
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters &sshParameters)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
|
|
|
setSshParameters(sshParameters);
|
2021-07-27 09:50:43 +02:00
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
SshSharedConnection *matchingConnection = nullptr;
|
|
|
|
|
|
|
|
// Find the matching connection
|
2022-10-07 14:46:06 +02:00
|
|
|
for (SshSharedConnection *connection : std::as_const(m_connections)) {
|
2022-02-14 18:37:56 +01:00
|
|
|
if (connection->sshParameters() == sshParameters) {
|
|
|
|
matchingConnection = connection;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no matching connection has been found, create a new one
|
|
|
|
if (!matchingConnection) {
|
|
|
|
matchingConnection = new SshSharedConnection(sshParameters);
|
|
|
|
connect(matchingConnection, &SshSharedConnection::autoDestructRequested,
|
|
|
|
this, [this, matchingConnection] {
|
|
|
|
// This slot is just for removing the matchingConnection from the connection list.
|
|
|
|
// The SshSharedConnection could have deleted itself otherwise.
|
|
|
|
m_connections.removeOne(matchingConnection);
|
|
|
|
matchingConnection->deleteLater();
|
|
|
|
});
|
|
|
|
m_connections.append(matchingConnection);
|
|
|
|
}
|
|
|
|
|
|
|
|
matchingConnection->ref();
|
|
|
|
|
|
|
|
connect(matchingConnection, &SshSharedConnection::connected,
|
|
|
|
connectionHandle, &SshConnectionHandle::connected);
|
|
|
|
connect(matchingConnection, &SshSharedConnection::disconnected,
|
|
|
|
connectionHandle, &SshConnectionHandle::disconnected);
|
|
|
|
|
|
|
|
connect(connectionHandle, &SshConnectionHandle::detachFromSharedConnection,
|
|
|
|
matchingConnection, &SshSharedConnection::deref,
|
|
|
|
Qt::BlockingQueuedConnection); // Ensure the signal is delivered before sender's
|
|
|
|
// destruction, otherwise we may get out of sync
|
|
|
|
// with ref count.
|
|
|
|
|
|
|
|
if (matchingConnection->state() == QProcess::Running)
|
|
|
|
return matchingConnection->socketFilePath();
|
|
|
|
|
|
|
|
if (matchingConnection->state() == QProcess::NotRunning)
|
|
|
|
matchingConnection->connectToHost();
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
// Call me with shell mutex locked, called from other thread
|
2022-05-12 18:43:44 +02:00
|
|
|
bool isRunning(const SshParameters &sshParameters) const
|
2022-04-22 16:02:03 +02:00
|
|
|
{
|
|
|
|
if (!m_shell)
|
|
|
|
return false;
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
if (m_displaylessSshParameters != displayless(sshParameters))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
2022-02-14 18:37:56 +01:00
|
|
|
private:
|
2022-04-22 16:02:03 +02:00
|
|
|
mutable QMutex m_mutex;
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters m_displaylessSshParameters;
|
2022-02-14 18:37:56 +01:00
|
|
|
QList<SshSharedConnection *> m_connections;
|
2022-05-17 10:07:27 +02:00
|
|
|
std::unique_ptr<LinuxDeviceShell> m_shell;
|
2021-07-27 09:50:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// LinuxDevice
|
2012-04-06 18:28:16 +02:00
|
|
|
|
2019-01-15 08:51:13 +01:00
|
|
|
LinuxDevice::LinuxDevice()
|
2023-09-07 12:44:04 +02:00
|
|
|
: IDevice(std::make_unique<LinuxDeviceSettings>())
|
|
|
|
, d(new LinuxDevicePrivate(this))
|
2019-01-11 14:50:08 +01:00
|
|
|
{
|
2022-10-10 17:32:56 +02:00
|
|
|
setFileAccess(&d->m_fileAccess);
|
2022-07-15 12:20:23 +02:00
|
|
|
setDisplayType(Tr::tr("Remote Linux"));
|
2019-08-16 10:17:45 +02:00
|
|
|
setOsType(OsTypeLinux);
|
|
|
|
|
2023-03-23 15:32:42 +01:00
|
|
|
setupId(IDevice::ManuallyAdded, Utils::Id());
|
|
|
|
setType(Constants::GenericLinuxOsType);
|
|
|
|
setMachineType(IDevice::Hardware);
|
|
|
|
setFreePorts(PortList::fromString(QLatin1String("10000-10100")));
|
|
|
|
SshParameters sshParams;
|
|
|
|
sshParams.timeout = 10;
|
|
|
|
setSshParameters(sshParams);
|
|
|
|
|
2022-07-15 12:20:23 +02:00
|
|
|
addDeviceAction({Tr::tr("Deploy Public Key..."), [](const IDevice::Ptr &device, QWidget *parent) {
|
2023-07-21 09:21:20 +02:00
|
|
|
if (auto d = Internal::PublicKeyDeploymentDialog::createDialog(device, parent)) {
|
2019-01-11 14:50:08 +01:00
|
|
|
d->exec();
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
}});
|
|
|
|
|
2021-08-02 18:02:10 +02:00
|
|
|
setOpenTerminal([this](const Environment &env, const FilePath &workingDir) {
|
2023-05-04 08:09:40 +02:00
|
|
|
Process proc;
|
2019-06-13 17:11:00 +02:00
|
|
|
|
2023-02-14 12:37:09 +01:00
|
|
|
// If we will not set any environment variables, we can leave out the shell executable
|
|
|
|
// as the "ssh ..." call will automatically launch the default shell if there are
|
|
|
|
// no arguments. But if we will set environment variables, we need to explicitly
|
|
|
|
// specify the shell executable.
|
2023-01-26 17:48:36 +01:00
|
|
|
const QString shell = env.hasChanges() ? env.value_or("SHELL", "/bin/sh") : QString();
|
2023-02-14 12:37:09 +01:00
|
|
|
|
2023-05-04 08:09:40 +02:00
|
|
|
proc.setCommand({filePath(shell), {}});
|
|
|
|
proc.setTerminalMode(TerminalMode::Detached);
|
|
|
|
proc.setEnvironment(env);
|
|
|
|
proc.setWorkingDirectory(workingDir);
|
|
|
|
proc.start();
|
2019-06-13 17:11:00 +02:00
|
|
|
});
|
|
|
|
|
2022-07-15 12:20:23 +02:00
|
|
|
addDeviceAction({Tr::tr("Open Remote Shell"), [](const IDevice::Ptr &device, QWidget *) {
|
2022-03-18 11:49:31 +01:00
|
|
|
device->openTerminal(Environment(), FilePath());
|
|
|
|
}});
|
2019-01-11 14:50:08 +01:00
|
|
|
}
|
|
|
|
|
2023-03-15 09:30:14 +01:00
|
|
|
void LinuxDevice::_setOsType(Utils::OsType osType)
|
|
|
|
{
|
|
|
|
qCDebug(linuxDeviceLog) << "Setting OS type to" << osType << "for" << displayName();
|
|
|
|
IDevice::setOsType(osType);
|
|
|
|
}
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
LinuxDevice::~LinuxDevice()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
IDeviceWidget *LinuxDevice::createWidget()
|
|
|
|
{
|
2022-07-14 17:02:19 +02:00
|
|
|
return new Internal::GenericLinuxDeviceConfigurationWidget(sharedFromThis());
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2013-06-27 17:12:08 +02:00
|
|
|
DeviceTester *LinuxDevice::createDeviceTester() const
|
2013-05-30 13:53:28 +02:00
|
|
|
{
|
|
|
|
return new GenericLinuxDeviceTester;
|
|
|
|
}
|
|
|
|
|
2013-09-16 15:30:30 +02:00
|
|
|
DeviceProcessSignalOperation::Ptr LinuxDevice::signalOperation() const
|
|
|
|
{
|
2022-05-03 16:30:25 +02:00
|
|
|
return DeviceProcessSignalOperation::Ptr(new RemoteLinuxSignalOperation(sharedFromThis()));
|
2013-09-16 15:30:30 +02:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:09:44 +02:00
|
|
|
bool LinuxDevice::usableAsBuildDevice() const
|
|
|
|
{
|
2022-10-13 16:36:48 +02:00
|
|
|
return true;
|
2022-10-04 08:09:44 +02:00
|
|
|
}
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
QString LinuxDevice::userAtHost() const
|
|
|
|
{
|
2022-02-03 13:20:15 +01:00
|
|
|
return sshParameters().userAtHost();
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-08-04 13:35:42 +02:00
|
|
|
FilePath LinuxDevice::rootPath() const
|
2022-05-31 11:16:44 +02:00
|
|
|
{
|
2022-08-04 15:00:24 +02:00
|
|
|
return FilePath::fromParts(u"ssh", userAtHost(), u"/");
|
2022-05-31 11:16:44 +02:00
|
|
|
}
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
bool LinuxDevice::handlesFile(const FilePath &filePath) const
|
|
|
|
{
|
2022-08-04 13:35:42 +02:00
|
|
|
if (filePath.scheme() == u"device" && filePath.host() == id().toString())
|
2022-03-25 10:35:17 +01:00
|
|
|
return true;
|
2022-08-04 13:35:42 +02:00
|
|
|
if (filePath.scheme() == u"ssh" && filePath.host() == userAtHost())
|
2022-03-25 10:35:17 +01:00
|
|
|
return true;
|
|
|
|
return false;
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
ProcessInterface *LinuxDevice::createProcessInterface() const
|
2021-07-27 09:50:43 +02:00
|
|
|
{
|
2023-03-16 10:30:46 +01:00
|
|
|
return new SshProcessInterface(sharedFromThis());
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent)
|
|
|
|
: q(parent)
|
|
|
|
{
|
2022-08-10 14:38:06 +02:00
|
|
|
m_shellThread.setObjectName("LinuxDeviceShell");
|
2021-07-27 09:50:43 +02:00
|
|
|
m_handler = new ShellThreadHandler();
|
|
|
|
m_handler->moveToThread(&m_shellThread);
|
|
|
|
QObject::connect(&m_shellThread, &QThread::finished, m_handler, &QObject::deleteLater);
|
|
|
|
m_shellThread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
LinuxDevicePrivate::~LinuxDevicePrivate()
|
|
|
|
{
|
2022-02-14 18:37:56 +01:00
|
|
|
auto closeShell = [this] {
|
|
|
|
m_shellThread.quit();
|
|
|
|
m_shellThread.wait();
|
|
|
|
};
|
|
|
|
if (QThread::currentThread() == m_shellThread.thread())
|
|
|
|
closeShell();
|
|
|
|
else // We might be in a non-main thread now due to extended lifetime of IDevice::Ptr
|
|
|
|
QMetaObject::invokeMethod(&m_shellThread, closeShell, Qt::BlockingQueuedConnection);
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2023-03-15 09:30:14 +01:00
|
|
|
void LinuxDevicePrivate::queryOsType(std::function<RunResult(const CommandLine &)> runInShell)
|
|
|
|
{
|
|
|
|
const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux});
|
|
|
|
if (result.exitCode != 0)
|
|
|
|
q->_setOsType(OsTypeOtherUnix);
|
|
|
|
const QString osName = QString::fromUtf8(result.stdOut).trimmed();
|
|
|
|
if (osName == "Darwin")
|
|
|
|
q->_setOsType(OsTypeMac);
|
|
|
|
if (osName == "Linux")
|
|
|
|
q->_setOsType(OsTypeLinux);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LinuxDevicePrivate::checkOsType()
|
|
|
|
{
|
|
|
|
queryOsType([this](const CommandLine &cmd) { return runInShell(cmd); });
|
|
|
|
}
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
// Call me with shell mutex locked
|
2021-07-27 09:50:43 +02:00
|
|
|
bool LinuxDevicePrivate::setupShell()
|
|
|
|
{
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters sshParameters = q->sshParameters();
|
2022-04-22 16:02:03 +02:00
|
|
|
if (m_handler->isRunning(sshParameters))
|
|
|
|
return true;
|
|
|
|
|
2023-01-31 09:38:37 +01:00
|
|
|
invalidateEnvironmentCache();
|
|
|
|
|
2021-07-27 09:50:43 +02:00
|
|
|
bool ok = false;
|
2022-04-22 16:02:03 +02:00
|
|
|
QMetaObject::invokeMethod(m_handler, [this, sshParameters] {
|
|
|
|
return m_handler->start(sshParameters);
|
2021-07-27 09:50:43 +02:00
|
|
|
}, Qt::BlockingQueuedConnection, &ok);
|
2023-03-15 09:30:14 +01:00
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
queryOsType([this](const CommandLine &cmd) { return m_handler->runInShell(cmd); });
|
|
|
|
}
|
2021-07-27 09:50:43 +02:00
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2022-10-07 16:54:27 +02:00
|
|
|
RunResult LinuxDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray &data)
|
2021-07-27 09:50:43 +02:00
|
|
|
{
|
|
|
|
QMutexLocker locker(&m_shellMutex);
|
|
|
|
DEBUG(cmd.toUserOutput());
|
2022-12-14 13:28:56 +01:00
|
|
|
const bool isSetup = setupShell();
|
|
|
|
QTC_ASSERT(isSetup, return {});
|
2022-10-07 16:54:27 +02:00
|
|
|
return m_handler->runInShell(cmd, data);
|
2021-07-27 09:50:43 +02:00
|
|
|
}
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
void LinuxDevicePrivate::attachToSharedConnection(SshConnectionHandle *connectionHandle,
|
2022-05-12 18:43:44 +02:00
|
|
|
const SshParameters &sshParameters)
|
2022-02-14 18:37:56 +01:00
|
|
|
{
|
|
|
|
QString socketFilePath;
|
2022-08-10 14:38:06 +02:00
|
|
|
|
|
|
|
Qt::ConnectionType connectionType = QThread::currentThread() == m_handler->thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection;
|
|
|
|
|
2022-04-22 16:02:03 +02:00
|
|
|
QMetaObject::invokeMethod(m_handler, [this, connectionHandle, sshParameters] {
|
|
|
|
return m_handler->attachToSharedConnection(connectionHandle, sshParameters);
|
2022-08-10 14:38:06 +02:00
|
|
|
}, connectionType, &socketFilePath);
|
|
|
|
|
2022-02-14 18:37:56 +01:00
|
|
|
if (!socketFilePath.isEmpty())
|
|
|
|
emit connectionHandle->connected(socketFilePath);
|
|
|
|
}
|
|
|
|
|
2022-05-10 10:36:34 +02:00
|
|
|
static FilePaths dirsToCreate(const FilesToTransfer &files)
|
|
|
|
{
|
|
|
|
FilePaths dirs;
|
|
|
|
for (const FileToTransfer &file : files) {
|
|
|
|
FilePath parentDir = file.m_target.parentDir();
|
|
|
|
while (true) {
|
|
|
|
if (dirs.contains(parentDir) || QDir(parentDir.path()).isRoot())
|
|
|
|
break;
|
|
|
|
dirs << parentDir;
|
|
|
|
parentDir = parentDir.parentDir();
|
|
|
|
}
|
|
|
|
}
|
2022-10-21 14:05:12 +02:00
|
|
|
return sorted(std::move(dirs));
|
2022-05-10 10:36:34 +02:00
|
|
|
}
|
|
|
|
|
2023-04-06 14:28:12 +02:00
|
|
|
static QByteArray transferCommand(bool link)
|
2022-05-10 10:36:34 +02:00
|
|
|
{
|
2023-04-06 14:28:12 +02:00
|
|
|
return link ? "ln -s" : "put";
|
2022-05-10 10:36:34 +02:00
|
|
|
}
|
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
class SshTransferInterface : public FileTransferInterface
|
2022-05-10 10:36:34 +02:00
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2022-05-11 19:23:43 +02:00
|
|
|
protected:
|
2023-03-16 10:30:46 +01:00
|
|
|
SshTransferInterface(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
|
2022-05-20 17:53:20 +02:00
|
|
|
: FileTransferInterface(setup)
|
2023-03-16 10:30:46 +01:00
|
|
|
, m_device(device)
|
2022-05-11 19:23:43 +02:00
|
|
|
, m_process(this)
|
|
|
|
{
|
2022-05-12 18:43:44 +02:00
|
|
|
SshParameters::setupSshEnvironment(&m_process);
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(&m_process, &Process::readyReadStandardOutput, this, [this] {
|
2023-01-05 17:55:04 +01:00
|
|
|
emit progress(QString::fromLocal8Bit(m_process.readAllRawStandardOutput()));
|
2022-05-11 19:23:43 +02:00
|
|
|
});
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(&m_process, &Process::done, this, &SshTransferInterface::doneImpl);
|
2022-05-11 19:23:43 +02:00
|
|
|
}
|
|
|
|
|
2023-04-06 13:20:55 +02:00
|
|
|
IDevice::ConstPtr device() const { return m_device; }
|
|
|
|
|
2022-05-11 19:23:43 +02:00
|
|
|
bool handleError()
|
|
|
|
{
|
|
|
|
ProcessResultData resultData = m_process.resultData();
|
|
|
|
if (resultData.m_error == QProcess::FailedToStart) {
|
2022-07-15 12:20:23 +02:00
|
|
|
resultData.m_errorString = Tr::tr("\"%1\" failed to start: %2")
|
2022-05-20 17:53:20 +02:00
|
|
|
.arg(FileTransfer::transferMethodName(m_setup.m_method), resultData.m_errorString);
|
2022-05-11 19:23:43 +02:00
|
|
|
} else if (resultData.m_exitStatus != QProcess::NormalExit) {
|
2022-07-15 12:20:23 +02:00
|
|
|
resultData.m_errorString = Tr::tr("\"%1\" crashed.")
|
2022-05-20 17:53:20 +02:00
|
|
|
.arg(FileTransfer::transferMethodName(m_setup.m_method));
|
2022-05-11 19:23:43 +02:00
|
|
|
} else if (resultData.m_exitCode != 0) {
|
2023-01-05 17:55:04 +01:00
|
|
|
resultData.m_errorString = QString::fromLocal8Bit(m_process.readAllRawStandardError());
|
2022-05-11 19:23:43 +02:00
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
2022-05-19 22:40:37 +02:00
|
|
|
emit done(resultData);
|
2022-05-11 19:23:43 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleDone()
|
|
|
|
{
|
|
|
|
if (!handleError())
|
|
|
|
emit done(m_process.resultData());
|
|
|
|
}
|
2022-05-10 10:36:34 +02:00
|
|
|
|
2022-05-17 00:04:48 +02:00
|
|
|
QStringList fullConnectionOptions() const
|
|
|
|
{
|
|
|
|
QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath());
|
|
|
|
if (!m_socketFilePath.isEmpty())
|
|
|
|
options << "-o" << ("ControlPath=" + m_socketFilePath);
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString host() const { return m_sshParameters.host(); }
|
2023-03-15 15:32:40 +01:00
|
|
|
QString userAtHost() const { return m_sshParameters.userAtHost(); }
|
2022-05-17 00:04:48 +02:00
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
Process &process() { return m_process; }
|
2022-05-11 19:23:43 +02:00
|
|
|
|
|
|
|
private:
|
|
|
|
virtual void startImpl() = 0;
|
|
|
|
virtual void doneImpl() = 0;
|
2022-05-17 00:04:48 +02:00
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
void start() final
|
|
|
|
{
|
|
|
|
m_sshParameters = displayless(m_device->sshParameters());
|
2023-03-16 15:39:05 +01:00
|
|
|
const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
|
|
|
|
const auto linkDevice = DeviceManager::instance()->find(linkDeviceId);
|
|
|
|
const bool useConnectionSharing = !linkDevice && SshSettings::connectionSharingEnabled();
|
|
|
|
|
|
|
|
if (useConnectionSharing) {
|
2022-05-20 17:53:20 +02:00
|
|
|
m_connecting = true;
|
|
|
|
m_connectionHandle.reset(new SshConnectionHandle(m_device));
|
|
|
|
m_connectionHandle->setParent(this);
|
|
|
|
connect(m_connectionHandle.get(), &SshConnectionHandle::connected,
|
|
|
|
this, &SshTransferInterface::handleConnected);
|
|
|
|
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
|
|
|
|
this, &SshTransferInterface::handleDisconnected);
|
2023-03-16 10:30:46 +01:00
|
|
|
auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
|
|
|
|
QTC_ASSERT(linuxDevice, startFailed("No Linux device"); return);
|
|
|
|
linuxDevice->connectionAccess()
|
|
|
|
->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
|
2022-05-20 17:53:20 +02:00
|
|
|
} else {
|
|
|
|
startImpl();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:04:48 +02:00
|
|
|
void handleConnected(const QString &socketFilePath)
|
|
|
|
{
|
|
|
|
m_connecting = false;
|
|
|
|
m_socketFilePath = socketFilePath;
|
|
|
|
startImpl();
|
|
|
|
}
|
|
|
|
|
|
|
|
void handleDisconnected(const ProcessResultData &result)
|
|
|
|
{
|
|
|
|
ProcessResultData resultData = result;
|
|
|
|
if (m_connecting)
|
|
|
|
resultData.m_error = QProcess::FailedToStart;
|
|
|
|
|
|
|
|
m_connecting = false;
|
|
|
|
if (m_connectionHandle) // TODO: should it disconnect from signals first?
|
|
|
|
m_connectionHandle.release()->deleteLater();
|
|
|
|
|
|
|
|
if (resultData.m_error != QProcess::UnknownError || m_process.state() != QProcess::NotRunning)
|
|
|
|
emit done(resultData); // TODO: don't emit done() on process finished afterwards
|
|
|
|
}
|
|
|
|
|
|
|
|
IDevice::ConstPtr m_device;
|
2022-05-20 17:53:20 +02:00
|
|
|
SshParameters m_sshParameters;
|
|
|
|
|
|
|
|
// ssh shared connection related
|
2022-05-17 00:04:48 +02:00
|
|
|
std::unique_ptr<SshConnectionHandle> m_connectionHandle;
|
|
|
|
QString m_socketFilePath;
|
|
|
|
bool m_connecting = false;
|
2022-05-20 17:53:20 +02:00
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
Process m_process;
|
2022-05-10 10:36:34 +02:00
|
|
|
};
|
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
class SftpTransferImpl : public SshTransferInterface
|
2022-05-10 10:36:34 +02:00
|
|
|
{
|
2022-05-11 19:23:43 +02:00
|
|
|
public:
|
2023-03-16 10:30:46 +01:00
|
|
|
SftpTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
|
|
|
|
: SshTransferInterface(setup, device)
|
|
|
|
{}
|
2022-05-10 10:36:34 +02:00
|
|
|
|
2022-05-11 19:23:43 +02:00
|
|
|
private:
|
2022-05-17 00:04:48 +02:00
|
|
|
void startImpl() final
|
2022-05-11 19:23:43 +02:00
|
|
|
{
|
2023-04-06 13:20:55 +02:00
|
|
|
FilePath sftpBinary = SshSettings::sftpFilePath();
|
|
|
|
|
|
|
|
// This is a hack. We only test the last hop here.
|
|
|
|
const Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
|
|
|
|
if (const auto linkDevice = DeviceManager::instance()->find(linkDeviceId))
|
|
|
|
sftpBinary = linkDevice->filePath(sftpBinary.fileName()).searchInPath();
|
|
|
|
|
2022-05-11 19:23:43 +02:00
|
|
|
if (!sftpBinary.exists()) {
|
2022-07-15 12:20:23 +02:00
|
|
|
startFailed(Tr::tr("\"sftp\" binary \"%1\" does not exist.")
|
2022-06-24 13:19:04 +02:00
|
|
|
.arg(sftpBinary.toUserOutput()));
|
2022-05-11 19:23:43 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-09-14 10:01:41 +02:00
|
|
|
QByteArray batchData;
|
2022-05-11 19:23:43 +02:00
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
const FilePaths dirs = dirsToCreate(m_setup.m_files);
|
2023-08-22 10:56:05 +02:00
|
|
|
for (const FilePath &dir : dirs) {
|
|
|
|
if (!dir.exists())
|
|
|
|
batchData += "-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n';
|
|
|
|
}
|
2022-05-11 19:23:43 +02:00
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
for (const FileToTransfer &file : m_setup.m_files) {
|
2022-05-11 19:23:43 +02:00
|
|
|
FilePath sourceFileOrLinkTarget = file.m_source;
|
|
|
|
bool link = false;
|
2023-04-06 14:28:12 +02:00
|
|
|
|
|
|
|
const QFileInfo fi(file.m_source.toFileInfo());
|
|
|
|
if (fi.isSymLink()) {
|
|
|
|
link = true;
|
|
|
|
batchData += "-rm " + ProcessArgs::quoteArgUnix(
|
|
|
|
file.m_target.path()).toLocal8Bit() + '\n';
|
|
|
|
// see QTBUG-5817.
|
|
|
|
sourceFileOrLinkTarget =
|
|
|
|
sourceFileOrLinkTarget.withNewPath(fi.dir().relativeFilePath(fi.symLinkTarget()));
|
|
|
|
}
|
|
|
|
|
|
|
|
batchData += transferCommand(link) + ' '
|
|
|
|
+ ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' '
|
|
|
|
+ ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n';
|
2022-05-11 19:23:43 +02:00
|
|
|
}
|
2022-09-14 10:01:41 +02:00
|
|
|
process().setCommand({sftpBinary, fullConnectionOptions() << "-b" << "-" << host()});
|
|
|
|
process().setWriteData(batchData);
|
2022-05-17 00:04:48 +02:00
|
|
|
process().start();
|
2022-05-11 19:23:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void doneImpl() final { handleDone(); }
|
|
|
|
};
|
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
class RsyncTransferImpl : public SshTransferInterface
|
2022-05-10 10:36:34 +02:00
|
|
|
{
|
2022-05-11 19:23:43 +02:00
|
|
|
public:
|
2023-03-16 10:30:46 +01:00
|
|
|
RsyncTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
|
|
|
|
: SshTransferInterface(setup, device)
|
2022-05-11 19:23:43 +02:00
|
|
|
{ }
|
2022-05-10 10:36:34 +02:00
|
|
|
|
2022-05-11 19:23:43 +02:00
|
|
|
private:
|
2022-05-17 00:04:48 +02:00
|
|
|
void startImpl() final
|
2022-05-12 12:32:55 +02:00
|
|
|
{
|
|
|
|
m_currentIndex = 0;
|
|
|
|
startNextFile();
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:04:48 +02:00
|
|
|
void doneImpl() final
|
2022-05-12 12:32:55 +02:00
|
|
|
{
|
2022-05-20 17:53:20 +02:00
|
|
|
if (m_setup.m_files.size() == 0 || m_currentIndex == m_setup.m_files.size() - 1)
|
2022-05-12 12:32:55 +02:00
|
|
|
return handleDone();
|
|
|
|
|
|
|
|
if (handleError())
|
|
|
|
return;
|
|
|
|
|
|
|
|
++m_currentIndex;
|
|
|
|
startNextFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
void startNextFile()
|
|
|
|
{
|
2022-05-17 00:04:48 +02:00
|
|
|
process().close();
|
2022-05-12 12:32:55 +02:00
|
|
|
|
|
|
|
const QString sshCmdLine = ProcessArgs::joinArgs(
|
2022-05-17 00:04:48 +02:00
|
|
|
QStringList{SshSettings::sshFilePath().toUserOutput()}
|
|
|
|
<< fullConnectionOptions(), OsTypeLinux);
|
2022-05-20 17:53:20 +02:00
|
|
|
QStringList options{"-e", sshCmdLine, m_setup.m_rsyncFlags};
|
2022-05-12 12:32:55 +02:00
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
if (!m_setup.m_files.isEmpty()) { // NormalRun
|
|
|
|
const FileToTransfer file = m_setup.m_files.at(m_currentIndex);
|
2022-05-16 17:24:36 +02:00
|
|
|
const FileToTransfer fixedFile = fixLocalFileOnWindows(file, options);
|
2022-05-17 00:04:48 +02:00
|
|
|
const auto fixedPaths = fixPaths(fixedFile, userAtHost());
|
2022-05-12 12:32:55 +02:00
|
|
|
|
2022-05-17 00:04:48 +02:00
|
|
|
options << fixedPaths.first << fixedPaths.second;
|
2022-05-16 17:24:36 +02:00
|
|
|
} else { // TestRun
|
2022-05-17 00:04:48 +02:00
|
|
|
options << "-n" << "--exclude=*" << (userAtHost() + ":/tmp");
|
2022-05-16 17:24:36 +02:00
|
|
|
}
|
2022-05-12 12:32:55 +02:00
|
|
|
// TODO: Get rsync location from settings?
|
2022-05-17 00:04:48 +02:00
|
|
|
process().setCommand(CommandLine("rsync", options));
|
|
|
|
process().start();
|
2022-05-12 12:32:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// On Windows, rsync is either from msys or cygwin. Neither work with the other's ssh.exe.
|
2022-05-19 22:40:37 +02:00
|
|
|
FileToTransfer fixLocalFileOnWindows(const FileToTransfer &file, const QStringList &options) const
|
2022-05-12 12:32:55 +02:00
|
|
|
{
|
|
|
|
if (!HostOsInfo::isWindowsHost())
|
|
|
|
return file;
|
|
|
|
|
2023-04-06 14:28:12 +02:00
|
|
|
QString localFilePath = file.m_source.path();
|
2022-05-12 12:32:55 +02:00
|
|
|
localFilePath = '/' + localFilePath.at(0) + localFilePath.mid(2);
|
|
|
|
if (anyOf(options, [](const QString &opt) {
|
|
|
|
return opt.contains("cygwin", Qt::CaseInsensitive); })) {
|
|
|
|
localFilePath.prepend("/cygdrive");
|
|
|
|
}
|
|
|
|
|
|
|
|
FileToTransfer fixedFile = file;
|
2023-04-06 14:28:12 +02:00
|
|
|
fixedFile.m_source = fixedFile.m_source.withNewPath(localFilePath);
|
2022-05-12 12:32:55 +02:00
|
|
|
return fixedFile;
|
|
|
|
}
|
|
|
|
|
2022-05-19 22:40:37 +02:00
|
|
|
QPair<QString, QString> fixPaths(const FileToTransfer &file, const QString &remoteHost) const
|
2022-05-12 12:32:55 +02:00
|
|
|
{
|
2023-04-06 14:28:12 +02:00
|
|
|
FilePath localPath = file.m_source;
|
|
|
|
FilePath remotePath = file.m_target;
|
2022-05-12 12:32:55 +02:00
|
|
|
const QString local = (localPath.isDir() && localPath.path().back() != '/')
|
|
|
|
? localPath.path() + '/' : localPath.path();
|
|
|
|
const QString remote = remoteHost + ':' + remotePath.path();
|
|
|
|
|
2023-04-06 14:28:12 +02:00
|
|
|
return qMakePair(local, remote);
|
2022-05-12 12:32:55 +02:00
|
|
|
}
|
2022-05-11 19:23:43 +02:00
|
|
|
|
2022-05-12 12:32:55 +02:00
|
|
|
int m_currentIndex = 0;
|
2022-05-11 19:23:43 +02:00
|
|
|
};
|
|
|
|
|
2022-10-14 10:06:21 +02:00
|
|
|
class GenericTransferImpl : public FileTransferInterface
|
|
|
|
{
|
|
|
|
public:
|
2023-03-16 10:30:46 +01:00
|
|
|
GenericTransferImpl(const FileTransferSetupData &setup)
|
2022-10-14 10:06:21 +02:00
|
|
|
: FileTransferInterface(setup)
|
|
|
|
{}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void start() final
|
|
|
|
{
|
|
|
|
m_fileCount = m_setup.m_files.size();
|
|
|
|
m_currentIndex = 0;
|
|
|
|
m_checkedDirectories.clear();
|
|
|
|
nextFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
void nextFile()
|
|
|
|
{
|
|
|
|
ProcessResultData result;
|
|
|
|
if (m_currentIndex >= m_fileCount) {
|
|
|
|
emit done(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const FileToTransfer &file = m_setup.m_files.at(m_currentIndex);
|
|
|
|
const FilePath &source = file.m_source;
|
|
|
|
const FilePath &target = file.m_target;
|
|
|
|
++m_currentIndex;
|
|
|
|
|
|
|
|
const FilePath targetDir = target.parentDir();
|
|
|
|
if (!m_checkedDirectories.contains(targetDir)) {
|
2022-10-31 14:50:30 +01:00
|
|
|
emit progress(Tr::tr("Creating directory: %1\n").arg(targetDir.toUserOutput()));
|
2022-10-14 10:06:21 +02:00
|
|
|
if (!targetDir.ensureWritableDir()) {
|
2022-10-31 14:50:30 +01:00
|
|
|
result.m_errorString = Tr::tr("Failed.");
|
2022-10-14 10:06:21 +02:00
|
|
|
result.m_exitCode = -1; // Random pick
|
|
|
|
emit done(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_checkedDirectories.insert(targetDir);
|
|
|
|
}
|
|
|
|
|
2022-10-31 14:50:30 +01:00
|
|
|
emit progress(Tr::tr("Copying %1/%2: %3 -> %4\n")
|
|
|
|
.arg(m_currentIndex)
|
|
|
|
.arg(m_fileCount)
|
|
|
|
.arg(source.toUserOutput(), target.toUserOutput()));
|
2023-03-28 13:54:27 +02:00
|
|
|
expected_str<void> copyResult = source.copyFile(target);
|
|
|
|
if (!copyResult) {
|
|
|
|
result.m_errorString = Tr::tr("Failed: %1").arg(copyResult.error());
|
2022-10-14 10:06:21 +02:00
|
|
|
result.m_exitCode = -1; // Random pick
|
|
|
|
emit done(result);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Use asyncCopyFile instead
|
|
|
|
nextFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
int m_currentIndex = 0;
|
|
|
|
int m_fileCount = 0;
|
|
|
|
QSet<FilePath> m_checkedDirectories;
|
|
|
|
};
|
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
FileTransferInterface *LinuxDevice::createFileTransferInterface(
|
|
|
|
const FileTransferSetupData &setup) const
|
2022-05-11 19:23:43 +02:00
|
|
|
{
|
2023-08-22 10:57:33 +02:00
|
|
|
if (Utils::anyOf(setup.m_files,
|
|
|
|
[](const FileToTransfer &f) { return f.m_source.needsDevice(); })) {
|
|
|
|
return new GenericTransferImpl(setup);
|
|
|
|
}
|
|
|
|
|
2022-05-20 17:53:20 +02:00
|
|
|
switch (setup.m_method) {
|
2023-03-16 10:30:46 +01:00
|
|
|
case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, sharedFromThis());
|
|
|
|
case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, sharedFromThis());
|
|
|
|
case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup);
|
2022-05-16 20:07:57 +02:00
|
|
|
}
|
|
|
|
QTC_CHECK(false);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-03-16 10:30:46 +01:00
|
|
|
LinuxDevicePrivate *LinuxDevice::connectionAccess() const
|
|
|
|
{
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
2023-03-15 09:30:14 +01:00
|
|
|
void LinuxDevice::checkOsType()
|
|
|
|
{
|
|
|
|
d->checkOsType();
|
|
|
|
}
|
|
|
|
|
2019-02-20 19:13:28 +01:00
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
LinuxDeviceFactory::LinuxDeviceFactory()
|
|
|
|
: IDeviceFactory(Constants::GenericLinuxOsType)
|
|
|
|
{
|
2022-07-15 12:20:23 +02:00
|
|
|
setDisplayName(Tr::tr("Remote Linux Device"));
|
2019-02-20 19:13:28 +01:00
|
|
|
setIcon(QIcon());
|
|
|
|
setConstructionFunction(&LinuxDevice::create);
|
2023-03-23 15:32:42 +01:00
|
|
|
setQuickCreationAllowed(true);
|
2023-07-21 00:21:07 +02:00
|
|
|
setCreator([]() -> IDevice::Ptr {
|
|
|
|
const IDevice::Ptr device = LinuxDevice::create();
|
2023-07-21 01:22:39 +02:00
|
|
|
SshDeviceWizard wizard(Tr::tr("New Remote Linux Device Configuration Setup"), device);
|
2022-01-26 09:05:35 +01:00
|
|
|
if (wizard.exec() != QDialog::Accepted)
|
2023-07-21 00:21:07 +02:00
|
|
|
return {};
|
|
|
|
return device;
|
2022-01-26 09:05:35 +01:00
|
|
|
});
|
2019-02-20 19:13:28 +01:00
|
|
|
}
|
|
|
|
|
2022-01-26 09:05:35 +01:00
|
|
|
} // namespace Internal
|
2011-05-31 12:47:53 +02:00
|
|
|
} // namespace RemoteLinux
|
2022-02-14 18:37:56 +01:00
|
|
|
|
|
|
|
#include "linuxdevice.moc"
|