diff options
author | hjk <hjk@qt.io> | 2017-08-07 18:54:01 +0200 |
---|---|---|
committer | hjk <hjk@qt.io> | 2017-08-10 15:23:07 +0000 |
commit | 7f87e2af3c21343a0a7fd0a74b63b748584d60c6 (patch) | |
tree | 4baacf5f7a2c0c7f2ac372035805dab17da92241 | |
parent | 4048629d1a5940032267f80e44c6032e881abd4e (diff) | |
download | qt-creator-7f87e2af3c21343a0a7fd0a74b63b748584d60c6.tar.gz |
DeviceSupport: Implement DesktopDevice::portsGatheringMethod()
The feature is useful in a QtApplicationManager debugging context.
Internally, DeviceUsedPortsGatherer uses a DeviceProcess now,
not an SshRemoteProcess, to cover cases where the (Windows Desktop)
device not have ssh available.
Change-Id: I9d33ceac65a135123a376ebd2727dcb540563179
Reviewed-by: Wolfgang Bremer <wolfgang.bremer@pelagicore.com>
Reviewed-by: Dan Cape <dcape@qnx.com>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
7 files changed, 152 insertions, 60 deletions
diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index dde4edce5d..7f243e8e43 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -37,11 +37,13 @@ #include <ssh/sshconnection.h> #include <utils/environment.h> +#include <utils/hostosinfo.h> #include <utils/portlist.h> #include <QCoreApplication> using namespace ProjectExplorer::Constants; +using namespace Utils; namespace ProjectExplorer { @@ -137,6 +139,104 @@ DeviceEnvironmentFetcher::Ptr DesktopDevice::environmentFetcher() const return DeviceEnvironmentFetcher::Ptr(new DesktopDeviceEnvironmentFetcher()); } +class DesktopPortsGatheringMethod : public PortsGatheringMethod +{ + Runnable runnable(QAbstractSocket::NetworkLayerProtocol protocol) const override + { + // We might encounter the situation that protocol is given IPv6 + // but the consumer of the free port information decides to open + // an IPv4(only) port. As a result the next IPv6 scan will + // report the port again as open (in IPv6 namespace), while the + // same port in IPv4 namespace might still be blocked, and + // re-use of this port fails. + // GDBserver behaves exactly like this. + + Q_UNUSED(protocol) + + StandardRunnable runnable; + if (HostOsInfo::isWindowsHost()) { + runnable.executable = "netstat"; + runnable.commandLineArguments = "-a -n"; + } else if (HostOsInfo::isLinuxHost()) { + runnable.executable = "/bin/sh"; + runnable.commandLineArguments = "-c 'cat /proc/net/tcp*'"; + } + return runnable; + } + + QList<Utils::Port> usedPorts(const QByteArray &output) const override + { + QList<Utils::Port> ports; + const QList<QByteArray> lines = output.split('\n'); + if (HostOsInfo::isWindowsHost()) { + // Expected output is something like + // + // Active Connections + // + // Proto Local Address Foreign Address State + // TCP 0.0.0.0:80 0.0.0.0:0 LISTENING + // TCP 0.0.0.0:113 0.0.0.0:0 LISTENING + // [...] + // TCP 10.9.78.4:14714 0.0.0.0:0 LISTENING + // TCP 10.9.78.4:50233 12.13.135.180:993 ESTABLISHED + for (const QByteArray &line : lines) { + const QByteArray trimmed = line.trimmed(); + if (!trimmed.startsWith("TCP")) + continue; + int colonPos = trimmed.indexOf(':'); + if (colonPos < 0) + continue; + int spacePos = trimmed.indexOf(':', colonPos + 1); + if (spacePos < 0) + continue; + bool ok; + int len = spacePos - colonPos - 1; + const Utils::Port port(line.mid(colonPos + 1, len).toInt(&ok, 16)); + if (ok) { + if (!ports.contains(port)) + ports << port; + } else { + qWarning("%s: Unexpected string '%s' is not a port.", + Q_FUNC_INFO, line.data()); + } + } + } else if (HostOsInfo::isLinuxHost()) { + // Expected outpit is something like + // + // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt ... + // 0: 00000000:2805 00000000:0000 0A 00000000:00000000 00:00000000 00000000 ... + // + for (const QByteArray &line : lines) { + int firstColonPos = line.indexOf(':'); + if (firstColonPos < 0) + continue; + int secondColonPos = line.indexOf(':', firstColonPos + 1); + if (secondColonPos < 0) + continue; + int spacePos = line.indexOf(':', secondColonPos + 1); + if (spacePos < 0) + continue; + bool ok; + int len = spacePos - secondColonPos - 1; + const Utils::Port port(line.mid(secondColonPos + 1, len).toInt(&ok, 16)); + if (ok) { + if (!ports.contains(port)) + ports << port; + } else { + qWarning("%s: Unexpected string '%s' is not a port.", + Q_FUNC_INFO, line.data()); + } + } + } + return ports; + } +}; + +PortsGatheringMethod::Ptr DesktopDevice::portsGatheringMethod() const +{ + return DesktopPortsGatheringMethod::Ptr(new DesktopPortsGatheringMethod); +} + QUrl DesktopDevice::toolControlChannel(const ControlChannelHint &) const { QUrl url; diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h index 588281c1d2..203dd8fe49 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h @@ -49,6 +49,7 @@ public: bool canCreateProcessModel() const override; DeviceProcessList *createProcessListModel(QObject *parent) const override; bool canCreateProcess() const override { return true; } + ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override; DeviceProcess *createProcess(QObject *parent) const override; DeviceProcessSignalOperation::Ptr signalOperation() const override; DeviceEnvironmentFetcher::Ptr environmentFetcher() const override; diff --git a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp index 7115bb0de3..2167f9ddbb 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp +++ b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.cpp @@ -23,14 +23,14 @@ ** ****************************************************************************/ +#include "deviceprocess.h" #include "deviceusedportsgatherer.h" +#include <projectexplorer/runnables.h> + #include <utils/port.h> #include <utils/portlist.h> #include <utils/qtcassert.h> -#include <ssh/sshconnection.h> -#include <ssh/sshconnectionmanager.h> -#include <ssh/sshremoteprocess.h> using namespace QSsh; using namespace Utils; @@ -41,12 +41,12 @@ namespace Internal { class DeviceUsedPortsGathererPrivate { public: - SshConnection *connection; - SshRemoteProcess::Ptr process; + QPointer<DeviceProcess> process; QList<Port> usedPorts; QByteArray remoteStdout; QByteArray remoteStderr; IDevice::ConstPtr device; + PortsGatheringMethod::Ptr portsGatheringMethod; }; } // namespace Internal @@ -54,7 +54,6 @@ class DeviceUsedPortsGathererPrivate DeviceUsedPortsGatherer::DeviceUsedPortsGatherer(QObject *parent) : QObject(parent), d(new Internal::DeviceUsedPortsGathererPrivate) { - d->connection = 0; } DeviceUsedPortsGatherer::~DeviceUsedPortsGatherer() @@ -65,51 +64,36 @@ DeviceUsedPortsGatherer::~DeviceUsedPortsGatherer() void DeviceUsedPortsGatherer::start(const IDevice::ConstPtr &device) { - QTC_ASSERT(!d->connection, emit error("No connection"); return); - QTC_ASSERT(device && device->portsGatheringMethod(), - emit error("Not implemented"); return); - d->device = device; - d->connection = QSsh::acquireConnection(device->sshParameters()); - connect(d->connection, &SshConnection::error, - this, &DeviceUsedPortsGatherer::handleConnectionError); - if (d->connection->state() == SshConnection::Connected) { - handleConnectionEstablished(); - return; - } - connect(d->connection, &SshConnection::connected, - this, &DeviceUsedPortsGatherer::handleConnectionEstablished); - if (d->connection->state() == SshConnection::Unconnected) - d->connection->connectToHost(); -} + QTC_ASSERT(d->device, emit error("No device given"); return); -void DeviceUsedPortsGatherer::handleConnectionEstablished() -{ - const QAbstractSocket::NetworkLayerProtocol protocol - = d->connection->connectionInfo().localAddress.protocol(); - const QByteArray commandLine = d->device->portsGatheringMethod()->commandLine(protocol); - d->process = d->connection->createRemoteProcess(commandLine); + d->portsGatheringMethod = d->device->portsGatheringMethod(); + QTC_ASSERT(d->portsGatheringMethod, emit error("Not implemented"); return); + + const QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol; + d->process = d->device->createProcess(this); - connect(d->process.data(), &SshRemoteProcess::closed, this, &DeviceUsedPortsGatherer::handleProcessClosed); - connect(d->process.data(), &SshRemoteProcess::readyReadStandardOutput, this, &DeviceUsedPortsGatherer::handleRemoteStdOut); - connect(d->process.data(), &SshRemoteProcess::readyReadStandardError, this, &DeviceUsedPortsGatherer::handleRemoteStdErr); + connect(d->process.data(), &DeviceProcess::finished, + this, &DeviceUsedPortsGatherer::handleProcessFinished); + connect(d->process.data(), &DeviceProcess::error, + this, &DeviceUsedPortsGatherer::handleProcessError); + connect(d->process.data(), &DeviceProcess::readyReadStandardOutput, + this, &DeviceUsedPortsGatherer::handleRemoteStdOut); + connect(d->process.data(), &DeviceProcess::readyReadStandardError, + this, &DeviceUsedPortsGatherer::handleRemoteStdErr); - d->process->start(); + const Runnable runnable = d->portsGatheringMethod->runnable(protocol); + d->process->start(runnable); } void DeviceUsedPortsGatherer::stop() { - if (!d->connection) - return; d->usedPorts.clear(); d->remoteStdout.clear(); d->remoteStderr.clear(); if (d->process) disconnect(d->process.data(), 0, this, 0); d->process.clear(); - disconnect(d->connection, 0, this, 0); - QSsh::releaseConnection(d->connection); - d->connection = 0; } Port DeviceUsedPortsGatherer::getNextFreePort(PortList *freePorts) const @@ -130,7 +114,7 @@ QList<Port> DeviceUsedPortsGatherer::usedPorts() const void DeviceUsedPortsGatherer::setupUsedPorts() { d->usedPorts.clear(); - const QList<Port> usedPorts = d->device->portsGatheringMethod()->usedPorts(d->remoteStdout); + const QList<Port> usedPorts = d->portsGatheringMethod->usedPorts(d->remoteStdout); foreach (const Port port, usedPorts) { if (d->device->freePorts().contains(port)) d->usedPorts << port; @@ -138,27 +122,23 @@ void DeviceUsedPortsGatherer::setupUsedPorts() emit portListReady(); } -void DeviceUsedPortsGatherer::handleConnectionError() +void DeviceUsedPortsGatherer::handleProcessError() { - if (!d->connection) - return; - emit error(tr("Connection error: %1").arg(d->connection->errorString())); + emit error(tr("Connection error: %1").arg(d->process->errorString())); stop(); } -void DeviceUsedPortsGatherer::handleProcessClosed(int exitStatus) +void DeviceUsedPortsGatherer::handleProcessFinished() { - if (!d->connection) + if (!d->process) return; QString errMsg; + QProcess::ExitStatus exitStatus = d->process->exitStatus(); switch (exitStatus) { - case SshRemoteProcess::FailedToStart: - errMsg = tr("Could not start remote process: %1").arg(d->process->errorString()); - break; - case SshRemoteProcess::CrashExit: + case QProcess::CrashExit: errMsg = tr("Remote process crashed: %1").arg(d->process->errorString()); break; - case SshRemoteProcess::NormalExit: + case QProcess::NormalExit: if (d->process->exitCode() == 0) setupUsedPorts(); else diff --git a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h index c298b6fbd0..cb1508aae7 100644 --- a/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h +++ b/src/plugins/projectexplorer/devicesupport/deviceusedportsgatherer.h @@ -33,6 +33,7 @@ namespace ProjectExplorer { namespace Internal { class DeviceUsedPortsGathererPrivate; } +class StandardRunnable; class PROJECTEXPLORER_EXPORT DeviceUsedPortsGatherer : public QObject { @@ -42,7 +43,7 @@ public: DeviceUsedPortsGatherer(QObject *parent = 0); ~DeviceUsedPortsGatherer() override; - void start(const ProjectExplorer::IDevice::ConstPtr &device); + void start(const IDevice::ConstPtr &device); void stop(); Utils::Port getNextFreePort(Utils::PortList *freePorts) const; // returns -1 if no more are left QList<Utils::Port> usedPorts() const; @@ -52,11 +53,10 @@ signals: void portListReady(); private: - void handleConnectionEstablished(); - void handleConnectionError(); - void handleProcessClosed(int exitStatus); void handleRemoteStdOut(); void handleRemoteStdErr(); + void handleProcessError(); + void handleProcessFinished(); void setupUsedPorts(); diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h index 4bf9ed09c2..7fa91b0f0e 100644 --- a/src/plugins/projectexplorer/devicesupport/idevice.h +++ b/src/plugins/projectexplorer/devicesupport/idevice.h @@ -57,6 +57,7 @@ class Connection; class DeviceProcess; class DeviceProcessList; class Kit; +class Runnable; class RunControl; class RunWorker; @@ -110,7 +111,7 @@ public: typedef QSharedPointer<const PortsGatheringMethod> Ptr; virtual ~PortsGatheringMethod() = default; - virtual QByteArray commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const = 0; + virtual Runnable runnable(QAbstractSocket::NetworkLayerProtocol protocol) const = 0; virtual QList<Utils::Port> usedPorts(const QByteArray &commandOutput) const = 0; }; diff --git a/src/plugins/qnx/qnxdevice.cpp b/src/plugins/qnx/qnxdevice.cpp index 994f74a839..31a8d9064d 100644 --- a/src/plugins/qnx/qnxdevice.cpp +++ b/src/plugins/qnx/qnxdevice.cpp @@ -55,19 +55,25 @@ class QnxPortsGatheringMethod : public PortsGatheringMethod { // TODO: The command is probably needlessly complicated because the parsing method // used to be fixed. These two can now be matched to each other. - QByteArray commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const + Runnable runnable(QAbstractSocket::NetworkLayerProtocol protocol) const override { Q_UNUSED(protocol); - return "netstat -na " + StandardRunnable runnable; + // FIXME: Is this extra shell needed? + runnable.executable = "/bin/sh"; + runnable.commandLineArguments = "-c \"" + "netstat -na " "| sed 's/[a-z]\\+\\s\\+[0-9]\\+\\s\\+[0-9]\\+\\s\\+\\(\\*\\|[0-9\\.]\\+\\)\\.\\([0-9]\\+\\).*/\\2/g' " "| while read line; do " "if [[ $line != udp* ]] && [[ $line != Active* ]]; then " "printf '%x\n' $line; " "fi; " - "done"; + "done" + "\""; + return runnable; } - QList<Port> usedPorts(const QByteArray &output) const + QList<Port> usedPorts(const QByteArray &output) const override { QList<Port> ports; QList<QByteArray> portStrings = output.split('\n'); diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp index 1e6fe89993..0e783a1a19 100644 --- a/src/plugins/remotelinux/linuxdevice.cpp +++ b/src/plugins/remotelinux/linuxdevice.cpp @@ -35,6 +35,7 @@ #include <coreplugin/id.h> #include <projectexplorer/devicesupport/sshdeviceprocesslist.h> +#include <projectexplorer/runnables.h> #include <ssh/sshremoteprocessrunner.h> #include <utils/algorithm.h> #include <utils/port.h> @@ -122,7 +123,7 @@ private: class LinuxPortsGatheringMethod : public PortsGatheringMethod { - QByteArray commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const + Runnable runnable(QAbstractSocket::NetworkLayerProtocol protocol) const { // We might encounter the situation that protocol is given IPv6 // but the consumer of the free port information decides to open @@ -135,7 +136,10 @@ class LinuxPortsGatheringMethod : public PortsGatheringMethod Q_UNUSED(protocol) // /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6 - return "sed -e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*"; + StandardRunnable runnable; + runnable.executable = "sed"; + runnable.commandLineArguments = "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*"; + return runnable; } QList<Utils::Port> usedPorts(const QByteArray &output) const |