From e91be5bd125b18197f89ee17dc18c31938a1171f Mon Sep 17 00:00:00 2001
From: Philip <philm@manjaro.org>
Date: Sat, 5 Nov 2016 09:55:37 +0100
Subject: [PATCH] v2.4.4 - [partition] remove sgdisk dependency -
 [displaymanager] use configparser and add better autologin handling -
 [displaymanager] set a preferred Xsession for autologin - [netinstall]
 Support selecting visible groups by default - [packages] Add option of
 updating packages db before perfoming package operations - [packages] Allow
 try_install and try_remove entries in packages module - [unpackfs] Do not
 fail if rsync returns exit code 23

---
 README.md                                     |  2 -
 src/modules/bootloader/main.py                | 29 +-----------
 src/modules/displaymanager/main.py            | 46 +++++++++----------
 src/modules/netinstall/NetInstallPage.cpp     |  2 +-
 .../widgets/groupselectionwidget.cpp          |  3 +-
 .../netinstall/widgets/groupselectionwidget.h |  2 +-
 src/modules/packages/main.py                  | 31 +++++++++++++
 src/modules/packages/packages.conf            |  9 ++--
 .../partition/core/PartitionActions.cpp       |  3 +-
 .../partition/core/PartitionCoreModule.cpp    | 21 ++-------
 src/modules/unpackfs/main.py                  | 13 +++++-
 11 files changed, 81 insertions(+), 80 deletions(-)

diff --git a/README.md b/README.md
index 584bebc6c0..2f125b1cbc 100644
--- a/README.md
+++ b/README.md
@@ -28,10 +28,8 @@ Modules:
  * extra-cmake-modules
  * KF5: KCoreAddons, KConfig, KI18n, KIconThemes, KIO, KService
  * KPMcore >= 2.2
- * sgdisk
 * bootloader:
  * systemd-boot or GRUB
- * sgdisk
 * unpackfs:
  * squashfs-tools
  * rsync
diff --git a/src/modules/bootloader/main.py b/src/modules/bootloader/main.py
index d292de6fda..e367eebbb3 100644
--- a/src/modules/bootloader/main.py
+++ b/src/modules/bootloader/main.py
@@ -258,7 +258,7 @@ def vfat_correct_case(parent, name):
 
 
 def prepare_bootloader(fw_type):
-    """ Prepares bootloader and set proper flags to EFI boot partition (esp,boot).
+    """ Prepares bootloader.
     Based on value 'efi_boot_loader', it either calls systemd-boot or grub to be installed.
 
     :param fw_type:
@@ -267,33 +267,6 @@ def prepare_bootloader(fw_type):
     efi_boot_loader = libcalamares.job.configuration["efiBootLoader"]
     efi_directory = libcalamares.globalstorage.value("efiSystemPartition")
 
-    if fw_type == "efi":
-        partitions = libcalamares.globalstorage.value("partitions")
-        boot_p = ""
-        device = ""
-
-        for partition in partitions:
-            if partition["mountPoint"] == efi_directory:
-                boot_device = partition["device"]
-                boot_p = boot_device[-1:]
-                device = boot_device[:-1]
-
-                if not boot_p or not device:
-                    return ("EFI directory \"{!s}\" not found!".format(efi_directory),
-                            "Boot partition: \"{!s}\"".format(boot_p),
-                            "Boot device: \"{!s}\"".format(device))
-                else:
-                    print("EFI directory: \"{!s}\"".format(efi_directory))
-                    print("Boot partition: \"{!s}\"".format(boot_p))
-                    print("Boot device: \"{!s}\"".format(device))
-
-        if not device:
-            print("WARNING: no EFI system partition or EFI system partition mount point not set.")
-            print("         >>> no EFI bootloader will be installed <<<")
-            return None
-        print("Set 'EF00' flag")
-        subprocess.call(["sgdisk", "--typecode={!s}:EF00".format(boot_p), "{!s}".format(device)])
-
     if efi_boot_loader == "systemd-boot" and fw_type == "efi":
         install_systemd_boot(efi_directory)
     else:
diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py
index 7fa4be0fd2..433e114ea9 100644
--- a/src/modules/displaymanager/main.py
+++ b/src/modules/displaymanager/main.py
@@ -24,6 +24,7 @@ import os
 import collections
 import re
 import libcalamares
+import configparser
 
 
 DesktopEnvironment = collections.namedtuple('DesktopEnvironment', ['executable', 'desktop_file'])
@@ -253,34 +254,31 @@ def set_autologin(username, displaymanagers, default_desktop_environment, root_m
         # Systems with Sddm as Desktop Manager
         sddm_conf_path = os.path.join(root_mount_point, "etc/sddm.conf")
 
+        sddm_config = configparser.ConfigParser()
+        # Make everything case sensitive
+        sddm_config.optionxform = str
+
         if os.path.isfile(sddm_conf_path):
-            libcalamares.utils.debug('SDDM config file exists')
-        else:
-            libcalamares.utils.check_target_env_call(["sh", "-c", "sddm --example-config > /etc/sddm.conf"])
+            sddm_config.read(sddm_conf_path)
 
-        text = []
+        autologin_section = {}
+        if 'Autologin' in sddm_config:
+            autologin_section = sddm_config['Autologin']
 
-        with open(sddm_conf_path, 'r') as sddm_conf:
-            text = sddm_conf.readlines()
-
-        with open(sddm_conf_path, 'w') as sddm_conf:
-            for line in text:
-                # User= line, possibly commented out
-                if re.match('\\s*(?:#\\s*)?User=', line):
-                    if do_autologin:
-                        line = 'User={!s}\n'.format(username)
-                    else:
-                        line = '#User=\n'
-
-                # Session= line, commented out or with empty value
-                if re.match('\\s*#\\s*Session=|\\s*Session=$', line):
-                    if default_desktop_environment is not None:
-                        if do_autologin:
-                            line = 'Session={!s}.desktop\n'.format(default_desktop_environment.desktop_file)
-                        else:
-                            line = '#Session={!s}.desktop\n'.format(default_desktop_environment.desktop_file)
+        if do_autologin:
+            autologin_section['User'] = username
+
+        if default_desktop_environment is not None:
+            autologin_section['Session'] = default_desktop_environment.desktop_file
+
+        if autologin_section:
+            if 'Autologin' in sddm_config:
+                sddm_config['Autologin'].update(autologin_section)
+            else:
+                sddm_config['Autologin'] = autologin_section
 
-                sddm_conf.write(line)
+        with open(sddm_conf_path, 'w') as sddm_config_file:
+            sddm_config.write(sddm_config_file, space_around_delimiters=False)
 
     return None
 
diff --git a/src/modules/netinstall/NetInstallPage.cpp b/src/modules/netinstall/NetInstallPage.cpp
index 0e3a18c95f..89f8e58dd5 100644
--- a/src/modules/netinstall/NetInstallPage.cpp
+++ b/src/modules/netinstall/NetInstallPage.cpp
@@ -111,7 +111,7 @@ NetInstallPage::dataIsHere( QNetworkReply* reply )
             continue;
         }
 
-        GroupSelectionWidget* groupWidget = new GroupSelectionWidget( group.name, group.description, group.packages, this );
+        GroupSelectionWidget* groupWidget = new GroupSelectionWidget( group.name, group.description, group.packages, group.selected, this );
         m_groupWidgets.insert( groupKey, groupWidget );
         ui->groupswidget->layout()->addWidget( groupWidget );
 
diff --git a/src/modules/netinstall/widgets/groupselectionwidget.cpp b/src/modules/netinstall/widgets/groupselectionwidget.cpp
index b477c0453a..3048fb01f6 100644
--- a/src/modules/netinstall/widgets/groupselectionwidget.cpp
+++ b/src/modules/netinstall/widgets/groupselectionwidget.cpp
@@ -20,7 +20,7 @@
 
 #include <QtDebug>
 
-GroupSelectionWidget::GroupSelectionWidget( QString name, QString description, QStringList packages, QWidget* parent ) :
+GroupSelectionWidget::GroupSelectionWidget( QString name, QString description, QStringList packages, bool selected, QWidget* parent ) :
     QWidget( parent ),
     m_isToggled( false )
 {
@@ -29,6 +29,7 @@ GroupSelectionWidget::GroupSelectionWidget( QString name, QString description, Q
     connect( ui.group, &QCheckBox::toggled, this, &GroupSelectionWidget::toggleGroup );
 
     ui.group->setText( name );
+    ui.group->setChecked( selected ); // also triggers the toggleGroup slot
     ui.description->setText( description );
     const int columns = 4;
     const int rows = ( packages.size() - 1 ) / columns + 1;
diff --git a/src/modules/netinstall/widgets/groupselectionwidget.h b/src/modules/netinstall/widgets/groupselectionwidget.h
index a003f48336..1f7a0fc973 100644
--- a/src/modules/netinstall/widgets/groupselectionwidget.h
+++ b/src/modules/netinstall/widgets/groupselectionwidget.h
@@ -29,7 +29,7 @@ class GroupSelectionWidget : public QWidget
 {
     Q_OBJECT
 public:
-    explicit GroupSelectionWidget( QString name, QString description, QStringList packages, QWidget* parent = nullptr );
+    explicit GroupSelectionWidget( QString name, QString description, QStringList packages, bool selected, QWidget* parent = nullptr );
 
     // Current status of the group: is it selected in the view?
     bool isToggled() const;
diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py
index 943b032cc3..f0f7b38a30 100644
--- a/src/modules/packages/main.py
+++ b/src/modules/packages/main.py
@@ -18,6 +18,7 @@
 #   You should have received a copy of the GNU General Public License
 #   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
 
+import subprocess
 import libcalamares
 from libcalamares.utils import check_target_env_call, target_env_call
 
@@ -91,6 +92,22 @@ class PackageManager:
         elif self.backend == "entropy":
             check_target_env_call(["equo", "rm"] + pkgs)
 
+    def update_db(self):
+        if self.backend == "packagekit":
+            check_target_env_call(["pkcon", "refresh"])
+        elif self.backend == "zypp":
+            check_target_env_call(["zypper", "update"])
+        elif self.backend == "urpmi":
+            check_target_env_call(["urpmi.update", "-a"])
+        elif self.backend == "apt":
+            check_target_env_call(["apt-get", "update"])
+        elif self.backend == "pacman":
+            check_target_env_call(["pacman", "-Sy"])
+        elif self.backend == "portage":
+            check_target_env_call(["emerge", "--sync"])
+        elif self.backend == "entropy":
+            check_target_env_call(["equo", "update"])
+
 
 def run_operations(pkgman, entry):
     """ Call package manager with given parameters.
@@ -101,8 +118,18 @@ def run_operations(pkgman, entry):
     for key in entry.keys():
         if key == "install":
             pkgman.install(entry[key])
+        elif key == "try_install":
+            try:
+                pkgman.install(entry[key])
+            except subprocess.CalledProcessError:
+                libcalamares.utils.debug("WARNING: could not install packages {}".format(", ".join(entry[key])))
         elif key == "remove":
             pkgman.remove(entry[key])
+        elif key == "try_remove":
+            try:
+                pkgman.remove(entry[key])
+            except subprocess.CalledProcessError:
+                libcalamares.utils.debug("WARNING: could not remove packages {}".format(", ".join(entry[key])))
         elif key == "localInstall":
             pkgman.install(entry[key], from_local=True)
 
@@ -121,6 +148,10 @@ def run():
     pkgman = PackageManager(backend)
     operations = libcalamares.job.configuration.get("operations", [])
 
+    update_db = libcalamares.job.configuration.get("update_db", False)
+    if update_db and libcalamares.globalstorage.value("hasInternet"):
+        pkgman.update_db()
+
     for entry in operations:
         run_operations(pkgman, entry)
 
diff --git a/src/modules/packages/packages.conf b/src/modules/packages/packages.conf
index e72763731b..4039278b3d 100644
--- a/src/modules/packages/packages.conf
+++ b/src/modules/packages/packages.conf
@@ -12,6 +12,9 @@
 #  - entropy	 - Sabayon package manager
 #
 backend: packagekit
+
+update_db: true
+
 #
 # List of maps with package operations such as install or remove.
 # Distro developers can provide a list of packages to remove
@@ -33,12 +36,12 @@ backend: packagekit
 #  - remove:
 #      - pkg3
 #      - pkg4
-#  - install:
+#  - try_install:   # no system install failure if a package cannot be installed
 #      - pkg5
-#  - remove:
+#  - try_remove:    # no system install failure if a package cannot be removed
 #      - pkg2
 #      - pkg1
-#    install:
+#  - install:
 #      - pkgs6
 #      - pkg7
 #  - localInstall:
diff --git a/src/modules/partition/core/PartitionActions.cpp b/src/modules/partition/core/PartitionActions.cpp
index e2c6ca6386..aa9c0af361 100644
--- a/src/modules/partition/core/PartitionActions.cpp
+++ b/src/modules/partition/core/PartitionActions.cpp
@@ -140,7 +140,8 @@ doAutopartition( PartitionCoreModule* core, Device* dev, const QString& luksPass
             PartitionRole( PartitionRole::Primary ),
             FileSystem::Fat32,
             firstFreeSector,
-            lastSector
+            lastSector,
+            PartitionTable::FlagEsp
         );
         PartitionInfo::setFormat( efiPartition, true );
         PartitionInfo::setMountPoint( efiPartition, Calamares::JobQueue::instance()
diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp
index cf2b2c2a5d..4e8da4f560 100644
--- a/src/modules/partition/core/PartitionCoreModule.cpp
+++ b/src/modules/partition/core/PartitionCoreModule.cpp
@@ -547,29 +547,14 @@ PartitionCoreModule::scanForEfiSystemPartitions()
         devices.append( device );
     }
 
-    //FIXME: Unfortunately right now we have to call sgdisk manually because
-    //       the KPM submodule does not expose the ESP flag from libparted.
-    //       The following findPartitions call and lambda should be scrapped and
-    //       rewritten based on libKPM.     -- Teo 5/2015
     QList< Partition* > efiSystemPartitions =
         KPMHelpers::findPartitions( devices,
                                  []( Partition* partition ) -> bool
     {
-        QProcess process;
-        process.setProgram( "sgdisk" );
-        process.setArguments( { "-i",
-                                QString::number( partition->number() ),
-                                partition->devicePath() } );
-        process.setProcessChannelMode( QProcess::MergedChannels );
-        process.start();
-        if ( process.waitForFinished() )
+        if ( partition->activeFlags().testFlag( PartitionTable::FlagEsp ) )
         {
-            if ( process.readAllStandardOutput()
-                    .contains( "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" ) )
-            {
-                cDebug() << "Found EFI system partition at" << partition->partitionPath();
-                return true;
-            }
+            cDebug() << "Found EFI system partition at" << partition->partitionPath();
+            return true;
         }
         return false;
     } );
diff --git a/src/modules/unpackfs/main.py b/src/modules/unpackfs/main.py
index 926bc0f016..34d9829d9e 100644
--- a/src/modules/unpackfs/main.py
+++ b/src/modules/unpackfs/main.py
@@ -119,7 +119,18 @@ def file_copy(source, dest, progress_cb):
 
     process.wait()
 
-    if process.returncode != 0:
+    # 23 is the return code rsync returns if it cannot write extended attributes
+    # (with -X) because the target file system does not support it, e.g., the
+    # FAT EFI system partition. We need -X because distributions using file
+    # system capabilities and/or SELinux require the extended attributes. But
+    # distributions using SELinux may also have SELinux labels set on files
+    # under /boot/efi, and rsync complains about those. The only clean way would
+    # be to split the rsync into one with -X and --exclude /boot/efi and a
+    # separate one without -X for /boot/efi, but only if /boot/efi is actually
+    # an EFI system partition. For now, this hack will have to do. See also:
+    # https://bugzilla.redhat.com/show_bug.cgi?id=868755#c50
+    # for the same issue in Anaconda, which uses a similar workaround.
+    if process.returncode != 0 and process.returncode != 23:
         return "rsync failed with error code {}.".format(process.returncode)
 
     return None
-- 
GitLab