@@ -150,56 +152,32 @@ ViewManager::insertViewStep( int before, ViewStep* step )
void
ViewManager::onInstallationFailed( const QString& message, const QString& details )
{
- bool shouldOfferWebPaste = std::get< 0 >( Calamares::Branding::instance()->uploadServer() )
- != Calamares::Branding::UploadServerType::None
- and std::get< 2 >( Calamares::Branding::instance()->uploadServer() ) != 0;
-
cError() << "Installation failed:" << message;
cDebug() << Logger::SubEntry << "- message:" << message;
cDebug() << Logger::SubEntry << "- details:" << Logger::NoQuote << details;
QString heading
= Calamares::Settings::instance()->isSetupMode() ? tr( "Setup Failed" ) : tr( "Installation Failed" );
- QString pasteMsg = tr( "Would you like to paste the install log to the web?" );
- QString text = "" + message + "
";
- if ( !details.isEmpty() )
- {
- text += ""
- + CalamaresUtils::truncateMultiLine( details, CalamaresUtils::LinesStartEnd { 6, 2 } )
- .replace( '\n', QStringLiteral( "
" ) )
- + "
";
- }
- if ( shouldOfferWebPaste )
- {
- text += "" + pasteMsg + "
";
- }
- QMessageBox* msgBox = new QMessageBox();
- msgBox->setIcon( QMessageBox::Critical );
- msgBox->setWindowTitle( tr( "Error" ) );
- msgBox->setText( "" + heading + "" );
- msgBox->setInformativeText( text );
- if ( shouldOfferWebPaste )
- {
- msgBox->setStandardButtons( QMessageBox::Yes | QMessageBox::No );
- msgBox->setDefaultButton( QMessageBox::No );
- }
- else
- {
- msgBox->setStandardButtons( QMessageBox::Close );
- msgBox->setDefaultButton( QMessageBox::Close );
- }
- Calamares::fixButtonLabels( msgBox );
- msgBox->show();
+ ErrorDialog* errorDialog = new ErrorDialog();
+ errorDialog->setWindowTitle( tr( "Error" ) );
+ errorDialog->setHeading( "" + heading + "" );
+ errorDialog->setInformativeText( message );
+ errorDialog->setShouldOfferWebPaste( Calamares::Branding::instance()->uploadServer() );
+ errorDialog->setDetails( details );
+ errorDialog->show();
cDebug() << "Calamares will quit when the dialog closes.";
- connect( msgBox, &QMessageBox::buttonClicked, [msgBox]( QAbstractButton* button ) {
- if ( msgBox->buttonRole( button ) == QMessageBox::ButtonRole::YesRole )
- {
- CalamaresUtils::Paste::doLogUploadUI( msgBox );
- }
- QApplication::quit();
- } );
+ connect( errorDialog,
+ &QDialog::finished,
+ [ errorDialog ]( int result )
+ {
+ if ( result == QDialog::Accepted && errorDialog->shouldOfferWebPaste() )
+ {
+ CalamaresUtils::Paste::doLogUploadUI( errorDialog );
+ }
+ QApplication::quit();
+ } );
}
diff --git a/src/libcalamaresui/utils/Paste.cpp b/src/libcalamaresui/utils/Paste.cpp
index 519dc0133..d782d138e 100644
--- a/src/libcalamaresui/utils/Paste.cpp
+++ b/src/libcalamaresui/utils/Paste.cpp
@@ -69,7 +69,8 @@ STATICTEST QString
ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent )
{
QTcpSocket* socket = new QTcpSocket( parent );
- socket->connectToHost( serverUrl.host(), serverUrl.port() );
+ // 16 bits of port-number
+ socket->connectToHost( serverUrl.host(), quint16( serverUrl.port() ) );
if ( !socket->waitForConnected() )
{
diff --git a/src/libcalamaresui/widgets/ErrorDialog.cpp b/src/libcalamaresui/widgets/ErrorDialog.cpp
new file mode 100644
index 000000000..8c682f964
--- /dev/null
+++ b/src/libcalamaresui/widgets/ErrorDialog.cpp
@@ -0,0 +1,106 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Artem Grinev
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares is Free Software: see the License-Identifier above.
+ *
+ */
+
+#include "ErrorDialog.h"
+#include "ui_ErrorDialog.h"
+
+#include "widgets/TranslationFix.h"
+#include
+#include
+
+namespace Calamares
+{
+
+ErrorDialog::ErrorDialog( QWidget* parent )
+ : QDialog( parent )
+ , ui( new Ui::ErrorDialog )
+{
+ ui->setupUi( this );
+ ui->iconLabel->setPixmap( QIcon::fromTheme( "dialog-error" ).pixmap( 64 ) );
+ ui->detailsWidget->hide();
+ ui->offerWebPasteLabel->hide();
+}
+
+ErrorDialog::~ErrorDialog()
+{
+ delete ui;
+}
+
+QString
+ErrorDialog::heading() const
+{
+ return ui->headingLabel->text();
+}
+
+QString
+ErrorDialog::informativeText() const
+{
+ return ui->informativeTextLabel->text();
+}
+
+QString
+ErrorDialog::details() const
+{
+ return ui->detailsBrowser->toPlainText();
+}
+
+void
+ErrorDialog::setHeading( const QString& newHeading )
+{
+ if ( ui->headingLabel->text() != newHeading )
+ {
+ ui->headingLabel->setText( newHeading );
+ emit headingChanged();
+ }
+}
+
+void
+ErrorDialog::setInformativeText( const QString& newInformativeText )
+{
+ if ( ui->informativeTextLabel->text() != newInformativeText )
+ {
+ ui->informativeTextLabel->setText( newInformativeText );
+ emit informativeTextChanged();
+ }
+}
+
+void
+ErrorDialog::setDetails( const QString& newDetails )
+{
+ if ( ui->detailsBrowser->toPlainText() != newDetails )
+ {
+ ui->detailsBrowser->setPlainText( newDetails );
+ ui->detailsWidget->setVisible( !ui->detailsBrowser->toPlainText().trimmed().isEmpty() );
+ emit detailsChanged();
+ }
+}
+
+bool
+ErrorDialog::shouldOfferWebPaste() const
+{
+ return m_shouldOfferWebPaste;
+}
+
+void
+ErrorDialog::setShouldOfferWebPaste( bool newShouldOfferWebPaste )
+{
+ if ( m_shouldOfferWebPaste != newShouldOfferWebPaste )
+ {
+ m_shouldOfferWebPaste = newShouldOfferWebPaste;
+
+ ui->offerWebPasteLabel->setVisible( m_shouldOfferWebPaste );
+ ui->buttonBox->setStandardButtons( m_shouldOfferWebPaste ? ( QDialogButtonBox::Yes | QDialogButtonBox::No )
+ : QDialogButtonBox::Close );
+ fixButtonLabels( ui->buttonBox );
+
+ emit shouldOfferWebPasteChanged();
+ }
+}
+
+} // namespace Calamares
diff --git a/src/libcalamaresui/widgets/ErrorDialog.h b/src/libcalamaresui/widgets/ErrorDialog.h
new file mode 100644
index 000000000..c4949a89c
--- /dev/null
+++ b/src/libcalamaresui/widgets/ErrorDialog.h
@@ -0,0 +1,83 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Artem Grinev
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares is Free Software: see the License-Identifier above.
+ *
+ */
+
+#ifndef LIBCALAMARESUI_ERRORDIALOG_H
+#define LIBCALAMARESUI_ERRORDIALOG_H
+
+#include
+
+namespace Ui
+{
+class ErrorDialog;
+}
+
+namespace Calamares
+{
+class ErrorDialog : public QDialog
+{
+ Q_OBJECT
+
+ Q_PROPERTY( QString heading READ heading WRITE setHeading NOTIFY headingChanged )
+ Q_PROPERTY( QString informativeText READ informativeText WRITE setInformativeText NOTIFY informativeTextChanged )
+ Q_PROPERTY( QString details READ details WRITE setDetails NOTIFY detailsChanged )
+ Q_PROPERTY( bool shouldOfferWebPaste READ shouldOfferWebPaste WRITE setShouldOfferWebPaste NOTIFY
+ shouldOfferWebPasteChanged )
+
+public:
+ explicit ErrorDialog( QWidget* parent = nullptr );
+ ~ErrorDialog() override;
+
+ /** @brief The heading (title) of the error dialog
+ *
+ * This is a short (one-line) title. It is human-readable, so should
+ * be translated at the time it is set.
+ */
+ QString heading() const;
+ void setHeading( const QString& newHeading );
+
+ /** @brief The description of the problem
+ *
+ * Longer, human-readable, description of the problem. This text
+ * is word-wrapped as necessary.
+ */
+ QString informativeText() const;
+ void setInformativeText( const QString& newInformativeText );
+
+ /** @brief Details of the problem
+ *
+ * This is generally command-output; it might not be translated
+ * when set. It should be considered "background to the informative
+ * text", or maybe "the reasons". Write the informative text for
+ * the end-user.
+ */
+ QString details() const;
+ void setDetails( const QString& newDetails );
+
+ /** @brief Enable web-paste button
+ *
+ * The web-paste button can be configured at a global level,
+ * but each individual error dialog can be set separately.
+ */
+ bool shouldOfferWebPaste() const;
+ void setShouldOfferWebPaste( bool newShouldOfferWebPaste );
+
+signals:
+ void headingChanged();
+ void informativeTextChanged();
+ void detailsChanged();
+ void shouldOfferWebPasteChanged();
+
+private:
+ Ui::ErrorDialog* ui;
+ bool m_shouldOfferWebPaste = false;
+};
+
+}; // namespace Calamares
+
+#endif // LIBCALAMARESUI_ERRORDIALOG_H
diff --git a/src/libcalamaresui/widgets/ErrorDialog.ui b/src/libcalamaresui/widgets/ErrorDialog.ui
new file mode 100644
index 000000000..cb480034e
--- /dev/null
+++ b/src/libcalamaresui/widgets/ErrorDialog.ui
@@ -0,0 +1,133 @@
+
+
+ ErrorDialog
+
+
+
+ 0
+ 0
+ 425
+ 262
+
+
+
+ Dialog
+
+
+ -
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ -
+
+
+ Details:
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ Would you like to paste the install log to the web?
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Close
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ ErrorDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ ErrorDialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/libcalamaresui/widgets/TranslationFix.cpp b/src/libcalamaresui/widgets/TranslationFix.cpp
index 1262fceb5..dbfd0bd83 100644
--- a/src/libcalamaresui/widgets/TranslationFix.cpp
+++ b/src/libcalamaresui/widgets/TranslationFix.cpp
@@ -11,30 +11,34 @@
#include
#include
+#include
#include
+#include
namespace Calamares
{
+//Using QMessageBox's StandardButton enum here but according to headers they should be kept in-sync between multiple classes.
+static std::pair< decltype( QMessageBox::Ok ), const char* > maps[] = {
+ { QMessageBox::Ok, QT_TRANSLATE_NOOP( "StandardButtons", "&OK" ) },
+ { QMessageBox::Yes, QT_TRANSLATE_NOOP( "StandardButtons", "&Yes" ) },
+ { QMessageBox::No, QT_TRANSLATE_NOOP( "StandardButtons", "&No" ) },
+ { QMessageBox::Cancel, QT_TRANSLATE_NOOP( "StandardButtons", "&Cancel" ) },
+ { QMessageBox::Close, QT_TRANSLATE_NOOP( "StandardButtons", "&Close" ) },
+};
+
+template < typename TButtonBox >
void
-fixButtonLabels( QMessageBox* box )
+fixButtonLabels( TButtonBox* box )
{
if ( !box )
{
return;
}
- static std::pair< decltype( QMessageBox::Ok ), const char* > maps[] = {
- { QMessageBox::Ok, QT_TRANSLATE_NOOP( "StandardButtons", "&OK" ) },
- { QMessageBox::Yes, QT_TRANSLATE_NOOP( "StandardButtons", "&Yes" ) },
- { QMessageBox::No, QT_TRANSLATE_NOOP( "StandardButtons", "&No" ) },
- { QMessageBox::Cancel, QT_TRANSLATE_NOOP( "StandardButtons", "&Cancel" ) },
- { QMessageBox::Close, QT_TRANSLATE_NOOP( "StandardButtons", "&Close" ) },
- };
-
for ( auto [ sb, label ] : maps )
{
- auto* button = box->button( sb );
+ auto* button = box->button( static_cast< typename TButtonBox::StandardButton >( int( sb ) ) );
if ( button )
{
button->setText( QCoreApplication::translate( "StandardButtons", label ) );
@@ -42,4 +46,16 @@ fixButtonLabels( QMessageBox* box )
}
}
+void
+fixButtonLabels( QMessageBox* box )
+{
+ fixButtonLabels< QMessageBox >( box );
+}
+
+void
+fixButtonLabels( QDialogButtonBox* box )
+{
+ fixButtonLabels< QDialogButtonBox >( box );
+}
+
} // namespace Calamares
diff --git a/src/libcalamaresui/widgets/TranslationFix.h b/src/libcalamaresui/widgets/TranslationFix.h
index 107dad67d..89ee9a51a 100644
--- a/src/libcalamaresui/widgets/TranslationFix.h
+++ b/src/libcalamaresui/widgets/TranslationFix.h
@@ -13,6 +13,7 @@
#include "DllMacro.h"
class QMessageBox;
+class QDialogButtonBox;
namespace Calamares
{
@@ -26,6 +27,8 @@ namespace Calamares
* guess the context.
*/
void UIDLLEXPORT fixButtonLabels( QMessageBox* );
+
+void UIDLLEXPORT fixButtonLabels( QDialogButtonBox* );
} // namespace Calamares
#endif
diff --git a/src/modules/README.md b/src/modules/README.md
index 81d5974da..0b6b87953 100644
--- a/src/modules/README.md
+++ b/src/modules/README.md
@@ -86,18 +86,18 @@ it needs those keys.
### Emergency Modules
-Only C++ modules and job modules may be emergency modules. If, during an
-*exec* step in the sequence, a module fails, installation as a whole fails
-and the install is aborted. If there are emergency modules in the **same**
-exec block, those will be executed before the installation is aborted.
-Non-emergency modules are not executed.
+If, during an *exec* step in the sequence, a module fails, installation as
+a whole fails and the install is aborted. If there are emergency modules
+in the **same** exec block, those will be executed before the installation
+is aborted. Non-emergency modules are not executed.
If an emergency-module fails while processing emergency-modules for
another failed module, that failure is ignored and emergency-module
processing continues.
Use the EMERGENCY keyword in the CMake description of a C++ module
-to generate a suitable `module.desc`.
+to generate a suitable `module.desc`. For Python modules, manually add
+`emergency: true` to `module.desc`.
A module that is marked as an emergency module in its module.desc
must **also** set the *emergency* key to *true* in its configuration file
diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf
index c05f1929a..560aa0073 100644
--- a/src/modules/fstab/fstab.conf
+++ b/src/modules/fstab/fstab.conf
@@ -18,10 +18,16 @@
#
# btrfs_swap options are used when a swapfile is chosen with a btrfs root
# the options are applied to the subvolume which holds the swap partition
+#
+# The settings shown here apply only the btrfs defaults; these
+# are generally the right ones. Commented-out lines show other
+# options wich **might** be applicable for specific situations.
mountOptions:
default: defaults,noatime
- btrfs: defaults,noatime,autodefrag,compress=zstd
- btrfs_swap: defaults,noatime
+ # btrfs: defaults,noatime,autodefrag,compress=zstd
+ btrfs: defaults
+ # btrfs_swap: defaults,noatime
+ btrfs_swap: defaults
# Mount options to use for the EFI System Partition. If not defined, the
# *mountOptions* for *vfat* are used, or if that is not set either,
diff --git a/src/modules/fstab/main.py b/src/modules/fstab/main.py
index 261d3cadb..6a771a24b 100644
--- a/src/modules/fstab/main.py
+++ b/src/modules/fstab/main.py
@@ -237,7 +237,7 @@ class FstabGenerator(object):
return None
# If this is btrfs subvol a dedicated to a swapfile, use different options than a normal btrfs subvol
- if filesystem == "btrfs" and partition["subvol"] == "/@swap":
+ if filesystem == "btrfs" and partition.get("subvol", None) == "/@swap":
options = self.get_mount_options("btrfs_swap", mount_point)
else:
options = self.get_mount_options(filesystem, mount_point)
@@ -258,7 +258,8 @@ class FstabGenerator(object):
if mount_point == "/":
self.root_is_ssd = is_ssd
- if filesystem == "btrfs" and "subvol" in partition:
+ # 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
if has_luks:
diff --git a/src/modules/interactiveterminal/InteractiveTerminalPage.cpp b/src/modules/interactiveterminal/InteractiveTerminalPage.cpp
index afd39936b..ea30c21ef 100644
--- a/src/modules/interactiveterminal/InteractiveTerminalPage.cpp
+++ b/src/modules/interactiveterminal/InteractiveTerminalPage.cpp
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -41,8 +42,10 @@ InteractiveTerminalPage::InteractiveTerminalPage( QWidget* parent )
void
InteractiveTerminalPage::errorKonsoleNotInstalled()
{
- QMessageBox mb(QMessageBox::Critical,
- tr( "Konsole not installed" ), tr( "Please install KDE Konsole and try again!" ), QMessageBox::Ok );
+ QMessageBox mb( QMessageBox::Critical,
+ tr( "Konsole not installed" ),
+ tr( "Please install KDE Konsole and try again!" ),
+ QMessageBox::Ok );
Calamares::fixButtonLabels( &mb );
mb.exec();
}
@@ -54,20 +57,30 @@ InteractiveTerminalPage::onActivate()
{
return;
}
- // For whatever reason, instead of simply linking against a library we
- // need to do a runtime query to KService just to get a sodding terminal
- // widget.
+
+#if KCOREADDONS_VERSION_MAJOR != 5
+#error Incompatible with not-KF5
+#endif
+#if KCOREADDONS_VERSION_MINOR >= 86
+ // 5.86 deprecated a bunch of KService and PluginFactory and related methods
+ auto md = KPluginMetaData::findPluginById( QString(), "konsolepart" );
+ if ( !md.isValid() )
+ {
+ errorKonsoleNotInstalled();
+ return;
+ }
+ auto* p = KPluginFactory::instantiatePlugin< KParts::ReadOnlyPart >( md, this ).plugin;
+#else
KService::Ptr service = KService::serviceByDesktopName( "konsolepart" );
if ( !service )
{
- // And all of this hoping the Konsole application is installed. If not,
- // tough cookies.
errorKonsoleNotInstalled();
return;
}
// Create one instance of konsolepart.
KParts::ReadOnlyPart* p = service->createInstance< KParts::ReadOnlyPart >( this, this, {} );
+#endif
if ( !p )
{
// One more opportunity for the loading operation to fail.
@@ -91,7 +104,6 @@ InteractiveTerminalPage::onActivate()
m_termHostWidget = p->widget();
m_layout->addWidget( m_termHostWidget );
- cDebug() << "Part widget ought to be" << m_termHostWidget->metaObject()->className();
t->showShellInDir( QDir::home().path() );
t->sendInput( QString( "%1\n" ).arg( m_command ) );
diff --git a/src/modules/luksopenswaphookcfg/CMakeLists.txt b/src/modules/luksopenswaphookcfg/CMakeLists.txt
new file mode 100644
index 000000000..6d80df815
--- /dev/null
+++ b/src/modules/luksopenswaphookcfg/CMakeLists.txt
@@ -0,0 +1,22 @@
+# === This file is part of Calamares - ===
+#
+# SPDX-FileCopyrightText: 2021 Adriaan de Groot
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# Because LUKS Open Swap Hook (Job) is such a mouthful, we'll
+# use LOSH all over the place as a shorthand.
+calamares_add_plugin( luksopenswaphook
+ TYPE job
+ EXPORT_MACRO PLUGINDLLEXPORT_PRO
+ SOURCES
+ LOSHJob.cpp
+ SHARED_LIB
+)
+
+calamares_add_test(
+ luksopenswaphooktest
+ SOURCES
+ LOSHJob.cpp
+ Tests.cpp
+)
diff --git a/src/modules/luksopenswaphookcfg/LOSHInfo.h b/src/modules/luksopenswaphookcfg/LOSHInfo.h
new file mode 100644
index 000000000..1a87f4e61
--- /dev/null
+++ b/src/modules/luksopenswaphookcfg/LOSHInfo.h
@@ -0,0 +1,66 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+#ifndef LUKSOPENSWAPHOOKCFG_LOSHINFO_H
+#define LUKSOPENSWAPHOOKCFG_LOSHINFO_H
+
+#include
+
+/** @brief Information needed to create a suitable config file
+ *
+ * The LUKS swap configuration has a handful of keys that need to
+ * be written to the config file. This struct holds those keys
+ * and can find the key values from Global Storage (where the
+ * *partition* module sets them).
+ */
+struct LOSHInfo
+{
+ // Member names copied from Python code
+ QString swap_outer_uuid;
+ QString swap_mapper_name;
+ QString mountable_keyfile_device;
+ QString swap_device_path;
+ QString keyfile_device_mount_options;
+
+ bool isValid() const { return !swap_device_path.isEmpty(); }
+
+ /** @brief Helper method for doing key-value replacements
+ *
+ * Given a named @p key (e.g. "duck", or "swap_device"), returns the
+ * value set for that key. Invalid keys (e.g. "duck") return an empty string.
+ */
+ QString replacementFor( const QString& key ) const
+ {
+ if ( key == QStringLiteral( "swap_device" ) )
+ {
+ return swap_device_path;
+ }
+ if ( key == QStringLiteral( "crypt_swap_name" ) )
+ {
+ return swap_mapper_name;
+ }
+ if ( key == QStringLiteral( "keyfile_device" ) )
+ {
+ return mountable_keyfile_device;
+ }
+ if ( key == QStringLiteral( "keyfile_filename" ) )
+ {
+ return QStringLiteral( "crypto_keyfile.bin" );
+ }
+ if ( key == QStringLiteral( "keyfile_device_mount_options" ) )
+ {
+ return keyfile_device_mount_options;
+ }
+ return QString();
+ }
+
+ /** @brief Creates a struct from information already set in GS
+ *
+ */
+ static LOSHInfo fromGlobalStorage();
+};
+
+#endif
diff --git a/src/modules/luksopenswaphookcfg/LOSHJob.cpp b/src/modules/luksopenswaphookcfg/LOSHJob.cpp
new file mode 100644
index 000000000..42f160460
--- /dev/null
+++ b/src/modules/luksopenswaphookcfg/LOSHJob.cpp
@@ -0,0 +1,181 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+#include "LOSHJob.h"
+
+#include "LOSHInfo.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+#include "utils/Permissions.h"
+#include "utils/PluginFactory.h"
+#include "utils/String.h"
+#include "utils/Variant.h"
+
+#include
+#include
+#include
+#include
+
+LOSHJob::LOSHJob( QObject* parent )
+ : Calamares::CppJob( parent )
+{
+}
+
+LOSHJob::~LOSHJob() {}
+
+
+QString
+LOSHJob::prettyName() const
+{
+ return tr( "Configuring encrypted swap." );
+}
+
+STATICTEST QString
+get_assignment_part( const QString& line )
+{
+ static QRegularExpression re( "^[# \\t]*([A-Za-z_]+)[ \\t]*=" );
+ auto m = re.match( line );
+ if ( m.hasMatch() )
+ {
+ return m.captured( 1 );
+ }
+ return QString();
+}
+
+/** Writes the config file at @p path
+ *
+ * NOTE: @p path is relative to the target system, not an absolute path.
+ */
+STATICTEST void
+write_openswap_conf( const QString& path, QStringList& contents, const LOSHInfo& info )
+{
+ if ( info.isValid() )
+ {
+ for ( auto& line : contents )
+ {
+ const QString key = get_assignment_part( line );
+ QString replacement = info.replacementFor( key );
+ if ( !replacement.isEmpty() )
+ {
+ line.clear();
+ line.append( QStringLiteral( "%1=%2" ).arg( key, replacement ) );
+ }
+ }
+ cDebug() << "Writing" << contents.length() << "line configuration to" << path;
+ // \n between each two lines, and a \n at the end
+ CalamaresUtils::System::instance()->createTargetFile(
+ path, contents.join( '\n' ).append( '\n' ).toUtf8(), CalamaresUtils::System::WriteMode::Overwrite );
+ }
+ else
+ {
+ cDebug() << "Will not write an invalid configuration to" << path;
+ }
+}
+
+Calamares::JobResult
+LOSHJob::exec()
+{
+ const auto* sys = CalamaresUtils::System::instance();
+ if ( !sys )
+ {
+ return Calamares::JobResult::internalError(
+ "LuksOpenSwapHook", tr( "No target system available." ), Calamares::JobResult::InvalidConfiguration );
+ }
+
+ Calamares::GlobalStorage* gs
+ = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
+ if ( !gs || gs->value( "rootMountPoint" ).toString().isEmpty() )
+ {
+ return Calamares::JobResult::internalError(
+ "LuksOpenSwapHook", tr( "No rootMountPoint is set." ), Calamares::JobResult::InvalidConfiguration );
+ }
+ if ( m_configFilePath.isEmpty() )
+ {
+ return Calamares::JobResult::internalError(
+ "LuksOpenSwapHook", tr( "No configFilePath is set." ), Calamares::JobResult::InvalidConfiguration );
+ }
+
+ QStringList contents = sys->readTargetFile( m_configFilePath );
+ if ( contents.isEmpty() )
+ {
+ contents << QStringLiteral( "# swap_device=" ) << QStringLiteral( "# crypt_swap_name=" )
+ << QStringLiteral( "# keyfile_device=" ) << QStringLiteral( "# keyfile_filename=" )
+ << QStringLiteral( "# keyfile_device_mount_options" );
+ }
+
+ write_openswap_conf( m_configFilePath, contents, LOSHInfo::fromGlobalStorage() );
+ return Calamares::JobResult::ok();
+}
+
+void
+LOSHJob::setConfigurationMap( const QVariantMap& configurationMap )
+{
+ m_configFilePath = CalamaresUtils::getString(
+ configurationMap, QStringLiteral( "configFilePath" ), QStringLiteral( "/etc/openswap.conf" ) );
+}
+
+STATICTEST void
+globalStoragePartitionInfo( Calamares::GlobalStorage* gs, LOSHInfo& info )
+{
+ if ( !gs )
+ {
+ return;
+ }
+ QVariantList l = gs->value( "partitions" ).toList();
+ if ( l.isEmpty() )
+ {
+ return;
+ }
+
+ for ( const auto& pv : l )
+ {
+ const QVariantMap partition = pv.toMap();
+ if ( !partition.isEmpty() )
+ {
+ QString mountPoint = partition.value( "mountPoint" ).toString();
+ QString fileSystem = partition.value( "fs" ).toString();
+ QString luksMapperName = partition.value( "luksMapperName" ).toString();
+ // if partition["fs"] == "linuxswap" and "luksMapperName" in partition:
+ if ( fileSystem == QStringLiteral( "linuxswap" ) && !luksMapperName.isEmpty() )
+ {
+ info.swap_outer_uuid = partition.value( "luksUuid" ).toString();
+ info.swap_mapper_name = luksMapperName;
+ }
+ else if ( mountPoint == QStringLiteral( "/" ) && !luksMapperName.isEmpty() )
+ {
+
+ info.mountable_keyfile_device = QStringLiteral( "/dev/mapper/" ) + luksMapperName;
+ }
+ }
+ }
+
+ if ( !info.mountable_keyfile_device.isEmpty() && !info.swap_outer_uuid.isEmpty() )
+ {
+ info.swap_device_path = QStringLiteral( "/dev/disk/by-uuid/" ) + info.swap_outer_uuid;
+ }
+
+ QString btrfsRootSubvolume = gs->value( "btrfsRootSubvolume" ).toString();
+ if ( !btrfsRootSubvolume.isEmpty() )
+ {
+ CalamaresUtils::removeLeading( btrfsRootSubvolume, '/' );
+ info.keyfile_device_mount_options
+ = QStringLiteral( "keyfile_device_mount_options=--options=subvol=" ) + btrfsRootSubvolume;
+ }
+}
+
+LOSHInfo
+LOSHInfo::fromGlobalStorage()
+{
+ LOSHInfo i {};
+ globalStoragePartitionInfo(
+ Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr, i );
+ return i;
+}
+
+CALAMARES_PLUGIN_FACTORY_DEFINITION( LOSHJobFactory, registerPlugin< LOSHJob >(); )
diff --git a/src/modules/luksopenswaphookcfg/LOSHJob.h b/src/modules/luksopenswaphookcfg/LOSHJob.h
new file mode 100644
index 000000000..4a435a935
--- /dev/null
+++ b/src/modules/luksopenswaphookcfg/LOSHJob.h
@@ -0,0 +1,37 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+#ifndef LUKSOPENSWAPHOOKCFG_LOSHJOB_H
+#define LUKSOPENSWAPHOOKCFG_LOSHJOB_H
+
+#include "CppJob.h"
+#include "DllMacro.h"
+#include "utils/PluginFactory.h"
+
+#include
+#include
+
+class PLUGINDLLEXPORT LOSHJob : public Calamares::CppJob
+{
+ Q_OBJECT
+
+public:
+ explicit LOSHJob( QObject* parent = nullptr );
+ ~LOSHJob() override;
+
+ QString prettyName() const override;
+
+ Calamares::JobResult exec() override;
+
+ void setConfigurationMap( const QVariantMap& configurationMap ) override;
+
+private:
+ QString m_configFilePath;
+};
+
+CALAMARES_PLUGIN_FACTORY_DECLARATION( LOSHJobFactory )
+
+#endif
diff --git a/src/modules/luksopenswaphookcfg/Tests.cpp b/src/modules/luksopenswaphookcfg/Tests.cpp
new file mode 100644
index 000000000..840bd9355
--- /dev/null
+++ b/src/modules/luksopenswaphookcfg/Tests.cpp
@@ -0,0 +1,253 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "LOSHInfo.h"
+#include "LOSHJob.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+
+#include
+
+// LOSH = LUKS Open Swap Hook (Job)
+
+// Implementation details
+extern QString get_assignment_part( const QString& line );
+extern void write_openswap_conf( const QString& path, QStringList& contents, const LOSHInfo& info );
+
+class LOSHTests : public QObject
+{
+ Q_OBJECT
+public:
+ LOSHTests();
+ ~LOSHTests() override {}
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testAssignmentExtraction_data();
+ void testAssignmentExtraction();
+
+ void testLOSHInfo();
+ void testConfigWriting();
+ void testJob();
+};
+
+LOSHTests::LOSHTests() {}
+
+void
+LOSHTests::initTestCase()
+{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
+ cDebug() << "LOSH test started.";
+}
+
+
+void
+LOSHTests::testAssignmentExtraction_data()
+{
+ QTest::addColumn< QString >( "line" );
+ QTest::addColumn< QString >( "match" );
+
+ QTest::newRow( "empty" ) << QString() << QString();
+ QTest::newRow( "comment-only1" ) << QStringLiteral( "# " ) << QString();
+ QTest::newRow( "comment-only2" ) << QStringLiteral( "###" ) << QString();
+ QTest::newRow( "comment-only3" ) << QStringLiteral( "# # #" ) << QString();
+
+ QTest::newRow( "comment-text" ) << QStringLiteral( "# NOTE:" ) << QString();
+ QTest::newRow( "comment-story" ) << QStringLiteral( "# This is a shell comment" ) << QString();
+ // We look for assignments, but only for single-words
+ QTest::newRow( "comment-space-eq" ) << QStringLiteral( "# Check that a = b" ) << QString();
+
+
+ QTest::newRow( "assignment1" ) << QStringLiteral( "a=1" ) << QStringLiteral( "a" );
+ QTest::newRow( "assignment2" ) << QStringLiteral( "a = 1" ) << QStringLiteral( "a" );
+ QTest::newRow( "assignment3" ) << QStringLiteral( "# a=1" ) << QStringLiteral( "a" );
+ QTest::newRow( "assignment4" ) << QStringLiteral( "cows = 12" ) << QStringLiteral( "cows" );
+ QTest::newRow( "assignment5" ) << QStringLiteral( "# # cows=1" ) << QStringLiteral( "cows" );
+ QTest::newRow( "assignment6" ) << QStringLiteral( "# moose='cool' # not cows" ) << QStringLiteral( "moose" );
+ QTest::newRow( "assignment7" ) << QStringLiteral( " moose=cows=42" ) << QStringLiteral( "moose" );
+ QTest::newRow( "assignment8" ) << QStringLiteral( "#swap_device=/dev/something" )
+ << QStringLiteral( "swap_device" );
+ QTest::newRow( "assignment9" ) << QStringLiteral( "# swap_device=/dev/something" )
+ << QStringLiteral( "swap_device" );
+ QTest::newRow( "assignment10" ) << QStringLiteral( "swap_device=/dev/something" )
+ << QStringLiteral( "swap_device" );
+}
+
+void
+LOSHTests::testAssignmentExtraction()
+{
+ QFETCH( QString, line );
+ QFETCH( QString, match );
+
+ QCOMPARE( get_assignment_part( line ), match );
+}
+
+static CalamaresUtils::System*
+file_setup( const QTemporaryDir& tempRoot )
+{
+ CalamaresUtils::System* ss = CalamaresUtils::System::instance();
+ if ( !ss )
+ {
+ ss = new CalamaresUtils::System( true );
+ }
+
+ Calamares::GlobalStorage* gs
+ = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
+ if ( !gs )
+ {
+ cDebug() << "Creating new JobQueue";
+ (void)new Calamares::JobQueue();
+ gs = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
+ }
+ if ( gs )
+ {
+ // Working with a rootMountPoint set
+ gs->insert( "rootMountPoint", tempRoot.path() );
+ }
+ return ss;
+}
+
+static void
+make_valid_loshinfo( LOSHInfo& i )
+{
+ i.swap_outer_uuid = QStringLiteral( "UUID-0000" );
+ i.swap_mapper_name = QStringLiteral( "/dev/mapper/0000" );
+ i.swap_device_path = QStringLiteral( "/dev/sda0" );
+ i.mountable_keyfile_device = QStringLiteral( "/dev/ada0p0s0" );
+}
+
+void
+LOSHTests::testLOSHInfo()
+{
+ LOSHInfo i {};
+ QVERIFY( !i.isValid() );
+
+ make_valid_loshinfo( i );
+ QVERIFY( i.isValid() );
+ QCOMPARE( i.replacementFor( QStringLiteral( "swap_device" ) ), QStringLiteral( "/dev/sda0" ) );
+ QCOMPARE( i.replacementFor( QStringLiteral( "duck" ) ), QString() );
+}
+
+
+void
+LOSHTests::testConfigWriting()
+{
+ QTemporaryDir tempRoot( QDir::tempPath() + QStringLiteral( "/test-job-XXXXXX" ) );
+ QVERIFY( tempRoot.isValid() );
+ auto* ss = file_setup( tempRoot );
+ QVERIFY( ss );
+ QVERIFY( Calamares::JobQueue::instance()->globalStorage() );
+ QVERIFY( QFile::exists( tempRoot.path() ) );
+ QVERIFY( QFileInfo( tempRoot.path() ).isDir() );
+
+ const QString targetFilePath = QStringLiteral( "losh.conf" );
+ const QString filePath = tempRoot.filePath( targetFilePath );
+ QStringList contents { QStringLiteral( "# Calamares demo" ),
+ QStringLiteral( "# swap_device=a thing" ),
+ QStringLiteral( "# duck duck swap_device=another" ) };
+
+ // When the information is invalid, file contents are unchanged,
+ // and no file is written either.
+ LOSHInfo i {};
+ QVERIFY( !i.isValid() );
+ QVERIFY( !QFile::exists( filePath ) );
+ write_openswap_conf( targetFilePath, contents, i ); // Invalid i
+ QVERIFY( !QFile::exists( filePath ) );
+ QCOMPARE( contents.length(), 3 );
+ QCOMPARE( contents.at( 1 ).left( 4 ), QStringLiteral( "# s" ) );
+
+ // Can we write there at all?
+ QFile derp( filePath );
+ QVERIFY( derp.open( QIODevice::WriteOnly ) );
+ QVERIFY( derp.write( "xx", 2 ) );
+ derp.close();
+ QVERIFY( QFile::exists( filePath ) );
+ QVERIFY( QFile::remove( filePath ) );
+
+ // Once the information is valid, though, the file is written
+ make_valid_loshinfo( i );
+ QVERIFY( i.isValid() );
+ QVERIFY( !QFile::exists( filePath ) );
+ write_openswap_conf( targetFilePath, contents, i ); // Now it is valid
+ QVERIFY( QFile::exists( filePath ) );
+ QCOMPARE( contents.length(), 3 );
+ QCOMPARE( i.swap_device_path, QStringLiteral( "/dev/sda0" ) ); // expected key value
+ QCOMPARE( contents.at( 1 ), QStringLiteral( "swap_device=/dev/sda0" ) ); // expected line
+
+ // readLine() returns with newlines-added
+ QFile f( filePath );
+ QVERIFY( f.open( QIODevice::ReadOnly ) );
+ QCOMPARE( f.readLine(), QStringLiteral( "# Calamares demo\n" ) );
+ QCOMPARE( f.readLine(), QStringLiteral( "swap_device=/dev/sda0\n" ) );
+ QCOMPARE( f.readLine(), QStringLiteral( "# duck duck swap_device=another\n" ) );
+ QCOMPARE( f.readLine(), QString() );
+ QVERIFY( f.atEnd() );
+
+ // Note how the contents is updated on every write_openswap_conf()
+ i.swap_device_path = QStringLiteral( "/dev/zram/0.zram" );
+ write_openswap_conf( targetFilePath, contents, i ); // Still valid
+ QCOMPARE( contents.length(), 3 );
+ QCOMPARE( i.swap_device_path, QStringLiteral( "/dev/zram/0.zram" ) ); // expected key value
+ QCOMPARE( contents.at( 1 ), QStringLiteral( "swap_device=/dev/zram/0.zram" ) ); // expected line
+}
+
+
+void
+LOSHTests::testJob()
+{
+ QTemporaryDir tempRoot( QDir::tempPath() + QStringLiteral( "/test-job-XXXXXX" ) );
+ QVERIFY( tempRoot.isValid() );
+ auto* ss = file_setup( tempRoot );
+ QVERIFY( ss );
+ Calamares::GlobalStorage* gs
+ = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
+ QVERIFY( gs );
+
+ {
+ QDir d( tempRoot.path() );
+ d.mkdir( "etc" );
+ }
+
+ QVERIFY( !LOSHInfo::fromGlobalStorage().isValid() );
+ QVariantList outerPartition;
+ QVariantMap innerPartition;
+ innerPartition.insert( "mountPoint", "/" );
+ innerPartition.insert( "fs", "ext4" );
+ innerPartition.insert( "luksMapperName", "root" );
+ innerPartition.insert( "luksUUID", "0000" );
+ outerPartition.append( innerPartition );
+ innerPartition.remove( "mountPoint" );
+ innerPartition.insert( "fs", "linuxswap" );
+ innerPartition.insert( "luksMapperName", "swap" );
+ innerPartition.insert( "luksUuid", "0001" );
+ outerPartition.append( innerPartition );
+ gs->insert( "partitions", outerPartition );
+ QVERIFY( LOSHInfo::fromGlobalStorage().isValid() );
+
+ LOSHJob j;
+ j.setConfigurationMap( QVariantMap() );
+ auto jobresult = j.exec();
+ QVERIFY( jobresult );
+
+ {
+ QFile f( tempRoot.filePath( "etc/openswap.conf" ) );
+ QVERIFY( f.exists() );
+ QVERIFY( f.open( QIODevice::ReadOnly ) );
+ cDebug() << f.readAll();
+ }
+}
+
+
+QTEST_GUILESS_MAIN( LOSHTests )
+
+#include "utils/moc-warnings.h"
+
+#include "Tests.moc"
diff --git a/src/modules/luksopenswaphookcfg/main.py b/src/modules/luksopenswaphookcfg/main.py
deleted file mode 100644
index ec09a631b..000000000
--- a/src/modules/luksopenswaphookcfg/main.py
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# === This file is part of Calamares - ===
-#
-# SPDX-FileCopyrightText: 2016 Teo Mrnjavac
-# SPDX-FileCopyrightText: 2017 Alf Gaida
-# SPDX-FileCopyrightText: 2019 Adriaan de Groot
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Calamares is Free Software: see the License-Identifier above.
-#
-
-import libcalamares
-import os.path
-
-
-import gettext
-_ = gettext.translation("calamares-python",
- localedir=libcalamares.utils.gettext_path(),
- languages=libcalamares.utils.gettext_languages(),
- fallback=True).gettext
-
-
-def pretty_name():
- return _("Configuring encrypted swap.")
-
-
-def write_openswap_conf(partitions, root_mount_point, openswap_conf_path):
- swap_outer_uuid = ""
- swap_mapper_name = ""
- mountable_keyfile_device = ""
-
- for partition in partitions:
- if partition["fs"] == "linuxswap" and "luksMapperName" in partition:
- swap_outer_uuid = partition["luksUuid"]
- swap_mapper_name = partition["luksMapperName"]
-
- elif partition["mountPoint"] == "/" and "luksMapperName" in partition:
- mountable_keyfile_device = (
- "/dev/mapper/{!s}".format(partition["luksMapperName"])
- )
-
- if not mountable_keyfile_device or not swap_outer_uuid:
- return None
-
- swap_device_path = "/dev/disk/by-uuid/{!s}".format(swap_outer_uuid)
-
- lines = []
- with open(os.path.join(root_mount_point,
- openswap_conf_path), 'r') as openswap_file:
- lines = [x.strip() for x in openswap_file.readlines()]
-
- for i in range(len(lines)):
- if lines[i].startswith("swap_device"):
- lines[i] = "swap_device={!s}".format(swap_device_path)
-
- elif lines[i].startswith("crypt_swap_name"):
- lines[i] = "crypt_swap_name={!s}".format(swap_mapper_name)
-
- elif lines[i].startswith("keyfile_device"):
- lines[i] = "keyfile_device={!s}".format(mountable_keyfile_device)
-
- elif lines[i].startswith("keyfile_filename"):
- lines[i] = "keyfile_filename=crypto_keyfile.bin"
-
- with open(os.path.join(root_mount_point,
- openswap_conf_path), 'w') as openswap_file:
- openswap_file.write("\n".join(lines) + "\n")
-
- return None
-
-
-def run():
- """
- This module sets up the openswap hook for a resumable encrypted swap.
- :return:
- """
-
- root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
- openswap_conf_path = libcalamares.job.configuration["configFilePath"]
- partitions = libcalamares.globalstorage.value("partitions")
-
- if not partitions:
- libcalamares.utils.warning("partitions is empty, {!s}".format(partitions))
- return (_("Configuration Error"),
- _("No partitions are defined for {!s}
to use." ).format("luksopenswaphookcfg"))
- if not root_mount_point:
- libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(root_mount_point))
- return (_("Configuration Error"),
- _("No root mount point is given for {!s}
to use." ).format("luksopenswaphookcfg"))
-
- openswap_conf_path = openswap_conf_path.lstrip('/')
-
- return write_openswap_conf(partitions, root_mount_point, openswap_conf_path)
diff --git a/src/modules/luksopenswaphookcfg/module.desc b/src/modules/luksopenswaphookcfg/module.desc
deleted file mode 100644
index 919a92792..000000000
--- a/src/modules/luksopenswaphookcfg/module.desc
+++ /dev/null
@@ -1,7 +0,0 @@
-# SPDX-FileCopyrightText: no
-# SPDX-License-Identifier: CC0-1.0
----
-type: "job"
-name: "luksopenswaphookcfg"
-interface: "python"
-script: "main.py"
diff --git a/src/modules/mount/main.py b/src/modules/mount/main.py
index 900342e6d..a3318d1a0 100644
--- a/src/modules/mount/main.py
+++ b/src/modules/mount/main.py
@@ -191,6 +191,9 @@ def mount_partition(root_mount_point, partition, partitions):
libcalamares.globalstorage.insert("btrfsSubvolumes", btrfs_subvolumes)
# Create the subvolumes that are in the completed list
for s in btrfs_subvolumes:
+ if not s["subvolume"]:
+ continue
+ os.makedirs(root_mount_point + os.path.dirname(s["subvolume"]), exist_ok=True)
subprocess.check_call(["btrfs", "subvolume", "create",
root_mount_point + s["subvolume"]])
if s["mountPoint"] == "/":
diff --git a/src/modules/mount/mount.conf b/src/modules/mount/mount.conf
index 6168e97cc..84dca05a7 100644
--- a/src/modules/mount/mount.conf
+++ b/src/modules/mount/mount.conf
@@ -42,15 +42,24 @@ extraMountsEfi:
mountPoint: /sys/firmware/efi/efivars
# Btrfs subvolumes to create if root filesystem is on btrfs volume.
-# If mountpoint is mounted already to another partition, it is ignored.
+# If *mountpoint* is mounted already to another partition, it is ignored.
# Separate subvolume for swapfile is handled separately and automatically.
+#
+# It is possible to prevent subvolume creation -- this is likely only relevant
+# for the root (/) subvolume -- by giving an empty string as a subvolume
+# name. In this case no subvolume will be created. When using snapper as
+# a rollback mechanism, it is recommended to **not** create a subvolume
+# for root.
btrfsSubvolumes:
- mountPoint: /
subvolume: /@
+ # As an alternative:
+ #
+ # subvolume: ""
- mountPoint: /home
subvolume: /@home
- mountPoint: /var/cache
subvolume: /@cache
- mountPoint: /var/log
- subvolume: /@log
\ No newline at end of file
+ subvolume: /@log
diff --git a/src/modules/networkcfg/main.py b/src/modules/networkcfg/main.py
index 0ee47c1aa..807fdf613 100644
--- a/src/modules/networkcfg/main.py
+++ b/src/modules/networkcfg/main.py
@@ -73,12 +73,12 @@ def replace_username(nm_config_filename, live_user, target_user):
if not os.path.exists(nm_config_filename):
return
- with open(nm_config_filename, "r") as network_conf:
+ with open(nm_config_filename, "r", encoding="UTF-8") as network_conf:
text = network_conf.readlines()
live_permissions = 'permissions=user:{}:;'.format(live_user)
target_permissions = 'permissions=user:{}:;\n'.format(target_user)
- with open(nm_config_filename, "w") as network_conf:
+ with open(nm_config_filename, "w", encoding="UTF-8") as network_conf:
for line in text:
if live_permissions in line:
line = target_permissions
diff --git a/src/modules/packages/tests/pm-pacman-1.yaml b/src/modules/packages/tests/pm-pacman-1.yaml
index e876bd0fe..aeb5b8625 100644
--- a/src/modules/packages/tests/pm-pacman-1.yaml
+++ b/src/modules/packages/tests/pm-pacman-1.yaml
@@ -5,6 +5,6 @@ operations: []
pacman:
num_retries: 4
- disable_download_timeout: yes
+ disable_download_timeout: true
needed_only: true
diff --git a/src/modules/partition/PartitionViewStep.cpp b/src/modules/partition/PartitionViewStep.cpp
index ced08f7fc..2285d55eb 100644
--- a/src/modules/partition/PartitionViewStep.cpp
+++ b/src/modules/partition/PartitionViewStep.cpp
@@ -552,7 +552,7 @@ PartitionViewStep::onLeave()
if ( !okSize )
{
cDebug() << o << "ESP too small";
- const auto atLeastBytes = PartUtils::efiFilesystemMinimumSize();
+ const qint64 atLeastBytes = static_cast< qint64 >( PartUtils::efiFilesystemMinimumSize() );
const auto atLeastMiB = CalamaresUtils::BytesToMiB( atLeastBytes );
description.append( ' ' );
description.append( tr( "The filesystem must be at least %1 MiB in size." ).arg( atLeastMiB ) );
diff --git a/src/modules/partition/core/DeviceList.cpp b/src/modules/partition/core/DeviceList.cpp
index c7de12e88..423c3b4ee 100644
--- a/src/modules/partition/core/DeviceList.cpp
+++ b/src/modules/partition/core/DeviceList.cpp
@@ -11,6 +11,7 @@
#include "DeviceList.h"
#include "partition/PartitionIterator.h"
+#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include
@@ -43,11 +44,9 @@ hasRootPartition( Device* device )
static bool
blkIdCheckIso9660( const QString& path )
{
- QProcess blkid;
- blkid.start( "blkid", { path } );
- blkid.waitForFinished();
- QString output = QString::fromLocal8Bit( blkid.readAllStandardOutput() );
- return output.contains( "iso9660" );
+ // If blkid fails, there's no output, but we don't care
+ auto r = CalamaresUtils::System::runCommand( { "blkid", path }, std::chrono::seconds( 30 ) );
+ return r.getOutput().contains( "iso9660" );
}
static bool
diff --git a/src/modules/partition/core/DeviceModel.cpp b/src/modules/partition/core/DeviceModel.cpp
index 33aae20c0..25bdbff6c 100644
--- a/src/modules/partition/core/DeviceModel.cpp
+++ b/src/modules/partition/core/DeviceModel.cpp
@@ -8,9 +8,10 @@
* Calamares is Free Software: see the License-Identifier above.
*
*/
-#include "core/DeviceModel.h"
+#include "DeviceModel.h"
#include "core/PartitionModel.h"
+#include "core/SizeUtils.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
@@ -18,9 +19,6 @@
// KPMcore
#include
-// KF5
-#include
-
#include
#include
@@ -83,7 +81,7 @@ DeviceModel::data( const QModelIndex& index, int role ) const
//: device[name] - size[number] (device-node[name])
return tr( "%1 - %2 (%3)" )
.arg( device->name() )
- .arg( KFormat().formatByteSize( device->capacity() ) )
+ .arg( formatByteSize( device->capacity() ) )
.arg( device->deviceNode() );
}
else
diff --git a/src/modules/partition/core/KPMHelpers.cpp b/src/modules/partition/core/KPMHelpers.cpp
index ed105e28b..d5086ef21 100644
--- a/src/modules/partition/core/KPMHelpers.cpp
+++ b/src/modules/partition/core/KPMHelpers.cpp
@@ -15,8 +15,8 @@
#include "partition/PartitionIterator.h"
#include "utils/Logger.h"
+#include "utils/String.h"
-// KPMcore
#include
#include
#include
@@ -127,4 +127,23 @@ clonePartition( Device* device, Partition* partition )
partition->activeFlags() );
}
+Calamares::JobResult
+execute( Operation& operation, const QString& failureMessage )
+{
+ operation.setStatus( Operation::StatusRunning );
+
+ Report report( nullptr );
+ if ( operation.execute( report ) )
+ {
+ return Calamares::JobResult::ok();
+ }
+
+ // Remove the === lines from the report by trimming them to empty
+ QStringList l = report.toText().split( '\n' );
+ std::for_each( l.begin(), l.end(), []( QString& s ) { CalamaresUtils::removeLeading( s, '=' ); } );
+
+ return Calamares::JobResult::error( failureMessage, l.join( '\n' ) );
+}
+
+
} // namespace KPMHelpers
diff --git a/src/modules/partition/core/KPMHelpers.h b/src/modules/partition/core/KPMHelpers.h
index 89a019f6c..2f867bc25 100644
--- a/src/modules/partition/core/KPMHelpers.h
+++ b/src/modules/partition/core/KPMHelpers.h
@@ -11,11 +11,13 @@
#ifndef KPMHELPERS_H
#define KPMHELPERS_H
-// KPMcore
+#include "Job.h"
+
#include
#include
+#include
+#include
-// Qt
#include
#include
@@ -72,6 +74,24 @@ Partition* createNewEncryptedPartition( PartitionNode* parent,
Partition* clonePartition( Device* device, Partition* partition );
+/** @brief Return a result for an @p operation
+ *
+ * Executes the operation, and if successful, returns a success result.
+ * Otherwise returns an error using @p failureMessage as the primary part
+ * of the error, and details obtained from the operation.
+ */
+Calamares::JobResult execute( Operation& operation, const QString& failureMessage );
+/** @brief Return a result for an @p operation
+ *
+ * It's acceptable to use an rvalue: the operation-running is the effect
+ * you're interested in, rather than keeping the temporary around.
+ */
+static inline Calamares::JobResult
+execute( Operation&& operation, const QString& failureMessage )
+{
+ return execute( operation, failureMessage );
+}
+
} // namespace KPMHelpers
#endif /* KPMHELPERS_H */
diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp
index 507330a80..cb7c8a01d 100644
--- a/src/modules/partition/core/PartUtils.cpp
+++ b/src/modules/partition/core/PartUtils.cpp
@@ -451,6 +451,8 @@ isEfiFilesystemSuitableType( const Partition* candidate )
{
auto type = candidate->fileSystem().type();
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_CLANG( "-Wswitch-enum" )
switch ( type )
{
case FileSystem::Type::Fat32:
@@ -465,6 +467,7 @@ isEfiFilesystemSuitableType( const Partition* candidate )
cWarning() << "EFI boot partition must be FAT32";
return false;
}
+ QT_WARNING_POP
}
bool
@@ -526,14 +529,15 @@ efiFilesystemMinimumSize()
{
using CalamaresUtils::Units::operator""_MiB;
- auto uefisys_part_sizeB = 300_MiB;
+ size_t uefisys_part_sizeB = 300_MiB;
// The default can be overridden; the key used here comes
// from the partition module Config.cpp
auto* gs = Calamares::JobQueue::instance()->globalStorage();
if ( gs->contains( "efiSystemPartitionSize_i" ) )
{
- uefisys_part_sizeB = gs->value( "efiSystemPartitionSize_i" ).toLongLong();
+ qint64 v = gs->value( "efiSystemPartitionSize_i" ).toLongLong();
+ uefisys_part_sizeB = v > 0 ? static_cast< size_t >( v ) : 0;
}
// There is a lower limit of what can be configured
if ( uefisys_part_sizeB < 32_MiB )
diff --git a/src/modules/partition/core/PartitionActions.cpp b/src/modules/partition/core/PartitionActions.cpp
index 8514bbe2c..a56446a39 100644
--- a/src/modules/partition/core/PartitionActions.cpp
+++ b/src/modules/partition/core/PartitionActions.cpp
@@ -71,15 +71,15 @@ swapSuggestion( const qint64 availableSpaceB, Config::SwapChoice swap )
// Allow for a fudge factor
- suggestedSwapSizeB *= overestimationFactor;
+ suggestedSwapSizeB = qRound64( suggestedSwapSizeB * overestimationFactor );
// don't use more than 10% of available space
if ( !ensureSuspendToDisk )
{
- suggestedSwapSizeB = qMin( suggestedSwapSizeB, qint64( 0.10 * availableSpaceB ) );
+ suggestedSwapSizeB = qMin( suggestedSwapSizeB, availableSpaceB / 10 /* 10% is 0.1 */ );
}
- cDebug() << "Suggested swap size:" << suggestedSwapSizeB / 1024. / 1024. / 1024. << "GiB";
+ cDebug() << "Suggested swap size:" << CalamaresUtils::BytesToGiB( suggestedSwapSizeB ) << "GiB";
return suggestedSwapSizeB;
}
diff --git a/src/modules/partition/core/PartitionLayout.cpp b/src/modules/partition/core/PartitionLayout.cpp
index f60952643..3813207ef 100644
--- a/src/modules/partition/core/PartitionLayout.cpp
+++ b/src/modules/partition/core/PartitionLayout.cpp
@@ -141,6 +141,8 @@ void
PartitionLayout::setDefaultFsType( FileSystem::Type defaultFsType )
{
using FileSystem = FileSystem::Type;
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_CLANG( "-Wswitch-enum" )
switch ( defaultFsType )
{
case FileSystem::Unknown:
@@ -196,6 +198,7 @@ PartitionLayout::setDefaultFsType( FileSystem::Type defaultFsType )
<< "Using ext4 instead.";
defaultFsType = FileSystem::Ext4;
}
+ QT_WARNING_POP
m_defaultFsType = defaultFsType;
}
diff --git a/src/modules/partition/core/PartitionModel.cpp b/src/modules/partition/core/PartitionModel.cpp
index e310eee5e..19dbcd076 100644
--- a/src/modules/partition/core/PartitionModel.cpp
+++ b/src/modules/partition/core/PartitionModel.cpp
@@ -8,11 +8,12 @@
*
*/
-#include "core/PartitionModel.h"
+#include "PartitionModel.h"
#include "core/ColorUtils.h"
#include "core/KPMHelpers.h"
#include "core/PartitionInfo.h"
+#include "core/SizeUtils.h"
#include "partition/FileSystem.h"
#include "partition/PartitionQuery.h"
@@ -24,9 +25,6 @@
#include
#include
-// KF5
-#include
-
// Qt
#include
@@ -178,7 +176,7 @@ PartitionModel::data( const QModelIndex& index, int role ) const
if ( col == SizeColumn )
{
qint64 size = ( partition->lastSector() - partition->firstSector() + 1 ) * m_device->logicalSize();
- return KFormat().formatByteSize( size );
+ return formatByteSize( size );
}
cDebug() << "Unknown column" << col;
return QVariant();
@@ -210,7 +208,7 @@ PartitionModel::data( const QModelIndex& index, int role ) const
QString prettyFileSystem
= CalamaresUtils::Partition::prettyNameForFileSystemType( partition->fileSystem().type() );
qint64 size = ( partition->lastSector() - partition->firstSector() + 1 ) * m_device->logicalSize();
- QString prettySize = KFormat().formatByteSize( size );
+ QString prettySize = formatByteSize( size );
return QVariant( name + " " + prettyFileSystem + " " + prettySize );
}
case SizeRole:
diff --git a/src/modules/partition/core/SizeUtils.h b/src/modules/partition/core/SizeUtils.h
new file mode 100644
index 000000000..d474656c9
--- /dev/null
+++ b/src/modules/partition/core/SizeUtils.h
@@ -0,0 +1,27 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares is Free Software: see the License-Identifier above.
+ *
+ */
+
+#ifndef PARTITION_CORE_SIZEUTILS_H
+#define PARTITION_CORE_SIZEUTILS_H
+
+#include
+
+/** @brief Helper function for printing sizes consistently.
+ *
+ * Most of Calamares uses a qint64 for partition sizes, so use that
+ * parameter type. However, the human-visible formatting doesn't need
+ * to bother with one-byte accuracy (and anyway, a double has at least 50 bits
+ * at which point we're printing giga (or gibi) bytes).
+ */
+static inline QString formatByteSize( qint64 sizeValue )
+{
+ return Capacity::formatByteSize( static_cast< double >( sizeValue ) );
+}
+
+#endif // PARTITION_CORE_SIZEUTILS_H
diff --git a/src/modules/partition/gui/DeviceInfoWidget.cpp b/src/modules/partition/gui/DeviceInfoWidget.cpp
index bbc8ce74c..39d9413e1 100644
--- a/src/modules/partition/gui/DeviceInfoWidget.cpp
+++ b/src/modules/partition/gui/DeviceInfoWidget.cpp
@@ -68,7 +68,8 @@ DeviceInfoWidget::setPartitionTableType( PartitionTable::TableType type )
void
DeviceInfoWidget::retranslateUi()
{
- QString typeString = PartitionTable::tableTypeToName( m_tableType ).toUpper();
+ QString typeString;
+ QString toolTipString;
// fix up if the name shouldn't be uppercase:
switch ( m_tableType )
@@ -76,38 +77,34 @@ DeviceInfoWidget::retranslateUi()
case PartitionTable::msdos:
case PartitionTable::msdos_sectorbased:
typeString = "MBR";
+ toolTipString += tr( "
This partition table type is only advisable on older "
+ "systems which start from a BIOS boot "
+ "environment. GPT is recommended in most other cases.
"
+ "Warning: the MBR partition table "
+ "is an obsolete MS-DOS era standard.
"
+ "Only 4 primary partitions may be created, and of "
+ "those 4, one can be an extended partition, which "
+ "may in turn contain many logical partitions." );
+ break;
+ case PartitionTable::gpt:
+ // TypeString is ok
+ toolTipString += tr( "
This is the recommended partition table type for modern "
+ "systems which start from an EFI boot "
+ "environment." );
break;
case PartitionTable::loop:
typeString = "loop";
- break;
- case PartitionTable::mac:
- typeString = "Mac";
- break;
- case PartitionTable::amiga:
- typeString = "Amiga";
- break;
- case PartitionTable::sun:
- typeString = "Sun";
- break;
- case PartitionTable::unknownTableType:
- typeString = " ? ";
- }
-
-
- QString toolTipString = tr( "This device has a %1 partition "
- "table." )
- .arg( typeString );
-
- switch ( m_tableType )
- {
- case PartitionTable::loop:
toolTipString = tr( "This is a loop "
"device.
"
"It is a pseudo-device with no partition table "
"that makes a file accessible as a block device. "
"This kind of setup usually only contains a single filesystem." );
break;
+#if defined( WITH_KPMCORE42API )
+ case PartitionTable::none:
+#endif
case PartitionTable::unknownTableType:
+ typeString = " ? ";
toolTipString = tr( "This installer cannot detect a partition table on the "
"selected storage device.
"
"The device either has no partition "
@@ -117,21 +114,35 @@ DeviceInfoWidget::retranslateUi()
"either automatically, or through the manual partitioning "
"page." );
break;
- case PartitionTable::gpt:
- toolTipString += tr( "
This is the recommended partition table type for modern "
- "systems which start from an EFI boot "
- "environment." );
+ // The next ones need to have the name adjusted, but the default tooltip is OK
+ case PartitionTable::mac:
+ typeString = "Mac";
break;
- case PartitionTable::msdos:
- case PartitionTable::msdos_sectorbased:
- toolTipString += tr( "
This partition table type is only advisable on older "
- "systems which start from a BIOS boot "
- "environment. GPT is recommended in most other cases.
"
- "Warning: the MBR partition table "
- "is an obsolete MS-DOS era standard.
"
- "Only 4 primary partitions may be created, and of "
- "those 4, one can be an extended partition, which "
- "may in turn contain many logical partitions." );
+ case PartitionTable::amiga:
+ typeString = "Amiga";
+ break;
+ case PartitionTable::sun:
+ typeString = "Sun";
+ break;
+ // Peculiar tables, do nothing and use default type and tooltip strings
+ case PartitionTable::aix:
+ case PartitionTable::bsd:
+ case PartitionTable::dasd:
+ case PartitionTable::dvh:
+ case PartitionTable::pc98:
+ case PartitionTable::vmd:
+ break;
+ }
+
+ if ( typeString.isEmpty() )
+ {
+ typeString = PartitionTable::tableTypeToName( m_tableType ).toUpper();
+ }
+ if ( toolTipString.isEmpty() )
+ {
+ toolTipString = tr( "This device has a %1 partition "
+ "table." )
+ .arg( typeString );
}
m_ptLabel->setText( typeString );
diff --git a/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.cpp b/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.cpp
index 1e8a9e573..8eeafcbf4 100644
--- a/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.cpp
+++ b/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.cpp
@@ -9,11 +9,10 @@
#include "ListPhysicalVolumeWidgetItem.h"
-#include
+#include "core/SizeUtils.h"
ListPhysicalVolumeWidgetItem::ListPhysicalVolumeWidgetItem( const Partition* partition, bool checked )
- : QListWidgetItem(
- QString( "%1 | %2" ).arg( partition->deviceNode(), Capacity::formatByteSize( partition->capacity() ) ) )
+ : QListWidgetItem( QString( "%1 | %2" ).arg( partition->deviceNode(), formatByteSize( partition->capacity() ) ) )
, m_partition( partition )
{
setToolTip( partition->deviceNode() );
@@ -26,3 +25,5 @@ ListPhysicalVolumeWidgetItem::partition() const
{
return m_partition;
}
+
+ListPhysicalVolumeWidgetItem::~ListPhysicalVolumeWidgetItem() {}
diff --git a/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.h b/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.h
index 6e0b1c85a..5d7fdcb76 100644
--- a/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.h
+++ b/src/modules/partition/gui/ListPhysicalVolumeWidgetItem.h
@@ -18,6 +18,7 @@ class ListPhysicalVolumeWidgetItem : public QListWidgetItem
{
public:
ListPhysicalVolumeWidgetItem( const Partition* partition, bool checked );
+ ~ListPhysicalVolumeWidgetItem() override;
const Partition* partition() const;
diff --git a/src/modules/partition/gui/PartitionLabelsView.cpp b/src/modules/partition/gui/PartitionLabelsView.cpp
index 5803d59a2..e73d7f4af 100644
--- a/src/modules/partition/gui/PartitionLabelsView.cpp
+++ b/src/modules/partition/gui/PartitionLabelsView.cpp
@@ -12,6 +12,7 @@
#include "core/ColorUtils.h"
#include "core/PartitionModel.h"
+#include "core/SizeUtils.h"
#include "utils/CalamaresUtilsGui.h"
#include "utils/Logger.h"
@@ -20,8 +21,6 @@
#include
#include
-#include
-
// Qt
#include
#include
@@ -39,7 +38,7 @@ static QStringList
buildUnknownDisklabelTexts( Device* dev )
{
QStringList texts = { QObject::tr( "Unpartitioned space or unknown partition table" ),
- KFormat().formatByteSize( dev->totalLogical() * dev->logicalSize() ) };
+ formatByteSize( dev->totalLogical() * dev->logicalSize() ) };
return texts;
}
diff --git a/src/modules/partition/gui/ReplaceWidget.cpp b/src/modules/partition/gui/ReplaceWidget.cpp
index 94f527646..2e5675a11 100644
--- a/src/modules/partition/gui/ReplaceWidget.cpp
+++ b/src/modules/partition/gui/ReplaceWidget.cpp
@@ -212,7 +212,8 @@ ReplaceWidget::onPartitionSelected()
}
}
- if ( partition->capacity() < requiredSpaceB )
+ // The loss of precision is ok; we're not going to fall over from a single byte
+ if ( static_cast< double >( partition->capacity() ) < requiredSpaceB )
{
updateStatus( CalamaresUtils::Fail,
tr( "%4
"
diff --git a/src/modules/partition/gui/VolumeGroupBaseDialog.cpp b/src/modules/partition/gui/VolumeGroupBaseDialog.cpp
index 6277c30e5..48b44f68d 100644
--- a/src/modules/partition/gui/VolumeGroupBaseDialog.cpp
+++ b/src/modules/partition/gui/VolumeGroupBaseDialog.cpp
@@ -10,10 +10,9 @@
#include "VolumeGroupBaseDialog.h"
#include "ui_VolumeGroupBaseDialog.h"
+#include "core/SizeUtils.h"
#include "gui/ListPhysicalVolumeWidgetItem.h"
-#include
-
#include
#include
#include
@@ -100,7 +99,7 @@ VolumeGroupBaseDialog::setUsedSizeValue( qint64 usedSize )
{
m_usedSizeValue = usedSize;
- ui->usedSize->setText( Capacity::formatByteSize( m_usedSizeValue ) );
+ ui->usedSize->setText( formatByteSize( m_usedSizeValue ) );
}
void
@@ -121,7 +120,7 @@ VolumeGroupBaseDialog::updateTotalSize()
% ( ui->peSize->value() * Capacity::unitFactor( Capacity::Unit::Byte, Capacity::Unit::MiB ) );
}
- ui->totalSize->setText( Capacity::formatByteSize( m_totalSizeValue ) );
+ ui->totalSize->setText( formatByteSize( m_totalSizeValue ) );
updateTotalSectors();
}
diff --git a/src/modules/partition/jobs/ClearMountsJob.cpp b/src/modules/partition/jobs/ClearMountsJob.cpp
index 831a1e868..2f5a8bd36 100644
--- a/src/modules/partition/jobs/ClearMountsJob.cpp
+++ b/src/modules/partition/jobs/ClearMountsJob.cpp
@@ -243,14 +243,8 @@ public:
}
private:
-#if ( QT_VERSION < QT_VERSION_CHECK( 5, 15, 0 ) )
- // TODO: 3.3 remove because newer Qt does support constness
const char* m_message = nullptr;
QString m_path;
-#else
- const char* const m_message = nullptr;
- QString const m_path;
-#endif
};
STATICTEST inline QDebug&
diff --git a/src/modules/partition/jobs/CreatePartitionJob.cpp b/src/modules/partition/jobs/CreatePartitionJob.cpp
index 83ebc0509..fe7c6f350 100644
--- a/src/modules/partition/jobs/CreatePartitionJob.cpp
+++ b/src/modules/partition/jobs/CreatePartitionJob.cpp
@@ -11,7 +11,9 @@
#include "CreatePartitionJob.h"
+#include "core/KPMHelpers.h"
#include "core/PartitionInfo.h"
+
#include "partition/FileSystem.h"
#include "partition/PartitionQuery.h"
#include "utils/CalamaresUtilsSystem.h"
@@ -60,24 +62,24 @@ createZfs( Partition* partition, Device* device )
// Now we need to do some things that would normally be done by kpmcore
// First we get the device node from the output and set it as the partition path
- QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) );
- QRegularExpressionMatch rem = re.match( r.getOutput() );
-
QString deviceNode;
- if ( rem.hasMatch() )
{
- if ( partition->devicePath().back().isDigit() )
+ QRegularExpression re( QStringLiteral( "Created a new partition (\\d+)" ) );
+ QRegularExpressionMatch rem = re.match( r.getOutput() );
+
+ if ( rem.hasMatch() )
{
- deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 );
- }
- else
- {
- deviceNode = partition->devicePath() + rem.captured( 1 );
+ if ( partition->devicePath().back().isDigit() )
+ {
+ deviceNode = partition->devicePath() + QLatin1Char( 'p' ) + rem.captured( 1 );
+ }
+ else
+ {
+ deviceNode = partition->devicePath() + rem.captured( 1 );
+ }
}
+ partition->setPartitionPath( deviceNode );
}
-
- partition->setPartitionPath( deviceNode );
-
// If it is a gpt device, set the partition UUID
if ( device->partitionTable()->type() == PartitionTable::gpt && partition->uuid().isEmpty() )
{
@@ -273,17 +275,9 @@ CreatePartitionJob::exec()
return createZfs( m_partition, m_device );
}
- Report report( nullptr );
- NewOperation op( *m_device, m_partition );
- op.setStatus( Operation::StatusRunning );
-
- QString message = tr( "The installer failed to create partition on disk '%1'." ).arg( m_device->name() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute(
+ NewOperation( *m_device, m_partition ),
+ tr( "The installer failed to create partition on disk '%1'." ).arg( m_device->name() ) );
}
void
diff --git a/src/modules/partition/jobs/CreatePartitionTableJob.cpp b/src/modules/partition/jobs/CreatePartitionTableJob.cpp
index 118ec8823..3b9415d1a 100644
--- a/src/modules/partition/jobs/CreatePartitionTableJob.cpp
+++ b/src/modules/partition/jobs/CreatePartitionTableJob.cpp
@@ -12,9 +12,11 @@
#include "CreatePartitionTableJob.h"
#include "partition/PartitionIterator.h"
+#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
-// KPMcore
+#include "core/KPMHelpers.h"
+
#include
#include
#include
@@ -63,8 +65,6 @@ CreatePartitionTableJob::prettyStatusMessage() const
Calamares::JobResult
CreatePartitionTableJob::exec()
{
- Report report( nullptr );
- QString message = tr( "The installer failed to create a partition table on %1." ).arg( m_device->name() );
PartitionTable* table = m_device->partitionTable();
@@ -76,30 +76,16 @@ CreatePartitionTableJob::exec()
cDebug() << Logger::SubEntry << ( ( *it ) ? ( *it )->deviceNode() : QString( "" ) );
}
- QProcess lsblk;
- lsblk.setProgram( "lsblk" );
- lsblk.setProcessChannelMode( QProcess::MergedChannels );
- lsblk.start();
- lsblk.waitForFinished();
- cDebug() << Logger::SubEntry << "lsblk output:\n" << Logger::NoQuote << lsblk.readAllStandardOutput();
+ auto lsblkResult = CalamaresUtils::System::runCommand( { "lsblk" }, std::chrono::seconds( 30 ) );
+ cDebug() << Logger::SubEntry << "lsblk output:\n" << Logger::NoQuote << lsblkResult.getOutput();
- QProcess mount;
- mount.setProgram( "mount" ); // Debug output only, not mounting something
- mount.setProcessChannelMode( QProcess::MergedChannels );
- mount.start();
- mount.waitForFinished();
- cDebug() << Logger::SubEntry << "mount output:\n" << Logger::NoQuote << mount.readAllStandardOutput();
+ auto mountResult = CalamaresUtils::System::runCommand( { "mount" }, std::chrono::seconds( 30 ) );
+ cDebug() << Logger::SubEntry << "mount output:\n" << Logger::NoQuote << mountResult.getOutput();
}
- CreatePartitionTableOperation op( *m_device, table );
- op.setStatus( Operation::StatusRunning );
-
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute(
+ CreatePartitionTableOperation( *m_device, table ),
+ tr( "The installer failed to create a partition table on %1." ).arg( m_device->name() ) );
}
void
diff --git a/src/modules/partition/jobs/CreateVolumeGroupJob.cpp b/src/modules/partition/jobs/CreateVolumeGroupJob.cpp
index 36d79b7b7..683913b7f 100644
--- a/src/modules/partition/jobs/CreateVolumeGroupJob.cpp
+++ b/src/modules/partition/jobs/CreateVolumeGroupJob.cpp
@@ -9,7 +9,8 @@
#include "CreateVolumeGroupJob.h"
-// KPMcore
+#include "core/KPMHelpers.h"
+
#include
#include
#include
@@ -46,19 +47,8 @@ CreateVolumeGroupJob::prettyStatusMessage() const
Calamares::JobResult
CreateVolumeGroupJob::exec()
{
- Report report( nullptr );
-
- CreateVolumeGroupOperation op( m_vgName, m_pvList, m_peSize );
-
- op.setStatus( Operation::StatusRunning );
-
- QString message = tr( "The installer failed to create a volume group named '%1'." ).arg( m_vgName );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute( CreateVolumeGroupOperation( m_vgName, m_pvList, m_peSize ),
+ tr( "The installer failed to create a volume group named '%1'." ).arg( m_vgName ) );
}
void
diff --git a/src/modules/partition/jobs/DeactivateVolumeGroupJob.cpp b/src/modules/partition/jobs/DeactivateVolumeGroupJob.cpp
index 92086015d..65920a84e 100644
--- a/src/modules/partition/jobs/DeactivateVolumeGroupJob.cpp
+++ b/src/modules/partition/jobs/DeactivateVolumeGroupJob.cpp
@@ -9,6 +9,8 @@
#include "DeactivateVolumeGroupJob.h"
+#include "core/KPMHelpers.h"
+
#include
#include
#include
@@ -39,18 +41,12 @@ DeactivateVolumeGroupJob::prettyStatusMessage() const
Calamares::JobResult
DeactivateVolumeGroupJob::exec()
{
- Report report( nullptr );
-
DeactivateVolumeGroupOperation op( *m_device );
-
- op.setStatus( Operation::OperationStatus::StatusRunning );
-
- QString message = tr( "The installer failed to deactivate a volume group named %1." ).arg( m_device->name() );
- if ( op.execute( report ) )
+ auto r = KPMHelpers::execute(
+ op, tr( "The installer failed to deactivate a volume group named %1." ).arg( m_device->name() ) );
+ if ( r )
{
op.preview();
- return Calamares::JobResult::ok();
}
-
- return Calamares::JobResult::error( message, report.toText() );
+ return r;
}
diff --git a/src/modules/partition/jobs/DeletePartitionJob.cpp b/src/modules/partition/jobs/DeletePartitionJob.cpp
index a678b5e90..d61bb955e 100644
--- a/src/modules/partition/jobs/DeletePartitionJob.cpp
+++ b/src/modules/partition/jobs/DeletePartitionJob.cpp
@@ -10,9 +10,11 @@
*/
#include "DeletePartitionJob.h"
+
+#include "core/KPMHelpers.h"
+
#include "utils/CalamaresUtilsSystem.h"
-// KPMcore
#include
#include
#include
@@ -45,7 +47,7 @@ removePartition( Partition* partition )
auto r = CalamaresUtils::System::instance()->runCommand(
{ "sfdisk", "--delete", "--force", partition->devicePath(), QString::number( partition->number() ) },
std::chrono::seconds( 5 ) );
- if ( r.getExitCode() !=0 || r.getOutput().contains("failed") )
+ if ( r.getExitCode() != 0 || r.getOutput().contains( "failed" ) )
{
return Calamares::JobResult::error(
QCoreApplication::translate( DeletePartitionJob::staticMetaObject.className(), "Deletion Failed" ),
@@ -96,17 +98,8 @@ DeletePartitionJob::exec()
return removePartition( m_partition );
}
- Report report( nullptr );
- DeleteOperation op( *m_device, m_partition );
- op.setStatus( Operation::StatusRunning );
-
- QString message = tr( "The installer failed to delete partition %1." ).arg( m_partition->devicePath() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute( DeleteOperation( *m_device, m_partition ),
+ tr( "The installer failed to delete partition %1." ).arg( m_partition->devicePath() ) );
}
void
diff --git a/src/modules/partition/jobs/FormatPartitionJob.cpp b/src/modules/partition/jobs/FormatPartitionJob.cpp
index 4dadacf9c..1ccc6e617 100644
--- a/src/modules/partition/jobs/FormatPartitionJob.cpp
+++ b/src/modules/partition/jobs/FormatPartitionJob.cpp
@@ -11,6 +11,8 @@
#include "FormatPartitionJob.h"
+#include "core/KPMHelpers.h"
+
#include "partition/FileSystem.h"
#include "utils/Logger.h"
@@ -65,17 +67,7 @@ FormatPartitionJob::prettyStatusMessage() const
Calamares::JobResult
FormatPartitionJob::exec()
{
- Report report( nullptr ); // Root of the report tree, no parent
- CreateFileSystemOperation op( *m_device, *m_partition, m_partition->fileSystem().type() );
- op.setStatus( Operation::StatusRunning );
-
- QString message = tr( "The installer failed to format partition %1 on disk '%2'." )
- .arg( m_partition->partitionPath(), m_device->name() );
-
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute( CreateFileSystemOperation( *m_device, *m_partition, m_partition->fileSystem().type() ),
+ tr( "The installer failed to format partition %1 on disk '%2'." )
+ .arg( m_partition->partitionPath(), m_device->name() ) );
}
diff --git a/src/modules/partition/jobs/RemoveVolumeGroupJob.cpp b/src/modules/partition/jobs/RemoveVolumeGroupJob.cpp
index 3c4e7b036..227351064 100644
--- a/src/modules/partition/jobs/RemoveVolumeGroupJob.cpp
+++ b/src/modules/partition/jobs/RemoveVolumeGroupJob.cpp
@@ -9,6 +9,8 @@
#include "RemoveVolumeGroupJob.h"
+#include "core/KPMHelpers.h"
+
#include
#include
#include
@@ -39,17 +41,7 @@ RemoveVolumeGroupJob::prettyStatusMessage() const
Calamares::JobResult
RemoveVolumeGroupJob::exec()
{
- Report report( nullptr );
-
- RemoveVolumeGroupOperation op( *m_device );
-
- op.setStatus( Operation::OperationStatus::StatusRunning );
-
- QString message = tr( "The installer failed to remove a volume group named '%1'." ).arg( m_device->name() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute(
+ RemoveVolumeGroupOperation( *m_device ),
+ tr( "The installer failed to remove a volume group named '%1'." ).arg( m_device->name() ) );
}
diff --git a/src/modules/partition/jobs/ResizePartitionJob.cpp b/src/modules/partition/jobs/ResizePartitionJob.cpp
index 87b87c1c0..fe1ceca4c 100644
--- a/src/modules/partition/jobs/ResizePartitionJob.cpp
+++ b/src/modules/partition/jobs/ResizePartitionJob.cpp
@@ -11,9 +11,10 @@
#include "ResizePartitionJob.h"
+#include "core/KPMHelpers.h"
+
#include "utils/Units.h"
-// KPMcore
#include
#include
#include
@@ -66,23 +67,16 @@ ResizePartitionJob::prettyStatusMessage() const
Calamares::JobResult
ResizePartitionJob::exec()
{
- Report report( nullptr );
// Restore partition sectors that were modified for preview
m_partition->setFirstSector( m_oldFirstSector );
m_partition->setLastSector( m_oldLastSector );
+
ResizeOperation op( *m_device, *m_partition, m_newFirstSector, m_newLastSector );
- op.setStatus( Operation::StatusRunning );
connect( &op, &Operation::progress, this, &ResizePartitionJob::iprogress );
-
- QString errorMessage = tr( "The installer failed to resize partition %1 on disk '%2'." )
- .arg( m_partition->partitionPath() )
- .arg( m_device->name() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( errorMessage, report.toText() );
+ return KPMHelpers::execute( op,
+ tr( "The installer failed to resize partition %1 on disk '%2'." )
+ .arg( m_partition->partitionPath() )
+ .arg( m_device->name() ) );
}
void
diff --git a/src/modules/partition/jobs/ResizeVolumeGroupJob.cpp b/src/modules/partition/jobs/ResizeVolumeGroupJob.cpp
index 1aa4541b8..f7a21d80d 100644
--- a/src/modules/partition/jobs/ResizeVolumeGroupJob.cpp
+++ b/src/modules/partition/jobs/ResizeVolumeGroupJob.cpp
@@ -9,7 +9,8 @@
#include "ResizeVolumeGroupJob.h"
-// KPMcore
+#include "core/KPMHelpers.h"
+
#include
#include
#include
@@ -51,19 +52,9 @@ ResizeVolumeGroupJob::prettyStatusMessage() const
Calamares::JobResult
ResizeVolumeGroupJob::exec()
{
- Report report( nullptr );
-
- ResizeVolumeGroupOperation op( *m_device, m_partitionList );
-
- op.setStatus( Operation::OperationStatus::StatusRunning );
-
- QString message = tr( "The installer failed to resize a volume group named '%1'." ).arg( m_device->name() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( message, report.toText() );
+ return KPMHelpers::execute(
+ ResizeVolumeGroupOperation( *m_device, m_partitionList ),
+ tr( "The installer failed to resize a volume group named '%1'." ).arg( m_device->name() ) );
}
QString
diff --git a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp
index de77d86f8..507773288 100644
--- a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp
+++ b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp
@@ -13,6 +13,8 @@
#include "SetPartitionFlagsJob.h"
+#include "core/KPMHelpers.h"
+
#include "partition/FileSystem.h"
#include "utils/Logger.h"
#include "utils/Units.h"
@@ -148,17 +150,8 @@ SetPartFlagsJob::exec()
cDebug() << "Setting flags on" << m_device->deviceNode() << "partition" << partition()->deviceNode()
<< Logger::DebugList( flagsList );
- Report report( nullptr );
SetPartFlagsOperation op( *m_device, *partition(), m_flags );
- op.setStatus( Operation::StatusRunning );
connect( &op, &Operation::progress, this, &SetPartFlagsJob::iprogress );
-
- QString errorMessage
- = tr( "The installer failed to set flags on partition %1." ).arg( m_partition->partitionPath() );
- if ( op.execute( report ) )
- {
- return Calamares::JobResult::ok();
- }
-
- return Calamares::JobResult::error( errorMessage, report.toText() );
+ return KPMHelpers::execute(
+ op, tr( "The installer failed to set flags on partition %1." ).arg( m_partition->partitionPath() ) );
}
diff --git a/src/modules/partition/tests/CMakeLists.txt b/src/modules/partition/tests/CMakeLists.txt
index 2839270fb..da017d96f 100644
--- a/src/modules/partition/tests/CMakeLists.txt
+++ b/src/modules/partition/tests/CMakeLists.txt
@@ -61,6 +61,7 @@ calamares_add_test(
SOURCES
${PartitionModule_SOURCE_DIR}/jobs/AutoMountManagementJob.cpp
AutoMountTests.cpp
+ DEFINITIONS ${_partition_defs}
)
calamares_add_test(
@@ -70,4 +71,5 @@ calamares_add_test(
${PartitionModule_SOURCE_DIR}/core/DeviceList.cpp
LIBRARIES
kpmcore
+ DEFINITIONS ${_partition_defs}
)
diff --git a/src/modules/preservefiles/CMakeLists.txt b/src/modules/preservefiles/CMakeLists.txt
index 820c50a2b..5df637321 100644
--- a/src/modules/preservefiles/CMakeLists.txt
+++ b/src/modules/preservefiles/CMakeLists.txt
@@ -3,14 +3,20 @@
# SPDX-FileCopyrightText: 2020 Adriaan de Groot
# SPDX-License-Identifier: BSD-2-Clause
#
-include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
-
calamares_add_plugin( preservefiles
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
+ Item.cpp
PreserveFiles.cpp
# REQUIRES mount # To set the rootMountPoint
SHARED_LIB
EMERGENCY
)
+
+calamares_add_test(
+ preservefilestest
+ SOURCES
+ Item.cpp
+ Tests.cpp
+)
diff --git a/src/modules/preservefiles/Item.cpp b/src/modules/preservefiles/Item.cpp
new file mode 100644
index 000000000..2ae929e67
--- /dev/null
+++ b/src/modules/preservefiles/Item.cpp
@@ -0,0 +1,159 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2018, 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+
+#include "Item.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+#include "utils/Units.h"
+#include "utils/Variant.h"
+
+#include
+
+using namespace CalamaresUtils::Units;
+
+static bool
+copy_file( const QString& source, const QString& dest )
+{
+ QFile sourcef( source );
+ if ( !sourcef.open( QFile::ReadOnly ) )
+ {
+ cWarning() << "Could not read" << source;
+ return false;
+ }
+
+ QFile destf( dest );
+ if ( !destf.open( QFile::WriteOnly ) )
+ {
+ sourcef.close();
+ cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source;
+ return false;
+ }
+
+ QByteArray b;
+ do
+ {
+ b = sourcef.read( 1_MiB );
+ destf.write( b );
+ } while ( b.count() > 0 );
+
+ sourcef.close();
+ destf.close();
+
+ return true;
+}
+
+Item
+Item::fromVariant( const QVariant& v, const CalamaresUtils::Permissions& defaultPermissions )
+{
+ if ( v.type() == QVariant::String )
+ {
+ QString filename = v.toString();
+ if ( !filename.isEmpty() )
+ {
+ return { filename, filename, defaultPermissions, ItemType::Path, false };
+ }
+ else
+ {
+ cWarning() << "Empty filename for preservefiles, item" << v;
+ return {};
+ }
+ }
+ else if ( v.type() == QVariant::Map )
+ {
+ const auto map = v.toMap();
+
+ CalamaresUtils::Permissions perm( defaultPermissions );
+ ItemType t = ItemType::None;
+ bool optional = CalamaresUtils::getBool( map, "optional", false );
+
+ {
+ QString perm_string = map[ "perm" ].toString();
+ if ( !perm_string.isEmpty() )
+ {
+ perm = CalamaresUtils::Permissions( perm_string );
+ }
+ }
+
+ {
+ QString from = map[ "from" ].toString();
+ t = ( from == "log" ) ? ItemType::Log : ( from == "config" ) ? ItemType::Config : ItemType::None;
+
+ if ( t == ItemType::None && !map[ "src" ].toString().isEmpty() )
+ {
+ t = ItemType::Path;
+ }
+ }
+
+ QString dest = map[ "dest" ].toString();
+ if ( dest.isEmpty() )
+ {
+ cWarning() << "Empty dest for preservefiles, item" << v;
+ return {};
+ }
+
+ switch ( t )
+ {
+ case ItemType::Config:
+ return { QString(), dest, perm, t, optional };
+ case ItemType::Log:
+ return { QString(), dest, perm, t, optional };
+ case ItemType::Path:
+ return { map[ "src" ].toString(), dest, perm, t, optional };
+ case ItemType::None:
+ cWarning() << "Invalid type for preservefiles, item" << v;
+ return {};
+ }
+ }
+ cWarning() << "Invalid type for preservefiles, item" << v;
+ return {};
+}
+
+
+bool
+Item::exec( const std::function< QString( QString ) >& replacements ) const
+{
+ QString expanded_dest = replacements( dest );
+ QString full_dest = CalamaresUtils::System::instance()->targetPath( expanded_dest );
+
+ bool success = false;
+ switch ( m_type )
+ {
+ case ItemType::None:
+ cWarning() << "Invalid item for preservefiles skipped.";
+ return false;
+ case ItemType::Config:
+ if ( !( success = Calamares::JobQueue::instance()->globalStorage()->saveJson( full_dest ) ) )
+ {
+ cWarning() << "Could not write a JSON dump of global storage to" << full_dest;
+ }
+ break;
+ case ItemType::Log:
+ if ( !( success = copy_file( Logger::logFile(), full_dest ) ) )
+ {
+ cWarning() << "Could not preserve log file to" << full_dest;
+ }
+ break;
+ case ItemType::Path:
+ if ( !( success = copy_file( source, full_dest ) ) )
+ {
+ cWarning() << "Could not preserve" << source << "to" << full_dest;
+ }
+ break;
+ }
+ if ( !success )
+ {
+ CalamaresUtils::System::instance()->removeTargetFile( expanded_dest );
+ return false;
+ }
+ else
+ {
+ return perm.apply( full_dest );
+ }
+}
diff --git a/src/modules/preservefiles/Item.h b/src/modules/preservefiles/Item.h
new file mode 100644
index 000000000..896b9471f
--- /dev/null
+++ b/src/modules/preservefiles/Item.h
@@ -0,0 +1,76 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2018, 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ */
+#ifndef PRESERVEFILES_ITEM_H
+#define PRESERVEFILES_ITEM_H
+
+#include "utils/Permissions.h"
+
+#include
+#include
+
+#include
+
+enum class ItemType
+{
+ None,
+ Path,
+ Log,
+ Config
+};
+
+/** @brief Represents one item to copy
+ *
+ * All item types need a destination (to place the data), this is
+ * intepreted within the target system. All items need a permission,
+ * which is applied to the data once written.
+ *
+ * The source may be a path, but not all types need a source.
+ */
+class Item
+{
+ QString source;
+ QString dest;
+ CalamaresUtils::Permissions perm;
+ ItemType m_type = ItemType::None;
+ bool m_optional = false;
+
+public:
+ Item( const QString& src, const QString& d, CalamaresUtils::Permissions p, ItemType t, bool optional )
+ : source( src )
+ , dest( d )
+ , perm( std::move( p ) )
+ , m_type( t )
+ , m_optional( optional )
+ {
+ }
+
+ Item()
+ : m_type( ItemType::None )
+ {
+ }
+
+ operator bool() const { return m_type != ItemType::None; }
+ ItemType type() const { return m_type; }
+ bool isOptional() const { return m_optional; }
+
+ bool exec( const std::function< QString( QString ) >& replacements ) const;
+
+
+ /** @brief Create an Item -- or one of its subclasses -- from @p v
+ *
+ * Depending on the structure and contents of @p v, a pointer
+ * to an Item is returned. If @p v cannot be interpreted meaningfully,
+ * then a nullptr is returned.
+ *
+ * When the entry contains a *perm* key, use that permission, otherwise
+ * apply @p defaultPermissions to the item.
+ */
+ static Item fromVariant( const QVariant& v, const CalamaresUtils::Permissions& defaultPermissions );
+};
+
+
+#endif
diff --git a/src/modules/preservefiles/PreserveFiles.cpp b/src/modules/preservefiles/PreserveFiles.cpp
index b235aac93..f904ded8c 100644
--- a/src/modules/preservefiles/PreserveFiles.cpp
+++ b/src/modules/preservefiles/PreserveFiles.cpp
@@ -7,46 +7,20 @@
#include "PreserveFiles.h"
+#include "Item.h"
+
#include "CalamaresVersion.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/CommandList.h"
#include "utils/Logger.h"
-#include "utils/Permissions.h"
#include "utils/Units.h"
#include
using namespace CalamaresUtils::Units;
-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 QLatin1String( "/" );
-}
-
QString
atReplacements( QString s )
{
@@ -79,95 +53,34 @@ PreserveFiles::prettyName() const
return tr( "Saving files for later ..." );
}
-static bool
-copy_file( const QString& source, const QString& dest )
-{
- QFile sourcef( source );
- if ( !sourcef.open( QFile::ReadOnly ) )
- {
- cWarning() << "Could not read" << source;
- return false;
- }
-
- QFile destf( dest );
- if ( !destf.open( QFile::WriteOnly ) )
- {
- sourcef.close();
- cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source;
- return false;
- }
-
- QByteArray b;
- do
- {
- b = sourcef.read( 1_MiB );
- destf.write( b );
- } while ( b.count() > 0 );
-
- sourcef.close();
- destf.close();
-
- return true;
-}
-
Calamares::JobResult
PreserveFiles::exec()
{
- if ( m_items.isEmpty() )
+ if ( m_items.empty() )
{
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 )
+ for ( const auto& it : qAsConst( m_items ) )
{
- QString source = it.source;
- QString bare_dest = atReplacements( it.dest );
- QString dest = prefix + bare_dest;
-
- if ( it.type == ItemType::Log )
+ if ( !it )
{
- source = Logger::logFile();
+ // Invalid entries are nullptr, ignore them but count as a success
+ // because they shouldn't block the installation. There are
+ // warnings in the log showing what the configuration problem is.
+ ++count;
+ continue;
}
- if ( it.type == ItemType::Config )
+ // Try to preserve the file. If it's marked as optional, count it
+ // as a success regardless.
+ if ( it.exec( atReplacements ) || it.isOptional() )
{
- if ( !Calamares::JobQueue::instance()->globalStorage()->saveJson( dest ) )
- {
- cWarning() << "Could not write a JSON dump of global storage to" << dest;
- }
- else
- {
- ++count;
- }
- }
- else if ( source.isEmpty() )
- {
- cWarning() << "Skipping unnamed source file for" << dest;
- }
- else
- {
- if ( copy_file( source, dest ) )
- {
- if ( it.perm.isValid() )
- {
- if ( !it.perm.apply( CalamaresUtils::System::instance()->targetPath( bare_dest ) ) )
- {
- cWarning() << "Could not set attributes of" << bare_dest;
- }
- }
-
- ++count;
- }
+ ++count;
}
}
- return count == m_items.count()
+ return count == m_items.size()
? Calamares::JobResult::ok()
: Calamares::JobResult::error( tr( "Not all of the configured files could be preserved." ) );
}
@@ -193,53 +106,11 @@ PreserveFiles::setConfigurationMap( const QVariantMap& configurationMap )
{
defaultPermissions = QStringLiteral( "root:root:0400" );
}
+ CalamaresUtils::Permissions perm( defaultPermissions );
- QVariantList l = files.toList();
- unsigned int c = 0;
- for ( const auto& li : l )
+ for ( const auto& li : files.toList() )
{
- if ( li.type() == QVariant::String )
- {
- QString filename = li.toString();
- if ( !filename.isEmpty() )
- m_items.append(
- Item { filename, filename, CalamaresUtils::Permissions( defaultPermissions ), 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;
- QString perm = map[ "perm" ].toString();
- if ( perm.isEmpty() )
- {
- perm = defaultPermissions;
- }
-
- 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, CalamaresUtils::Permissions( perm ), t } );
- }
- }
- else
- {
- cDebug() << "Invalid type for preservefiles, item" << c;
- }
-
- ++c;
+ m_items.push_back( Item::fromVariant( li, perm ) );
}
}
diff --git a/src/modules/preservefiles/PreserveFiles.h b/src/modules/preservefiles/PreserveFiles.h
index 7a0aab34d..dfd2804e3 100644
--- a/src/modules/preservefiles/PreserveFiles.h
+++ b/src/modules/preservefiles/PreserveFiles.h
@@ -10,33 +10,14 @@
#include "CppJob.h"
#include "DllMacro.h"
-#include "utils/Permissions.h"
#include "utils/PluginFactory.h"
-#include
-#include
-#include
+class Item;
class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob
{
Q_OBJECT
- enum class ItemType
- {
- None,
- Path,
- Log,
- Config
- };
-
- struct Item
- {
- QString source;
- QString dest;
- CalamaresUtils::Permissions perm;
- ItemType type;
- };
-
using ItemList = QList< Item >;
public:
diff --git a/src/modules/preservefiles/Tests.cpp b/src/modules/preservefiles/Tests.cpp
new file mode 100644
index 000000000..57cefcf9d
--- /dev/null
+++ b/src/modules/preservefiles/Tests.cpp
@@ -0,0 +1,93 @@
+/* === This file is part of Calamares - ===
+ *
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares is Free Software: see the License-Identifier above.
+ *
+ */
+
+#include "Item.h"
+
+#include "Settings.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+#include "utils/NamedEnum.h"
+#include "utils/Yaml.h"
+
+#include
+
+class PreserveFilesTests : public QObject
+{
+ Q_OBJECT
+public:
+ PreserveFilesTests();
+ ~PreserveFilesTests() override {}
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void testItems_data();
+ void testItems();
+};
+
+PreserveFilesTests::PreserveFilesTests() {}
+
+void
+PreserveFilesTests::initTestCase()
+{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
+ cDebug() << "PreserveFiles test started.";
+
+ // Ensure we have a system object, expect it to be a "bogus" one
+ CalamaresUtils::System* system = CalamaresUtils::System::instance();
+ QVERIFY( system );
+ cDebug() << Logger::SubEntry << "System @" << Logger::Pointer( system );
+
+ const auto* settings = Calamares::Settings::instance();
+ if ( !settings )
+ {
+ (void)new Calamares::Settings( true );
+ }
+}
+
+void
+PreserveFilesTests::testItems_data()
+{
+ QTest::addColumn< QString >( "filename" );
+ QTest::addColumn< bool >( "ok" );
+ QTest::addColumn< int >( "type_i" );
+
+ QTest::newRow( "log " ) << QString( "1a-log.conf" ) << true << smash( ItemType::Log );
+ QTest::newRow( "config " ) << QString( "1b-config.conf" ) << true << smash( ItemType::Config );
+ QTest::newRow( "src " ) << QString( "1c-src.conf" ) << true << smash( ItemType::Path );
+ QTest::newRow( "filename" ) << QString( "1d-filename.conf" ) << true << smash( ItemType::Path );
+ QTest::newRow( "empty " ) << QString( "1e-empty.conf" ) << false << smash( ItemType::None );
+ QTest::newRow( "bad " ) << QString( "1f-bad.conf" ) << false << smash( ItemType::None );
+}
+
+void
+PreserveFilesTests::testItems()
+{
+ QFETCH( QString, filename );
+ QFETCH( bool, ok );
+ QFETCH( int, type_i );
+
+ QFile fi( QString( "%1/tests/%2" ).arg( BUILD_AS_TEST, filename ) );
+ QVERIFY( fi.exists() );
+
+ bool config_file_ok = false;
+ const auto map = CalamaresUtils::loadYaml( fi, &config_file_ok );
+ QVERIFY( config_file_ok );
+
+ CalamaresUtils::Permissions perm( QStringLiteral( "adridg:adridg:0750" ) );
+ auto i = Item::fromVariant( map[ "item" ], perm );
+ QCOMPARE( bool( i ), ok );
+ QCOMPARE( smash( i.type() ), type_i );
+}
+
+QTEST_GUILESS_MAIN( PreserveFilesTests )
+
+#include "utils/moc-warnings.h"
+
+#include "Tests.moc"
diff --git a/src/modules/preservefiles/preservefiles.conf b/src/modules/preservefiles/preservefiles.conf
index 6af0872d7..4fb393b2e 100644
--- a/src/modules/preservefiles/preservefiles.conf
+++ b/src/modules/preservefiles/preservefiles.conf
@@ -7,42 +7,58 @@
# 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).
+# as the same path within the target system (chroot). If, globally,
+# *dontChroot* is true, then these items will be 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 three possible other keys in the map:
+# target system (if the global *dontChroot* is true, then the host is the
+# target as well). Relative paths are not recommended. There are two
+# ways to select the source data for the file:
# - *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.
+# Exactly one of the two source keys (either *from* or *src*) must be set.
+#
+# Special values for the key *from* are:
+# - *log*, for the complete log file (up to the moment the preservefiles
+# module is run),
+# - *config*, for a JSON dump of the contents of global storage.
+# Note that this may contain sensitive information, and should be
+# given restrictive permissions.
+#
+# A map with a *dest* key can have these additional fields:
# - *perm*, is a colon-separated tuple of ::
# where is in octal (e.g. 4777 for wide-open, 0400 for read-only
# by owner). If set, the file's ownership and permissions are set to
# those values within the target system; if not set, no permissions
# are changed.
-# Only one of the two source keys (either *from* or *src*) may be set.
+# - *optional*, is a boolean; if this is set to `true` then failure to
+# preserve the file will **not** be counted as a failure of the
+# module, and installation will proceed. Set this for files that might
+# not exist in the host system (e.g. nvidia configuration files that
+# are created in some boot scenarios and not in others).
#
-# The target filename is modified as follows:
-# - `@@ROOT@@` is replaced by the path to the target root (may be /)
+# The target path (*dest*) is modified as follows:
+# - `@@ROOT@@` is replaced by the path to the target root (may be /).
+# There is never any reason to use this, since the *dest* is already
+# interpreted in the target system.
# - `@@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 a JSON dump of the contents of global storage
----
+#
+#
files:
- - /etc/oem-information
- from: log
- dest: /root/install.log
- perm: root:wheel:644
+ dest: /var/log/Calamares.log
+ perm: root:wheel:600
- from: config
- dest: /root/install.json
- perm: root:wheel:400
+ dest: /var/log/Calamares-install.json
+ perm: root:wheel:600
+# - src: /var/log/nvidia.conf
+# dest: /var/log/Calamares-nvidia.conf
+# optional: true
# The *perm* key contains a default value to apply to all files listed
# above that do not have a *perm* key of their own. If not set,
diff --git a/src/modules/preservefiles/preservefiles.schema.yaml b/src/modules/preservefiles/preservefiles.schema.yaml
new file mode 100644
index 000000000..65067ea97
--- /dev/null
+++ b/src/modules/preservefiles/preservefiles.schema.yaml
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: 2020 Adriaan de Groot
+# SPDX-License-Identifier: GPL-3.0-or-later
+---
+$schema: https://json-schema.org/schema#
+$id: https://calamares.io/schemas/preservefiles
+additionalProperties: false
+type: object
+properties:
+ # TODO: it's a particularly-formatted string
+ perm: { type: string }
+ files:
+ type: array
+ items:
+ # There are three entries here because: string, or an entry with
+ # a src (but no from) or an entry with from (but no src).
+ anyOf:
+ - type: string
+ - type: object
+ properties:
+ dest: { type: string }
+ src: { type: string }
+ # TODO: it's a particularly-formatted string
+ perm: { type: string }
+ optional: { type: boolean }
+ required: [ dest ]
+ additionalProperties: false
+ - type: object
+ properties:
+ dest: { type: string }
+ from: { type: string, enum: [config, log] }
+ # TODO: it's a particularly-formatted string
+ perm: { type: string }
+ optional: { type: boolean }
+ required: [ dest ]
+ additionalProperties: false
+
+required: [ files ]
diff --git a/src/modules/preservefiles/tests/1a-log.conf b/src/modules/preservefiles/tests/1a-log.conf
new file mode 100644
index 000000000..d589d4dfb
--- /dev/null
+++ b/src/modules/preservefiles/tests/1a-log.conf
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+#
+item:
+ from: log
+ dest: /var/log/Calamares.log
+ perm: root:wheel:601
diff --git a/src/modules/preservefiles/tests/1b-config.conf b/src/modules/preservefiles/tests/1b-config.conf
new file mode 100644
index 000000000..409dc89d9
--- /dev/null
+++ b/src/modules/preservefiles/tests/1b-config.conf
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+item:
+ from: config
+ dest: /var/log/Calamares-install.json
+ perm: root:wheel:600
diff --git a/src/modules/preservefiles/tests/1c-src.conf b/src/modules/preservefiles/tests/1c-src.conf
new file mode 100644
index 000000000..130ddd06f
--- /dev/null
+++ b/src/modules/preservefiles/tests/1c-src.conf
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+item:
+ src: /root/.cache/calamares/session.log
+ dest: /var/log/Calamares.log
+ perm: root:wheel:600
diff --git a/src/modules/preservefiles/tests/1d-filename.conf b/src/modules/preservefiles/tests/1d-filename.conf
new file mode 100644
index 000000000..130ddd06f
--- /dev/null
+++ b/src/modules/preservefiles/tests/1d-filename.conf
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+item:
+ src: /root/.cache/calamares/session.log
+ dest: /var/log/Calamares.log
+ perm: root:wheel:600
diff --git a/src/modules/preservefiles/tests/1e-empty.conf b/src/modules/preservefiles/tests/1e-empty.conf
new file mode 100644
index 000000000..183d4e456
--- /dev/null
+++ b/src/modules/preservefiles/tests/1e-empty.conf
@@ -0,0 +1,3 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+item: []
diff --git a/src/modules/preservefiles/tests/1f-bad.conf b/src/modules/preservefiles/tests/1f-bad.conf
new file mode 100644
index 000000000..b2c008955
--- /dev/null
+++ b/src/modules/preservefiles/tests/1f-bad.conf
@@ -0,0 +1,4 @@
+# SPDX-FileCopyrightText: no
+# SPDX-License-Identifier: CC0-1.0
+item:
+ bop: 1
diff --git a/src/modules/tracking/TrackingJobs.cpp b/src/modules/tracking/TrackingJobs.cpp
index 5e3cd12b5..7430bd57b 100644
--- a/src/modules/tracking/TrackingJobs.cpp
+++ b/src/modules/tracking/TrackingJobs.cpp
@@ -19,6 +19,8 @@
#include
+#include
+
#include
@@ -37,7 +39,6 @@ namespace
*/
class TrackingInstallJob : public Calamares::Job
{
- Q_OBJECT
public:
TrackingInstallJob( const QString& url );
~TrackingInstallJob() override;
@@ -58,7 +59,6 @@ private:
*/
class TrackingMachineUpdateManagerJob : public Calamares::Job
{
- Q_OBJECT
public:
~TrackingMachineUpdateManagerJob() override;
@@ -75,7 +75,6 @@ public:
*/
class TrackingKUserFeedbackJob : public Calamares::Job
{
- Q_OBJECT
public:
TrackingKUserFeedbackJob( const QString& username, const QStringList& areas );
~TrackingKUserFeedbackJob() override;
@@ -99,13 +98,13 @@ TrackingInstallJob::~TrackingInstallJob() {}
QString
TrackingInstallJob::prettyName() const
{
- return tr( "Installation feedback" );
+ return QCoreApplication::translate( "TrackingInstallJob", "Installation feedback" );
}
QString
TrackingInstallJob::prettyStatusMessage() const
{
- return tr( "Sending installation feedback." );
+ return QCoreApplication::translate( "TrackingInstallJob", "Sending installation feedback." );
}
Calamares::JobResult
@@ -122,8 +121,9 @@ TrackingInstallJob::exec()
if ( result.status == RequestStatus::Timeout )
{
cWarning() << "install-tracking request timed out.";
- return Calamares::JobResult::error( tr( "Internal error in install-tracking." ),
- tr( "HTTP request timed out." ) );
+ return Calamares::JobResult::error(
+ QCoreApplication::translate( "TrackingInstallJob", "Internal error in install-tracking." ),
+ QCoreApplication::translate( "TrackingInstallJob", "HTTP request timed out." ) );
}
return Calamares::JobResult::ok();
}
@@ -133,13 +133,13 @@ TrackingMachineUpdateManagerJob::~TrackingMachineUpdateManagerJob() {}
QString
TrackingMachineUpdateManagerJob::prettyName() const
{
- return tr( "Machine feedback" );
+ return QCoreApplication::translate( "TrackingMachineUpdateManagerJob", "Machine feedback" );
}
QString
TrackingMachineUpdateManagerJob::prettyStatusMessage() const
{
- return tr( "Configuring machine feedback." );
+ return QCoreApplication::translate( "TrackingMachineUpdateManagerJob", "Configuring machine feedback." );
}
Calamares::JobResult
@@ -162,14 +162,20 @@ TrackingMachineUpdateManagerJob::exec()
else if ( r > 0 )
{
return Calamares::JobResult::error(
- tr( "Error in machine feedback configuration." ),
- tr( "Could not configure machine feedback correctly, script error %1." ).arg( r ) );
+ QCoreApplication::translate( "TrackingMachineUpdateManagerJob",
+ "Error in machine feedback configuration." ),
+ QCoreApplication::translate( "TrackingMachineUpdateManagerJob",
+ "Could not configure machine feedback correctly, script error %1." )
+ .arg( r ) );
}
else
{
return Calamares::JobResult::error(
- tr( "Error in machine feedback configuration." ),
- tr( "Could not configure machine feedback correctly, Calamares error %1." ).arg( r ) );
+ QCoreApplication::translate( "TrackingMachineUpdateManagerJob",
+ "Error in machine feedback configuration." ),
+ QCoreApplication::translate( "TrackingMachineUpdateManagerJob",
+ "Could not configure machine feedback correctly, Calamares error %1." )
+ .arg( r ) );
}
}
@@ -184,13 +190,13 @@ TrackingKUserFeedbackJob::~TrackingKUserFeedbackJob() {}
QString
TrackingKUserFeedbackJob::prettyName() const
{
- return tr( "KDE user feedback" );
+ return QCoreApplication::translate( "TrackingKUserFeedbackJob", "KDE user feedback" );
}
QString
TrackingKUserFeedbackJob::prettyStatusMessage() const
{
- return tr( "Configuring KDE user feedback." );
+ return QCoreApplication::translate( "TrackingKUserFeedbackJob", "Configuring KDE user feedback." );
}
Calamares::JobResult
@@ -212,21 +218,25 @@ FeedbackLevel=16
if ( r > 0 )
{
return Calamares::JobResult::error(
- tr( "Error in KDE user feedback configuration." ),
- tr( "Could not configure KDE user feedback correctly, script error %1." ).arg( r ) );
+ QCoreApplication::translate( "TrackingKUserFeedbackJob", "Error in KDE user feedback configuration." ),
+ QCoreApplication::translate( "TrackingKUserFeedbackJob",
+ "Could not configure KDE user feedback correctly, script error %1." )
+ .arg( r ) );
}
else if ( r < 0 )
{
return Calamares::JobResult::error(
- tr( "Error in KDE user feedback configuration." ),
- tr( "Could not configure KDE user feedback correctly, Calamares error %1." ).arg( r ) );
+ QCoreApplication::translate( "TrackingKUserFeedbackJob", "Error in KDE user feedback configuration." ),
+ QCoreApplication::translate( "TrackingKUserFeedbackJob",
+ "Could not configure KDE user feedback correctly, Calamares error %1." )
+ .arg( r ) );
}
}
return Calamares::JobResult::ok();
}
-} // namespace
+} // namespace
void
addJob( Calamares::JobList& list, InstallTrackingConfig* config )
@@ -290,7 +300,3 @@ addJob( Calamares::JobList& list, UserTrackingConfig* config )
}
}
}
-
-#include "utils/moc-warnings.h"
-
-#include "TrackingJobs.moc"
diff --git a/src/modules/umount/main.py b/src/modules/umount/main.py
index 77ea91e34..5fdb36014 100644
--- a/src/modules/umount/main.py
+++ b/src/modules/umount/main.py
@@ -80,6 +80,7 @@ def run():
if(libcalamares.job.configuration and
"srcLog" in libcalamares.job.configuration and
"destLog" in libcalamares.job.configuration):
+ libcalamares.utils.warning("Log-file preserving is **deprecated** in the *umount* module")
log_source = libcalamares.job.configuration["srcLog"]
log_destination = libcalamares.job.configuration["destLog"]
# Relocate log_destination into target system
diff --git a/src/modules/umount/umount.conf b/src/modules/umount/umount.conf
index b6d86e353..7c11f4db6 100644
--- a/src/modules/umount/umount.conf
+++ b/src/modules/umount/umount.conf
@@ -10,16 +10,21 @@
# The "copy log files" functionality is deprecated; use the *preservefiles*
# module instead, which is more flexible.
#
-# This module has two configuration keys:
-# srcLog location in the live system where the log is
-# destLog location in the target system to copy the log
#
---
-# example when using the normal Calamares log:
-srcLog: "/root/.cache/calamares/session.log"
-destLog: "/var/log/Calamares.log"
-
+# This is a **deprecated** example. Use the *preservefiles* module
+# instead, where the equivalent configuration is this:
+#
+# files:
+# - from: log
+# dest: /var/log/installation.log
+#
+# Note that the "equivalent configuration" always finds the log,
+# and is not dependent on specific user names or the vagaries of
+# polkit configuration -- so it is a **better** "equivalent".
+#
# example when using a log created by `sudo calamares -d`:
#srcLog: "/home/live/installation.log"
#destLog: "/var/log/installation.log"
+srcLog: "/bogus/just/do/not/use/this/anymore.txt"