[libcalamares] Begin implementing Runner
This copies a bunch of code from the System methods for running, and then #if 0's them to get it to compile. Add some basic directory-management.
This commit is contained in:
parent
b9dac6eef8
commit
0b943e801c
@ -77,6 +77,7 @@ set( libSources
|
|||||||
utils/Permissions.cpp
|
utils/Permissions.cpp
|
||||||
utils/PluginFactory.cpp
|
utils/PluginFactory.cpp
|
||||||
utils/Retranslator.cpp
|
utils/Retranslator.cpp
|
||||||
|
utils/Runner.cpp
|
||||||
utils/String.cpp
|
utils/String.cpp
|
||||||
utils/UMask.cpp
|
utils/UMask.cpp
|
||||||
utils/Variant.cpp
|
utils/Variant.cpp
|
||||||
|
@ -10,8 +10,82 @@
|
|||||||
|
|
||||||
#include "Runner.h"
|
#include "Runner.h"
|
||||||
|
|
||||||
namespace
|
#include "GlobalStorage.h"
|
||||||
|
#include "JobQueue.h"
|
||||||
|
#include "utils/Logger.h"
|
||||||
|
|
||||||
|
/** @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 >
|
||||||
|
calculateWorkingDirectory( Calamares::Utils::RunLocation location, QString directory )
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Calamares
|
namespace Calamares
|
||||||
@ -21,25 +95,122 @@ namespace Utils
|
|||||||
|
|
||||||
struct Runner::Private
|
struct Runner::Private
|
||||||
{
|
{
|
||||||
QStringList m_command;
|
QProcess m_process;
|
||||||
RunLocation m_location = RunLocation::RunInHost;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Runner::Runner() {}
|
Runner::Runner() {}
|
||||||
Runner::Runner( const QStringList& command )
|
|
||||||
: d( std::make_unique< Private >() )
|
|
||||||
{
|
|
||||||
d->m_command = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
Runner::~Runner() {}
|
|
||||||
|
|
||||||
Runner&
|
|
||||||
Runner::setCommand( const QStringList& command )
|
|
||||||
{
|
|
||||||
d->m_command = command;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
} // namespace Calamares
|
} // namespace Calamares
|
||||||
|
|
||||||
|
|
||||||
|
Calamares::Utils::Runner::Runner( const QStringList& command )
|
||||||
|
{
|
||||||
|
setCommand( command );
|
||||||
|
}
|
||||||
|
|
||||||
|
Calamares::Utils::Runner::~Runner() {}
|
||||||
|
|
||||||
|
Calamares::Utils::ProcessResult
|
||||||
|
Calamares::Utils::Runner::run()
|
||||||
|
{
|
||||||
|
if ( m_command.isEmpty() )
|
||||||
|
{
|
||||||
|
cWarning() << "Cannot run an empty program list";
|
||||||
|
return ProcessResult::Code::FailedToStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [ ok, workingDirectory ] = calculateWorkingDirectory( m_location, m_directory );
|
||||||
|
if ( !ok || !workingDirectory.exists() )
|
||||||
|
{
|
||||||
|
// Warnings have already been printed
|
||||||
|
return ProcessResult::Code::NoWorkingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QProcess process;
|
||||||
|
process.setProcessChannelMode( QProcess::MergedChannels );
|
||||||
|
process.setWorkingDirectory( workingDirectory.absolutePath() );
|
||||||
|
if ( m_location == RunLocation::RunInTarget )
|
||||||
|
{
|
||||||
|
process.setProgram( "chroot" );
|
||||||
|
process.setArguments( QStringList { workingDirectory.absolutePath() } << m_command );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
process.setProgram( "env" );
|
||||||
|
process.setArguments( m_command );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessResult::Code::Crashed;
|
||||||
|
#if 0
|
||||||
|
if ( !workingPath.isEmpty() )
|
||||||
|
{
|
||||||
|
if ( QDir( workingPath ).exists() )
|
||||||
|
{
|
||||||
|
process.setWorkingDirectory( QDir( workingPath ).absolutePath() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cWarning() << "Invalid working directory:" << workingPath;
|
||||||
|
return ProcessResult::Code::NoWorkingDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cDebug() << Logger::SubEntry << "Running" << program << RedactedList( arguments );
|
||||||
|
process.start();
|
||||||
|
if ( !process.waitForStarted() )
|
||||||
|
{
|
||||||
|
cWarning() << "Process" << args.first() << "failed to start" << process.error();
|
||||||
|
return ProcessResult::Code::FailedToStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !stdInput.isEmpty() )
|
||||||
|
{
|
||||||
|
process.write( stdInput.toLocal8Bit() );
|
||||||
|
}
|
||||||
|
process.closeWriteChannel();
|
||||||
|
|
||||||
|
if ( !process.waitForFinished( timeoutSec > std::chrono::seconds::zero()
|
||||||
|
? ( static_cast< int >( std::chrono::milliseconds( timeoutSec ).count() ) )
|
||||||
|
: -1 ) )
|
||||||
|
{
|
||||||
|
cWarning() << "Process" << args.first() << "timed out after" << timeoutSec.count() << "s. Output so far:\n"
|
||||||
|
<< Logger::NoQuote << process.readAllStandardOutput();
|
||||||
|
return ProcessResult::Code::TimedOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString output = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed();
|
||||||
|
|
||||||
|
if ( process.exitStatus() == QProcess::CrashExit )
|
||||||
|
{
|
||||||
|
cWarning() << "Process" << args.first() << "crashed. Output so far:\n" << Logger::NoQuote << output;
|
||||||
|
return ProcessResult::Code::Crashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto r = process.exitCode();
|
||||||
|
bool showDebug = ( !Calamares::Settings::instance() ) || ( Calamares::Settings::instance()->debugMode() );
|
||||||
|
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() )
|
||||||
|
{
|
||||||
|
cDebug() << Logger::SubEntry << "Target cmd:" << RedactedList( args ) << "Exit code:" << r << "output:\n"
|
||||||
|
<< Logger::NoQuote << output;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cDebug() << Logger::SubEntry << "Target cmd:" << RedactedList( args ) << "Exit code:" << r << "(no output)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProcessResult( r, output );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QProcess>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@ -27,25 +28,88 @@ namespace Utils
|
|||||||
{
|
{
|
||||||
|
|
||||||
using RunLocation = CalamaresUtils::System::RunLocation;
|
using RunLocation = CalamaresUtils::System::RunLocation;
|
||||||
|
using ProcessResult = CalamaresUtils::ProcessResult;
|
||||||
|
|
||||||
/** @brief A Runner wraps a process and handles running it and signalling */
|
/** @brief A Runner wraps a process and handles running it and processing output
|
||||||
|
*
|
||||||
|
* This is basically a QProcess, but handles both running in the
|
||||||
|
* host system (natively) or in the target (by calling chroot).
|
||||||
|
* It has an output signal that handles output one line at a time
|
||||||
|
* (unlike QProcess that lets you do the buffering yourself).
|
||||||
|
* This output processing is only enabled if you do so explicitly.
|
||||||
|
*/
|
||||||
class Runner : public QObject
|
class Runner : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/** @brief Create an empty runner
|
||||||
|
*
|
||||||
|
* This is a runner with no commands, nothing; call set*() methods
|
||||||
|
* to configure it.
|
||||||
|
*/
|
||||||
Runner();
|
Runner();
|
||||||
|
/** @brief Create a runner with a specified command
|
||||||
|
*
|
||||||
|
* Equivalent to Calamares::Utils::Runner::Runner()
|
||||||
|
*/
|
||||||
Runner( const QStringList& command );
|
Runner( const QStringList& command );
|
||||||
virtual ~Runner() override;
|
virtual ~Runner() override;
|
||||||
|
|
||||||
Runner& setCommand( const QStringList& command );
|
Runner& setCommand( const QStringList& command )
|
||||||
Runner& setLocation( RunLocation r );
|
{
|
||||||
Runner& setWorkingDirectory( const QString& directory );
|
m_command = command;
|
||||||
Runner& setWorkingDirectory( const QDir& directory );
|
return *this;
|
||||||
Runner& setTimeout( std::chrono::seconds timeout );
|
}
|
||||||
Runner& setInput( const QString& stdin );
|
Runner& setLocation( RunLocation r )
|
||||||
|
{
|
||||||
|
m_location = r;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Runner& setWorkingDirectory( const QDir& directory )
|
||||||
|
{
|
||||||
|
m_directory = directory.absolutePath();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Runner& setWorkingDirectory( const QString& directory )
|
||||||
|
{
|
||||||
|
m_directory = directory;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Runner& setTimeout( std::chrono::seconds timeout )
|
||||||
|
{
|
||||||
|
m_timeout = timeout;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Runner& setInput( const QString& stdin )
|
||||||
|
{
|
||||||
|
m_input = stdin;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Runner& enableOutputProcessing( bool enable = true )
|
||||||
|
{
|
||||||
|
m_output = enable;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessResult run();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void output( QString line );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// What to run, and where.
|
||||||
|
QStringList m_command;
|
||||||
|
QString m_directory;
|
||||||
|
RunLocation m_location { RunLocation::RunInHost };
|
||||||
|
|
||||||
|
// Settings for when it actually runs
|
||||||
|
QString m_input;
|
||||||
|
std::chrono::milliseconds m_timeout { 0 };
|
||||||
|
bool m_output = false;
|
||||||
|
|
||||||
|
// Internals for when it really does run
|
||||||
struct Private;
|
struct Private;
|
||||||
std::unique_ptr< Private > d;
|
std::unique_ptr< Private > d;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user