From d8e87413a34ba7036bf9b677adb825970ca0e717 Mon Sep 17 00:00:00 2001 From: hjk Date: Thu, 16 Mar 2023 15:39:05 +0100 Subject: [PATCH] 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 --- .../genericlinuxdeviceconfigurationwidget.cpp | 37 +++++- .../genericlinuxdeviceconfigurationwidget.h | 3 + src/plugins/remotelinux/linuxdevice.cpp | 109 +++++++++++++++--- .../remotelinux/remotelinux_constants.h | 5 +- 4 files changed, 133 insertions(+), 21 deletions(-) diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp index fb4ef53579f..b7890bb610b 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp @@ -7,6 +7,7 @@ #include "remotelinuxtr.h" #include "sshkeycreationdialog.h" +#include #include #include @@ -17,6 +18,7 @@ #include #include +#include #include #include #include @@ -84,6 +86,16 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( m_sourceProfileCheckBox = 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:")); sshPortLabel->setBuddy(m_sshPortSpinBox); @@ -98,7 +110,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( m_keyLabel, m_keyFileLineEdit, createKeyButton, br, Tr::tr("GDB server executable:"), m_gdbServerLineEdit, 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); connect(m_hostLineEdit, &QLineEdit::editingFinished, @@ -131,6 +144,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget( this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged); connect(m_sourceProfileCheckBox, &QCheckBox::toggled, this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged); + connect(m_linkDeviceComboBox, &QComboBox::currentIndexChanged, + this, &GenericLinuxDeviceConfigurationWidget::linkDeviceChanged); initGui(); } @@ -226,6 +241,12 @@ void GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged(bool do 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() { hostNameEditingFinished(); @@ -235,6 +256,10 @@ void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi() keyFileEditingFinished(); handleFreePortsChanged(); gdbServerEditingFinished(); + sshPortEditingFinished(); + timeoutEditingFinished(); + sourceProfileCheckingChanged(m_sourceProfileCheckBox->isChecked()); + linkDeviceChanged(m_linkDeviceComboBox->currentIndex()); } void GenericLinuxDeviceConfigurationWidget::updatePortsWarningLabel() @@ -273,6 +298,16 @@ void GenericLinuxDeviceConfigurationWidget::initGui() m_sshPortSpinBox->setEnabled(!device()->isAutoDetected()); m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode != SshHostKeyCheckingNone); 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_sshPortSpinBox->setValue(sshParams.port()); diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h index 024656d1fa0..e9840c9a436 100644 --- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h +++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h @@ -7,6 +7,7 @@ QT_BEGIN_NAMESPACE class QCheckBox; +class QComboBox; class QLabel; class QLineEdit; class QRadioButton; @@ -43,6 +44,7 @@ private: void createNewKey(); void hostKeyCheckingChanged(bool doCheck); void sourceProfileCheckingChanged(bool doCheck); + void linkDeviceChanged(int index); void updateDeviceFromUi() override; void updatePortsWarningLabel(); @@ -63,6 +65,7 @@ private: Utils::PathChooser *m_gdbServerLineEdit; Utils::PathChooser *m_qmlRuntimeLineEdit; QCheckBox *m_sourceProfileCheckBox; + QComboBox *m_linkDeviceComboBox; }; } // RemoteLinux::Internal diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index da7b7aa2d18..4ef2c7340a1 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -389,6 +390,8 @@ public: QString m_socketFilePath; SshParameters m_sshParameters; + IDevice::ConstPtr m_linkDevice; + bool m_connecting = false; bool m_killed = false; @@ -397,6 +400,7 @@ public: QByteArray m_output; QByteArray m_error; bool m_pidParsed = false; + bool m_useConnectionSharing = false; }; SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device) @@ -591,11 +595,15 @@ void SshProcessInterfacePrivate::start() { 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(); // TODO: Do we really need it for master process? m_sshParameters.x11DisplayName = q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString(); - if (SshSettings::connectionSharingEnabled()) { + if (m_useConnectionSharing) { m_connecting = true; m_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle->setParent(this); @@ -662,30 +670,35 @@ void SshProcessInterfacePrivate::doStart() 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"); - if (q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData) + if (useTerminal) cmd.addArg("-tt"); 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); - CommandLine commandLine = q->m_setup.m_commandLine; + CommandLine commandLine = commandLine0; FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path()); commandLine.setExecutable(executable); CommandLine inner; - if (!commandLine.isEmpty() && m_device->extraData(Constants::SourceProfile).toBool()) { + if (!commandLine.isEmpty() && sourceProfile) { const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"}; for (const QString &filePath : rcFilesToSource) { inner.addArgs({"test", "-f", filePath}); @@ -695,31 +708,87 @@ CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const } } - if (!q->m_setup.m_workingDirectory.isEmpty()) { - inner.addArgs({"cd", q->m_setup.m_workingDirectory.path()}); + if (!workingDirectory.isEmpty()) { + inner.addArgs({"cd", workingDirectory.path()}); 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); - const Environment &env = q->m_setup.m_environment; env.forEachEntry([&](const QString &key, const QString &value, bool) { 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"); if (!commandLine.isEmpty()) inner.addCommandLineAsArgs(commandLine, CommandLine::Raw); - cmd.addArg(inner.arguments()); 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 static SshParameters displayless(const SshParameters &sshParameters) @@ -1203,7 +1272,11 @@ private: void start() final { 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_connectionHandle.reset(new SshConnectionHandle(m_device)); m_connectionHandle->setParent(this); diff --git a/src/plugins/remotelinux/remotelinux_constants.h b/src/plugins/remotelinux/remotelinux_constants.h index a7ad8de6989..40cc1d1a8b1 100644 --- a/src/plugins/remotelinux/remotelinux_constants.h +++ b/src/plugins/remotelinux/remotelinux_constants.h @@ -19,8 +19,9 @@ const char RsyncDeployStepId[] = "RemoteLinux.RsyncDeployStep"; const char CustomCommandDeployStepId[] = "RemoteLinux.GenericRemoteLinuxCustomCommandDeploymentStep"; const char KillAppStepId[] = "RemoteLinux.KillAppStep"; -const char SupportsRSync[] = "RemoteLinux.SupportsRSync"; -const char SourceProfile[] = "RemoteLinux.SourceProfile"; +const char SupportsRSync[] = "RemoteLinux.SupportsRSync"; +const char SourceProfile[] = "RemoteLinux.SourceProfile"; +const char LinkDevice[] = "RemoteLinux.LinkDevice"; const char RunConfigId[] = "RemoteLinuxRunConfiguration:"; const char CustomRunConfigId[] = "RemoteLinux.CustomRunConfig";