Merge upstream development

This commit is contained in:
Philip Müller 2022-01-18 15:13:19 +01:00
commit e3bf37c261
38 changed files with 5518 additions and 289 deletions

View File

@ -7,22 +7,55 @@ contributors are listed. Note that Calamares does not have a historical
changelog -- this log starts with version 3.2.0. The release notes on the
website will have to do for older versions.
# 3.2.50 (unreleased) #
# 3.2.51 (unreleased) #
This release contains contributions from (alphabetically by first name):
- Erik Dubois
- No external contributors yet
## Core ##
- No core changes yet
## Modules ##
- The *umount* module has been re-written in C++. The copy-a-log-file
functionality has been removed. Use the *preservefiles* module for that.
# 3.2.50 (2022-01-18) #
This release contains contributions from (alphabetically by first name):
- Anke Boersma
- Erik Dubois
- Evan James
- Johannes Kamprad
- Taejun Park (new contributor, welcome!)
**Replacement notice:** The *umount* module will be replaced by a C++
implementation in the next release. The "preserve log file" feature
will be removed in that release. Use the *preservefiles* module instead.
## Core ##
- No core changes yet
## Modules ##
- *initcpiocfg* mentioned a special kernel-name "all", which did not work,
and a special kernel-name "$uname" which cannot work. Fixed the former
and removed the "$uname" special key. (Thanks Evan)
- *luksswaphookcfg* has been converted to a C++ module.
- *networkcfg* could fail to update the NetworkManager configuration
if the SSID or username contained non-ASCII characters **and** the
default Python text-file encoding was set to ASCII. The files are
now read and written in UTF-8, explicitly. #1848
- *partition* always sets *bigtime* option on XFS filesystems, if possible.
Requires sufficiently-recent xfsprogs. #1874
- *preservefiles* was missing some necessary features, needed for it
to replace the deprecated log-file-saving functionality in the *umount*
module. (Thanks Erik and Joe for testing) #1851
- *umount* is now marked as an emergency module in the example configuration,
since it should **probably** be run as a cleanup. (Thanks Evan)
- *welcome* and *locale* could be confusing, together, and configure
the target system with a language that does not match the installer
language, even though the user did not make any explicit choice.
(Thanks Taejun) #1864
# 3.2.49.1 (2021-12-11) #

View File

@ -41,7 +41,7 @@
# TODO:3.3: Require CMake 3.12
cmake_minimum_required( VERSION 3.3 FATAL_ERROR )
project( CALAMARES
VERSION 3.2.50
VERSION 3.2.51
LANGUAGES C CXX
)

View File

@ -75,7 +75,7 @@ fi
#
#
BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.)
KEY_ID="CFDDC96F12B1915C"
KEY_ID="328D742D8807A435"
# Try to make gpg cache the signing key, so we can leave the process
# to run and sign.

View File

@ -693,27 +693,27 @@ Instalační program bude ukončen a všechny změny ztraceny.</translation>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation>Úspěšně odpojeno %1.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation>Úspěšně vypnut swap %1.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation>Úspěšně vyčištěn swap %1.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation>Úspěšně zavřeno mapper zařízení %1.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation>Úspěšně vypnuta skupina svazků %1.</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>

4366
lang/calamares_ja-Hira.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2014,7 +2014,7 @@ O instalador será encerrado e todas as alterações serão perdidas.</translati
<source>Please select your preferred location on the map so the installer can suggest the locale
and timezone settings for you. You can fine-tune the suggested settings below. Search the map by dragging
to move and using the +/- buttons to zoom in/out or use mouse scrolling for zooming.</source>
<translation>Por favor selecione o seu local preferido no mapa para que o instalador possa sugerir a localização
<translation>Selecione o seu local preferido no mapa para que o instalador possa sugerir a localização
e fuso horário para si. Pode ajustar as definições sugeridas abaixo. Procure no mapa arrastando
para mover e utilizando os botões +/- para aumentar/diminuir ou utilize a roda do rato para dar zoom.</translation>
</message>
@ -4003,7 +4003,7 @@ Saída de Dados:
<message>
<location filename="../src/modules/welcome/WelcomePage.cpp" line="238"/>
<source>&lt;h1&gt;%1&lt;/h1&gt;&lt;br/&gt;&lt;strong&gt;%2&lt;br/&gt;for %3&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;Copyright 2014-2017 Teo Mrnjavac &amp;lt;teo@kde.org&amp;gt;&lt;br/&gt;Copyright 2017-2020 Adriaan de Groot &amp;lt;groot@kde.org&amp;gt;&lt;br/&gt;Thanks to &lt;a href="https://calamares.io/team/"&gt;the Calamares team&lt;/a&gt; and the &lt;a href="https://www.transifex.com/calamares/calamares/"&gt;Calamares translators team&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;&lt;a href="https://calamares.io/"&gt;Calamares&lt;/a&gt; development is sponsored by &lt;br/&gt;&lt;a href="http://www.blue-systems.com/"&gt;Blue Systems&lt;/a&gt; - Liberating Software.</source>
<translation>&lt;h1&gt;%1&lt;/h1&gt;&lt;br/&gt;&lt;strong&gt;%2&lt;br/&gt;para %3&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;Copyright 2014-2017 Teo Mrnjavac &amp;lt;teo@kde.org&amp;gt;&lt;br/&gt;Copyright 2017-2020 Adriaan de Groot &amp;lt;groot@kde.org&amp;gt;&lt;br/&gt;Obrigado à &lt;a href="https://calamares.io/team/"&gt;equipa Calamares&lt;/a&gt; e à &lt;a href="https://www.transifex.com/calamares/calamares/"&gt;equipa de tradutores do Calamares&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;O desenvolvimento do &lt;a href="https://calamares.io/"&gt;Calamares&lt;/a&gt; é patrocinado pela &lt;br/&gt;&lt;a href="http://www.blue-systems.com/"&gt;Blue Systems&lt;/a&gt; - Liberating Software.</translation>
<translation>&lt;h1&gt;%1&lt;/h1&gt;&lt;br/&gt;&lt;strong&gt;%2&lt;br/&gt;para o %3&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;Copyright 2014-2017 Teo Mrnjavac &amp;lt;teo@kde.org&amp;gt;&lt;br/&gt;Copyright 2017-2020 Adriaan de Groot &amp;lt;groot@kde.org&amp;gt;&lt;br/&gt;Obrigado à &lt;a href="https://calamares.io/team/"&gt;equipa Calamares&lt;/a&gt; e à &lt;a href="https://www.transifex.com/calamares/calamares/"&gt;equipa de tradutores do Calamares&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;O desenvolvimento do &lt;a href="https://calamares.io/"&gt;Calamares&lt;/a&gt; é patrocinado pela &lt;br/&gt;&lt;a href="http://www.blue-systems.com/"&gt;Blue Systems&lt;/a&gt; - Liberating Software.</translation>
</message>
</context>
<context>

View File

@ -104,17 +104,17 @@
<message>
<location filename="../src/calamares/DebugWindow.ui" line="102"/>
<source>Crashes Calamares, so that Dr. Konqui can look at it.</source>
<translation type="unfinished"/>
<translation> crash lui Calamares, pentru ca doctorul Konqui se uite la el.</translation>
</message>
<message>
<location filename="../src/calamares/DebugWindow.ui" line="115"/>
<source>Reloads the stylesheet from the branding directory.</source>
<translation type="unfinished"/>
<translation>Reîncarcă foaia de stil din directorul branding.</translation>
</message>
<message>
<location filename="../src/calamares/DebugWindow.ui" line="141"/>
<source>Uploads the session log to the configured pastebin.</source>
<translation type="unfinished"/>
<translation>Încarcă jurnalul sesiunii pe pastebin-ul configurat.</translation>
</message>
<message>
<location filename="../src/calamares/DebugWindow.ui" line="144"/>
@ -134,7 +134,7 @@
<message>
<location filename="../src/calamares/DebugWindow.ui" line="131"/>
<source>Widget Tree</source>
<translation>Lista widget</translation>
<translation>Arborele de widget</translation>
</message>
<message>
<location filename="../src/calamares/DebugWindow.cpp" line="221"/>

View File

@ -6,7 +6,7 @@
<message>
<location filename="../src/modules/partition/jobs/AutoMountManagementJob.cpp" line="22"/>
<source>Manage auto-mount settings</source>
<translation type="unfinished"/>
<translation>Идора кардани танзимоти васлкунии худкор</translation>
</message>
</context>
<context>

View File

@ -688,27 +688,27 @@ The installer will quit and all changes will be lost.</source>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="259"/>
<source>Successfully unmounted %1.</source>
<translation type="unfinished"/>
<translation> %1</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="266"/>
<source>Successfully disabled swap %1.</source>
<translation type="unfinished"/>
<translation> %1</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="292"/>
<source>Successfully cleared swap %1.</source>
<translation type="unfinished"/>
<translation> %1</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="306"/>
<source>Successfully closed mapper device %1.</source>
<translation type="unfinished"/>
<translation> %1</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="319"/>
<source>Successfully disabled volume group %1.</source>
<translation type="unfinished"/>
<translation> %1</translation>
</message>
<message>
<location filename="../src/modules/partition/jobs/ClearMountsJob.cpp" line="358"/>

View File

@ -6,7 +6,7 @@
# Translators:
# pavelrz, 2017
# LiberteCzech <martin.kriz.czech@gmail.com>, 2020
# Pavel Borecki <pavel.borecki@gmail.com>, 2021
# Pavel Borecki <pavel.borecki@gmail.com>, 2022
#
#, fuzzy
msgid ""
@ -15,7 +15,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: Pavel Borecki <pavel.borecki@gmail.com>, 2021\n"
"Last-Translator: Pavel Borecki <pavel.borecki@gmail.com>, 2022\n"
"Language-Team: Czech (Czech Republic) (https://www.transifex.com/calamares/teams/20061/cs_CZ/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -331,7 +331,7 @@ msgstr "Nedaří se zapnout systemd službu <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr ""
msgstr "Nedaří se zapnout systemd časovač <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -413,6 +413,8 @@ msgid ""
"Failed to find unsquashfs, make sure you have the squashfs-tools package "
"installed."
msgstr ""
"Nepodařilo se nalézt nástroj unsquashfs ověřte, že je nainstalovaný "
"balíček squashfs-tools."
#: src/modules/unpackfs/main.py:479
msgid "The destination \"{}\" in the target system is not a directory"

View File

@ -0,0 +1,385 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"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"
"Language-Team: Japanese (Hiragana) (https://www.transifex.com/calamares/teams/20061/ja-Hira/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ja-Hira\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/modules/initramfscfg/main.py:32
msgid "Configuring initramfs."
msgstr ""
#: src/modules/initramfscfg/main.py:85 src/modules/initramfscfg/main.py:89
#: src/modules/fstab/main.py:355 src/modules/fstab/main.py:361
#: src/modules/fstab/main.py:388 src/modules/networkcfg/main.py:105
#: src/modules/initcpiocfg/main.py:227 src/modules/initcpiocfg/main.py:231
#: src/modules/localecfg/main.py:135 src/modules/mount/main.py:144
#: src/modules/rawfs/main.py:164 src/modules/openrcdmcryptcfg/main.py:72
#: src/modules/openrcdmcryptcfg/main.py:76
#: src/modules/luksopenswaphookcfg/main.py:86
#: src/modules/luksopenswaphookcfg/main.py:90
msgid "Configuration Error"
msgstr ""
#: src/modules/initramfscfg/main.py:86 src/modules/fstab/main.py:356
#: src/modules/initcpiocfg/main.py:228 src/modules/mount/main.py:145
#: src/modules/rawfs/main.py:165 src/modules/openrcdmcryptcfg/main.py:73
#: src/modules/luksopenswaphookcfg/main.py:87
msgid "No partitions are defined for <pre>{!s}</pre> 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 <pre>{!s}</pre> 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 "
"<pre>{!s}</pre> returned error code {!s}."
msgstr ""
#: src/modules/fstab/main.py:29
msgid "Writing fstab."
msgstr ""
#: src/modules/fstab/main.py:389
msgid "No <pre>{!s}</pre> configuration is given for <pre>{!s}</pre> 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 <code>{arg!s}</code> 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 ""
"<code>rc-update {arg!s}</code> 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 <code>{path!s}</code>, 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 <code>{path!s}</code>, 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 <pre>{!s}</pre> "
"returned error code {!s}."
msgstr ""
#: src/modules/packages/main.py:651
msgid ""
"The package manager could not update the system. The command <pre>{!s}</pre>"
" 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 <pre>{!s}</pre> 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 ""
"<code>systemctl {arg!s}</code> 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 <code>{name!s}</code>."
msgstr ""
#: src/modules/services-systemd/main.py:65
msgid "Cannot enable systemd target <code>{name!s}</code>."
msgstr ""
#: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr ""
#: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>."
msgstr ""
#: src/modules/services-systemd/main.py:73
msgid "Cannot mask systemd unit <code>{name!s}</code>."
msgstr ""
#: src/modules/services-systemd/main.py:75
msgid ""
"Unknown systemd commands <code>{command!s}</code> and "
"<code>{suffix!s}</code> 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 ""

View File

@ -9,6 +9,7 @@
# Feng Chao <chaofeng111@qq.com>, 2020
# Bobby Rong <admin@bobby285271.top>, 2020
# 玉堂白鹤 <yjwork@qq.com>, 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: 玉堂白鹤 <yjwork@qq.com>, 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 目标 <code>{name!s}</code>."
#: src/modules/services-systemd/main.py:67
msgid "Cannot enable systemd timer <code>{name!s}</code>."
msgstr ""
msgstr "无法启用 systemd 计时器 <code>{name!s}</code>。"
#: src/modules/services-systemd/main.py:71
msgid "Cannot disable systemd target <code>{name!s}</code>."
@ -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"

View File

@ -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();

View File

@ -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

View File

@ -14,6 +14,7 @@
#include "partition/Sync.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include "utils/String.h"
#include <QDir>
#include <QTemporaryDir>
@ -92,7 +93,7 @@ struct TemporaryMount::Private
TemporaryMount::TemporaryMount( const QString& devicePath, const QString& filesystemName, const QString& options )
: m_d( std::make_unique<Private>() )
: 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: <device> <mountpoint> <options>, 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

View File

@ -14,6 +14,7 @@
#include "DllMacro.h"
#include <QList>
#include <QString>
#include <QStringList>
@ -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: <device> <mountpoint> <other>
* 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

View File

@ -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 `<modulename>.conf`, write `emergency: true` to make that specific
module run in emergency mode.
### Module-specific configuration
@ -112,6 +118,10 @@ named `<modulename>.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

View File

@ -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

View File

@ -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 @@<something>@@ 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 @@<something>@@ 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

View File

@ -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}
)

View File

@ -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")

View File

@ -1,6 +1,7 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
* SPDX-FileCopyrightText: 2022 Evan James <dalto@fastmail.com>
* 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 );
}

View File

@ -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

View File

@ -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"]:

View File

@ -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();
}

View File

@ -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;

View File

@ -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")

View File

@ -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 ) );
}
}

View File

@ -14,6 +14,7 @@
#include "core/KPMHelpers.h"
#include "partition/FileSystem.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include <kpmcore/core/device.h>
@ -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;
}

View File

@ -0,0 +1,19 @@
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
# 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
)

View File

@ -0,0 +1,52 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* 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 <QDir>
#include <QFile>
#include <QtTest/QtTest>
// 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"

View File

@ -0,0 +1,158 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* Tags from the Python version of this module:
* SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
* SPDX-FileCopyrightText: 2016 Anke Boersma <demm@kaosx.us>
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
* Tags for the C++ version of this module:
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* 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 <QCoreApplication>
#include <QDir>
#include <QList>
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 >(); )

View File

@ -0,0 +1,41 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
* 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 <QObject>
#include <QStringList>
#include <QVariantMap>
/** @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

View File

@ -1,123 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://calamares.io> ===
#
# SPDX-FileCopyrightText: 2014 Aurélien Gâteau <agateau@kde.org>
# SPDX-FileCopyrightText: 2016 Anke Boersma <demm@kaosx.us>
# SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
# 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

View File

@ -1,7 +0,0 @@
# SPDX-FileCopyrightText: no
# SPDX-License-Identifier: CC0-1.0
---
type: "job"
name: "umount"
interface: "python"
script: "main.py"

View File

@ -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

View File

@ -6,5 +6,4 @@ $id: https://calamares.io/schemas/umount
additionalProperties: false
type: object
properties:
srcLog: { type: string }
destLog: { type: string }
emergency: { type: boolean }

View File

@ -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.")