diff --git a/CMakeLists.txt b/CMakeLists.txt index 113a491a1..0a9b26aee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() option( INSTALL_CONFIG "Install configuration files" ON ) option( WITH_PYTHON "Enable Python modules API (requires Boost.Python)." ON ) -option( WITH_PYTHONQT "Enable next generation Python modules API (experimental, requires PythonQt)." OFF ) +option( WITH_PYTHONQT "Enable next generation Python modules API (experimental, requires PythonQt)." ON ) option( WITH_KF5Crash "Enable crash reporting with KCrash." ON ) option( BUILD_TESTING "Build the testing tree." ON ) diff --git a/ci/HACKING.md b/ci/HACKING.md index dfc768be9..ca3901f07 100644 --- a/ci/HACKING.md +++ b/ci/HACKING.md @@ -44,7 +44,7 @@ it's just a typo-fix which might not be copyrightable in all jurisdictions). Formatting C++ -------------- This formatting guide applies to C++ code only; for Python modules, we use -[pycodestyle][https://github.com/PyCQA/pycodestyle] to apply a check of +[pycodestyle](https://github.com/PyCQA/pycodestyle) to apply a check of some PEP8 guidelines. * Spaces, not tabs. diff --git a/src/calamares/CMakeLists.txt b/src/calamares/CMakeLists.txt index 270abbb88..e1f8e4236 100644 --- a/src/calamares/CMakeLists.txt +++ b/src/calamares/CMakeLists.txt @@ -68,3 +68,9 @@ install( FILES ${CMAKE_SOURCE_DIR}/data/images/squid.svg RENAME calamares.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps ) + +if( BUILD_TESTING ) + add_executable( loadmodule testmain.cpp ) + target_link_libraries( loadmodule ${CALAMARES_LIBRARIES} Qt5::Core Qt5::Widgets calamaresui ) + # Don't install, it's just for enable_testing +endif() diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp new file mode 100644 index 000000000..c22342f9a --- /dev/null +++ b/src/calamares/testmain.cpp @@ -0,0 +1,192 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +/* + * This executable loads and runs a Calamares Python module + * within a C++ application, in order to test the different + * bindings. + */ + +#include "utils/Logger.h" +#include "utils/YamlUtils.h" +#include "modulesystem/Module.h" + +#include "Settings.h" +#include "Job.h" +#include "JobQueue.h" + +#include +#include +#include +#include + +#include + +struct ModuleConfig : public QPair< QString, QString > +{ + ModuleConfig( const QString& a, const QString& b ) : QPair< QString, QString >(a, b) { } + ModuleConfig() : QPair< QString, QString >( QString(), QString() ) { } + + QString moduleName() const { return first; } + QString configFile() const { return second; } +} ; + +static ModuleConfig +handle_args( QCoreApplication& a ) +{ + QCommandLineOption debugLevelOption( QStringLiteral("D"), + "Verbose output for debugging purposes (0-8).", "level" ); + + QCommandLineParser parser; + parser.setApplicationDescription( "Calamares module tester" ); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption( debugLevelOption ); + parser.addPositionalArgument( "module", "Path or name of module to run." ); + + parser.process( a ); + + if ( parser.isSet( debugLevelOption ) ) + { + bool ok = true; + int l = parser.value( debugLevelOption ).toInt( &ok ); + unsigned int dlevel = 0; + if ( !ok || ( l < 0 ) ) + dlevel = Logger::LOGVERBOSE; + else + dlevel = l; + Logger::setupLogLevel( dlevel ); + } + + const QStringList args = parser.positionalArguments(); + if ( args.isEmpty() ) + { + cError() << "Missing path.\n"; + parser.showHelp(); + return ModuleConfig(); // NOTREACHED + } + if ( args.size() > 2 ) + { + cError() << "More than one path.\n"; + parser.showHelp(); + return ModuleConfig(); // NOTREACHED + } + + return ModuleConfig( args.first(), args.size() == 2 ? args.at(1) : QString() ); +} + + +static Calamares::Module* +load_module( const ModuleConfig& moduleConfig ) +{ + QString moduleName = moduleConfig.moduleName(); + QFileInfo fi; + + bool ok = false; + QVariantMap descriptor; + + for ( const QString& prefix : QStringList{ "./", "src/modules/", "modules/" } ) + { + // Could be a complete path, eg. src/modules/dummycpp/module.desc + fi = QFileInfo( prefix + moduleName ); + if ( fi.exists() && fi.isFile() ) + descriptor = CalamaresUtils::loadYaml( fi, &ok ); + if ( ok ) + break; + + // Could be a path without module.desc + fi = QFileInfo( prefix + moduleName ); + if ( fi.exists() && fi.isDir() ) + { + fi = QFileInfo( prefix + moduleName + "/module.desc" ); + if ( fi.exists() && fi.isFile() ) + descriptor = CalamaresUtils::loadYaml( fi, &ok ); + if ( ok ) break; + } + } + + if ( !ok ) + { + cWarning() << "No suitable module descriptor found."; + return nullptr; + } + + QString name = descriptor.value( "name" ).toString(); + if ( name.isEmpty() ) + { + cWarning() << "No name found in module descriptor" << fi.absoluteFilePath(); + return nullptr; + } + + QString moduleDirectory = fi.absolutePath(); + QString configFile( + moduleConfig.configFile().isEmpty() + ? moduleDirectory + '/' + name + ".conf" + : moduleConfig.configFile() ); + + Calamares::Module* module = Calamares::Module::fromDescriptor( + descriptor, name, configFile, moduleDirectory ); + + return module; +} + +int +main( int argc, char* argv[] ) +{ + QCoreApplication a( argc, argv ); + + ModuleConfig module = handle_args( a ); + if ( module.moduleName().isEmpty() ) + return 1; + + std::unique_ptr< Calamares::Settings > settings_p( new Calamares::Settings( QString(), true ) ); + std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); + + cDebug() << "Calamares test module-loader" << module.moduleName(); + Calamares::Module* m = load_module( module ); + if ( !m ) + { + cError() << "Could not load module" << module.moduleName(); + return 1; + } + + if ( !m->isLoaded() ) + m->loadSelf(); + + if ( !m->isLoaded() ) + { + cError() << "Module" << module.moduleName() << "could not be loaded."; + return 1; + } + + cDebug() << "Module" << m->name() << m->typeString() << m->interfaceString(); + + Calamares::JobList jobList = m->jobs(); + unsigned int count = 1; + for ( const auto& p : jobList ) + { + cDebug() << count << p->prettyName(); + Calamares::JobResult r = p->exec(); + if ( !r ) + cDebug() << count << ".. failed" << r; + ++count; + } + + return 0; +} diff --git a/src/libcalamares/GlobalStorage.cpp b/src/libcalamares/GlobalStorage.cpp index fd72697cf..4f98ea2eb 100644 --- a/src/libcalamares/GlobalStorage.cpp +++ b/src/libcalamares/GlobalStorage.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2015, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,9 @@ #include "utils/Logger.h" +#include +#include + #ifdef WITH_PYTHON #include "PythonHelper.h" @@ -94,6 +97,19 @@ GlobalStorage::debugDump() const } } +bool +GlobalStorage::save(const QString& filename) +{ + QFile f( filename ); + if ( !f.open( QFile::WriteOnly ) ) + return false; + + f.write( QJsonDocument::fromVariant( m ).toJson() ) ; + f.close(); + return true; +} + + } // namespace Calamares #ifdef WITH_PYTHON diff --git a/src/libcalamares/GlobalStorage.h b/src/libcalamares/GlobalStorage.h index dc79c55e8..72524ba4f 100644 --- a/src/libcalamares/GlobalStorage.h +++ b/src/libcalamares/GlobalStorage.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014-2015, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -58,7 +58,16 @@ public: int remove( const QString& key ); QVariant value( const QString& key ) const; + /// @brief dump keys and values to the debug log void debugDump() const; + /** @brief write as JSON to the given filename + * + * No tidying, sanitization, or censoring is done -- for instance, + * the user module sets a slightly-obscured password in global storage, + * and this JSON file will contain that password in-the-only-slightly- + * obscured form. + */ + bool save( const QString& filename ); signals: void changed(); diff --git a/src/libcalamares/Settings.cpp b/src/libcalamares/Settings.cpp index 06178c621..732afa8d8 100644 --- a/src/libcalamares/Settings.cpp +++ b/src/libcalamares/Settings.cpp @@ -156,12 +156,12 @@ Settings::Settings( const QString& settingsFilePath, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what() << "in" << file.fileName(); + CalamaresUtils::explainYamlException( e, ba, file.fileName() ); } } else { - cWarning() << "Cannot read " << file.fileName(); + cWarning() << "Cannot read settings file" << file.fileName(); } s_instance = this; diff --git a/src/libcalamares/utils/CommandList.cpp b/src/libcalamares/utils/CommandList.cpp index 3b2935c55..8e332a066 100644 --- a/src/libcalamares/utils/CommandList.cpp +++ b/src/libcalamares/utils/CommandList.cpp @@ -98,9 +98,19 @@ CommandList::~CommandList() { } +static inline bool +findInCommands( const CommandList& l, const QString& needle ) +{ + for ( CommandList::const_iterator i = l.cbegin(); i != l.cend(); ++i ) + if ( i->command().contains( needle ) ) + return true; + return false; +} + Calamares::JobResult CommandList::run() { QLatin1Literal rootMagic( "@@ROOT@@" ); + QLatin1Literal userMagic( "@@USER@@" ); System::RunLocation location = m_doChroot ? System::RunLocation::RunInTarget : System::RunLocation::RunInHost; @@ -108,14 +118,7 @@ Calamares::JobResult CommandList::run() QString root = QStringLiteral( "/" ); Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - bool needsRootSubstitution = false; - for ( CommandList::const_iterator i = cbegin(); i != cend(); ++i ) - if ( i->command().contains( rootMagic ) ) - { - needsRootSubstitution = true; - break; - } - + bool needsRootSubstitution = findInCommands( *this, rootMagic ); if ( needsRootSubstitution && ( location == System::RunLocation::RunInHost ) ) { if ( !gs || !gs->contains( "rootMountPoint" ) ) @@ -127,10 +130,20 @@ Calamares::JobResult CommandList::run() root = gs->value( "rootMountPoint" ).toString(); } + bool needsUserSubstitution = findInCommands( *this, userMagic ); + if ( needsUserSubstitution && ( !gs || !gs->contains( "username" ) ) ) + { + cError() << "No username defined."; + return Calamares::JobResult::error( + QCoreApplication::translate( "CommandList", "Could not run command." ), + QCoreApplication::translate( "CommandList", "The command needs to know the user's name, but no username is defined." ) ); + } + QString user = gs->value( "username" ).toString(); // may be blank if unset + for ( CommandList::const_iterator i = cbegin(); i != cend(); ++i ) { QString processed_cmd = i->command(); - processed_cmd.replace( rootMagic, root ); + processed_cmd.replace( rootMagic, root ).replace( userMagic, user ); bool suppress_result = false; if ( processed_cmd.startsWith( '-' ) ) { diff --git a/src/libcalamares/utils/CommandList.h b/src/libcalamares/utils/CommandList.h index b766259c0..9faf705f2 100644 --- a/src/libcalamares/utils/CommandList.h +++ b/src/libcalamares/utils/CommandList.h @@ -74,6 +74,9 @@ using CommandList_t = QList< CommandLine >; * A list of commands; the list may have its own default timeout * for commands (which is then applied to each individual command * that doesn't have one of its own). + * + * Documentation for the format of commands can be found in + * `shellprocess.conf`. */ class CommandList : protected CommandList_t { diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp index 0a13881d3..735414b85 100644 --- a/src/libcalamares/utils/Logger.cpp +++ b/src/libcalamares/utils/Logger.cpp @@ -116,7 +116,7 @@ CalamaresLogHandler( QtMsgType type, const QMessageLogContext& context, const QS } -static QString +QString logFile() { return CalamaresUtils::appLogDir().filePath( "session.log" ); diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index f7488b553..dba386eae 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -64,6 +64,11 @@ namespace Logger virtual ~CDebug(); }; + /** + * @brief The full path of the log file. + */ + DLLEXPORT QString logFile(); + /** * @brief Start logging to the log file. * diff --git a/src/libcalamares/utils/YamlUtils.cpp b/src/libcalamares/utils/YamlUtils.cpp index 962cbd1da..474ced2a1 100644 --- a/src/libcalamares/utils/YamlUtils.cpp +++ b/src/libcalamares/utils/YamlUtils.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include #include +#include +#include #include void @@ -112,6 +114,19 @@ void explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, const char *label ) { cWarning() << "YAML error " << e.what() << "in" << label << '.'; + explainYamlException( e, yamlData ); +} + +void +explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, const QString& label ) +{ + cWarning() << "YAML error " << e.what() << "in" << label << '.'; + explainYamlException( e, yamlData ); +} + +void +explainYamlException( const YAML::Exception& e, const QByteArray& yamlData ) +{ if ( ( e.mark.line >= 0 ) && ( e.mark.column >= 0 ) ) { // Try to show the line where it happened. @@ -146,4 +161,46 @@ explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, cons } } +QVariantMap +loadYaml(const QFileInfo& fi, bool* ok) +{ + return loadYaml( fi.absoluteFilePath(), ok ); +} + +QVariantMap +loadYaml(const QString& filename, bool* ok) +{ + if ( ok ) + *ok = false; + + QFile descriptorFile( filename ); + QVariant moduleDescriptor; + if ( descriptorFile.exists() && descriptorFile.open( QFile::ReadOnly | QFile::Text ) ) + { + QByteArray ba = descriptorFile.readAll(); + try + { + YAML::Node doc = YAML::Load( ba.constData() ); + moduleDescriptor = CalamaresUtils::yamlToVariant( doc ); + } + catch ( YAML::Exception& e ) + { + explainYamlException( e, ba, filename ); + return QVariantMap(); + } + } + + + if ( moduleDescriptor.isValid() && + !moduleDescriptor.isNull() && + moduleDescriptor.type() == QVariant::Map ) + { + if ( ok ) + *ok = true; + return moduleDescriptor.toMap(); + } + + return QVariantMap(); +} + } // namespace diff --git a/src/libcalamares/utils/YamlUtils.h b/src/libcalamares/utils/YamlUtils.h index 33ee8dca0..268c0bdc7 100644 --- a/src/libcalamares/utils/YamlUtils.h +++ b/src/libcalamares/utils/YamlUtils.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ #include class QByteArray; +class QFileInfo; namespace YAML { @@ -35,6 +36,15 @@ void operator>>( const YAML::Node& node, QStringList& v ); namespace CalamaresUtils { +/** + * Loads a given @p filename and returns the YAML data + * as a QVariantMap. If filename doesn't exist, or is + * malformed in some way, returns an empty map and sets + * @p *ok to false. Otherwise sets @p *ok to true. + */ +QVariantMap loadYaml( const QString& filename, bool* ok = nullptr ); +/** Convenience overload. */ +QVariantMap loadYaml( const QFileInfo&, bool* ok = nullptr ); QVariant yamlToVariant( const YAML::Node& node ); QVariant yamlScalarToVariant( const YAML::Node& scalarNode ); @@ -47,6 +57,8 @@ QVariant yamlMapToVariant( const YAML::Node& mapNode ); * Uses @p label when labeling the data source (e.g. "netinstall data") */ void explainYamlException( const YAML::Exception& e, const QByteArray& data, const char *label ); +void explainYamlException( const YAML::Exception& e, const QByteArray& data, const QString& label ); +void explainYamlException( const YAML::Exception& e, const QByteArray& data ); } //ns diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index 584d85c0b..c71b1daa5 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -179,7 +179,7 @@ Branding::Branding( const QString& brandingFilePath, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what() << "in" << file.fileName(); + CalamaresUtils::explainYamlException( e, ba, file.fileName() ); } QDir translationsDir( componentDir.filePath( "lang" ) ); @@ -192,7 +192,7 @@ Branding::Branding( const QString& brandingFilePath, } else { - cWarning() << "Cannot read " << file.fileName(); + cWarning() << "Cannot read branding file" << file.fileName(); } s_instance = this; diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index f80f4d48b..05394e69f 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -86,7 +86,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, #ifdef WITH_PYTHONQT m = new PythonQtViewModule(); #else - cError() << "PythonQt modules are not supported in this version of Calamares."; + cError() << "PythonQt view modules are not supported in this version of Calamares."; #endif } else @@ -118,7 +118,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, if ( !m ) { - cDebug() << "Bad module type (" << typeString + cError() << "Bad module type (" << typeString << ") or interface string (" << intfString << ") for module " << instanceId; return nullptr; @@ -129,8 +129,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, m->m_directory = moduleDir.absolutePath(); else { - cError() << "Bad module directory" << moduleDirectory - << "for" << instanceId; + cError() << "Bad module directory" << moduleDirectory << "for" << instanceId; delete m; return nullptr; } @@ -144,7 +143,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what(); + cError() << "YAML parser error " << e.what(); delete m; return nullptr; } diff --git a/src/libcalamaresui/modulesystem/ModuleManager.cpp b/src/libcalamaresui/modulesystem/ModuleManager.cpp index cbee61129..83273e924 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.cpp +++ b/src/libcalamaresui/modulesystem/ModuleManager.cpp @@ -103,39 +103,16 @@ ModuleManager::doInit() continue; } - QFile descriptorFile( descriptorFileInfo.absoluteFilePath() ); - QVariant moduleDescriptor; - if ( descriptorFile.exists() && descriptorFile.open( QFile::ReadOnly | QFile::Text ) ) + bool ok = false; + QVariantMap moduleDescriptorMap = CalamaresUtils::loadYaml( descriptorFileInfo, &ok ); + QString moduleName = ok ? moduleDescriptorMap.value( "name" ).toString() : QString(); + + if ( ok && ( moduleName == currentDir.dirName() ) && + !m_availableDescriptorsByModuleName.contains( moduleName ) ) { - QByteArray ba = descriptorFile.readAll(); - try - { - YAML::Node doc = YAML::Load( ba.constData() ); - - moduleDescriptor = CalamaresUtils::yamlToVariant( doc ); - } - catch ( YAML::Exception& e ) - { - cWarning() << "YAML parser error " << e.what(); - continue; - } - } - - - if ( moduleDescriptor.isValid() && - !moduleDescriptor.isNull() && - moduleDescriptor.type() == QVariant::Map ) - { - QVariantMap moduleDescriptorMap = moduleDescriptor.toMap(); - - if ( moduleDescriptorMap.value( "name" ) == currentDir.dirName() && - !m_availableDescriptorsByModuleName.contains( moduleDescriptorMap.value( "name" ).toString() ) ) - { - m_availableDescriptorsByModuleName.insert( moduleDescriptorMap.value( "name" ).toString(), - moduleDescriptorMap ); - m_moduleDirectoriesByModuleName.insert( moduleDescriptorMap.value( "name" ).toString(), - descriptorFileInfo.absoluteDir().absolutePath() ); - } + m_availableDescriptorsByModuleName.insert( moduleName, moduleDescriptorMap ); + m_moduleDirectoriesByModuleName.insert( moduleName, + descriptorFileInfo.absoluteDir().absolutePath() ); } } else diff --git a/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp index e2b497f2e..486d9a8e2 100644 --- a/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp +++ b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp @@ -143,12 +143,12 @@ PythonQtViewModule::loadSelf() return; } - QString calamares_module_annotation = + static const QLatin1Literal calamares_module_annotation( "_calamares_module_typename = ''\n" "def calamares_module(viewmodule_type):\n" " global _calamares_module_typename\n" " _calamares_module_typename = viewmodule_type.__name__\n" - " return viewmodule_type\n"; + " return viewmodule_type\n"); // Load in the decorator PythonQt::self()->evalScript( cxt, calamares_module_annotation ); diff --git a/src/modules/partition/core/DeviceList.cpp b/src/modules/partition/core/DeviceList.cpp index d7e6bab29..ebc40a03d 100644 --- a/src/modules/partition/core/DeviceList.cpp +++ b/src/modules/partition/core/DeviceList.cpp @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2015-2016, Teo Mrnjavac + * Copyright 2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,22 +53,6 @@ hasRootPartition( Device* device ) return false; } -/* Unused */ -static bool -hasMountedPartitions( Device* device ) -{ - cDebug() << "Checking for mounted partitions in" << device->deviceNode(); - for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it ) - { - if ( ! ( *it )->isMounted() ) - { - cDebug() << " .." << ( *it )->partitionPath() << "is mounted on" << ( *it )->mountPoint(); - return true; - } - } - return false; -} - static bool isIso9660( const Device* device ) { diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp index 2c2944997..a39cb5395 100644 --- a/src/modules/partition/core/PartUtils.cpp +++ b/src/modules/partition/core/PartUtils.cpp @@ -1,6 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2015-2016, Teo Mrnjavac + * Copyright 2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -139,7 +140,6 @@ canBeResized( PartitionCoreModule* core, const QString& partitionPath ) if ( partitionWithOs.startsWith( "/dev/" ) ) { cDebug() << partitionWithOs << "seems like a good path"; - bool canResize = false; DeviceModel* dm = core->deviceModel(); for ( int i = 0; i < dm->rowCount(); ++i ) { diff --git a/src/modules/preservefiles/CMakeLists.txt b/src/modules/preservefiles/CMakeLists.txt new file mode 100644 index 000000000..43602024c --- /dev/null +++ b/src/modules/preservefiles/CMakeLists.txt @@ -0,0 +1,11 @@ +include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui ) + +calamares_add_plugin( preservefiles + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + PreserveFiles.cpp + LINK_PRIVATE_LIBRARIES + calamares + SHARED_LIB +) diff --git a/src/modules/preservefiles/PreserveFiles.cpp b/src/modules/preservefiles/PreserveFiles.cpp new file mode 100644 index 000000000..29564fcd1 --- /dev/null +++ b/src/modules/preservefiles/PreserveFiles.cpp @@ -0,0 +1,206 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PreserveFiles.h" + +#include "CalamaresVersion.h" +#include "JobQueue.h" +#include "GlobalStorage.h" + +#include "utils/CalamaresUtils.h" +#include "utils/CalamaresUtilsSystem.h" +#include "utils/CommandList.h" +#include "utils/Logger.h" +#include "utils/Units.h" + +#include + +using CalamaresUtils::operator""_MiB; + +QString targetPrefix() +{ + if ( CalamaresUtils::System::instance()->doChroot() ) + { + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs && gs->contains( "rootMountPoint" ) ) + { + QString r = gs->value( "rootMountPoint" ).toString(); + if ( !r.isEmpty() ) + return r; + else + cDebug() << "RootMountPoint is empty"; + } + else + { + cDebug() << "No rootMountPoint defined, preserving files to '/'"; + } + } + + return QLatin1Literal( "/" ); +} + +QString atReplacements( QString s ) +{ + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QString root( "/" ); + QString user; + + if ( gs && gs->contains( "rootMountPoint" ) ) + root = gs->value( "rootMountPoint" ).toString(); + if ( gs && gs->contains( "username" ) ) + user = gs->value( "username" ).toString(); + + return s.replace( "@@ROOT@@", root ).replace( "@@USER@@", user ); +} + +PreserveFiles::PreserveFiles( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + +PreserveFiles::~PreserveFiles() +{ +} + +QString +PreserveFiles::prettyName() const +{ + return tr( "Saving files for later ..." ); +} + +Calamares::JobResult PreserveFiles::exec() +{ + if ( m_items.isEmpty() ) + return Calamares::JobResult::error( tr( "No files configured to save for later." ) ); + + QString prefix = targetPrefix(); + if ( !prefix.endsWith( '/' ) ) + prefix.append( '/' ); + + int count = 0; + for ( const auto it : m_items ) + { + QString source = it.source; + QString dest = prefix + atReplacements( it.dest ); + + if ( it.type == ItemType::Log ) + source = Logger::logFile(); + if ( it.type == ItemType::Config ) + { + if ( Calamares::JobQueue::instance()->globalStorage()->save( dest ) ) + cWarning() << "Could not write config for" << dest; + else + ++count; + } + else if ( source.isEmpty() ) + cWarning() << "Skipping unnamed source file for" << dest; + else + { + QFile sourcef( source ); + if ( !sourcef.open( QFile::ReadOnly ) ) + { + cWarning() << "Could not read" << source; + continue; + } + + QFile destf( dest ); + if ( !destf.open( QFile::WriteOnly ) ) + { + sourcef.close(); + cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source; + continue; + } + + QByteArray b; + do + { + b = sourcef.read( 1_MiB ); + destf.write( b ); + } + while ( b.count() > 0 ); + + sourcef.close(); + destf.close(); + ++count; + } + } + + return count == m_items.count() ? + Calamares::JobResult::ok() : + Calamares::JobResult::error( tr( "Not all of the configured files could be preserved." ) ); +} + +void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap) +{ + auto files = configurationMap[ "files" ]; + if ( !files.isValid() ) + { + cDebug() << "No 'files' key for preservefiles."; + return; + } + + if ( files.type() != QVariant::List ) + { + cDebug() << "Configuration key 'files' is not a list for preservefiles."; + return; + } + + QVariantList l = files.toList(); + unsigned int c = 0; + for ( const auto li : l ) + { + if ( li.type() == QVariant::String ) + { + QString filename = li.toString(); + if ( !filename.isEmpty() ) + m_items.append( Item{ filename, filename, ItemType::Path } ); + else + cDebug() << "Empty filename for preservefiles, item" << c; + } + else if ( li.type() == QVariant::Map ) + { + const auto map = li.toMap(); + QString dest = map[ "dest" ].toString(); + QString from = map[ "from" ].toString(); + ItemType t = + ( from == "log" ) ? ItemType::Log : + ( from == "config" ) ? ItemType::Config : + ItemType::None; + + if ( dest.isEmpty() ) + { + cDebug() << "Empty dest for preservefiles, item" << c; + } + else if ( t == ItemType::None ) + { + cDebug() << "Invalid type for preservefiles, item" << c; + } + else + { + m_items.append( Item{ QString(), dest, t } ); + } + } + else + cDebug() << "Invalid type for preservefiles, item" << c; + + ++c; + } +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( PreserveFilesFactory, registerPlugin(); ) + diff --git a/src/modules/preservefiles/PreserveFiles.h b/src/modules/preservefiles/PreserveFiles.h new file mode 100644 index 000000000..0c9216336 --- /dev/null +++ b/src/modules/preservefiles/PreserveFiles.h @@ -0,0 +1,70 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef PRESERVEFILES_H +#define PRESERVEFILES_H + +#include +#include +#include + +#include "CppJob.h" + +#include "utils/PluginFactory.h" + +#include "PluginDllMacro.h" + + +class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob +{ + Q_OBJECT + + enum class ItemType + { + None, + Path, + Log, + Config + } ; + + struct Item + { + QString source; + QString dest; + ItemType type; + } ; + + using ItemList = QList< Item >; + +public: + explicit PreserveFiles( QObject* parent = nullptr ); + virtual ~PreserveFiles() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + ItemList m_items; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( PreserveFilesFactory ) + +#endif // PRESERVEFILES_H diff --git a/src/modules/preservefiles/module.desc b/src/modules/preservefiles/module.desc new file mode 100644 index 000000000..953d8c81b --- /dev/null +++ b/src/modules/preservefiles/module.desc @@ -0,0 +1,5 @@ +--- +type: "job" +name: "preservefiles" +interface: "qtplugin" +load: "libcalamares_job_preservefiles.so" diff --git a/src/modules/preservefiles/preservefiles.conf b/src/modules/preservefiles/preservefiles.conf new file mode 100644 index 000000000..ab9114d20 --- /dev/null +++ b/src/modules/preservefiles/preservefiles.conf @@ -0,0 +1,36 @@ +# Configuration for the preserve-files job +# +# The *files* key contains a list of files to preserve. Each element of +# the list should have one of these forms: +# +# - an absolute path (probably within the host system). This will be preserved +# as the same path within the target system (chroot). If, globally, dontChroot +# is true, then these items are ignored (since the destination is the same +# as the source). +# - a map with a *dest* key. The *dest* value is a path interpreted in the +# target system (if dontChroot is true, in the host system). Relative paths +# are not recommended. There are two possible other keys in the map: +# - *from*, which must have one of the values, below; it is used to +# preserve files whose pathname is known to Calamares internally. +# - *src*, to refer to a path interpreted in the host system. Relative +# paths are not recommended, and are interpreted relative to where +# Calamares is being run. +# Only one of the two other keys (either *from* or *src*) may be set. +# +# The target filename is modified as follows: +# - `@@ROOT@@` is replaced by the path to the target root (may be /) +# - `@@USER@@` is replaced by the username entered by on the user +# page (may be empty, for instance if no user page is enabled) +# +# Special values for the key *from* are: +# - *log*, for the complete log file (up to the moment the preservefiles +# module is run), +# - *config*, for the Calamares configuration file +# - *globals*, for a JSON dump of the contents of global storage +--- +files: + - /etc/oem-information + - from: log + dest: /root/install.log + - from: config + dest: /root/install.cfg diff --git a/src/modules/shellprocess/Tests.cpp b/src/modules/shellprocess/Tests.cpp index c6643325f..7ebb8e624 100644 --- a/src/modules/shellprocess/Tests.cpp +++ b/src/modules/shellprocess/Tests.cpp @@ -18,7 +18,12 @@ #include "Tests.h" +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "Settings.h" + #include "utils/CommandList.h" +#include "utils/Logger.h" #include "utils/YamlUtils.h" #include @@ -149,3 +154,60 @@ script: QCOMPARE( cl.at(0).command(), QStringLiteral( "ls /tmp" ) ); QCOMPARE( cl.at(1).timeout(), -1 ); // not set } + +void ShellProcessTests::testRootSubstitution() +{ + YAML::Node doc = YAML::Load( R"(--- +script: + - "ls /tmp" +)" ); + QVariant plainScript = CalamaresUtils::yamlMapToVariant( doc ).toMap().value( "script" ); + QVariant rootScript = CalamaresUtils::yamlMapToVariant( + YAML::Load( R"(--- +script: + - "ls @@ROOT@@" +)" ) ).toMap().value( "script" ); + QVariant userScript = CalamaresUtils::yamlMapToVariant( + YAML::Load( R"(--- +script: + - mktemp -d @@ROOT@@/calatestXXXXXXXX + - "chown @@USER@@ @@ROOT@@/calatest*" + - rm -rf @@ROOT@@/calatest* +)" ) ).toMap().value( "script" ); + + if ( !Calamares::JobQueue::instance() ) + (void *)new Calamares::JobQueue( nullptr ); + if ( !Calamares::Settings::instance() ) + (void *)new Calamares::Settings( QString(), true ); + + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QVERIFY( gs != nullptr ); + + qDebug() << "Expect WARNING, ERROR, WARNING"; + // Doesn't use @@ROOT@@, so no failures + QVERIFY( bool(CommandList(plainScript, false, 10 ).run()) ); + + // Doesn't use @@ROOT@@, but does chroot, so fails + QVERIFY( !bool(CommandList(plainScript, true, 10 ).run()) ); + + // Does use @@ROOT@@, which is not set, so fails + QVERIFY( !bool(CommandList(rootScript, false, 10 ).run()) ); + // .. fails for two reasons + QVERIFY( !bool(CommandList(rootScript, true, 10 ).run()) ); + + gs->insert( "rootMountPoint", "/tmp" ); + // Now that the root is set, two variants work .. still can't + // chroot, unless the rootMountPoint contains a full system, + // *and* we're allowed to chroot (ie. running tests as root). + qDebug() << "Expect no output."; + QVERIFY( bool(CommandList(plainScript, false, 10 ).run()) ); + QVERIFY( bool(CommandList(rootScript, false, 10 ).run()) ); + + qDebug() << "Expect ERROR"; + // But no user set yet + QVERIFY( !bool(CommandList(userScript, false, 10 ).run()) ); + + // Now play dangerous games with shell expansion + gs->insert( "username", "`id -u`" ); + QVERIFY( bool(CommandList(userScript, false, 10 ).run()) ); +} diff --git a/src/modules/shellprocess/Tests.h b/src/modules/shellprocess/Tests.h index af1f78487..5b4ebebbb 100644 --- a/src/modules/shellprocess/Tests.h +++ b/src/modules/shellprocess/Tests.h @@ -40,6 +40,8 @@ private Q_SLOTS: void testProcessFromObject(); // Create from a complex YAML list void testProcessListFromObject(); + // Check @@ROOT@@ substitution + void testRootSubstitution(); }; #endif diff --git a/src/modules/shellprocess/shellprocess.conf b/src/modules/shellprocess/shellprocess.conf index ff53dc228..4734aaadd 100644 --- a/src/modules/shellprocess/shellprocess.conf +++ b/src/modules/shellprocess/shellprocess.conf @@ -4,9 +4,11 @@ # If the top-level key *dontChroot* is true, then the commands # are executed in the context of the live system, otherwise # in the context of the target system. In all of the commands, -# `@@ROOT@@` is replaced by the root mount point of the **target** -# system from the point of view of the command (for chrooted -# commands, that will be */*). +# the following substitutions will take place: +# - `@@ROOT@@` is replaced by the root mount point of the **target** +# system from the point of view of the command (for chrooted +# commands, that will be */*). +# - `@@USER@@` is replaced by the username, set on the user page. # # The (global) timeout for the command list can be set with # the *timeout* key. The value is a time in seconds, default diff --git a/src/modules/users/CreateUserJob.cpp b/src/modules/users/CreateUserJob.cpp index 119028059..052af87c6 100644 --- a/src/modules/users/CreateUserJob.cpp +++ b/src/modules/users/CreateUserJob.cpp @@ -161,29 +161,23 @@ CreateUserJob::exec() return pres.explainProcess( useradd, 10 /* bogus timeout */ ); } - int ec = CalamaresUtils::System::instance()-> - targetEnvCall( { "usermod", - "-aG", - defaultGroups, - m_userName } ); - if ( ec ) - return Calamares::JobResult::error( tr( "Cannot add user %1 to groups: %2." ) - .arg( m_userName ) - .arg( defaultGroups ), - tr( "usermod terminated with error code %1." ) - .arg( ec ) ); + pres = CalamaresUtils::System::instance()->targetEnvCommand( + { "usermod", "-aG", defaultGroups, m_userName } ); + if ( pres.getExitCode() ) + { + cError() << "usermod failed" << pres.getExitCode(); + return pres.explainProcess( "usermod", 10 ); + } - ec = CalamaresUtils::System::instance()-> - targetEnvCall( { "chown", - "-R", - QString( "%1:%2" ).arg( m_userName ) - .arg( m_userName ), - QString( "/home/%1" ).arg( m_userName ) } ); - if ( ec ) - return Calamares::JobResult::error( tr( "Cannot set home directory ownership for user %1." ) - .arg( m_userName ), - tr( "chown terminated with error code %1." ) - .arg( ec ) ); + QString userGroup = QString( "%1:%2" ).arg( m_userName ).arg( m_userName ); + QString homeDir = QString( "/home/%1" ).arg( m_userName ); + pres = CalamaresUtils::System::instance()->targetEnvCommand( + { "chown", "-R", userGroup, homeDir } ); + if ( pres.getExitCode() ) + { + cError() << "chown failed" << pres.getExitCode(); + return pres.explainProcess( "chown", 10 ); + } return Calamares::JobResult::ok(); }