2021-07-28 13:11:06 +02:00
|
|
|
/* === This file is part of Calamares - <https://calamares.io> ===
|
|
|
|
*
|
|
|
|
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
*
|
|
|
|
* Calamares is Free Software: see the License-Identifier above.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Runner.h"
|
|
|
|
|
2021-10-04 00:10:44 +02:00
|
|
|
#include "GlobalStorage.h"
|
|
|
|
#include "JobQueue.h"
|
2021-11-02 22:05:38 +01:00
|
|
|
#include "Settings.h"
|
2021-10-04 00:10:44 +02:00
|
|
|
#include "utils/Logger.h"
|
|
|
|
|
2021-11-02 23:30:19 +01:00
|
|
|
#include <QProcess>
|
|
|
|
|
2021-10-04 00:10:44 +02:00
|
|
|
/** @brief Descend from directory, always relative
|
|
|
|
*
|
|
|
|
* If @p subdir begins with a "/" or "../" or "./" those are stripped
|
|
|
|
* until none are left, then changes @p directory into that
|
|
|
|
* subdirectory.
|
|
|
|
*
|
|
|
|
* Returns @c false if the @p subdir doesn't make sense.
|
|
|
|
*/
|
|
|
|
STATICTEST bool
|
|
|
|
relativeChangeDirectory( QDir& directory, const QString& subdir )
|
|
|
|
{
|
|
|
|
const QString rootPath = directory.absolutePath();
|
|
|
|
const QString concatenatedPath = rootPath + '/' + subdir;
|
|
|
|
const QString relPath = QDir::cleanPath( concatenatedPath );
|
|
|
|
|
|
|
|
if ( !relPath.startsWith( rootPath ) )
|
|
|
|
{
|
|
|
|
cWarning() << "Relative path" << subdir << "escapes from" << rootPath;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return directory.cd( relPath );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
STATICTEST std::pair< bool, QDir >
|
2021-10-30 14:35:56 +02:00
|
|
|
calculateWorkingDirectory( Calamares::Utils::RunLocation location, const QString& directory )
|
2021-07-28 13:11:06 +02:00
|
|
|
{
|
2021-10-04 00:10:44 +02:00
|
|
|
Calamares::GlobalStorage* gs
|
|
|
|
= Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
|
|
|
|
|
|
|
|
if ( location == Calamares::Utils::RunLocation::RunInTarget )
|
|
|
|
{
|
|
|
|
if ( !gs || !gs->contains( "rootMountPoint" ) )
|
|
|
|
{
|
|
|
|
cWarning() << "No rootMountPoint in global storage, while RunInTarget is specified";
|
|
|
|
return std::make_pair( false, QDir() );
|
|
|
|
}
|
|
|
|
|
|
|
|
QDir rootMountPoint( gs->value( "rootMountPoint" ).toString() );
|
|
|
|
if ( !rootMountPoint.exists() )
|
|
|
|
{
|
|
|
|
cWarning() << "rootMountPoint points to a dir which does not exist";
|
|
|
|
return std::make_pair( false, QDir() );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !directory.isEmpty() )
|
|
|
|
{
|
|
|
|
|
|
|
|
if ( !relativeChangeDirectory( rootMountPoint, directory ) || !rootMountPoint.exists() )
|
|
|
|
{
|
|
|
|
cWarning() << "Working directory" << directory << "does not exist in target";
|
|
|
|
return std::make_pair( false, QDir() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return std::make_pair( true, rootMountPoint ); // Now changed to subdir
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QDir root;
|
|
|
|
if ( !directory.isEmpty() )
|
|
|
|
{
|
|
|
|
root = QDir::root();
|
|
|
|
|
|
|
|
if ( !relativeChangeDirectory( root, directory ) || !root.exists() )
|
|
|
|
{
|
|
|
|
cWarning() << "Working directory" << directory << "does not exist in host";
|
|
|
|
return std::make_pair( false, QDir() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return std::make_pair( true, root ); // Now changed to subdir
|
|
|
|
}
|
2021-07-28 13:11:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace Calamares
|
|
|
|
{
|
|
|
|
namespace Utils
|
|
|
|
{
|
|
|
|
|
|
|
|
Runner::Runner() {}
|
2021-10-04 00:10:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
} // namespace Utils
|
|
|
|
} // namespace Calamares
|
|
|
|
|
|
|
|
|
|
|
|
Calamares::Utils::Runner::Runner( const QStringList& command )
|
2021-07-28 13:11:06 +02:00
|
|
|
{
|
2021-10-04 00:10:44 +02:00
|
|
|
setCommand( command );
|
2021-07-28 13:11:06 +02:00
|
|
|
}
|
|
|
|
|
2021-10-04 00:10:44 +02:00
|
|
|
Calamares::Utils::Runner::~Runner() {}
|
2021-07-28 13:11:06 +02:00
|
|
|
|
2021-10-04 00:10:44 +02:00
|
|
|
Calamares::Utils::ProcessResult
|
|
|
|
Calamares::Utils::Runner::run()
|
2021-10-01 13:16:37 +02:00
|
|
|
{
|
2021-10-04 00:10:44 +02:00
|
|
|
if ( m_command.isEmpty() )
|
|
|
|
{
|
|
|
|
cWarning() << "Cannot run an empty program list";
|
|
|
|
return ProcessResult::Code::FailedToStart;
|
|
|
|
}
|
2021-07-28 13:11:06 +02:00
|
|
|
|
2021-10-04 00:10:44 +02:00
|
|
|
auto [ ok, workingDirectory ] = calculateWorkingDirectory( m_location, m_directory );
|
|
|
|
if ( !ok || !workingDirectory.exists() )
|
|
|
|
{
|
|
|
|
// Warnings have already been printed
|
|
|
|
return ProcessResult::Code::NoWorkingDirectory;
|
|
|
|
}
|
|
|
|
|
|
|
|
QProcess process;
|
2021-11-05 16:26:45 +01:00
|
|
|
// Make the process run in "C" locale so we don't get issues with translation
|
|
|
|
{
|
|
|
|
auto env = QProcessEnvironment::systemEnvironment();
|
|
|
|
env.insert( "LC_ALL", "C" );
|
|
|
|
process.setProcessEnvironment( env );
|
|
|
|
}
|
2021-10-04 00:10:44 +02:00
|
|
|
process.setProcessChannelMode( QProcess::MergedChannels );
|
2021-11-02 22:05:38 +01:00
|
|
|
if ( !m_directory.isEmpty() )
|
|
|
|
{
|
|
|
|
process.setWorkingDirectory( workingDirectory.absolutePath() );
|
|
|
|
}
|
2021-10-04 00:10:44 +02:00
|
|
|
if ( m_location == RunLocation::RunInTarget )
|
|
|
|
{
|
|
|
|
process.setProgram( "chroot" );
|
|
|
|
process.setArguments( QStringList { workingDirectory.absolutePath() } << m_command );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
process.setProgram( "env" );
|
|
|
|
process.setArguments( m_command );
|
|
|
|
}
|
|
|
|
|
2021-11-02 22:50:13 +01:00
|
|
|
if ( m_output )
|
|
|
|
{
|
|
|
|
connect( &process, &QProcess::readyReadStandardOutput, [this, &process]() {
|
2021-11-05 16:26:45 +01:00
|
|
|
do
|
2021-11-02 22:50:13 +01:00
|
|
|
{
|
|
|
|
QString s = process.readLine();
|
|
|
|
if ( !s.isEmpty() )
|
|
|
|
{
|
|
|
|
Q_EMIT this->output( s );
|
|
|
|
}
|
2021-11-05 16:26:45 +01:00
|
|
|
} while ( process.canReadLine() );
|
2021-11-02 22:50:13 +01:00
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2021-11-02 22:05:38 +01:00
|
|
|
cDebug() << Logger::SubEntry << "Running" << Logger::Redacted( m_command );
|
2021-10-04 00:10:44 +02:00
|
|
|
process.start();
|
|
|
|
if ( !process.waitForStarted() )
|
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
cWarning() << "Process" << m_command.first() << "failed to start" << process.error();
|
2021-10-04 00:10:44 +02:00
|
|
|
return ProcessResult::Code::FailedToStart;
|
|
|
|
}
|
|
|
|
|
2021-11-02 22:05:38 +01:00
|
|
|
if ( !m_input.isEmpty() )
|
2021-10-04 00:10:44 +02:00
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
process.write( m_input.toLocal8Bit() );
|
2021-10-04 00:10:44 +02:00
|
|
|
}
|
|
|
|
process.closeWriteChannel();
|
|
|
|
|
2021-11-02 22:05:38 +01:00
|
|
|
if ( !process.waitForFinished( m_timeout > std::chrono::seconds::zero()
|
|
|
|
? ( static_cast< int >( std::chrono::milliseconds( m_timeout ).count() ) )
|
|
|
|
: -1 ) )
|
2021-10-04 00:10:44 +02:00
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
cWarning() << "Process" << m_command.first() << "timed out after" << m_timeout.count() << "ms. Output so far:\n"
|
2021-10-04 00:10:44 +02:00
|
|
|
<< Logger::NoQuote << process.readAllStandardOutput();
|
|
|
|
return ProcessResult::Code::TimedOut;
|
|
|
|
}
|
|
|
|
|
2021-11-05 16:26:45 +01:00
|
|
|
QString output = m_output ? QString() : QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed();
|
|
|
|
if ( m_output )
|
2021-11-02 22:58:41 +01:00
|
|
|
{
|
2021-11-05 16:26:45 +01:00
|
|
|
// Try to read trailing output, if any
|
|
|
|
do
|
|
|
|
{
|
|
|
|
output = process.readLine();
|
|
|
|
if ( !output.isEmpty() )
|
|
|
|
{
|
|
|
|
Q_EMIT this->output( output );
|
|
|
|
}
|
|
|
|
} while ( !output.isEmpty() );
|
2021-11-05 16:31:47 +01:00
|
|
|
output = process.readAllStandardOutput();
|
|
|
|
if ( !output.isEmpty() )
|
|
|
|
{
|
|
|
|
cWarning() << "Some process output left-over";
|
|
|
|
Q_EMIT this->output( output );
|
|
|
|
}
|
2021-11-02 22:58:41 +01:00
|
|
|
}
|
2021-10-04 00:10:44 +02:00
|
|
|
|
|
|
|
if ( process.exitStatus() == QProcess::CrashExit )
|
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
cWarning() << "Process" << m_command.first() << "crashed. Output so far:\n" << Logger::NoQuote << output;
|
2021-10-04 00:10:44 +02:00
|
|
|
return ProcessResult::Code::Crashed;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto r = process.exitCode();
|
2021-11-02 22:05:38 +01:00
|
|
|
const bool showDebug = ( !Calamares::Settings::instance() ) || ( Calamares::Settings::instance()->debugMode() );
|
2021-10-04 00:10:44 +02:00
|
|
|
if ( r == 0 )
|
|
|
|
{
|
|
|
|
if ( showDebug && !output.isEmpty() )
|
|
|
|
{
|
|
|
|
cDebug() << Logger::SubEntry << "Finished. Exit code:" << r << "output:\n" << Logger::NoQuote << output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // if ( r != 0 )
|
|
|
|
{
|
|
|
|
if ( !output.isEmpty() )
|
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
|
|
|
|
<< "output:\n"
|
2021-10-04 00:10:44 +02:00
|
|
|
<< Logger::NoQuote << output;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-02 22:05:38 +01:00
|
|
|
cDebug() << Logger::SubEntry << "Target cmd:" << Logger::Redacted( m_command ) << "Exit code:" << r
|
|
|
|
<< "(no output)";
|
2021-10-04 00:10:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ProcessResult( r, output );
|
|
|
|
}
|