OcBootManagement: Support OPT and CMD+R with picker

This commit is contained in:
vit9696 2019-09-01 22:33:14 +03:00
parent a1fbe0757b
commit 8fe7bef825
6 changed files with 334 additions and 86 deletions

View File

@ -20,6 +20,18 @@
#include <Library/OcAppleBootPolicyLib.h>
#include <Protocol/LoadedImage.h>
/**
Operating system boot type.
WARNING: This is only for debug purposes.
**/
typedef enum OC_BOOT_ENTRY_TYPE_ {
OcBootUnknown,
OcBootApple,
OcBootAppleRecovery,
OcBootWindows,
OcBootCustom
} OC_BOOT_ENTRY_TYPE;
/**
Discovered boot entry.
Note, inner resources must be freed with OcResetBootEntry.
@ -40,9 +52,10 @@ typedef struct OC_BOOT_ENTRY_ {
//
CHAR16 *PathName;
//
// Set when this entry is a custom externally loadable tool entry.
// Heuristical value signalising about booted os.
// WARNING: This is only for debug purposes.
//
BOOLEAN IsCustom;
OC_BOOT_ENTRY_TYPE Type;
//
// Set when this entry is an externally available entry (e.g. USB).
//
@ -52,15 +65,6 @@ typedef struct OC_BOOT_ENTRY_ {
//
BOOLEAN IsFolder;
//
// Heuristical value signalising about recovery os.
//
BOOLEAN IsRecovery;
//
// Heuristical value signalising about Windows os (otherwise macOS).
// WARNING: This is only for debug purposes.
//
BOOLEAN IsWindows;
//
// Load option data (usually "boot args") size.
//
UINT32 LoadOptionsSize;
@ -270,7 +274,7 @@ EFI_STATUS
);
/**
Custom picker entry
Custom picker entry.
**/
typedef struct {
//
@ -283,6 +287,17 @@ typedef struct {
CONST CHAR8 *Path;
} OC_PICKER_ENTRY;
/**
Picker behaviour action.
**/
typedef enum {
OcPickerDefault = 0,
OcPickerShowPicker = 1,
OcPickerResetNvram = 2,
OcPickerBootApple = 3,
OcPickerBootAppleRecovery = 4,
} OC_PICKER_CMD;
/**
Boot picker context describing picker behaviour.
**/
@ -300,9 +315,10 @@ typedef struct {
//
UINT32 TimeoutSeconds;
//
// Show boot menu or just boot the default option.
// Define picker behaviour.
// For example, show boot menu or just boot the default option.
//
BOOLEAN ShowPicker;
OC_PICKER_CMD PickerCommand;
//
// Use custom (gOcVendorVariableGuid) for Boot#### variables.
//
@ -412,21 +428,19 @@ OcScanForBootEntries (
);
/**
Obtain default entry from the list.
Obtain default entry from picker context.
@param[in] Context Picker context.
@param[in,out] BootEntries Described list of entries, may get updated.
@param[in] NumBootEntries Positive number of boot entries.
@param[in] CustomBootGuid Use custom GUID for Boot#### lookup.
@param[in] LoadHandle Handle to skip (potential OpenCore handle).
@retval boot entry or NULL.
@retval boot entry or 0.
**/
OC_BOOT_ENTRY *
UINT32
OcGetDefaultBootEntry (
IN OUT OC_BOOT_ENTRY *BootEntries,
IN UINTN NumBootEntries,
IN BOOLEAN CustomBootGuid,
IN EFI_HANDLE LoadHandle OPTIONAL
IN OC_PICKER_CONTEXT *Context,
IN OUT OC_BOOT_ENTRY *BootEntries,
IN UINTN NumBootEntries
);
/**
@ -490,6 +504,16 @@ OcIsAppleHibernateWake (
VOID
);
/**
Check pressed hotkeys and update booter context based on this.
@param[in,out] Context Picker context.
**/
VOID
OcLoadPickerHotkeys (
IN OUT OC_PICKER_CONTEXT *Context
);
/**
Install missing boot policy, scan, and show simple boot menu.
@ -528,6 +552,18 @@ OcGetFileSystemPolicyType (
IN EFI_HANDLE Handle
);
/**
Check if supplied device path contains Apple bootloader.
@param[in] DevicePath Device path.
@retval TRUE for potentially Apple images.
**/
BOOLEAN
OcIsAppleBootDevicePath (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
);
/**
Get loaded image protocol for Apple bootloader.

View File

@ -297,9 +297,8 @@ InternalSetBootEntryFlags (
BOOLEAN Result;
INTN CmpResult;
BootEntry->Type = OcBootUnknown;
BootEntry->IsFolder = FALSE;
BootEntry->IsRecovery = FALSE;
BootEntry->IsWindows = FALSE;
DevicePathWalker = BootEntry->DevicePath;
@ -307,6 +306,9 @@ InternalSetBootEntryFlags (
return;
}
//
// TODO: Move this to a new OcIsAppleRecoveryBootDevicePath function.
//
while (!IsDevicePathEnd (DevicePathWalker)) {
if ((DevicePathType (DevicePathWalker) == MEDIA_DEVICE_PATH)
&& (DevicePathSubType (DevicePathWalker) == MEDIA_FILEPATH_DP)) {
@ -318,7 +320,7 @@ InternalSetBootEntryFlags (
//
BootEntry->IsFolder = (FilePath->PathName[Len - 1] == L'\\');
if (!BootEntry->IsRecovery) {
if (BootEntry->Type == OcBootUnknown) {
Result = OcOverflowSubUN (
Len,
L_STR_LEN (L"com.apple.recovery.boot"),
@ -332,7 +334,7 @@ InternalSetBootEntryFlags (
L_STR_SIZE_NT (L"com.apple.recovery.boot")
);
if (CmpResult == 0) {
BootEntry->IsRecovery = TRUE;
BootEntry->Type = OcBootAppleRecovery;
break;
}
}
@ -345,6 +347,10 @@ InternalSetBootEntryFlags (
DevicePathWalker = NextDevicePathNode (DevicePathWalker);
}
if (BootEntry->Type == OcBootUnknown && OcIsAppleBootDevicePath (BootEntry->DevicePath)) {
BootEntry->Type = OcBootApple;
}
}
EFI_STATUS
@ -544,7 +550,7 @@ InternalFillValidBootEntries (
DEBUG_BULK_INFO,
"OCB: Adding entry %u, external - %d, skip recovery - %d\n",
(UINT32) EntryIndex,
Entries[EntryIndex].IsExternal,
DevPathScanInfo->IsExternal,
DevPathScanInfo->SkipRecovery
));
DebugPrintDevicePath (DEBUG_BULK_INFO, "DevicePath", DevicePath);
@ -571,7 +577,7 @@ InternalFillValidBootEntries (
DEBUG ((
DEBUG_BULK_INFO,
"OCB: Adding entry %u recovery (%s) - %r\n",
Entries[EntryIndex].IsExternal,
DevPathScanInfo->IsExternal,
RecoveryPath != NULL ? RecoveryPath : L"<null>",
Status
));

View File

@ -272,7 +272,7 @@ InternalGetBootEntryByDevicePath (
for (Index = 0; Index < NumBootEntries; ++Index) {
BootEntry = &BootEntries[Index];
if (BootEntry->IsCustom) {
if (BootEntry->Type == OcBootCustom) {
continue;
}
@ -352,8 +352,19 @@ InternalIsAppleLegacyLoadApp (
return FALSE;
}
/**
Obtain default entry from the list.
@param[in,out] BootEntries Described list of entries, may get updated.
@param[in] NumBootEntries Positive number of boot entries.
@param[in] CustomBootGuid Use custom GUID for Boot#### lookup.
@param[in] LoadHandle Handle to skip (potential OpenCore handle).
@retval boot entry or NULL.
**/
STATIC
OC_BOOT_ENTRY *
OcGetDefaultBootEntry (
InternalGetDefaultBootEntry (
IN OUT OC_BOOT_ENTRY *BootEntries,
IN UINTN NumBootEntries,
IN BOOLEAN CustomBootGuid,
@ -695,6 +706,63 @@ OcGetDefaultBootEntry (
return NULL;
}
UINT32
OcGetDefaultBootEntry (
IN OC_PICKER_CONTEXT *Context,
IN OUT OC_BOOT_ENTRY *BootEntries,
IN UINTN NumBootEntries
)
{
UINT32 BootEntryIndex;
OC_BOOT_ENTRY *BootEntry;
UINTN Index;
BootEntry = InternalGetDefaultBootEntry (
BootEntries,
NumBootEntries,
Context->CustomBootGuid,
Context->ExcludeHandle
);
if (BootEntry != NULL) {
BootEntryIndex = (UINT32) (BootEntry - BootEntries);
DEBUG ((DEBUG_INFO, "OCB: Initial default is %u\n", BootEntryIndex));
} else {
BootEntryIndex = 0;
DEBUG ((DEBUG_INFO, "OCB: Initial default is 0, fallback\n"));
}
if (Context->PickerCommand == OcPickerBootApple) {
if (BootEntries[BootEntryIndex].Type != OcBootApple) {
for (Index = 0; Index < NumBootEntries; ++Index) {
if (BootEntries[Index].Type == OcBootApple) {
BootEntryIndex = (UINT32) Index;
DEBUG ((DEBUG_INFO, "OCB: Override default to Apple %u\n", BootEntryIndex));
break;
}
}
}
} else if (Context->PickerCommand == OcPickerBootAppleRecovery) {
if (BootEntries[BootEntryIndex].Type != OcBootAppleRecovery) {
if (BootEntryIndex + 1 < NumBootEntries
&& BootEntries[BootEntryIndex + 1].Type == OcBootAppleRecovery) {
BootEntryIndex = BootEntryIndex + 1;
DEBUG ((DEBUG_INFO, "OCB: Override default to Apple Recovery %u, next\n", BootEntryIndex));
} else {
for (Index = 0; Index < NumBootEntries; ++Index) {
if (BootEntries[Index].Type == OcBootAppleRecovery) {
BootEntryIndex = (UINT32) Index;
DEBUG ((DEBUG_INFO, "OCB: Override default option to Apple Recovery %u\n", BootEntryIndex));
break;
}
}
}
}
}
return BootEntryIndex;
}
#if 0
STATIC
VOID
@ -830,7 +898,7 @@ InternalLoadBootEntry (
if (DevicePath == NULL) {
return EFI_UNSUPPORTED;
}
} else if (BootEntry->IsCustom && BootEntry->DevicePath == NULL) {
} else if (BootEntry->Type == OcBootCustom && BootEntry->DevicePath == NULL) {
ASSERT (Context->CustomRead != NULL);
Status = Context->CustomRead (
@ -894,7 +962,7 @@ InternalLoadBootEntry (
LoadedImage->LoadOptionsSize = BootEntry->LoadOptionsSize;
LoadedImage->LoadOptions = BootEntry->LoadOptions;
if (BootEntry->IsCustom) {
if (BootEntry->Type == OcBootCustom) {
DEBUG ((
DEBUG_INFO,
"OCB: Custom DeviceHandle %p FilePath %p\n",

View File

@ -19,6 +19,7 @@
#include <IndustryStandard/AppleHibernate.h>
#include <Protocol/AppleBootPolicy.h>
#include <Protocol/AppleKeyMapAggregator.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleTextOut.h>
@ -56,7 +57,7 @@ OcDescribeBootEntry (
//
// Custom entries need no special description.
//
if (BootEntry->IsCustom) {
if (BootEntry->Type == OcBootCustom) {
return EFI_SUCCESS;
}
@ -106,14 +107,16 @@ OcDescribeBootEntry (
//
//
// Windows boot entry may have a custom name, so ensure IsWindows is set correctly.
// Windows boot entry may have a custom name, so ensure OcBootWindows is set correctly.
//
DEBUG ((DEBUG_INFO, "Trying to detect Microsoft BCD\n"));
Status = ReadFileSize (FileSystem, L"\\EFI\\Microsoft\\Boot\\BCD", &BcdSize);
if (!EFI_ERROR (Status)) {
BootEntry->IsWindows = TRUE;
if (BootEntry->Name == NULL) {
BootEntry->Name = AllocateCopyPool (sizeof (L"BOOTCAMP Windows"), L"BOOTCAMP Windows");
if (BootEntry->Type == OcBootUnknown) {
DEBUG ((DEBUG_INFO, "Trying to detect Microsoft BCD\n"));
Status = ReadFileSize (FileSystem, L"\\EFI\\Microsoft\\Boot\\BCD", &BcdSize);
if (!EFI_ERROR (Status)) {
BootEntry->Type = OcBootWindows;
if (BootEntry->Name == NULL) {
BootEntry->Name = AllocateCopyPool (sizeof (L"BOOTCAMP Windows"), L"BOOTCAMP Windows");
}
}
}
@ -122,7 +125,9 @@ OcDescribeBootEntry (
if (BootEntry->Name != NULL
&& (!StrCmp (BootEntry->Name, L"Recovery HD")
|| !StrCmp (BootEntry->Name, L"Recovery"))) {
BootEntry->IsRecovery = TRUE;
if (BootEntry->Type == OcBootUnknown || BootEntry->Type == OcBootApple) {
BootEntry->Type = OcBootAppleRecovery;
}
RecoveryBootName = InternalGetAppleRecoveryName (FileSystem, BootDirectoryName);
if (RecoveryBootName != NULL) {
FreePool (BootEntry->Name);
@ -323,12 +328,11 @@ OcScanForBootEntries (
DEBUG_CODE_BEGIN ();
DEBUG ((
DEBUG_INFO,
"Entry %u is %s at %s (W:%d|R:%d|F:%d)\n",
"Entry %u is %s at %s (T:%d|F:%d)\n",
(UINT32) Index,
Entries[Index].Name,
Entries[Index].PathName,
Entries[Index].IsWindows,
Entries[Index].IsRecovery,
Entries[Index].Type,
Entries[Index].IsFolder
));
@ -360,7 +364,7 @@ OcScanForBootEntries (
return EFI_OUT_OF_RESOURCES;
}
Entries[EntryIndex].IsCustom = TRUE;
Entries[EntryIndex].Type = OcBootCustom;
if (Index < Context->AbsoluteEntryCount) {
Entries[EntryIndex].DevicePath = ConvertTextToDevicePath (PathName);
@ -758,6 +762,131 @@ OcLoadBootEntry (
return Status;
}
STATIC
BOOLEAN
OcKeyMapHasModifier (
IN APPLE_KEY_MAP_AGGREGATOR_PROTOCOL *KeyMapAggregator,
IN APPLE_MODIFIER_MAP ModifierLeft,
IN APPLE_MODIFIER_MAP ModifierRight OPTIONAL
)
{
EFI_STATUS Status;
Status = KeyMapAggregator->ContainsKeyStrokes (
KeyMapAggregator,
ModifierLeft,
0,
NULL,
FALSE
);
if (EFI_ERROR (Status) && ModifierRight != 0) {
Status = KeyMapAggregator->ContainsKeyStrokes (
KeyMapAggregator,
ModifierRight,
0,
NULL,
FALSE
);
}
return !EFI_ERROR (Status);
}
STATIC
BOOLEAN
OcKeyMapHasKey (
IN APPLE_KEY_MAP_AGGREGATOR_PROTOCOL *KeyMapAggregator,
IN APPLE_KEY_CODE KeyCode
)
{
EFI_STATUS Status;
Status = KeyMapAggregator->ContainsKeyStrokes (
KeyMapAggregator,
0,
1,
&KeyCode,
FALSE
);
return !EFI_ERROR (Status);
}
VOID
OcLoadPickerHotkeys (
IN OUT OC_PICKER_CONTEXT *Context
)
{
EFI_STATUS Status;
APPLE_KEY_MAP_AGGREGATOR_PROTOCOL *KeyMap;
BOOLEAN HasCommand;
BOOLEAN HasOption;
BOOLEAN HasShift;
BOOLEAN HasKeyP;
BOOLEAN HasKeyR;
BOOLEAN HasKeyX;
Status = gBS->LocateProtocol (
&gAppleKeyMapAggregatorProtocolGuid,
NULL,
(VOID **) &KeyMap
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "OCB: Missing AppleKeyMapAggregator - %r\n", Status));
return;
}
//
// I do not like this code a little, as it is prone to race conditions during key presses.
// For the good false positives are not too critical here, and in reality users are not that fast.
//
// Reference key list:
// https://support.apple.com/HT201255
// https://support.apple.com/HT204904
//
// We are slightly more permissive than AppleBds, as we permit combining keys.
//
HasCommand = OcKeyMapHasModifier (KeyMap, APPLE_MODIFIER_LEFT_COMMAND, APPLE_MODIFIER_RIGHT_COMMAND);
HasOption = OcKeyMapHasModifier (KeyMap, APPLE_MODIFIER_LEFT_OPTION, APPLE_MODIFIER_RIGHT_OPTION);
HasShift = OcKeyMapHasModifier (KeyMap, APPLE_MODIFIER_LEFT_SHIFT, APPLE_MODIFIER_RIGHT_SHIFT);
HasKeyP = OcKeyMapHasKey (KeyMap, AppleHidUsbKbUsageKeyP);
HasKeyR = OcKeyMapHasKey (KeyMap, AppleHidUsbKbUsageKeyP);
HasKeyX = OcKeyMapHasKey (KeyMap, AppleHidUsbKbUsageKeyX);
if (HasOption && HasCommand && HasKeyP && HasKeyR) {
//
// TODO: Protect this with some policy?
//
DEBUG ((DEBUG_INFO, "OCB: CMD+OPT+P+R causes NVRAM reset\n"));
Context->PickerCommand = OcPickerResetNvram;
} else if (HasCommand && HasKeyR) {
DEBUG ((DEBUG_INFO, "OCB: CMD+R causes recovery to boot\n"));
Context->PickerCommand = OcPickerBootAppleRecovery;
} else if (HasKeyX) {
DEBUG ((DEBUG_INFO, "OCB: X causes macOS to boot\n"));
Context->PickerCommand = OcPickerBootApple;
} else if (HasOption) {
DEBUG ((DEBUG_INFO, "OCB: OPT causes picker to show\n"));
Context->PickerCommand = OcPickerShowPicker;
} else {
//
// In addition to these overrides we always have ShowPicker = YES in config.
// The following keys are not implemented:
// C - CD/DVD boot, legacy that is gone now.
// D - Diagnostics, could implement dumping stuff here in some future,
// but we will need to store the data before handling the key.
// Should also be DEBUG only for security reasons.
// N - Network boot, simply not supported (and bad for security).
// T - Target disk mode, simply not supported (and bad for security).
//
}
}
EFI_STATUS
OcRunSimpleBootPicker (
IN OC_PICKER_CONTEXT *Context
@ -767,9 +896,8 @@ OcRunSimpleBootPicker (
APPLE_BOOT_POLICY_PROTOCOL *AppleBootPolicy;
OC_BOOT_ENTRY *Chosen;
OC_BOOT_ENTRY *Entries;
OC_BOOT_ENTRY *Entry;
UINTN EntryCount;
UINT32 DefaultEntry;
INTN DefaultEntry;
AppleBootPolicy = OcAppleBootPolicyInstallProtocol (FALSE);
if (AppleBootPolicy == NULL) {
@ -801,13 +929,9 @@ OcRunSimpleBootPicker (
DEBUG ((DEBUG_INFO, "Performing OcShowSimpleBootMenu...\n"));
DefaultEntry = 0;
Entry = OcGetDefaultBootEntry (Entries, EntryCount, Context->CustomBootGuid, Context->ExcludeHandle);
if (Entry != NULL) {
DefaultEntry = (UINT32)(Entry - Entries);
}
DefaultEntry = OcGetDefaultBootEntry (Context, Entries, EntryCount);
if (Context->ShowPicker) {
if (Context->PickerCommand == OcPickerShowPicker) {
Status = OcShowSimpleBootMenu (
Entries,
EntryCount,
@ -831,10 +955,9 @@ OcRunSimpleBootPicker (
if (!EFI_ERROR (Status)) {
DEBUG ((
DEBUG_INFO,
"Should boot from %s (W:%d|R:%d|F:%d)\n",
"Should boot from %s (T:%d|F:%d)\n",
Chosen->Name,
Chosen->IsWindows,
Chosen->IsRecovery,
Chosen->Type,
Chosen->IsFolder
));
}

View File

@ -61,6 +61,7 @@
[Protocols]
gAppleBootPolicyProtocolGuid ## PRODUCES
gAppleKeyMapAggregatorProtocolGuid ## SOMETIMES_CONSUMES
gEfiSimpleFileSystemProtocolGuid ## SOMETIMES_CONSUMES
gEfiLoadedImageProtocolGuid ## SOMETIMES_CONSUMES
gEfiUsbIoProtocolGuid ## SOMETIMES_CONSUMES

View File

@ -220,6 +220,39 @@ InternalCheckScanPolicy (
return RETURN_SUCCESS;
}
BOOLEAN
OcIsAppleBootDevicePath (
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
)
{
EFI_DEVICE_PATH_PROTOCOL *CurrNode;
FILEPATH_DEVICE_PATH *LastNode;
UINTN PathLen;
UINTN Index;
LastNode = NULL;
for (CurrNode = DevicePath; !IsDevicePathEnd (CurrNode); CurrNode = NextDevicePathNode (CurrNode)) {
if (DevicePathType (CurrNode) == MEDIA_DEVICE_PATH && DevicePathSubType (CurrNode) == MEDIA_FILEPATH_DP) {
LastNode = (FILEPATH_DEVICE_PATH *) CurrNode;
}
}
if (LastNode != NULL) {
//
// Detect macOS by boot.efi in the bootloader name.
//
PathLen = OcFileDevicePathNameLen (LastNode);
if (PathLen >= L_STR_LEN ("boot.efi")) {
Index = PathLen - L_STR_LEN ("boot.efi");
return (Index == 0 || LastNode->PathName[Index - 1] == L'\\')
&& CompareMem (&LastNode->PathName[Index], L"boot.efi", L_STR_SIZE (L"boot.efi")) == 0;
}
}
return FALSE;
}
EFI_LOADED_IMAGE_PROTOCOL *
OcGetAppleBootLoadedImage (
IN EFI_HANDLE ImageHandle
@ -227,37 +260,18 @@ OcGetAppleBootLoadedImage (
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
EFI_DEVICE_PATH_PROTOCOL *CurrNode;
FILEPATH_DEVICE_PATH *LastNode;
BOOLEAN IsMacOS;
UINTN PathLen;
UINTN Index;
IsMacOS = FALSE;
Status = gBS->HandleProtocol (
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID **)&LoadedImage
);
Status = gBS->HandleProtocol (ImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage);
if (!EFI_ERROR (Status) && LoadedImage->FilePath) {
LastNode = NULL;
for (CurrNode = LoadedImage->FilePath; !IsDevicePathEnd (CurrNode); CurrNode = NextDevicePathNode (CurrNode)) {
if (DevicePathType (CurrNode) == MEDIA_DEVICE_PATH && DevicePathSubType (CurrNode) == MEDIA_FILEPATH_DP) {
LastNode = (FILEPATH_DEVICE_PATH *) CurrNode;
}
}
if (LastNode != NULL) {
//
// Detect macOS by boot.efi in the bootloader name.
//
PathLen = OcFileDevicePathNameLen (LastNode);
if (PathLen >= L_STR_LEN ("boot.efi")) {
Index = PathLen - L_STR_LEN ("boot.efi");
IsMacOS = (Index == 0 || LastNode->PathName[Index - 1] == L'\\')
&& CompareMem (&LastNode->PathName[Index], L"boot.efi", L_STR_SIZE (L"boot.efi")) == 0;
}
}
if (!EFI_ERROR (Status)
&& LoadedImage->FilePath != NULL
&& OcIsAppleBootDevicePath (LoadedImage->FilePath)) {
return LoadedImage;
}
return IsMacOS ? LoadedImage : NULL;
return NULL;
}