2012-10-02 09:12:39 +02:00
/****************************************************************************
2011-05-31 12:47:53 +02:00
* *
2016-01-15 14:57:40 +01:00
* * Copyright ( C ) 2016 The Qt Company Ltd .
* * Contact : https : //www.qt.io/licensing/
2011-05-31 12:47:53 +02:00
* *
2012-10-02 09:12:39 +02:00
* * This file is part of Qt Creator .
2011-05-31 12:47:53 +02:00
* *
2012-10-02 09:12:39 +02:00
* * Commercial License Usage
* * Licensees holding valid commercial Qt licenses may use this file in
* * accordance with the commercial license agreement provided with the
* * Software or , alternatively , in accordance with the terms contained in
2016-01-15 14:57:40 +01:00
* * a written agreement between you and The Qt Company . For licensing terms
* * and conditions see https : //www.qt.io/terms-conditions. For further
* * information use the contact form at https : //www.qt.io/contact-us.
2011-05-31 12:47:53 +02:00
* *
2016-01-15 14:57:40 +01:00
* * GNU General Public License Usage
* * Alternatively , this file may be used under the terms of the GNU
* * General Public License version 3 as published by the Free Software
* * Foundation with exceptions as appearing in the file LICENSE . GPL3 - EXCEPT
* * included in the packaging of this file . Please review the following
* * information to ensure the GNU General Public License requirements will
* * be met : https : //www.gnu.org/licenses/gpl-3.0.html.
2011-05-31 12:47:53 +02:00
* *
2012-10-02 09:12:39 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2012-04-24 15:49:09 +02:00
2012-07-27 13:31:13 +02:00
# include "linuxdevice.h"
2011-05-31 12:47:53 +02:00
2012-04-06 18:28:16 +02:00
# include "genericlinuxdeviceconfigurationwidget.h"
2019-02-20 19:13:28 +01:00
# include "genericlinuxdeviceconfigurationwizard.h"
2013-08-08 14:05:11 +02:00
# include "linuxdeviceprocess.h"
2013-06-27 17:12:08 +02:00
# include "linuxdevicetester.h"
2012-04-06 18:28:16 +02:00
# include "publickeydeploymentdialog.h"
2011-07-25 11:55:00 +02:00
# include "remotelinux_constants.h"
2013-09-16 15:30:30 +02:00
# include "remotelinuxsignaloperation.h"
2016-04-06 13:56:32 +02:00
# include "remotelinuxenvironmentreader.h"
2022-02-14 18:37:56 +01:00
# include "sshprocessinterface.h"
2011-07-25 11:55:00 +02:00
2019-02-20 19:13:28 +01:00
# include <coreplugin/icore.h>
2019-01-10 12:58:04 +01:00
# include <coreplugin/messagemanager.h>
2019-03-13 08:06:08 +01:00
2012-08-02 14:45:27 +02:00
# include <projectexplorer/devicesupport/sshdeviceprocesslist.h>
2019-03-13 08:06:08 +01:00
# include <projectexplorer/runcontrol.h>
2013-09-16 15:30:30 +02:00
# include <ssh/sshremoteprocessrunner.h>
2021-07-27 09:50:43 +02:00
# include <ssh/sshsettings.h>
2019-03-13 08:06:08 +01:00
2014-06-16 18:25:52 +04:00
# include <utils/algorithm.h>
2019-05-21 18:15:29 +02:00
# include <utils/environment.h>
2019-01-10 12:58:04 +01:00
# include <utils/hostosinfo.h>
2016-04-19 16:43:30 +02:00
# include <utils/port.h>
2022-02-24 18:42:47 +01:00
# include <utils/processinfo.h>
2012-02-15 14:47:45 -08:00
# include <utils/qtcassert.h>
2020-06-17 06:35:31 +02:00
# include <utils/stringutils.h>
2021-07-27 09:50:43 +02:00
# include <utils/temporaryfile.h>
# include <QDateTime>
# include <QLoggingCategory>
# include <QMutex>
# include <QRegularExpression>
2022-03-17 02:14:41 +01:00
# include <QScopeGuard>
2022-02-14 18:37:56 +01:00
# include <QTemporaryDir>
2021-07-27 09:50:43 +02:00
# include <QThread>
2022-02-14 18:37:56 +01:00
# include <QTimer>
2011-08-02 12:20:16 +02:00
2012-07-26 16:23:20 +02:00
using namespace ProjectExplorer ;
2021-07-27 09:50:43 +02:00
using namespace QSsh ;
2019-06-20 17:19:12 +02:00
using namespace Utils ;
2012-07-26 16:23:20 +02:00
2011-05-31 12:47:53 +02:00
namespace RemoteLinux {
2011-08-02 12:20:16 +02:00
2022-02-14 18:37:56 +01:00
const QByteArray s_pidMarker = " __qtc " ;
2012-07-26 17:41:52 +02:00
const char Delimiter0 [ ] = " x-- " ;
const char Delimiter1 [ ] = " --- " ;
2021-07-27 09:50:43 +02:00
static Q_LOGGING_CATEGORY ( linuxDeviceLog , " qtc.remotelinux.device " , QtWarningMsg ) ;
# define LOG(x) qCDebug(linuxDeviceLog) << x << '\n'
//#define DEBUG(x) qDebug() << x;
//#define DEBUG(x) LOG(x)
# define DEBUG(x)
2022-02-14 18:37:56 +01:00
class SshSharedConnection : public QObject
{
Q_OBJECT
public :
explicit SshSharedConnection ( const SshConnectionParameters & sshParameters , QObject * parent = nullptr ) ;
~ SshSharedConnection ( ) override ;
SshConnectionParameters sshParameters ( ) const { return m_sshParameters ; }
void ref ( ) ;
void deref ( ) ;
void makeStale ( ) ;
void connectToHost ( ) ;
void disconnectFromHost ( ) ;
QProcess : : ProcessState state ( ) const ;
QString socketFilePath ( ) const
{
QTC_ASSERT ( m_masterSocketDir , return QString ( ) ) ;
return m_masterSocketDir - > path ( ) + " /cs " ;
}
QStringList connectionOptions ( const Utils : : FilePath & binary ) const
{
return m_sshParameters . connectionOptions ( binary ) < < " -o " < < ( " ControlPath= " + socketFilePath ( ) ) ;
}
signals :
void connected ( const QString & socketFilePath ) ;
void disconnected ( const ProcessResultData & result ) ;
void autoDestructRequested ( ) ;
private :
void emitConnected ( ) ;
2022-04-22 16:02:03 +02:00
void emitError ( QProcess : : ProcessError processError , const QString & errorString ) ;
void emitDisconnected ( ) ;
2022-02-14 18:37:56 +01:00
QString fullProcessError ( const QString & sshErrorPrefix ) ;
QStringList connectionArgs ( const FilePath & binary ) const
{ return connectionOptions ( binary ) < < m_sshParameters . host ( ) ; }
const SshConnectionParameters m_sshParameters ;
std : : unique_ptr < QtcProcess > m_masterProcess ;
std : : unique_ptr < QTemporaryDir > m_masterSocketDir ;
QTimer m_timer ;
int m_ref = 0 ;
bool m_stale = false ;
2022-04-22 16:02:03 +02:00
QProcess : : ProcessState m_state = QProcess : : NotRunning ;
2022-02-14 18:37:56 +01:00
} ;
SshSharedConnection : : SshSharedConnection ( const SshConnectionParameters & sshParameters , QObject * parent )
: QObject ( parent ) , m_sshParameters ( sshParameters )
{
}
SshSharedConnection : : ~ SshSharedConnection ( )
{
QTC_CHECK ( m_ref = = 0 ) ;
disconnect ( ) ;
disconnectFromHost ( ) ;
}
void SshSharedConnection : : ref ( )
{
+ + m_ref ;
m_timer . stop ( ) ;
}
void SshSharedConnection : : deref ( )
{
QTC_ASSERT ( m_ref , return ) ;
if ( - - m_ref )
return ;
if ( m_stale ) // no one uses it
deleteLater ( ) ;
// not stale, so someone may reuse it
m_timer . start ( SshSettings : : connectionSharingTimeout ( ) * 1000 * 60 ) ;
}
void SshSharedConnection : : makeStale ( )
{
m_stale = true ;
if ( ! m_ref ) // no one uses it
deleteLater ( ) ;
}
void SshSharedConnection : : connectToHost ( )
{
if ( state ( ) ! = QProcess : : NotRunning )
return ;
const FilePath sshBinary = SshSettings : : sshFilePath ( ) ;
if ( ! sshBinary . exists ( ) ) {
emitError ( QProcess : : FailedToStart , tr ( " Cannot establish SSH connection: ssh binary "
" \" %1 \" does not exist. " ) . arg ( sshBinary . toUserOutput ( ) ) ) ;
return ;
}
m_masterSocketDir . reset ( new QTemporaryDir ) ;
if ( ! m_masterSocketDir - > isValid ( ) ) {
emitError ( QProcess : : FailedToStart , tr ( " Cannot establish SSH connection: Failed to create temporary "
" directory for control socket: %1 " )
. arg ( m_masterSocketDir - > errorString ( ) ) ) ;
m_masterSocketDir . reset ( ) ;
return ;
}
m_masterProcess . reset ( new QtcProcess ) ;
SshRemoteProcess : : setupSshEnvironment ( m_masterProcess . get ( ) ) ;
m_timer . setSingleShot ( true ) ;
connect ( & m_timer , & QTimer : : timeout , this , & SshSharedConnection : : autoDestructRequested ) ;
connect ( m_masterProcess . get ( ) , & QtcProcess : : readyReadStandardOutput , [ this ] {
const QByteArray reply = m_masterProcess - > readAllStandardOutput ( ) ;
if ( reply = = " \n " )
emitConnected ( ) ;
2022-04-22 16:02:03 +02:00
// TODO: otherwise emitError and finish master process?
2022-02-14 18:37:56 +01:00
} ) ;
2022-04-22 16:02:03 +02:00
// 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
2022-02-14 18:37:56 +01:00
connect ( m_masterProcess . get ( ) , & QtcProcess : : done , [ this ] {
const QProcess : : ProcessError error = m_masterProcess - > error ( ) ;
if ( error = = QProcess : : FailedToStart ) {
emitError ( error , fullProcessError ( tr ( " Cannot establish SSH connection. "
" Control process failed to start: " ) ) ) ;
return ;
} else if ( error ! = QProcess : : UnknownError ) {
emitError ( error , fullProcessError ( tr ( " SSH connection failure: " ) ) ) ;
return ;
}
emit disconnected ( m_masterProcess - > resultData ( ) ) ;
} ) ;
QStringList args = QStringList { " -M " , " -N " , " -o " , " ControlPersist=no " ,
" -o " , " PermitLocalCommand=yes " , // Enable local command
" -o " , " LocalCommand=echo " } // Local command is executed after successfully
// connecting to the server. "echo" will print "\n"
// on the process output if everything went fine.
< < connectionArgs ( sshBinary ) ;
if ( ! m_sshParameters . x11DisplayName . isEmpty ( ) ) {
args . prepend ( " -X " ) ;
Environment env = m_masterProcess - > environment ( ) ;
env . set ( " DISPLAY " , m_sshParameters . x11DisplayName ) ;
m_masterProcess - > setEnvironment ( env ) ;
}
m_masterProcess - > setCommand ( CommandLine ( sshBinary , args ) ) ;
m_masterProcess - > start ( ) ;
}
void SshSharedConnection : : disconnectFromHost ( )
{
m_masterProcess . reset ( ) ;
m_masterSocketDir . reset ( ) ;
}
QProcess : : ProcessState SshSharedConnection : : state ( ) const
{
2022-04-22 16:02:03 +02:00
return m_state ;
2022-02-14 18:37:56 +01:00
}
2022-04-22 16:02:03 +02:00
void SshSharedConnection : : emitConnected ( )
{
m_state = QProcess : : Running ;
emit connected ( socketFilePath ( ) ) ;
}
2022-02-14 18:37:56 +01:00
void SshSharedConnection : : emitError ( QProcess : : ProcessError error , const QString & errorString )
{
2022-04-22 16:02:03 +02:00
m_state = QProcess : : NotRunning ;
2022-02-14 18:37:56 +01:00
emit disconnected ( { 0 , QProcess : : NormalExit , error , errorString } ) ;
}
2022-04-22 16:02:03 +02:00
void SshSharedConnection : : emitDisconnected ( )
2022-02-14 18:37:56 +01:00
{
2022-04-22 16:02:03 +02:00
m_state = QProcess : : NotRunning ;
emit disconnected ( m_masterProcess - > resultData ( ) ) ;
2022-02-14 18:37:56 +01:00
}
QString SshSharedConnection : : fullProcessError ( const QString & sshErrorPrefix )
{
QString error ;
if ( m_masterProcess - > exitStatus ( ) ! = QProcess : : NormalExit )
error = m_masterProcess - > errorString ( ) ;
const QByteArray stdErr = m_masterProcess - > readAllStandardError ( ) ;
if ( ! stdErr . isEmpty ( ) ) {
if ( ! error . isEmpty ( ) )
error . append ( ' \n ' ) ;
error . append ( QString : : fromLocal8Bit ( stdErr ) ) ;
}
QString fullError = sshErrorPrefix ;
if ( ! error . isEmpty ( ) )
fullError . append ( ' \n ' ) . append ( error ) ;
return fullError ;
}
// SshConnectionHandle
class SshConnectionHandle : public QObject
{
Q_OBJECT
public :
SshConnectionHandle ( const IDevice : : ConstPtr & device ) : m_device ( device ) { }
~ SshConnectionHandle ( ) override { emit detachFromSharedConnection ( ) ; }
signals :
// direction: connection -> caller
void connected ( const QString & socketFilePath ) ;
void disconnected ( const ProcessResultData & result ) ;
// direction: caller -> connection
void detachFromSharedConnection ( ) ;
private :
// Store the IDevice::ConstPtr in order to extend the lifetime of device for as long
// as this object is alive.
IDevice : : ConstPtr m_device ;
} ;
2012-07-26 17:41:52 +02:00
static QString visualizeNull ( QString s )
{
return s . replace ( QLatin1Char ( ' \0 ' ) , QLatin1String ( " <null> " ) ) ;
}
2012-08-02 14:45:27 +02:00
class LinuxDeviceProcessList : public SshDeviceProcessList
2012-08-01 16:26:27 +02:00
{
2012-08-02 14:45:27 +02:00
public :
LinuxDeviceProcessList ( const IDevice : : ConstPtr & device , QObject * parent )
: SshDeviceProcessList ( device , parent )
{
}
private :
2018-11-24 17:06:01 +01:00
QString listProcessesCommandLine ( ) const override
2012-08-02 14:45:27 +02:00
{
return QString : : fromLatin1 (
2012-08-01 16:26:27 +02:00
" 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; "
2014-05-02 14:32:00 +02:00
" printf '%1''%2'; "
2012-11-26 14:41:39 +02:00
" done " ) . arg ( QLatin1String ( Delimiter0 ) ) . arg ( QLatin1String ( Delimiter1 ) ) ;
2012-08-02 14:45:27 +02:00
}
2012-08-01 16:26:27 +02:00
2022-02-24 18:42:47 +01:00
QList < ProcessInfo > buildProcessList ( const QString & listProcessesReply ) const override
2012-08-02 14:45:27 +02:00
{
2022-02-24 18:42:47 +01:00
QList < ProcessInfo > processes ;
2012-08-02 14:45:27 +02:00
const QStringList lines = listProcessesReply . split ( QString : : fromLatin1 ( Delimiter0 )
2020-07-21 10:19:36 +02:00
+ QString : : fromLatin1 ( Delimiter1 ) , Qt : : SkipEmptyParts ) ;
2022-02-24 18:42:47 +01:00
for ( const QString & line : lines ) {
2012-08-02 14:45:27 +02:00
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 ,
2020-07-21 15:47:35 +02:00
int ( elements . count ( ) ) , qPrintable ( visualizeNull ( line ) ) ) ;
2012-08-01 16:26:27 +02:00
continue ;
2012-08-02 14:45:27 +02:00
}
bool ok ;
2020-09-18 13:15:18 +02:00
const int pid = elements . first ( ) . mid ( 6 ) . toInt ( & ok ) ;
2012-08-02 14:45:27 +02:00
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 ( ' ] ' ) ;
}
2022-02-24 18:42:47 +01:00
ProcessInfo process ;
process . processId = pid ;
process . commandLine = command ;
process . executable = elements . at ( 3 ) ;
2012-08-02 14:45:27 +02:00
processes . append ( process ) ;
2012-08-01 16:26:27 +02:00
}
2014-06-16 18:25:52 +04:00
Utils : : sort ( processes ) ;
2012-08-02 14:45:27 +02:00
return processes ;
2012-08-01 16:26:27 +02:00
}
2012-08-02 14:45:27 +02:00
} ;
2012-08-01 16:26:27 +02:00
2015-02-03 23:51:02 +02:00
class LinuxPortsGatheringMethod : public PortsGatheringMethod
2012-08-01 16:26:27 +02:00
{
2022-01-31 18:08:39 +01:00
CommandLine commandLine ( QAbstractSocket : : NetworkLayerProtocol protocol ) const override
2012-08-01 16:26:27 +02:00
{
2016-04-15 12:23:23 +02:00
// 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
2022-01-31 18:08:39 +01:00
return { " sed " , " -e 's/.*: [[:xdigit:]]*: \\ ([[:xdigit:]] \\ {4 \\ } \\ ).*/ \\ 1/g' /proc/net/tcp* " ,
CommandLine : : Raw } ;
2012-08-01 16:26:27 +02:00
}
2018-11-24 17:06:01 +01:00
QList < Utils : : Port > usedPorts ( const QByteArray & output ) const override
2012-08-01 16:26:27 +02:00
{
2016-04-19 16:43:30 +02:00
QList < Utils : : Port > ports ;
2012-08-01 16:26:27 +02:00
QList < QByteArray > portStrings = output . split ( ' \n ' ) ;
foreach ( const QByteArray & portString , portStrings ) {
2016-04-15 12:23:23 +02:00
if ( portString . size ( ) ! = 4 )
2012-08-01 16:26:27 +02:00
continue ;
bool ok ;
2016-04-19 16:43:30 +02:00
const Utils : : Port port ( portString . toInt ( & ok , 16 ) ) ;
2012-08-01 16:26:27 +02:00
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 ;
}
} ;
2022-02-14 18:37:56 +01:00
// LinuxDevicePrivate
class ShellThreadHandler ;
class LinuxDevicePrivate
{
public :
explicit LinuxDevicePrivate ( LinuxDevice * parent ) ;
~ LinuxDevicePrivate ( ) ;
bool setupShell ( ) ;
bool runInShell ( const CommandLine & cmd , const QByteArray & data = { } ) ;
QByteArray outputForRunInShell ( const QString & cmd ) ;
QByteArray outputForRunInShell ( const CommandLine & cmd ) ;
void attachToSharedConnection ( SshConnectionHandle * connectionHandle ,
const SshConnectionParameters & sshParameters ) ;
LinuxDevice * q = nullptr ;
QThread m_shellThread ;
ShellThreadHandler * m_handler = nullptr ;
mutable QMutex m_shellMutex ;
2022-04-28 21:11:20 +02:00
QList < QtcProcess * > m_terminals ;
2022-02-14 18:37:56 +01:00
} ;
// SshProcessImpl
class SshProcessInterfacePrivate : public QObject
{
Q_OBJECT
public :
SshProcessInterfacePrivate ( SshProcessInterface * sshInterface , LinuxDevicePrivate * devicePrivate ) ;
void start ( ) ;
void sendControlSignal ( ControlSignal controlSignal ) ;
void handleConnected ( const QString & socketFilePath ) ;
void handleDisconnected ( const ProcessResultData & result ) ;
void handleStarted ( ) ;
void handleDone ( ) ;
void handleReadyReadStandardOutput ( ) ;
void handleReadyReadStandardError ( ) ;
void clearForStart ( ) ;
void doStart ( ) ;
CommandLine fullLocalCommandLine ( ) const ;
SshProcessInterface * q = nullptr ;
qint64 m_processId = 0 ;
QtcProcess m_process ;
LinuxDevicePrivate * m_devicePrivate = nullptr ;
// Store the IDevice::ConstPtr in order to extend the lifetime of device for as long
// as this object is alive.
IDevice : : ConstPtr m_device ;
std : : unique_ptr < SshConnectionHandle > m_connectionHandle ;
QString m_socketFilePath ;
SshConnectionParameters m_sshParameters ;
bool m_connecting = false ;
ProcessResultData m_result ;
} ;
SshProcessInterface : : SshProcessInterface ( const LinuxDevice * linuxDevice )
: d ( new SshProcessInterfacePrivate ( this , linuxDevice - > d ) )
{
}
SshProcessInterface : : ~ SshProcessInterface ( )
{
delete d ;
}
void SshProcessInterface : : handleStarted ( qint64 processId )
{
emitStarted ( processId ) ;
}
void SshProcessInterface : : handleReadyReadStandardOutput ( const QByteArray & outputData )
{
emit readyRead ( outputData , { } ) ;
}
void SshProcessInterface : : emitStarted ( qint64 processId )
{
d - > m_processId = processId ;
emit started ( processId ) ;
}
void SshProcessInterface : : killIfRunning ( )
{
if ( d - > m_process . state ( ) = = QProcess : : Running )
sendControlSignal ( ControlSignal : : Kill ) ;
}
void SshProcessInterface : : start ( )
{
d - > start ( ) ;
}
qint64 SshProcessInterface : : write ( const QByteArray & data )
{
2022-04-22 16:02:03 +02:00
return d - > m_process . writeRaw ( data ) ;
2022-02-14 18:37:56 +01:00
}
void SshProcessInterface : : sendControlSignal ( ControlSignal controlSignal )
{
d - > sendControlSignal ( controlSignal ) ;
}
bool SshProcessInterface : : waitForStarted ( int msecs )
{
Q_UNUSED ( msecs )
QTC_CHECK ( false ) ;
return false ;
}
bool SshProcessInterface : : waitForReadyRead ( int msecs )
{
Q_UNUSED ( msecs )
QTC_CHECK ( false ) ;
return false ;
}
bool SshProcessInterface : : waitForFinished ( int msecs )
{
Q_UNUSED ( msecs )
QTC_CHECK ( false ) ;
return false ;
}
class LinuxProcessImpl final : public SshProcessInterface
{
Q_OBJECT
public :
LinuxProcessImpl ( const LinuxDevice * linuxDevice ) ;
~ LinuxProcessImpl ( ) { killIfRunning ( ) ; }
private :
void handleStarted ( qint64 processId ) final ;
void handleReadyReadStandardOutput ( const QByteArray & outputData ) final ;
QString fullCommandLine ( const Utils : : CommandLine & commandLine ) const final ;
QByteArray m_output ;
bool m_pidParsed = false ;
} ;
LinuxProcessImpl : : LinuxProcessImpl ( const LinuxDevice * linuxDevice )
: SshProcessInterface ( linuxDevice )
{
}
QString LinuxProcessImpl : : fullCommandLine ( const CommandLine & commandLine ) const
{
CommandLine cmd ;
2022-04-28 21:11:20 +02:00
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 ) ;
}
2022-02-14 18:37:56 +01:00
}
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_remoteEnvironment ;
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 " ) ;
2022-04-28 21:11:20 +02:00
if ( ! commandLine . isEmpty ( ) )
cmd . addCommandLineAsArgs ( commandLine , CommandLine : : Raw ) ;
2022-02-14 18:37:56 +01:00
return cmd . arguments ( ) ;
}
void LinuxProcessImpl : : 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 )
return ;
2022-04-22 16:02:03 +02:00
m_pidParsed = true ;
2022-02-14 18:37:56 +01:00
emitStarted ( processId ) ;
}
void LinuxProcessImpl : : handleReadyReadStandardOutput ( const QByteArray & outputData )
{
2022-04-22 16:02:03 +02:00
if ( m_pidParsed ) {
2022-02-14 18:37:56 +01:00
emit 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 ;
const int startMarkerOffset = m_output . indexOf ( s_pidMarker ) ;
if ( startMarkerOffset = = endMarkerOffset ) // Only theoretically possible.
return ;
const int pidStart = startMarkerOffset + s_pidMarker . length ( ) ;
const QByteArray pidString = m_output . mid ( pidStart , endMarkerOffset - pidStart ) ;
m_pidParsed = true ;
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 ( ) ) ;
emitStarted ( processId ) ;
if ( ! m_output . isEmpty ( ) )
emit readyRead ( m_output , { } ) ;
m_output . clear ( ) ;
}
SshProcessInterfacePrivate : : SshProcessInterfacePrivate ( SshProcessInterface * sshInterface ,
LinuxDevicePrivate * devicePrivate )
: QObject ( sshInterface )
, q ( sshInterface )
2022-05-02 14:00:42 +02:00
, m_process ( this )
2022-02-14 18:37:56 +01:00
, m_devicePrivate ( devicePrivate )
, m_device ( m_devicePrivate - > q - > sharedFromThis ( ) )
{
connect ( & m_process , & QtcProcess : : started , this , & SshProcessInterfacePrivate : : handleStarted ) ;
connect ( & m_process , & QtcProcess : : done , this , & SshProcessInterfacePrivate : : handleDone ) ;
connect ( & m_process , & QtcProcess : : readyReadStandardOutput ,
this , & SshProcessInterfacePrivate : : handleReadyReadStandardOutput ) ;
connect ( & m_process , & QtcProcess : : readyReadStandardError ,
this , & SshProcessInterfacePrivate : : handleReadyReadStandardError ) ;
}
void SshProcessInterfacePrivate : : start ( )
{
clearForStart ( ) ;
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 ( ) ) {
m_connecting = true ;
m_connectionHandle . reset ( new SshConnectionHandle ( m_devicePrivate - > q - > sharedFromThis ( ) ) ) ;
2022-05-02 14:00:42 +02:00
m_connectionHandle - > setParent ( this ) ;
2022-02-14 18:37:56 +01:00
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 ) ;
} else {
doStart ( ) ;
}
}
QString SshProcessInterface : : pidArgumentForKill ( ) const
{
return QString : : fromLatin1 ( " -%1 %1 " ) . arg ( d - > m_processId ) ;
}
void SshProcessInterfacePrivate : : sendControlSignal ( ControlSignal controlSignal )
{
QTC_ASSERT ( controlSignal ! = ControlSignal : : KickOff , return ) ;
// TODO: In case if m_processId == 0 try sending a signal based on process name.
const QString args = QString : : fromLatin1 ( " -%1 %2 " )
2022-04-28 16:15:46 +02:00
. arg ( ProcessInterface : : controlSignalToInt ( controlSignal ) ) . arg ( q - > pidArgumentForKill ( ) ) ;
2022-02-14 18:37:56 +01:00
CommandLine command = { " kill " , args , CommandLine : : Raw } ;
// Note: This blocking call takes up to 2 ms for local remote.
m_devicePrivate - > runInShell ( command ) ;
}
void SshProcessInterfacePrivate : : handleConnected ( const QString & socketFilePath )
{
m_connecting = false ;
m_socketFilePath = socketFilePath ;
doStart ( ) ;
}
void SshProcessInterfacePrivate : : handleDisconnected ( const ProcessResultData & result )
{
ProcessResultData resultData = result ;
if ( m_connecting )
resultData . m_error = QProcess : : FailedToStart ;
m_connecting = false ;
if ( m_connectionHandle ) // TODO: should it disconnect from signals first?
m_connectionHandle . release ( ) - > deleteLater ( ) ;
if ( resultData . m_error ! = QProcess : : UnknownError & & m_process . state ( ) ! = QProcess : : NotRunning )
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 ( )
{
m_connectionHandle . reset ( ) ;
emit q - > done ( m_process . resultData ( ) ) ;
}
void SshProcessInterfacePrivate : : handleReadyReadStandardOutput ( )
{
q - > handleReadyReadStandardOutput ( m_process . readAllStandardOutput ( ) ) ; // by default emits signal. linux impl does custom parsing for processId and emits delayed start() - only when terminal is off
}
void SshProcessInterfacePrivate : : handleReadyReadStandardError ( )
{
emit q - > readyRead ( { } , m_process . readAllStandardError ( ) ) ;
}
void SshProcessInterfacePrivate : : clearForStart ( )
{
m_result = { } ;
}
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 ) ;
2022-04-22 16:02:03 +02:00
m_process . setWriteData ( q - > m_setup . m_writeData ) ;
2022-02-14 18:37:56 +01:00
// TODO: what about other fields from m_setup?
SshRemoteProcess : : setupSshEnvironment ( & m_process ) ;
if ( ! m_sshParameters . x11DisplayName . isEmpty ( ) ) {
Environment env = m_process . environment ( ) ;
// Note: it seems this is no-op when shared connection is used.
// In this case the display is taken from master process.
env . set ( " DISPLAY " , m_sshParameters . x11DisplayName ) ;
m_process . setEnvironment ( env ) ;
}
m_process . setCommand ( fullLocalCommandLine ( ) ) ;
m_process . start ( ) ;
}
CommandLine SshProcessInterfacePrivate : : fullLocalCommandLine ( ) const
{
Utils : : CommandLine cmd { SshSettings : : sshFilePath ( ) } ;
if ( ! m_sshParameters . x11DisplayName . isEmpty ( ) )
cmd . addArg ( " -X " ) ;
if ( q - > m_setup . m_terminalMode ! = TerminalMode : : Off )
cmd . addArg ( " -tt " ) ;
cmd . addArg ( " -q " ) ;
QStringList options = m_sshParameters . connectionOptions ( SshSettings : : sshFilePath ( ) ) ;
if ( ! m_socketFilePath . isEmpty ( ) )
options < < " -o " < < ( " ControlPath= " + m_socketFilePath ) ;
options < < m_sshParameters . host ( ) ;
cmd . addArgs ( options ) ;
CommandLine remoteWithLocalPath = q - > m_setup . m_commandLine ;
FilePath executable ;
executable . setPath ( remoteWithLocalPath . executable ( ) . path ( ) ) ;
remoteWithLocalPath . setExecutable ( executable ) ;
cmd . addArg ( q - > fullCommandLine ( remoteWithLocalPath ) ) ;
return cmd ;
}
2021-07-27 09:50:43 +02:00
// ShellThreadHandler
2022-02-14 18:37:56 +01:00
static SshConnectionParameters displayless ( const SshConnectionParameters & sshParameters )
{
SshConnectionParameters parameters = sshParameters ;
parameters . x11DisplayName . clear ( ) ;
return parameters ;
}
2021-07-27 09:50:43 +02:00
class ShellThreadHandler : public QObject
2012-04-06 18:28:16 +02:00
{
2021-07-27 09:50:43 +02:00
public :
~ ShellThreadHandler ( )
{
2022-04-22 16:02:03 +02:00
closeShell ( ) ;
2022-02-14 18:37:56 +01:00
qDeleteAll ( m_connections ) ;
2021-07-27 09:50:43 +02:00
}
2022-04-22 16:02:03 +02:00
void closeShell ( )
2022-01-13 12:56:24 +01:00
{
2022-04-22 16:02:03 +02:00
if ( m_shell & & m_shell - > isRunning ( ) ) {
m_shell - > write ( " exit \n " ) ;
m_shell - > waitForFinished ( - 1 ) ;
}
2022-02-14 18:37:56 +01:00
m_shell . reset ( ) ;
2022-01-13 12:56:24 +01:00
}
2022-04-22 16:02:03 +02:00
// Call me with shell mutex locked
2021-07-27 09:50:43 +02:00
bool start ( const SshConnectionParameters & parameters )
{
2022-04-22 16:02:03 +02:00
closeShell ( ) ;
setSshParameters ( parameters ) ;
2022-04-28 15:42:09 +02:00
m_shell . reset ( new QtcProcess ) ;
SshRemoteProcess : : setupSshEnvironment ( m_shell . get ( ) ) ;
const FilePath sshPath = SshSettings : : sshFilePath ( ) ;
CommandLine cmd { sshPath } ;
cmd . addArg ( " -q " ) ;
cmd . addArgs ( m_displaylessSshParameters . connectionOptions ( sshPath )
< < m_displaylessSshParameters . host ( ) ) ;
cmd . addArg ( " /bin/sh " ) ;
m_shell - > setCommand ( cmd ) ;
2022-02-18 10:06:56 +01:00
m_shell - > setProcessMode ( ProcessMode : : Writer ) ;
2022-04-22 16:02:03 +02:00
m_shell - > setWriteData ( " echo \n " ) ;
2021-07-27 09:50:43 +02:00
m_shell - > start ( ) ;
2022-01-13 12:56:24 +01:00
2022-04-22 16:02:03 +02:00
if ( ! m_shell - > waitForStarted ( ) | | ! m_shell - > waitForReadyRead ( )
| | m_shell - > readAllStandardOutput ( ) ! = " \n " ) {
closeShell ( ) ;
qCDebug ( linuxDeviceLog ) < < " Failed to connect to " < < m_displaylessSshParameters . host ( ) ;
return false ;
}
2022-01-13 12:56:24 +01:00
return true ;
2021-07-27 09:50:43 +02:00
}
2022-04-22 16:02:03 +02:00
// Call me with shell mutex locked
2021-07-27 09:50:43 +02:00
bool runInShell ( const CommandLine & cmd , const QByteArray & data = { } )
{
QTC_ASSERT ( m_shell , return false ) ;
2022-03-16 20:21:49 +01:00
QTC_CHECK ( m_shell - > readAllStandardOutput ( ) . isNull ( ) ) ; // clean possible left-overs
2022-03-17 02:14:41 +01:00
QTC_CHECK ( m_shell - > readAllStandardError ( ) . isNull ( ) ) ; // clean possible left-overs
2021-07-27 09:50:43 +02:00
2022-04-27 14:19:49 +02:00
QString prefix ;
if ( ! data . isEmpty ( ) )
prefix = " echo ' " + QString : : fromUtf8 ( data . toBase64 ( ) ) + " ' | base64 -d | " ;
const QString suffix = " > /dev/null 2>&1 \n echo $? \n " ;
const QString command = prefix + cmd . toUserOutput ( ) + suffix ;
2022-03-16 20:21:49 +01:00
m_shell - > write ( command ) ;
2021-07-27 09:50:43 +02:00
DEBUG ( " RUN1 " < < cmd . toUserOutput ( ) ) ;
m_shell - > waitForReadyRead ( ) ;
const QByteArray output = m_shell - > readAllStandardOutput ( ) ;
DEBUG ( " GOT1 " < < output ) ;
bool ok = false ;
const int result = output . toInt ( & ok ) ;
LOG ( " Run command in shell: " < < cmd . toUserOutput ( ) < < " result: " < < output < < " ==> " < < result ) ;
2022-03-16 20:21:49 +01:00
QTC_ASSERT ( ok , return false ) ;
return ! result ;
2021-07-27 09:50:43 +02:00
}
2022-04-22 16:02:03 +02:00
// Call me with shell mutex locked
2022-02-03 18:09:06 +01:00
QByteArray outputForRunInShell ( const QString & cmd )
2021-07-27 09:50:43 +02:00
{
QTC_ASSERT ( m_shell , return { } ) ;
2022-03-16 20:21:49 +01:00
QTC_CHECK ( m_shell - > readAllStandardOutput ( ) . isNull ( ) ) ; // clean possible left-overs
2022-03-17 02:14:41 +01:00
QTC_CHECK ( m_shell - > readAllStandardError ( ) . isNull ( ) ) ; // clean possible left-overs
auto cleanup = qScopeGuard ( [ this ] { m_shell - > readAllStandardOutput ( ) ; } ) ; // clean on assert
2021-07-27 09:50:43 +02:00
2022-04-27 14:19:49 +02:00
const QString suffix = " 2> /dev/null \n echo $? 1>&2 \n " ;
const QString command = cmd + suffix ;
2021-07-27 09:50:43 +02:00
2022-03-17 02:14:41 +01:00
m_shell - > write ( command ) ;
DEBUG ( " RUN2 " < < cmd . toUserOutput ( ) ) ;
while ( true ) {
2021-07-27 09:50:43 +02:00
m_shell - > waitForReadyRead ( ) ;
2022-03-17 02:14:41 +01:00
const QByteArray error = m_shell - > readAllStandardError ( ) ;
if ( ! error . isNull ( ) ) {
bool ok = false ;
const int result = error . toInt ( & ok ) ;
QTC_ASSERT ( ok , return { } ) ;
QTC_ASSERT ( ! result , return { } ) ;
break ;
}
2021-07-27 09:50:43 +02:00
}
2022-03-17 02:14:41 +01:00
const QByteArray output = m_shell - > readAllStandardOutput ( ) ;
2021-07-27 09:50:43 +02:00
DEBUG ( " GOT2 " < < output ) ;
2022-01-04 15:46:55 +01:00
LOG ( " Run command in shell: " < < cmd < < " output size: " < < output . size ( ) ) ;
2022-02-03 18:09:06 +01:00
return output ;
2021-07-27 09:50:43 +02:00
}
2022-02-14 18:37:56 +01:00
void setSshParameters ( const SshConnectionParameters & sshParameters )
{
2022-04-22 16:02:03 +02:00
QMutexLocker locker ( & m_mutex ) ;
2022-02-14 18:37:56 +01:00
const SshConnectionParameters displaylessSshParameters = displayless ( sshParameters ) ;
2021-07-27 09:50:43 +02:00
2022-02-14 18:37:56 +01:00
if ( m_displaylessSshParameters = = displaylessSshParameters )
return ;
2021-07-27 09:50:43 +02:00
2022-02-14 18:37:56 +01:00
// If displayless sshParameters don't match the old connections' sshParameters, then stale
// old connections (don't delete, as the last deref() to each one will delete them).
for ( SshSharedConnection * connection : qAsConst ( m_connections ) )
connection - > makeStale ( ) ;
m_connections . clear ( ) ;
m_displaylessSshParameters = displaylessSshParameters ;
}
2021-07-27 09:50:43 +02:00
2022-02-14 18:37:56 +01:00
QString attachToSharedConnection ( SshConnectionHandle * connectionHandle ,
const SshConnectionParameters & sshParameters )
{
setSshParameters ( sshParameters ) ;
2021-07-27 09:50:43 +02:00
2022-02-14 18:37:56 +01:00
SshSharedConnection * matchingConnection = nullptr ;
// Find the matching connection
for ( SshSharedConnection * connection : qAsConst ( m_connections ) ) {
if ( connection - > sshParameters ( ) = = sshParameters ) {
matchingConnection = connection ;
break ;
}
}
// If no matching connection has been found, create a new one
if ( ! matchingConnection ) {
matchingConnection = new SshSharedConnection ( sshParameters ) ;
connect ( matchingConnection , & SshSharedConnection : : autoDestructRequested ,
this , [ this , matchingConnection ] {
// This slot is just for removing the matchingConnection from the connection list.
// The SshSharedConnection could have deleted itself otherwise.
m_connections . removeOne ( matchingConnection ) ;
matchingConnection - > deleteLater ( ) ;
} ) ;
m_connections . append ( matchingConnection ) ;
}
matchingConnection - > ref ( ) ;
connect ( matchingConnection , & SshSharedConnection : : connected ,
connectionHandle , & SshConnectionHandle : : connected ) ;
connect ( matchingConnection , & SshSharedConnection : : disconnected ,
connectionHandle , & SshConnectionHandle : : disconnected ) ;
connect ( connectionHandle , & SshConnectionHandle : : detachFromSharedConnection ,
matchingConnection , & SshSharedConnection : : deref ,
Qt : : BlockingQueuedConnection ) ; // Ensure the signal is delivered before sender's
// destruction, otherwise we may get out of sync
// with ref count.
if ( matchingConnection - > state ( ) = = QProcess : : Running )
return matchingConnection - > socketFilePath ( ) ;
if ( matchingConnection - > state ( ) = = QProcess : : NotRunning )
matchingConnection - > connectToHost ( ) ;
return { } ;
}
2022-04-22 16:02:03 +02:00
// Call me with shell mutex locked, called from other thread
bool isRunning ( const SshConnectionParameters & sshParameters ) const
{
if ( ! m_shell )
return false ;
QMutexLocker locker ( & m_mutex ) ;
if ( m_displaylessSshParameters ! = displayless ( sshParameters ) )
return false ;
return true ;
}
2022-02-14 18:37:56 +01:00
private :
2022-04-22 16:02:03 +02:00
mutable QMutex m_mutex ;
2022-02-14 18:37:56 +01:00
SshConnectionParameters m_displaylessSshParameters ;
QList < SshSharedConnection * > m_connections ;
2022-04-22 16:02:03 +02:00
std : : unique_ptr < QtcProcess > m_shell ;
2021-07-27 09:50:43 +02:00
} ;
// LinuxDevice
2012-04-06 18:28:16 +02:00
2019-01-15 08:51:13 +01:00
LinuxDevice : : LinuxDevice ( )
2021-07-27 09:50:43 +02:00
: d ( new LinuxDevicePrivate ( this ) )
2019-01-11 14:50:08 +01:00
{
2019-06-19 13:28:14 +02:00
setDisplayType ( tr ( " Generic Linux " ) ) ;
2019-08-01 14:30:10 +02:00
setDefaultDisplayName ( tr ( " Generic Linux Device " ) ) ;
2019-08-16 10:17:45 +02:00
setOsType ( OsTypeLinux ) ;
2019-01-11 14:50:08 +01:00
addDeviceAction ( { tr ( " Deploy Public Key... " ) , [ ] ( const IDevice : : Ptr & device , QWidget * parent ) {
if ( auto d = PublicKeyDeploymentDialog : : createDialog ( device , parent ) ) {
d - > exec ( ) ;
delete d ;
}
} } ) ;
2021-08-02 18:02:10 +02:00
setOpenTerminal ( [ this ] ( const Environment & env , const FilePath & workingDir ) {
2022-04-28 21:11:20 +02:00
QtcProcess * const proc = new QtcProcess ;
d - > m_terminals . append ( proc ) ;
QObject : : connect ( proc , & QtcProcess : : done , [ this , proc ] {
2022-04-14 13:02:16 +02:00
if ( proc - > error ( ) ! = QProcess : : UnknownError ) {
const QString errorString = proc - > errorString ( ) ;
QString message ;
if ( proc - > error ( ) = = QProcess : : FailedToStart )
message = tr ( " Error starting remote shell. " ) ;
else if ( errorString . isEmpty ( ) )
message = tr ( " Error running remote shell. " ) ;
else
message = tr ( " Error running remote shell: %1 " ) . arg ( errorString ) ;
Core : : MessageManager : : writeDisrupting ( message ) ;
2019-06-13 17:11:00 +02:00
}
proc - > deleteLater ( ) ;
2022-04-28 21:11:20 +02:00
d - > m_terminals . removeOne ( proc ) ;
2019-06-13 17:11:00 +02:00
} ) ;
2022-04-28 21:11:20 +02:00
proc - > setCommand ( { mapToGlobalPath ( { } ) , { } } ) ;
2022-02-18 00:56:14 +01:00
proc - > setTerminalMode ( TerminalMode : : On ) ;
2022-02-11 17:38:16 +01:00
proc - > setEnvironment ( env ) ;
2022-04-28 21:11:20 +02:00
proc - > setRemoteEnvironment ( env ) ;
2022-02-11 17:38:16 +01:00
proc - > setWorkingDirectory ( workingDir ) ;
proc - > start ( ) ;
2019-06-13 17:11:00 +02:00
} ) ;
2022-03-18 11:49:31 +01:00
addDeviceAction ( { tr ( " Open Remote Shell " ) , [ ] ( const IDevice : : Ptr & device , QWidget * ) {
device - > openTerminal ( Environment ( ) , FilePath ( ) ) ;
} } ) ;
2019-01-11 14:50:08 +01:00
}
2021-07-27 09:50:43 +02:00
LinuxDevice : : ~ LinuxDevice ( )
{
delete d ;
}
IDeviceWidget * LinuxDevice : : createWidget ( )
{
return new GenericLinuxDeviceConfigurationWidget ( sharedFromThis ( ) ) ;
}
2022-02-28 12:25:26 +01:00
QtcProcess * LinuxDevice : : createProcess ( QObject * parent ) const
2013-08-08 14:05:11 +02:00
{
return new LinuxDeviceProcess ( sharedFromThis ( ) , parent ) ;
}
2013-04-18 09:31:22 +02:00
bool LinuxDevice : : canAutoDetectPorts ( ) const
{
return true ;
}
2012-08-01 16:26:27 +02:00
PortsGatheringMethod : : Ptr LinuxDevice : : portsGatheringMethod ( ) const
2012-07-26 17:41:52 +02:00
{
2012-08-01 16:26:27 +02:00
return LinuxPortsGatheringMethod : : Ptr ( new LinuxPortsGatheringMethod ) ;
2012-07-26 17:41:52 +02:00
}
2012-08-02 14:45:27 +02:00
DeviceProcessList * LinuxDevice : : createProcessListModel ( QObject * parent ) const
{
return new LinuxDeviceProcessList ( sharedFromThis ( ) , parent ) ;
}
2013-06-27 17:12:08 +02:00
DeviceTester * LinuxDevice : : createDeviceTester ( ) const
2013-05-30 13:53:28 +02:00
{
return new GenericLinuxDeviceTester ;
}
2013-09-16 15:30:30 +02:00
DeviceProcessSignalOperation : : Ptr LinuxDevice : : signalOperation ( ) const
{
return DeviceProcessSignalOperation : : Ptr ( new RemoteLinuxSignalOperation ( sshParameters ( ) ) ) ;
}
2016-04-06 13:56:32 +02:00
class LinuxDeviceEnvironmentFetcher : public DeviceEnvironmentFetcher
{
public :
LinuxDeviceEnvironmentFetcher ( const IDevice : : ConstPtr & device )
: m_reader ( device )
{
connect ( & m_reader , & Internal : : RemoteLinuxEnvironmentReader : : finished ,
this , & LinuxDeviceEnvironmentFetcher : : readerFinished ) ;
connect ( & m_reader , & Internal : : RemoteLinuxEnvironmentReader : : error ,
this , & LinuxDeviceEnvironmentFetcher : : readerError ) ;
}
private :
void start ( ) override { m_reader . start ( ) ; }
void readerFinished ( ) { emit finished ( m_reader . remoteEnvironment ( ) , true ) ; }
void readerError ( ) { emit finished ( Utils : : Environment ( ) , false ) ; }
Internal : : RemoteLinuxEnvironmentReader m_reader ;
} ;
DeviceEnvironmentFetcher : : Ptr LinuxDevice : : environmentFetcher ( ) const
{
return DeviceEnvironmentFetcher : : Ptr ( new LinuxDeviceEnvironmentFetcher ( sharedFromThis ( ) ) ) ;
}
2021-07-27 09:50:43 +02:00
QString LinuxDevice : : userAtHost ( ) const
{
2022-02-03 13:20:15 +01:00
return sshParameters ( ) . userAtHost ( ) ;
2021-07-27 09:50:43 +02:00
}
2022-01-04 18:06:42 +01:00
FilePath LinuxDevice : : mapToGlobalPath ( const FilePath & pathOnDevice ) const
{
if ( pathOnDevice . needsDevice ( ) ) {
// Already correct form, only sanity check it's ours...
QTC_CHECK ( handlesFile ( pathOnDevice ) ) ;
return pathOnDevice ;
}
FilePath result ;
2022-02-14 18:37:56 +01:00
result . setScheme ( " device " ) ;
result . setHost ( id ( ) . toString ( ) ) ;
2022-01-04 18:06:42 +01:00
result . setPath ( pathOnDevice . path ( ) ) ;
return result ;
}
2021-07-27 09:50:43 +02:00
bool LinuxDevice : : handlesFile ( const FilePath & filePath ) const
{
2022-03-25 10:35:17 +01:00
if ( filePath . scheme ( ) = = " device " & & filePath . host ( ) = = id ( ) . toString ( ) )
return true ;
if ( filePath . scheme ( ) = = " ssh " & & filePath . host ( ) = = userAtHost ( ) )
return true ;
return false ;
2021-07-27 09:50:43 +02:00
}
2022-02-14 18:37:56 +01:00
ProcessInterface * LinuxDevice : : createProcessInterface ( ) const
2021-07-27 09:50:43 +02:00
{
2022-02-14 18:37:56 +01:00
return new LinuxProcessImpl ( this ) ;
2021-07-27 09:50:43 +02:00
}
LinuxDevicePrivate : : LinuxDevicePrivate ( LinuxDevice * parent )
: q ( parent )
{
m_handler = new ShellThreadHandler ( ) ;
m_handler - > moveToThread ( & m_shellThread ) ;
QObject : : connect ( & m_shellThread , & QThread : : finished , m_handler , & QObject : : deleteLater ) ;
m_shellThread . start ( ) ;
}
LinuxDevicePrivate : : ~ LinuxDevicePrivate ( )
{
2022-04-28 21:11:20 +02:00
qDeleteAll ( m_terminals ) ;
2022-02-14 18:37:56 +01:00
auto closeShell = [ this ] {
m_shellThread . quit ( ) ;
m_shellThread . wait ( ) ;
} ;
if ( QThread : : currentThread ( ) = = m_shellThread . thread ( ) )
closeShell ( ) ;
else // We might be in a non-main thread now due to extended lifetime of IDevice::Ptr
QMetaObject : : invokeMethod ( & m_shellThread , closeShell , Qt : : BlockingQueuedConnection ) ;
2021-07-27 09:50:43 +02:00
}
2022-04-22 16:02:03 +02:00
// Call me with shell mutex locked
2021-07-27 09:50:43 +02:00
bool LinuxDevicePrivate : : setupShell ( )
{
2022-04-22 16:02:03 +02:00
const SshConnectionParameters sshParameters = q - > sshParameters ( ) ;
if ( m_handler - > isRunning ( sshParameters ) )
return true ;
2021-07-27 09:50:43 +02:00
bool ok = false ;
2022-04-22 16:02:03 +02:00
QMetaObject : : invokeMethod ( m_handler , [ this , sshParameters ] {
return m_handler - > start ( sshParameters ) ;
2021-07-27 09:50:43 +02:00
} , Qt : : BlockingQueuedConnection , & ok ) ;
return ok ;
}
bool LinuxDevicePrivate : : runInShell ( const CommandLine & cmd , const QByteArray & data )
{
QMutexLocker locker ( & m_shellMutex ) ;
DEBUG ( cmd . toUserOutput ( ) ) ;
2022-04-22 16:02:03 +02:00
QTC_ASSERT ( setupShell ( ) , return false ) ;
2021-07-27 09:50:43 +02:00
bool ret = false ;
QMetaObject : : invokeMethod ( m_handler , [ this , & cmd , & data ] {
return m_handler - > runInShell ( cmd , data ) ;
} , Qt : : BlockingQueuedConnection , & ret ) ;
return ret ;
}
2022-02-03 18:09:06 +01:00
QByteArray LinuxDevicePrivate : : outputForRunInShell ( const QString & cmd )
2021-07-27 09:50:43 +02:00
{
QMutexLocker locker ( & m_shellMutex ) ;
2022-01-04 15:46:55 +01:00
DEBUG ( cmd ) ;
2022-04-22 16:02:03 +02:00
QTC_ASSERT ( setupShell ( ) , return { } ) ;
2021-07-27 09:50:43 +02:00
2022-02-03 18:09:06 +01:00
QByteArray ret ;
2021-07-27 09:50:43 +02:00
QMetaObject : : invokeMethod ( m_handler , [ this , & cmd ] {
return m_handler - > outputForRunInShell ( cmd ) ;
} , Qt : : BlockingQueuedConnection , & ret ) ;
return ret ;
}
2022-02-03 18:09:06 +01:00
QByteArray LinuxDevicePrivate : : outputForRunInShell ( const CommandLine & cmd )
2022-01-04 15:46:55 +01:00
{
return outputForRunInShell ( cmd . toUserOutput ( ) ) ;
}
2022-02-14 18:37:56 +01:00
void LinuxDevicePrivate : : attachToSharedConnection ( SshConnectionHandle * connectionHandle ,
const SshConnectionParameters & sshParameters )
{
QString socketFilePath ;
2022-04-22 16:02:03 +02:00
QMetaObject : : invokeMethod ( m_handler , [ this , connectionHandle , sshParameters ] {
return m_handler - > attachToSharedConnection ( connectionHandle , sshParameters ) ;
} , Qt : : BlockingQueuedConnection , & socketFilePath ) ;
2022-02-14 18:37:56 +01:00
if ( ! socketFilePath . isEmpty ( ) )
emit connectionHandle - > connected ( socketFilePath ) ;
}
2021-07-27 09:50:43 +02:00
bool LinuxDevice : : isExecutableFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -x " , path } } ) ;
}
bool LinuxDevice : : isReadableFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -r " , path , " -a " , " -f " , path } } ) ;
}
bool LinuxDevice : : isWritableFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -w " , path , " -a " , " -f " , path } } ) ;
}
bool LinuxDevice : : isReadableDirectory ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -r " , path , " -a " , " -d " , path } } ) ;
}
bool LinuxDevice : : isWritableDirectory ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -w " , path , " -a " , " -d " , path } } ) ;
}
bool LinuxDevice : : isFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -f " , path } } ) ;
}
bool LinuxDevice : : isDirectory ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -d " , path } } ) ;
}
bool LinuxDevice : : createDirectory ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " mkdir " , { " -p " , path } } ) ;
}
bool LinuxDevice : : exists ( const FilePath & filePath ) const
{
DEBUG ( " filepath " < < filePath . path ( ) ) ;
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " test " , { " -e " , path } } ) ;
}
bool LinuxDevice : : ensureExistingFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const QString path = filePath . path ( ) ;
return d - > runInShell ( { " touch " , { path } } ) ;
}
bool LinuxDevice : : removeFile ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
return d - > runInShell ( { " rm " , { filePath . path ( ) } } ) ;
}
bool LinuxDevice : : removeRecursively ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
QTC_ASSERT ( filePath . path ( ) . startsWith ( ' / ' ) , return false ) ;
const QString path = filePath . cleanPath ( ) . path ( ) ;
// We are expecting this only to be called in a context of build directories or similar.
// Chicken out in some cases that _might_ be user code errors.
QTC_ASSERT ( path . startsWith ( ' / ' ) , return false ) ;
2022-01-04 15:11:49 +01:00
const int levelsNeeded = path . startsWith ( " /home/ " ) ? 3 : 2 ;
2021-07-27 09:50:43 +02:00
QTC_ASSERT ( path . count ( ' / ' ) > = levelsNeeded , return false ) ;
return d - > runInShell ( { " rm " , { " -rf " , " -- " , path } } ) ;
}
bool LinuxDevice : : copyFile ( const FilePath & filePath , const FilePath & target ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
QTC_ASSERT ( handlesFile ( target ) , return false ) ;
return d - > runInShell ( { " cp " , { filePath . path ( ) , target . path ( ) } } ) ;
}
bool LinuxDevice : : renameFile ( const FilePath & filePath , const FilePath & target ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
QTC_ASSERT ( handlesFile ( target ) , return false ) ;
return d - > runInShell ( { " mv " , { filePath . path ( ) , target . path ( ) } } ) ;
}
QDateTime LinuxDevice : : lastModified ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return { } ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( { " stat " , { " -c " , " %Y " , filePath . path ( ) } } ) ;
2021-07-27 09:50:43 +02:00
const qint64 secs = output . toLongLong ( ) ;
const QDateTime dt = QDateTime : : fromSecsSinceEpoch ( secs , Qt : : UTC ) ;
return dt ;
}
FilePath LinuxDevice : : symLinkTarget ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return { } ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( { " readlink " , { " -n " , " -e " , filePath . path ( ) } } ) ;
const QString out = QString : : fromUtf8 ( output . data ( ) , output . size ( ) ) ;
return output . isEmpty ( ) ? FilePath ( ) : filePath . withNewPath ( out ) ;
2021-07-27 09:50:43 +02:00
}
qint64 LinuxDevice : : fileSize ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return - 1 ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( { " stat " , { " -c " , " %s " , filePath . path ( ) } } ) ;
2021-07-27 09:50:43 +02:00
return output . toLongLong ( ) ;
}
2022-01-04 15:46:55 +01:00
qint64 LinuxDevice : : bytesAvailable ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return - 1 ) ;
CommandLine cmd ( " df " , { " -k " } ) ;
cmd . addArg ( filePath . path ( ) ) ;
cmd . addArgs ( " |tail -n 1 |sed 's/ */ /g'|cut -d ' ' -f 4 " , CommandLine : : Raw ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( cmd . toUserOutput ( ) ) ;
2022-01-04 15:46:55 +01:00
bool ok = false ;
const qint64 size = output . toLongLong ( & ok ) ;
if ( ok )
return size * 1024 ;
return - 1 ;
}
2021-07-27 09:50:43 +02:00
QFileDevice : : Permissions LinuxDevice : : permissions ( const FilePath & filePath ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return { } ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( { " stat " , { " -c " , " %a " , filePath . path ( ) } } ) ;
2021-07-27 09:50:43 +02:00
const uint bits = output . toUInt ( nullptr , 8 ) ;
QFileDevice : : Permissions perm = { } ;
# define BIT(n, p) if (bits & (1<<n)) perm |= QFileDevice::p
BIT ( 0 , ExeOther ) ;
BIT ( 1 , WriteOther ) ;
BIT ( 2 , ReadOther ) ;
BIT ( 3 , ExeGroup ) ;
BIT ( 4 , WriteGroup ) ;
BIT ( 5 , ReadGroup ) ;
BIT ( 6 , ExeUser ) ;
BIT ( 7 , WriteUser ) ;
BIT ( 8 , ReadUser ) ;
# undef BIT
return perm ;
}
bool LinuxDevice : : setPermissions ( const Utils : : FilePath & filePath , QFileDevice : : Permissions permissions ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return false ) ;
const int flags = int ( permissions ) ;
return d - > runInShell ( { " chmod " , { QString : : number ( flags , 16 ) , filePath . path ( ) } } ) ;
}
void LinuxDevice : : iterateDirectory ( const FilePath & filePath ,
const std : : function < bool ( const FilePath & ) > & callBack ,
2022-01-21 12:22:54 +01:00
const FileFilter & filter ) const
2021-07-27 09:50:43 +02:00
{
QTC_ASSERT ( handlesFile ( filePath ) , return ) ;
// if we do not have find - use ls as fallback
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( { " ls " , { " -1 " , " -b " , " -- " , filePath . path ( ) } } ) ;
2022-01-21 13:11:39 +01:00
const QStringList entries = QString : : fromUtf8 ( output ) . split ( ' \n ' , Qt : : SkipEmptyParts ) ;
FileUtils : : iterateLsOutput ( filePath , entries , filter , callBack ) ;
2021-07-27 09:50:43 +02:00
}
QByteArray LinuxDevice : : fileContents ( const FilePath & filePath , qint64 limit , qint64 offset ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return { } ) ;
QString args = " if= " + filePath . path ( ) + " status=none " ;
if ( limit > 0 | | offset > 0 ) {
const qint64 gcd = std : : gcd ( limit , offset ) ;
args + = QString ( " bs=%1 count=%2 seek=%3 " ) . arg ( gcd ) . arg ( limit / gcd ) . arg ( offset / gcd ) ;
}
CommandLine cmd ( FilePath : : fromString ( " dd " ) , args , CommandLine : : Raw ) ;
2022-02-03 18:09:06 +01:00
const QByteArray output = d - > outputForRunInShell ( cmd ) ;
DEBUG ( output < < output < < QByteArray : : fromHex ( output ) ) ;
return output ;
2021-07-27 09:50:43 +02:00
}
bool LinuxDevice : : writeFileContents ( const FilePath & filePath , const QByteArray & data ) const
{
QTC_ASSERT ( handlesFile ( filePath ) , return { } ) ;
return d - > runInShell ( { " dd " , { " of= " + filePath . path ( ) } } , data ) ;
}
2019-02-20 19:13:28 +01:00
namespace Internal {
// Factory
LinuxDeviceFactory : : LinuxDeviceFactory ( )
: IDeviceFactory ( Constants : : GenericLinuxOsType )
{
2020-01-21 16:17:59 +01:00
setDisplayName ( LinuxDevice : : tr ( " Generic Linux Device " ) ) ;
2019-02-20 19:13:28 +01:00
setIcon ( QIcon ( ) ) ;
setConstructionFunction ( & LinuxDevice : : create ) ;
2022-01-26 09:05:35 +01:00
setCreator ( [ ] {
GenericLinuxDeviceConfigurationWizard wizard ( Core : : ICore : : dialogParent ( ) ) ;
if ( wizard . exec ( ) ! = QDialog : : Accepted )
return IDevice : : Ptr ( ) ;
return wizard . device ( ) ;
} ) ;
2019-02-20 19:13:28 +01:00
}
2022-01-26 09:05:35 +01:00
} // namespace Internal
2011-05-31 12:47:53 +02:00
} // namespace RemoteLinux
2022-02-14 18:37:56 +01:00
# include "linuxdevice.moc"