diff --git a/CHANGES b/CHANGES index 8c56b4023..7094f5866 100644 --- a/CHANGES +++ b/CHANGES @@ -8,14 +8,36 @@ website will have to do for older versions. This release contains contributions from (alphabetically by first name): - Alf Gaida - Caio Carvalho + - Kevin Kofler - Philip Mueller + - Scott Harvey ## Core ## + * The Calamares application now recognizes the `-X` or `--xdg-config` + option, which adds XDG_DATA_DIRS to the places used to find QML + and branding directories, and XDG_CONFIG_DIRS to the places used + to find the global settings and module configurations. This allows + a more fine-grained, and more layered, approach to setting up + Calamares configurations (in particular, distro's can **add** + configuration files and give them priority, instead of **forking** + configuration files). + ## Modules ## * The *partition* module supports RAID devices, but only when Calamares is compiled with the newest KPMCore release. + * The calculation of required space -- including swap -- has been simplified, + and Calamares no longer reserves 2GiB of space in calculations for internal + use (this means that it no longer mysteriously drops swap when the disk + size is close to the required installation size). + * The *keyboard* module now handles the (bogus) Austrian keymap for + the system console properly. + * The *preservefiles* module now has a mechanism for setting the permissions + (and ownership) of preserved files. + * New module *fsresizer* can be used to resize filesystems. It is intended + for use in OEM installs where an image of fixed size is created, + and then sized to the actual SD card the user has used. # 3.2.2 (2018-09-04) # diff --git a/CMakeLists.txt b/CMakeLists.txt index f241cedd0..4fb88420b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,6 +237,11 @@ set_package_properties( find_package(ECM ${ECM_VERSION} NO_MODULE) if( ECM_FOUND ) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_MODULE_PATH}) + if ( BUILD_TESTING ) + # ECM implies that we can build the tests, too + find_package( Qt5 COMPONENTS Test REQUIRED ) + include( ECMAddTests ) + endif() endif() find_package( KF5 COMPONENTS CoreAddons Crash ) diff --git a/calamares.desktop b/calamares.desktop index 3cdce6888..46b2e979f 100644 --- a/calamares.desktop +++ b/calamares.desktop @@ -14,165 +14,165 @@ Categories=Qt;System; X-AppStream-Ignore=true Name[ar]=نظام التثبيت -Icon[be]=calamares -GenericName[be]=Усталёўшчык сістэмы Comment[be]=Calamares — усталёўшчык сістэмы +Icon[be]=calamares Name[be]=Усталяваць сістэму -Icon[bg]=calamares -GenericName[bg]=Системен Инсталатор +GenericName[be]=Усталёўшчык сістэмы Comment[bg]=Calamares — Системен Инсталатор +Icon[bg]=calamares Name[bg]=Инсталирай системата -Icon[ca]=calamares -GenericName[ca]=Instal·lador de sistema +GenericName[bg]=Системен Инсталатор Comment[ca]=Calamares — Instal·lador de sistema +Icon[ca]=calamares Name[ca]=Instal·la el sistema -Icon[da]=calamares -GenericName[da]=Systeminstallationsprogram +GenericName[ca]=Instal·lador de sistema Comment[da]=Calamares — Systeminstallationsprogram +Icon[da]=calamares Name[da]=Installér system -Icon[de]=calamares -GenericName[de]=Installation des Betriebssystems +GenericName[da]=Systeminstallationsprogram Comment[de]=Calamares - Installation des Betriebssystems +Icon[de]=calamares Name[de]=System installieren -Icon[el]=calamares -GenericName[el]=Εγκατάσταση συστήματος +GenericName[de]=Installation des Betriebssystems Comment[el]=Calamares — Εγκατάσταση συστήματος +Icon[el]=calamares Name[el]=Εγκατάσταση συστήματος -Icon[en_GB]=calamares -GenericName[en_GB]=System Installer +GenericName[el]=Εγκατάσταση συστήματος Comment[en_GB]=Calamares — System Installer +Icon[en_GB]=calamares Name[en_GB]=Install System -Icon[es]=calamares -GenericName[es]=Instalador del Sistema +GenericName[en_GB]=System Installer Comment[es]=Calamares — Instalador del Sistema +Icon[es]=calamares Name[es]=Instalar Sistema -Icon[et]=calamares -GenericName[et]=Süsteemipaigaldaja +GenericName[es]=Instalador del Sistema Comment[et]=Calamares — süsteemipaigaldaja +Icon[et]=calamares Name[et]=Paigalda süsteem +GenericName[et]=Süsteemipaigaldaja Name[eu]=Sistema instalatu Name[es_PR]=Instalar el sistema -Icon[fr]=calamares -GenericName[fr]=Installateur système Comment[fr]=Calamares - Installateur système +Icon[fr]=calamares Name[fr]=Installer le système +GenericName[fr]=Installateur système Name[gl]=Instalación do Sistema -Icon[he]=calamares -GenericName[he]=אשף התקנה Comment[he]=Calamares - אשף התקנה +Icon[he]=calamares Name[he]=התקנת מערכת -Icon[hi]=calamares -GenericName[hi]=सिस्टम इंस्टॉलर +GenericName[he]=אשף התקנה Comment[hi]=Calamares — सिस्टम इंस्टॉलर +Icon[hi]=calamares Name[hi]=सिस्टम इंस्टॉल करें -Icon[hr]=calamares -GenericName[hr]=Instalacija sustava +GenericName[hi]=सिस्टम इंस्टॉलर Comment[hr]=Calamares — Instalacija sustava +Icon[hr]=calamares Name[hr]=Instaliraj sustav -Icon[hu]=calamares -GenericName[hu]=Rendszer Telepítő +GenericName[hr]=Instalacija sustava Comment[hu]=Calamares — Rendszer Telepítő +Icon[hu]=calamares Name[hu]=Rendszer telepítése -Icon[id]=calamares -GenericName[id]=Pemasang +GenericName[hu]=Rendszer Telepítő Comment[id]=Calamares — Pemasang Sistem +Icon[id]=calamares Name[id]=Instal Sistem -Icon[is]=calamares -GenericName[is]=Kerfis uppsetning +GenericName[id]=Pemasang Comment[is]=Calamares — Kerfis uppsetning +Icon[is]=calamares Name[is]=Setja upp kerfið -Icon[cs_CZ]=calamares -GenericName[cs_CZ]=Instalátor systému +GenericName[is]=Kerfis uppsetning Comment[cs_CZ]=Calamares – instalátor operačních systémů +Icon[cs_CZ]=calamares Name[cs_CZ]=Nainstalovat -Icon[ja]=calamares -GenericName[ja]=システムインストーラー +GenericName[cs_CZ]=Instalátor systému Comment[ja]=Calamares — システムインストーラー +Icon[ja]=calamares Name[ja]=システムをインストール -Icon[ko]=깔라마레스 -GenericName[ko]=시스템 설치 관리자 +GenericName[ja]=システムインストーラー Comment[ko]=깔라마레스 — 시스템 설치 관리자 +Icon[ko]=깔라마레스 Name[ko]=시스템 설치 -Icon[lt]=calamares -GenericName[lt]=Sistemos diegimas į kompiuterį +GenericName[ko]=시스템 설치 관리자 Comment[lt]=Calamares — Sistemos diegimo programa +Icon[lt]=calamares Name[lt]=Įdiegti Sistemą -Icon[it_IT]=calamares -GenericName[it_IT]=Programma d'installazione del sistema +GenericName[lt]=Sistemos diegimas į kompiuterį Comment[it_IT]=Calamares — Programma d'installazione del sistema +Icon[it_IT]=calamares Name[it_IT]=Installa il sistema -Icon[nb]=calamares -GenericName[nb]=Systeminstallatør +GenericName[it_IT]=Programma d'installazione del sistema Comment[nb]=Calamares-systeminstallatør +Icon[nb]=calamares Name[nb]=Installer System -Icon[nl]=calamares -GenericName[nl]=Installatieprogramma +GenericName[nb]=Systeminstallatør Comment[nl]=Calamares — Installatieprogramma +Icon[nl]=calamares Name[nl]=Installeer systeem -Icon[pl]=calamares -GenericName[pl]=Instalator systemu +GenericName[nl]=Installatieprogramma Comment[pl]=Calamares — Instalator systemu +Icon[pl]=calamares Name[pl]=Zainstaluj system -Icon[pt_BR]=calamares -GenericName[pt_BR]=Instalador de Sistema +GenericName[pl]=Instalator systemu Comment[pt_BR]=Calamares — Instalador de Sistema +Icon[pt_BR]=calamares Name[pt_BR]=Sistema de Instalação -Icon[ro]=calamares -GenericName[ro]=Instalator de sistem +GenericName[pt_BR]=Instalador de Sistema Comment[ro]=Calamares — Instalator de sistem +Icon[ro]=calamares Name[ro]=Instalează sistemul -Icon[ru]=calamares -GenericName[ru]=Установщик системы +GenericName[ro]=Instalator de sistem Comment[ru]=Calamares - Установщик системы +Icon[ru]=calamares Name[ru]=Установить систему -Icon[sk]=calamares -GenericName[sk]=Inštalátor systému +GenericName[ru]=Установщик системы Comment[sk]=Calamares — Inštalátor systému +Icon[sk]=calamares Name[sk]=Inštalovať systém +GenericName[sk]=Inštalátor systému Name[sl]=Namesti sistem -Icon[sq]=calamares -GenericName[sq]=Instalues Sistemi Comment[sq]=Calamares — Instalues Sistemi +Icon[sq]=calamares Name[sq]=Instalo Sistemin -Icon[fi_FI]=calamares -GenericName[fi_FI]=Järjestelmän Asennusohjelma +GenericName[sq]=Instalues Sistemi Comment[fi_FI]=Calamares — Järjestelmän Asentaja +Icon[fi_FI]=calamares Name[fi_FI]=Asenna Järjestelmä +GenericName[fi_FI]=Järjestelmän Asennusohjelma Name[sr@latin]=Instaliraj sistem Name[sr]=Инсталирај систем -Icon[sv]=calamares -GenericName[sv]=Systeminstallerare Comment[sv]=Calamares — Systeminstallerare +Icon[sv]=calamares Name[sv]=Installera system +GenericName[sv]=Systeminstallerare Name[th]=ติดตั้งระบบ -GenericName[uk]=Встановлювач системи Comment[uk]=Calamares - Встановлювач системи Name[uk]=Встановити Систему -Icon[zh_CN]=calamares -GenericName[zh_CN]=系统安装程序 +GenericName[uk]=Встановлювач системи Comment[zh_CN]=Calamares — 系统安装程序 +Icon[zh_CN]=calamares Name[zh_CN]=安装系统 -Icon[zh_TW]=calamares -GenericName[zh_TW]=系統安裝程式 +GenericName[zh_CN]=系统安装程序 Comment[zh_TW]=Calamares ── 系統安裝程式 +Icon[zh_TW]=calamares Name[zh_TW]=安裝系統 -Icon[ast]=calamares -GenericName[ast]=Instalador del sistema +GenericName[zh_TW]=系統安裝程式 Comment[ast]=Calamares — Instalador del sistema +Icon[ast]=calamares Name[ast]=Instalar sistema -Icon[eo]=calamares -GenericName[eo]=Sistema Instalilo +GenericName[ast]=Instalador del sistema Comment[eo]=Calamares — Sistema Instalilo +Icon[eo]=calamares Name[eo]=Instali Sistemo -Icon[es_MX]=calamares -GenericName[es_MX]=Instalador del sistema +GenericName[eo]=Sistema Instalilo Comment[es_MX]=Calamares - Instalador del sistema +Icon[es_MX]=calamares Name[es_MX]=Instalar el Sistema -Icon[pt_PT]=calamares -GenericName[pt_PT]=Instalador de Sistema +GenericName[es_MX]=Instalador del sistema Comment[pt_PT]=Calamares - Instalador de Sistema +Icon[pt_PT]=calamares Name[pt_PT]=Instalar Sistema -Icon[tr_TR]=calamares -GenericName[tr_TR]=Sistem Yükleyici +GenericName[pt_PT]=Instalador de Sistema Comment[tr_TR]=Calamares — Sistem Yükleyici +Icon[tr_TR]=calamares Name[tr_TR]=Sistemi Yükle +GenericName[tr_TR]=Sistem Yükleyici diff --git a/ci/travis-continuous.sh b/ci/travis-continuous.sh index eccb6743e..1b3841e54 100755 --- a/ci/travis-continuous.sh +++ b/ci/travis-continuous.sh @@ -51,4 +51,4 @@ df -h echo "# Install results" install_debugging "$DESTDIR" -$result # Result of make install, above +$result || { echo "! Install failed" ; exit 1 ; } # Result of make install, above diff --git a/lang/calamares_da.ts b/lang/calamares_da.ts index 7cfef1875..c69150583 100644 --- a/lang/calamares_da.ts +++ b/lang/calamares_da.ts @@ -1693,7 +1693,7 @@ Installationsprogrammet vil stoppe og alle ændringer vil gå tabt. The partition table on %1 already has %2 primary partitions, and no more can be added. Please remove one primary partition and add an extended partition, instead. - Partitionstabellen på %1 har allerede %2 primære partitioner, og der kan ikke tilføjes flere. Fjern venligst en primær partition og tilføj i stedet en udviddet partition. + Partitionstabellen på %1 har allerede %2 primære partitioner, og der kan ikke tilføjes flere. Fjern venligst en primær partition og tilføj i stedet en udvidet partition. diff --git a/lang/calamares_fr.ts b/lang/calamares_fr.ts index 572a4b1df..26062f604 100644 --- a/lang/calamares_fr.ts +++ b/lang/calamares_fr.ts @@ -4,7 +4,7 @@ The <strong>boot environment</strong> of this system.<br><br>Older x86 systems only support <strong>BIOS</strong>.<br>Modern systems usually use <strong>EFI</strong>, but may also show up as BIOS if started in compatibility mode. - L'<strong>environnement de démarrage</strong> de ce système.<br><br>Les anciens systèmes x86 supportent uniquement le <strong>BIOS</strong>.<br>Les systèmes récents utilisent habituellement <strong>EFI</strong>, mais peuvent également afficher BIOS s'ils sont démarrés en mode de compatibilité. + L'<strong>environnement de démarrage</strong> de ce système.<br><br>Les anciens systèmes x86 supportent uniquement <strong>BIOS</strong>.<br>Les systèmes récents utilisent habituellement <strong>EFI</strong>, mais peuvent également afficher BIOS s'ils sont démarrés en mode de compatibilité. diff --git a/lang/calamares_he.ts b/lang/calamares_he.ts index fed3a48f4..cdceb60de 100644 --- a/lang/calamares_he.ts +++ b/lang/calamares_he.ts @@ -521,7 +521,7 @@ The installer will quit and all changes will be lost. Contextual Processes Job - + משימת תהליכי הקשר @@ -2358,7 +2358,7 @@ Output: Shell Processes Job - + משימת תהליכי מעטפת diff --git a/lang/calamares_hu.ts b/lang/calamares_hu.ts index 20ffbb815..75f3457b8 100644 --- a/lang/calamares_hu.ts +++ b/lang/calamares_hu.ts @@ -207,7 +207,7 @@ &Install - + &Telepítés @@ -229,7 +229,7 @@ Minden változtatás elveszik, ha kilépsz a telepítőből. &No - @Nem + &Nem @@ -944,7 +944,7 @@ Telepítés nem folytatható. <a href="#details">Részletek...&l &Restart now - $Újraindítás most + Új&raindítás most diff --git a/lang/calamares_lt.ts b/lang/calamares_lt.ts index 12d53ba11..33191566b 100644 --- a/lang/calamares_lt.ts +++ b/lang/calamares_lt.ts @@ -244,7 +244,7 @@ Diegimo programa užbaigs darbą ir visi pakeitimai bus prarasti. The %1 installer is about to make changes to your disk in order to install %2.<br/><strong>You will not be able to undo these changes.</strong> - %1 diegimo programa, siekdama įdiegti %2, ketina atlikti pakeitimus diske.<br/><strong>Šių pakeitimų atšaukti nebegalėsite.</strong> + %1 diegimo programa, siekdama įdiegti %2, ketina atlikti pakeitimus diske.<br/><strong>Šių pakeitimų nebegalėsite atšaukti.</strong> diff --git a/lang/calamares_nl.ts b/lang/calamares_nl.ts index d20d75630..d1c1a62cc 100644 --- a/lang/calamares_nl.ts +++ b/lang/calamares_nl.ts @@ -50,7 +50,7 @@ Blank Page - + Lege pagina @@ -192,7 +192,7 @@ Calamares Initialization Failed - + Calamares Initialisatie mislukt @@ -202,7 +202,7 @@ <br/>The following modules could not be loaded: - + <br/>The volgende modules konden niet worden geladen: @@ -1244,7 +1244,7 @@ Het installatieprogramma zal afsluiten en alle wijzigingen zullen verloren gaan. Password is too weak - + Wachtwoord is te zwak @@ -1259,12 +1259,12 @@ Het installatieprogramma zal afsluiten en alle wijzigingen zullen verloren gaan. The password is the same as the old one - + Het wachtwoord is hetzelfde als het oude wachtwoord The password is a palindrome - + Het wachtwoord is een palindroom @@ -1274,12 +1274,12 @@ Het installatieprogramma zal afsluiten en alle wijzigingen zullen verloren gaan. The password is too similar to the old one - + Het wachtwoord lijkt te veel op het oude wachtwoord The password contains the user name in some form - + Het wachtwoord bevat de gebruikersnaam op een of andere manier diff --git a/lang/calamares_zh_CN.ts b/lang/calamares_zh_CN.ts index 4889eabf1..b69c5f63c 100644 --- a/lang/calamares_zh_CN.ts +++ b/lang/calamares_zh_CN.ts @@ -9,7 +9,7 @@ This system was started with an <strong>EFI</strong> boot environment.<br><br>To configure startup from an EFI environment, this installer must deploy a boot loader application, like <strong>GRUB</strong> or <strong>systemd-boot</strong> on an <strong>EFI System Partition</strong>. This is automatic, unless you choose manual partitioning, in which case you must choose it or create it on your own. - 这个系统从 <strong>EFI</strong> 引导环境启动。<br><br>目前市面上大多数的民用设备都使用 EFI,并同时与之使用 GPT 分区表。<br>要从 EFI 环境引导的话,本安装程序必须部署一个引导器(如 <strong>GRUB</strong> 或 <strong>systemd-boot</strong>)到 <strong>EFI 系统分区</strong>。这个步骤是自动的,除非您选择手动分区——此时您必须自行选择或创建。 + 这个系统是从 <strong>EFI</strong> 引导环境启动的。<br><br>目前市面上大多数的民用设备都使用 EFI,并同时对硬盘使用 GPT 分区表分区。<br>您如果要从 EFI 环境引导这个系统的话,本安装程序必须安装一个引导器(如 <strong>GRUB</strong> 或 <strong>systemd-boot</strong>)到 <strong>EFI 分区</strong>。这个步骤将会由本安装程序自动执行,除非您选择自己创建分区——此时您必须选择让本安装程序自动创建EFI分区或您自己手动创建EFI分区。 @@ -51,7 +51,7 @@ Blank Page - + 空白页 @@ -193,17 +193,17 @@ Calamares Initialization Failed - + Calamares安装失败 %1 can not be installed. Calamares was unable to load all of the configured modules. This is a problem with the way Calamares is being used by the distribution. - + %1无法安装。 Calamares无法加载所有已配置的模块。这是分配使用Calamares的方式的问题。 <br/>The following modules could not be loaded: - + <br/>无法加载以下模块: @@ -265,7 +265,7 @@ The installer will quit and all changes will be lost. The installation is complete. Close the installer. - 安装过程已完毕。请关闭安装器。 + 安装已完成。请关闭安装程序。 @@ -334,7 +334,7 @@ The installer will quit and all changes will be lost. For best results, please ensure that this computer: - 为了更好的体验,请确定这台电脑: + 为了更好的体验,请确保这台电脑: @@ -509,12 +509,12 @@ The installer will quit and all changes will be lost. The command runs in the host environment and needs to know the root path, but no rootMountPoint is defined. - + 该命令在主机环境中运行,且需要知道根路径,但没有定义root挂载点。 The command needs to know the user's name, but no username is defined. - + 命令行需要知道用户的名字,但用户名没有被设置 @@ -1665,7 +1665,7 @@ The installer will quit and all changes will be lost. Cre&ate - + 创建 @@ -1690,12 +1690,12 @@ The installer will quit and all changes will be lost. Can not create new partition - + 无法创建新分区 The partition table on %1 already has %2 primary partitions, and no more can be added. Please remove one primary partition and add an extended partition, instead. - + %1上的分区表已经有%2个主分区,并且不能再添加。请删除一个主分区并添加扩展分区。 @@ -1841,17 +1841,17 @@ The installer will quit and all changes will be lost. Saving files for later ... - + 保存文件以供日后使用 No files configured to save for later. - + 没有已保存且供日后使用的配置文件。 Not all of the configured files could be preserved. - + 并不是所有配置文件都可以被保留 diff --git a/lang/python/hu/LC_MESSAGES/python.mo b/lang/python/hu/LC_MESSAGES/python.mo index 94e29ac49..4cb87d96a 100644 Binary files a/lang/python/hu/LC_MESSAGES/python.mo and b/lang/python/hu/LC_MESSAGES/python.mo differ diff --git a/lang/python/hu/LC_MESSAGES/python.po b/lang/python/hu/LC_MESSAGES/python.po index 286e27b3b..e2b46349c 100644 --- a/lang/python/hu/LC_MESSAGES/python.po +++ b/lang/python/hu/LC_MESSAGES/python.po @@ -10,7 +10,7 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-06-18 07:46-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: miku84, 2017\n" +"Last-Translator: Adriaan de Groot , 2018\n" "Language-Team: Hungarian (https://www.transifex.com/calamares/teams/20061/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,7 +20,7 @@ msgstr "" #: src/modules/umount/main.py:40 msgid "Unmount file systems." -msgstr "" +msgstr "Fájlrendszerek leválasztása." #: src/modules/dummypython/main.py:44 msgid "Dummy python job." @@ -47,12 +47,12 @@ msgstr "Csomagok telepítése." #, python-format msgid "Installing one package." msgid_plural "Installing %(num)d packages." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Egy csomag telepítése." +msgstr[1] "%(num)d csomag telepítése." #: src/modules/packages/main.py:70 #, python-format msgid "Removing one package." msgid_plural "Removing %(num)d packages." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Egy csomag eltávolítása." +msgstr[1] "%(num)d csomag eltávolítása." diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index 018e2b677..0b715d6df 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -145,6 +145,9 @@ qmlDirCandidates( bool assumeBuilddir ) { if ( assumeBuilddir ) qmlDirs << QDir::current().absoluteFilePath( "src/qml" ); // In build-dir + if ( CalamaresUtils::haveExtraDirs() ) + for ( auto s : CalamaresUtils::extraDataDirs() ) + qmlDirs << ( s + QML ); qmlDirs << CalamaresUtils::appDataDir().absoluteFilePath( QML ); } @@ -164,6 +167,9 @@ settingsFileCandidates( bool assumeBuilddir ) { if ( assumeBuilddir ) settingsPaths << QDir::current().absoluteFilePath( settings ); + if ( CalamaresUtils::haveExtraDirs() ) + for ( auto s : CalamaresUtils::extraConfigDirs() ) + settingsPaths << ( s + settings ); settingsPaths << CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/settings.conf"; // String concat settingsPaths << CalamaresUtils::appDataDir().absoluteFilePath( settings ); } @@ -182,6 +188,9 @@ brandingFileCandidates( bool assumeBuilddir, const QString& brandingFilename ) { if ( assumeBuilddir ) brandingPaths << ( QDir::currentPath() + QStringLiteral( "/src/" ) + brandingFilename ); + if ( CalamaresUtils::haveExtraDirs() ) + for ( auto s : CalamaresUtils::extraDataDirs() ) + brandingPaths << ( s + brandingFilename ); brandingPaths << QDir( CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/" ).absoluteFilePath( brandingFilename ); brandingPaths << CalamaresUtils::appDataDir().absoluteFilePath( brandingFilename); } diff --git a/src/calamares/main.cpp b/src/calamares/main.cpp index 9893e6792..f855b060f 100644 --- a/src/calamares/main.cpp +++ b/src/calamares/main.cpp @@ -44,6 +44,8 @@ handle_args( CalamaresApplication& a ) "Verbose output for debugging purposes (0-8).", "level" ); QCommandLineOption configOption( QStringList{ "c", "config"}, "Configuration directory to use, for testing purposes.", "config" ); + QCommandLineOption xdgOption( QStringList{"X", "xdg-config"}, + "Use XDG_{CONFIG,DATA}_DIRS as well." ); QCommandLineParser parser; parser.setApplicationDescription( "Distribution-independent installer framework" ); @@ -53,6 +55,7 @@ handle_args( CalamaresApplication& a ) parser.addOption( debugOption ); parser.addOption( debugLevelOption ); parser.addOption( configOption ); + parser.addOption( xdgOption ); parser.process( a ); @@ -72,6 +75,8 @@ handle_args( CalamaresApplication& a ) } if ( parser.isSet( configOption ) ) CalamaresUtils::setAppDataDir( QDir( parser.value( configOption ) ) ); + if ( parser.isSet( xdgOption ) ) + CalamaresUtils::setXdgDirs(); } int diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp index c22342f9a..a8b363209 100644 --- a/src/calamares/testmain.cpp +++ b/src/calamares/testmain.cpp @@ -59,6 +59,7 @@ handle_args( QCoreApplication& a ) parser.addOption( debugLevelOption ); parser.addPositionalArgument( "module", "Path or name of module to run." ); + parser.addPositionalArgument( "config", "Path of job-config file to use.", "[config]"); parser.process( a ); @@ -140,6 +141,8 @@ load_module( const ModuleConfig& moduleConfig ) ? moduleDirectory + '/' + name + ".conf" : moduleConfig.configFile() ); + cDebug() << "Module" << moduleName << "job-configuration:" << configFile; + Calamares::Module* module = Calamares::Module::fromDescriptor( descriptor, name, configFile, moduleDirectory ); @@ -158,7 +161,7 @@ main( int argc, char* argv[] ) std::unique_ptr< Calamares::Settings > settings_p( new Calamares::Settings( QString(), true ) ); std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); - cDebug() << "Calamares test module-loader" << module.moduleName(); + cDebug() << "Calamares module-loader testing" << module.moduleName(); Calamares::Module* m = load_module( module ); if ( !m ) { @@ -175,16 +178,27 @@ main( int argc, char* argv[] ) return 1; } - cDebug() << "Module" << m->name() << m->typeString() << m->interfaceString(); + using TR = Logger::DebugRow; + cDebug() << "Module metadata" + << TR( "name", m->name() ) + << TR( "type", m->typeString() ) + << TR( "interface", m->interfaceString() ); + + cDebug() << "Job outputs:"; Calamares::JobList jobList = m->jobs(); unsigned int count = 1; for ( const auto& p : jobList ) { - cDebug() << count << p->prettyName(); + cDebug() << "Job #" << count << "name" << p->prettyName(); Calamares::JobResult r = p->exec(); if ( !r ) - cDebug() << count << ".. failed" << r; + { + using TR = Logger::DebugRow; + cDebug() << count << ".. failed" + << TR( "summary", r.message() ) + << TR( "details", r.details() ); + } ++count; } diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 598d3c313..4bf78176e 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -105,6 +105,19 @@ install( TARGETS calamares ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) +if ( ECM_FOUND AND BUILD_TESTING ) + ecm_add_test( + Tests.cpp + TEST_NAME + libcalamarestest + LINK_LIBRARIES + calamares + Qt5::Core + Qt5::Test + ) + set_target_properties( libcalamarestest PROPERTIES AUTOMOC TRUE ) +endif() + # Make symlink lib/calamares/libcalamares.so to lib/libcalamares.so.VERSION so # lib/calamares can be used as module path for the Python interpreter. install( CODE " diff --git a/src/libcalamares/Tests.cpp b/src/libcalamares/Tests.cpp new file mode 100644 index 000000000..acf5b03d3 --- /dev/null +++ b/src/libcalamares/Tests.cpp @@ -0,0 +1,59 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Tests.h" + +#include "utils/Logger.h" + +#include + +QTEST_GUILESS_MAIN( LibCalamaresTests ) + +LibCalamaresTests::LibCalamaresTests() +{ +} + +LibCalamaresTests::~LibCalamaresTests() +{ +} + +void +LibCalamaresTests::initTestCase() +{ +} + +void +LibCalamaresTests::testDebugLevels() +{ + Logger::setupLogLevel( Logger::LOG_DISABLE ); + + QCOMPARE( Logger::logLevel(), static_cast( Logger::LOG_DISABLE ) ); + + for ( unsigned int level = 0; level <= Logger::LOGVERBOSE ; ++level ) + { + Logger::setupLogLevel( level ); + QCOMPARE( Logger::logLevel(), level ); + QVERIFY( Logger::logLevelEnabled( level ) ); + + for ( unsigned int xlevel = 0; xlevel <= Logger::LOGVERBOSE; ++xlevel ) + { + QCOMPARE( Logger::logLevelEnabled( xlevel ), xlevel <= level ); + } + } +} + diff --git a/src/libcalamares/Tests.h b/src/libcalamares/Tests.h new file mode 100644 index 000000000..123655c6e --- /dev/null +++ b/src/libcalamares/Tests.h @@ -0,0 +1,36 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef TESTS_H +#define TESTS_H + +#include + +class LibCalamaresTests : public QObject +{ + Q_OBJECT +public: + LibCalamaresTests(); + ~LibCalamaresTests() override; + +private Q_SLOTS: + void initTestCase(); + void testDebugLevels(); +}; + +#endif diff --git a/src/libcalamares/utils/CalamaresUtils.cpp b/src/libcalamares/utils/CalamaresUtils.cpp index 6a892511a..3ab758522 100644 --- a/src/libcalamares/utils/CalamaresUtils.cpp +++ b/src/libcalamares/utils/CalamaresUtils.cpp @@ -49,6 +49,9 @@ static QTranslator* s_brandingTranslator = nullptr; static QTranslator* s_translator = nullptr; static QString s_translatorLocaleName; +static bool s_haveExtraDirs = false; +static QStringList s_extraConfigDirs; +static QStringList s_extraDataDirs; static bool isWritableDir( const QDir& dir ) @@ -94,6 +97,46 @@ setAppDataDir( const QDir& dir ) s_isAppDataDirOverridden = true; } +/* Split $ENV{@p name} on :, append to @p l, making sure each ends in / */ +static void +mungeEnvironment( QStringList& l, const char *name ) +{ + for ( auto s : QString( qgetenv( name ) ).split(':') ) + if ( s.endsWith( '/' ) ) + l << s; + else + l << ( s + '/' ); +} + +void +setXdgDirs() +{ + s_haveExtraDirs = true; + mungeEnvironment( s_extraConfigDirs, "XDG_CONFIG_DIRS" ); + mungeEnvironment( s_extraDataDirs, "XDG_DATA_DIRS" ); +} + +QStringList +extraConfigDirs() +{ + if ( s_haveExtraDirs ) + return s_extraConfigDirs; + return QStringList(); +} + +QStringList +extraDataDirs() +{ + if ( s_haveExtraDirs ) + return s_extraDataDirs; + return QStringList(); +} + +bool +haveExtraDirs() +{ + return s_haveExtraDirs && ( !s_extraConfigDirs.isEmpty() || !s_extraDataDirs.isEmpty() ); +} bool isAppDataDirOverridden() diff --git a/src/libcalamares/utils/CalamaresUtils.h b/src/libcalamares/utils/CalamaresUtils.h index e64fe4eec..baf7a12dc 100644 --- a/src/libcalamares/utils/CalamaresUtils.h +++ b/src/libcalamares/utils/CalamaresUtils.h @@ -79,6 +79,16 @@ namespace CalamaresUtils DLLEXPORT void setQmlModulesDir( const QDir& dir ); + /** @brief Setup extra config and data dirs from the XDG variables. + */ + DLLEXPORT void setXdgDirs(); + /** @brief Are any extra directories configured? */ + DLLEXPORT bool haveExtraDirs(); + /** @brief XDG_CONFIG_DIRS, each guaranteed to end with / */ + DLLEXPORT QStringList extraConfigDirs(); + /** @brief XDG_DATA_DIRS, each guaranteed to end with / */ + DLLEXPORT QStringList extraDataDirs(); + /** * @brief removeDiacritics replaces letters with diacritics and ligatures with * alternative forms and digraphs. diff --git a/src/libcalamares/utils/Logger.cpp b/src/libcalamares/utils/Logger.cpp index 735414b85..98aa2121f 100644 --- a/src/libcalamares/utils/Logger.cpp +++ b/src/libcalamares/utils/Logger.cpp @@ -55,6 +55,18 @@ setupLogLevel(unsigned int level) s_threshold = level + 1; // Comparison is < in log() function } +bool +logLevelEnabled(unsigned int level) +{ + return level < s_threshold; +} + +unsigned int +logLevel() +{ + return s_threshold > 0 ? s_threshold - 1 : 0; +} + static void log( const char* msg, unsigned int debugLevel, bool toDisk = true ) { diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index dba386eae..0cb4b494f 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -89,6 +89,12 @@ namespace Logger */ DLLEXPORT void setupLogLevel( unsigned int level ); + /** @brief Return the configured log-level. */ + DLLEXPORT unsigned int logLevel(); + + /** @brief Would the given @p level really be logged? */ + DLLEXPORT bool logLevelEnabled( unsigned int level ); + /** * @brief Row-oriented formatted logging. * diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index 8d92c37ad..ef629ac4d 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -145,8 +145,19 @@ moduleConfigurationCandidates( bool assumeBuildDir, const QString& moduleName, c paths << CalamaresUtils::appDataDir().absoluteFilePath( QString( "modules/%1" ).arg( configFileName ) ); else { + // If an absolute path is given, in debug mode, look for it + // first. The case contains('/'), below, will add the absolute + // path a second time, though. + if ( assumeBuildDir && configFileName.startsWith( '/' ) ) + paths << configFileName; if ( assumeBuildDir ) paths << QDir().absoluteFilePath(QString( "src/modules/%1/%2" ).arg( moduleName ).arg( configFileName ) ); + if ( assumeBuildDir && configFileName.contains( '/' ) ) + paths << QDir().absoluteFilePath( configFileName ); + + if ( CalamaresUtils::haveExtraDirs() ) + for ( auto s : CalamaresUtils::extraConfigDirs() ) + paths << ( s + QString( "modules/%1" ).arg( configFileName ) ); paths << QString( "/etc/calamares/modules/%1" ).arg( configFileName ); paths << CalamaresUtils::appDataDir().absoluteFilePath( QString( "modules/%1" ).arg( configFileName ) ); @@ -168,6 +179,7 @@ Module::loadConfigurationFile( const QString& configFileName ) //throws YAML::Ex YAML::Node doc = YAML::Load( ba.constData() ); if ( doc.IsNull() ) { + cDebug() << "Found empty module configuration" << path; // Special case: empty config files are valid, // but aren't a map. return; @@ -178,14 +190,13 @@ Module::loadConfigurationFile( const QString& configFileName ) //throws YAML::Ex return; } + cDebug() << "Loaded module configuration" << path; m_configurationMap = CalamaresUtils::yamlMapToVariant( doc ).toMap(); m_emergency = m_maybe_emergency && m_configurationMap.contains( EMERGENCY ) && m_configurationMap[ EMERGENCY ].toBool(); return; } - else - continue; } } diff --git a/src/modules/contextualprocess/CMakeLists.txt b/src/modules/contextualprocess/CMakeLists.txt index 2cf8d3879..f75946b58 100644 --- a/src/modules/contextualprocess/CMakeLists.txt +++ b/src/modules/contextualprocess/CMakeLists.txt @@ -8,10 +8,7 @@ calamares_add_plugin( contextualprocess SHARED_LIB ) -if( ECM_FOUND ) - find_package( Qt5 COMPONENTS Test REQUIRED ) - include( ECMAddTests ) - +if( ECM_FOUND AND BUILD_TESTING ) ecm_add_test( Tests.cpp ContextualProcessJob.cpp # Builds it a second time diff --git a/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.mo b/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.mo index 0dac94ca0..240d5c4db 100644 Binary files a/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.mo and b/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.mo differ diff --git a/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.po b/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.po index 14bf4463a..564bcdabc 100644 --- a/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.po +++ b/src/modules/dummypythonqt/lang/zh_CN/LC_MESSAGES/dummypythonqt.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-04 08:16-0400\n" +"POT-Creation-Date: 2018-06-18 07:46-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Mingcong Bai , 2017\n" +"Last-Translator: soenggam , 2017\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" @@ -24,7 +24,7 @@ msgstr "按我按我!" #: src/modules/dummypythonqt/main.py:94 msgid "A new QLabel." -msgstr "一个平淡无奇的 QLabel。" +msgstr "一个新的QLabel。" #: src/modules/dummypythonqt/main.py:97 msgid "Dummy PythonQt ViewStep" diff --git a/src/modules/fsresizer/CMakeLists.txt b/src/modules/fsresizer/CMakeLists.txt new file mode 100644 index 000000000..e339b2799 --- /dev/null +++ b/src/modules/fsresizer/CMakeLists.txt @@ -0,0 +1,41 @@ +find_package( KPMcore 3.3 ) +find_package( Qt5 REQUIRED DBus ) # Needed for KPMCore +find_package( KF5 REQUIRED I18n WidgetsAddons ) # Needed for KPMCore + +if ( KPMcore_FOUND ) + include_directories( ${KPMCORE_INCLUDE_DIR} ) + include_directories( ${PROJECT_BINARY_DIR}/src/libcalamares ) + + # The PartitionIterator is a small class, and it's easiest -- but also a + # gross hack -- to just compile it again from the partition module tree. + calamares_add_plugin( fsresizer + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + ResizeFSJob.cpp + ${PROJECT_SOURCE_DIR}/src/modules/partition/core/PartitionIterator.cpp + LINK_PRIVATE_LIBRARIES + kpmcore + calamares + SHARED_LIB + ) + + if( ECM_FOUND AND BUILD_TESTING ) + ecm_add_test( + Tests.cpp + TEST_NAME + fsresizertest + LINK_LIBRARIES + ${CALAMARES_LIBRARIES} + calamares + calamares_job_fsresizer # From above + ${YAMLCPP_LIBRARY} + Qt5::Core + Qt5::Test + ) + set_target_properties( fsresizertest PROPERTIES AUTOMOC TRUE ) + target_include_directories(fsresizertest PRIVATE /usr/local/include ) + endif() +else() + calamares_skip_module( "fsresizer (missing suitable KPMcore)" ) +endif() diff --git a/src/modules/fsresizer/ResizeFSJob.cpp b/src/modules/fsresizer/ResizeFSJob.cpp new file mode 100644 index 000000000..4df41a7d4 --- /dev/null +++ b/src/modules/fsresizer/ResizeFSJob.cpp @@ -0,0 +1,347 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "ResizeFSJob.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "CalamaresVersion.h" +#include "JobQueue.h" +#include "GlobalStorage.h" + +#include "utils/CalamaresUtils.h" +#include "utils/Logger.h" +#include "utils/Units.h" + +#include "modules/partition/core/PartitionIterator.h" + +ResizeFSJob::RelativeSize::RelativeSize() + : m_value( 0 ) + , m_unit( None ) +{ +} + + +template +void matchUnitSuffix( + const QString& s, + const char ( &suffix )[N], + ResizeFSJob::RelativeSize::Unit matchedUnit, + int& value, + ResizeFSJob::RelativeSize::Unit& unit +) +{ + if ( s.endsWith( suffix ) ) + { + value = s.left( s.length() - N + 1 ).toInt(); + unit = matchedUnit; + } +} + + +ResizeFSJob::RelativeSize::RelativeSize( const QString& s ) + : m_value( 0 ) + , m_unit( None ) +{ + matchUnitSuffix( s, "%", Percent, m_value, m_unit ); + matchUnitSuffix( s, "MiB", Absolute, m_value, m_unit ); + + if ( ( unit() == Percent ) && ( value() > 100 ) ) + { + cDebug() << "Percent value" << value() << "is not valid."; + m_value = 0; + m_unit = None; + } + + if ( !m_value ) + m_unit = None; +} + +qint64 +ResizeFSJob::RelativeSize::apply( qint64 totalSectors, qint64 sectorSize ) +{ + if ( !isValid() ) + return -1; + if ( sectorSize < 1 ) + return -1; + + switch ( m_unit ) + { + case None: + return -1; + case Absolute: + return CalamaresUtils::MiBtoBytes( value() ) / sectorSize; + case Percent: + if ( value() == 100 ) + return totalSectors; // Common-case, avoid futzing around + else + return totalSectors * value() / 100; + } + + // notreached + return -1; +} + +qint64 +ResizeFSJob::RelativeSize::apply( Device* d ) +{ + return apply( d->totalLogical(), d->logicalSize() ); +} + +ResizeFSJob::ResizeFSJob( QObject* parent ) + : Calamares::CppJob( parent ) + , m_required( false ) +{ +} + + +ResizeFSJob::~ResizeFSJob() +{ +} + + +QString +ResizeFSJob::prettyName() const +{ + return tr( "Resize Filesystem Job" ); +} + +ResizeFSJob::PartitionMatch +ResizeFSJob::findPartition( CoreBackend* backend ) +{ + using DeviceList = QList< Device* >; + DeviceList devices = backend->scanDevices( false ); + cDebug() << "ResizeFSJob found" << devices.count() << "devices."; + for ( DeviceList::iterator dev_it = devices.begin(); dev_it != devices.end(); ++dev_it ) + { + if ( ! ( *dev_it ) ) + continue; + cDebug() << "ResizeFSJob found" << ( *dev_it )->deviceNode(); + for ( auto part_it = PartitionIterator::begin( *dev_it ); part_it != PartitionIterator::end( *dev_it ); ++part_it ) + { + cDebug() << ".." << ( *part_it )->mountPoint() << "on" << ( *part_it )->deviceNode(); + if ( ( !m_fsname.isEmpty() && ( *part_it )->mountPoint() == m_fsname ) || + ( !m_devicename.isEmpty() && ( *part_it )->deviceNode() == m_devicename ) ) + { + cDebug() << ".. matched configuration dev=" << m_devicename << "fs=" << m_fsname; + return PartitionMatch( *dev_it, *part_it ); + } + } + } + + cDebug() << "No match for configuration dev=" << m_devicename << "fs=" << m_fsname; + return PartitionMatch( nullptr, nullptr ); +} + +/** @brief Returns the last sector the matched partition should occupy. + * + * Returns a sector number. Returns -1 if something is wrong (e.g. + * can't resize at all, or missing data). Returns 0 if the resize + * won't fit because it doesn't satisfy the settings for atleast + * and size (or won't grow at all because the partition is blocked + * by occupied space after it). + */ +qint64 +ResizeFSJob::findGrownEnd( ResizeFSJob::PartitionMatch m ) +{ + if ( !m.first || !m.second ) + return -1; // Missing device data + if ( !ResizeOperation::canGrow( m.second ) ) + return -1; // Operation is doomed + if ( !m_size.isValid() ) + return -1; // Must have a grow-size + + cDebug() << "Containing device size" << m.first->totalLogical(); + qint64 last_available = m.first->totalLogical() - 1; // Numbered from 0 + qint64 last_currently = m.second->lastSector(); + cDebug() << "Growing partition" << m.second->firstSector() << '-' << last_currently; + + for ( auto part_it = PartitionIterator::begin( m.first ); part_it != PartitionIterator::end( m.first ); ++part_it ) + { + qint64 next_start = ( *part_it )->firstSector(); + qint64 next_end = ( *part_it )->lastSector(); + if ( next_start > next_end ) + { + cWarning() << "Corrupt partition has end" << next_end << " < start" << next_start; + std::swap( next_start, next_end ); + } + if ( ( *part_it )->roles().has( PartitionRole::Unallocated ) ) + { + cDebug() << ".. ignoring unallocated" << next_start << '-' << next_end; + continue; + } + cDebug() << ".. comparing" << next_start << '-' << next_end; + if ( ( next_start > last_currently ) && ( next_start < last_available ) ) + { + cDebug() << " .. shrunk last available to" << next_start; + last_available = next_start - 1; // Before that one starts + } + } + + if ( !( last_available > last_currently ) ) + { + cDebug() << "Partition cannot grow larger."; + return 0; + } + + qint64 expand = last_available - last_currently; // number of sectors + if ( m_atleast.isValid() ) + { + qint64 required = m_atleast.apply( m.first ); + if ( expand < required ) + { + cDebug() << ".. need to expand by" << required << "but only" << expand << "is available."; + return 0; + } + } + + qint64 wanted = m_size.apply( expand, m.first->logicalSize() ); + if ( wanted < expand ) + { + cDebug() << ".. only growing by" << wanted << "instead of full" << expand; + last_available -= ( expand - wanted ); + } + + return last_available; +} + + +Calamares::JobResult +ResizeFSJob::exec() +{ + if ( !isValid() ) + return Calamares::JobResult::error( + tr( "Invalid configuration" ), + tr( "The file-system resize job has an invalid configuration and will not run." ) ); + + // Get KPMCore + auto backend_p = CoreBackendManager::self()->backend(); + if ( backend_p ) + cDebug() << "KPMCore backend @" << ( void* )backend_p << backend_p->id() << backend_p->version(); + else + { + cDebug() << "No KPMCore backend loaded yet"; + QByteArray backendName = qgetenv( "KPMCORE_BACKEND" ); + if ( !CoreBackendManager::self()->load( backendName.isEmpty() ? CoreBackendManager::defaultBackendName() : backendName ) ) + { + cWarning() << "Could not load KPMCore backend."; + return Calamares::JobResult::error( + tr( "KPMCore not Available" ), + tr( "Calamares cannot start KPMCore for the file-system resize job." ) ); + } + + backend_p = CoreBackendManager::self()->backend(); + } + if ( !backend_p ) + { + cWarning() << "Could not load KPMCore backend (2)."; + return Calamares::JobResult::error( + tr( "KPMCore not Available" ), + tr( "Calamares cannot start KPMCore for the file-system resize job." ) ); + } + backend_p->initFSSupport(); // Might not be enough, see below + + // Now get the partition and FS we want to work on + PartitionMatch m = findPartition( backend_p ); + if ( !m.first || !m.second ) + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 could not be found in this system, and cannot be resized." ).arg( m_fsname ) + : tr( "The device %1 could not be found in this system, and cannot be resized." ).arg( m_devicename ) ); + + m.second->fileSystem().init(); // Initialize support for specific FS + if ( !ResizeOperation::canGrow( m.second ) ) + { + cDebug() << "canGrow() returned false."; + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 cannot be resized." ).arg( m_fsname ) + : tr( "The device %1 cannot be resized." ).arg( m_devicename ) ); + } + + qint64 new_end = findGrownEnd( m ); + cDebug() << "Resize from" + << m.second->firstSector() << '-' << m.second->lastSector() + << '(' << m.second->length() << ')' + << "to -" << new_end; + + if ( new_end < 0 ) + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 cannot be resized." ).arg( m_fsname ) + : tr( "The device %1 cannot be resized." ).arg( m_devicename ) ); + if ( new_end == 0 ) + { + cWarning() << "Resize operation on" << m_fsname << m_devicename + << "skipped as not-useful."; + if ( m_required ) + return Calamares::JobResult::error( + tr( "Resize Failed" ), + !m_fsname.isEmpty() ? tr( "The filesystem %1 must be resized, but cannot." ).arg( m_fsname ) + : tr( "The device %1 must be resized, but cannot" ).arg( m_fsname ) ); + + return Calamares::JobResult::ok(); + } + + if ( ( new_end > 0 ) && ( new_end > m.second->lastSector() ) ) + { + ResizeOperation op( *m.first, *m.second, m.second->firstSector(), new_end ); + Report op_report( nullptr ); + if ( op.execute( op_report ) ) + cDebug() << "Resize operation OK."; + else + { + cDebug() << "Resize failed." << op_report.output(); + return Calamares::JobResult::error( + tr( "Resize Failed" ), + op_report.toText() ); + } + } + + return Calamares::JobResult::ok(); +} + + +void +ResizeFSJob::setConfigurationMap( const QVariantMap& configurationMap ) +{ + m_fsname = configurationMap["fs"].toString(); + m_devicename = configurationMap["dev"].toString(); + + if ( m_fsname.isEmpty() && m_devicename.isEmpty() ) + { + cWarning() << "No fs or dev configured for resize."; + return; + } + + m_size = RelativeSize( configurationMap["size"].toString() ); + m_atleast = RelativeSize( configurationMap["atleast"].toString() ); + + m_required = CalamaresUtils::getBool( configurationMap, "required", false ); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( ResizeFSJobFactory, registerPlugin(); ) diff --git a/src/modules/fsresizer/ResizeFSJob.h b/src/modules/fsresizer/ResizeFSJob.h new file mode 100644 index 000000000..c34ccb865 --- /dev/null +++ b/src/modules/fsresizer/ResizeFSJob.h @@ -0,0 +1,122 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef RESIZEFSJOB_H +#define RESIZEFSJOB_H + +#include +#include + +#include + +#include + +#include + +class CoreBackend; // From KPMCore +class Device; // From KPMCore +class Partition; + +class PLUGINDLLEXPORT ResizeFSJob : public Calamares::CppJob +{ + Q_OBJECT + +public: + /** @brief Size expressions + * + * Sizes can be specified in MiB or percent (of the device they + * are on). This class handles parsing of such strings from the + * config file. + */ + class RelativeSize + { + public: + RelativeSize(); + RelativeSize( const QString& ); + + enum Unit + { + None, + Percent, + Absolute + }; + + int value() const { return m_value; } + Unit unit() const { return m_unit; } + + bool isValid() const + { + return ( unit() != None ) && ( value() > 0 ); + } + + /** @brief Apply this size to the number of sectors @p totalSectors . + * + * Each sector has size @p sectorSize , for converting absolute + * sizes in MiB to sector counts. + * + * For invalid sizes, returns -1. + * For absolute sizes, returns the number of sectors needed. + * For percent sizes, returns that percent of the number of sectors. + */ + qint64 apply( qint64 totalSectors, qint64 sectorSize ); + + /** @brief Apply this size to the given device. + * + * Equivalent to apply( d->totalLogical(), d->logicalSize() ) + */ + qint64 apply( Device* d ); + + private: + int m_value; + Unit m_unit; + } ; + + explicit ResizeFSJob( QObject* parent = nullptr ); + virtual ~ResizeFSJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + + /** @brief Is the configuration of this job valid? */ + bool isValid() const + { + return ( !m_fsname.isEmpty() || !m_devicename.isEmpty() ) && + m_size.isValid(); + } + +private: + RelativeSize m_size; + RelativeSize m_atleast; + QString m_fsname; // Either this, or devicename, is set, not both + QString m_devicename; + bool m_required; + + using PartitionMatch = QPair; + /** @brief Find the configured FS using KPMCore @p backend */ + PartitionMatch findPartition( CoreBackend* backend ); + + /** @brief Return a new end-sector for the given dev-part pair. */ + qint64 findGrownEnd( PartitionMatch ); +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( ResizeFSJobFactory ) + +#endif // RESIZEFSJOB_H diff --git a/src/modules/fsresizer/Tests.cpp b/src/modules/fsresizer/Tests.cpp new file mode 100644 index 000000000..255153fa2 --- /dev/null +++ b/src/modules/fsresizer/Tests.cpp @@ -0,0 +1,126 @@ +/* === This file is part of Calamares - === + * + * Copyright 2017-2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "Tests.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "Settings.h" + +#include "utils/Logger.h" +#include "utils/YamlUtils.h" + +#include + +#include + +#include +#include + +#define private public +#include "ResizeFSJob.h" +#undef private + +QTEST_GUILESS_MAIN( FSResizerTests ) + +FSResizerTests::FSResizerTests() +{ +} + +FSResizerTests::~FSResizerTests() +{ +} + +void +FSResizerTests::initTestCase() +{ +} + +void FSResizerTests::testConfigurationRobust() +{ + ResizeFSJob j; + + // Empty config + j.setConfigurationMap( QVariantMap() ); + QVERIFY( j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + + // Config is missing fs and dev, so it isn't valid + YAML::Node doc0 = YAML::Load( R"(--- +size: 100% +atleast: 600MiB +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_size.value(), 0 ); + QCOMPARE( j.m_atleast.value(), 0 ); +} + +void FSResizerTests::testConfigurationValues() +{ + ResizeFSJob j; + + // Check both + YAML::Node doc0 = YAML::Load( R"(--- +fs: / +size: 100% +atleast: 600MiB +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Percent ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_size.value(), 100 ); + QCOMPARE( j.m_atleast.value(), 600 ); + + // Silly config + doc0 = YAML::Load( R"(--- +fs: / +dev: /dev/m00 +size: 72 MiB +atleast: 127 % +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( !j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::Percent ); + QCOMPARE( j.m_size.value(), 72 ); + QCOMPARE( j.m_atleast.value(), 127 ); + + // Silly config + doc0 = YAML::Load( R"(--- +fs: / +# dev: /dev/m00 +size: 71MiB +# atleast: 127% +)" ); + j.setConfigurationMap( CalamaresUtils::yamlMapToVariant( doc0 ).toMap() ); + QVERIFY( !j.m_fsname.isEmpty() ); + QVERIFY( j.m_devicename.isEmpty() ); + QCOMPARE( j.m_size.unit(), ResizeFSJob::RelativeSize::Absolute ); + QCOMPARE( j.m_atleast.unit(), ResizeFSJob::RelativeSize::None ); + QCOMPARE( j.m_size.value(), 71 ); + QCOMPARE( j.m_atleast.value(), 0 ); +} diff --git a/src/modules/fsresizer/Tests.h b/src/modules/fsresizer/Tests.h new file mode 100644 index 000000000..958c0e655 --- /dev/null +++ b/src/modules/fsresizer/Tests.h @@ -0,0 +1,39 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef TESTS_H +#define TESTS_H + +#include + +class FSResizerTests : public QObject +{ + Q_OBJECT +public: + FSResizerTests(); + ~FSResizerTests() override; + +private Q_SLOTS: + void initTestCase(); + // Can handle missing values + void testConfigurationRobust(); + // Can parse % and MiB values + void testConfigurationValues(); +}; + +#endif diff --git a/src/modules/fsresizer/fsresizer.conf b/src/modules/fsresizer/fsresizer.conf new file mode 100644 index 000000000..33329248d --- /dev/null +++ b/src/modules/fsresizer/fsresizer.conf @@ -0,0 +1,49 @@ +# Module that resizes a single FS to fill the entire (rest) of +# a device. This is used in OEM situations where an image is +# flashed onto an SD card (or similar) and used to boot a device, +# after which the FS should expand to fill the SD card. +# +# Example: a distro produces a 6GiB large image that is +# written to an 8GiB SD card; the FS should expand to take +# advantage of the unused 2GiB. The FS should expand much +# more if the same image is written to a 16GiB card. +--- + +# Which FS needs to be grown? Choose one way to identify it: +# - *fs* names a mount point which should already be mounted +# in the system. +# - *dev* names a device +fs: / +# dev: /dev/mmcblk0p1 + +# How much of the total remaining space should the FS use? +# The only sensible amount is "all of it". The value is +# in percent, so set it to 100. Perhaps a fixed size is +# needed (that would be weird though, since you don't know +# how big the card is), use MiB as suffix in that case. +# If missing, then it's assumed to be 0, and no resizing +# will happen. +# +# Percentages apply to **available space**. +size: 100% + +# Resizing might not be worth it, though. Set the minimum +# that it must grow; if it cannot grow that much, the +# resizing is skipped. Can be in percentage or absolute +# size, as above. If missing, then it's assumed to be 0, +# which means resizing is always worthwhile. +# +# If *atleast* is not zero, then the setting *required*, +# below, becomes relevant. +# +# Percentages apply to **total device size**. +#atleast: 1000MiB + +# When *atleast* is not zero, then the resize may be +# recommended (the default) or **required**. If the +# resize is required and cannot be carried out (because +# there's not enough space), then that is a fatal +# error for the installer. By default, resize is only +# recommended and it is not an error for no resize to be +# carried out. +required: false diff --git a/src/modules/keyboard/SetKeyboardLayoutJob.cpp b/src/modules/keyboard/SetKeyboardLayoutJob.cpp index 02d807045..75c52bb51 100644 --- a/src/modules/keyboard/SetKeyboardLayoutJob.cpp +++ b/src/modules/keyboard/SetKeyboardLayoutJob.cpp @@ -65,6 +65,8 @@ SetKeyboardLayoutJob::prettyName() const QString SetKeyboardLayoutJob::findConvertedKeymap( const QString& convertedKeymapPath ) const { + cDebug() << "Looking for converted keymap in" << convertedKeymapPath; + // No search path supplied, assume the distribution does not provide // converted keymaps if ( convertedKeymapPath.isEmpty() ) @@ -76,8 +78,7 @@ SetKeyboardLayoutJob::findConvertedKeymap( const QString& convertedKeymapPath ) if ( convertedKeymapDir.exists( name + ".map" ) || convertedKeymapDir.exists( name + ".map.gz" ) ) { - cDebug() << "Found converted keymap" << name; - + cDebug() << ".. Found converted keymap" << name; return name; } @@ -88,6 +89,8 @@ SetKeyboardLayoutJob::findConvertedKeymap( const QString& convertedKeymapPath ) QString SetKeyboardLayoutJob::findLegacyKeymap() const { + cDebug() << "Looking for legacy keymap in QRC"; + int bestMatching = 0; QString name; @@ -137,7 +140,7 @@ SetKeyboardLayoutJob::findLegacyKeymap() const // The best matching entry so far, then let's save that if ( matching >= qMax( bestMatching, 1 ) ) { - cDebug() << "Found legacy keymap" << mapping[0] + cDebug() << ".. Found legacy keymap" << mapping[0] << "with score" << matching; if ( matching > bestMatching ) diff --git a/src/modules/keyboard/kbd-model-map b/src/modules/keyboard/kbd-model-map index b00639b7f..49ec4ad75 100644 --- a/src/modules/keyboard/kbd-model-map +++ b/src/modules/keyboard/kbd-model-map @@ -6,6 +6,9 @@ # This is the version from 534644b7be7b240eb0fbbe06e20cbecbe8206767, # committed 2015-01-22 01:07:24 . # +# Updates: +# - 2018-09-26 Added "Austrian" keyboard (de at). Issue #1035 +# # Generated from system-config-keyboard's model list # consolelayout xlayout xmodel xvariant xoptions sg ch pc105 de_nodeadkeys terminate:ctrl_alt_bksp @@ -14,8 +17,8 @@ mk-utf mk,us pc105 - terminate:ctrl_alt_bksp,grp:shifts_toggle,grp_led:scrol trq tr pc105 - terminate:ctrl_alt_bksp uk gb pc105 - terminate:ctrl_alt_bksp is-latin1 is pc105 - terminate:ctrl_alt_bksp -at de pc105 - terminate:ctrl_alt_bksp de de pc105 - terminate:ctrl_alt_bksp +de at pc105 - terminate:ctrl_alt_bksp la-latin1 latam pc105 - terminate:ctrl_alt_bksp us us pc105+inet - terminate:ctrl_alt_bksp ko kr pc105 - terminate:ctrl_alt_bksp diff --git a/src/modules/locale/CMakeLists.txt b/src/modules/locale/CMakeLists.txt index 24259d797..576f2e16e 100644 --- a/src/modules/locale/CMakeLists.txt +++ b/src/modules/locale/CMakeLists.txt @@ -1,9 +1,3 @@ -find_package(ECM ${ECM_VERSION} NO_MODULE) -if( ECM_FOUND AND BUILD_TESTING ) - include( ECMAddTests ) - find_package( Qt5 COMPONENTS Core Test REQUIRED ) -endif() - # When debugging the timezone widget, add this debugging definition # to have a debugging-friendly timezone widget, debug logging, # and no intrusive timezone-setting while clicking around. diff --git a/src/modules/localecfg/main.py b/src/modules/localecfg/main.py index 62a00b738..713b1e321 100644 --- a/src/modules/localecfg/main.py +++ b/src/modules/localecfg/main.py @@ -134,7 +134,7 @@ def run(): if os.path.exists(target_locale_gen_bak): shutil.copy2(target_locale_gen_bak, target_locale_gen) libcalamares.utils.debug("Restored backup {!s} -> {!s}" - .format(target_locale_gen_bak).format(target_locale_gen)) + .format(target_locale_gen_bak, target_locale_gen)) # run locale-gen if detected; this *will* cause an exception # if the live system has locale.gen, but the target does not: diff --git a/src/modules/partition/core/DeviceList.cpp b/src/modules/partition/core/DeviceList.cpp index 4a353efb4..f51eec047 100644 --- a/src/modules/partition/core/DeviceList.cpp +++ b/src/modules/partition/core/DeviceList.cpp @@ -113,28 +113,32 @@ QList< Device* > getDevices( DeviceType which, qint64 minimumSize ) // Remove the device which contains / from the list for ( DeviceList::iterator it = devices.begin(); it != devices.end(); ) - if ( ! ( *it ) || - ( *it )->deviceNode().startsWith( "/dev/zram" ) + if ( !( *it ) ) + { + cDebug() << " .. Skipping nullptr device"; + it = erase( devices, it); + } + else if ( ( *it )->deviceNode().startsWith( "/dev/zram" ) ) { cDebug() << " .. Removing zram" << it; - it = erase(devices, it ); + it = erase( devices, it ); } else if ( writableOnly && hasRootPartition( *it ) ) { cDebug() << " .. Removing device with root filesystem (/) on it" << it; - it = erase(devices, it ); + it = erase( devices, it ); } else if ( writableOnly && isIso9660( *it ) ) { cDebug() << " .. Removing device with iso9660 filesystem (probably a CD) on it" << it; - it = erase(devices, it ); + it = erase( devices, it ); } else if ( (minimumSize >= 0) && !( (*it)->capacity() > minimumSize ) ) { cDebug() << " .. Removing too-small" << it; - it = erase(devices, it ); + it = erase( devices, it ); } else ++it; diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp index 0913664ca..f41142b6a 100644 --- a/src/modules/partition/core/PartitionCoreModule.cpp +++ b/src/modules/partition/core/PartitionCoreModule.cpp @@ -509,18 +509,8 @@ PartitionCoreModule::jobs() const lst << info->jobs; devices << info->device.data(); } - cDebug() << "Creating FillGlobalStorageJob with bootLoader path" << m_bootLoaderInstallPath; lst << Calamares::job_ptr( new FillGlobalStorageJob( devices, m_bootLoaderInstallPath ) ); - - QStringList jobsDebug; - foreach ( auto job, lst ) - jobsDebug.append( job->prettyName() ); - - cDebug() << "PartitionCodeModule has been asked for jobs. About to return:"; - for ( const auto item: jobsDebug ) - cDebug() << " .." << item; - return lst; } @@ -573,9 +563,9 @@ PartitionCoreModule::dumpQueue() const cDebug() << "# Queue:"; for ( auto info : m_deviceInfos ) { - cDebug() << " .. Device:" << info->device->name(); + cDebug() << "## Device:" << info->device->name(); for ( auto job : info->jobs ) - cDebug() << " .." << job->prettyName(); + cDebug() << "-" << job->prettyName(); } } diff --git a/src/modules/partition/core/PartitionIterator.cpp b/src/modules/partition/core/PartitionIterator.cpp index 5ed48fd91..8301835c6 100644 --- a/src/modules/partition/core/PartitionIterator.cpp +++ b/src/modules/partition/core/PartitionIterator.cpp @@ -18,7 +18,7 @@ * along with Calamares. If not, see . */ -#include +#include "PartitionIterator.h" // KPMcore #include diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp index abe7795c0..9bc571459 100644 --- a/src/modules/partition/gui/ChoicePage.cpp +++ b/src/modules/partition/gui/ChoicePage.cpp @@ -1161,6 +1161,13 @@ force_uncheck(QButtonGroup* grp, PrettyRadioButton* button) grp->setExclusive( true ); } +static inline QDebug& +operator <<( QDebug& s, PartitionIterator& it ) +{ + s << ( ( *it ) ? ( *it )->deviceNode() : QString( "" ) ); + return s; +} + /** * @brief ChoicePage::setupActions happens every time a new Device* is selected in the * device picker. Sets up the text and visibility of the partitioning actions based @@ -1174,6 +1181,9 @@ ChoicePage::setupActions() OsproberEntryList osproberEntriesForCurrentDevice = getOsproberEntriesForDevice( currentDevice ); + cDebug() << "Setting up actions for" << currentDevice->deviceNode() + << "with" << osproberEntriesForCurrentDevice.count() << "entries."; + if ( currentDevice->partitionTable() ) m_deviceInfoWidget->setPartitionTableType( currentDevice->partitionTable()->type() ); else @@ -1190,18 +1200,30 @@ ChoicePage::setupActions() #ifdef WITH_KPMCOREGT33 if ( currentDevice->type() == Device::Type::SoftwareRAID_Device && static_cast< SoftwareRAID* >(currentDevice)->status() == SoftwareRAID::Status::Inactive ) + { + cDebug() << ".. part of an inactive RAID device"; isInactiveRAID = true; + } #endif for ( auto it = PartitionIterator::begin( currentDevice ); it != PartitionIterator::end( currentDevice ); ++it ) { if ( PartUtils::canBeResized( *it ) ) + { + cDebug() << ".. contains resizable" << it; atLeastOneCanBeResized = true; + } if ( PartUtils::canBeReplaced( *it ) ) + { + cDebug() << ".. contains replacable" << it; atLeastOneCanBeReplaced = true; + } if ( (*it)->isMounted() ) + { + cDebug() << ".. contains mounted" << it; atLeastOneIsMounted = true; + } } if ( osproberEntriesForCurrentDevice.count() == 0 ) @@ -1318,7 +1340,12 @@ ChoicePage::setupActions() if ( !atLeastOneIsMounted && !isInactiveRAID ) m_eraseButton->show(); // None mounted else + { + cDebug() << "Erase button suppressed" + << "mount?" << atLeastOneIsMounted + << "raid?" << isInactiveRAID; force_uncheck( m_grp, m_eraseButton ); + } bool isEfi = PartUtils::isEfiSystem(); bool efiSystemPartitionFound = !m_core->efiSystemPartitions().isEmpty(); diff --git a/src/modules/partition/jobs/CreatePartitionTableJob.cpp b/src/modules/partition/jobs/CreatePartitionTableJob.cpp index 3ae201d62..937b8437d 100644 --- a/src/modules/partition/jobs/CreatePartitionTableJob.cpp +++ b/src/modules/partition/jobs/CreatePartitionTableJob.cpp @@ -20,6 +20,8 @@ #include "jobs/CreatePartitionTableJob.h" +#include "core/PartitionIterator.h" + #include "utils/Logger.h" // KPMcore @@ -65,6 +67,14 @@ CreatePartitionTableJob::prettyStatusMessage() const } +static inline QDebug& +operator <<( QDebug& s, PartitionIterator& it ) +{ + s << ( ( *it ) ? ( *it )->deviceNode() : QString( "" ) ); + return s; +} + + Calamares::JobResult CreatePartitionTableJob::exec() { @@ -73,33 +83,28 @@ CreatePartitionTableJob::exec() PartitionTable* table = m_device->partitionTable(); cDebug() << "Creating new partition table of type" << table->typeName() - << " - Uncommitted yet: " << table; + << ", uncommitted yet:"; - QProcess lsblk; - lsblk.setProgram( "lsblk" ); - lsblk.setProcessChannelMode( QProcess::MergedChannels ); - lsblk.start(); - lsblk.waitForFinished(); + if ( Logger::logLevelEnabled( Logger::LOGDEBUG ) ) + { + for ( auto it = PartitionIterator::begin( table ); + it != PartitionIterator::end( table ); ++it ) + cDebug() << *it; - QByteArray byte = lsblk.readAllStandardOutput(); - QStringList lines = QString(byte).split(("\n"),QString::SkipEmptyParts); - cDebug() << "CreatePartitionTableJob asked for lsblk output:"; + QProcess lsblk; + lsblk.setProgram( "lsblk" ); + lsblk.setProcessChannelMode( QProcess::MergedChannels ); + lsblk.start(); + lsblk.waitForFinished(); + cDebug() << "lsblk:\n" << lsblk.readAllStandardOutput(); - for (const auto line: lines) - cDebug() << " .." << line; - - QProcess mount; - mount.setProgram( "mount" ); - mount.setProcessChannelMode( QProcess::MergedChannels ); - mount.start(); - mount.waitForFinished(); - - QByteArray mbyte = mount.readAllStandardOutput(); - QStringList mlines = QString(mbyte).split(("\n"),QString::SkipEmptyParts); - cDebug() << "CreatePartitionTableJob asked for mount output:"; - - for (const auto mline: mlines) - cDebug() << " .." << mline; + QProcess mount; + mount.setProgram( "mount" ); + mount.setProcessChannelMode( QProcess::MergedChannels ); + mount.start(); + mount.waitForFinished(); + cDebug() << "mount:\n" << mount.readAllStandardOutput(); + } CreatePartitionTableOperation op(*m_device, table); op.setStatus(Operation::StatusRunning); diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index 43a5f3904..597d62a82 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -56,9 +56,12 @@ findPartitionUuids( QList < Device* > devices ) QString path = p->partitionPath(); QString uuid = p->fileSystem().readUUID( p->partitionPath() ); hash.insert( path, uuid ); + cDebug() << ".. added path=" << path << "UUID=" << uuid; } } - cDebug() << hash; + + if ( hash.isEmpty() ) + cDebug() << ".. no UUIDs found."; return hash; } @@ -90,10 +93,16 @@ mapForPartition( Partition* partition, const QString& uuid ) dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS() ) map[ "fs" ] = dynamic_cast< FS::luks& >( partition->fileSystem() ).innerFS()->name(); map[ "uuid" ] = uuid; - cDebug() << partition->partitionPath() - << "mtpoint:" << PartitionInfo::mountPoint( partition ) - << "fs:" << map[ "fs" ] << '(' << map[ "fsName" ] << ')' - << uuid; + + // Debugging for inside the loop in createPartitionList(), + // so indent a bit + Logger::CLog deb = cDebug(); + using TR = Logger::DebugRow; + deb << " .. mapping for" << partition->partitionPath() << partition->deviceNode() + << TR( "mtpoint:", PartitionInfo::mountPoint( partition ) ) + << TR( "fs:", map[ "fs" ].toString() ) + << TR( "fsname", map[ "fsName" ].toString() ) + << TR( "uuid", uuid ); if ( partition->roles().has( PartitionRole::Luks ) ) { @@ -104,7 +113,7 @@ mapForPartition( Partition* partition, const QString& uuid ) map[ "luksMapperName" ] = luksFs->mapperName().split( "/" ).last(); map[ "luksUuid" ] = getLuksUuid( partition->partitionPath() ); map[ "luksPassphrase" ] = luksFs->passphrase(); - cDebug() << "luksMapperName:" << map[ "luksMapperName" ]; + deb << TR( "luksMapperName:", map[ "luksMapperName" ].toString() ); } } @@ -215,9 +224,11 @@ FillGlobalStorageJob::createPartitionList() const cDebug() << "Writing to GlobalStorage[\"partitions\"]"; for ( auto device : m_devices ) { + cDebug() << ".. partitions on" << device->deviceNode(); for ( auto it = PartitionIterator::begin( device ); it != PartitionIterator::end( device ); ++it ) { + // Debug-logging is done when creating the map lst << mapForPartition( *it, hash.value( ( *it )->partitionPath() ) ); } } diff --git a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp index 7f6169bbe..fee987479 100644 --- a/src/modules/partition/jobs/SetPartitionFlagsJob.cpp +++ b/src/modules/partition/jobs/SetPartitionFlagsJob.cpp @@ -132,6 +132,10 @@ SetPartFlagsJob::prettyStatusMessage() const Calamares::JobResult SetPartFlagsJob::exec() { + cDebug() << "Setting flags on" << m_device->deviceNode() + << "partition" << partition()->deviceNode() + << "to" << m_flags; + Report report ( nullptr ); SetPartFlagsOperation op( *m_device, *partition(), m_flags ); op.setStatus( Operation::StatusRunning ); diff --git a/src/modules/partition/tests/CMakeLists.txt b/src/modules/partition/tests/CMakeLists.txt index 68474287e..7b40c34a5 100644 --- a/src/modules/partition/tests/CMakeLists.txt +++ b/src/modules/partition/tests/CMakeLists.txt @@ -1,6 +1,4 @@ -find_package( Qt5 COMPONENTS Gui Test REQUIRED ) - -include( ECMAddTests ) +find_package( Qt5 COMPONENTS Gui REQUIRED ) set( PartitionModule_SOURCE_DIR .. ) @@ -23,13 +21,15 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) -ecm_add_test( ${partitionjobtests_SRCS} - TEST_NAME partitionjobtests - LINK_LIBRARIES - ${CALAMARES_LIBRARIES} - kpmcore - Qt5::Core - Qt5::Test -) +if( ECM_FOUND AND BUILD_TESTING ) + ecm_add_test( ${partitionjobtests_SRCS} + TEST_NAME partitionjobtests + LINK_LIBRARIES + ${CALAMARES_LIBRARIES} + kpmcore + Qt5::Core + Qt5::Test + ) -set_target_properties( partitionjobtests PROPERTIES AUTOMOC TRUE ) + set_target_properties( partitionjobtests PROPERTIES AUTOMOC TRUE ) +endif() diff --git a/src/modules/preservefiles/CMakeLists.txt b/src/modules/preservefiles/CMakeLists.txt index 1ac979d1b..c1021eeda 100644 --- a/src/modules/preservefiles/CMakeLists.txt +++ b/src/modules/preservefiles/CMakeLists.txt @@ -4,6 +4,7 @@ calamares_add_plugin( preservefiles TYPE job EXPORT_MACRO PLUGINDLLEXPORT_PRO SOURCES + permissions.cpp PreserveFiles.cpp LINK_PRIVATE_LIBRARIES calamares diff --git a/src/modules/preservefiles/PreserveFiles.cpp b/src/modules/preservefiles/PreserveFiles.cpp index 0fe1d278b..2c1b85103 100644 --- a/src/modules/preservefiles/PreserveFiles.cpp +++ b/src/modules/preservefiles/PreserveFiles.cpp @@ -18,6 +18,8 @@ #include "PreserveFiles.h" +#include "permissions.h" + #include "CalamaresVersion.h" #include "JobQueue.h" #include "GlobalStorage.h" @@ -83,6 +85,38 @@ PreserveFiles::prettyName() const return tr( "Saving files for later ..." ); } +static bool +copy_file( const QString& source, const QString& dest ) +{ + QFile sourcef( source ); + if ( !sourcef.open( QFile::ReadOnly ) ) + { + cWarning() << "Could not read" << source; + return false; + } + + QFile destf( dest ); + if ( !destf.open( QFile::WriteOnly ) ) + { + sourcef.close(); + cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source; + return false; + } + + QByteArray b; + do + { + b = sourcef.read( 1_MiB ); + destf.write( b ); + } + while ( b.count() > 0 ); + + sourcef.close(); + destf.close(); + + return true; +} + Calamares::JobResult PreserveFiles::exec() { if ( m_items.isEmpty() ) @@ -96,7 +130,8 @@ Calamares::JobResult PreserveFiles::exec() for ( const auto& it : m_items ) { QString source = it.source; - QString dest = prefix + atReplacements( it.dest ); + QString bare_dest = atReplacements( it.dest ); + QString dest = prefix + bare_dest; if ( it.type == ItemType::Log ) source = Logger::logFile(); @@ -111,32 +146,29 @@ Calamares::JobResult PreserveFiles::exec() cWarning() << "Skipping unnamed source file for" << dest; else { - QFile sourcef( source ); - if ( !sourcef.open( QFile::ReadOnly ) ) + if ( copy_file( source, dest ) ) { - cWarning() << "Could not read" << source; - continue; + if ( it.perm.isValid() ) + { + auto s_p = CalamaresUtils::System::instance(); + + int r; + + r = s_p->targetEnvCall( QStringList{ "chown", it.perm.username(), bare_dest } ); + if ( r ) + cWarning() << "Could not chown target" << bare_dest; + + r = s_p->targetEnvCall( QStringList{ "chgrp", it.perm.group(), bare_dest } ); + if ( r ) + cWarning() << "Could not chgrp target" << bare_dest; + + r = s_p->targetEnvCall( QStringList{ "chmod", it.perm.octal(), bare_dest } ); + if ( r ) + cWarning() << "Could not chmod target" << bare_dest; + } + + ++count; } - - QFile destf( dest ); - if ( !destf.open( QFile::WriteOnly ) ) - { - sourcef.close(); - cWarning() << "Could not open" << destf.fileName() << "for writing; could not copy" << source; - continue; - } - - QByteArray b; - do - { - b = sourcef.read( 1_MiB ); - destf.write( b ); - } - while ( b.count() > 0 ); - - sourcef.close(); - destf.close(); - ++count; } } @@ -160,6 +192,10 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap) return; } + QString defaultPermissions = configurationMap[ "perm" ].toString(); + if ( defaultPermissions.isEmpty() ) + defaultPermissions = QStringLiteral( "root:root:0400" ); + QVariantList l = files.toList(); unsigned int c = 0; for ( const auto& li : l ) @@ -168,7 +204,7 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap) { QString filename = li.toString(); if ( !filename.isEmpty() ) - m_items.append( Item{ filename, filename, ItemType::Path } ); + m_items.append( Item{ filename, filename, Permissions( defaultPermissions ), ItemType::Path } ); else cDebug() << "Empty filename for preservefiles, item" << c; } @@ -181,6 +217,9 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap) ( from == "log" ) ? ItemType::Log : ( from == "config" ) ? ItemType::Config : ItemType::None; + QString perm = map[ "perm" ].toString(); + if ( perm.isEmpty() ) + perm = defaultPermissions; if ( dest.isEmpty() ) { @@ -192,7 +231,7 @@ void PreserveFiles::setConfigurationMap(const QVariantMap& configurationMap) } else { - m_items.append( Item{ QString(), dest, t } ); + m_items.append( Item{ QString(), dest, Permissions( perm ), t } ); } } else diff --git a/src/modules/preservefiles/PreserveFiles.h b/src/modules/preservefiles/PreserveFiles.h index 0c9216336..ed2fe889c 100644 --- a/src/modules/preservefiles/PreserveFiles.h +++ b/src/modules/preservefiles/PreserveFiles.h @@ -24,11 +24,11 @@ #include #include "CppJob.h" +#include "PluginDllMacro.h" #include "utils/PluginFactory.h" -#include "PluginDllMacro.h" - +#include "permissions.h" class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob { @@ -46,6 +46,7 @@ class PLUGINDLLEXPORT PreserveFiles : public Calamares::CppJob { QString source; QString dest; + Permissions perm; ItemType type; } ; diff --git a/src/modules/preservefiles/permissions.cpp b/src/modules/preservefiles/permissions.cpp new file mode 100644 index 000000000..a3f8ac136 --- /dev/null +++ b/src/modules/preservefiles/permissions.cpp @@ -0,0 +1,75 @@ +/* === This file is part of Calamares - === + * + * Copyright (C) 2018 Scott Harvey + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include "permissions.h" + +Permissions::Permissions() : + m_username(), + m_group(), + m_valid(false), + m_value(0) +{ +} + + +Permissions::Permissions(QString p) : Permissions() +{ + parsePermissions(p); +} + +void Permissions::parsePermissions(const QString& p) { + + QStringList segments = p.split(":"); + + if (segments.length() != 3) { + m_valid = false; + return; + } + + if (segments[0].isEmpty() || segments[1].isEmpty()) { + m_valid = false; + return; + } + + bool ok; + int octal = segments[2].toInt(&ok, 8); + if (!ok || octal == 0) { + m_valid = false; + return; + } else { + m_value = octal; + } + + // We have exactly three segments and the third is valid octal, + // so we can declare the string valid and set the user and group names + m_valid = true; + m_username = segments[0]; + m_group = segments[1]; + + return; + +} + + + + + + diff --git a/src/modules/preservefiles/permissions.h b/src/modules/preservefiles/permissions.h new file mode 100644 index 000000000..4cb70a2c2 --- /dev/null +++ b/src/modules/preservefiles/permissions.h @@ -0,0 +1,62 @@ +/* === This file is part of Calamares - === + * + * Copyright (C) 2018 Scott Harvey + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef PERMISSIONS_H +#define PERMISSIONS_H + +#include + +/** + * @brief The Permissions class takes a QString @p in the form of + * ::, checks it for validity, and makes the three + * components available indivdually. + */ +class Permissions +{ + +public: + + /** @brief Constructor + * + * Splits the string @p at the colon (":") into separate elements for + * , , and (permissions), where is returned as + * an **octal** integer. + */ + Permissions(QString p); + + /** @brief Default constructor of an invalid Permissions. */ + Permissions(); + + bool isValid() const { return m_valid; } + QString username() const { return m_username; } + QString group() const { return m_group; } + int value() const { return m_value; } + QString octal() const { return QString::number( m_value, 8 ); } + +private: + void parsePermissions(QString const &p); + + QString m_username; + QString m_group; + bool m_valid; + int m_value; + +}; + +#endif // PERMISSIONS_H diff --git a/src/modules/preservefiles/preservefiles.conf b/src/modules/preservefiles/preservefiles.conf index ab9114d20..671a308cc 100644 --- a/src/modules/preservefiles/preservefiles.conf +++ b/src/modules/preservefiles/preservefiles.conf @@ -9,13 +9,18 @@ # as the source). # - a map with a *dest* key. The *dest* value is a path interpreted in the # target system (if dontChroot is true, in the host system). Relative paths -# are not recommended. There are two possible other keys in the map: +# are not recommended. There are three possible other keys in the map: # - *from*, which must have one of the values, below; it is used to # preserve files whose pathname is known to Calamares internally. # - *src*, to refer to a path interpreted in the host system. Relative # paths are not recommended, and are interpreted relative to where # Calamares is being run. -# Only one of the two other keys (either *from* or *src*) may be set. +# - *perm*, is a colon-separated tuple of :: +# where is in octal (e.g. 4777 for wide-open, 0400 for read-only +# by owner). If set, the file's ownership and permissions are set to +# those values within the target system; if not set, no permissions +# are changed. +# Only one of the two source keys (either *from* or *src*) may be set. # # The target filename is modified as follows: # - `@@ROOT@@` is replaced by the path to the target root (may be /) @@ -32,5 +37,13 @@ files: - /etc/oem-information - from: log dest: /root/install.log + perm: root:wheel:644 - from: config dest: /root/install.cfg + perm: root:wheel:400 + +# The *perm* key contains a default value to apply to all files listed +# above that do not have a *perm* key of their own. If not set, +# root:root:0400 (highly restrictive) is used. +# +# perm: "root:root:0400" diff --git a/src/modules/shellprocess/CMakeLists.txt b/src/modules/shellprocess/CMakeLists.txt index 51d4c4a4c..82ae8b911 100644 --- a/src/modules/shellprocess/CMakeLists.txt +++ b/src/modules/shellprocess/CMakeLists.txt @@ -8,10 +8,7 @@ calamares_add_plugin( shellprocess SHARED_LIB ) -if( ECM_FOUND ) - find_package( Qt5 COMPONENTS Test REQUIRED ) - include( ECMAddTests ) - +if( ECM_FOUND AND BUILD_TESTING ) ecm_add_test( Tests.cpp TEST_NAME diff --git a/src/modules/test_conf.cpp b/src/modules/test_conf.cpp index b5362d25a..ca6b72cc7 100644 --- a/src/modules/test_conf.cpp +++ b/src/modules/test_conf.cpp @@ -87,7 +87,7 @@ int main(int argc, char** argv) if ( !doc.IsMap() ) { cerr << "WARNING:" << filename << '\n'; - cerr << "WARNING: not-a-YAML-map\n"; + cerr << "WARNING: not-a-YAML-map (type=" << doc.Type() << ")\n"; return 1; } diff --git a/src/modules/users/CMakeLists.txt b/src/modules/users/CMakeLists.txt index 16e235fd5..207ffbb3a 100644 --- a/src/modules/users/CMakeLists.txt +++ b/src/modules/users/CMakeLists.txt @@ -1,9 +1,4 @@ -find_package(ECM ${ECM_VERSION} NO_MODULE) -if( ECM_FOUND ) - include( ECMAddTests ) -endif() - -find_package( Qt5 COMPONENTS Core Test REQUIRED ) +find_package( Qt5 COMPONENTS Core REQUIRED ) find_package( Crypt REQUIRED ) # Add optional libraries here @@ -44,7 +39,7 @@ calamares_add_plugin( users SHARED_LIB ) -if( ECM_FOUND ) +if( ECM_FOUND AND BUILD_TESTING ) ecm_add_test( PasswordTests.cpp SetPasswordJob.cpp