Commit 753c62d7 authored by Christian Kandeler's avatar Christian Kandeler Committed by hjk

Device support: Introduce IDevice helper classes.

These are for configuration of process and ports gathering activities,
respectively.
This couples related functionality more tightly, while keeping
the number of IDevice methods at a reasonable level.
For ports gathering, the patch also adds the ability to configure
both the command and the parsing function; the latter used to be
hardcoded in the PortsGatherer class.

Change-Id: I1b8940397a51efa7ddc05dd15cf861777d118c1a
Reviewed-by: default avatarhjk <qthjk@ovi.com>
parent ac815062
......@@ -90,22 +90,5 @@ IDevice::Ptr AndroidDevice::clone() const
return IDevice::Ptr(new AndroidDevice(*this));
}
QString AndroidDevice::listProcessesCommandLine() const
{
return QString();
}
QString AndroidDevice::killProcessCommandLine(const DeviceProcess &process) const
{
Q_UNUSED(process);
return QString();
}
QList<DeviceProcess> AndroidDevice::buildProcessList(const QString &listProcessesReply) const
{
Q_UNUSED(listProcessesReply);
return QList<DeviceProcess>();
}
} // namespace Internal
} // namespace Android
......@@ -51,10 +51,6 @@ public:
ProjectExplorer::IDevice::Ptr clone() const;
QString listProcessesCommandLine() const;
QString killProcessCommandLine(const ProjectExplorer::DeviceProcess &process) const;
QList<ProjectExplorer::DeviceProcess> buildProcessList(const QString &listProcessesReply) const;
protected:
friend class AndroidDeviceFactory;
friend class Android::AndroidPlugin;
......
......@@ -85,21 +85,4 @@ IDevice::Ptr DesktopDevice::clone() const
return Ptr(new DesktopDevice(*this));
}
QString DesktopDevice::listProcessesCommandLine() const
{
return QString();
}
QString DesktopDevice::killProcessCommandLine(const DeviceProcess &process) const
{
Q_UNUSED(process);
return QString();
}
QList<DeviceProcess> DesktopDevice::buildProcessList(const QString &listProcessesReply) const
{
Q_UNUSED(listProcessesReply);
return QList<DeviceProcess>();
}
} // namespace ProjectExplorer
......@@ -54,10 +54,6 @@ public:
IDevice::Ptr clone() const;
QString listProcessesCommandLine() const;
QString killProcessCommandLine(const DeviceProcess &process) const;
QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const;
protected:
DesktopDevice();
DesktopDevice(const DesktopDevice &other);
......
......@@ -71,6 +71,7 @@ DeviceProcessList::~DeviceProcessList()
void DeviceProcessList::update()
{
QTC_ASSERT(d->state == Inactive, return);
QTC_ASSERT(d->device && d->device->processSupport(), return);
if (!d->remoteProcesses.isEmpty()) {
beginRemoveRows(QModelIndex(), 0, d->remoteProcesses.count() - 1);
......@@ -78,7 +79,7 @@ void DeviceProcessList::update()
endRemoveRows();
}
d->state = Listing;
startProcess(d->device->listProcessesCommandLine());
startProcess(d->device->processSupport()->listProcessesCommandLine());
}
void DeviceProcessList::killProcess(int row)
......@@ -87,7 +88,8 @@ void DeviceProcessList::killProcess(int row)
QTC_ASSERT(d->state == Inactive, return);
d->state = Killing;
startProcess(d->device->killProcessCommandLine(d->remoteProcesses.at(row)));
const int pid = d->remoteProcesses.at(row).pid;
startProcess(d->device->processSupport()->killProcessByPidCommandLine(pid));
}
DeviceProcess DeviceProcessList::at(int row) const
......@@ -160,8 +162,10 @@ void DeviceProcessList::handleRemoteProcessFinished(int exitStatus)
if (d->process.processExitCode() == 0) {
if (d->state == Listing) {
const QByteArray remoteStdout = d->process.readAllStandardOutput();
QList<DeviceProcess> processes = d->device->buildProcessList(QString::fromUtf8(remoteStdout.data(),
remoteStdout.count()));
const QString stdoutString
= QString::fromUtf8(remoteStdout.data(), remoteStdout.count());
QList<DeviceProcess> processes
= d->device->processSupport()->buildProcessList(stdoutString);
if (!processes.isEmpty()) {
beginInsertRows(QModelIndex(), 0, processes.count()-1);
d->remoteProcesses = processes;
......
......@@ -46,11 +46,10 @@ class DeviceUsedPortsGathererPrivate
public:
SshConnection *connection;
SshRemoteProcess::Ptr process;
PortList portsToCheck;
QList<int> usedPorts;
QByteArray remoteStdout;
QByteArray remoteStderr;
QString command;
IDevice::ConstPtr device;
};
} // namespace Internal
......@@ -70,7 +69,9 @@ DeviceUsedPortsGatherer::~DeviceUsedPortsGatherer()
void DeviceUsedPortsGatherer::start(const IDevice::ConstPtr &device)
{
QTC_ASSERT(!d->connection, return);
d->portsToCheck = device->freePorts();
QTC_ASSERT(device && device->portsGatheringMethod(), return);
d->device = device;
d->connection = SshConnectionManager::instance().acquireConnection(device->sshParameters());
connect(d->connection, SIGNAL(error(QSsh::SshError)), SLOT(handleConnectionError()));
if (d->connection->state() == SshConnection::Connected) {
......@@ -84,22 +85,10 @@ void DeviceUsedPortsGatherer::start(const IDevice::ConstPtr &device)
void DeviceUsedPortsGatherer::handleConnectionEstablished()
{
QString command = d->command;
if (command.isEmpty()) {
QString procFilePath;
int addressLength;
if (d->connection->connectionInfo().localAddress.protocol() == QAbstractSocket::IPv4Protocol) {
procFilePath = QLatin1String("/proc/net/tcp");
addressLength = 8;
} else {
procFilePath = QLatin1String("/proc/net/tcp6");
addressLength = 32;
}
command = QString::fromLatin1("sed "
"'s/.*: [[:xdigit:]]\\{%1\\}:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' %2")
.arg(addressLength).arg(procFilePath);
}
d->process = d->connection->createRemoteProcess(command.toUtf8());
const QAbstractSocket::NetworkLayerProtocol protocol
= d->connection->connectionInfo().localAddress.protocol();
const QByteArray commandLine = d->device->portsGatheringMethod()->commandLine(protocol);
d->process = d->connection->createRemoteProcess(commandLine);
connect(d->process.data(), SIGNAL(closed(int)), SLOT(handleProcessClosed(int)));
connect(d->process.data(), SIGNAL(readyReadStandardOutput()), SLOT(handleRemoteStdOut()));
......@@ -138,27 +127,13 @@ QList<int> DeviceUsedPortsGatherer::usedPorts() const
return d->usedPorts;
}
void DeviceUsedPortsGatherer::setCommand(const QString &command)
{
d->command = command;
}
void DeviceUsedPortsGatherer::setupUsedPorts()
{
QList<QByteArray> portStrings = d->remoteStdout.split('\n');
portStrings.removeFirst();
foreach (const QByteArray &portString, portStrings) {
if (portString.isEmpty())
continue;
bool ok;
const int port = portString.toInt(&ok, 16);
if (ok) {
if (d->portsToCheck.contains(port) && !d->usedPorts.contains(port))
d->usedPorts << port;
} else {
qWarning("%s: Unexpected string '%s' is not a port.",
Q_FUNC_INFO, portString.data());
}
d->usedPorts.clear();
const QList<int> usedPorts = d->device->portsGatheringMethod()->usedPorts(d->remoteStdout);
foreach (const int port, usedPorts) {
if (d->device->freePorts().contains(port))
d->usedPorts << port;
}
emit portListReady();
}
......
......@@ -50,8 +50,6 @@ public:
int getNextFreePort(Utils::PortList *freePorts) const; // returns -1 if no more are left
QList<int> usedPorts() const;
void setCommand(const QString &command); // Will use default command if not set
signals:
void error(const QString &errMsg);
void portListReady();
......
......@@ -190,6 +190,9 @@ public:
};
} // namespace Internal
PortsGatheringMethod::~PortsGatheringMethod() { }
DeviceProcessSupport::~DeviceProcessSupport() { }
IDevice::IDevice() : d(new Internal::IDevicePrivate)
{ }
......@@ -246,6 +249,16 @@ Core::Id IDevice::id() const
return d->id;
}
DeviceProcessSupport::Ptr IDevice::processSupport() const
{
return DeviceProcessSupport::Ptr();
}
PortsGatheringMethod::Ptr IDevice::portsGatheringMethod() const
{
return PortsGatheringMethod::Ptr();
}
IDevice::DeviceState IDevice::deviceState() const
{
return d->deviceState;
......
......@@ -34,6 +34,7 @@
#include <coreplugin/id.h>
#include <QAbstractSocket>
#include <QList>
#include <QSharedPointer>
#include <QVariantMap>
......@@ -62,6 +63,29 @@ public:
QString exe;
};
class PROJECTEXPLORER_EXPORT DeviceProcessSupport
{
public:
typedef QSharedPointer<const DeviceProcessSupport> Ptr;
virtual ~DeviceProcessSupport();
virtual QString listProcessesCommandLine() const = 0;
virtual QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const = 0;
virtual QString killProcessByPidCommandLine(int pid) const = 0;
virtual QString killProcessByNameCommandLine(const QString &filePath) const = 0;
};
class PROJECTEXPLORER_EXPORT PortsGatheringMethod
{
public:
typedef QSharedPointer<const PortsGatheringMethod> Ptr;
virtual ~PortsGatheringMethod();
virtual QByteArray commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const = 0;
virtual QList<int> usedPorts(const QByteArray &commandOutput) const = 0;
};
// See cpp file for documentation.
class PROJECTEXPLORER_EXPORT IDevice
{
......@@ -99,9 +123,8 @@ public:
virtual QString displayNameForActionId(Core::Id actionId) const = 0;
virtual void executeAction(Core::Id actionId, QWidget *parent = 0) const = 0;
virtual QString listProcessesCommandLine() const = 0;
virtual QString killProcessCommandLine(const DeviceProcess &process) const = 0;
virtual QList<DeviceProcess> buildProcessList(const QString &listProcessesReply) const = 0;
virtual DeviceProcessSupport::Ptr processSupport() const;
virtual PortsGatheringMethod::Ptr portsGatheringMethod() const;
enum DeviceState { DeviceReadyToUse, DeviceConnected, DeviceDisconnected, DeviceStateUnknown };
DeviceState deviceState() const;
......
include(projectexplorer_dependencies.pri)
LIBS *= -l$$qtLibraryName(ProjectExplorer)
QT *= network
......@@ -328,5 +328,10 @@ QtcPlugin {
"abstractmsvctoolchain.h"
]
}
}
ProductModule {
Depends { name: "cpp" }
Depends { name: "Qt"; submodules: ["network"] }
cpp.includePaths: [".."]
}
}
......@@ -35,7 +35,6 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/target.h>
#include <remotelinux/remotelinuxrunconfiguration.h>
#include <remotelinux/remotelinuxutils.h>
#include <utils/portlist.h>
#include <utils/qtcassert.h>
......@@ -81,7 +80,8 @@ void RemoteLinuxQmlProfilerRunner::stop()
if (m_port == 0)
m_portsGatherer->stop();
else
m_runner->stop(RemoteLinuxUtils::killApplicationCommandLine(m_remoteExecutable).toUtf8());
m_runner->stop(m_device->processSupport()
->killProcessByNameCommandLine(m_remoteExecutable).toUtf8());
m_port = 0;
}
......
......@@ -77,14 +77,6 @@ const char QNX_QNX_OS_TYPE[] = "QnxOsType";
const char QNX_DEBUG_TOKEN_KEY[] = "debugToken";
const char QNX_PORT_GATHERER_COMMAND[] = "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";
const char QNX_BAR_DESCRIPTOR_WIZARD_ID[] = "Q.QnxBlackBerryBarDescriptor";
const char QNX_BLACKBERRY_QTQUICK_APP_WIZARD_ID[] = "Q.QnxBlackBerryQQApp";
const char QNX_BLACKBERRY_QTQUICK2_APP_WIZARD_ID[] = "Q.QnxBlackBerryQQ2App";
......
......@@ -34,7 +34,6 @@
#include "qnxdebugsupport.h"
#include "qnxconstants.h"
#include "qnxrunconfiguration.h"
#include "qnxutils.h"
#include <debugger/debuggerengine.h>
#include <projectexplorer/devicesupport/deviceapplicationrunner.h>
......@@ -62,7 +61,6 @@ QnxDebugSupport::QnxDebugSupport(QnxRunConfiguration *runConfig, Debugger::Debug
{
m_runner = new DeviceApplicationRunner(this);
m_portsGatherer = new DeviceUsedPortsGatherer(this);
m_portsGatherer->setCommand(QLatin1String(Constants::QNX_PORT_GATHERER_COMMAND));
connect(m_portsGatherer, SIGNAL(error(QString)), SLOT(handleError(QString)));
connect(m_portsGatherer, SIGNAL(portListReady()), SLOT(handlePortListReady()));
......@@ -135,7 +133,7 @@ void QnxDebugSupport::handleDebuggingFinished()
void QnxDebugSupport::setFinished()
{
m_state = Inactive;
m_runner->stop(QnxUtils::applicationKillCommand(m_executable).toUtf8());
m_runner->stop(m_device->processSupport()->killProcessByNameCommandLine(m_executable).toUtf8());
}
void QnxDebugSupport::handleProgressReport(const QString &progressOutput)
......
......@@ -36,6 +36,57 @@
using namespace Qnx;
using namespace Qnx::Internal;
class QnxDeviceProcessSupport : public RemoteLinux::LinuxDeviceProcessSupport
{
QString killProcessByNameCommandLine(const QString &filePath) const
{
QString executable = filePath;
return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); "
"do "
"kill $PID; sleep 1; kill -9 $PID; "
"done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/")));
}
};
class QnxPortsGatheringMethod : public ProjectExplorer::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
{
Q_UNUSED(protocol);
return "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";
}
QList<int> usedPorts(const QByteArray &output) const
{
QList<int> ports;
QList<QByteArray> portStrings = output.split('\n');
portStrings.removeFirst();
foreach (const QByteArray &portString, portStrings) {
if (portString.isEmpty())
continue;
bool ok;
const int 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;
}
};
QnxDeviceConfiguration::QnxDeviceConfiguration()
: RemoteLinux::LinuxDevice()
{
......@@ -72,3 +123,12 @@ ProjectExplorer::IDevice::Ptr QnxDeviceConfiguration::clone() const
return Ptr(new QnxDeviceConfiguration(*this));
}
ProjectExplorer::DeviceProcessSupport::Ptr QnxDeviceConfiguration::processSupport() const
{
return ProjectExplorer::DeviceProcessSupport::Ptr(new QnxDeviceProcessSupport);
}
ProjectExplorer::PortsGatheringMethod::Ptr QnxDeviceConfiguration::portsGatheringMethod() const
{
return ProjectExplorer::PortsGatheringMethod::Ptr(new QnxPortsGatheringMethod);
}
......@@ -52,6 +52,9 @@ public:
Origin origin = ManuallyAdded, Core::Id id = Core::Id());
ProjectExplorer::IDevice::Ptr clone() const;
ProjectExplorer::DeviceProcessSupport::Ptr processSupport() const;
ProjectExplorer::PortsGatheringMethod::Ptr portsGatheringMethod() const;
QString displayType() const;
protected:
......
......@@ -81,8 +81,6 @@ IDevice::Ptr QnxDeviceConfigurationWizard::device()
device->setFreePorts(Utils::PortList::fromString(QLatin1String("10000-10100")));
RemoteLinux::GenericLinuxDeviceTester *devTester = new RemoteLinux::GenericLinuxDeviceTester(this);
devTester->usedPortsGatherer()->setCommand(QLatin1String(Constants::QNX_PORT_GATHERER_COMMAND));
RemoteLinux::LinuxDeviceTestDialog dlg(device, devTester, this);
dlg.exec();
......
......@@ -33,7 +33,6 @@
#include "qnxruncontrol.h"
#include "qnxrunconfiguration.h"
#include "qnxutils.h"
#include <projectexplorer/runconfiguration.h>
#include <remotelinux/remotelinuxrunconfiguration.h>
......@@ -45,9 +44,4 @@ using namespace RemoteLinux;
QnxRunControl::QnxRunControl(ProjectExplorer::RunConfiguration *runConfig)
: RemoteLinuxRunControl(runConfig)
{
const RemoteLinuxRunConfiguration * const rc
= qobject_cast<RemoteLinuxRunConfiguration *>(runConfig);
QString executable = rc->remoteExecutableFilePath();
executable.replace(QLatin1String("/"), QLatin1String("\\/"));
overrideStopCommandLine(QnxUtils::applicationKillCommand(executable).toUtf8());
}
......@@ -75,11 +75,3 @@ QStringList QnxUtils::searchPaths(QnxAbstractQtVersion *qtVersion)
return searchPaths;
}
QString QnxUtils::applicationKillCommand(const QString &applicationFilePath)
{
return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); "
"do "
"kill $PID; sleep 1; kill -9 $PID; "
"done").arg(applicationFilePath);
}
......@@ -49,7 +49,6 @@ public:
static QString addQuotes(const QString &string);
static Qnx::QnxArchitecture cpudirToArch(const QString &cpuDir);
static QStringList searchPaths(QnxAbstractQtVersion *qtVersion);
static QString applicationKillCommand(const QString &applicationFilePath);
};
} // namespace Internal
......
......@@ -249,21 +249,4 @@ void SymbianIDevice::updateState()
}
}
QString SymbianIDevice::listProcessesCommandLine() const
{
return QString();
}
QString SymbianIDevice::killProcessCommandLine(const DeviceProcess &process) const
{
Q_UNUSED(process);
return QString();
}
QList<DeviceProcess> SymbianIDevice::buildProcessList(const QString &listProcessesReply) const
{
Q_UNUSED(listProcessesReply);
return QList<DeviceProcess>();
}
} // namespace qt4projectmanager
......@@ -78,10 +78,6 @@ public:
QString displayNameForActionId(Core::Id actionId) const;
void executeAction(Core::Id actionId, QWidget*parent) const;
QString listProcessesCommandLine() const;
QString killProcessCommandLine(const ProjectExplorer::DeviceProcess &process) const;
QList<ProjectExplorer::DeviceProcess> buildProcessList(const QString &listProcessesReply) const;
protected:
SymbianIDevice(const SymbianIDevice &other);
SymbianIDevice &operator=(const SymbianIDevice &); // no impl.
......
......@@ -54,6 +54,119 @@ static QString visualizeNull(QString s)
return s.replace(QLatin1Char('\0'), QLatin1String("<null>"));
}
QString LinuxDeviceProcessSupport::listProcessesCommandLine() const
{
return QString::fromLatin1(
"for dir in `ls -d /proc/[0123456789]*`; do "
"test -d $dir || continue;" // Decrease the likelihood of a race condition.
"echo $dir;"
"cat $dir/cmdline;echo;" // cmdline does not end in newline
"cat $dir/stat;"
"readlink $dir/exe;"
"printf '%1''%2';"
"done").arg(Delimiter0).arg(Delimiter1);
}
QList<DeviceProcess> LinuxDeviceProcessSupport::buildProcessList(const QString &listProcessesReply) const
{
QList<DeviceProcess> processes;
const QStringList lines = listProcessesReply.split(QString::fromLatin1(Delimiter0)
+ QString::fromLatin1(Delimiter1), QString::SkipEmptyParts);
foreach (const QString &line, lines) {
const QStringList elements = line.split(QLatin1Char('\n'));
if (elements.count() < 4) {
qDebug("%s: Expected four list elements, got %d. Line was '%s'.", Q_FUNC_INFO,
elements.count(), qPrintable(visualizeNull(line)));
continue;
}
bool ok;
const int pid = elements.first().mid(6).toInt(&ok);
if (!ok) {
qDebug("%s: Expected number in %s. Line was '%s'.", Q_FUNC_INFO,
qPrintable(elements.first()), qPrintable(visualizeNull(line)));
continue;
}
QString command = elements.at(1);
command.replace(QLatin1Char('\0'), QLatin1Char(' '));
if (command.isEmpty()) {
const QString &statString = elements.at(2);
const int openParenPos = statString.indexOf(QLatin1Char('('));
const int closedParenPos = statString.indexOf(QLatin1Char(')'), openParenPos);
if (openParenPos == -1 || closedParenPos == -1)
continue;
command = QLatin1Char('[')
+ statString.mid(openParenPos + 1, closedParenPos - openParenPos - 1)
+ QLatin1Char(']');
}
DeviceProcess process;
process.pid = pid;
process.cmdLine = command;
process.exe = elements.at(3);
processes.append(process);
}
qSort(processes);
return processes;
}
QString LinuxDeviceProcessSupport::killProcessByPidCommandLine(int pid) const
{
return QLatin1String("kill -9 ") + QString::number(pid);
}
QString LinuxDeviceProcessSupport::killProcessByNameCommandLine(const QString &filePath) const
{
return QString::fromLatin1("cd /proc; for pid in `ls -d [0123456789]*`; "
"do "
"if [ \"`readlink /proc/$pid/exe`\" = \"%1\" ]; then "
" kill $pid; sleep 1; kill -9 $pid; "
"fi; "
"done").arg(filePath);
}
class LinuxPortsGatheringMethod : public ProjectExplorer::PortsGatheringMethod
{
QByteArray commandLine(QAbstractSocket::NetworkLayerProtocol protocol) const
{
QString procFilePath;
int addressLength;
if (protocol == QAbstractSocket::IPv4Protocol) {
procFilePath = QLatin1String("/proc/net/tcp");
addressLength = 8;
} else {
procFilePath = QLatin1String("/proc/net/tcp6");
addressLength = 32;
}
return QString::fromLatin1("sed "
"'s/.*: [[:xdigit:]]\\{%1\\}:\\([[:xdigit:]]\\{4\\}\\).*/\\1/g' %2")
.arg(addressLength).arg(procFilePath).toUtf8();
}
QList<int> usedPorts(const QByteArray &output) const
{
QList<int> ports;
QList<QByteArray> portStrings = output.split('\n');
portStrings.removeFirst();
foreach (const QByteArray &portString, portStrings) {
if (portString.isEmpty())
continue;
bool ok;
const int 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;
}
};
LinuxDevice::Ptr LinuxDevice::create(const QString &name,
Core::Id type, MachineType machineType, Origin origin, Core::Id id)
{
......@@ -128,65 +241,14 @@ ProjectExplorer::IDevice::Ptr LinuxDevice::clone() const
return Ptr(new LinuxDevice(*this));
}
QString LinuxDevice::listProcessesCommandLine() const
{
return QString::fromLatin1(
"for dir in `ls -d /proc/[0123456789]*`; do "
"test -d $dir || continue;" // Decrease the likelihood of a race condition.
"echo $dir;"
"cat $dir/cmdline;echo;" // cmdline does not end in newline