Merge remote-tracking branch 'qubesos/pr/20'

* qubesos/pr/20:
  Fix macOS EFI Installation
This commit is contained in:
Marek Marczykowski-Górecki 2018-02-26 01:04:06 +01:00
commit 7dbbe7c5c4
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

View File

@ -1908,16 +1908,208 @@ class XenEFI(EFIGRUB):
write_config = BootLoader.write_config
class MacEFIGRUB(EFIGRUB):
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")
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):
"""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