diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h index 3eaa251ef..3a68297ca 100644 --- a/src/libcalamares/Job.h +++ b/src/libcalamares/Job.h @@ -46,11 +46,16 @@ public: InvalidConfiguration = 2 }; + // Can't copy, but you can keep a temporary JobResult( const JobResult& rhs ) = delete; JobResult( JobResult&& rhs ); virtual ~JobResult() {} + /** @brief Is this JobResult a success? + * + * Equivalent to errorCode() == 0, might be named isValid(). + */ virtual operator bool() const; virtual QString message() const; @@ -63,9 +68,16 @@ public: /// @brief an "ok status" result static JobResult ok(); - /// @brief an "error" result resulting from the execution of the job + /** @brief an "error" result resulting from the execution of the job + * + * The error code is set to GenericError. + */ static JobResult error( const QString& message, const QString& details = QString() ); - /// @brief an "internal error" meaning the job itself has a problem (usually for python) + /** @brief an "internal error" meaning the job itself has a problem (usually for python) + * + * Pass in a suitable error code; using 0 (which would normally mean "ok") instead + * gives you a GenericError code. + */ static JobResult internalError( const QString&, const QString& details, int errorCode ); protected: diff --git a/src/modules/machineid/CMakeLists.txt b/src/modules/machineid/CMakeLists.txt new file mode 100644 index 000000000..efb6454e8 --- /dev/null +++ b/src/modules/machineid/CMakeLists.txt @@ -0,0 +1,24 @@ +calamares_add_plugin( machineid + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + MachineIdJob.cpp + Workers.cpp + LINK_PRIVATE_LIBRARIES + calamares + SHARED_LIB +) + +if ( ECM_FOUND AND BUILD_TESTING ) + ecm_add_test( + Tests.cpp + Workers.cpp + TEST_NAME + machineidtest + LINK_LIBRARIES + calamares + Qt5::Core + Qt5::Test + ) + calamares_automoc( machineidtest ) +endif() diff --git a/src/modules/machineid/MachineIdJob.cpp b/src/modules/machineid/MachineIdJob.cpp new file mode 100644 index 000000000..393950ded --- /dev/null +++ b/src/modules/machineid/MachineIdJob.cpp @@ -0,0 +1,153 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Kevin Kofler + * Copyright 2016, Philip Müller + * Copyright 2017, Alf Gaida + * Copyright 2019, 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 "MachineIdJob.h" +#include "Workers.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" + +#include + +MachineIdJob::MachineIdJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + + +MachineIdJob::~MachineIdJob() {} + + +QString +MachineIdJob::prettyName() const +{ + return tr( "Generate machine-id." ); +} + +Calamares::JobResult +MachineIdJob::exec() +{ + QString root; + + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs && gs->contains( "rootMountPoint" ) ) + { + root = gs->value( "rootMountPoint" ).toString(); + } + else + { + cWarning() << "No *rootMountPoint* defined."; + return Calamares::JobResult::internalError( tr( "Configuration Error" ), + tr( "No root mount point is set for MachineId." ), + Calamares::JobResult::InvalidConfiguration ); + } + + QString target_systemd_machineid_file = QStringLiteral( "/etc/machine-id" ); + QString target_dbus_machineid_file = QStringLiteral( "/var/lib/dbus/machine-id" ); + QString target_entropy_file = QStringLiteral( "/var/lib/urandom/random-seed" ); + + // Clear existing files + if ( m_entropy ) + { + MachineId::removeFile( root, target_entropy_file ); + } + if ( m_dbus ) + { + MachineId::removeFile( root, target_dbus_machineid_file ); + } + if ( m_systemd ) + { + MachineId::removeFile( root, target_systemd_machineid_file ); + } + + //Create new files + if ( m_entropy ) + { + auto r = MachineId::createEntropy( m_entropy_copy ? MachineId::EntropyGeneration::CopyFromHost + : MachineId::EntropyGeneration::New, + root, + target_entropy_file ); + if ( !r ) + { + return r; + } + } + if ( m_systemd ) + { + auto r = MachineId::createSystemdMachineId( root, target_systemd_machineid_file ); + if ( !r ) + { + return r; + } + } + if ( m_dbus ) + { + if ( m_dbus_symlink && QFile::exists( root + target_systemd_machineid_file ) ) + { + auto r = MachineId::createDBusLink( root, target_dbus_machineid_file, target_systemd_machineid_file ); + if ( !r ) + { + return r; + } + } + else + { + auto r = MachineId::createDBusMachineId( root, target_dbus_machineid_file ); + if ( !r ) + { + return r; + } + } + } + + return Calamares::JobResult::ok(); +} + + +void +MachineIdJob::setConfigurationMap( const QVariantMap& map ) +{ + m_systemd = CalamaresUtils::getBool( map, "systemd", false ); + + m_dbus = CalamaresUtils::getBool( map, "dbus", false ); + if ( map.contains( "dbus-symlink" ) ) + { + m_dbus_symlink = CalamaresUtils::getBool( map, "dbus-symlink", false ); + } + else if ( map.contains( "symlink" ) ) + { + m_dbus_symlink = CalamaresUtils::getBool( map, "symlink", false ); + cWarning() << "MachineId: configuration setting *symlink* is deprecated, use *dbus-symlink*."; + } + // else it's still false from the constructor + + // ignore it, though, if dbus is false + m_dbus_symlink = m_dbus && m_dbus_symlink; + + m_entropy = CalamaresUtils::getBool( map, "entropy", false ); + m_entropy_copy = CalamaresUtils::getBool( map, "entropy-copy", false ); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( MachineIdJobFactory, registerPlugin< MachineIdJob >(); ) diff --git a/src/modules/machineid/MachineIdJob.h b/src/modules/machineid/MachineIdJob.h new file mode 100644 index 000000000..5d44b2017 --- /dev/null +++ b/src/modules/machineid/MachineIdJob.h @@ -0,0 +1,57 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, 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 MACHINEIDJOB_H +#define MACHINEIDJOB_H + +#include +#include + +#include + +#include + +#include + +class PLUGINDLLEXPORT MachineIdJob : public Calamares::CppJob +{ + Q_OBJECT + +public: + explicit MachineIdJob( QObject* parent = nullptr ); + virtual ~MachineIdJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + +private: + bool m_systemd = false; ///< write systemd's files + + bool m_dbus = false; ///< write dbus files + bool m_dbus_symlink = false; ///< .. or just symlink to systemd + + bool m_entropy = false; ///< write an entropy file + bool m_entropy_copy = false; ///< copy from host system +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( MachineIdJobFactory ) + +#endif // MACHINEIDJOB_H diff --git a/src/modules/machineid/Tests.cpp b/src/modules/machineid/Tests.cpp new file mode 100644 index 000000000..cfd96bbdd --- /dev/null +++ b/src/modules/machineid/Tests.cpp @@ -0,0 +1,108 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, 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 "Workers.h" + +#include "utils/Logger.h" + + +#include +#include +#include + +class MachineIdTests : public QObject +{ + Q_OBJECT +public: + MachineIdTests() {} + virtual ~MachineIdTests() {} + +private Q_SLOTS: + void initTestCase(); + + void testRemoveFile(); + void testCopyFile(); + + void testPoolSize(); +}; + +void +MachineIdTests::initTestCase() +{ + Logger::setupLogLevel( Logger::LOGDEBUG ); +} + +void +MachineIdTests::testCopyFile() +{ + QTemporaryDir tempRoot( QDir::tempPath() + QStringLiteral( "/test-root-XXXXXX" ) ); + cDebug() << "Temporary files as" << QDir::tempPath(); + cDebug() << "Temp dir file at " << tempRoot.path(); + QVERIFY( !tempRoot.path().isEmpty() ); + + // This will pretend to be the host system + QTemporaryDir tempISOdir( QDir::tempPath() + QStringLiteral( "/test-live-XXXXXX" ) ); + QVERIFY( QDir( tempRoot.path() ).mkpath( tempRoot.path() + tempISOdir.path() ) ); + + QFile source( tempRoot.filePath( "example" ) ); + QVERIFY( !source.exists() ); + source.open( QIODevice::WriteOnly ); + source.write( "Derp" ); + source.close(); + QCOMPARE( source.size(), 4 ); + QVERIFY( source.exists() ); + + // This should fail since "example" isn't standard in our test directory + auto r0 = MachineId::copyFile( tempRoot.path(), "example" ); + QVERIFY( !r0 ); + + const QString sampleFile = QStringLiteral( "CMakeCache.txt" ); + if ( QFile::exists( sampleFile ) ) + { + auto r1 = MachineId::copyFile( tempRoot.path(), sampleFile ); + // Also fail, because it's not an absolute path + QVERIFY( !r1 ); + + QVERIFY( QFile::copy( sampleFile, tempISOdir.path() + '/' + sampleFile ) ); + auto r2 = MachineId::copyFile( tempRoot.path(), tempISOdir.path() + '/' + sampleFile ); + QVERIFY( r2 ); + } +} + +void +MachineIdTests::testRemoveFile() +{ +} + +void +MachineIdTests::testPoolSize() +{ +#ifdef Q_OS_FREEBSD + // It hardly makes sense, but also the /proc entry is missing + QCOMPARE( MachineId::getUrandomPoolSize(), 512 ); +#else + // Based on a sample size of 1, Netrunner + QCOMPARE( MachineId::getUrandomPoolSize(), 4096 ); +#endif +} + + +QTEST_GUILESS_MAIN( MachineIdTests ) + +#include "Tests.moc" +#include "utils/moc-warnings.h" diff --git a/src/modules/machineid/Workers.cpp b/src/modules/machineid/Workers.cpp new file mode 100644 index 000000000..e6379f3f1 --- /dev/null +++ b/src/modules/machineid/Workers.cpp @@ -0,0 +1,199 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014, Kevin Kofler + * Copyright 2016, Philip Müller + * Copyright 2017, Alf Gaida + * Copyright 2019, 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 "Workers.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" + +#include + +namespace MachineId +{ + +static inline bool +isAbsolutePath( const QString& fileName ) +{ + return fileName.startsWith( '/' ); +} + +// might need to use a helper to remove the file +void +removeFile( const QString& rootMountPoint, const QString& fileName ) +{ + if ( isAbsolutePath( fileName ) ) + { + QFile::remove( rootMountPoint + fileName ); + } + // Otherwise, do nothing +} + +Calamares::JobResult +copyFile( const QString& rootMountPoint, const QString& fileName ) +{ + if ( !isAbsolutePath( fileName ) ) + { + return Calamares::JobResult::internalError( + QObject::tr( "File not found" ), + QObject::tr( "Path
%1
must be an absolute path." ).arg( fileName ), + 0 ); + } + + QFile f( fileName ); + if ( !f.exists() ) + { + return Calamares::JobResult::error( QObject::tr( "File not found" ), fileName ); + } + if ( !f.copy( rootMountPoint + fileName ) ) + { + return Calamares::JobResult::error( QObject::tr( "File not found" ), rootMountPoint + fileName ); + } + return Calamares::JobResult::ok(); +} + +int +getUrandomPoolSize() +{ + QFile f( "/proc/sys/kernel/random/poolsize" ); + constexpr const int minimumPoolSize = 512; + int poolSize = minimumPoolSize; + + if ( f.exists() && f.open( QIODevice::ReadOnly | QIODevice::Text ) ) + { + QByteArray v = f.read( 16 ); + if ( v.length() > 2 ) + { + if ( v.endsWith( '\n' ) ) + { + v.chop(1); + } + bool ok = false; + poolSize = v.toInt( &ok ); + if ( !ok ) + { + poolSize = minimumPoolSize; + } + } + } + return (poolSize >= minimumPoolSize) ? poolSize : minimumPoolSize; +} + +Calamares::JobResult +createNewEntropy( int poolSize, const QString& rootMountPoint, const QString& fileName ) +{ + QFile urandom( "/dev/urandom" ); + if ( urandom.exists() && urandom.open( QIODevice::ReadOnly ) ) + { + QByteArray data = urandom.read( poolSize ); + urandom.close(); + + QFile entropyFile( rootMountPoint + fileName ); + if ( entropyFile.exists() ) + { + cWarning() << "Entropy file" << ( rootMountPoint + fileName ) << "already exists."; + return Calamares::JobResult::ok(); // .. anyway + } + if ( !entropyFile.open( QIODevice::WriteOnly ) ) + { + return Calamares::JobResult::error( + QObject::tr( "File not found" ), + QObject::tr( "Could not create new random file
%1
." ).arg( fileName ) ); + } + entropyFile.write( data ); + entropyFile.close(); + if ( entropyFile.size() < data.length() ) + { + cWarning() << "Entropy file is" << entropyFile.size() << "bytes, random data was" << data.length(); + } + if ( data.length() < poolSize ) + { + cWarning() << "Entropy data is" << data.length() << "bytes, rather than poolSize" << poolSize; + } + } + return Calamares::JobResult::error( + QObject::tr( "File not found" ), + QObject::tr( "Could not read random file
%1
." ).arg( QStringLiteral( "/dev/urandom" ) ) ); +} + + +Calamares::JobResult +createEntropy( const EntropyGeneration kind, const QString& rootMountPoint, const QString& fileName ) +{ + if ( kind == EntropyGeneration::CopyFromHost ) + { + if ( QFile::exists( fileName ) ) + { + auto r = copyFile( rootMountPoint, fileName ); + if ( r ) + { + return r; + } + else + { + cWarning() << "Could not copy" << fileName << "for entropy, generating new."; + } + } + else + { + cWarning() << "Host system entropy does not exist at" << fileName; + } + } + + int poolSize = getUrandomPoolSize(); + return createNewEntropy( poolSize, rootMountPoint, fileName ); +} + +static Calamares::JobResult +runCmd( const QStringList& cmd ) +{ + auto r = CalamaresUtils::System::instance()->targetEnvCommand( cmd ); + if ( r.getExitCode() ) + { + return r.explainProcess( cmd, std::chrono::seconds( 0 ) ); + } + + return Calamares::JobResult::ok(); +} + +Calamares::JobResult +createSystemdMachineId( const QString& rootMountPoint, const QString& fileName ) +{ + Q_UNUSED( rootMountPoint ) + Q_UNUSED( fileName ) + return runCmd( QStringList { QStringLiteral( "systemd-machine-id-setup" ) } ); +} + +Calamares::JobResult +createDBusMachineId( const QString& rootMountPoint, const QString& fileName ) +{ + Q_UNUSED( rootMountPoint ) + Q_UNUSED( fileName ) + return runCmd( QStringList { QStringLiteral( "dbus-uuidgen" ), QStringLiteral( "--ensure" ) } ); +} + +Calamares::JobResult +createDBusLink( const QString& rootMountPoint, const QString& fileName, const QString& systemdFileName ) +{ + Q_UNUSED( rootMountPoint ); + return runCmd( QStringList { QStringLiteral( "ln" ), QStringLiteral( "-s" ), systemdFileName, fileName } ); +} + +} // namespace MachineId diff --git a/src/modules/machineid/Workers.h b/src/modules/machineid/Workers.h new file mode 100644 index 000000000..5cf6270d9 --- /dev/null +++ b/src/modules/machineid/Workers.h @@ -0,0 +1,82 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, 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 WORKERS_H +#define WORKERS_H + +#include "Job.h" + +/// @brief Utility functions for doing the random-data stuff for MachineId +namespace MachineId +{ +/** @brief Utility functions + * + * These probably belong in libcalamares, since they're general utilities + * for moving files around in the target system. + */ + +/// @brief Remove @p fileName from the target system at @p rootMountPoint +void removeFile( const QString& rootMountPoint, const QString& fileName ); + +/// @brief Copy @p fileName from host into target system at @p rootMountPoint +Calamares::JobResult copyFile( const QString& rootMountPoint, const QString& fileName ); + + +/** @brief Entropy functions + * + * The target system may want to pre-seed the entropy pool with a suitable + * chunk of entropy data. During installation we have lots of disk access + * so plenty of entropy -- this is used mostly be Debian. + */ + +/// @brief How to generate entropy (bool-like) +enum class EntropyGeneration +{ + New, + CopyFromHost +}; + +/// @brief Returns a recommended size for the entropy pool (in bytes) +int getUrandomPoolSize(); + +/// @brief Creates a new entropy file @p fileName in the target system at @p rootMountPoint +Calamares::JobResult createNewEntropy( int poolSize, const QString& rootMountPoint, const QString& fileName ); + +/// @brief Create an entropy file @p fileName in the target system at @p rootMountPoint +Calamares::JobResult +createEntropy( const EntropyGeneration kind, const QString& rootMountPoint, const QString& fileName ); + + +/** @brief MachineID functions + * + * Creating UUIDs for DBUS and SystemD. + */ + +/// @brief Create a new DBus UUID file +Calamares::JobResult createDBusMachineId( const QString& rootMountPoint, const QString& fileName ); + +/// @brief Symlink DBus UUID file to the one from systemd (which must exist already) +Calamares::JobResult +createDBusLink( const QString& rootMountPoint, const QString& fileName, const QString& systemdFileName ); + +Calamares::JobResult createSystemdMachineId( const QString& rootMountPoint, const QString& fileName ); + + +} // namespace MachineId + +#endif // WORKERS_H diff --git a/src/modules/machineid/machineid.conf b/src/modules/machineid/machineid.conf index 263687263..97bd10a06 100644 --- a/src/modules/machineid/machineid.conf +++ b/src/modules/machineid/machineid.conf @@ -1,8 +1,24 @@ +# Machine-ID and other random data on the target system. +# +# This module can create a number of "random" things on the target: +# - a systemd machine-id file (hence the name of the Calamares module) +# with a random UUID. +# - a dbus machine-id file (or, optionally, link to the one from systemd) +# - an entropy file +# --- # Whether to create /etc/machine-id for systemd. systemd: true + # Whether to create /var/lib/dbus/machine-id for D-Bus. dbus: true # Whether /var/lib/dbus/machine-id should be a symlink to /etc/machine-id # (ignored if dbus is false, or if there is no /etc/machine-id to point to). +dbus-symlink: true +# this is a deprecated form of *dbus-symlink* symlink: true + +# Whether to create an entropy file +entropy: false +# Whether to copy entropy from the host +entropy-copy: false diff --git a/src/modules/machineid/module.desc b/src/modules/machineid/module.desc deleted file mode 100644 index 8d42b64f5..000000000 --- a/src/modules/machineid/module.desc +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: "job" -name: "machineid" -interface: "python" -script: "main.py"