@ -1875,16 +1875,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 : 1024 M dom0_mem = max : 4096 M 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 512 x512 .
"""
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 )
@ -1899,6 +2091,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