Merge branch 'pr-1632' into work-3.3

- merge in recent *calamares* branch

FIXES #1632 (PR from Anubhav)
FIXES #1886
FIXES #1456
FIXES #517
This commit is contained in:
Adriaan de Groot 2022-04-25 13:50:42 +02:00
commit e15e57600e
15 changed files with 391 additions and 115 deletions

View File

@ -8,6 +8,22 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions.
# 3.2.57 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Arjen Balfoort (new contributor! Welcome!)
- Victor Fuentes
## Core ##
- No core changes yet
## Modules ##
- *fstab* and *luksbootkeyfile* have better support for an **un**encrypted
`/boot` partition. #1931 (thanks Arjen)
- *packagechooser* and *packagechooserq* can now be given a custom name
in the side-panel. #1932 (thanks Victor)
# 3.2.56 (2022-04-22) #
As of this release, Calamares 3.2 development is winding down. The

View File

@ -798,8 +798,12 @@ def run():
fw_type = libcalamares.globalstorage.value("firmwareType")
if (libcalamares.globalstorage.value("bootLoader") is None and fw_type != "efi"):
libcalamares.utils.warning( "Non-EFI system, and no bootloader is set." )
if libcalamares.globalstorage.value("bootLoader") is None:
# Don't want a bootloader, but do log that this has an effect:
if fw_type != "efi":
libcalamares.utils.warning( "Non-EFI system, and no bootloader is set." )
else:
libcalamares.utils.warning( "EFI system, and no bootloader is set." )
return None
partitions = libcalamares.globalstorage.value("partitions")

32
src/modules/fstab/main.py Normal file → Executable file
View File

@ -156,11 +156,23 @@ class FstabGenerator(object):
if not mapper_name or not luks_uuid:
return None
password = "/crypto_keyfile.bin"
crypttab_options = self.crypttab_options
# Set crypttab password for partition to none and remove crypttab options
# on root partition when /boot is unencrypted
if partition["mountPoint"] == "/":
if any([p["mountPoint"] == "/boot"
and "luksMapperName" not in p
for p in self.partitions]):
password = "none"
crypttab_options = ""
return dict(
name=mapper_name,
device="UUID=" + luks_uuid,
password="/crypto_keyfile.bin",
options=self.crypttab_options,
password=password,
options=crypttab_options,
)
def print_crypttab_line(self, dct, file=None):
@ -218,7 +230,7 @@ class FstabGenerator(object):
# Some "fs" names need special handling in /etc/fstab, so remap them.
filesystem = partition["fs"].lower()
filesystem = FS_MAP.get(filesystem, filesystem)
has_luks = "luksMapperName" in partition
luks_mapper_name = partition.get("luksMapperName", None)
mount_point = partition["mountPoint"]
disk_name = disk_name_for_partition(partition)
is_ssd = disk_name in self.ssd_disks
@ -247,13 +259,23 @@ class FstabGenerator(object):
if mount_point == "/":
self.root_is_ssd = is_ssd
if has_luks:
device = "/dev/mapper/" + partition["luksMapperName"]
# If there's a set-and-not-empty subvolume set, add it
if filesystem == "btrfs" and partition.get("subvol",None):
options = "subvol={},".format(partition["subvol"]) + options
device = None
if luks_mapper_name:
device = "/dev/mapper/" + luks_mapper_name
elif partition["uuid"]:
device = "UUID=" + partition["uuid"]
else:
device = partition["device"]
if not device:
# TODO: we get here when the user mounted a previously encrypted partition
# This should be catched early in the process
return None
return dict(device=device,
mount_point=mount_point,
fs=filesystem,

View File

@ -11,3 +11,10 @@ calamares_add_plugin(luksbootkeyfile
SHARED_LIB
NO_CONFIG
)
calamares_add_test(
luksbootkeyfiletest
SOURCES
Tests.cpp
LuksBootKeyFileJob.cpp
)

View File

@ -150,26 +150,50 @@ setupLuks( const LuksDevice& d )
}
static QVariantList
partitions()
partitionsFromGlobalStorage()
{
Calamares::GlobalStorage* globalStorage = Calamares::JobQueue::instance()->globalStorage();
return globalStorage->value( QStringLiteral( "partitions" ) ).toList();
}
static bool
/// Checks if the partition (represented by @p map) mounts to the given @p path
STATICTEST bool
hasMountPoint( const QVariantMap& map, const QString& path )
{
const auto v = map.value( QStringLiteral( "mountPoint" ) );
return v.isValid() && QDir::cleanPath( v.toString() ) == path;
}
STATICTEST bool
isEncrypted( const QVariantMap& map )
{
return map.contains( QStringLiteral( "luksMapperName" ) );
}
/// Checks for any partition satisfying @p pred
STATICTEST bool
anyPartition( bool ( *pred )( const QVariantMap& ) )
{
const auto partitions = partitionsFromGlobalStorage();
return std::find_if( partitions.cbegin(),
partitions.cend(),
[ &pred ]( const QVariant& partitionVariant ) { return pred( partitionVariant.toMap() ); } )
!= partitions.cend();
}
STATICTEST bool
hasUnencryptedSeparateBoot()
{
const QVariantList partitions = ::partitions();
for ( const QVariant& partition : partitions )
{
QVariantMap partitionMap = partition.toMap();
QString mountPoint = partitionMap.value( QStringLiteral( "mountPoint" ) ).toString();
if ( QDir::cleanPath( mountPoint ) == QStringLiteral( "/boot" ) )
{
return !partitionMap.contains( QStringLiteral( "luksMapperName" ) );
}
}
return false;
return anyPartition(
[]( const QVariantMap& partition )
{ return hasMountPoint( partition, QStringLiteral( "/boot" ) ) && !isEncrypted( partition ); } );
}
STATICTEST bool
hasEncryptedRoot()
{
return anyPartition( []( const QVariantMap& partition )
{ return hasMountPoint( partition, QStringLiteral( "/" ) ) && isEncrypted( partition ); } );
}
Calamares::JobResult
@ -218,7 +242,8 @@ LuksBootKeyFileJob::exec()
}
// /boot partition is not encrypted, keyfile must not be used
if ( hasUnencryptedSeparateBoot() )
// But only if root partition is not encrypted
if ( hasUnencryptedSeparateBoot() && !hasEncryptedRoot() )
{
cDebug() << Logger::SubEntry << "/boot partition is not encrypted, skipping keyfile creation.";
return Calamares::JobResult::ok();
@ -241,6 +266,12 @@ LuksBootKeyFileJob::exec()
for ( const auto& d : s.devices )
{
// Skip setupLuks for root partition if system has an unencrypted /boot
if ( d.isRoot && hasUnencryptedSeparateBoot() )
{
continue;
}
if ( !setupLuks( d ) )
return Calamares::JobResult::error(
tr( "Encrypted rootfs setup error" ),

View File

@ -0,0 +1,169 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/Logger.h"
#include <QtTest/QtTest>
#undef STATICTEST
#define STATICTEST extern
// Implementation details
STATICTEST bool hasMountPoint( const QVariantMap& map, const QString& path );
STATICTEST bool isEncrypted( const QVariantMap& map );
STATICTEST bool anyPartition( bool ( *pred )( const QVariantMap& ) );
STATICTEST bool hasUnencryptedSeparateBoot();
STATICTEST bool hasEncryptedRoot();
class LuksBootKeyFileTests : public QObject
{
Q_OBJECT
public:
LuksBootKeyFileTests() {}
~LuksBootKeyFileTests() override {}
private Q_SLOTS:
void initTestCase();
void testMountPoint();
void testIsEncrypted();
void testAnyPartition();
};
void
LuksBootKeyFileTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
cDebug() << "LuksBootKeyFile test started.";
if ( !Calamares::JobQueue::instance() )
{
(void)new Calamares::JobQueue();
}
}
void
LuksBootKeyFileTests::testMountPoint()
{
QVariantMap m; // As if this is a partition data
const QString key = QStringLiteral( "mountPoint" );
const QString boot = QStringLiteral( "/boot" );
const QString root = QStringLiteral( "/" );
QVERIFY( !hasMountPoint( m, QString() ) );
QVERIFY( !hasMountPoint( m, boot ) );
m.insert( key, boot );
QVERIFY( hasMountPoint( m, boot ) );
QVERIFY( !hasMountPoint( m, QString() ) );
QVERIFY( !hasMountPoint( m, root ) );
m.insert( key, root );
QVERIFY( !hasMountPoint( m, boot ) );
QVERIFY( !hasMountPoint( m, QString() ) );
QVERIFY( hasMountPoint( m, root ) );
m.remove( key );
QVERIFY( !hasMountPoint( m, root ) );
}
void
LuksBootKeyFileTests::testIsEncrypted()
{
QVariantMap m; // As if this is a partition data
const QString key = QStringLiteral( "luksMapperName" );
const QString name = QStringLiteral( "any-name" );
QVERIFY( !isEncrypted( m ) );
// Even an empty string is considered encrypted
m.insert( key, QString() );
QVERIFY( isEncrypted( m ) );
m.insert( key, name );
QVERIFY( isEncrypted( m ) );
m.insert( key, QString() );
QVERIFY( isEncrypted( m ) );
m.remove( key );
QVERIFY( !isEncrypted( m ) );
}
void
LuksBootKeyFileTests::testAnyPartition()
{
// This is kind of annoying: we need to build up
// partition data in GS because the functions we're testing
// go straight to GS.
auto* gs = Calamares::JobQueue::instanceGlobalStorage();
QVERIFY( gs );
const QString partitionsKey = QStringLiteral( "partitions" );
const QString mountPointKey = QStringLiteral( "mountPoint" );
const QString boot = QStringLiteral( "/boot" );
const QString root = QStringLiteral( "/" );
QVariantList partitions;
QVariantMap p;
QVERIFY( !gs->contains( partitionsKey ) );
// Empty list!
QVERIFY( !anyPartition( []( const QVariantMap& ) { return true; } ) );
gs->insert( partitionsKey, partitions );
QVERIFY( !anyPartition( []( const QVariantMap& ) { return true; } ) ); // Still an empty list
partitions.append( p );
QCOMPARE( partitions.count(), 1 );
gs->insert( partitionsKey, partitions );
QVERIFY( anyPartition( []( const QVariantMap& ) { return true; } ) ); // Now a one-element list
QVERIFY( !anyPartition( []( const QVariantMap& ) { return false; } ) ); // Now a one-element list
p.insert( mountPointKey, boot );
QVERIFY( hasMountPoint( p, boot ) );
partitions.append( p );
QCOMPARE( partitions.count(), 2 );
// Note that GS is not updated yet, so we expect this to fail
QEXPECT_FAIL( "", "GS not updated", Continue );
QVERIFY( anyPartition(
[]( const QVariantMap& partdata )
{
cDebug() << partdata;
return hasMountPoint( partdata, QStringLiteral( "/boot" ) );
} ) );
gs->insert( partitionsKey, partitions ); // Update GS
QVERIFY( anyPartition(
[]( const QVariantMap& partdata )
{
cDebug() << partdata;
return hasMountPoint( partdata, QStringLiteral( "/boot" ) );
} ) );
QVERIFY( !anyPartition( []( const QVariantMap& partdata )
{ return hasMountPoint( partdata, QStringLiteral( "/" ) ); } ) );
QVERIFY( !anyPartition( []( const QVariantMap& partdata ) { return hasMountPoint( partdata, QString() ); } ) );
QVERIFY( !hasEncryptedRoot() );
QVERIFY( hasUnencryptedSeparateBoot() );
}
QTEST_GUILESS_MAIN( LuksBootKeyFileTests )
#include "utils/moc-warnings.h"
#include "Tests.moc"

View File

@ -237,6 +237,12 @@ Config::setPackageChoice( const QString& packageChoice )
emit packageChoiceChanged( m_packageChoice.value_or( QString() ) );
}
QString
Config::prettyName() const
{
return m_stepName ? m_stepName->get() : tr( "Packages" );
}
QString
Config::prettyStatus() const
{
@ -343,4 +349,14 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
cWarning() << "Single-selection QML module must use 'Legacy' method.";
}
}
bool labels_ok = false;
auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
if ( labels_ok )
{
if ( labels.contains( "step" ) )
{
m_stepName = new CalamaresUtils::Locale::TranslatedString( labels, "step" );
}
}
}

View File

@ -98,6 +98,7 @@ public:
QString packageChoice() const { return m_packageChoice.value_or( QString() ); }
void setPackageChoice( const QString& packageChoice );
QString prettyName() const;
QString prettyStatus() const;
signals:
@ -120,6 +121,7 @@ private:
* Reading the property will return an empty QString.
*/
std::optional< QString > m_packageChoice;
CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
};

View File

@ -29,7 +29,6 @@ PackageChooserViewStep::PackageChooserViewStep( QObject* parent )
: Calamares::ViewStep( parent )
, m_config( new Config( this ) )
, m_widget( nullptr )
, m_stepName( nullptr )
{
emit nextStatusChanged( false );
}
@ -41,14 +40,13 @@ PackageChooserViewStep::~PackageChooserViewStep()
{
m_widget->deleteLater();
}
delete m_stepName;
}
QString
PackageChooserViewStep::prettyName() const
{
return m_stepName ? m_stepName->get() : tr( "Packages" );
return m_config->prettyName();
}
@ -139,16 +137,6 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap
m_config->setDefaultId( moduleInstanceKey() );
m_config->setConfigurationMap( configurationMap );
bool labels_ok = false;
auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok );
if ( labels_ok )
{
if ( labels.contains( "step" ) )
{
m_stepName = new CalamaresUtils::Locale::TranslatedString( labels, "step" );
}
}
if ( m_widget )
{
hookupModel();

View File

@ -50,7 +50,6 @@ private:
Config* m_config;
PackageChooserPage* m_widget;
CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory )

View File

@ -29,7 +29,7 @@ PackageChooserQmlViewStep::PackageChooserQmlViewStep( QObject* parent )
QString
PackageChooserQmlViewStep::prettyName() const
{
return tr( "Packages" );
return m_config->prettyName();
}
QString

View File

@ -42,6 +42,19 @@
#
method: legacy
# Human-visible strings in this module. These are all optional.
# The following translated keys are used:
# - *step*, used in the overall progress view (left-hand pane)
#
# Each key can have a [locale] added to it, which is used as
# the translated string for that locale. For the strings
# associated with the "no-selection" item, see *items*, below
# with the explicit item-*id* "".
#
labels:
step: "Packages"
step[nl]: "Pakketten"
# The *packageChoice* value is used for setting the default selection
# in the QML view; this should match one of the keys used in the QML
# module for package names.

View File

@ -3,6 +3,7 @@
* SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
* SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2021 Anubhav Choudhary <ac.10edu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
@ -79,7 +80,7 @@ BootLoaderModel::updateInternal()
clear();
createMbrItems();
// An empty model is possible if you don't havee permissions: don't crash though.
// An empty model is possible if you don't have permissions: don't crash though.
if ( rowCount() < 1 )
{
return;
@ -124,10 +125,10 @@ BootLoaderModel::updateInternal()
{
appendRow( createBootLoaderItem( partitionText, PartitionInfo::mountPoint( partition ), true ) );
}
// Create "don't install bootloader" item
appendRow( createBootLoaderItem( tr( "Do not install a boot loader" ), QString(), false ) );
}
// Create "don't install bootloader" item. This is always available,
// also if there was no /boot or / partition found.
appendRow( createBootLoaderItem( tr( "Do not install a boot loader" ), QString(), false ) );
}

View File

@ -3,6 +3,7 @@
* SPDX-FileCopyrightText: 2014-2017 Teo Mrnjavac <teo@kde.org>
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2019 Collabora Ltd
* SPDX-FileCopyrightText: 2021 Anubhav Choudhary <ac.10edu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
@ -1021,6 +1022,12 @@ ChoicePage::updateActionChoicePreview( InstallChoice choice )
QLabel* sizeLabel = new QLabel( m_previewAfterFrame );
layout->addWidget( sizeLabel );
sizeLabel->setWordWrap( true );
if ( !m_isEfi )
{
layout->addWidget( createBootloaderPanel() );
}
connect( m_afterPartitionSplitterWidget,
&PartitionSplitterWidget::partitionResized,
this,
@ -1079,51 +1086,7 @@ ChoicePage::updateActionChoicePreview( InstallChoice choice )
if ( !m_isEfi )
{
QWidget* eraseWidget = new QWidget;
QHBoxLayout* eraseLayout = new QHBoxLayout;
eraseWidget->setLayout( eraseLayout );
eraseLayout->setContentsMargins( 0, 0, 0, 0 );
QLabel* eraseBootloaderLabel = new QLabel( eraseWidget );
eraseLayout->addWidget( eraseBootloaderLabel );
eraseBootloaderLabel->setText( tr( "Boot loader location:" ) );
m_bootloaderComboBox = createBootloaderComboBox( eraseWidget );
connect( m_core->bootLoaderModel(),
&QAbstractItemModel::modelReset,
[ this ]()
{
if ( !m_bootloaderComboBox.isNull() )
{
Calamares::restoreSelectedBootLoader( *m_bootloaderComboBox,
m_core->bootLoaderInstallPath() );
}
} );
connect(
m_core,
&PartitionCoreModule::deviceReverted,
this,
[ this ]( Device* dev )
{
Q_UNUSED( dev )
if ( !m_bootloaderComboBox.isNull() )
{
if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() )
{
m_bootloaderComboBox->setModel( m_core->bootLoaderModel() );
}
m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
}
},
Qt::QueuedConnection );
// ^ Must be Queued so it's sure to run when the widget is already visible.
eraseLayout->addWidget( m_bootloaderComboBox );
eraseBootloaderLabel->setBuddy( m_bootloaderComboBox );
eraseLayout->addStretch();
layout->addWidget( eraseWidget );
layout->addWidget( createBootloaderPanel() );
}
m_previewAfterFrame->show();
@ -1235,35 +1198,6 @@ ChoicePage::setupEfiSystemPartitionSelector()
}
}
QComboBox*
ChoicePage::createBootloaderComboBox( QWidget* parent )
{
QComboBox* comboForBootloader = new QComboBox( parent );
comboForBootloader->setModel( m_core->bootLoaderModel() );
// When the chosen bootloader device changes, we update the choice in the PCM
connect( comboForBootloader,
QOverload< int >::of( &QComboBox::currentIndexChanged ),
this,
[ this ]( int newIndex )
{
QComboBox* bootloaderCombo = qobject_cast< QComboBox* >( sender() );
if ( bootloaderCombo )
{
QVariant var = bootloaderCombo->itemData( newIndex, BootLoaderModel::BootLoaderPathRole );
if ( !var.isValid() )
{
return;
}
m_core->setBootLoaderInstallPath( var.toString() );
}
} );
return comboForBootloader;
}
static inline void
force_uncheck( QButtonGroup* grp, PrettyRadioButton* button )
{
@ -1733,3 +1667,72 @@ ChoicePage::setLastSelectedDeviceIndex( int index )
m_lastSelectedDeviceIndex = index;
m_drivesCombo->setCurrentIndex( m_lastSelectedDeviceIndex );
}
QWidget*
ChoicePage::createBootloaderPanel()
{
QWidget* panelWidget = new QWidget;
QHBoxLayout* mainLayout = new QHBoxLayout;
panelWidget->setLayout( mainLayout );
mainLayout->setContentsMargins( 0, 0, 0, 0 );
QLabel* widgetLabel = new QLabel( panelWidget );
mainLayout->addWidget( widgetLabel );
widgetLabel->setText( tr( "Boot loader location:" ) );
QComboBox* comboForBootloader = new QComboBox( panelWidget );
comboForBootloader->setModel( m_core->bootLoaderModel() );
// When the chosen bootloader device changes, we update the choice in the PCM
connect( comboForBootloader,
QOverload< int >::of( &QComboBox::currentIndexChanged ),
this,
[ this ]( int newIndex )
{
QComboBox* bootloaderCombo = qobject_cast< QComboBox* >( sender() );
if ( bootloaderCombo )
{
QVariant var = bootloaderCombo->itemData( newIndex, BootLoaderModel::BootLoaderPathRole );
if ( !var.isValid() )
{
return;
}
m_core->setBootLoaderInstallPath( var.toString() );
}
} );
m_bootloaderComboBox = comboForBootloader;
connect( m_core->bootLoaderModel(),
&QAbstractItemModel::modelReset,
[ this ]()
{
if ( !m_bootloaderComboBox.isNull() )
{
Calamares::restoreSelectedBootLoader( *m_bootloaderComboBox, m_core->bootLoaderInstallPath() );
}
} );
connect(
m_core,
&PartitionCoreModule::deviceReverted,
this,
[ this ]( Device* )
{
if ( !m_bootloaderComboBox.isNull() )
{
if ( m_bootloaderComboBox->model() != m_core->bootLoaderModel() )
{
m_bootloaderComboBox->setModel( m_core->bootLoaderModel() );
}
m_bootloaderComboBox->setCurrentIndex( m_lastSelectedDeviceIndex );
}
},
Qt::QueuedConnection );
// ^ Must be Queued so it's sure to run when the widget is already visible.
mainLayout->addWidget( m_bootloaderComboBox );
widgetLabel->setBuddy( m_bootloaderComboBox );
mainLayout->addStretch();
return panelWidget;
}

View File

@ -108,7 +108,12 @@ private:
void updateNextEnabled();
void setupChoices();
void checkInstallChoiceRadioButton( Config::InstallChoice choice ); ///< Sets the chosen button to "on"
QComboBox* createBootloaderComboBox( QWidget* parentButton );
/** @brief Create a panel with "boot loader location:"
*
* Panel + dropdown and handling for model updates. Returns a pointer
* to the panel's widget.
*/
QWidget* createBootloaderPanel();
Device* selectedDevice();
/* Change the UI depending on the device selected. */