Merge branch 'master' of https://github.com/calamares/calamares into development

This commit is contained in:
Philip 2018-05-26 11:07:07 +02:00
commit e31cb0a77b
28 changed files with 763 additions and 101 deletions

View File

@ -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 )

View File

@ -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.

View File

@ -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()

192
src/calamares/testmain.cpp Normal file
View File

@ -0,0 +1,192 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <QCommandLineOption>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QFileInfo>
#include <memory>
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 <module> path.\n";
parser.showHelp();
return ModuleConfig(); // NOTREACHED
}
if ( args.size() > 2 )
{
cError() << "More than one <module> 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;
}

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2017, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
*
* 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 <QFile>
#include <QJsonDocument>
#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

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014-2015, Teo Mrnjavac <teo@kde.org>
* Copyright 2017, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
*
* 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();

View File

@ -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;

View File

@ -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( '-' ) )
{

View File

@ -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
{

View File

@ -116,7 +116,7 @@ CalamaresLogHandler( QtMsgType type, const QMessageLogContext& context, const QS
}
static QString
QString
logFile()
{
return CalamaresUtils::appLogDir().filePath( "session.log" );

View File

@ -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.
*

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014, Teo Mrnjavac <teo@kde.org>
* Copyright 2017, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
*
* 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 <yaml-cpp/yaml.h>
#include <QByteArray>
#include <QFile>
#include <QFileInfo>
#include <QRegExp>
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

View File

@ -1,7 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2014, Teo Mrnjavac <teo@kde.org>
* Copyright 2017, Adriaan de Groot <groot@kde.org>
* Copyright 2017-2018, Adriaan de Groot <groot@kde.org>
*
* 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 <QVariant>
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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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 );

View File

@ -1,6 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 )
{

View File

@ -1,6 +1,7 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2015-2016, Teo Mrnjavac <teo@kde.org>
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 )
{

View File

@ -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
)

View File

@ -0,0 +1,206 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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 <QFile>
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<PreserveFiles>(); )

View File

@ -0,0 +1,70 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2018, Adriaan de Groot <groot@kde.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef PRESERVEFILES_H
#define PRESERVEFILES_H
#include <QList>
#include <QObject>
#include <QVariantMap>
#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

View File

@ -0,0 +1,5 @@
---
type: "job"
name: "preservefiles"
interface: "qtplugin"
load: "libcalamares_job_preservefiles.so"

View File

@ -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

View File

@ -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 <yaml-cpp/yaml.h>
@ -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()) );
}

View File

@ -40,6 +40,8 @@ private Q_SLOTS:
void testProcessFromObject();
// Create from a complex YAML list
void testProcessListFromObject();
// Check @@ROOT@@ substitution
void testRootSubstitution();
};
#endif

View File

@ -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

View File

@ -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();
}