ProjectExplorer: Avoid need to fix up portsgatherer command lines

... by creating them with the right device to start with.

Change-Id: Ib2f0f10b67322fe66a609287a629d3d720d83c93
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
hjk
2022-05-10 12:53:27 +02:00
parent da0f0ac523
commit 7f2288d9cc
11 changed files with 139 additions and 164 deletions

View File

@@ -105,46 +105,6 @@ const QString s_pidMarker = "__qtc$$qtc__";
static Q_LOGGING_CATEGORY(dockerDeviceLog, "qtc.docker.device", QtWarningMsg);
#define LOG(x) qCDebug(dockerDeviceLog) << this << x << '\n'
class DockerPortsGatheringMethod : public PortsGatheringMethod
{
CommandLine commandLine(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)
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
return {"sed", "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
CommandLine::Raw};
}
QList<Utils::Port> usedPorts(const QByteArray &output) const override
{
QList<Utils::Port> ports;
QList<QByteArray> portStrings = output.split('\n');
foreach (const QByteArray &portString, portStrings) {
if (portString.size() != 4)
continue;
bool ok;
const Utils::Port port(portString.toInt(&ok, 16));
if (ok) {
if (!ports.contains(port))
ports << port;
} else {
qWarning("%s: Unexpected string '%s' is not a port.",
Q_FUNC_INFO, portString.data());
}
}
return ports;
}
};
class ContainerShell : public Utils::DeviceShell
{
public:
@@ -607,10 +567,46 @@ bool DockerDevice::canAutoDetectPorts() const
return true;
}
PortsGatheringMethod::Ptr DockerDevice::portsGatheringMethod() const
PortsGatheringMethod DockerDevice::portsGatheringMethod() const
{
return DockerPortsGatheringMethod::Ptr(new DockerPortsGatheringMethod);
}
return {
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
// 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)
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
return {filePath("sed"),
"-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
CommandLine::Raw};
},
[](const QByteArray &output) {
QList<Utils::Port> ports;
const QList<QByteArray> portStrings = output.split('\n');
for (const QByteArray &portString : portStrings) {
if (portString.size() != 4)
continue;
bool ok;
const Utils::Port port(portString.toInt(&ok, 16));
if (ok) {
if (!ports.contains(port))
ports << port;
} else {
qWarning("%s: Unexpected string '%s' is not a port.",
Q_FUNC_INFO, portString.data());
}
}
return ports;
}
};
};
DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const
{

View File

@@ -66,7 +66,7 @@ public:
Utils::ProcessInterface *createProcessInterface() const override;
bool canAutoDetectPorts() const override;
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override;
ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
bool canCreateProcessModel() const override { return false; }
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
bool hasDeviceTester() const override { return false; }

View File

@@ -118,10 +118,10 @@ DeviceEnvironmentFetcher::Ptr DesktopDevice::environmentFetcher() const
return DeviceEnvironmentFetcher::Ptr(new DesktopDeviceEnvironmentFetcher());
}
class DesktopPortsGatheringMethod : public PortsGatheringMethod
PortsGatheringMethod DesktopDevice::portsGatheringMethod() const
{
CommandLine commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const override
{
return {
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
// 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
@@ -133,14 +133,13 @@ class DesktopPortsGatheringMethod : public PortsGatheringMethod
Q_UNUSED(protocol)
if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost())
return {"netstat", {"-a", "-n"}};
return {filePath("netstat"), {"-a", "-n"}};
if (HostOsInfo::isLinuxHost())
return {"/bin/sh", {"-c", "cat /proc/net/tcp*"}};
return {filePath("/bin/sh"), {"-c", "cat /proc/net/tcp*"}};
return {};
}
},
QList<Utils::Port> usedPorts(const QByteArray &output) const override
{
[](const QByteArray &output) {
QList<Utils::Port> ports;
const QList<QByteArray> lines = output.split('\n');
for (const QByteArray &line : lines) {
@@ -150,11 +149,7 @@ class DesktopPortsGatheringMethod : public PortsGatheringMethod
}
return ports;
}
};
PortsGatheringMethod::Ptr DesktopDevice::portsGatheringMethod() const
{
return DesktopPortsGatheringMethod::Ptr(new DesktopPortsGatheringMethod);
};
}
QUrl DesktopDevice::toolControlChannel(const ControlChannelHint &) const

View File

@@ -48,7 +48,7 @@ public:
bool canAutoDetectPorts() const override;
bool canCreateProcessModel() const override;
DeviceProcessList *createProcessListModel(QObject *parent) const override;
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override;
ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
DeviceProcessSignalOperation::Ptr signalOperation() const override;
DeviceEnvironmentFetcher::Ptr environmentFetcher() const override;
QUrl toolControlChannel(const ControlChannelHint &) const override;

View File

@@ -48,7 +48,7 @@ class DeviceUsedPortsGathererPrivate
QByteArray remoteStdout;
QByteArray remoteStderr;
IDevice::ConstPtr device;
PortsGatheringMethod::Ptr portsGatheringMethod;
PortsGatheringMethod portsGatheringMethod;
};
} // namespace Internal
@@ -71,11 +71,14 @@ void DeviceUsedPortsGatherer::start(const IDevice::ConstPtr &device)
QTC_ASSERT(d->device, emit error("No device given"); return);
d->portsGatheringMethod = d->device->portsGatheringMethod();
QTC_ASSERT(d->portsGatheringMethod, emit error("Not implemented"); return);
QTC_ASSERT(d->portsGatheringMethod.commandLine, emit error("Not implemented"); return);
QTC_ASSERT(d->portsGatheringMethod.parsePorts, emit error("Not implemented"); return);
const QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol;
d->process.reset(new QtcProcess);
d->process->setCommand(d->portsGatheringMethod.commandLine(protocol));
connect(d->process.get(), &QtcProcess::done,
this, &DeviceUsedPortsGatherer::handleProcessDone);
connect(d->process.get(), &QtcProcess::readyReadStandardOutput,
@@ -83,10 +86,7 @@ void DeviceUsedPortsGatherer::start(const IDevice::ConstPtr &device)
connect(d->process.get(), &QtcProcess::readyReadStandardError,
this, [this] { d->remoteStderr += d->process->readAllStandardError(); });
CommandLine command = d->portsGatheringMethod->commandLine(protocol);
const FilePath executable = d->device->mapToGlobalPath(command.executable());
command.setExecutable(executable);
d->process->setCommand(command);
d->process->start();
}
@@ -118,7 +118,7 @@ QList<Port> DeviceUsedPortsGatherer::usedPorts() const
void DeviceUsedPortsGatherer::setupUsedPorts()
{
d->usedPorts.clear();
const QList<Port> usedPorts = d->portsGatheringMethod->usedPorts(d->remoteStdout);
const QList<Port> usedPorts = d->portsGatheringMethod.parsePorts(d->remoteStdout);
for (const Port port : usedPorts) {
if (d->device->freePorts().contains(port))
d->usedPorts << port;

View File

@@ -565,11 +565,6 @@ const QList<IDevice::DeviceAction> IDevice::deviceActions() const
return d->deviceActions;
}
PortsGatheringMethod::Ptr IDevice::portsGatheringMethod() const
{
return PortsGatheringMethod::Ptr();
}
DeviceProcessList *IDevice::createProcessListModel(QObject *parent) const
{
Q_UNUSED(parent)

View File

@@ -109,14 +109,11 @@ protected:
explicit DeviceEnvironmentFetcher();
};
class PROJECTEXPLORER_EXPORT PortsGatheringMethod
class PROJECTEXPLORER_EXPORT PortsGatheringMethod final
{
public:
using Ptr = QSharedPointer<const PortsGatheringMethod>;
virtual ~PortsGatheringMethod() = default;
virtual Utils::CommandLine commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const = 0;
virtual QList<Utils::Port> usedPorts(const QByteArray &commandOutput) const = 0;
std::function<Utils::CommandLine(QAbstractSocket::NetworkLayerProtocol protocol)> commandLine;
std::function<QList<Utils::Port>(const QByteArray &commandOutput)> parsePorts;
};
// See cpp file for documentation.
@@ -175,7 +172,7 @@ public:
// Devices that can auto detect ports need not return a ports gathering method. Such devices can
// obtain a free port on demand. eg: Desktop device.
virtual bool canAutoDetectPorts() const { return false; }
virtual PortsGatheringMethod::Ptr portsGatheringMethod() const;
virtual PortsGatheringMethod portsGatheringMethod() const { return {}; }
virtual bool canCreateProcessModel() const { return false; }
virtual DeviceProcessList *createProcessListModel(QObject *parent = nullptr) const;
virtual bool hasDeviceTester() const { return false; }

View File

@@ -106,29 +106,6 @@ void QnxProcessImpl::sendControlSignal(Utils::ControlSignal controlSignal)
const char QnxVersionKey[] = "QnxVersion";
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.
CommandLine commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const override
{
Q_UNUSED(protocol)
return {"netstat", {"-na"}};
}
QList<Port> usedPorts(const QByteArray &output) const override
{
QList<Utils::Port> ports;
const QList<QByteArray> lines = output.split('\n');
for (const QByteArray &line : lines) {
const Port port(Utils::parseUsedPortFromNetstatOutput(line));
if (port.isValid() && !ports.contains(port))
ports.append(port);
}
return ports;
}
};
QnxDevice::QnxDevice()
{
setDisplayType(tr("QNX"));
@@ -181,9 +158,27 @@ QVariantMap QnxDevice::toMap() const
return map;
}
PortsGatheringMethod::Ptr QnxDevice::portsGatheringMethod() const
PortsGatheringMethod QnxDevice::portsGatheringMethod() const
{
return PortsGatheringMethod::Ptr(new QnxPortsGatheringMethod);
return {
// TODO: The command is probably needlessly complicated because the parsing method
// used to be fixed. These two can now be matched to each other.
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
Q_UNUSED(protocol)
return {filePath("netstat"), {"-na"}};
},
[](const QByteArray &output) {
QList<Utils::Port> ports;
const QList<QByteArray> lines = output.split('\n');
for (const QByteArray &line : lines) {
const Port port(Utils::parseUsedPortFromNetstatOutput(line));
if (port.isValid() && !ports.contains(port))
ports.append(port);
}
return ports;
}
};
}
DeviceProcessList *QnxDevice::createProcessListModel(QObject *parent) const

View File

@@ -42,7 +42,7 @@ public:
static Ptr create() { return Ptr(new QnxDevice); }
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override;
ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override;

View File

@@ -366,45 +366,6 @@ private:
}
};
class LinuxPortsGatheringMethod : public PortsGatheringMethod
{
CommandLine commandLine(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)
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
return {"sed", "-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
CommandLine::Raw};
}
QList<Utils::Port> usedPorts(const QByteArray &output) const override
{
QList<Utils::Port> ports;
QList<QByteArray> portStrings = output.split('\n');
foreach (const QByteArray &portString, portStrings) {
if (portString.size() != 4)
continue;
bool ok;
const Utils::Port port(portString.toInt(&ok, 16));
if (ok) {
if (!ports.contains(port))
ports << port;
} else {
qWarning("%s: Unexpected string '%s' is not a port.",
Q_FUNC_INFO, portString.data());
}
}
return ports;
}
};
// LinuxDevicePrivate
@@ -1054,9 +1015,45 @@ bool LinuxDevice::canAutoDetectPorts() const
return true;
}
PortsGatheringMethod::Ptr LinuxDevice::portsGatheringMethod() const
PortsGatheringMethod LinuxDevice::portsGatheringMethod() const
{
return LinuxPortsGatheringMethod::Ptr(new LinuxPortsGatheringMethod);
return {
[this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
// 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)
// /proc/net/tcp* covers /proc/net/tcp and /proc/net/tcp6
return {filePath("sed"),
"-e 's/.*: [[:xdigit:]]*:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' /proc/net/tcp*",
CommandLine::Raw};
},
[](const QByteArray &output) {
QList<Utils::Port> ports;
const QList<QByteArray> portStrings = output.split('\n');
for (const QByteArray &portString : portStrings) {
if (portString.size() != 4)
continue;
bool ok;
const Utils::Port port(portString.toInt(&ok, 16));
if (ok) {
if (!ports.contains(port))
ports << port;
} else {
qWarning("%s: Unexpected string '%s' is not a port.",
Q_FUNC_INFO, portString.data());
}
}
return ports;
}
};
}
DeviceProcessList *LinuxDevice::createProcessListModel(QObject *parent) const

View File

@@ -47,7 +47,7 @@ public:
ProjectExplorer::IDeviceWidget *createWidget() override;
bool canAutoDetectPorts() const override;
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const override;
ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
bool canCreateProcessModel() const override { return true; }
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
bool hasDeviceTester() const override { return true; }