You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qubes-installer-qubes-os/anaconda/0042-anaconda-Fix-macOS-EFI...

277 lines
11 KiB

From a1980ee725fc73e367bde3706c327684ac5e35ee Mon Sep 17 00:00:00 2001
From: Eric Duncan <me@eduncan911.com>
Date: Fri, 19 Oct 2018 08:02:13 +0200
Subject: [PATCH] anaconda: Fix macOS EFI Installation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Typical GRUB2 installations would execute the script
located at /usr/libexec/mactel-boot-setup which would
modify the HFS+ ESP files and bless the specified efi.
However, we are not using GRUB at this time which would
cause that script to exit earlier.
These changes will execute the relevant commands
to symlink the efi file in the /System directory as well
the cfg file. Lastly, macOS requires the bootable efi
file to be blessed.
We also attempt to place some user-friendly icons
for Qubes to show to the user.
Lastly, we add a README with some instructions on how
to get into rescue mode from macOS.
Signed-off-by: Frédéric Pierret <frederic.epitre@orange.fr>
---
pyanaconda/bootloader.py | 220 +++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 215 insertions(+), 5 deletions(-)
diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index ad8e8c2e7..821eb2100 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -1908,16 +1908,208 @@ class XenEFI(EFIGRUB):
write_config = BootLoader.write_config
-class MacEFIGRUB(EFIGRUB):
+class MacEFIGRUB(XenEFI):
+ """Special EFI handling for macOS HFS+ ESP partition.
+
+ Typical GRUB2 installations would execute the script
+ located at /usr/libexec/mactel-boot-setup which would
+ modify the HFS+ ESP files and bless the specified efi.
+
+ However, we are not using GRUB at this time which would
+ cause that script to exit earlier.
+
+ In this class, we will execute the relevant commands
+ to symlink the efi file in the /System directory as well
+ the cfg file. Lastly, macOS requires the bootable efi
+ file to be blessed.
+ """
+
+ def __init__(self):
+ super(MacEFIGRUB, self).__init__()
+ self._mountpoint = "/boot/efi" # fixme: extract from writeBootLoader()
+ self._system_label = "Qubes OS"
+ self._mactel_sys_dir = "{}/{}".format(self._mountpoint, "/System/Library/Coreservices")
+ self._mactel_artwork_dir = "{}/{}".format("/usr/share/pixmaps/bootloader/apple", self.efi_dir)
+
def mactel_config(self):
- if os.path.exists(iutil.getSysroot() + "/usr/libexec/mactel-boot-setup"):
- rc = iutil.execInSysroot("/usr/libexec/mactel-boot-setup", [])
- if rc:
- log.error("failed to configure Mac boot loader")
+ """Modifies the HFS+ ESP partition to be bootable.
+
+ Based on the /usr/libexec/mactel-boot-setup script,
+ we will create two symlinks pointing to the Qubes
+ Xen version that is installed and configured for
+ the system. We also need to run hfs-bless on
+ the specific EFI file we allow to be bootable.
+ """
+
+ log.info("mactel configure MacEFI boot partition")
+
+ not_ready = False
+ xen_efi_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.efi")
+ if not os.path.exists(xen_efi_file):
+ log.waring("mactel efi file not found: %s", xen_efi_file)
+ not_ready = True
+ xen_cfg_file = "{}/{}".format(iutil.getSysroot() + self.config_dir, "xen.cfg")
+ if not os.path.exists(xen_cfg_file):
+ log.warning("mactel efi cfg not found: %s", xen_cfg_file)
+ not_ready = True
+ if not_ready:
+ log.error("mactel cannot continue with mactel_config. see log for details.")
+ return
+
+ sys_dir = iutil.getSysroot() + self._mactel_sys_dir
+ if not os.path.exists(sys_dir):
+ try:
+ os.makedirs(sys_dir)
+ except Exception as error:
+ log.warning("mactel tried to create sys_dir %s but received error: %s", sys_dir, error)
+
+ src_efi = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.efi")
+ sys_efi = "{}/{}".format(sys_dir, "boot.efi")
+ self._symlink(src_efi, sys_efi)
+
+ src_cfg = "{}/{}".format("../../../EFI/" + self.efi_dir, "xen.cfg")
+ sys_cfg = "{}/{}".format(sys_dir, "xen.cfg") # convention for Xen's cfg lookup
+ self._symlink(src_cfg, sys_cfg)
+
+ result_code = iutil.execInSysroot("touch", ["{}/{}".format(self._mountpoint, "mach_kernel")])
+ if result_code:
+ log.error("mactel failed to touch: %s", "{}/{}".format(self._mountpoint, "mach_kernel"))
+
+ text = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ProductBuildVersion</key>
+ <string></string>
+ <key>ProductName</key>
+ <string>Linux</string>
+ <key>ProductVersion</key>
+ <string>{}</string>
+</dict>
+</plist>""".format(self._system_label)
+ sys_ver_file_name = "{}/{}".format(sys_dir, "SystemVersion.plist")
+ try:
+ with open(sys_ver_file_name, "w") as sys_version_file:
+ sys_version_file.write(text)
+ except IOError as error:
+ log.error("mactel failed to open %s for write: %s", sys_ver_file_name, error)
+ cfg_ver_file_name = "{}/{}".format(iutil.getSysroot() + self.config_dir, "SystemVersion.plist")
+ self._copy_file(sys_ver_file_name, cfg_ver_file_name)
+
+ bless_file = "{}/{}".format(self.config_dir, "xen.efi")
+ result_code = iutil.execInSysroot("hfs-bless", [bless_file])
+ if result_code:
+ log.error("mactel failed to run 'hfs-bless %s'", bless_file)
+
+ # make the partition macOS friendly (e.g. to set rescue mode from macOS)
+ fseventsd = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".fseventsd")
+ try:
+ os.makedirs(fseventsd)
+ except Exception as error:
+ log.error("mactel could not make directory %s: %s", fseventsd, error)
+ touch_files = [
+ "{}/{}/{}".format(self._mountpoint, ".fseventsd", "no_log"),
+ "{}/{}".format(self._mountpoint, ".metadata_never_index"),
+ "{}/{}".format(self._mountpoint, ".Trashes")]
+ result_code = iutil.execInSysroot("touch", touch_files)
+ if result_code:
+ log.error("mactel failed to touch: %s", touch_files)
+
+ text = """Qubes OS
+
+CAUTION
+--
+This partition is used to boot Qubes OS on a macOS machine.
+Modifying the contents could render your installation unbootable.
+
+RESCUE / RECOVERY MODE
+--
+In the event that you need to boot into Rescue mode, you will need
+to change the default Xen configuration.
+
+0. Backup your xen.cfg.
+
+ cp /EFI/qubes/xen.cfg /EFI/qubes/xen.cfg~
+
+1. Open /EFI/qubes/xen.cfg
+2. Look at the sections with the named headers. One may already be
+ named "qubes-rescue", which we will use in Step 4 below.
+
+ If not, we will need to make one. Copy your current configuration
+ to another entry. But change the kernel line to only have "rescue"
+ at the end. As an example only:
+
+ [qubes-rescue]
+ options=loglvl=all dom0_mem=min:1024M dom0_mem=max:4096M iommu=no-igfx
+ kernel=vmlinuz-4.14.13-2.pvops.qubes.x86_64 rescue
+ ramdisk=initramfs-4.14.13-2.pvops.qubes.x86_64.img
+
+ Do not simply copy this section above. You will need to make sure
+ all of your existing parameters, vmlinuz and initramfs versions match
+ your current kernel(s) you are booting.
+
+3. At the top of the file, edit the default link to use your new entry.
+
+ [global]
+ default=qubes-rescue
+
+Now when you reboot using Boot Camp and select "Qubes OS", it will boot
+into Rescue mode.
+
+To revert, change the default entry back to the original first entry name."""
+ readme = "{}/{}".format(iutil.getSysroot() + self._mountpoint, "00-README.txt")
+ try:
+ with open(readme, "w") as readme_file:
+ readme_file.write(text)
+ except IOError as error:
+ log.error("mactel failed to open %s for write: %s", readme, error)
+
+ def mactel_install_qubes_artwork(self):
+ """Configures the Qubes logo and label for macOS boot selector.
+
+ Shows during boot selection options.
+
+ .disk_label is defined as a text representation of the volume
+ label converted to a special image. See this link for
+ more details: http://refit.sourceforge.net/info/vollabel.html
+
+ .VolumeIcon.icns is defined as an ICNS image format of 512x512.
+ """
+
+ log.info("mactel creating Qubes Artwork")
+
+ artwork_dir = self._mactel_artwork_dir
+ if not os.path.exists(artwork_dir):
+ log.debug("mactel using sysroot for artwork prefix")
+ artwork_dir = iutil.getSysroot() + artwork_dir
+ if not os.path.exists(artwork_dir):
+ log.warning("mactel artwork missing from: %s", artwork_dir)
+ return
+
+ icon_src = artwork_dir + ".icns"
+ if os.path.exists(icon_src):
+ icon_dst_fil = "{}/{}".format(iutil.getSysroot() + self._mountpoint, ".VolumeIcon.icns")
+ self._copy_file(icon_src, icon_dst_fil)
+ else:
+ log.warning("mactel volume icon not found: %s", icon_src)
+
+ src_files = os.listdir(artwork_dir)
+ for file_name in src_files:
+ full_file_name = "{}/{}".format(artwork_dir, file_name)
+ if os.path.isfile(full_file_name):
+ sys_dir_file_name = "{}/{}".format(iutil.getSysroot() + self._mactel_sys_dir, "." + file_name)
+ self._copy_file(full_file_name, sys_dir_file_name)
+ config_dir = iutil.getSysroot() + self.config_dir
+ if os.path.exists(config_dir):
+ dest = "{}/{}".format(config_dir, "." + file_name)
+ self._copy_file(full_file_name, dest)
+
def install(self, args=None):
super(MacEFIGRUB, self).install()
+ log.info("Installing mactel MacEFI")
self.mactel_config()
+ self.mactel_install_qubes_artwork()
def is_valid_stage1_device(self, device, early=False):
valid = super(MacEFIGRUB, self).is_valid_stage1_device(device, early)
@@ -1932,6 +2124,24 @@ class MacEFIGRUB(EFIGRUB):
log.debug("MacEFIGRUB.is_valid_stage1_device(%s) returning %s", device.name, valid)
return valid
+ @staticmethod
+ def _symlink(source="", target=""):
+ """Creates a symlink between source and target."""
+ try:
+ os.symlink(source, target)
+ except OSError as error:
+ log.error("mactel failed to symlink %s -> %s : %s", source, target, error)
+
+ @staticmethod
+ def _copy_file(source="", target=""):
+ """Copies source to target using shutil.
+
+ Also chmod to 0644."""
+ try:
+ shutil.copy(source, target)
+ os.chmod(target, 0o644)
+ except OSError as error:
+ log.error("mactel failed to copy %s to %s: %s", source, target, error)
# Inherit abstract methods from BootLoader
# pylint: disable=abstract-method
--
2.14.4