diff --git a/CHANGES b/CHANGES index e6a16fac8..c7e5432ce 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,9 @@ This release contains contributions from (alphabetically by first name): is compiled with the newest KPMCore release. * The *keyboard* module now handles the (bogus) Austrian keymap for the system console properly. + * New module *fsresizer* can be used to resize filesystems. It is intended + for use in OEM installs where an image of fixed size is created, + and then sized to the actual SD card the user has used. # 3.2.2 (2018-09-04) # diff --git a/ci/travis-continuous.sh b/ci/travis-continuous.sh index eccb6743e..1b3841e54 100755 --- a/ci/travis-continuous.sh +++ b/ci/travis-continuous.sh @@ -51,4 +51,4 @@ df -h echo "# Install results" install_debugging "$DESTDIR" -$result # Result of make install, above +$result || { echo "! Install failed" ; exit 1 ; } # Result of make install, above diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp index e1d934b54..a8b363209 100644 --- a/src/calamares/testmain.cpp +++ b/src/calamares/testmain.cpp @@ -59,6 +59,7 @@ handle_args( QCoreApplication& a ) parser.addOption( debugLevelOption ); parser.addPositionalArgument( "module", "Path or name of module to run." ); + parser.addPositionalArgument( "config", "Path of job-config file to use.", "[config]"); parser.process( a ); @@ -140,6 +141,8 @@ load_module( const ModuleConfig& moduleConfig ) ? moduleDirectory + '/' + name + ".conf" : moduleConfig.configFile() ); + cDebug() << "Module" << moduleName << "job-configuration:" << configFile; + Calamares::Module* module = Calamares::Module::fromDescriptor( descriptor, name, configFile, moduleDirectory ); @@ -158,7 +161,7 @@ main( int argc, char* argv[] ) 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(); + cDebug() << "Calamares module-loader testing" << module.moduleName(); Calamares::Module* m = load_module( module ); if ( !m ) { @@ -175,13 +178,19 @@ main( int argc, char* argv[] ) return 1; } - cDebug() << "Module" << m->name() << m->typeString() << m->interfaceString(); + using TR = Logger::DebugRow; + cDebug() << "Module metadata" + << TR( "name", m->name() ) + << TR( "type", m->typeString() ) + << TR( "interface", m->interfaceString() ); + + cDebug() << "Job outputs:"; Calamares::JobList jobList = m->jobs(); unsigned int count = 1; for ( const auto& p : jobList ) { - cDebug() << count << p->prettyName(); + cDebug() << "Job #" << count << "name" << p->prettyName(); Calamares::JobResult r = p->exec(); if ( !r ) { diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index 8d92c37ad..a1349c280 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -145,8 +145,15 @@ moduleConfigurationCandidates( bool assumeBuildDir, const QString& moduleName, c paths << CalamaresUtils::appDataDir().absoluteFilePath( QString( "modules/%1" ).arg( configFileName ) ); else { + // If an absolute path is given, in debug mode, look for it + // first. The case contains('/'), below, will add the absolute + // path a second time, though. + if ( assumeBuildDir && configFileName.startsWith( '/' ) ) + paths << configFileName; if ( assumeBuildDir ) paths << QDir().absoluteFilePath(QString( "src/modules/%1/%2" ).arg( moduleName ).arg( configFileName ) ); + if ( assumeBuildDir && configFileName.contains( '/' ) ) + paths << QDir().absoluteFilePath( configFileName ); paths << QString( "/etc/calamares/modules/%1" ).arg( configFileName ); paths << CalamaresUtils::appDataDir().absoluteFilePath( QString( "modules/%1" ).arg( configFileName ) ); @@ -168,6 +175,7 @@ Module::loadConfigurationFile( const QString& configFileName ) //throws YAML::Ex YAML::Node doc = YAML::Load( ba.constData() ); if ( doc.IsNull() ) { + cDebug() << "Found empty module configuration" << path; // Special case: empty config files are valid, // but aren't a map. return; @@ -178,14 +186,13 @@ Module::loadConfigurationFile( const QString& configFileName ) //throws YAML::Ex return; } + cDebug() << "Loaded module configuration" << path; m_configurationMap = CalamaresUtils::yamlMapToVariant( doc ).toMap(); m_emergency = m_maybe_emergency && m_configurationMap.contains( EMERGENCY ) && m_configurationMap[ EMERGENCY ].toBool(); return; } - else - continue; } } diff --git a/src/modules/fsresizer/CMakeLists.txt b/src/modules/fsresizer/CMakeLists.txt new file mode 100644 index 000000000..9f0234406 --- /dev/null +++ b/src/modules/fsresizer/CMakeLists.txt @@ -0,0 +1,48 @@ +find_package( KPMcore 3.3 ) +find_package( Qt5 REQUIRED DBus ) # Needed for KPMCore +find_package( KF5 REQUIRED I18n WidgetsAddons ) # Needed for KPMCore + +if ( KPMcore_FOUND ) + if ( KPMcore_VERSION VERSION_GREATER "3.3.0") + add_definitions(-DWITH_KPMCOREGT33) # kpmcore greater than 3.3 + endif() + + include_directories( ${KPMCORE_INCLUDE_DIR} ) + include_directories( ${PROJECT_BINARY_DIR}/src/libcalamares ) + + # The PartitionIterator is a small class, and it's easiest -- but also a + # gross hack -- to just compile it again from the partition module tree. + calamares_add_plugin( fsresizer + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ResizeFSJob.cpp + ${PROJECT_SOURCE_DIR}/src/modules/partition/core/PartitionIterator.cpp + LINK_PRIVATE_LIBRARIES + kpmcore + calamares + SHARED_LIB + ) + + if( ECM_FOUND ) + find_package( Qt5 COMPONENTS Test REQUIRED ) + include( ECMAddTests ) + + ecm_add_test( + Tests.cpp + TEST_NAME + fsresizertest + LINK_LIBRARIES + ${CALAMARES_LIBRARIES} + calamares + calamares_job_fsresizer # From above + ${YAMLCPP_LIBRARY} + Qt5::Core + Qt5::Test + ) + set_target_properties( fsresizertest PROPERTIES AUTOMOC TRUE ) + target_include_directories(fsresizertest PRIVATE /usr/local/include ) + endif() +else() + calamares_skip_module( "fsresizer (missing suitable KPMcore)" ) +endif() diff --git a/src/modules/fsresizer/ResizeFSJob.cpp b/src/modules/fsresizer/ResizeFSJob.cpp new file mode 100644 index 000000000..0589e1866 --- /dev/null +++ b/src/modules/fsresizer/ResizeFSJob.cpp @@ -0,0 +1,339 @@ +/* === 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 "ResizeFSJob.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "CalamaresVersion.h" +#include "JobQueue.h" +#include "GlobalStorage.h" + +#include "utils/Logger.h" +#include "utils/Units.h" + +#include "modules/partition/core/PartitionIterator.h" + +ResizeFSJob::RelativeSize::RelativeSize() + : m_value( 0 ) + , m_unit( None ) +{ +} + + +template +void matchUnitSuffix( + const QString& s, + const char (&suffix)[N], + ResizeFSJob::RelativeSize::Unit matchedUnit, + int& value, + ResizeFSJob::RelativeSize::Unit& unit + ) +{ + if ( s.endsWith( suffix ) ) + { + value = s.left( s.length() - N + 1 ).toInt(); + unit = matchedUnit; + } +} + + +ResizeFSJob::RelativeSize::RelativeSize( const QString& s) + : m_value( 0 ) + , m_unit( None ) +{ + matchUnitSuffix( s, "%", Percent, m_value, m_unit ); + matchUnitSuffix( s, "MiB", Absolute, m_value, m_unit ); + + if ( ( unit() == Percent ) && ( value() > 100 ) ) + { + cDebug() << "Percent value" << value() << "is not valid."; + m_value = 0; + m_unit = None; + } + + if ( !m_value ) + m_unit = None; +} + +qint64 +ResizeFSJob::RelativeSize::apply( qint64 totalSectors , qint64 sectorSize ) +{ + if ( !isValid() ) + return -1; + if ( sectorSize < 1 ) + return -1; + + switch( m_unit ) + { + case None: + return -1; + case Absolute: + return CalamaresUtils::MiBtoBytes( value() ) / sectorSize; + case Percent: + if ( value() == 100 ) + return totalSectors; // Common-case, avoid futzing around + else + return totalSectors * value() / 100; + } + + // notreached + return -1; +} + +qint64 +ResizeFSJob::RelativeSize::apply( Device* d ) +{ + return apply( d->totalLogical(), d->logicalSize() ); +} + +ResizeFSJob::ResizeFSJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + + +ResizeFSJob::~ResizeFSJob() +{ +} + + +QString +ResizeFSJob::prettyName() const +{ + return tr( "Resize Filesystem Job" ); +} + +ResizeFSJob::PartitionMatch +ResizeFSJob::findPartition( CoreBackend* backend ) +{ + using DeviceList = QList< Device* >; + DeviceList devices = backend->scanDevices( false ); + cDebug() << "ResizeFSJob found" << devices.count() << "devices."; + for ( DeviceList::iterator dev_it = devices.begin(); dev_it != devices.end(); ++dev_it ) + { + if ( ! (*dev_it) ) + continue; + cDebug() << "ResizeFSJob found" << ( *dev_it )->deviceNode(); + for ( auto part_it = PartitionIterator::begin( *dev_it ); part_it != PartitionIterator::end( *dev_it ); ++part_it ) + { + cDebug() << ".." << ( *part_it )->mountPoint() << "on" << ( *part_it )->deviceNode(); + if ( ( !m_fsname.isEmpty() && ( *part_it )->mountPoint() == m_fsname ) || + ( !m_devicename.isEmpty() && ( *part_it )->deviceNode() == m_devicename ) ) + { + cDebug() << ".. matched configuration dev=" << m_devicename << "fs=" << m_fsname; + return PartitionMatch( *dev_it, *part_it ); + } + } + } + + cDebug() << "No match for configuration dev=" << m_devicename << "fs=" << m_fsname; + return PartitionMatch( nullptr, nullptr ); +} + +/** @brief Returns the last sector the matched partition should occupy. + * + * Returns a sector number. Returns -1 if something is wrong (e.g. + * can't resize at all, or missing data). Returns 0 if the resize + * won't fit because it doesn't satisfy the settings for atleast + * and size (or won't grow at all because the partition is blocked + * by occupied space after it). + */ +qint64 +ResizeFSJob::findGrownEnd(ResizeFSJob::PartitionMatch m) +{ + if ( !m.first || !m.second ) + return -1; // Missing device data + if ( !ResizeOperation::canGrow( m.second ) ) + return -1; // Operation is doomed + if ( !m_size.isValid() ) + return -1; // Must have a grow-size + + cDebug() << "Containing device size" << m.first->totalLogical(); + qint64 last_available = m.first->totalLogical() - 1; // Numbered from 0 + qint64 last_currently = m.second->lastSector(); + cDebug() << "Growing partition" << m.second->firstSector() << '-' << last_currently; + + for ( auto part_it = PartitionIterator::begin( m.first ); part_it != PartitionIterator::end( m.first ); ++part_it ) + { + qint64 next_start = ( *part_it )->firstSector(); + qint64 next_end = ( *part_it )->lastSector(); + if ( next_start > next_end ) + { + cWarning() << "Corrupt partition has end" << next_end << " < start" << next_start; + std::swap( next_start, next_end ); + } + if ( ( *part_it )->roles().has( PartitionRole::Unallocated ) ) + { + cDebug() << ".. ignoring unallocated" << next_start << '-' << next_end; + continue; + } + cDebug() << ".. comparing" << next_start << '-' << next_end; + if ( (next_start > last_currently) && (next_start < last_available) ) + { + cDebug() << " .. shrunk last available to" << next_start; + last_available = next_start - 1; // Before that one starts + } + } + + if ( !( last_available > last_currently ) ) + { + cDebug() << "Partition can not grow larger."; + return 0; + } + + qint64 expand = last_available - last_currently; // number of sectors + if ( m_atleast.isValid() ) + { + qint64 required = m_atleast.apply( m.first ); + if ( expand < required ) + { + cDebug() << ".. need to expand by" << required << "but only" << expand << "is available."; + return 0; + } + } + + qint64 wanted = m_size.apply( expand, m.first->logicalSize() ); + if ( wanted < expand ) + { + cDebug() << ".. only growing by" << wanted << "instead of full" << expand; + last_available -= ( expand - wanted ); + } + + return last_available; +} + + +Calamares::JobResult +ResizeFSJob::exec() +{ + if ( !isValid() ) + return Calamares::JobResult::error( + tr( "Invalid configuration" ), + tr( "The file-system resize job has an invalid configuration " + "and will not run." ) ); + + // Get KPMCore + auto backend_p = CoreBackendManager::self()->backend(); + if ( backend_p ) + cDebug() << "KPMCore backend @" << (void *)backend_p << backend_p->id() << backend_p->version(); + else + { + cDebug() << "No KPMCore backend loaded yet"; + QByteArray backendName = qgetenv( "KPMCORE_BACKEND" ); + if ( !CoreBackendManager::self()->load( backendName.isEmpty() ? CoreBackendManager::defaultBackendName() : backendName ) ) + { + cWarning() << "Could not load KPMCore backend."; + return Calamares::JobResult::error( + tr( "KPMCore not Available" ), + tr( "Calamares cannot start KPMCore for the file-system resize job." ) ); + } + + backend_p = CoreBackendManager::self()->backend(); + } + if ( !backend_p ) + { + cWarning() << "Could not load KPMCore backend (2)."; + return Calamares::JobResult::error( + tr( "KPMCore not Available" ), + tr( "Calamares cannot start KPMCore for the file-system resize job." ) ); + } + backend_p->initFSSupport(); // Might not be enough, see below + + // Now get the partition and FS we want to work on + PartitionMatch m = findPartition( backend_p ); + if ( !m.first || !m.second ) + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 could not be found in this system, and can not be resized." ).arg(m_fsname) + : tr( "The device %1 could not be found in this system, and can not be resized." ).arg(m_devicename) ); + + m.second->fileSystem().init(); // Initialize support for specific FS + if ( !ResizeOperation::canGrow( m.second ) ) + { + cDebug() << "canGrow() returned false."; + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 can not be resized." ).arg(m_fsname) + : tr( "The device %1 can not be resized." ).arg(m_devicename) ); + } + + qint64 new_end = findGrownEnd( m ); + cDebug() << "Resize from" + << m.second->firstSector() << '-' << m.second->lastSector() + << '(' << m.second->length() << ')' + << "to -" << new_end; + + if ( new_end < 0 ) + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 can not be resized." ).arg(m_fsname) + : tr( "The device %1 can not be resized." ).arg(m_devicename) ); + if ( new_end == 0 ) + { + // TODO: is that a bad thing? is the resize required? + cWarning() << "Resize operation on" << m_fsname << m_devicename + << "skipped as not-useful."; + return Calamares::JobResult::ok(); + } + + if ( ( new_end > 0 ) && ( new_end > m.second->lastSector() ) ) + { + ResizeOperation op( *m.first, *m.second, m.second->firstSector(), new_end ); + Report op_report( nullptr ); + if ( op.execute( op_report ) ) + cDebug() << "Resize operation OK."; + else + { + cDebug() << "Resize failed." << op_report.output(); + return Calamares::JobResult::error( + tr( "Resize Failed" ), + op_report.toText() ); + } + } + + return Calamares::JobResult::ok(); +} + + +void +ResizeFSJob::setConfigurationMap( const QVariantMap& configurationMap ) +{ + m_fsname = configurationMap["fs"].toString(); + m_devicename = configurationMap["dev"].toString(); + + if ( m_fsname.isEmpty() && m_devicename.isEmpty() ) + { + cWarning() << "No fs or dev configured for resize."; + return; + } + + m_size = RelativeSize( configurationMap["size"].toString() ); + m_atleast = RelativeSize( configurationMap["atleast"].toString() ); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( ResizeFSJobFactory, registerPlugin(); ) diff --git a/src/modules/fsresizer/ResizeFSJob.h b/src/modules/fsresizer/ResizeFSJob.h new file mode 100644 index 000000000..063495e7d --- /dev/null +++ b/src/modules/fsresizer/ResizeFSJob.h @@ -0,0 +1,121 @@ +/* === 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 RESIZEFSJOB_H +#define RESIZEFSJOB_H + +#include +#include + +#include + +#include + +#include + +class CoreBackend; // From KPMCore +class Device; // From KPMCore +class Partition; + +class PLUGINDLLEXPORT ResizeFSJob : public Calamares::CppJob +{ + Q_OBJECT + +public: + /** @brief Size expressions + * + * Sizes can be specified in MiB or percent (of the device they + * are on). This class handles parsing of such strings from the + * config file. + */ + class RelativeSize + { + public: + RelativeSize(); + RelativeSize( const QString& ); + + enum Unit + { + None, + Percent, + Absolute + }; + + int value() const { return m_value; } + Unit unit() const { return m_unit; } + + bool isValid() const + { + return ( unit() != None ) && ( value() > 0 ); + } + + /** @brief Apply this size to the number of sectors @p totalSectors . + * + * Each sector has size @p sectorSize , for converting absolute + * sizes in MiB to sector counts. + * + * For invalid sizes, returns -1. + * For absolute sizes, returns the number of sectors needed. + * For percent sizes, returns that percent of the number of sectors. + */ + qint64 apply( qint64 totalSectors, qint64 sectorSize ); + + /** @brief Apply this size to the given device. + * + * Equivalent to apply( d->totalLogical(), d->logicalSize() ) + */ + qint64 apply( Device* d ); + + private: + int m_value; + Unit m_unit; + } ; + + explicit ResizeFSJob( QObject* parent = nullptr ); + virtual ~ResizeFSJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + + /** @brief Is the configuration of this job valid? */ + bool isValid() const + { + return ( !m_fsname.isEmpty() || !m_devicename.isEmpty() ) && + m_size.isValid(); + } + +private: + RelativeSize m_size; + RelativeSize m_atleast; + QString m_fsname; // Either this, or devicename, is set, not both + QString m_devicename; + + using PartitionMatch = QPair; + /** @brief Find the configured FS using KPMCore @p backend */ + PartitionMatch findPartition( CoreBackend* backend ); + + /** @brief Return a new end-sector for the given dev-part pair. */ + qint64 findGrownEnd( PartitionMatch ); +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( ResizeFSJobFactory ) + +#endif // RESIZEFSJOB_H diff --git a/src/modules/fsresizer/Tests.cpp b/src/modules/fsresizer/Tests.cpp new file mode 100644 index 000000000..255153fa2 --- /dev/null +++ b/src/modules/fsresizer/Tests.cpp @@ -0,0 +1,126 @@ +/* === This file is part of Calamares - === + * + * 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 + * 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 "Tests.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "Settings.h" + +#include "utils/Logger.h" +#include "utils/YamlUtils.h" + +#include + +#include + +#include +#include + +#define private public +#include "ResizeFSJob.h" +#undef private + +QTEST_GUILESS_MAIN( FSResizerTests ) + +FSResizerTests::FSResizerTests() +{ +} + +FSResizerTests::~FSResizerTests() +{ +} + +void +FSResizerTests::initTestCase() +{ +} + +void FSResizerTests::testConfigurationRobust() +{ + ResizeFSJob j; + + // Empty config + j.setConfigurationMap( QVariantMap() ); + QVERIFY( j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + + // Config is missing fs and dev, so it isn't valid + YAML::Node doc0 = YAML::Load( R"(--- +size: 100% +atleast: 600MiB +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_size.value(), 0 ); + QCOMPARE( j.m_atleast.value(), 0 ); +} + +void FSResizerTests::testConfigurationValues() +{ + ResizeFSJob j; + + // Check both + YAML::Node doc0 = YAML::Load( R"(--- +fs: / +size: 100% +atleast: 600MiB +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Percent ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_size.value(), 100 ); + QCOMPARE( j.m_atleast.value(), 600 ); + + // Silly config + doc0 = YAML::Load( R"(--- +fs: / +dev: /dev/m00 +size: 72 MiB +atleast: 127 % +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( !j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::Percent ); + QCOMPARE( j.m_size.value(), 72 ); + QCOMPARE( j.m_atleast.value(), 127 ); + + // Silly config + doc0 = YAML::Load( R"(--- +fs: / +# dev: /dev/m00 +size: 71MiB +# atleast: 127% +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_size.value(), 71 ); + QCOMPARE( j.m_atleast.value(), 0 ); +} diff --git a/src/modules/fsresizer/Tests.h b/src/modules/fsresizer/Tests.h new file mode 100644 index 000000000..958c0e655 --- /dev/null +++ b/src/modules/fsresizer/Tests.h @@ -0,0 +1,39 @@ +/* === 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 TESTS_H +#define TESTS_H + +#include + +class FSResizerTests : public QObject +{ + Q_OBJECT +public: + FSResizerTests(); + ~FSResizerTests() override; + +private Q_SLOTS: + void initTestCase(); + // Can handle missing values + void testConfigurationRobust(); + // Can parse % and MiB values + void testConfigurationValues(); +}; + +#endif diff --git a/src/modules/fsresizer/fsresizer.conf b/src/modules/fsresizer/fsresizer.conf new file mode 100644 index 000000000..06141fc7d --- /dev/null +++ b/src/modules/fsresizer/fsresizer.conf @@ -0,0 +1,37 @@ +# Module that resizes a single FS to fill the entire (rest) of +# a device. This is used in OEM situations where an image is +# flashed onto an SD card (or similar) and used to boot a device, +# after which the FS should expand to fill the SD card. +# +# Example: a distro produces a 6GiB large image that is +# written to an 8GiB SD card; the FS should expand to take +# advantage of the unused 2GiB. The FS should expand much +# more if the same image is written to a 16GiB card. +--- + +# Which FS needs to be grown? Choose one way to identify it: +# - *fs* names a mount point which should already be mounted +# in the system. +# - *dev* names a device +fs: / +# dev: /dev/mmcblk0p1 + +# How much of the total remaining space should the FS use? +# The only sensible amount is "all of it". The value is +# in percent, so set it to 100. Perhaps a fixed size is +# needed (that would be weird though, since you don't know +# how big the card is), use MiB as suffix in that case. +# If missing, then it's assumed to be 0, and no resizing +# will happen. +# +# Percentages apply to **available space**. +size: 100% + +# Resizing might not be worth it, though. Set the minimum +# that it must grow; if it cannot grow that much, the +# resizing is skipped. Can be in percentage or absolute +# size, as above. If missing, then it's assumed to be 0, +# which means resizing is always worthwhile. +# +# Percentages apply to **total device size**. +atleast: 1000MiB diff --git a/src/modules/partition/core/PartitionIterator.cpp b/src/modules/partition/core/PartitionIterator.cpp index 5ed48fd91..8301835c6 100644 --- a/src/modules/partition/core/PartitionIterator.cpp +++ b/src/modules/partition/core/PartitionIterator.cpp @@ -18,7 +18,7 @@ * along with Calamares. If not, see . */ -#include +#include "PartitionIterator.h" // KPMcore #include diff --git a/src/modules/test_conf.cpp b/src/modules/test_conf.cpp index b5362d25a..ca6b72cc7 100644 --- a/src/modules/test_conf.cpp +++ b/src/modules/test_conf.cpp @@ -87,7 +87,7 @@ int main(int argc, char** argv) if ( !doc.IsMap() ) { cerr << "WARNING:" << filename << '\n'; - cerr << "WARNING: not-a-YAML-map\n"; + cerr << "WARNING: not-a-YAML-map (type=" << doc.Type() << ")\n"; return 1; }