aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/remotelinux/linuxdevice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/remotelinux/linuxdevice.cpp')
-rw-r--r--src/plugins/remotelinux/linuxdevice.cpp660
1 files changed, 294 insertions, 366 deletions
diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp
index 5d0d2d6b92e..cc44dddb3c4 100644
--- a/src/plugins/remotelinux/linuxdevice.cpp
+++ b/src/plugins/remotelinux/linuxdevice.cpp
@@ -11,14 +11,14 @@
#include "remotelinux_constants.h"
#include "remotelinuxsignaloperation.h"
#include "remotelinuxtr.h"
-#include "sshprocessinterface.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/filetransferinterface.h>
-#include <projectexplorer/devicesupport/sshdeviceprocesslist.h>
+#include <projectexplorer/devicesupport/processlist.h>
#include <projectexplorer/devicesupport/sshparameters.h>
#include <projectexplorer/devicesupport/sshsettings.h>
@@ -28,9 +28,10 @@
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/port.h>
+#include <utils/portlist.h>
+#include <utils/process.h>
#include <utils/processinfo.h>
#include <utils/qtcassert.h>
-#include <utils/qtcprocess.h>
#include <utils/stringutils.h>
#include <utils/temporaryfile.h>
@@ -51,9 +52,6 @@ namespace RemoteLinux {
const QByteArray s_pidMarker = "__qtc";
-const char Delimiter0[] = "x--";
-const char Delimiter1[] = "---";
-
static Q_LOGGING_CATEGORY(linuxDeviceLog, "qtc.remotelinux.device", QtWarningMsg);
#define DEBUG(x) qCDebug(linuxDeviceLog) << x << '\n'
@@ -94,7 +92,7 @@ private:
QStringList connectionArgs(const FilePath &binary) const;
const SshParameters m_sshParameters;
- std::unique_ptr<QtcProcess> m_masterProcess;
+ std::unique_ptr<Process> m_masterProcess;
std::unique_ptr<QTemporaryDir> m_masterSocketDir;
QTimer m_timer;
int m_ref = 0;
@@ -160,11 +158,11 @@ void SshSharedConnection::connectToHost()
return;
}
- m_masterProcess.reset(new QtcProcess);
+ m_masterProcess.reset(new Process);
SshParameters::setupSshEnvironment(m_masterProcess.get());
m_timer.setSingleShot(true);
connect(&m_timer, &QTimer::timeout, this, &SshSharedConnection::autoDestructRequested);
- connect(m_masterProcess.get(), &QtcProcess::readyReadStandardOutput, this, [this] {
+ connect(m_masterProcess.get(), &Process::readyReadStandardOutput, this, [this] {
const QByteArray reply = m_masterProcess->readAllRawStandardOutput();
if (reply == "\n")
emitConnected();
@@ -172,7 +170,7 @@ void SshSharedConnection::connectToHost()
});
// TODO: in case of refused connection we are getting the following on stdErr:
// ssh: connect to host 127.0.0.1 port 22: Connection refused\r\n
- connect(m_masterProcess.get(), &QtcProcess::done, this, [this] {
+ connect(m_masterProcess.get(), &Process::done, this, [this] {
const ProcessResult result = m_masterProcess->result();
const ProcessResultData resultData = m_masterProcess->resultData();
if (result == ProcessResult::StartFailed) {
@@ -276,77 +274,6 @@ private:
IDevice::ConstPtr m_device;
};
-static QString visualizeNull(QString s)
-{
- return s.replace(QLatin1Char('\0'), QLatin1String("<null>"));
-}
-
-class LinuxDeviceProcessList : public SshDeviceProcessList
-{
-public:
- LinuxDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent)
- : SshDeviceProcessList(device, parent)
- {
- }
-
-private:
- QString listProcessesCommandLine() const override
- {
- 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(QLatin1String(Delimiter0)).arg(QLatin1String(Delimiter1));
- }
-
- QList<ProcessInfo> buildProcessList(const QString &listProcessesReply) const override
- {
- QList<ProcessInfo> processes;
- const QStringList lines = listProcessesReply.split(QString::fromLatin1(Delimiter0)
- + QString::fromLatin1(Delimiter1), Qt::SkipEmptyParts);
- for (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,
- int(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(']');
- }
-
- ProcessInfo process;
- process.processId = pid;
- process.commandLine = command;
- process.executable = elements.at(3);
- processes.append(process);
- }
-
- return Utils::sorted(std::move(processes));
- }
-};
-
-
// LinuxDevicePrivate
class ShellThreadHandler;
@@ -382,11 +309,13 @@ public:
Environment getEnvironment();
void invalidateEnvironmentCache();
+ void checkOsType();
+ void queryOsType(std::function<RunResult(const CommandLine &)> run);
+
LinuxDevice *q = nullptr;
QThread m_shellThread;
ShellThreadHandler *m_handler = nullptr;
mutable QMutex m_shellMutex;
- QList<QtcProcess *> m_terminals;
LinuxDeviceFileAccess m_fileAccess{this};
QReadWriteLock m_environmentCacheLock;
@@ -410,11 +339,8 @@ Environment LinuxDevicePrivate::getEnvironment()
if (m_environmentCache.has_value())
return m_environmentCache.value();
- QtcProcess getEnvProc;
- getEnvProc.setCommand({FilePath("env").onDevice(q->rootPath()), {}});
- Environment inEnv;
- inEnv.setCombineWithDeviceEnvironment(false);
- getEnvProc.setEnvironment(inEnv);
+ Process getEnvProc;
+ getEnvProc.setCommand({q->filePath("env"), {}});
getEnvProc.runBlocking();
const QString remoteOutput = getEnvProc.cleanedStdOut();
@@ -437,10 +363,8 @@ Environment LinuxDeviceFileAccess::deviceEnvironment() const
class SshProcessInterfacePrivate : public QObject
{
- Q_OBJECT
-
public:
- SshProcessInterfacePrivate(SshProcessInterface *sshInterface, LinuxDevicePrivate *devicePrivate);
+ SshProcessInterfacePrivate(SshProcessInterface *sshInterface, const IDevice::ConstPtr &device);
void start();
@@ -463,47 +387,32 @@ public:
// as this object is alive.
IDevice::ConstPtr m_device;
std::unique_ptr<SshConnectionHandle> m_connectionHandle;
- QtcProcess m_process;
- LinuxDevicePrivate *m_devicePrivate = nullptr;
+ Process m_process;
QString m_socketFilePath;
SshParameters m_sshParameters;
+
bool m_connecting = false;
bool m_killed = false;
ProcessResultData m_result;
+
+ QByteArray m_output;
+ QByteArray m_error;
+ bool m_pidParsed = false;
+ bool m_useConnectionSharing = false;
};
-SshProcessInterface::SshProcessInterface(const LinuxDevice *linuxDevice)
- : d(new SshProcessInterfacePrivate(this, linuxDevice->d))
-{
-}
+SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device)
+ : d(new SshProcessInterfacePrivate(this, device))
+{}
SshProcessInterface::~SshProcessInterface()
{
+ killIfRunning();
delete d;
}
-void SshProcessInterface::handleStarted(qint64 processId)
-{
- emitStarted(processId);
-}
-
-void SshProcessInterface::handleDone(const ProcessResultData &resultData)
-{
- emit done(resultData);
-}
-
-void SshProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData)
-{
- emit readyRead(outputData, {});
-}
-
-void SshProcessInterface::handleReadyReadStandardError(const QByteArray &errorData)
-{
- emit readyRead({}, errorData);
-}
-
void SshProcessInterface::emitStarted(qint64 processId)
{
d->m_processId = processId;
@@ -525,7 +434,7 @@ qint64 SshProcessInterface::processId() const
bool SshProcessInterface::runInShell(const CommandLine &command, const QByteArray &data)
{
- QtcProcess process;
+ Process process;
CommandLine cmd = {d->m_device->filePath("/bin/sh"), {"-c"}};
QString tmp;
ProcessArgs::addArg(&tmp, command.executable().path());
@@ -556,7 +465,7 @@ void SshProcessInterface::sendControlSignal(ControlSignal controlSignal)
d->m_process.closeWriteChannel();
return;
}
- if (d->m_process.usesTerminal()) {
+ if (d->m_process.usesTerminal() || d->m_process.ptyData().has_value()) {
switch (controlSignal) {
case ControlSignal::Terminate: d->m_process.terminate(); break;
case ControlSignal::Kill: d->m_process.kill(); break;
@@ -569,17 +478,7 @@ void SshProcessInterface::sendControlSignal(ControlSignal controlSignal)
handleSendControlSignal(controlSignal);
}
-LinuxProcessInterface::LinuxProcessInterface(const LinuxDevice *linuxDevice)
- : SshProcessInterface(linuxDevice)
-{
-}
-
-LinuxProcessInterface::~LinuxProcessInterface()
-{
- killIfRunning();
-}
-
-void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
+void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
{
QTC_ASSERT(controlSignal != ControlSignal::KickOff, return);
QTC_ASSERT(controlSignal != ControlSignal::CloseWriteChannel, return);
@@ -592,75 +491,58 @@ void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
runInShell(command);
}
-QString LinuxProcessInterface::fullCommandLine(const CommandLine &commandLine) const
+void SshProcessInterfacePrivate::handleStarted()
{
- CommandLine cmd;
-
- if (!commandLine.isEmpty()) {
- const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
- for (const QString &filePath : rcFilesToSource) {
- cmd.addArgs({"test", "-f", filePath});
- cmd.addArgs("&&", CommandLine::Raw);
- cmd.addArgs({".", filePath});
- cmd.addArgs(";", CommandLine::Raw);
- }
- }
-
- if (!m_setup.m_workingDirectory.isEmpty()) {
- cmd.addArgs({"cd", m_setup.m_workingDirectory.path()});
- cmd.addArgs("&&", CommandLine::Raw);
- }
-
- if (m_setup.m_terminalMode == TerminalMode::Off)
- cmd.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
-
- const Environment &env = m_setup.m_environment;
- for (auto it = env.constBegin(); it != env.constEnd(); ++it)
- cmd.addArgs(env.key(it) + "='" + env.expandedValueForKey(env.key(it)) + '\'', CommandLine::Raw);
-
- if (m_setup.m_terminalMode == TerminalMode::Off)
- cmd.addArg("exec");
-
- if (!commandLine.isEmpty())
- cmd.addCommandLineAsArgs(commandLine, CommandLine::Raw);
- return cmd.arguments();
-}
+ const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0;
-void LinuxProcessInterface::handleStarted(qint64 processId)
-{
// Don't emit started() when terminal is off,
// it's being done later inside handleReadyReadStandardOutput().
- if (m_setup.m_terminalMode == TerminalMode::Off)
+ if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData)
return;
m_pidParsed = true;
- emitStarted(processId);
+ q->emitStarted(processId);
}
-void LinuxProcessInterface::handleDone(const ProcessResultData &resultData)
+void SshProcessInterfacePrivate::handleDone()
{
- ProcessResultData finalData = resultData;
+ if (m_connectionHandle) // TODO: should it disconnect from signals first?
+ m_connectionHandle.release()->deleteLater();
+
+ ProcessResultData finalData = m_process.resultData();
if (!m_pidParsed) {
finalData.m_error = QProcess::FailedToStart;
finalData.m_errorString = Utils::joinStrings({finalData.m_errorString,
QString::fromLocal8Bit(m_error)}, '\n');
}
- emit done(finalData);
+ emit q->done(finalData);
}
-void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData)
+void SshProcessInterfacePrivate::handleReadyReadStandardOutput()
{
+ // By default this forwards readyRead immediately, but only buffers the
+ // output in case the start signal is not emitted yet.
+ // In case the pid can be parsed now, the delayed started() is
+ // emitted, and any previously buffered output emitted now.
+ const QByteArray outputData = m_process.readAllRawStandardOutput();
+
if (m_pidParsed) {
- emit readyRead(outputData, {});
+ emit q->readyRead(outputData, {});
return;
}
m_output.append(outputData);
static const QByteArray endMarker = s_pidMarker + '\n';
- const int endMarkerOffset = m_output.indexOf(endMarker);
- if (endMarkerOffset == -1)
- return;
+ int endMarkerLength = endMarker.length();
+ int endMarkerOffset = m_output.indexOf(endMarker);
+ if (endMarkerOffset == -1) {
+ static const QByteArray endMarker = s_pidMarker + "\r\n";
+ endMarkerOffset = m_output.indexOf(endMarker);
+ endMarkerLength = endMarker.length();
+ if (endMarkerOffset == -1)
+ return;
+ }
const int startMarkerOffset = m_output.indexOf(s_pidMarker);
if (startMarkerOffset == endMarkerOffset) // Only theoretically possible.
return;
@@ -670,59 +552,109 @@ void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outp
const qint64 processId = pidString.toLongLong();
// We don't want to show output from e.g. /etc/profile.
- m_output = m_output.mid(endMarkerOffset + endMarker.length());
+ m_output = m_output.mid(endMarkerOffset + endMarkerLength);
- emitStarted(processId);
+ q->emitStarted(processId);
if (!m_output.isEmpty() || !m_error.isEmpty())
- emit readyRead(m_output, m_error);
+ emit q->readyRead(m_output, m_error);
m_output.clear();
m_error.clear();
}
-void LinuxProcessInterface::handleReadyReadStandardError(const QByteArray &errorData)
+void SshProcessInterfacePrivate::handleReadyReadStandardError()
{
+ // By default forwards readyRead immediately, but buffers it in
+ // case the start signal is not emitted yet.
+ const QByteArray errorData = m_process.readAllRawStandardError();
+
if (m_pidParsed) {
- emit readyRead({}, errorData);
+ emit q->readyRead({}, errorData);
return;
}
m_error.append(errorData);
}
SshProcessInterfacePrivate::SshProcessInterfacePrivate(SshProcessInterface *sshInterface,
- LinuxDevicePrivate *devicePrivate)
+ const IDevice::ConstPtr &device)
: QObject(sshInterface)
, q(sshInterface)
- , m_device(devicePrivate->q->sharedFromThis())
+ , m_device(device)
, m_process(this)
- , m_devicePrivate(devicePrivate)
{
- connect(&m_process, &QtcProcess::started, this, &SshProcessInterfacePrivate::handleStarted);
- connect(&m_process, &QtcProcess::done, this, &SshProcessInterfacePrivate::handleDone);
- connect(&m_process, &QtcProcess::readyReadStandardOutput,
+ connect(&m_process, &Process::started, this, &SshProcessInterfacePrivate::handleStarted);
+ connect(&m_process, &Process::done, this, &SshProcessInterfacePrivate::handleDone);
+ connect(&m_process, &Process::readyReadStandardOutput,
this, &SshProcessInterfacePrivate::handleReadyReadStandardOutput);
- connect(&m_process, &QtcProcess::readyReadStandardError,
+ connect(&m_process, &Process::readyReadStandardError,
this, &SshProcessInterfacePrivate::handleReadyReadStandardError);
}
void SshProcessInterfacePrivate::start()
{
clearForStart();
+ m_sshParameters = m_device->sshParameters();
+
+ const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
+ if (const IDevice::ConstPtr linkDevice = DeviceManager::instance()->find(linkDeviceId)) {
+ CommandLine cmd{linkDevice->filePath("ssh")};
+ if (!m_sshParameters.userName().isEmpty()) {
+ cmd.addArg("-l");
+ cmd.addArg(m_sshParameters.userName());
+ }
+ cmd.addArg(m_sshParameters.host());
+
+ const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off
+ || q->m_setup.m_ptyData;
+ if (useTerminal)
+ cmd.addArg("-tt");
+
+ const CommandLine full = q->m_setup.m_commandLine;
+ if (!full.isEmpty()) { // Empty is ok in case of opening a terminal.
+ CommandLine inner;
+ const QString wd = q->m_setup.m_workingDirectory.path();
+ if (!wd.isEmpty())
+ inner.addCommandLineWithAnd({"cd", {wd}});
+ if (!useTerminal) {
+ const QString pidArg = QString("%1\\$\\$%1").arg(QString::fromLatin1(s_pidMarker));
+ inner.addCommandLineWithAnd({"echo", pidArg, CommandLine::Raw});
+ }
+ inner.addCommandLineWithAnd(full);
+ cmd.addCommandLineAsSingleArg(inner);
+ }
+
+ m_process.setProcessImpl(q->m_setup.m_processImpl);
+ m_process.setProcessMode(q->m_setup.m_processMode);
+ m_process.setTerminalMode(q->m_setup.m_terminalMode);
+ m_process.setPtyData(q->m_setup.m_ptyData);
+ m_process.setReaperTimeout(q->m_setup.m_reaperTimeout);
+ m_process.setWriteData(q->m_setup.m_writeData);
+ m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows);
+ m_process.setExtraData(q->m_setup.m_extraData);
+
+ m_process.setCommand(cmd);
+ m_process.start();
+ return;
+ }
+
+ m_useConnectionSharing = SshSettings::connectionSharingEnabled();
- m_sshParameters = m_devicePrivate->q->sshParameters();
// TODO: Do we really need it for master process?
m_sshParameters.x11DisplayName
= q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString();
- if (SshSettings::connectionSharingEnabled()) {
+ if (m_useConnectionSharing) {
m_connecting = true;
- m_connectionHandle.reset(new SshConnectionHandle(m_devicePrivate->q->sharedFromThis()));
+ m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this);
connect(m_connectionHandle.get(), &SshConnectionHandle::connected,
this, &SshProcessInterfacePrivate::handleConnected);
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
this, &SshProcessInterfacePrivate::handleDisconnected);
- m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
+ auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
+ QTC_ASSERT(linuxDevice, handleDone(); return);
+ linuxDevice->connectionAccess()
+ ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
} else {
doStart();
}
@@ -749,34 +681,6 @@ void SshProcessInterfacePrivate::handleDisconnected(const ProcessResultData &res
emit q->done(resultData); // TODO: don't emit done() on process finished afterwards
}
-void SshProcessInterfacePrivate::handleStarted()
-{
- const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0;
- // By default emits started signal, Linux impl doesn't emit it when terminal is off.
- q->handleStarted(processId);
-}
-
-void SshProcessInterfacePrivate::handleDone()
-{
- if (m_connectionHandle) // TODO: should it disconnect from signals first?
- m_connectionHandle.release()->deleteLater();
- q->handleDone(m_process.resultData());
-}
-
-void SshProcessInterfacePrivate::handleReadyReadStandardOutput()
-{
- // By default emits signal. LinuxProcessImpl does custom parsing for processId
- // and emits delayed start() - only when terminal is off.
- q->handleReadyReadStandardOutput(m_process.readAllRawStandardOutput());
-}
-
-void SshProcessInterfacePrivate::handleReadyReadStandardError()
-{
- // By default emits signal. LinuxProcessImpl buffers the error channel until
- // it emits delayed start() - only when terminal is off.
- q->handleReadyReadStandardError(m_process.readAllRawStandardError());
-}
-
void SshProcessInterfacePrivate::clearForStart()
{
m_result = {};
@@ -787,8 +691,11 @@ void SshProcessInterfacePrivate::doStart()
m_process.setProcessImpl(q->m_setup.m_processImpl);
m_process.setProcessMode(q->m_setup.m_processMode);
m_process.setTerminalMode(q->m_setup.m_terminalMode);
+ m_process.setPtyData(q->m_setup.m_ptyData);
m_process.setReaperTimeout(q->m_setup.m_reaperTimeout);
m_process.setWriteData(q->m_setup.m_writeData);
+ m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows);
+
// TODO: what about other fields from m_setup?
SshParameters::setupSshEnvironment(&m_process);
if (!m_sshParameters.x11DisplayName.isEmpty()) {
@@ -798,32 +705,72 @@ void SshProcessInterfacePrivate::doStart()
env.set("DISPLAY", m_sshParameters.x11DisplayName);
m_process.setControlEnvironment(env);
}
+ m_process.setExtraData(q->m_setup.m_extraData);
m_process.setCommand(fullLocalCommandLine());
m_process.start();
}
CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
{
- CommandLine cmd{SshSettings::sshFilePath()};
+ const FilePath sshBinary = SshSettings::sshFilePath();
+ const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData;
+ const bool usePidMarker = !useTerminal;
+ const bool sourceProfile = m_device->extraData(Constants::SourceProfile).toBool();
+ const bool useX = !m_sshParameters.x11DisplayName.isEmpty();
+
+ CommandLine cmd{sshBinary};
- if (!m_sshParameters.x11DisplayName.isEmpty())
+ if (useX)
cmd.addArg("-X");
- if (q->m_setup.m_terminalMode != TerminalMode::Off)
+ if (useTerminal)
cmd.addArg("-tt");
cmd.addArg("-q");
- QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath());
+ cmd.addArgs(m_sshParameters.connectionOptions(sshBinary));
if (!m_socketFilePath.isEmpty())
- options << "-o" << ("ControlPath=" + m_socketFilePath);
- options << m_sshParameters.host();
- cmd.addArgs(options);
+ cmd.addArgs({"-o", "ControlPath=" + m_socketFilePath});
+
+ cmd.addArg(m_sshParameters.host());
- CommandLine remoteWithLocalPath = q->m_setup.m_commandLine;
- FilePath executable = FilePath::fromParts({}, {}, remoteWithLocalPath.executable().path());
- remoteWithLocalPath.setExecutable(executable);
+ CommandLine commandLine = q->m_setup.m_commandLine;
+ FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path());
+ commandLine.setExecutable(executable);
+
+ CommandLine inner;
+
+ if (!commandLine.isEmpty() && sourceProfile) {
+ const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
+ for (const QString &filePath : rcFilesToSource) {
+ inner.addArgs({"test", "-f", filePath});
+ inner.addArgs("&&", CommandLine::Raw);
+ inner.addArgs({".", filePath});
+ inner.addArgs(";", CommandLine::Raw);
+ }
+ }
+
+ const FilePath &workingDirectory = q->m_setup.m_workingDirectory;
+ if (!workingDirectory.isEmpty()) {
+ inner.addArgs({"cd", workingDirectory.path()});
+ inner.addArgs("&&", CommandLine::Raw);
+ }
+
+ if (usePidMarker)
+ inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
+
+ const Environment &env = q->m_setup.m_environment;
+ env.forEachEntry([&](const QString &key, const QString &value, bool) {
+ inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw);
+ });
+
+ if (!useTerminal && !commandLine.isEmpty())
+ inner.addArg("exec");
+
+ if (!commandLine.isEmpty())
+ inner.addCommandLineAsArgs(commandLine, CommandLine::Raw);
+
+ cmd.addArg(inner.arguments());
- cmd.addArg(q->fullCommandLine(remoteWithLocalPath));
return cmd;
}
@@ -848,7 +795,7 @@ class ShellThreadHandler : public QObject
}
private:
- void setupShellProcess(QtcProcess *shellProcess) override
+ void setupShellProcess(Process *shellProcess) override
{
SshParameters::setupSshEnvironment(shellProcess);
shellProcess->setCommand(m_cmdLine);
@@ -857,7 +804,7 @@ class ShellThreadHandler : public QObject
CommandLine createFallbackCommand(const CommandLine &cmdLine) override
{
CommandLine result = cmdLine;
- result.setExecutable(cmdLine.executable().onDevice(m_devicePath));
+ result.setExecutable(m_devicePath.withNewMappedPath(cmdLine.executable())); // Needed?
return result;
}
@@ -995,9 +942,16 @@ LinuxDevice::LinuxDevice()
{
setFileAccess(&d->m_fileAccess);
setDisplayType(Tr::tr("Remote Linux"));
- setDefaultDisplayName(Tr::tr("Remote Linux Device"));
setOsType(OsTypeLinux);
+ setupId(IDevice::ManuallyAdded, Utils::Id());
+ setType(Constants::GenericLinuxOsType);
+ setMachineType(IDevice::Hardware);
+ setFreePorts(PortList::fromString(QLatin1String("10000-10100")));
+ SshParameters sshParams;
+ sshParams.timeout = 10;
+ setSshParameters(sshParams);
+
addDeviceAction({Tr::tr("Deploy Public Key..."), [](const IDevice::Ptr &device, QWidget *parent) {
if (auto d = PublicKeyDeploymentDialog::createDialog(device, parent)) {
d->exec();
@@ -1006,45 +960,31 @@ LinuxDevice::LinuxDevice()
}});
setOpenTerminal([this](const Environment &env, const FilePath &workingDir) {
- QtcProcess * const proc = new QtcProcess;
- d->m_terminals.append(proc);
- QObject::connect(proc, &QtcProcess::done, proc, [this, proc] {
- if (proc->error() != QProcess::UnknownError) {
- const QString errorString = proc->errorString();
- QString message;
- if (proc->error() == QProcess::FailedToStart)
- message = Tr::tr("Error starting remote shell.");
- else if (errorString.isEmpty())
- message = Tr::tr("Error running remote shell.");
- else
- message = Tr::tr("Error running remote shell: %1").arg(errorString);
- Core::MessageManager::writeDisrupting(message);
- }
- proc->deleteLater();
- d->m_terminals.removeOne(proc);
- });
+ Process proc;
- // We recreate the same way that QtcProcess uses to create the actual environment.
- const Environment finalEnv = (!env.hasChanges() && env.combineWithDeviceEnvironment())
- ? d->getEnvironment()
- : env;
// If we will not set any environment variables, we can leave out the shell executable
// as the "ssh ..." call will automatically launch the default shell if there are
// no arguments. But if we will set environment variables, we need to explicitly
// specify the shell executable.
- const QString shell = finalEnv.hasChanges() ? finalEnv.value_or("SHELL", "/bin/sh")
- : QString();
-
- proc->setCommand({filePath(shell), {}});
- proc->setTerminalMode(TerminalMode::On);
- proc->setEnvironment(env);
- proc->setWorkingDirectory(workingDir);
- proc->start();
+ const QString shell = env.hasChanges() ? env.value_or("SHELL", "/bin/sh") : QString();
+
+ proc.setCommand({filePath(shell), {}});
+ proc.setTerminalMode(TerminalMode::Detached);
+ proc.setEnvironment(env);
+ proc.setWorkingDirectory(workingDir);
+ proc.start();
});
addDeviceAction({Tr::tr("Open Remote Shell"), [](const IDevice::Ptr &device, QWidget *) {
device->openTerminal(Environment(), FilePath());
}});
+ setQmlRunCommand(filePath("qml"));
+}
+
+void LinuxDevice::_setOsType(Utils::OsType osType)
+{
+ qCDebug(linuxDeviceLog) << "Setting OS type to" << osType << "for" << displayName();
+ IDevice::setOsType(osType);
}
LinuxDevice::~LinuxDevice()
@@ -1057,38 +997,9 @@ IDeviceWidget *LinuxDevice::createWidget()
return new Internal::GenericLinuxDeviceConfigurationWidget(sharedFromThis());
}
-bool LinuxDevice::canAutoDetectPorts() const
-{
- return true;
-}
-
-PortsGatheringMethod LinuxDevice::portsGatheringMethod() const
-{
- 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};
- },
-
- &Port::parseFromSedOutput
- };
-}
-
DeviceProcessList *LinuxDevice::createProcessListModel(QObject *parent) const
{
- return new LinuxDeviceProcessList(sharedFromThis(), parent);
+ return new ProcessList(sharedFromThis(), parent);
}
DeviceTester *LinuxDevice::createDeviceTester() const
@@ -1127,7 +1038,7 @@ bool LinuxDevice::handlesFile(const FilePath &filePath) const
ProcessInterface *LinuxDevice::createProcessInterface() const
{
- return new LinuxProcessInterface(this);
+ return new SshProcessInterface(sharedFromThis());
}
LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent)
@@ -1142,7 +1053,6 @@ LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent)
LinuxDevicePrivate::~LinuxDevicePrivate()
{
- qDeleteAll(m_terminals);
auto closeShell = [this] {
m_shellThread.quit();
m_shellThread.wait();
@@ -1153,6 +1063,23 @@ LinuxDevicePrivate::~LinuxDevicePrivate()
QMetaObject::invokeMethod(&m_shellThread, closeShell, Qt::BlockingQueuedConnection);
}
+void LinuxDevicePrivate::queryOsType(std::function<RunResult(const CommandLine &)> runInShell)
+{
+ const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux});
+ if (result.exitCode != 0)
+ q->_setOsType(OsTypeOtherUnix);
+ const QString osName = QString::fromUtf8(result.stdOut).trimmed();
+ if (osName == "Darwin")
+ q->_setOsType(OsTypeMac);
+ if (osName == "Linux")
+ q->_setOsType(OsTypeLinux);
+}
+
+void LinuxDevicePrivate::checkOsType()
+{
+ queryOsType([this](const CommandLine &cmd) { return runInShell(cmd); });
+}
+
// Call me with shell mutex locked
bool LinuxDevicePrivate::setupShell()
{
@@ -1166,6 +1093,10 @@ bool LinuxDevicePrivate::setupShell()
QMetaObject::invokeMethod(m_handler, [this, sshParameters] {
return m_handler->start(sshParameters);
}, Qt::BlockingQueuedConnection, &ok);
+
+ if (ok) {
+ queryOsType([this](const CommandLine &cmd) { return m_handler->runInShell(cmd); });
+ }
return ok;
}
@@ -1208,13 +1139,9 @@ static FilePaths dirsToCreate(const FilesToTransfer &files)
return sorted(std::move(dirs));
}
-static QByteArray transferCommand(const FileTransferDirection direction, bool link)
+static QByteArray transferCommand(bool link)
{
- if (direction == FileTransferDirection::Upload)
- return link ? "ln -s" : "put";
- if (direction == FileTransferDirection::Download)
- return "get";
- return {};
+ return link ? "ln -s" : "put";
}
class SshTransferInterface : public FileTransferInterface
@@ -1222,21 +1149,20 @@ class SshTransferInterface : public FileTransferInterface
Q_OBJECT
protected:
- SshTransferInterface(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
+ SshTransferInterface(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
: FileTransferInterface(setup)
- , m_device(devicePrivate->q->sharedFromThis())
- , m_devicePrivate(devicePrivate)
+ , m_device(device)
, m_process(this)
{
- m_direction = m_setup.m_files.isEmpty() ? FileTransferDirection::Invalid
- : m_setup.m_files.first().direction();
SshParameters::setupSshEnvironment(&m_process);
- connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] {
+ connect(&m_process, &Process::readyReadStandardOutput, this, [this] {
emit progress(QString::fromLocal8Bit(m_process.readAllRawStandardOutput()));
});
- connect(&m_process, &QtcProcess::done, this, &SshTransferInterface::doneImpl);
+ connect(&m_process, &Process::done, this, &SshTransferInterface::doneImpl);
}
+ IDevice::ConstPtr device() const { return m_device; }
+
bool handleError()
{
ProcessResultData resultData = m_process.resultData();
@@ -1270,10 +1196,9 @@ protected:
}
QString host() const { return m_sshParameters.host(); }
- QString userAtHost() const { return m_sshParameters.userName() + '@' + m_sshParameters.host(); }
+ QString userAtHost() const { return m_sshParameters.userAtHost(); }
- QtcProcess &process() { return m_process; }
- FileTransferDirection direction() const { return m_direction; }
+ Process &process() { return m_process; }
private:
virtual void startImpl() = 0;
@@ -1282,7 +1207,11 @@ private:
void start() final
{
m_sshParameters = displayless(m_device->sshParameters());
- if (SshSettings::connectionSharingEnabled()) {
+ const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
+ const auto linkDevice = DeviceManager::instance()->find(linkDeviceId);
+ const bool useConnectionSharing = !linkDevice && SshSettings::connectionSharingEnabled();
+
+ if (useConnectionSharing) {
m_connecting = true;
m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this);
@@ -1290,7 +1219,10 @@ private:
this, &SshTransferInterface::handleConnected);
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
this, &SshTransferInterface::handleDisconnected);
- m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
+ auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
+ QTC_ASSERT(linuxDevice, startFailed("No Linux device"); return);
+ linuxDevice->connectionAccess()
+ ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
} else {
startImpl();
}
@@ -1318,28 +1250,33 @@ private:
}
IDevice::ConstPtr m_device;
- LinuxDevicePrivate *m_devicePrivate = nullptr;
SshParameters m_sshParameters;
- FileTransferDirection m_direction = FileTransferDirection::Invalid; // helper
// ssh shared connection related
std::unique_ptr<SshConnectionHandle> m_connectionHandle;
QString m_socketFilePath;
bool m_connecting = false;
- QtcProcess m_process;
+ Process m_process;
};
class SftpTransferImpl : public SshTransferInterface
{
public:
- SftpTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
- : SshTransferInterface(setup, devicePrivate) { }
+ SftpTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
+ : SshTransferInterface(setup, device)
+ {}
private:
void startImpl() final
{
- const FilePath sftpBinary = SshSettings::sftpFilePath();
+ FilePath sftpBinary = SshSettings::sftpFilePath();
+
+ // This is a hack. We only test the last hop here.
+ const Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
+ if (const auto linkDevice = DeviceManager::instance()->find(linkDeviceId))
+ sftpBinary = linkDevice->filePath(sftpBinary.fileName()).searchInPath();
+
if (!sftpBinary.exists()) {
startFailed(Tr::tr("\"sftp\" binary \"%1\" does not exist.")
.arg(sftpBinary.toUserOutput()));
@@ -1349,35 +1286,26 @@ private:
QByteArray batchData;
const FilePaths dirs = dirsToCreate(m_setup.m_files);
- for (const FilePath &dir : dirs) {
- if (direction() == FileTransferDirection::Upload) {
- batchData += "-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n';
- } else if (direction() == FileTransferDirection::Download) {
- if (!QDir::root().mkpath(dir.path())) {
- startFailed(Tr::tr("Failed to create local directory \"%1\".")
- .arg(QDir::toNativeSeparators(dir.path())));
- return;
- }
- }
- }
+ for (const FilePath &dir : dirs)
+ batchData += "-mkdir " + ProcessArgs::quoteArgUnix(dir.path()).toLocal8Bit() + '\n';
for (const FileToTransfer &file : m_setup.m_files) {
FilePath sourceFileOrLinkTarget = file.m_source;
bool link = false;
- if (direction() == FileTransferDirection::Upload) {
- const QFileInfo fi(file.m_source.toFileInfo());
- if (fi.isSymLink()) {
- link = true;
- batchData += "-rm " + ProcessArgs::quoteArgUnix(
- file.m_target.path()).toLocal8Bit() + '\n';
- // see QTBUG-5817.
- sourceFileOrLinkTarget =
- sourceFileOrLinkTarget.withNewPath(fi.dir().relativeFilePath(fi.symLinkTarget()));
- }
- }
- batchData += transferCommand(direction(), link) + ' '
- + ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' '
- + ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n';
+
+ const QFileInfo fi(file.m_source.toFileInfo());
+ if (fi.isSymLink()) {
+ link = true;
+ batchData += "-rm " + ProcessArgs::quoteArgUnix(
+ file.m_target.path()).toLocal8Bit() + '\n';
+ // see QTBUG-5817.
+ sourceFileOrLinkTarget =
+ sourceFileOrLinkTarget.withNewPath(fi.dir().relativeFilePath(fi.symLinkTarget()));
+ }
+
+ batchData += transferCommand(link) + ' '
+ + ProcessArgs::quoteArgUnix(sourceFileOrLinkTarget.path()).toLocal8Bit() + ' '
+ + ProcessArgs::quoteArgUnix(file.m_target.path()).toLocal8Bit() + '\n';
}
process().setCommand({sftpBinary, fullConnectionOptions() << "-b" << "-" << host()});
process().setWriteData(batchData);
@@ -1390,8 +1318,8 @@ private:
class RsyncTransferImpl : public SshTransferInterface
{
public:
- RsyncTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
- : SshTransferInterface(setup, devicePrivate)
+ RsyncTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
+ : SshTransferInterface(setup, device)
{ }
private:
@@ -1442,8 +1370,7 @@ private:
if (!HostOsInfo::isWindowsHost())
return file;
- QString localFilePath = direction() == FileTransferDirection::Upload
- ? file.m_source.path() : file.m_target.path();
+ QString localFilePath = file.m_source.path();
localFilePath = '/' + localFilePath.at(0) + localFilePath.mid(2);
if (anyOf(options, [](const QString &opt) {
return opt.contains("cygwin", Qt::CaseInsensitive); })) {
@@ -1451,30 +1378,19 @@ private:
}
FileToTransfer fixedFile = file;
- if (direction() == FileTransferDirection::Upload)
- fixedFile.m_source = fixedFile.m_source.withNewPath(localFilePath);
- else
- fixedFile.m_target = fixedFile.m_target.withNewPath(localFilePath);
+ fixedFile.m_source = fixedFile.m_source.withNewPath(localFilePath);
return fixedFile;
}
QPair<QString, QString> fixPaths(const FileToTransfer &file, const QString &remoteHost) const
{
- FilePath localPath;
- FilePath remotePath;
- if (direction() == FileTransferDirection::Upload) {
- localPath = file.m_source;
- remotePath = file.m_target;
- } else {
- remotePath = file.m_source;
- localPath = file.m_target;
- }
+ FilePath localPath = file.m_source;
+ FilePath remotePath = file.m_target;
const QString local = (localPath.isDir() && localPath.path().back() != '/')
? localPath.path() + '/' : localPath.path();
const QString remote = remoteHost + ':' + remotePath.path();
- return direction() == FileTransferDirection::Upload ? qMakePair(local, remote)
- : qMakePair(remote, local);
+ return qMakePair(local, remote);
}
int m_currentIndex = 0;
@@ -1483,7 +1399,7 @@ private:
class GenericTransferImpl : public FileTransferInterface
{
public:
- GenericTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *)
+ GenericTransferImpl(const FileTransferSetupData &setup)
: FileTransferInterface(setup)
{}
@@ -1525,8 +1441,9 @@ private:
.arg(m_currentIndex)
.arg(m_fileCount)
.arg(source.toUserOutput(), target.toUserOutput()));
- if (!source.copyFile(target)) {
- result.m_errorString = Tr::tr("Failed.");
+ expected_str<void> copyResult = source.copyFile(target);
+ if (!copyResult) {
+ result.m_errorString = Tr::tr("Failed: %1").arg(copyResult.error());
result.m_exitCode = -1; // Random pick
emit done(result);
return;
@@ -1545,14 +1462,24 @@ FileTransferInterface *LinuxDevice::createFileTransferInterface(
const FileTransferSetupData &setup) const
{
switch (setup.m_method) {
- case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, d);
- case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, d);
- case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup, d);
+ case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, sharedFromThis());
+ case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, sharedFromThis());
+ case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup);
}
QTC_CHECK(false);
return {};
}
+LinuxDevicePrivate *LinuxDevice::connectionAccess() const
+{
+ return d;
+}
+
+void LinuxDevice::checkOsType()
+{
+ d->checkOsType();
+}
+
namespace Internal {
// Factory
@@ -1563,6 +1490,7 @@ LinuxDeviceFactory::LinuxDeviceFactory()
setDisplayName(Tr::tr("Remote Linux Device"));
setIcon(QIcon());
setConstructionFunction(&LinuxDevice::create);
+ setQuickCreationAllowed(true);
setCreator([] {
GenericLinuxDeviceConfigurationWizard wizard(Core::ICore::dialogParent());
if (wizard.exec() != QDialog::Accepted)