/* === This file is part of Calamares - === * * SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac * SPDX-FileCopyrightText: 2017-2020 Adriaan de Groot * SPDX-License-Identifier: GPL-3.0-or-later * * Calamares is Free Software: see the License-Identifier above. * */ #include "PythonJobApi.h" #include "GlobalStorage.h" #include "JobQueue.h" #include "PythonHelper.h" #include "locale/Global.h" #include "partition/Mount.h" #include "utils/Logger.h" #include "utils/RAII.h" #include "utils/Runner.h" #include "utils/String.h" #include "utils/System.h" #include "utils/Yaml.h" #include #include #include namespace bp = boost::python; static int handle_check_target_env_call_error( const Calamares::ProcessResult& ec, const QString& cmd ) { if ( !ec.first ) { return ec.first; } QString raise = QString( "import subprocess\n" "e = subprocess.CalledProcessError(%1,\"%2\")\n" ) .arg( ec.first ) .arg( cmd ); if ( !ec.second.isEmpty() ) { raise.append( QStringLiteral( "e.output = \"\"\"%1\"\"\"\n" ).arg( ec.second ) ); } raise.append( "raise e" ); bp::exec( raise.toStdString().c_str() ); bp::throw_error_already_set(); return ec.first; } static inline QStringList bp_list_to_qstringlist( const bp::list& args ) { QStringList list; for ( int i = 0; i < bp::len( args ); ++i ) { list.append( QString::fromStdString( bp::extract< std::string >( args[ i ] ) ) ); } return list; } static inline Calamares::ProcessResult target_env_command( const QStringList& args, const std::string& input, int timeout ) { // Since Python doesn't give us the type system for distinguishing // seconds from other integral types, massage to seconds here. return Calamares::System::instance()->targetEnvCommand( args, QString(), QString::fromStdString( input ), std::chrono::seconds( timeout ) ); } namespace CalamaresPython { int mount( const std::string& device_path, const std::string& mount_point, const std::string& filesystem_name, const std::string& options ) { return Calamares::Partition::mount( QString::fromStdString( device_path ), QString::fromStdString( mount_point ), QString::fromStdString( filesystem_name ), QString::fromStdString( options ) ); } int target_env_call( const std::string& command, const std::string& input, int timeout ) { return target_env_command( QStringList { QString::fromStdString( command ) }, input, timeout ).first; } int target_env_call( const bp::list& args, const std::string& input, int timeout ) { return target_env_command( bp_list_to_qstringlist( args ), input, timeout ).first; } int check_target_env_call( const std::string& command, const std::string& input, int timeout ) { auto ec = target_env_command( QStringList { QString::fromStdString( command ) }, input, timeout ); return handle_check_target_env_call_error( ec, QString::fromStdString( command ) ); } int check_target_env_call( const bp::list& args, const std::string& input, int timeout ) { auto ec = target_env_command( bp_list_to_qstringlist( args ), input, timeout ); if ( !ec.first ) { return ec.first; } QStringList failedCmdList = bp_list_to_qstringlist( args ); return handle_check_target_env_call_error( ec, failedCmdList.join( ' ' ) ); } std::string check_target_env_output( const std::string& command, const std::string& input, int timeout ) { auto ec = target_env_command( QStringList { QString::fromStdString( command ) }, input, timeout ); handle_check_target_env_call_error( ec, QString::fromStdString( command ) ); return ec.second.toStdString(); } std::string check_target_env_output( const bp::list& args, const std::string& input, int timeout ) { QStringList list = bp_list_to_qstringlist( args ); auto ec = target_env_command( list, input, timeout ); handle_check_target_env_call_error( ec, list.join( ' ' ) ); return ec.second.toStdString(); } static const char output_prefix[] = "[PYTHON JOB]:"; static inline void log_action( unsigned int level, const std::string& s ) { Logger::CDebug( level ) << output_prefix << QString::fromStdString( s ); } void debug( const std::string& s ) { log_action( Logger::LOGDEBUG, s ); } void warning( const std::string& s ) { log_action( Logger::LOGWARNING, s ); } void error( const std::string& s ) { log_action( Logger::LOGERROR, s ); } boost::python::dict load_yaml( const std::string& path ) { const QString filePath = QString::fromStdString( path ); bool ok = false; auto map = Calamares::YAML::load( filePath, &ok ); if ( !ok ) { cWarning() << "Loading YAML from" << filePath << "failed."; } return variantMapToPyDict( map ); } PythonJobInterface::PythonJobInterface( Calamares::PythonJob* parent ) : m_parent( parent ) { auto moduleDir = QDir( m_parent->m_workingPath ); moduleName = moduleDir.dirName().toStdString(); prettyName = m_parent->prettyName().toStdString(); workingPath = m_parent->m_workingPath.toStdString(); configuration = CalamaresPython::variantMapToPyDict( m_parent->m_configurationMap ); } void PythonJobInterface::setprogress( qreal progress ) { if ( progress >= 0.0 && progress <= 1.0 ) { m_parent->emitProgress( progress ); } } static inline int _process_output( Calamares::Utils::RunLocation location, const boost::python::list& args, const boost::python::object& callback, const std::string& input, int timeout ) { Calamares::Utils::Runner r( bp_list_to_qstringlist( args ) ); r.setLocation( location ); if ( !callback.is_none() ) { bp::extract< bp::list > x( callback ); if ( x.check() ) { QObject::connect( &r, &decltype( r )::output, [ cb = callback.attr( "append" ) ]( const QString& s ) { cb( s.toStdString() ); } ); } else { QObject::connect( &r, &decltype( r )::output, [ &callback ]( const QString& s ) { callback( s.toStdString() ); } ); } r.enableOutputProcessing(); } if ( !input.empty() ) { r.setInput( QString::fromStdString( input ) ); } if ( timeout > 0 ) { r.setTimeout( std::chrono::seconds( timeout ) ); } auto result = r.run(); if ( result.getExitCode() ) { return handle_check_target_env_call_error( result, r.executable() ); } return 0; } int target_env_process_output( const boost::python::list& args, const boost::python::object& callback, const std::string& input, int timeout ) { return _process_output( Calamares::Utils::RunLocation::RunInTarget, args, callback, input, timeout ); } int host_env_process_output( const boost::python::list& args, const boost::python::object& callback, const std::string& input, int timeout ) { return _process_output( Calamares::Utils::RunLocation::RunInHost, args, callback, input, timeout ); } std::string obscure( const std::string& string ) { return Calamares::String::obscure( QString::fromStdString( string ) ).toStdString(); } static QStringList _gettext_languages() { QStringList languages; // There are two ways that Python jobs can be initialised: // - through JobQueue, in which case that has an instance which holds // a GlobalStorage object, or // - through the Python test-script, which initialises its // own GlobalStoragePythonWrapper, which then holds a // GlobalStorage object for all of Python. Calamares::JobQueue* jq = Calamares::JobQueue::instance(); Calamares::GlobalStorage* gs = jq ? jq->globalStorage() : CalamaresPython::GlobalStoragePythonWrapper::globalStorageInstance(); QString lang = Calamares::Locale::readGS( *gs, QStringLiteral( "LANG" ) ); if ( !lang.isEmpty() ) { languages.append( lang ); if ( lang.indexOf( '.' ) > 0 ) { lang.truncate( lang.indexOf( '.' ) ); languages.append( lang ); } if ( lang.indexOf( '_' ) > 0 ) { lang.truncate( lang.indexOf( '_' ) ); languages.append( lang ); } } return languages; } bp::list gettext_languages() { bp::list pyList; for ( auto lang : _gettext_languages() ) { pyList.append( lang.toStdString() ); } return pyList; } static void _add_localedirs( QStringList& pathList, const QString& candidate ) { if ( !candidate.isEmpty() && !pathList.contains( candidate ) ) { pathList.prepend( candidate ); if ( QDir( candidate ).cd( "lang" ) ) { pathList.prepend( candidate + "/lang" ); } } } bp::object gettext_path() { // Going to log informatively just once static bool first_time = true; cScopedAssignment( &first_time, false ); // TODO: distinguish between -d runs and normal runs // TODO: can we detect DESTDIR-installs? QStringList candidatePaths = QStandardPaths::locateAll( QStandardPaths::GenericDataLocation, "locale", QStandardPaths::LocateDirectory ); QString extra = QCoreApplication::applicationDirPath(); _add_localedirs( candidatePaths, extra ); // Often /usr/local/bin if ( !extra.isEmpty() ) { QDir d( extra ); if ( d.cd( "../share/locale" ) ) // Often /usr/local/bin/../share/locale -> /usr/local/share/locale { _add_localedirs( candidatePaths, d.canonicalPath() ); } } _add_localedirs( candidatePaths, QDir().canonicalPath() ); // . if ( first_time ) { cDebug() << "Determining gettext path from" << candidatePaths; } QStringList candidateLanguages = _gettext_languages(); for ( const auto& lang : candidateLanguages ) { for ( auto localedir : candidatePaths ) { QDir ldir( localedir ); if ( ldir.cd( lang ) ) { Logger::CDebug( Logger::LOGDEBUG ) << output_prefix << "Found gettext" << lang << "in" << ldir.canonicalPath(); return bp::object( localedir.toStdString() ); } } } cWarning() << "No translation found for languages" << candidateLanguages; return bp::object(); // None } } // namespace CalamaresPython