forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
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);
|
||||
const QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol;
|
||||
d->process = d->device->createProcess(this);
|
||||
|
||||
d->process->start();
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user