calamares/src/libcalamares/PythonJob.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

379 lines
17 KiB
C++
Raw Normal View History

/* === This file is part of Calamares - <https://calamares.io> ===
*
[libcalamares] Update SPDX identifiers. Update CppJob.h Update CalamaresConfig.h.in Update DllMacro.h Update GlobalStorage.cpp Update GlobalStorage.h Update Job.cpp Update Job.h Update JobExample.cpp Update JobExample.h Update JobQueue.cpp Update CalamaresConfig.h.in Update CppJob.cpp Update CppJob.h Update DllMacro.h Update GlobalStorage.cpp Update GlobalStorage.h Update Job.cpp Update Job.h Update JobExample.cpp Update JobExample.h Update JobQueue.h Update ProcessJob.cpp Update ProcessJob.h Update PythonHelper.cpp Update PythonJob.cpp Update PythonJob.h Update PythonHelper.h Update PythonJobApi.cpp Update PythonJobApi.h Update Settings.cpp Update Settings.h Update GeoIPJSON.cpp Update GeoIPJSON.h Update GeoIPTests.cpp Update GeoIPTests.h Update GeoIPXML.cpp Update GeoIPXML.h Update Handler.cpp Update Handler.h Update Interface.cpp Update Interface.h Update test_geoip.cpp Update CountryData_p.cpp Update Label.cpp Update Label.h Update LabelModel.cpp Update LabelModel.h Update CountryData_p.cpp Update CountryData_p.cpp Update Lookup.cpp Update Lookup.h Update Tests.cpp Update Tests.h Update TimeZone.cpp Update TimeZone.h Update TranslatableConfiguration.cpp Update TranslatableConfiguration.h Update ZoneData_p.cxxtr Update cldr-extractor.py Update zone-extractor.py Update Actions.h Update Actions.h Update Descriptor.h Update InstanceKey.cpp Update Module.cpp Update Module.h Update Requirement.cpp Update RequirementsChecker.h Update RequirementsModel.cpp Update RequirementsModel.h Update Tests.cpp Update Manager.cpp Update Manager.h Update Tests.cpp Update FileSystem.cpp Update FileSystem.h Update KPMManager.cpp Update KPMManager.h Update KPMTests.cpp Update FileSystem.cpp Update FileSystem.cpp Update FileSystem.h Update KPMManager.cpp Update KPMManager.h Update Mount.cpp Update Mount.h Update PartitionIterator.cpp Update PartitionIterator.h Update PartitionIterator.h Update PartitionQuery.cpp Update PartitionQuery.h Update PartitionSize.cpp Update PartitionSize.h Update Sync.cpp Update Sync.h Update Tests.cpp Update Tests.h Update BoostPython.h Update CalamaresUtilsSystem.cpp Update CalamaresUtilsSystem.h Update CommandList.cpp Update CommandList.h Update Dirs.cpp Update Dirs.h Update Entropy.cpp Update Entropy.h Update Entropy.cpp Update Logger.cpp Update Logger.h Update NamedEnum.h Update NamedSuffix.h Update PluginFactory.cpp Update PluginFactory.h Update RAII.h Update RAII.h Update Retranslator.cpp Update Retranslator.h Update String.cpp Update String.h Update TestPaths.cpp Update Tests.cpp Update Tests.h Update UMask.cpp Update UMask.h Update Units.h Update Variant.cpp Update Variant.h Update Yaml.cpp Update Yaml.h Update moc-warnings.h
2020-05-30 16:15:03 +02:00
* SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2018-2020 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
[libcalamares] Update SPDX identifiers. Update CppJob.h Update CalamaresConfig.h.in Update DllMacro.h Update GlobalStorage.cpp Update GlobalStorage.h Update Job.cpp Update Job.h Update JobExample.cpp Update JobExample.h Update JobQueue.cpp Update CalamaresConfig.h.in Update CppJob.cpp Update CppJob.h Update DllMacro.h Update GlobalStorage.cpp Update GlobalStorage.h Update Job.cpp Update Job.h Update JobExample.cpp Update JobExample.h Update JobQueue.h Update ProcessJob.cpp Update ProcessJob.h Update PythonHelper.cpp Update PythonJob.cpp Update PythonJob.h Update PythonHelper.h Update PythonJobApi.cpp Update PythonJobApi.h Update Settings.cpp Update Settings.h Update GeoIPJSON.cpp Update GeoIPJSON.h Update GeoIPTests.cpp Update GeoIPTests.h Update GeoIPXML.cpp Update GeoIPXML.h Update Handler.cpp Update Handler.h Update Interface.cpp Update Interface.h Update test_geoip.cpp Update CountryData_p.cpp Update Label.cpp Update Label.h Update LabelModel.cpp Update LabelModel.h Update CountryData_p.cpp Update CountryData_p.cpp Update Lookup.cpp Update Lookup.h Update Tests.cpp Update Tests.h Update TimeZone.cpp Update TimeZone.h Update TranslatableConfiguration.cpp Update TranslatableConfiguration.h Update ZoneData_p.cxxtr Update cldr-extractor.py Update zone-extractor.py Update Actions.h Update Actions.h Update Descriptor.h Update InstanceKey.cpp Update Module.cpp Update Module.h Update Requirement.cpp Update RequirementsChecker.h Update RequirementsModel.cpp Update RequirementsModel.h Update Tests.cpp Update Manager.cpp Update Manager.h Update Tests.cpp Update FileSystem.cpp Update FileSystem.h Update KPMManager.cpp Update KPMManager.h Update KPMTests.cpp Update FileSystem.cpp Update FileSystem.cpp Update FileSystem.h Update KPMManager.cpp Update KPMManager.h Update Mount.cpp Update Mount.h Update PartitionIterator.cpp Update PartitionIterator.h Update PartitionIterator.h Update PartitionQuery.cpp Update PartitionQuery.h Update PartitionSize.cpp Update PartitionSize.h Update Sync.cpp Update Sync.h Update Tests.cpp Update Tests.h Update BoostPython.h Update CalamaresUtilsSystem.cpp Update CalamaresUtilsSystem.h Update CommandList.cpp Update CommandList.h Update Dirs.cpp Update Dirs.h Update Entropy.cpp Update Entropy.h Update Entropy.cpp Update Logger.cpp Update Logger.h Update NamedEnum.h Update NamedSuffix.h Update PluginFactory.cpp Update PluginFactory.h Update RAII.h Update RAII.h Update Retranslator.cpp Update Retranslator.h Update String.cpp Update String.h Update TestPaths.cpp Update Tests.cpp Update Tests.h Update UMask.cpp Update UMask.h Update Units.h Update Variant.cpp Update Variant.h Update Yaml.cpp Update Yaml.h Update moc-warnings.h
2020-05-30 16:15:03 +02:00
*
*/
#include "PythonJob.h"
#include "CalamaresVersion.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "PythonHelper.h"
#include "PythonJobApi.h"
#include "utils/BoostPython.h"
#include "utils/Logger.h"
#include <QDir>
static const char* s_preScript = nullptr;
namespace bp = boost::python;
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
#endif
BOOST_PYTHON_FUNCTION_OVERLOADS( mount_overloads, CalamaresPython::mount, 2, 4 );
BOOST_PYTHON_FUNCTION_OVERLOADS( target_env_call_str_overloads, CalamaresPython::target_env_call, 1, 3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( target_env_call_list_overloads, CalamaresPython::target_env_call, 1, 3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_call_str_overloads, CalamaresPython::check_target_env_call, 1, 3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_call_list_overloads, CalamaresPython::check_target_env_call, 1, 3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_output_str_overloads,
CalamaresPython::check_target_env_output,
1,
3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( check_target_env_output_list_overloads,
CalamaresPython::check_target_env_output,
1,
3 );
BOOST_PYTHON_FUNCTION_OVERLOADS( target_env_process_output_overloads,
CalamaresPython::target_env_process_output,
1,
4 );
BOOST_PYTHON_FUNCTION_OVERLOADS( host_env_process_output_overloads, CalamaresPython::host_env_process_output, 1, 4 );
#ifdef __clang__
#pragma clang diagnostic pop
#endif
BOOST_PYTHON_MODULE( libcalamares )
{
bp::object package = bp::scope();
package.attr( "__path__" ) = "libcalamares";
bp::scope().attr( "ORGANIZATION_NAME" ) = CALAMARES_ORGANIZATION_NAME;
bp::scope().attr( "ORGANIZATION_DOMAIN" ) = CALAMARES_ORGANIZATION_DOMAIN;
bp::scope().attr( "APPLICATION_NAME" ) = CALAMARES_APPLICATION_NAME;
bp::scope().attr( "VERSION" ) = CALAMARES_VERSION;
bp::scope().attr( "VERSION_SHORT" ) = CALAMARES_VERSION_SHORT;
bp::class_< CalamaresPython::PythonJobInterface >( "Job", bp::init< Calamares::PythonJob* >() )
.def_readonly( "module_name", &CalamaresPython::PythonJobInterface::moduleName )
.def_readonly( "pretty_name", &CalamaresPython::PythonJobInterface::prettyName )
.def_readonly( "working_path", &CalamaresPython::PythonJobInterface::workingPath )
2014-07-22 18:05:58 +02:00
.def_readonly( "configuration", &CalamaresPython::PythonJobInterface::configuration )
.def( "setprogress",
&CalamaresPython::PythonJobInterface::setprogress,
bp::args( "progress" ),
"Reports the progress status of this job to Calamares, "
"as a real number between 0 and 1." );
bp::class_< CalamaresPython::GlobalStoragePythonWrapper >( "GlobalStorage",
bp::init< Calamares::GlobalStorage* >() )
.def( "contains", &CalamaresPython::GlobalStoragePythonWrapper::contains )
.def( "count", &CalamaresPython::GlobalStoragePythonWrapper::count )
.def( "insert", &CalamaresPython::GlobalStoragePythonWrapper::insert )
.def( "keys", &CalamaresPython::GlobalStoragePythonWrapper::keys )
.def( "remove", &CalamaresPython::GlobalStoragePythonWrapper::remove )
.def( "value", &CalamaresPython::GlobalStoragePythonWrapper::value );
// libcalamares.utils submodule starts here
2014-08-04 17:06:16 +02:00
bp::object utilsModule( bp::handle<>( bp::borrowed( PyImport_AddModule( "libcalamares.utils" ) ) ) );
bp::scope().attr( "utils" ) = utilsModule;
bp::scope utilsScope = utilsModule;
Q_UNUSED( utilsScope )
// .. Logging functions
2014-08-05 13:23:04 +02:00
bp::def(
"debug", &CalamaresPython::debug, bp::args( "s" ), "Writes the given string to the Calamares debug stream." );
bp::def( "warning",
&CalamaresPython::warning,
bp::args( "s" ),
"Writes the given string to the Calamares warning stream." );
bp::def( "warn",
&CalamaresPython::warning,
bp::args( "s" ),
"Writes the given string to the Calamares warning stream." );
bp::def(
"error", &CalamaresPython::error, bp::args( "s" ), "Writes the given string to the Calamares error stream." );
// .. YAML functions
bp::def( "load_yaml", &CalamaresPython::load_yaml, bp::args( "path" ), "Loads YAML from a file." );
// .. Filesystem functions
bp::def( "mount",
&CalamaresPython::mount,
mount_overloads( bp::args( "device_path", "mount_point", "filesystem_name", "options" ),
"Runs the mount utility with the specified parameters.\n"
"Returns the program's exit code, or:\n"
"-1 = QProcess crash\n"
"-2 = QProcess cannot start\n"
"-3 = bad arguments" ) );
// .. Process functions
bp::def(
"target_env_call",
static_cast< int ( * )( const std::string&, const std::string&, int ) >( &CalamaresPython::target_env_call ),
target_env_call_str_overloads( bp::args( "command", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns the program's exit code, or:\n"
"-1 = QProcess crash\n"
"-2 = QProcess cannot start\n"
"-3 = bad arguments\n"
"-4 = QProcess timeout" ) );
bp::def( "target_env_call",
static_cast< int ( * )( const bp::list&, const std::string&, int ) >( &CalamaresPython::target_env_call ),
target_env_call_list_overloads( bp::args( "args", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns the program's exit code, or:\n"
"-1 = QProcess crash\n"
"-2 = QProcess cannot start\n"
"-3 = bad arguments\n"
"-4 = QProcess timeout" ) );
bp::def( "check_target_env_call",
static_cast< int ( * )( const std::string&, const std::string&, int ) >(
&CalamaresPython::check_target_env_call ),
check_target_env_call_str_overloads( bp::args( "command", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns 0, which is program's exit code if the program exited "
"successfully, or raises a subprocess.CalledProcessError." ) );
bp::def(
"check_target_env_call",
static_cast< int ( * )( const bp::list&, const std::string&, int ) >( &CalamaresPython::check_target_env_call ),
check_target_env_call_list_overloads( bp::args( "args", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns 0, which is program's exit code if the program exited "
"successfully, or raises a subprocess.CalledProcessError." ) );
bp::def( "check_target_env_output",
static_cast< std::string ( * )( const std::string&, const std::string&, int ) >(
&CalamaresPython::check_target_env_output ),
check_target_env_output_str_overloads( bp::args( "command", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns the program's standard output, and raises a "
"subprocess.CalledProcessError if something went wrong." ) );
bp::def( "check_target_env_output",
static_cast< std::string ( * )( const bp::list&, const std::string&, int ) >(
&CalamaresPython::check_target_env_output ),
check_target_env_output_list_overloads( bp::args( "args", "stdin", "timeout" ),
"Runs the specified command in the chroot of the target system.\n"
"Returns the program's standard output, and raises a "
"subprocess.CalledProcessError if something went wrong." ) );
bp::def( "target_env_process_output",
&CalamaresPython::target_env_process_output,
target_env_process_output_overloads( bp::args( "command", "callback", "stdin", "timeout" ),
"Runs the specified @p command in the target system." ) );
bp::def( "host_env_process_output",
&CalamaresPython::host_env_process_output,
host_env_process_output_overloads( bp::args( "command", "callback", "stdin", "timeout" ),
"Runs the specified command in the host system." ) );
// .. String functions
bp::def( "obscure",
&CalamaresPython::obscure,
bp::args( "s" ),
"Simple string obfuscation function based on KStringHandler::obscure.\n"
"Returns a string, generated using a simple symmetric encryption.\n"
"Applying the function to a string obscured by this function will result "
"in the original string." );
// .. Translation functions
bp::def( "gettext_languages",
&CalamaresPython::gettext_languages,
"Returns list of languages (most to least-specific) for gettext." );
bp::def( "gettext_path", &CalamaresPython::gettext_path, "Returns path for gettext search." );
}
namespace Calamares
{
struct PythonJob::Private
{
bp::object m_prettyStatusMessage;
};
PythonJob::PythonJob( const QString& scriptFile,
const QString& workingPath,
const QVariantMap& moduleConfiguration,
QObject* parent )
: Job( parent )
, m_d( std::make_unique< Private >() )
, m_scriptFile( scriptFile )
, m_workingPath( workingPath )
, m_description()
, m_configurationMap( moduleConfiguration )
{
}
PythonJob::~PythonJob() {}
QString
PythonJob::prettyName() const
{
2015-06-13 01:16:11 +02:00
return QDir( m_workingPath ).dirName();
}
QString
PythonJob::prettyStatusMessage() const
2015-06-13 01:16:11 +02:00
{
// The description is updated when progress is reported, see emitProgress()
if ( m_description.isEmpty() )
{
2023-10-15 13:39:17 +02:00
return tr( "Running %1 operation…", "@status" ).arg( QDir( m_workingPath ).dirName() );
}
else
{
return m_description;
}
}
static QString
pythonStringMethod( bp::dict& script, const char* funcName )
{
bp::object func = script.get( funcName, bp::object() );
if ( !func.is_none() )
{
bp::extract< std::string > result( func() );
return result.check() ? QString::fromStdString( result() ).trimmed() : QString();
}
return QString();
}
JobResult
PythonJob::exec()
{
// We assume m_scriptFile to be relative to m_workingPath.
QDir workingDir( m_workingPath );
if ( !workingDir.exists() || !workingDir.isReadable() )
{
2023-11-07 23:43:09 +01:00
return JobResult::error( tr( "Bad working directory path", "@error" ),
tr( "Working directory %1 for python job %2 is not readable.", "@error" )
.arg( m_workingPath )
.arg( prettyName() ) );
}
QFileInfo scriptFI( workingDir.absoluteFilePath( m_scriptFile ) );
if ( !scriptFI.exists() || !scriptFI.isFile() || !scriptFI.isReadable() )
{
2023-10-15 13:39:17 +02:00
return JobResult::error( tr( "Bad main script file", "@error" ),
tr( "Main script file %1 for python job %2 is not readable.", "@error" )
.arg( scriptFI.absoluteFilePath() )
.arg( prettyName() ) );
}
try
{
bp::dict scriptNamespace = CalamaresPython::Helper::instance()->createCleanNamespace();
bp::object calamaresModule = bp::import( "libcalamares" );
bp::dict calamaresNamespace = bp::extract< bp::dict >( calamaresModule.attr( "__dict__" ) );
2014-07-18 11:59:12 +02:00
calamaresNamespace[ "job" ] = CalamaresPython::PythonJobInterface( this );
calamaresNamespace[ "globalstorage" ]
= CalamaresPython::GlobalStoragePythonWrapper( JobQueue::instance()->globalStorage() );
if ( s_preScript )
{
bp::exec( s_preScript, scriptNamespace, scriptNamespace );
}
cDebug() << "Job file" << scriptFI.absoluteFilePath();
bp::object execResult
= bp::exec_file( scriptFI.absoluteFilePath().toLocal8Bit().data(), scriptNamespace, scriptNamespace );
bp::object entryPoint = scriptNamespace[ "run" ];
m_d->m_prettyStatusMessage = scriptNamespace.get( "pretty_status_message", bp::object() );
m_description = pythonStringMethod( scriptNamespace, "pretty_name" );
if ( m_description.isEmpty() )
{
bp::extract< std::string > entryPoint_doc_attr( entryPoint.attr( "__doc__" ) );
if ( entryPoint_doc_attr.check() )
{
m_description = QString::fromStdString( entryPoint_doc_attr() ).trimmed();
auto i_newline = m_description.indexOf( '\n' );
if ( i_newline > 0 )
{
m_description.truncate( i_newline );
}
cDebug() << Logger::SubEntry << "Job description from __doc__" << prettyName() << '=' << m_description;
}
}
else
{
cDebug() << Logger::SubEntry << "Job description from pretty_name" << prettyName() << '=' << m_description;
}
emit progress( 0 );
bp::object runResult = entryPoint();
if ( runResult.is_none() )
{
return JobResult::ok();
}
else // Something happened in the Python job
{
bp::tuple resultTuple = bp::extract< bp::tuple >( runResult );
QString message = QString::fromStdString( bp::extract< std::string >( resultTuple[ 0 ] ) );
QString description = QString::fromStdString( bp::extract< std::string >( resultTuple[ 1 ] ) );
2014-08-01 09:56:40 +02:00
return JobResult::error( message, description );
}
}
catch ( bp::error_already_set& )
{
QString msg;
if ( PyErr_Occurred() )
{
msg = CalamaresPython::Helper::instance()->handleLastError();
}
bp::handle_exception();
PyErr_Clear();
2023-11-07 23:43:09 +01:00
return JobResult::internalError( tr( "Boost.Python error in job \"%1\"", "@error" ).arg( prettyName() ),
msg,
JobResult::PythonUncaughtException );
}
}
2014-07-22 18:05:58 +02:00
void
2014-07-23 12:54:53 +02:00
PythonJob::emitProgress( qreal progressValue )
2014-07-22 18:05:58 +02:00
{
// This is called from the JobApi (and only from there) from the Job thread,
// so it is safe to call into the Python interpreter. Update the description
// as needed (don't call this from prettyStatusMessage(), which can be
// called from other threads as well).
if ( m_d && !m_d->m_prettyStatusMessage.is_none() )
{
QString r;
bp::extract< std::string > result( m_d->m_prettyStatusMessage() );
r = result.check() ? QString::fromStdString( result() ).trimmed() : QString();
if ( !r.isEmpty() )
{
m_description = r;
}
}
2014-07-23 12:54:53 +02:00
emit progress( progressValue );
2014-07-22 18:05:58 +02:00
}
void
PythonJob::setInjectedPreScript( const char* preScript )
{
s_preScript = preScript;
cDebug() << "Python pre-script set to string" << Logger::Pointer( preScript ) << "length"
<< ( preScript ? strlen( preScript ) : 0 );
}
} // namespace Calamares