Commit 7f87e2af authored by hjk's avatar hjk

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: default avatarWolfgang Bremer <wolfgang.bremer@pelagicore.com>
Reviewed-by: default avatarDan Cape <dcape@qnx.com>
Reviewed-by: Christian Kandeler's avatarChristian Kandeler <christian.kandeler@qt.io>
parent 4048629d
......@@ -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);
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
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment