/* === This file is part of Calamares - === * * SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac * SPDX-FileCopyrightText: 2019 Gabriel Craciunescu * SPDX-FileCopyrightText: 2019 Dominic Hayes * SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot * SPDX-License-Identifier: GPL-3.0-or-later * * 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 "Settings.h" #include "CalamaresConfig.h" #include "utils/Dirs.h" #include "utils/Logger.h" #include "utils/Yaml.h" #include #include #include static bool hasValue( const YAML::Node& v ) { return v.IsDefined() && !v.IsNull(); } /** @brief Helper function to grab a QString out of the config, and to warn if not present. */ static QString requireString( const YAML::Node& config, const char* key ) { auto v = config[ key ]; if ( hasValue( v ) ) { return QString::fromStdString( v.as< std::string >() ); } else { cWarning() << Logger::SubEntry << "Required settings.conf key" << key << "is missing."; return QString(); } } /** @brief Helper function to grab a bool out of the config, and to warn if not present. */ static bool requireBool( const YAML::Node& config, const char* key, bool d ) { auto v = config[ key ]; if ( hasValue( v ) ) { return v.as< bool >(); } else { cWarning() << Logger::SubEntry << "Required settings.conf key" << key << "is missing."; return d; } } namespace Calamares { InstanceDescription::InstanceDescription( const Calamares::ModuleSystem::InstanceKey& key ) : m_instanceKey( key ) , m_weight( 1 ) { if ( !isValid() ) { m_weight = 0; } } InstanceDescription::InstanceDescription( Calamares::ModuleSystem::InstanceKey&& key, int weight ) : m_instanceKey( key ) , m_weight( qBound( 1, weight, 100 ) ) { if ( !isValid() ) { m_weight = 0; } } InstanceDescription InstanceDescription::fromSettings( const QVariantMap& m ) { InstanceDescription r( Calamares::ModuleSystem::InstanceKey( m.value( "module" ).toString(), m.value( "id" ).toString() ), m.value( "weight" ).toInt() ); if ( r.isValid() ) { r.m_configFileName = m.value( "config" ).toString(); if ( r.m_configFileName.isEmpty() ) { r.m_configFileName = r.key().module() + QStringLiteral( ".conf" ); } } return r; } Settings* Settings::s_instance = nullptr; Settings* Settings::instance() { return s_instance; } static void interpretModulesSearch( const bool debugMode, const QStringList& rawPaths, QStringList& output ) { for ( const auto& path : rawPaths ) { if ( path == "local" ) { // If we're running in debug mode, we assume we might also be // running from the build dir, so we add a maximum priority // module search path in the build dir. if ( debugMode ) { QString buildDirModules = QDir::current().absolutePath() + QDir::separator() + "src" + QDir::separator() + "modules"; if ( QDir( buildDirModules ).exists() ) { output.append( buildDirModules ); } } // Install path is set in CalamaresAddPlugin.cmake output.append( CalamaresUtils::systemLibDir().absolutePath() + QDir::separator() + "calamares" + QDir::separator() + "modules" ); } else { QDir d( path ); if ( d.exists() && d.isReadable() ) { output.append( d.absolutePath() ); } else { cDebug() << Logger::SubEntry << "module-search entry non-existent" << path; } } } } static void interpretInstances( const YAML::Node& node, Settings::InstanceDescriptionList& customInstances ) { // Parse the custom instances section if ( node ) { QVariant instancesV = CalamaresUtils::yamlToVariant( node ).toList(); if ( instancesV.type() == QVariant::List ) { const auto instances = instancesV.toList(); for ( const QVariant& instancesVListItem : instances ) { if ( instancesVListItem.type() != QVariant::Map ) { continue; } customInstances.append( InstanceDescription::fromSettings( instancesVListItem.toMap() ) ); } } } } static void interpretSequence( const YAML::Node& node, Settings::ModuleSequence& moduleSequence ) { // Parse the modules sequence section if ( node ) { QVariant sequenceV = CalamaresUtils::yamlToVariant( node ); if ( !( sequenceV.type() == QVariant::List ) ) { throw YAML::Exception( YAML::Mark(), "sequence key does not have a list-value" ); } const auto sequence = sequenceV.toList(); for ( const QVariant& sequenceVListItem : sequence ) { if ( sequenceVListItem.type() != QVariant::Map ) { continue; } QString thisActionS = sequenceVListItem.toMap().firstKey(); ModuleSystem::Action thisAction; if ( thisActionS == "show" ) { thisAction = ModuleSystem::Action::Show; } else if ( thisActionS == "exec" ) { thisAction = ModuleSystem::Action::Exec; } else { continue; } QStringList thisActionRoster = sequenceVListItem.toMap().value( thisActionS ).toStringList(); moduleSequence.append( qMakePair( thisAction, thisActionRoster ) ); } } else { throw YAML::Exception( YAML::Mark(), "sequence key is missing" ); } } Settings::Settings( bool debugMode ) : QObject() , m_debug( debugMode ) , m_doChroot( true ) , m_promptInstall( false ) , m_disableCancel( false ) , m_disableCancelDuringExec( false ) { } Settings::Settings( const QString& settingsFilePath, bool debugMode ) : QObject() , m_debug( debugMode ) , m_doChroot( true ) , m_promptInstall( false ) , m_disableCancel( false ) , m_disableCancelDuringExec( false ) { cDebug() << "Using Calamares settings file at" << settingsFilePath; QFile file( settingsFilePath ); if ( file.exists() && file.open( QFile::ReadOnly | QFile::Text ) ) { setConfiguration( file.readAll(), file.fileName() ); } else { cWarning() << "Cannot read settings file" << file.fileName(); } s_instance = this; } void Settings::validateSequence() { // Since moduleFinder captures targetKey by reference, we can // update targetKey to change what the finder lambda looks for. Calamares::ModuleSystem::InstanceKey targetKey; auto moduleFinder = [&targetKey]( const InstanceDescription& d ) { return d.isValid() && d.key() == targetKey; }; // Check the sequence against the existing instances (which so far are only custom) for ( const auto& step : m_modulesSequence ) { for ( const auto& instance : step.second ) { Calamares::ModuleSystem::InstanceKey k = Calamares::ModuleSystem::InstanceKey::fromString( instance ); if ( !k.isValid() ) { cWarning() << "Invalid instance key in *sequence*," << instance; } if ( k.isValid() && k.isCustom() ) { targetKey = k; const auto it = std::find_if( m_moduleInstances.constBegin(), m_moduleInstances.constEnd(), moduleFinder ); if ( it == m_moduleInstances.constEnd() ) { cWarning() << "Custom instance key" << instance << "is not listed in the *instances*"; // don't add it, let this fail later. } } if ( k.isValid() && !k.isCustom() ) { targetKey = k; const auto it = std::find_if( m_moduleInstances.constBegin(), m_moduleInstances.constEnd(), moduleFinder ); if ( it == m_moduleInstances.constEnd() ) { // Non-custom instance, just mentioned in *sequence* m_moduleInstances.append( InstanceDescription( k ) ); } } } } } void Settings::setConfiguration( const QByteArray& ba, const QString& explainName ) { try { YAML::Node config = YAML::Load( ba.constData() ); Q_ASSERT( config.IsMap() ); interpretModulesSearch( debugMode(), CalamaresUtils::yamlToStringList( config[ "modules-search" ] ), m_modulesSearchPaths ); interpretInstances( config[ "instances" ], m_moduleInstances ); interpretSequence( config[ "sequence" ], m_modulesSequence ); m_brandingComponentName = requireString( config, "branding" ); m_promptInstall = requireBool( config, "prompt-install", false ); m_doChroot = !requireBool( config, "dont-chroot", false ); m_isSetupMode = requireBool( config, "oem-setup", !m_doChroot ); m_disableCancel = requireBool( config, "disable-cancel", false ); m_disableCancelDuringExec = requireBool( config, "disable-cancel-during-exec", false ); m_quitAtEnd = requireBool( config, "quit-at-end", false ); validateSequence(); } catch ( YAML::Exception& e ) { CalamaresUtils::explainYamlException( e, ba, explainName ); } } QStringList Settings::modulesSearchPaths() const { return m_modulesSearchPaths; } Settings::InstanceDescriptionList Settings::moduleInstances() const { return m_moduleInstances; } Settings::ModuleSequence Settings::modulesSequence() const { return m_modulesSequence; } QString Settings::brandingComponentName() const { return m_brandingComponentName; } static QStringList settingsFileCandidates( bool assumeBuilddir ) { static const char settings[] = "settings.conf"; QStringList settingsPaths; if ( CalamaresUtils::isAppDataDirOverridden() ) { settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); } else { if ( assumeBuilddir ) { settingsPaths << QDir::current().absoluteFilePath( settings ); } if ( CalamaresUtils::haveExtraDirs() ) for ( auto s : CalamaresUtils::extraConfigDirs() ) { settingsPaths << ( s + settings ); } settingsPaths << CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/settings.conf"; // String concat settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); } return settingsPaths; } Settings* Settings::init( bool debugMode ) { if ( s_instance ) { cWarning() << "Calamares::Settings already created"; return s_instance; } QStringList settingsFileCandidatesByPriority = settingsFileCandidates( debugMode ); QFileInfo settingsFile; bool found = false; foreach ( const QString& path, settingsFileCandidatesByPriority ) { QFileInfo pathFi( path ); if ( pathFi.exists() && pathFi.isReadable() ) { settingsFile = pathFi; found = true; break; } } if ( !found || !settingsFile.exists() || !settingsFile.isReadable() ) { cError() << "Cowardly refusing to continue startup without settings." << Logger::DebugList( settingsFileCandidatesByPriority ); if ( CalamaresUtils::isAppDataDirOverridden() ) { cError() << "FATAL: explicitly configured application data directory is missing settings.conf"; } else { cError() << "FATAL: none of the expected configuration file paths exist."; } ::exit( EXIT_FAILURE ); } auto* settings = new Calamares::Settings( settingsFile.absoluteFilePath(), debugMode ); // Creates singleton if ( settings->modulesSequence().count() < 1 ) { cError() << "FATAL: no sequence set."; ::exit( EXIT_FAILURE ); } return settings; } Settings* Settings::init( const QString& path ) { if ( s_instance ) { cWarning() << "Calamares::Settings already created"; return s_instance; } return new Calamares::Settings( path, true ); } } // namespace Calamares