RemoteLinux: Add some support for "doubly remote" devices

This adds an "Access via:" settings to RL based devices that allow the user
to select an intermediate (also RL based) device as "stepping stone".

Communication to these devices is done by running ssh from the
intermediate device, currently without connection sharing.

Currently kind-of-functional: "Show running processes"

Change-Id: I6964fb4005ab8f42551c877da2c0bdb1e825cd61
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
hjk
2023-03-16 15:39:05 +01:00
parent 5f65dc84f3
commit d8e87413a3
4 changed files with 133 additions and 21 deletions

View File

@@ -7,6 +7,7 @@
#include "remotelinuxtr.h" #include "remotelinuxtr.h"
#include "sshkeycreationdialog.h" #include "sshkeycreationdialog.h"
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/idevice.h> #include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/sshparameters.h> #include <projectexplorer/devicesupport/sshparameters.h>
@@ -17,6 +18,7 @@
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QPushButton> #include <QPushButton>
@@ -84,6 +86,16 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_sourceProfileCheckBox = m_sourceProfileCheckBox =
new QCheckBox(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile")); new QCheckBox(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile"));
m_linkDeviceComboBox = new QComboBox;
m_linkDeviceComboBox->addItem(Tr::tr("Direct"), QVariant());
auto dm = DeviceManager::instance();
const int dmCount = dm->deviceCount();
for (int i = 0; i < dmCount; ++i) {
IDevice::ConstPtr dev = dm->deviceAt(i);
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
}
auto sshPortLabel = new QLabel(Tr::tr("&SSH port:")); auto sshPortLabel = new QLabel(Tr::tr("&SSH port:"));
sshPortLabel->setBuddy(m_sshPortSpinBox); sshPortLabel->setBuddy(m_sshPortSpinBox);
@@ -98,7 +110,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_keyLabel, m_keyFileLineEdit, createKeyButton, br, m_keyLabel, m_keyFileLineEdit, createKeyButton, br,
Tr::tr("GDB server executable:"), m_gdbServerLineEdit, br, Tr::tr("GDB server executable:"), m_gdbServerLineEdit, br,
Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br, Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br,
QString(), m_sourceProfileCheckBox, br QString(), m_sourceProfileCheckBox, br,
Tr::tr("Access via:"), m_linkDeviceComboBox
}.attachTo(this); }.attachTo(this);
connect(m_hostLineEdit, &QLineEdit::editingFinished, connect(m_hostLineEdit, &QLineEdit::editingFinished,
@@ -131,6 +144,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged); this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged);
connect(m_sourceProfileCheckBox, &QCheckBox::toggled, connect(m_sourceProfileCheckBox, &QCheckBox::toggled,
this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged); this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged);
connect(m_linkDeviceComboBox, &QComboBox::currentIndexChanged,
this, &GenericLinuxDeviceConfigurationWidget::linkDeviceChanged);
initGui(); initGui();
} }
@@ -226,6 +241,12 @@ void GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged(bool do
device()->setExtraData(Constants::SourceProfile, doCheck); device()->setExtraData(Constants::SourceProfile, doCheck);
} }
void GenericLinuxDeviceConfigurationWidget::linkDeviceChanged(int index)
{
const QVariant deviceId = m_linkDeviceComboBox->itemData(index);
device()->setExtraData(Constants::LinkDevice, deviceId);
}
void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi() void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi()
{ {
hostNameEditingFinished(); hostNameEditingFinished();
@@ -235,6 +256,10 @@ void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi()
keyFileEditingFinished(); keyFileEditingFinished();
handleFreePortsChanged(); handleFreePortsChanged();
gdbServerEditingFinished(); gdbServerEditingFinished();
sshPortEditingFinished();
timeoutEditingFinished();
sourceProfileCheckingChanged(m_sourceProfileCheckBox->isChecked());
linkDeviceChanged(m_linkDeviceComboBox->currentIndex());
} }
void GenericLinuxDeviceConfigurationWidget::updatePortsWarningLabel() void GenericLinuxDeviceConfigurationWidget::updatePortsWarningLabel()
@@ -273,6 +298,16 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
m_sshPortSpinBox->setEnabled(!device()->isAutoDetected()); m_sshPortSpinBox->setEnabled(!device()->isAutoDetected());
m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode != SshHostKeyCheckingNone); m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode != SshHostKeyCheckingNone);
m_sourceProfileCheckBox->setChecked(device()->extraData(Constants::SourceProfile).toBool()); m_sourceProfileCheckBox->setChecked(device()->extraData(Constants::SourceProfile).toBool());
Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
auto dm = DeviceManager::instance();
int found = -1;
for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
if (dm->deviceAt(i)->id() == linkDeviceId) {
found = i;
break;
}
}
m_linkDeviceComboBox->setCurrentIndex(found + 1); // There's the "Direct" entry first.
m_hostLineEdit->setText(sshParams.host()); m_hostLineEdit->setText(sshParams.host());
m_sshPortSpinBox->setValue(sshParams.port()); m_sshPortSpinBox->setValue(sshParams.port());

View File

@@ -7,6 +7,7 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QCheckBox; class QCheckBox;
class QComboBox;
class QLabel; class QLabel;
class QLineEdit; class QLineEdit;
class QRadioButton; class QRadioButton;
@@ -43,6 +44,7 @@ private:
void createNewKey(); void createNewKey();
void hostKeyCheckingChanged(bool doCheck); void hostKeyCheckingChanged(bool doCheck);
void sourceProfileCheckingChanged(bool doCheck); void sourceProfileCheckingChanged(bool doCheck);
void linkDeviceChanged(int index);
void updateDeviceFromUi() override; void updateDeviceFromUi() override;
void updatePortsWarningLabel(); void updatePortsWarningLabel();
@@ -63,6 +65,7 @@ private:
Utils::PathChooser *m_gdbServerLineEdit; Utils::PathChooser *m_gdbServerLineEdit;
Utils::PathChooser *m_qmlRuntimeLineEdit; Utils::PathChooser *m_qmlRuntimeLineEdit;
QCheckBox *m_sourceProfileCheckBox; QCheckBox *m_sourceProfileCheckBox;
QComboBox *m_linkDeviceComboBox;
}; };
} // RemoteLinux::Internal } // RemoteLinux::Internal

View File

@@ -15,6 +15,7 @@
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h> #include <coreplugin/messagemanager.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/filetransfer.h> #include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/filetransferinterface.h> #include <projectexplorer/devicesupport/filetransferinterface.h>
#include <projectexplorer/devicesupport/processlist.h> #include <projectexplorer/devicesupport/processlist.h>
@@ -389,6 +390,8 @@ public:
QString m_socketFilePath; QString m_socketFilePath;
SshParameters m_sshParameters; SshParameters m_sshParameters;
IDevice::ConstPtr m_linkDevice;
bool m_connecting = false; bool m_connecting = false;
bool m_killed = false; bool m_killed = false;
@@ -397,6 +400,7 @@ public:
QByteArray m_output; QByteArray m_output;
QByteArray m_error; QByteArray m_error;
bool m_pidParsed = false; bool m_pidParsed = false;
bool m_useConnectionSharing = false;
}; };
SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device) SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device)
@@ -591,11 +595,15 @@ void SshProcessInterfacePrivate::start()
{ {
clearForStart(); clearForStart();
const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
m_linkDevice = DeviceManager::instance()->find(linkDeviceId);
m_useConnectionSharing = !m_linkDevice && SshSettings::connectionSharingEnabled();
m_sshParameters = m_device->sshParameters(); m_sshParameters = m_device->sshParameters();
// TODO: Do we really need it for master process? // TODO: Do we really need it for master process?
m_sshParameters.x11DisplayName m_sshParameters.x11DisplayName
= q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString(); = q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString();
if (SshSettings::connectionSharingEnabled()) { if (m_useConnectionSharing) {
m_connecting = true; m_connecting = true;
m_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this); m_connectionHandle->setParent(this);
@@ -662,30 +670,35 @@ void SshProcessInterfacePrivate::doStart()
m_process.start(); m_process.start();
} }
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const static CommandLine getCommandLine(
const FilePath sshBinary,
const CommandLine commandLine0,
const FilePath &workingDirectory,
const Environment &env,
const QStringList &options,
bool useX,
bool useTerminal,
bool usePidMarker,
bool sourceProfile)
{ {
CommandLine cmd{SshSettings::sshFilePath()}; CommandLine cmd{sshBinary};
if (!m_sshParameters.x11DisplayName.isEmpty()) if (useX)
cmd.addArg("-X"); cmd.addArg("-X");
if (q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData) if (useTerminal)
cmd.addArg("-tt"); cmd.addArg("-tt");
cmd.addArg("-q"); cmd.addArg("-q");
QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath());
if (!m_socketFilePath.isEmpty())
options << "-o" << ("ControlPath=" + m_socketFilePath);
options << m_sshParameters.host();
cmd.addArgs(options); cmd.addArgs(options);
CommandLine commandLine = q->m_setup.m_commandLine; CommandLine commandLine = commandLine0;
FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path()); FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path());
commandLine.setExecutable(executable); commandLine.setExecutable(executable);
CommandLine inner; CommandLine inner;
if (!commandLine.isEmpty() && m_device->extraData(Constants::SourceProfile).toBool()) { if (!commandLine.isEmpty() && sourceProfile) {
const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"}; const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
for (const QString &filePath : rcFilesToSource) { for (const QString &filePath : rcFilesToSource) {
inner.addArgs({"test", "-f", filePath}); inner.addArgs({"test", "-f", filePath});
@@ -695,31 +708,87 @@ CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
} }
} }
if (!q->m_setup.m_workingDirectory.isEmpty()) { if (!workingDirectory.isEmpty()) {
inner.addArgs({"cd", q->m_setup.m_workingDirectory.path()}); inner.addArgs({"cd", workingDirectory.path()});
inner.addArgs("&&", CommandLine::Raw); inner.addArgs("&&", CommandLine::Raw);
} }
if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData) if (usePidMarker)
inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw); inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
const Environment &env = q->m_setup.m_environment;
env.forEachEntry([&](const QString &key, const QString &value, bool) { env.forEachEntry([&](const QString &key, const QString &value, bool) {
inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw); inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw);
}); });
if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData) if (!useTerminal && !commandLine.isEmpty())
inner.addArg("exec"); inner.addArg("exec");
if (!commandLine.isEmpty()) if (!commandLine.isEmpty())
inner.addCommandLineAsArgs(commandLine, CommandLine::Raw); inner.addCommandLineAsArgs(commandLine, CommandLine::Raw);
cmd.addArg(inner.arguments()); cmd.addArg(inner.arguments());
return cmd; return cmd;
} }
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
{
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();
CommandLine cmd;
if (m_linkDevice) {
QStringList farOptions = m_sshParameters.connectionOptions("ssh");
farOptions << m_sshParameters.host();
const SshParameters nearParameters = m_linkDevice->sshParameters();
QStringList nearOptions = nearParameters.connectionOptions(sshBinary);
// if (!m_socketFilePath.isEmpty())
// options << "-o" << ("ControlPath=" + m_socketFilePath);
nearOptions << nearParameters.host();
cmd = getCommandLine("ssh",
q->m_setup.m_commandLine,
{},
{},
farOptions,
false,
false,
false,
false);
cmd = getCommandLine(sshBinary,
cmd,
{},
{},
nearOptions,
false,
false,
usePidMarker,
false);
} else {
QStringList options = m_sshParameters.connectionOptions(sshBinary);
if (!m_socketFilePath.isEmpty())
options << "-o" << ("ControlPath=" + m_socketFilePath);
options << m_sshParameters.host();
cmd = getCommandLine(sshBinary,
q->m_setup.m_commandLine,
q->m_setup.m_workingDirectory,
q->m_setup.m_environment,
options,
useX,
useTerminal,
usePidMarker,
sourceProfile);
}
return cmd;
}
// ShellThreadHandler // ShellThreadHandler
static SshParameters displayless(const SshParameters &sshParameters) static SshParameters displayless(const SshParameters &sshParameters)
@@ -1203,7 +1272,11 @@ private:
void start() final void start() final
{ {
m_sshParameters = displayless(m_device->sshParameters()); m_sshParameters = displayless(m_device->sshParameters());
if (SshSettings::connectionSharingEnabled()) { 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) {
m_connecting = true; m_connecting = true;
m_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this); m_connectionHandle->setParent(this);

View File

@@ -21,6 +21,7 @@ const char KillAppStepId[] = "RemoteLinux.KillAppStep";
const char SupportsRSync[] = "RemoteLinux.SupportsRSync"; const char SupportsRSync[] = "RemoteLinux.SupportsRSync";
const char SourceProfile[] = "RemoteLinux.SourceProfile"; const char SourceProfile[] = "RemoteLinux.SourceProfile";
const char LinkDevice[] = "RemoteLinux.LinkDevice";
const char RunConfigId[] = "RemoteLinuxRunConfiguration:"; const char RunConfigId[] = "RemoteLinuxRunConfiguration:";
const char CustomRunConfigId[] = "RemoteLinux.CustomRunConfig"; const char CustomRunConfigId[] = "RemoteLinux.CustomRunConfig";