to use."
+msgstr ""
+
+#: src/modules/initramfscfg/main.py:90 src/modules/fstab/main.py:362
+#: src/modules/networkcfg/main.py:106 src/modules/initcpiocfg/main.py:232
+#: src/modules/localecfg/main.py:136 src/modules/openrcdmcryptcfg/main.py:77
+#: src/modules/luksopenswaphookcfg/main.py:91
+msgid "No root mount point is given for
{!s}
to use."
+msgstr ""
+
+#: src/modules/grubcfg/main.py:28
+msgid "Configure GRUB."
+msgstr ""
+
+#: src/modules/bootloader/main.py:43
+msgid "Install bootloader."
+msgstr ""
+
+#: src/modules/bootloader/main.py:508
+msgid "Bootloader installation error"
+msgstr ""
+
+#: src/modules/bootloader/main.py:509
+msgid ""
+"The bootloader could not be installed. The installation command "
+"
{!s}
returned error code {!s}."
+msgstr ""
+
+#: src/modules/fstab/main.py:29
+msgid "Writing fstab."
+msgstr ""
+
+#: src/modules/fstab/main.py:389
+msgid "No
{!s}
configuration is given for
{!s}
to use."
+msgstr ""
+
+#: src/modules/dracut/main.py:27
+msgid "Creating initramfs with dracut."
+msgstr ""
+
+#: src/modules/dracut/main.py:49
+msgid "Failed to run dracut on the target"
+msgstr ""
+
+#: src/modules/dracut/main.py:50 src/modules/mkinitfs/main.py:50
+msgid "The exit code was {}"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:526
+msgid "Cannot write KDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:527
+msgid "KDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:588
+msgid "Cannot write LXDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:589
+msgid "LXDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:672
+msgid "Cannot write LightDM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:673
+msgid "LightDM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:747
+msgid "Cannot configure LightDM"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:748
+msgid "No LightDM greeter installed."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:779
+msgid "Cannot write SLIM configuration file"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:780
+msgid "SLIM config file {!s} does not exist"
+msgstr ""
+
+#: src/modules/displaymanager/main.py:906
+msgid "No display managers selected for the displaymanager module."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:907
+msgid ""
+"The displaymanagers list is empty or undefined in both globalstorage and "
+"displaymanager.conf."
+msgstr ""
+
+#: src/modules/displaymanager/main.py:989
+msgid "Display manager configuration was incomplete"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:29
+msgid "Configure OpenRC services"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:57
+msgid "Cannot add service {name!s} to run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:59
+msgid "Cannot remove service {name!s} from run-level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:61
+msgid ""
+"Unknown service-action {arg!s} for service {name!s} in run-"
+"level {level!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:93
+#: src/modules/services-systemd/main.py:59
+msgid "Cannot modify service"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:94
+msgid ""
+"rc-update {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:101
+msgid "Target runlevel does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:102
+msgid ""
+"The path for runlevel {level!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/services-openrc/main.py:110
+msgid "Target service does not exist"
+msgstr ""
+
+#: src/modules/services-openrc/main.py:111
+msgid ""
+"The path for service {name!s} is {path!s}, which does not "
+"exist."
+msgstr ""
+
+#: src/modules/networkcfg/main.py:29
+msgid "Saving network configuration."
+msgstr ""
+
+#: src/modules/packages/main.py:50 src/modules/packages/main.py:59
+#: src/modules/packages/main.py:69
+msgid "Install packages."
+msgstr ""
+
+#: src/modules/packages/main.py:57
+#, python-format
+msgid "Processing packages (%(count)d / %(total)d)"
+msgstr ""
+
+#: src/modules/packages/main.py:62
+#, python-format
+msgid "Installing one package."
+msgid_plural "Installing %(num)d packages."
+msgstr[0] ""
+
+#: src/modules/packages/main.py:65
+#, python-format
+msgid "Removing one package."
+msgid_plural "Removing %(num)d packages."
+msgstr[0] ""
+
+#: src/modules/packages/main.py:638 src/modules/packages/main.py:650
+#: src/modules/packages/main.py:678
+msgid "Package Manager error"
+msgstr ""
+
+#: src/modules/packages/main.py:639
+msgid ""
+"The package manager could not prepare updates. The command
{!s}
"
+"returned error code {!s}."
+msgstr ""
+
+#: src/modules/packages/main.py:651
+msgid ""
+"The package manager could not update the system. The command
{!s}
"
+" returned error code {!s}."
+msgstr ""
+
+#: src/modules/packages/main.py:679
+msgid ""
+"The package manager could not make changes to the installed system. The "
+"command
{!s}
returned error code {!s}."
+msgstr ""
+
+#: src/modules/plymouthcfg/main.py:27
+msgid "Configure Plymouth theme"
+msgstr ""
+
+#: src/modules/initcpiocfg/main.py:28
+msgid "Configuring mkinitcpio."
+msgstr ""
+
+#: src/modules/localecfg/main.py:30
+msgid "Configuring locales."
+msgstr ""
+
+#: src/modules/mount/main.py:30
+msgid "Mounting partitions."
+msgstr ""
+
+#: src/modules/rawfs/main.py:26
+msgid "Installing data."
+msgstr ""
+
+#: src/modules/dummypython/main.py:35
+msgid "Dummy python job."
+msgstr ""
+
+#: src/modules/dummypython/main.py:37 src/modules/dummypython/main.py:93
+#: src/modules/dummypython/main.py:94
+msgid "Dummy python step {}"
+msgstr ""
+
+#: src/modules/hwclock/main.py:26
+msgid "Setting hardware clock."
+msgstr ""
+
+#: src/modules/umount/main.py:31
+msgid "Unmount file systems."
+msgstr ""
+
+#: src/modules/openrcdmcryptcfg/main.py:26
+msgid "Configuring OpenRC dmcrypt service."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:26
+msgid "Configure systemd services"
+msgstr ""
+
+#: src/modules/services-systemd/main.py:60
+msgid ""
+"systemctl {arg!s} call in chroot returned error code {num!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:63
+#: src/modules/services-systemd/main.py:69
+msgid "Cannot enable systemd service {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:65
+msgid "Cannot enable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:67
+msgid "Cannot enable systemd timer {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:71
+msgid "Cannot disable systemd target {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:73
+msgid "Cannot mask systemd unit {name!s}."
+msgstr ""
+
+#: src/modules/services-systemd/main.py:75
+msgid ""
+"Unknown systemd commands {command!s} and "
+"{suffix!s} for unit {name!s}."
+msgstr ""
+
+#: src/modules/mkinitfs/main.py:27
+msgid "Creating initramfs with mkinitfs."
+msgstr ""
+
+#: src/modules/mkinitfs/main.py:49
+msgid "Failed to run mkinitfs on the target"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:34
+msgid "Filling up filesystems."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:254
+msgid "rsync failed with error code {}."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:299
+msgid "Unpacking image {}/{}, file {}/{}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:314
+msgid "Starting to unpack {}"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:323 src/modules/unpackfs/main.py:465
+msgid "Failed to unpack image \"{}\""
+msgstr ""
+
+#: src/modules/unpackfs/main.py:430
+msgid "No mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:431
+msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:436
+msgid "Bad mount point for root partition"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:437
+msgid "rootMountPoint is \"{}\", which does not exist, doing nothing"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:453 src/modules/unpackfs/main.py:457
+#: src/modules/unpackfs/main.py:463 src/modules/unpackfs/main.py:478
+msgid "Bad unsquash configuration"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:454
+msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:458
+msgid "The source filesystem \"{}\" does not exist"
+msgstr ""
+
+#: src/modules/unpackfs/main.py:464
+msgid ""
+"Failed to find unsquashfs, make sure you have the squashfs-tools package "
+"installed."
+msgstr ""
+
+#: src/modules/unpackfs/main.py:479
+msgid "The destination \"{}\" in the target system is not a directory"
+msgstr ""
+
+#: src/modules/luksopenswaphookcfg/main.py:26
+msgid "Configuring encrypted swap."
+msgstr ""
diff --git a/lang/python/zh_CN/LC_MESSAGES/python.po b/lang/python/zh_CN/LC_MESSAGES/python.po
index 706b02d24..c6972b0f5 100644
--- a/lang/python/zh_CN/LC_MESSAGES/python.po
+++ b/lang/python/zh_CN/LC_MESSAGES/python.po
@@ -9,6 +9,7 @@
# Feng Chao , 2020
# Bobby Rong , 2020
# 玉堂白鹤 , 2021
+# Giovanni Schiano-Moriello, 2022
#
#, fuzzy
msgid ""
@@ -17,7 +18,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-02 15:45+0100\n"
"PO-Revision-Date: 2017-08-09 10:34+0000\n"
-"Last-Translator: 玉堂白鹤 , 2021\n"
+"Last-Translator: Giovanni Schiano-Moriello, 2022\n"
"Language-Team: Chinese (China) (https://www.transifex.com/calamares/teams/20061/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -305,7 +306,7 @@ msgstr "无法启用 systemd 目标 {name!s}."
#: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer {name!s}."
-msgstr ""
+msgstr "无法启用 systemd 计时器 {name!s}。"
#: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target {name!s}."
@@ -384,7 +385,7 @@ msgstr "源文件系统 \"{}\" 不存在"
msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed."
-msgstr ""
+msgstr "寻找 unsquashfs 失败,请确定您已安装 squashfs-tools 软体包。"
#: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory"
diff --git a/src/libcalamares/Job.h b/src/libcalamares/Job.h
index 33965e15f..dc89f1c49 100644
--- a/src/libcalamares/Job.h
+++ b/src/libcalamares/Job.h
@@ -47,7 +47,7 @@ public:
/** @brief Is this JobResult a success?
*
- * Equivalent to errorCode() == 0, might be named isValid().
+ * Equivalent to errorCode() == 0, see succeeded().
*/
virtual operator bool() const;
@@ -58,6 +58,11 @@ public:
virtual void setDetails( const QString& details );
int errorCode() const { return m_number; }
+ /** @brief Is this JobResult a success?
+ *
+ * Equivalent to errorCode() == 0.
+ */
+ bool succeeded() const { return this->operator bool(); }
/// @brief an "ok status" result
static JobResult ok();
diff --git a/src/libcalamares/PythonJob.cpp b/src/libcalamares/PythonJob.cpp
index b1373f5f3..ec17c31f3 100644
--- a/src/libcalamares/PythonJob.cpp
+++ b/src/libcalamares/PythonJob.cpp
@@ -100,7 +100,7 @@ BOOST_PYTHON_MODULE( libcalamares )
bp::args( "s" ),
"Writes the given string to the Calamares warning stream." );
bp::def(
- "error", &CalamaresPython::warning, bp::args( "s" ), "Writes the given string to the Calamares error stream." );
+ "error", &CalamaresPython::error, bp::args( "s" ), "Writes the given string to the Calamares error stream." );
// .. YAML functions
diff --git a/src/libcalamares/partition/Mount.cpp b/src/libcalamares/partition/Mount.cpp
index 89e17a885..6bc3a7cd2 100644
--- a/src/libcalamares/partition/Mount.cpp
+++ b/src/libcalamares/partition/Mount.cpp
@@ -14,6 +14,7 @@
#include "partition/Sync.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
+#include "utils/String.h"
#include
#include
@@ -92,7 +93,7 @@ struct TemporaryMount::Private
TemporaryMount::TemporaryMount( const QString& devicePath, const QString& filesystemName, const QString& options )
- : m_d( std::make_unique() )
+ : m_d( std::make_unique< Private >() )
{
m_d->m_devicePath = devicePath;
m_d->m_mountDir.setAutoRemove( false );
@@ -123,5 +124,32 @@ TemporaryMount::path() const
return m_d ? m_d->m_mountDir.path() : QString();
}
+QList< MtabInfo >
+MtabInfo::fromMtabFilteredByPrefix( const QString& mountPrefix, const QString& mtabPath )
+{
+ QFile f( mtabPath.isEmpty() ? "/etc/mtab" : mtabPath );
+ if ( !f.open( QIODevice::ReadOnly ) )
+ {
+ return {};
+ }
+
+ QTextStream in( &f );
+ QList< MtabInfo > l;
+ while ( !f.atEnd() )
+ {
+ QStringList line = in.readLine().split( ' ', SplitSkipEmptyParts );
+ if ( line.length() == 3 && !line[ 0 ].startsWith( '#' ) )
+ {
+ // Lines have format: , so check
+ // the mountpoint field. Everything starts with an empty string.
+ if ( line[ 1 ].startsWith( mountPrefix ) )
+ {
+ l.append( { line[ 0 ], line[ 1 ] } );
+ }
+ }
+ }
+ return l;
+}
+
} // namespace Partition
} // namespace CalamaresUtils
diff --git a/src/libcalamares/partition/Mount.h b/src/libcalamares/partition/Mount.h
index d088b108f..f772c33a4 100644
--- a/src/libcalamares/partition/Mount.h
+++ b/src/libcalamares/partition/Mount.h
@@ -14,6 +14,7 @@
#include "DllMacro.h"
+#include
#include
#include
@@ -50,6 +51,13 @@ DLLEXPORT int mount( const QString& devicePath,
*/
DLLEXPORT int unmount( const QString& path, const QStringList& options = QStringList() );
+
+/** @brief Mount and automatically unmount a device
+ *
+ * The TemporaryMount object mounts a filesystem, and is like calling
+ * the mount() function, above. When the object is destroyed, unmount()
+ * is called with suitable options to undo the original mount.
+ */
class DLLEXPORT TemporaryMount
{
public:
@@ -68,6 +76,36 @@ private:
std::unique_ptr< Private > m_d;
};
+
+/** @brief Information about a mount point from /etc/mtab
+ *
+ * Entries in /etc/mtab are of the form:
+ * This struct only stores device and mountpoint.
+ *
+ * The main way of getting these structs is to call fromMtab() to read
+ * an /etc/mtab-like file and storing all of the entries from it.
+ */
+struct DLLEXPORT MtabInfo
+{
+ QString device;
+ QString mountPoint;
+
+ /** @brief Reads an mtab-like file and returns the entries from it
+ *
+ * When @p mtabPath is given, that file is read. If the given name is
+ * empty (e.g. the default) then /etc/mtab is read, instead.
+ *
+ * If @p mountPrefix is given, then only entries that have a mount point
+ * that starts with that prefix are returned.
+ */
+ static QList< MtabInfo > fromMtabFilteredByPrefix( const QString& mountPrefix = QString(),
+ const QString& mtabPath = QString() );
+ /// @brief Predicate to sort MtabInfo objects by device-name
+ static bool deviceOrder( const MtabInfo& a, const MtabInfo& b ) { return a.device > b.device; }
+ /// @brief Predicate to sort MtabInfo objects by mount-point
+ static bool mountPointOrder( const MtabInfo& a, const MtabInfo& b ) { return a.mountPoint > b.mountPoint; }
+};
+
} // namespace Partition
} // namespace CalamaresUtils
diff --git a/src/modules/README.md b/src/modules/README.md
index 09696e66b..97a8556a1 100644
--- a/src/modules/README.md
+++ b/src/modules/README.md
@@ -67,7 +67,7 @@ Note that process modules are not recommended.
Module descriptors **may** have the following keys:
- *emergency* (a boolean value, set to true to mark the module
- as an emergency module)
+ as an emergency module; see the section *Emergency Modules*, below)
- *noconfig* (a boolean value, set to true to state that the module
has no configuration file; defaults to false)
- *requiredModules* (a list of modules which are required for this module
@@ -102,8 +102,14 @@ to generate a suitable `module.desc`. For Python modules, manually add
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
(see below). If it does not, the module is not considered to be an emergency
-module after all (this is so that you can have modules that have several
-instances, only some of which are actually needed for emergencies).
+module after all. This is so that you can have modules that have several
+instances, only some of which are actually needed for emergencies.
+
+In summary:
+- in `module.desc`, write `emergency: true` to make it **possible** to
+ run the module in emergency mode,
+- in `.conf`, write `emergency: true` to make that specific
+ module run in emergency mode.
### Module-specific configuration
@@ -112,6 +118,10 @@ named `.conf`. If such a file is present in the
module's directory, it can be shipped as a *default* configuration file.
This only happens if the CMake-time option `INSTALL_CONFIG` is on.
+The name of the configuration file for a given module can be
+influenced by the `settings.conf` of the overall Calamares configuration.
+By default, though, the module's own name is used.
+
Modules that have *noconfig* set to true will not attempt to
read a configuration file, and will not warn that one is missing;
conversely if *noconfig* is set to false (or is missing, since
diff --git a/src/modules/bootloader/bootloader.conf b/src/modules/bootloader/bootloader.conf
index 9f9ad84aa..b28412b0f 100644
--- a/src/modules/bootloader/bootloader.conf
+++ b/src/modules/bootloader/bootloader.conf
@@ -46,6 +46,16 @@ efiBootMgr: "efibootmgr"
# setting the option here, keep in mind that the name is sanitized
# (problematic characters, see above, are replaced).
#
+# There are some special words possible at the end of *efiBootloaderId*:
+# @@SERIAL@@ can be used to obtain a uniquely-numbered suffix
+# that is added to the Id (yielding, e.g., `dirname1` or `dirname72`)
+# @@RANDOM@@ can be used to obtain a unique 4-digit hex suffix
+# @@PHRASE@@ can be used to obtain a unique 1-to-3-word suffix
+# from a dictionary of space-themed words
+# Note that these must be at the **end** of the *efiBootloaderId* value.
+# There must also be at most one of them. If there is none, no suffix-
+# processing is done and the *efiBootloaderId* is used unchanged.
+#
# efiBootloaderId: "dirname"
# Optionally install a copy of the GRUB EFI bootloader as the EFI
diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py
index 0182d1110..81e271a71 100644
--- a/src/modules/bootloader/main.py
+++ b/src/modules/bootloader/main.py
@@ -268,10 +268,166 @@ def create_loader(loader_path, entry):
loader_file.write(line)
-def efi_label():
+class suffix_iterator(object):
+ """
+ Wrapper for one of the "generator" classes below to behave like
+ a proper Python iterator. The iterator is initialized with a
+ maximum number of attempts to generate a new suffix.
+ """
+ def __init__(self, attempts, generator):
+ self.generator = generator
+ self.attempts = attempts
+ self.counter = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ self.counter += 1
+ if self.counter <= self.attempts:
+ return self.generator.next()
+ raise StopIteration
+
+
+class serialEfi(object):
+ """
+ EFI Id generator that appends a serial number to the given name.
+ """
+ def __init__(self, name):
+ self.name = name
+ # So the first call to next() will bump it to 0
+ self.counter = -1
+
+ def next(self):
+ self.counter += 1
+ if self.counter > 0:
+ return "{!s}{!s}".format(self.name, self.counter)
+ else:
+ return self.name
+
+
+def render_in_base(value, base_values, length=-1):
+ """
+ Renders @p value in base-N, where N is the number of
+ items in @p base_values. When rendering, use the items
+ of @p base_values (e.g. use "0123456789" to get regular decimal
+ rendering, or "ABCDEFGHIJ" for letters-as-numbers 'encoding').
+
+ If length is positive, pads out to at least that long with
+ leading "zeroes", whatever base_values[0] is.
+ """
+ if value < 0:
+ raise ValueError("Cannot render negative values")
+ if len(base_values) < 2:
+ raise ValueError("Insufficient items for base-N rendering")
+ if length < 1:
+ length = 1
+ digits = []
+ base = len(base_values)
+ while value > 0:
+ place = value % base
+ value = value // base
+ digits.append(base_values[place])
+ while len(digits) < length:
+ digits.append(base_values[0])
+ return "".join(reversed(digits))
+
+
+class randomEfi(object):
+ """
+ EFI Id generator that appends a random 4-digit hex number to the given name.
+ """
+ def __init__(self, name):
+ self.name = name
+ # So the first call to next() will bump it to 0
+ self.counter = -1
+
+ def next(self):
+ self.counter += 1
+ if self.counter > 0:
+ import random
+ v = random.randint(0, 65535) # 16 bits
+ return "{!s}{!s}".format(self.name, render_in_base(v, "0123456789ABCDEF", 4))
+ else:
+ return self.name
+
+
+class phraseEfi(object):
+ """
+ EFI Id generator that appends a random phrase to the given name.
+ """
+ words = ("Sun", "Moon", "Mars", "Soyuz", "Falcon", "Kuaizhou", "Gaganyaan")
+
+ def __init__(self, name):
+ self.name = name
+ # So the first call to next() will bump it to 0
+ self.counter = -1
+
+ def next(self):
+ self.counter += 1
+ if self.counter > 0:
+ import random
+ desired_length = 1 + self.counter // 5
+ v = random.randint(0, len(self.words) ** desired_length)
+ return "{!s}{!s}".format(self.name, render_in_base(v, self.words))
+ else:
+ return self.name
+
+
+def get_efi_suffix_generator(name):
+ """
+ Handle EFI bootloader Ids with @@@@ for suffix-processing.
+ """
+ if "@@" not in name:
+ raise ValueError("Misplaced call to get_efi_suffix_generator, no @@")
+ parts = name.split("@@")
+ if len(parts) != 3:
+ raise ValueError("EFI Id {!r} is malformed".format(name))
+ if parts[2]:
+ # Supposed to be empty because the string ends with "@@"
+ raise ValueError("EFI Id {!r} is malformed".format(name))
+ if parts[1] not in ("SERIAL", "RANDOM", "PHRASE"):
+ raise ValueError("EFI suffix {!r} is unknown".format(parts[1]))
+
+ generator = None
+ if parts[1] == "SERIAL":
+ generator = serialEfi(parts[0])
+ elif parts[1] == "RANDOM":
+ generator = randomEfi(parts[0])
+ elif parts[1] == "PHRASE":
+ generator = phraseEfi(parts[0])
+ if generator is None:
+ raise ValueError("EFI suffix {!r} is unsupported".format(parts[1]))
+
+ return generator
+
+
+def change_efi_suffix(efi_directory, bootloader_id):
+ """
+ Returns a label based on @p bootloader_id that is usable within
+ @p efi_directory. If there is a @@@@ suffix marker
+ in the given id, tries to generate a unique label.
+ """
+ if bootloader_id.endswith("@@"):
+ # Do 10 attempts with any suffix generator
+ g = suffix_iterator(10, get_efi_suffix_generator(bootloader_id))
+ else:
+ # Just one attempt
+ g = [bootloader_id]
+
+ for candidate_name in g:
+ if not os.path.exists(os.path.join(efi_directory, candidate_name)):
+ return candidate_name
+ return bootloader_id
+
+
+def efi_label(efi_directory):
+ """
+ Returns a sanitized label, possibly unique, that can be
+ used within @p efi_directory.
+ """
if "efiBootloaderId" in libcalamares.job.configuration:
- efi_bootloader_id = libcalamares.job.configuration[
- "efiBootloaderId"]
+ efi_bootloader_id = change_efi_suffix( efi_directory, calamares.job.configuration["efiBootloaderId"] )
else:
branding = libcalamares.globalstorage.value("branding")
efi_bootloader_id = branding["bootloaderEntryName"]
@@ -390,7 +546,7 @@ def run_grub_mkconfig(partitions, output_file):
check_target_env_call([libcalamares.job.configuration["grubMkconfig"], "-o", output_file])
-def run_grub_install(fw_type, partitions, efi_directory=None):
+def run_grub_install(fw_type, partitions, efi_directory):
"""
Runs grub-install in the target environment
@@ -407,7 +563,7 @@ def run_grub_install(fw_type, partitions, efi_directory=None):
check_target_env_call(["sh", "-c", "echo ZPOOL_VDEV_NAME_PATH=1 >> /etc/environment"])
if fw_type == "efi":
- efi_bootloader_id = efi_label()
+ efi_bootloader_id = efi_label(efi_directory)
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
if is_zfs:
@@ -462,7 +618,7 @@ def install_grub(efi_directory, fw_type):
if not os.path.isdir(install_efi_directory):
os.makedirs(install_efi_directory)
- efi_bootloader_id = efi_label()
+ efi_bootloader_id = efi_label(efi_directory)
efi_target, efi_grub_file, efi_boot_file = get_grub_efi_parameters()
@@ -506,7 +662,7 @@ def install_secureboot(efi_directory):
"""
Installs the secureboot shim in the system by calling efibootmgr.
"""
- efi_bootloader_id = efi_label()
+ efi_bootloader_id = efi_label(efi_directory)
install_path = libcalamares.globalstorage.value("rootMountPoint")
install_efi_directory = install_path + efi_directory
diff --git a/src/modules/bootloader/tests/CMakeTests.txt b/src/modules/bootloader/tests/CMakeTests.txt
new file mode 100644
index 000000000..5b16d5009
--- /dev/null
+++ b/src/modules/bootloader/tests/CMakeTests.txt
@@ -0,0 +1,7 @@
+# We have tests to exercise some of the module internals.
+# Those tests conventionally live in Python files here in the tests/ directory. Add them.
+add_test(
+ NAME test-bootloader-efiname
+ COMMAND env PYTHONPATH=.: python3 ${CMAKE_CURRENT_LIST_DIR}/test-bootloader-efiname.py
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
diff --git a/src/modules/bootloader/tests/test-bootloader-efiname.py b/src/modules/bootloader/tests/test-bootloader-efiname.py
new file mode 100644
index 000000000..67cb91747
--- /dev/null
+++ b/src/modules/bootloader/tests/test-bootloader-efiname.py
@@ -0,0 +1,64 @@
+# Calamares Boilerplate
+import libcalamares
+libcalamares.globalstorage = libcalamares.GlobalStorage(None)
+libcalamares.globalstorage.insert("testing", True)
+
+# Module prep-work
+from src.modules.bootloader import main
+
+# Specific Bootloader test
+g = main.get_efi_suffix_generator("derp@@SERIAL@@")
+assert g is not None
+assert g.next() == "derp" # First time, no suffix
+for n in range(9):
+ print(g.next())
+# We called next() 10 times in total, starting from 0
+assert g.next() == "derp10"
+
+g = main.get_efi_suffix_generator("derp@@RANDOM@@")
+assert g is not None
+for n in range(10):
+ print(g.next())
+# it's random, nothing to assert
+
+g = main.get_efi_suffix_generator("derp@@PHRASE@@")
+assert g is not None
+for n in range(10):
+ print(g.next())
+# it's random, nothing to assert
+
+# Check invalid things
+try:
+ g = main.get_efi_suffix_generator("derp")
+ raise TypeError("Shouldn't get generator (no indicator)")
+except ValueError as e:
+ pass
+
+try:
+ g = main.get_efi_suffix_generator("derp@@HEX@@")
+ raise TypeError("Shouldn't get generator (unknown indicator)")
+except ValueError as e:
+ pass
+
+try:
+ g = main.get_efi_suffix_generator("derp@@SERIAL@@x")
+ raise TypeError("Shouldn't get generator (trailing garbage)")
+except ValueError as e:
+ pass
+
+try:
+ g = main.get_efi_suffix_generator("derp@@SERIAL@@@@RANDOM@@")
+ raise TypeError("Shouldn't get generator (multiple indicators)")
+except ValueError as e:
+ pass
+
+
+# Try the generator (assuming no calamares- test files exist in /tmp)
+import os
+assert "calamares-single" == main.change_efi_suffix("/tmp", "calamares-single")
+assert "calamares-serial" == main.change_efi_suffix("/tmp", "calamares-serial@@SERIAL@@")
+try:
+ os.makedirs("/tmp/calamares-serial", exist_ok=True)
+ assert "calamares-serial1" == main.change_efi_suffix("/tmp", "calamares-serial@@SERIAL@@")
+finally:
+ os.rmdir("/tmp/calamares-serial")
diff --git a/src/modules/initcpio/InitcpioJob.cpp b/src/modules/initcpio/InitcpioJob.cpp
index b96f3b316..df995ccbf 100644
--- a/src/modules/initcpio/InitcpioJob.cpp
+++ b/src/modules/initcpio/InitcpioJob.cpp
@@ -1,6 +1,7 @@
/* === This file is part of Calamares - ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot
+ * SPDX-FileCopyrightText: 2022 Evan James
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
@@ -31,15 +32,22 @@ InitcpioJob::prettyName() const
return tr( "Creating initramfs with mkinitcpio." );
}
+/** @brief Sets secure permissions on each initramfs
+ *
+ * Iterates over each initramfs contained directly in the directory @p d.
+ * For each initramfs found, the permissions are set to owner read/write only.
+ *
+ */
void
fixPermissions( const QDir& d )
{
- for ( const auto& fi : d.entryInfoList( { "initramfs*" }, QDir::Files ) )
+ const auto initramList = d.entryInfoList( { "initramfs*" }, QDir::Files );
+ for ( const auto& fi : initramList )
{
QFile f( fi.absoluteFilePath() );
if ( f.exists() )
{
- cDebug() << "initcpio fixing permissions for" << f.fileName();
+ cDebug() << "initcpio setting permissions for" << f.fileName();
f.setPermissions( QFileDevice::ReadOwner | QFileDevice::WriteOwner );
}
}
@@ -63,9 +71,19 @@ InitcpioJob::exec()
}
}
+ // If the kernel option isn't set to a specific kernel, run mkinitcpio on all kernels
+ QStringList command = { "mkinitcpio" };
+ if ( m_kernel.isEmpty() || m_kernel == "all" )
+ {
+ command.append( "-P" );
+ }
+ else
+ {
+ command.append( { "-p", m_kernel } );
+ }
+
cDebug() << "Updating initramfs with kernel" << m_kernel;
- auto r = CalamaresUtils::System::instance()->targetEnvCommand(
- { "mkinitcpio", "-p", m_kernel }, QString(), QString() /* no timeout , 0 */ );
+ auto r = CalamaresUtils::System::instance()->targetEnvCommand( command, QString(), QString() /* no timeout , 0 */ );
return r.explainProcess( "mkinitcpio", std::chrono::seconds( 10 ) /* fake timeout */ );
}
@@ -73,28 +91,6 @@ void
InitcpioJob::setConfigurationMap( const QVariantMap& configurationMap )
{
m_kernel = CalamaresUtils::getString( configurationMap, "kernel" );
- if ( m_kernel.isEmpty() )
- {
- m_kernel = QStringLiteral( "all" );
- }
- else if ( m_kernel == "$uname" )
- {
- auto r = CalamaresUtils::System::runCommand( CalamaresUtils::System::RunLocation::RunInHost,
- { "/bin/uname", "-r" },
- QString(),
- QString(),
- std::chrono::seconds( 3 ) );
- if ( r.getExitCode() == 0 )
- {
- m_kernel = r.getOutput();
- cDebug() << "*initcpio* using running kernel" << m_kernel;
- }
- else
- {
- cWarning() << "*initcpio* could not determine running kernel, using 'all'." << Logger::Continuation
- << r.getExitCode() << r.getOutput();
- }
- }
m_unsafe = CalamaresUtils::getBool( configurationMap, "be_unsafe", false );
}
diff --git a/src/modules/initcpio/initcpio.conf b/src/modules/initcpio/initcpio.conf
index 717a511df..d2a126864 100644
--- a/src/modules/initcpio/initcpio.conf
+++ b/src/modules/initcpio/initcpio.conf
@@ -5,21 +5,22 @@
---
# This key defines the kernel to be loaded.
# It can have the following values:
-# - empty or unset, interpreted as "all"
-# - the literal string "$uname" (without quotes, with dollar),
-# which will use the output of `uname -r` to determine the
-# running kernel, and use that.
-# - any other string.
+# - the name of a single mkinitcpio preset
+# - empty or unset
+# - the literal string "all"
#
-# Whatever is set, that string is passed as *preset* argument to the
-# `-p` option of *mkinitcpio*. Take care that both "$uname" operates
-# in the host system, and might not be correct if the target system is
-# updated (to a newer kernel) as part of the installation.
+# If kernel is set to "all" or empty/unset then mkinitpio is called for all
+# kernels. Otherwise it is called with a single preset with the value
+# contained in kernel.
#
-# Note that "all" is probably not a good preset to use either.
-kernel: linux312
+kernel: linux
# Set this to true to turn off mitigations for lax file
# permissions on initramfs (which, in turn, can compromise
# your LUKS encryption keys, CVS-2019-13179).
+#
+# If your initramfs are stored in the EFI partition or another non-POSIX
+# filesystem, this has no effect as the file permissions cannot be changed.
+# In this case, ensure the partition is mounted securely.
+#
be_unsafe: false
diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py
index 8fc305829..1fd4af714 100644
--- a/src/modules/initcpiocfg/main.py
+++ b/src/modules/initcpiocfg/main.py
@@ -180,7 +180,8 @@ def find_initcpio_features(partitions, root_mount_point):
if partition["fs"] == "btrfs":
uses_btrfs = True
- if partition["fs"] == "zfs":
+ # In addition to checking the filesystem, check to ensure that zfs is enabled
+ if partition["fs"] == "zfs" and libcalamares.globalstorage.contains("zfsPoolInfo"):
uses_zfs = True
if "lvm2" in partition["fs"]:
diff --git a/src/modules/initramfs/InitramfsJob.cpp b/src/modules/initramfs/InitramfsJob.cpp
index 1b10a1a05..d83b4673c 100644
--- a/src/modules/initramfs/InitramfsJob.cpp
+++ b/src/modules/initramfs/InitramfsJob.cpp
@@ -81,6 +81,7 @@ InitramfsJob::setConfigurationMap( const QVariantMap& configurationMap )
}
else
{
+ m_kernel = QStringLiteral( "all" );
cWarning() << "*initramfs* could not determine running kernel, using 'all'." << Logger::Continuation
<< r.getExitCode() << r.getOutput();
}
diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp
index ce48edd82..8593f8385 100644
--- a/src/modules/locale/Config.cpp
+++ b/src/modules/locale/Config.cpp
@@ -221,6 +221,11 @@ Config::setCurrentLocation()
{
setCurrentLocation( m_startingTimezone.first, m_startingTimezone.second );
}
+ if ( !m_selectedLocaleConfiguration.explicit_lang )
+ {
+ auto newLocale = automaticLocaleConfiguration();
+ setLanguage( newLocale.language() );
+ }
}
void
@@ -252,15 +257,21 @@ Config::setCurrentLocation( const QString& regionName, const QString& zoneName )
void
Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location )
{
- if ( location != m_currentLocation )
+ const bool updateLocation = ( location != m_currentLocation );
+ if ( updateLocation )
{
m_currentLocation = location;
- // Overwrite those settings that have not been made explicit.
- auto newLocale = automaticLocaleConfiguration();
- if ( !m_selectedLocaleConfiguration.explicit_lang )
- {
- setLanguage( newLocale.language() );
- }
+ }
+
+ // lang should be always be updated
+ auto newLocale = automaticLocaleConfiguration();
+ if ( !m_selectedLocaleConfiguration.explicit_lang )
+ {
+ setLanguage( newLocale.language() );
+ }
+
+ if ( updateLocation )
+ {
if ( !m_selectedLocaleConfiguration.explicit_lc )
{
m_selectedLocaleConfiguration.lc_numeric = newLocale.lc_numeric;
diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py
index 10371777e..59777cedb 100644
--- a/src/modules/packages/main.py
+++ b/src/modules/packages/main.py
@@ -382,7 +382,7 @@ class PMPacman(PackageManager):
def line_cb(line):
if line.startswith(":: "):
- self.in_package_changes = "package changes" in line
+ self.in_package_changes = "package" in line or "hooks" in line
else:
if self.in_package_changes and line.endswith("...\n"):
# Update the message, untranslated; do not change the
@@ -392,7 +392,7 @@ class PMPacman(PackageManager):
global custom_status_message
custom_status_message = "pacman: " + line.strip()
libcalamares.job.setprogress(self.progress_fraction)
- libcalamares.utils.debug(line)
+ libcalamares.utils.debug(line)
self.in_package_changes = False
self.line_cb = line_cb
@@ -444,8 +444,12 @@ class PMPacman(PackageManager):
else:
command.append("-S")
+ # Don't ask for user intervention, take the default action
command.append("--noconfirm")
+ # Don't report download progress for each file
+ command.append("--noprogressbar")
+
if self.pacman_needed_only is True:
command.append("--needed")
diff --git a/src/modules/partition/jobs/ClearTempMountsJob.cpp b/src/modules/partition/jobs/ClearTempMountsJob.cpp
index ffbc35044..6219de004 100644
--- a/src/modules/partition/jobs/ClearTempMountsJob.cpp
+++ b/src/modules/partition/jobs/ClearTempMountsJob.cpp
@@ -9,6 +9,7 @@
#include "ClearTempMountsJob.h"
+#include "partition/Mount.h"
#include "utils/Logger.h"
#include "utils/String.h"
@@ -45,51 +46,23 @@ ClearTempMountsJob::exec()
{
Logger::Once o;
// Fetch a list of current mounts to Calamares temporary directories.
- QList< QPair< QString, QString > > lst;
- QFile mtab( "/etc/mtab" );
- if ( !mtab.open( QFile::ReadOnly | QFile::Text ) )
- {
- return Calamares::JobResult::error( tr( "Cannot get list of temporary mounts." ) );
- }
+ using MtabInfo = CalamaresUtils::Partition::MtabInfo;
+ auto targetMounts = MtabInfo::fromMtabFilteredByPrefix( QStringLiteral( "/tmp/calamares-" ) );
- cVerbose() << o << "Opened mtab. Lines:";
- QTextStream in( &mtab );
- QString lineIn = in.readLine();
- while ( !lineIn.isNull() )
- {
- QStringList line = lineIn.split( ' ', SplitSkipEmptyParts );
- cVerbose() << o << line.join( ' ' );
- QString device = line.at( 0 );
- QString mountPoint = line.at( 1 );
- if ( mountPoint.startsWith( "/tmp/calamares-" ) )
- {
- lst.append( qMakePair( device, mountPoint ) );
- }
- lineIn = in.readLine();
- }
-
- if ( lst.empty() )
+ if ( targetMounts.isEmpty() )
{
return Calamares::JobResult::ok();
}
-
- std::sort(
- lst.begin(), lst.end(), []( const QPair< QString, QString >& a, const QPair< QString, QString >& b ) -> bool {
- return a.first > b.first;
- } );
+ std::sort( targetMounts.begin(), targetMounts.end(), MtabInfo::mountPointOrder );
QStringList goodNews;
- QProcess process;
-
- for ( const auto& line : qAsConst( lst ) )
+ for ( const auto& m : qAsConst( targetMounts ) )
{
- QString partPath = line.second;
- cDebug() << o << "Will try to umount path" << partPath;
- process.start( "umount", { "-lv", partPath } );
- process.waitForFinished();
- if ( process.exitCode() == 0 )
+ cDebug() << o << "Will try to umount path" << m.mountPoint;
+ if ( CalamaresUtils::Partition::unmount( m.mountPoint, { "-lv" } ) == 0 )
{
- goodNews.append( QString( "Successfully unmounted %1." ).arg( partPath ) );
+ // Returns the program's exit code, so 0 is success
+ goodNews.append( QString( "Successfully unmounted %1." ).arg( m.mountPoint ) );
}
}
diff --git a/src/modules/partition/jobs/FormatPartitionJob.cpp b/src/modules/partition/jobs/FormatPartitionJob.cpp
index 1ccc6e617..63d233426 100644
--- a/src/modules/partition/jobs/FormatPartitionJob.cpp
+++ b/src/modules/partition/jobs/FormatPartitionJob.cpp
@@ -14,6 +14,7 @@
#include "core/KPMHelpers.h"
#include "partition/FileSystem.h"
+#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include
@@ -67,7 +68,18 @@ FormatPartitionJob::prettyStatusMessage() const
Calamares::JobResult
FormatPartitionJob::exec()
{
- 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() ) );
+ const auto fsType = m_partition->fileSystem().type();
+ auto r = KPMHelpers::execute( CreateFileSystemOperation( *m_device, *m_partition, fsType ),
+ tr( "The installer failed to format partition %1 on disk '%2'." )
+ .arg( m_partition->partitionPath(), m_device->name() ) );
+ if ( fsType == FileSystem::Xfs && r.succeeded() )
+ {
+ // We are going to try to set modern timestamps for the filesystem,
+ // (ignoring whether this succeeds). Requires a sufficiently-new
+ // xfs_admin and xfs_repair and might be made obsolete by newer
+ // kpmcore releases.
+ CalamaresUtils::System::runCommand( { "xfs_admin", "-O", "bigtime=1", m_partition->partitionPath() },
+ std::chrono::seconds( 60 ) );
+ }
+ return r;
}
diff --git a/src/modules/umount/CMakeLists.txt b/src/modules/umount/CMakeLists.txt
new file mode 100644
index 000000000..d72847007
--- /dev/null
+++ b/src/modules/umount/CMakeLists.txt
@@ -0,0 +1,19 @@
+# === This file is part of Calamares - ===
+#
+# SPDX-FileCopyrightText: 2021 Adriaan de Groot
+# SPDX-License-Identifier: BSD-2-Clause
+#
+calamares_add_plugin( umount
+ TYPE job
+ EXPORT_MACRO PLUGINDLLEXPORT_PRO
+ SOURCES
+ UmountJob.cpp
+ SHARED_LIB
+ EMERGENCY
+)
+
+calamares_add_test(
+ umounttest
+ SOURCES
+ Tests.cpp
+)
diff --git a/src/modules/umount/Tests.cpp b/src/modules/umount/Tests.cpp
new file mode 100644
index 000000000..dc0198619
--- /dev/null
+++ b/src/modules/umount/Tests.cpp
@@ -0,0 +1,52 @@
+/* === 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 "UmountJob.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+
+#include
+#include
+#include
+
+// Internals of UmountJob.cpp
+
+// Actual tests
+class UmountTests : public QObject
+{
+ Q_OBJECT
+public:
+ UmountTests() {}
+ ~UmountTests() override {}
+
+private Q_SLOTS:
+ void initTestCase();
+ void testTrue();
+};
+
+void
+UmountTests::initTestCase()
+{
+ Logger::setupLogLevel( Logger::LOGDEBUG );
+}
+
+void
+UmountTests::testTrue()
+{
+ QVERIFY( true );
+}
+
+QTEST_GUILESS_MAIN( UmountTests )
+
+#include "utils/moc-warnings.h"
+
+#include "Tests.moc"
diff --git a/src/modules/umount/UmountJob.cpp b/src/modules/umount/UmountJob.cpp
new file mode 100644
index 000000000..b9d92fa87
--- /dev/null
+++ b/src/modules/umount/UmountJob.cpp
@@ -0,0 +1,158 @@
+/* === This file is part of Calamares - ===
+ *
+ * Tags from the Python version of this module:
+ * SPDX-FileCopyrightText: 2014 Aurélien Gâteau
+ * SPDX-FileCopyrightText: 2016 Anke Boersma
+ * SPDX-FileCopyrightText: 2018 Adriaan de Groot
+ * Tags for the C++ version of this module:
+ * SPDX-FileCopyrightText: 2021 Adriaan de Groot
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Calamares is Free Software: see the License-Identifier above.
+ *
+ */
+
+#include "UmountJob.h"
+
+#include "partition/Mount.h"
+#include "utils/CalamaresUtilsSystem.h"
+#include "utils/Logger.h"
+#include "utils/Variant.h"
+
+#include "GlobalStorage.h"
+#include "JobQueue.h"
+
+#include
+#include
+#include
+
+UmountJob::UmountJob( QObject* parent )
+ : Calamares::CppJob( parent )
+{
+}
+
+UmountJob::~UmountJob() {}
+
+QString
+UmountJob::prettyName() const
+{
+ return tr( "Unmount file systems." );
+}
+
+static Calamares::JobResult
+unmountTargetMounts( const QString& rootMountPoint )
+{
+ QDir targetMount( rootMountPoint );
+ if ( !targetMount.exists() )
+ {
+ return Calamares::JobResult::internalError(
+ QCoreApplication::translate( UmountJob::staticMetaObject.className(), "Could not unmount target system." ),
+ QCoreApplication::translate( UmountJob::staticMetaObject.className(),
+ "The target system is not mounted at '%1'." )
+ .arg( rootMountPoint ),
+ Calamares::JobResult::GenericError );
+ }
+ QString targetMountPath = targetMount.absolutePath();
+ if ( !targetMountPath.endsWith( '/' ) )
+ {
+ targetMountPath.append( '/' );
+ }
+
+ using MtabInfo = CalamaresUtils::Partition::MtabInfo;
+ auto targetMounts = MtabInfo::fromMtabFilteredByPrefix( targetMountPath );
+ std::sort( targetMounts.begin(), targetMounts.end(), MtabInfo::mountPointOrder );
+
+ for ( const auto& m : qAsConst( targetMounts ) )
+ {
+ if ( CalamaresUtils::Partition::unmount( m.mountPoint, { "-lv" } ) )
+ {
+ // Returns the program's exit code, so 0 is success
+ return Calamares::JobResult::error(
+ QCoreApplication::translate( UmountJob::staticMetaObject.className(),
+ "Could not unmount target system." ),
+ QCoreApplication::translate( UmountJob::staticMetaObject.className(),
+ "The device '%1' is mounted in the target system. It is mounted at '%2'. "
+ "The device could not be unmounted." )
+ .arg( m.device, m.mountPoint ) );
+ }
+ }
+ return Calamares::JobResult::ok();
+}
+
+static Calamares::JobResult
+exportZFSPools( const QString& rootMountPoint )
+{
+ auto* gs = Calamares::JobQueue::instance()->globalStorage();
+ QStringList poolNames;
+ {
+ // The pools are dictionaries / VariantMaps
+ auto zfs_pool_list = gs->value( "zfsPoolInfo" ).toList();
+ for ( const auto& v : zfs_pool_list )
+ {
+ auto m = v.toMap();
+ QString poolName = m.value( "poolName" ).toString();
+ if ( !poolName.isEmpty() )
+ {
+ poolNames.append( poolName );
+ }
+ }
+ poolNames.sort();
+ }
+
+ for ( const auto& poolName : poolNames )
+ {
+ auto result = CalamaresUtils::System::runCommand( { "zpool", "export", poolName }, std::chrono::seconds( 30 ) );
+ if ( result.getExitCode() )
+ {
+ cWarning() << "Failed to export pool" << result.getOutput();
+ }
+ }
+ // Exporting ZFS pools does not cause the install to fail
+ return Calamares::JobResult::ok();
+}
+
+
+Calamares::JobResult
+UmountJob::exec()
+{
+ const auto* sys = CalamaresUtils::System::instance();
+ if ( !sys )
+ {
+ return Calamares::JobResult::internalError(
+ "UMount", 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(
+ "UMount", tr( "No rootMountPoint is set." ), Calamares::JobResult::InvalidConfiguration );
+ }
+
+ // Do the unmounting of target-system filesystems
+ {
+ auto r = unmountTargetMounts( gs->value( "rootMountPoint" ).toString() );
+ if ( !r )
+ {
+ return r;
+ }
+ }
+ // For ZFS systems, export the pools
+ {
+ auto r = exportZFSPools( gs->value( "rootMountPoint" ).toString() );
+ if ( !r )
+ {
+ return r;
+ }
+ }
+
+ return Calamares::JobResult::ok();
+}
+
+void
+UmountJob::setConfigurationMap( const QVariantMap& map )
+{
+}
+
+CALAMARES_PLUGIN_FACTORY_DEFINITION( UmountJobFactory, registerPlugin< UmountJob >(); )
diff --git a/src/modules/umount/UmountJob.h b/src/modules/umount/UmountJob.h
new file mode 100644
index 000000000..6ca5428bc
--- /dev/null
+++ b/src/modules/umount/UmountJob.h
@@ -0,0 +1,41 @@
+/* === 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 UMOUNTJOB_H
+#define UMOUNTJOB_H
+
+#include "CppJob.h"
+#include "DllMacro.h"
+#include "utils/PluginFactory.h"
+
+#include
+#include
+#include
+
+/** @brief Write 'random' data: machine id, entropy, UUIDs
+ *
+ */
+class PLUGINDLLEXPORT UmountJob : public Calamares::CppJob
+{
+ Q_OBJECT
+
+public:
+ explicit UmountJob( QObject* parent = nullptr );
+ ~UmountJob() override;
+
+ QString prettyName() const override;
+
+ Calamares::JobResult exec() override;
+
+ void setConfigurationMap( const QVariantMap& configurationMap ) override;
+};
+
+CALAMARES_PLUGIN_FACTORY_DECLARATION( UmountJobFactory )
+
+#endif // UMOUNTJOB_H
diff --git a/src/modules/umount/main.py b/src/modules/umount/main.py
deleted file mode 100644
index 5fdb36014..000000000
--- a/src/modules/umount/main.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# === This file is part of Calamares - ===
-#
-# SPDX-FileCopyrightText: 2014 Aurélien Gâteau
-# SPDX-FileCopyrightText: 2016 Anke Boersma
-# SPDX-FileCopyrightText: 2018 Adriaan de Groot
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# Calamares is Free Software: see the License-Identifier above.
-#
-
-import os
-import subprocess
-import shutil
-
-import libcalamares
-from libcalamares.utils import gettext_path, gettext_languages
-
-import gettext
-_translation = gettext.translation("calamares-python",
- localedir=gettext_path(),
- languages=gettext_languages(),
- fallback=True)
-_ = _translation.gettext
-_n = _translation.ngettext
-
-
-def pretty_name():
- return _( "Unmount file systems." )
-
-
-def list_mounts(root_mount_point):
- """ List mount points.
-
- :param root_mount_point:
- :return:
- """
- lst = []
-
- root_mount_point = os.path.normpath(root_mount_point)
- for line in open("/etc/mtab").readlines():
- device, mount_point, _ = line.split(" ", 2)
-
- if os.path.commonprefix([root_mount_point, mount_point]) == root_mount_point:
- lst.append((device, mount_point))
-
- return lst
-
-
-def export_zpools(root_mount_point):
- """ Exports the zpools if defined in global storage
-
- :param root_mount_point: The absolute path to the root of the install
- :return:
- """
- try:
- zfs_pool_list = libcalamares.globalstorage.value("zfsPoolInfo")
- zfs_pool_list.sort(reverse=True, key=lambda x: x["poolName"])
- if zfs_pool_list:
- for zfs_pool in zfs_pool_list:
- try:
- libcalamares.utils.host_env_process_output(['zpool', 'export', zfs_pool["poolName"]])
- except subprocess.CalledProcessError:
- libcalamares.utils.warning("Failed to export zpool")
- except Exception as e:
- # If this fails it shouldn't cause the installation to fail
- libcalamares.utils.warning("Received exception while exporting zpools: " + format(e))
- pass
-
-
-def run():
- """ Unmounts given mountpoints in decreasing order.
-
- :return:
- """
- root_mount_point = libcalamares.globalstorage.value("rootMountPoint")
-
- 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
- log_destination = '{!s}/{!s}'.format(root_mount_point, log_destination)
- # Make sure source is a string
- log_source = '{!s}'.format(log_source)
-
- # copy installation log before umount
- if os.path.exists(log_source):
- try:
- shutil.copy2(log_source, log_destination)
- except Exception as e:
- libcalamares.utils.warning("Could not preserve file {!s}, "
- "error {!s}".format(log_source, e))
-
- if not root_mount_point:
- return ("No mount point for root partition in globalstorage",
- "globalstorage does not contain a \"rootMountPoint\" key, "
- "doing nothing")
-
- if not os.path.exists(root_mount_point):
- return ("Bad mount point for root partition in globalstorage",
- "globalstorage[\"rootMountPoint\"] is \"{}\", which does not "
- "exist, doing nothing".format(root_mount_point))
-
- lst = list_mounts(root_mount_point)
- # Sort the list by mount point in decreasing order. This way we can be sure
- # we unmount deeper dirs first.
- lst.sort(key=lambda x: x[1], reverse=True)
-
- for device, mount_point in lst:
- # On success, no output; if the command fails, its output is
- # in the exception object.
- subprocess.check_output(["umount", "-lv", mount_point], stderr=subprocess.STDOUT)
-
- export_zpools(root_mount_point)
-
- os.rmdir(root_mount_point)
-
- return None
diff --git a/src/modules/umount/module.desc b/src/modules/umount/module.desc
deleted file mode 100644
index a1c189a62..000000000
--- a/src/modules/umount/module.desc
+++ /dev/null
@@ -1,7 +0,0 @@
-# SPDX-FileCopyrightText: no
-# SPDX-License-Identifier: CC0-1.0
----
-type: "job"
-name: "umount"
-interface: "python"
-script: "main.py"
diff --git a/src/modules/umount/umount.conf b/src/modules/umount/umount.conf
index 7c11f4db6..9fb922740 100644
--- a/src/modules/umount/umount.conf
+++ b/src/modules/umount/umount.conf
@@ -4,27 +4,11 @@
### Umount Module
#
# This module represents the last part of the installation, the unmounting
-# of partitions used for the install. It is also the last place where it
-# is possible to copy files to the target system.
-#
-# The "copy log files" functionality is deprecated; use the *preservefiles*
-# module instead, which is more flexible.
-#
+# of partitions used for the install. After this, there is no regular way
+# to modify the target system anymore.
#
---
-# 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"
+# Setting emergency to true will make it so this module is still run
+# when a prior module fails
+emergency: false
diff --git a/src/modules/umount/umount.schema.yaml b/src/modules/umount/umount.schema.yaml
index 9b81db3d9..37771e5f6 100644
--- a/src/modules/umount/umount.schema.yaml
+++ b/src/modules/umount/umount.schema.yaml
@@ -6,5 +6,4 @@ $id: https://calamares.io/schemas/umount
additionalProperties: false
type: object
properties:
- srcLog: { type: string }
- destLog: { type: string }
+ emergency: { type: boolean }
diff --git a/src/modules/unpackfs/main.py b/src/modules/unpackfs/main.py
index 9f1bd822c..033073881 100644
--- a/src/modules/unpackfs/main.py
+++ b/src/modules/unpackfs/main.py
@@ -428,14 +428,16 @@ def run():
if not root_mount_point:
libcalamares.utils.warning("No mount point for root partition")
return (_("No mount point for root partition"),
- _("globalstorage does not contain a \"rootMountPoint\" key, "
- "doing nothing"))
-
+ _("globalstorage does not contain a \"rootMountPoint\" key."))
if not os.path.exists(root_mount_point):
libcalamares.utils.warning("Bad root mount point \"{}\"".format(root_mount_point))
return (_("Bad mount point for root partition"),
- _("rootMountPoint is \"{}\", which does not "
- "exist, doing nothing").format(root_mount_point))
+ _("rootMountPoint is \"{}\", which does not exist.".format(root_mount_point)))
+
+ if libcalamares.job.configuration.get("unpack", None) is None:
+ libcalamares.utils.warning("No *unpack* key in job configuration.")
+ return (_("Bad unpackfs configuration"),
+ _("There is no configuration information."))
supported_filesystems = get_supported_filesystems()
@@ -450,17 +452,17 @@ def run():
if sourcefs not in supported_filesystems:
libcalamares.utils.warning("The filesystem for \"{}\" ({}) is not supported by your current kernel".format(source, sourcefs))
libcalamares.utils.warning(" ... modprobe {} may solve the problem".format(sourcefs))
- return (_("Bad unsquash configuration"),
+ return (_("Bad unpackfs configuration"),
_("The filesystem for \"{}\" ({}) is not supported by your current kernel").format(source, sourcefs))
if not os.path.exists(source):
libcalamares.utils.warning("The source filesystem \"{}\" does not exist".format(source))
- return (_("Bad unsquash configuration"),
+ return (_("Bad unpackfs configuration"),
_("The source filesystem \"{}\" does not exist").format(source))
if sourcefs == "squashfs":
if shutil.which("unsquashfs") is None:
libcalamares.utils.warning("Failed to find unsquashfs")
- return (_("Bad unsquash configuration"),
+ return (_("Bad unpackfs configuration"),
_("Failed to find unsquashfs, make sure you have the squashfs-tools package installed.") +
" " + _("Failed to unpack image \"{}\"").format(source))
@@ -475,7 +477,7 @@ def run():
if not os.path.isdir(destination) and sourcefs != "file":
libcalamares.utils.warning(("The destination \"{}\" in the target system is not a directory").format(destination))
if is_first:
- return (_("Bad unsquash configuration"),
+ return (_("Bad unpackfs configuration"),
_("The destination \"{}\" in the target system is not a directory").format(destination))
else:
libcalamares.utils.debug(".. assuming that the previous targets will create that directory.")