mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-27 12:15:19 +01:00
cd23181296
Signed-off-by: SergeySlice <sergey.slice@gmail.com>
1812 lines
47 KiB
C
1812 lines
47 KiB
C
/** @file
|
||
Copyright (C) 2019, vit9696. All rights reserved.
|
||
|
||
All rights reserved.
|
||
|
||
This program and the accompanying materials
|
||
are licensed and made available under the terms and conditions of the BSD License
|
||
which accompanies this distribution. The full text of the license may be found at
|
||
http://opensource.org/licenses/bsd-license.php
|
||
|
||
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
||
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||
**/
|
||
|
||
#include "BootManagementInternal.h"
|
||
|
||
#include <Protocol/DevicePath.h>
|
||
#include <Protocol/SimpleFileSystem.h>
|
||
|
||
#include <Guid/AppleVariable.h>
|
||
#include <Guid/FileInfo.h>
|
||
#include <Guid/GlobalVariable.h>
|
||
#include <Guid/OcVariable.h>
|
||
|
||
#include <Library/BaseLib.h>
|
||
#include <Library/BaseMemoryLib.h>
|
||
#include <Library/OcDebugLogLib.h>
|
||
#include <Library/DevicePathLib.h>
|
||
#include <Library/MemoryAllocationLib.h>
|
||
#include <Library/OcBootManagementLib.h>
|
||
#include <Library/OcDevicePathLib.h>
|
||
#include <Library/OcFileLib.h>
|
||
#include <Library/OcStringLib.h>
|
||
#include <Library/UefiBootServicesTableLib.h>
|
||
#include <Library/UefiLib.h>
|
||
#include <Library/UefiRuntimeServicesTableLib.h>
|
||
#include <Library/PrintLib.h>
|
||
|
||
/*
|
||
Expands DevicePath from short-form to full-form.
|
||
The only valid expansions are full Device Paths refering to a file or a
|
||
volume root. Latter type may be used with custom policies to determine a
|
||
bootable file.
|
||
|
||
@param[in] BootContext Context of filesystems.
|
||
@param[in] DevicePath The Device Path to expand.
|
||
@param[in] LazyScan Lazy filesystem scanning.
|
||
@param[out] FileSystem Resulting filesystem.
|
||
@param[out] IsRoot Whether DevicePath refers to the root of a volume.
|
||
|
||
@returns DevicePath expansion or NULL on failure.
|
||
*/
|
||
STATIC
|
||
EFI_DEVICE_PATH_PROTOCOL *
|
||
ExpandShortFormBootPath (
|
||
IN OC_BOOT_CONTEXT *BootContext,
|
||
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
|
||
IN BOOLEAN LazyScan,
|
||
OUT OC_BOOT_FILESYSTEM **FileSystem,
|
||
OUT BOOLEAN *IsRoot
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
|
||
EFI_DEVICE_PATH_PROTOCOL *FullDevicePath;
|
||
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath;
|
||
EFI_DEVICE_PATH_PROTOCOL *PrevDevicePath;
|
||
|
||
EFI_HANDLE FileSystemHandle;
|
||
EFI_FILE_PROTOCOL *File;
|
||
EFI_FILE_INFO *FileInfo;
|
||
BOOLEAN IsRootPath;
|
||
BOOLEAN IsDirectory;
|
||
|
||
ASSERT (BootContext != NULL);
|
||
ASSERT (DevicePath != NULL);
|
||
ASSERT (FileSystem != NULL);
|
||
ASSERT (IsRoot != NULL);
|
||
|
||
//
|
||
// Iteratively expand the short-form Device Path to its possible full forms.
|
||
// A valid Device Path will either refer to a valid file or to a valid root
|
||
// volume.
|
||
//
|
||
PrevDevicePath = NULL;
|
||
IsDirectory = FALSE;
|
||
do {
|
||
FullDevicePath = OcGetNextLoadOptionDevicePath (
|
||
DevicePath,
|
||
PrevDevicePath
|
||
);
|
||
|
||
if (PrevDevicePath != NULL) {
|
||
FreePool (PrevDevicePath);
|
||
}
|
||
|
||
//
|
||
// When no more full representations can be built, the Device Path is
|
||
// not bootable.
|
||
//
|
||
if (FullDevicePath == NULL) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Short-form DP could not be expanded\n"));
|
||
return NULL;
|
||
}
|
||
|
||
PrevDevicePath = FullDevicePath;
|
||
|
||
DebugPrintDevicePath (
|
||
DEBUG_INFO,
|
||
"OCB: Expanded DP",
|
||
FullDevicePath
|
||
);
|
||
|
||
//
|
||
// Retrieve the filesystem handle.
|
||
//
|
||
RemainingDevicePath = FullDevicePath;
|
||
Status = gBS->LocateDevicePath (
|
||
&gEfiSimpleFileSystemProtocolGuid,
|
||
&RemainingDevicePath,
|
||
&FileSystemHandle
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
continue;
|
||
}
|
||
|
||
DebugPrintDevicePath (
|
||
DEBUG_INFO,
|
||
"OCB: Expanded DP remainder",
|
||
RemainingDevicePath
|
||
);
|
||
|
||
//
|
||
// Check whether we are allowed to boot from this filesystem.
|
||
//
|
||
*FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan);
|
||
if (*FileSystem == NULL) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Check whether the Device Path refers to a valid file handle.
|
||
//
|
||
Status = OcOpenFileByRemainingDevicePath (
|
||
FileSystemHandle,
|
||
RemainingDevicePath,
|
||
&File,
|
||
EFI_FILE_MODE_READ,
|
||
0
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Retrieve file info to determine potentially bootable state.
|
||
//
|
||
FileInfo = GetFileInfo (
|
||
File,
|
||
&gEfiFileInfoGuid,
|
||
sizeof (EFI_FILE_INFO),
|
||
NULL
|
||
);
|
||
//
|
||
// When File Info cannot be retrieved, assume the worst case but don't
|
||
// skip the Device Path expansion as it is valid.
|
||
//
|
||
IsDirectory = TRUE;
|
||
if (FileInfo != NULL) {
|
||
IsDirectory = (FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0;
|
||
FreePool (FileInfo);
|
||
}
|
||
|
||
File->Close (File);
|
||
|
||
//
|
||
// Return only Device Paths that either refer to a file or a volume root.
|
||
// Root Device Paths may be expanded by custom policies (such as Apple Boot
|
||
// Policy) later.
|
||
//
|
||
IsRootPath = IsDevicePathEnd (RemainingDevicePath);
|
||
if (IsRootPath || !IsDirectory) {
|
||
ASSERT (FullDevicePath != NULL);
|
||
ASSERT (*FileSystem != NULL);
|
||
|
||
*IsRoot = IsDirectory;
|
||
return FullDevicePath;
|
||
}
|
||
|
||
//
|
||
// Request a new device path expansion.
|
||
//
|
||
} while (TRUE);
|
||
}
|
||
|
||
/**
|
||
Check whether device path points to OpenCore bootloader.
|
||
|
||
@param[in] DevicePath Device path of the entry.
|
||
|
||
@retval TRUE Entry represents OpenCore bootloader.
|
||
@retval FALSE Entry is not necessarily OpenCore bootloader.
|
||
**/
|
||
STATIC
|
||
BOOLEAN
|
||
IsOpenCoreBootloader (
|
||
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath
|
||
)
|
||
{
|
||
STATIC CONST UINT32 OpenCoreMagicOffset = 0x40;
|
||
STATIC CONST UINT8 OpenCoreMagic[] = {
|
||
0x0E, 0x1F, 0xBA, 0x10, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21, 0x0F, 0x0B,
|
||
0x4F, 0x70, 0x65, 0x6E, 0x43, 0x6F, 0x72, 0x65, 0x20, 0x42, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61,
|
||
0x64, 0x65, 0x72, 0x20, 0x28, 0x63, 0x29, 0x20, 0x41, 0x63, 0x69, 0x64, 0x61, 0x6E, 0x74, 0x68,
|
||
0x65, 0x72, 0x61, 0x20, 0x52, 0x65, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x0D, 0x0A, 0x24, 0x00
|
||
};
|
||
|
||
EFI_STATUS Status;
|
||
|
||
EFI_FILE_PROTOCOL *File;
|
||
UINT8 FileReadMagic[sizeof (OpenCoreMagic)];
|
||
|
||
Status = OcOpenFileByDevicePath (
|
||
&DevicePath,
|
||
&File,
|
||
EFI_FILE_MODE_READ,
|
||
0
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
return FALSE;
|
||
}
|
||
|
||
Status = GetFileData (
|
||
File,
|
||
OpenCoreMagicOffset,
|
||
sizeof (FileReadMagic),
|
||
FileReadMagic
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
return FALSE;
|
||
}
|
||
|
||
return CompareMem (FileReadMagic, OpenCoreMagic, sizeof (OpenCoreMagic)) == 0;
|
||
}
|
||
|
||
/**
|
||
Register bootable entry on the filesystem.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem for creation.
|
||
@param[in] BootEntry Entry to register.
|
||
**/
|
||
STATIC
|
||
VOID
|
||
RegisterBootOption (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem,
|
||
IN OC_BOOT_ENTRY *BootEntry
|
||
)
|
||
{
|
||
CHAR16 *TextDevicePath;
|
||
|
||
DEBUG_CODE_BEGIN ();
|
||
|
||
if (BootEntry->DevicePath != NULL) {
|
||
TextDevicePath = ConvertDevicePathToText (BootEntry->DevicePath, TRUE, FALSE);
|
||
} else {
|
||
TextDevicePath = NULL;
|
||
}
|
||
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Registering entry %s (T:%d|F:%d|G:%d|E:%d) - %s\n",
|
||
BootEntry->Name,
|
||
BootEntry->Type,
|
||
BootEntry->IsFolder,
|
||
BootEntry->IsGeneric,
|
||
BootEntry->IsExternal,
|
||
OC_HUMAN_STRING (TextDevicePath)
|
||
));
|
||
|
||
if (TextDevicePath != NULL) {
|
||
FreePool (TextDevicePath);
|
||
}
|
||
|
||
DEBUG_CODE_END ();
|
||
|
||
//
|
||
// Register boot entry.
|
||
// Not using RecoveryFs is intended for correct order.
|
||
//
|
||
InsertTailList (&FileSystem->BootEntries, &BootEntry->Link);
|
||
++BootContext->BootEntryCount;
|
||
|
||
//
|
||
// For tools and system options we are done.
|
||
//
|
||
if ((BootEntry->Type & (OC_BOOT_SYSTEM | OC_BOOT_EXTERNAL_TOOL)) != 0) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// If no options were previously found this is the default one.
|
||
//
|
||
if (BootContext->DefaultEntry == NULL) {
|
||
BootContext->DefaultEntry = BootEntry;
|
||
}
|
||
|
||
//
|
||
// Set override picker commands.
|
||
//
|
||
if (BootContext->PickerContext->PickerCommand == OcPickerBootApple) {
|
||
if (BootContext->DefaultEntry->Type != OC_BOOT_APPLE_OS
|
||
&& BootEntry->Type == OC_BOOT_APPLE_OS) {
|
||
BootContext->DefaultEntry = BootEntry;
|
||
}
|
||
} else if (BootContext->PickerContext->PickerCommand == OcPickerBootAppleRecovery) {
|
||
if (BootContext->DefaultEntry->Type != OC_BOOT_APPLE_RECOVERY
|
||
&& BootEntry->Type == OC_BOOT_APPLE_RECOVERY) {
|
||
BootContext->DefaultEntry = BootEntry;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
Create single bootable entry from device path.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem for creation.
|
||
@param[in] DevicePath Device path of the entry.
|
||
@param[in] RecoveryPart Device path is on recovery partition.
|
||
@param[in] Deduplicate Ensure that duplicated entries are not added.
|
||
|
||
@retval EFI_SUCCESS on success.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryOnFileSystem (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem,
|
||
IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
|
||
IN BOOLEAN RecoveryPart,
|
||
IN BOOLEAN Deduplicate
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
OC_BOOT_ENTRY *BootEntry;
|
||
OC_BOOT_ENTRY_TYPE EntryType;
|
||
LIST_ENTRY *Link;
|
||
OC_BOOT_ENTRY *ExistingEntry;
|
||
CHAR16 *TextDevicePath;
|
||
BOOLEAN IsFolder;
|
||
BOOLEAN IsGeneric;
|
||
|
||
EntryType = OcGetBootDevicePathType (DevicePath, &IsFolder, &IsGeneric);
|
||
|
||
DEBUG_CODE_BEGIN ();
|
||
|
||
TextDevicePath = ConvertDevicePathToText (DevicePath, TRUE, FALSE);
|
||
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Adding entry type (T:%u|F:%d|G:%d) - %s\n",
|
||
EntryType,
|
||
IsFolder,
|
||
IsGeneric,
|
||
OC_HUMAN_STRING (TextDevicePath)
|
||
));
|
||
|
||
if (TextDevicePath != NULL) {
|
||
FreePool (TextDevicePath);
|
||
}
|
||
|
||
DEBUG_CODE_END ();
|
||
|
||
//
|
||
// Mark self recovery presence.
|
||
//
|
||
if (!RecoveryPart && EntryType == OC_BOOT_APPLE_RECOVERY) {
|
||
FileSystem->HasSelfRecovery = TRUE;
|
||
}
|
||
|
||
//
|
||
// Do not add recoveries when not requested (e.g. can be HFS+ recovery).
|
||
//
|
||
if (BootContext->PickerContext->HideAuxiliary && EntryType == OC_BOOT_APPLE_RECOVERY) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Discarding recovery entry due to auxiliary\n"));
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Do not add Time Machine when not requested.
|
||
//
|
||
if (BootContext->PickerContext->HideAuxiliary && EntryType == OC_BOOT_APPLE_TIME_MACHINE) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Discarding time machine entry due to auxiliary\n"));
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Skip OpenCore bootloaders on own entry.
|
||
// We do not waste time doing this for other entries.
|
||
//
|
||
if (RecoveryPart ? FileSystem->RecoveryFs->LoaderFs : FileSystem->LoaderFs
|
||
&& IsOpenCoreBootloader (DevicePath)) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Discarding discovered OpenCore bootloader\n"));
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Skip duplicated entries, which may happen in BootOrder.
|
||
// For example, macOS during hibernation may leave Boot0082 in BootNext and Boot0080 in BootOrder,
|
||
// and they will have exactly the same boot entry.
|
||
//
|
||
if (Deduplicate) {
|
||
for (
|
||
Link = GetFirstNode (&FileSystem->BootEntries);
|
||
!IsNull (&FileSystem->BootEntries, Link);
|
||
Link = GetNextNode (&FileSystem->BootEntries, Link)) {
|
||
ExistingEntry = BASE_CR (Link, OC_BOOT_ENTRY, Link);
|
||
//
|
||
// All non-custom entries have DPs.
|
||
//
|
||
ASSERT (ExistingEntry->DevicePath != NULL);
|
||
if (IsDevicePathEqual (ExistingEntry->DevicePath, DevicePath)) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Discarding already present DP\n"));
|
||
//
|
||
// We may have more than one macOS installation in APFS container.
|
||
// Boot policy returns them in a defined (constant) order, and we want
|
||
// to preserve this order regardless of the BootOrder.
|
||
//
|
||
// When an operating system is present in BootOrder it will be put to
|
||
// the front of FileSystem boot entries. As a result instead of:
|
||
// [OS1], [REC1], [OS2], [REC2] we may get [OS2], [OS1], [REC1], [REC2].
|
||
// The latter happens because after [REC1] discovered [OS1] is skipped
|
||
// due to being already present. The code below moves [OS2] to the end
|
||
// of list at [REC1] stage to fix the order.
|
||
//
|
||
// This change assumes that only one operating system from the container
|
||
// can be present as a boot option. For now this appears to be true.
|
||
//
|
||
RemoveEntryList (Link);
|
||
InsertTailList (&FileSystem->BootEntries, Link);
|
||
return EFI_ALREADY_STARTED;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Allocate, initialise, and describe boot entry.
|
||
//
|
||
BootEntry = AllocateZeroPool (sizeof (*BootEntry));
|
||
if (BootEntry == NULL) {
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
BootEntry->DevicePath = DevicePath;
|
||
BootEntry->Type = EntryType;
|
||
BootEntry->IsFolder = IsFolder;
|
||
BootEntry->IsGeneric = IsGeneric;
|
||
BootEntry->IsExternal = RecoveryPart ? FileSystem->RecoveryFs->External : FileSystem->External;
|
||
|
||
Status = InternalDescribeBootEntry (BootEntry);
|
||
if (EFI_ERROR(Status)) {
|
||
FreePool (BootEntry);
|
||
return Status;
|
||
}
|
||
|
||
RegisterBootOption (
|
||
BootContext,
|
||
FileSystem,
|
||
BootEntry
|
||
);
|
||
|
||
return EFI_SUCCESS;
|
||
}
|
||
|
||
/**
|
||
Create bootable entry from custom entry.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem to add custom entry.
|
||
@param[in] CustomEntry Custom entry.
|
||
|
||
@retval EFI_SUCCESS on success.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryFromCustomEntry (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem,
|
||
IN OC_PICKER_ENTRY *CustomEntry
|
||
)
|
||
{
|
||
OC_BOOT_ENTRY *BootEntry;
|
||
CHAR16 *PathName;
|
||
FILEPATH_DEVICE_PATH *FilePath;
|
||
|
||
if (CustomEntry->Auxiliary && BootContext->PickerContext->HideAuxiliary) {
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Allocate, initialise, and describe boot entry.
|
||
//
|
||
BootEntry = AllocateZeroPool (sizeof (*BootEntry));
|
||
if (BootEntry == NULL) {
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
BootEntry->Name = AsciiStrCopyToUnicode (CustomEntry->Name, 0);
|
||
if (BootEntry->Name == NULL) {
|
||
FreePool (BootEntry);
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
PathName = AsciiStrCopyToUnicode (CustomEntry->Path, 0);
|
||
if (PathName == NULL) {
|
||
FreePool (BootEntry->Name);
|
||
FreePool (BootEntry);
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Adding custom entry %s (%a) -> %a\n",
|
||
BootEntry->Name,
|
||
CustomEntry->Tool ? "tool" : "os",
|
||
CustomEntry->Path
|
||
));
|
||
|
||
if (CustomEntry->Tool) {
|
||
BootEntry->Type = OC_BOOT_EXTERNAL_TOOL;
|
||
UnicodeUefiSlashes (PathName);
|
||
BootEntry->PathName = PathName;
|
||
} else {
|
||
BootEntry->Type = OC_BOOT_EXTERNAL_OS;
|
||
|
||
BootEntry->DevicePath = ConvertTextToDevicePath (PathName);
|
||
FreePool (PathName);
|
||
if (BootEntry->DevicePath == NULL) {
|
||
FreePool (BootEntry->Name);
|
||
FreePool (BootEntry);
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
FilePath = (FILEPATH_DEVICE_PATH *) (
|
||
FindDevicePathNodeWithType (
|
||
BootEntry->DevicePath,
|
||
MEDIA_DEVICE_PATH,
|
||
MEDIA_FILEPATH_DP
|
||
)
|
||
);
|
||
if (FilePath == NULL) {
|
||
FreePool (BootEntry->Name);
|
||
FreePool (BootEntry->DevicePath);
|
||
FreePool (BootEntry);
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
BootEntry->PathName = AllocateCopyPool (
|
||
OcFileDevicePathNameSize (FilePath),
|
||
FilePath->PathName
|
||
);
|
||
if (BootEntry->PathName == NULL) {
|
||
FreePool (BootEntry->Name);
|
||
FreePool (BootEntry->DevicePath);
|
||
FreePool (BootEntry);
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
}
|
||
|
||
BootEntry->LoadOptionsSize = (UINT32) AsciiStrLen (CustomEntry->Arguments);
|
||
if (BootEntry->LoadOptionsSize > 0) {
|
||
BootEntry->LoadOptions = AllocateCopyPool (
|
||
BootEntry->LoadOptionsSize + 1,
|
||
CustomEntry->Arguments
|
||
);
|
||
if (BootEntry->LoadOptions == NULL) {
|
||
BootEntry->LoadOptionsSize = 0;
|
||
}
|
||
}
|
||
|
||
RegisterBootOption (
|
||
BootContext,
|
||
FileSystem,
|
||
BootEntry
|
||
);
|
||
|
||
return EFI_SUCCESS;
|
||
}
|
||
|
||
/**
|
||
Create bootable entry from system entry.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem to add custom entry.
|
||
@param[in] Name System entry name.
|
||
@param[in] Action System entry action.
|
||
|
||
@retval EFI_SUCCESS on success.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryFromSystemEntry (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem,
|
||
IN CONST CHAR16 *Name,
|
||
IN OC_BOOT_SYSTEM_ACTION Action
|
||
)
|
||
{
|
||
OC_BOOT_ENTRY *BootEntry;
|
||
|
||
if (BootContext->PickerContext->HideAuxiliary) {
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Adding system entry %s\n", Name));
|
||
|
||
//
|
||
// Allocate, initialise, and describe boot entry.
|
||
//
|
||
BootEntry = AllocateZeroPool (sizeof (*BootEntry));
|
||
if (BootEntry == NULL) {
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
BootEntry->Name = AllocateCopyPool (StrSize (Name), Name);
|
||
if (BootEntry->Name == NULL) {
|
||
FreePool (BootEntry);
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
BootEntry->Type = OC_BOOT_RESET_NVRAM;
|
||
BootEntry->SystemAction = Action;
|
||
|
||
RegisterBootOption (
|
||
BootContext,
|
||
FileSystem,
|
||
BootEntry
|
||
);
|
||
|
||
return EFI_SUCCESS;
|
||
}
|
||
|
||
/**
|
||
Create bootable entries from bless policy.
|
||
This function may create more than one entry, and for APFS
|
||
it will likely produce a sequence of 'OS, RECOVERY' entry pairs.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem to scan for bless.
|
||
@param[in] PredefinedPaths The predefined boot file locations to scan.
|
||
@param[in] NumPredefinedPaths The number of elements in PredefinedPaths.
|
||
@param[in] LazyScan Lazy filesystem scanning.
|
||
@param[in] Deduplicate Ensure that duplicated entries are not added.
|
||
|
||
@retval EFI_STATUS for last created option.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryFromBless (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem,
|
||
IN CONST CHAR16 **PredefinedPaths,
|
||
IN UINTN NumPredefinedPaths,
|
||
IN BOOLEAN LazyScan,
|
||
IN BOOLEAN Deduplicate
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
EFI_STATUS PrimaryStatus;
|
||
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFs;
|
||
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
||
EFI_DEVICE_PATH_PROTOCOL *DevicePathWalker;
|
||
EFI_DEVICE_PATH_PROTOCOL *NewDevicePath;
|
||
UINTN NewDevicePathSize;
|
||
EFI_DEVICE_PATH_PROTOCOL *HdDevicePath;
|
||
UINTN HdPrefixSize;
|
||
INTN CmpResult;
|
||
EFI_FILE_PROTOCOL *Root;
|
||
CHAR16 *RecoveryPath;
|
||
EFI_FILE_PROTOCOL *RecoveryRoot;
|
||
EFI_HANDLE RecoveryDeviceHandle;
|
||
|
||
//
|
||
// We need to ensure that blessed device paths are on the same filesystem.
|
||
// Read the prefix path.
|
||
//
|
||
Status = gBS->HandleProtocol (
|
||
FileSystem->Handle,
|
||
&gEfiDevicePathProtocolGuid,
|
||
(VOID **) &HdDevicePath
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
DebugPrintDevicePath (DEBUG_INFO, "OCB: Adding bless entry on disk", HdDevicePath);
|
||
|
||
HdPrefixSize = GetDevicePathSize (HdDevicePath) - END_DEVICE_PATH_LENGTH;
|
||
|
||
//
|
||
// Custom bless paths have the priority, try to look them up first.
|
||
//
|
||
if (BootContext->PickerContext->NumCustomBootPaths > 0) {
|
||
Status = gBS->HandleProtocol (
|
||
FileSystem->Handle,
|
||
&gEfiSimpleFileSystemProtocolGuid,
|
||
(VOID **) &SimpleFs
|
||
);
|
||
|
||
if (!EFI_ERROR(Status)) {
|
||
Status = SimpleFs->OpenVolume (SimpleFs, &Root);
|
||
if (!EFI_ERROR(Status)) {
|
||
Status = OcGetBooterFromPredefinedPathList (
|
||
FileSystem->Handle,
|
||
Root,
|
||
(CONST CHAR16 **) BootContext->PickerContext->CustomBootPaths,
|
||
BootContext->PickerContext->NumCustomBootPaths,
|
||
&DevicePath,
|
||
NULL
|
||
);
|
||
|
||
Root->Close (Root);
|
||
}
|
||
}
|
||
} else {
|
||
Status = EFI_NOT_FOUND;
|
||
}
|
||
|
||
//
|
||
// On failure obtain normal bless paths.
|
||
//
|
||
if (EFI_ERROR(Status)) {
|
||
Status = OcBootPolicyGetBootFileEx (
|
||
FileSystem->Handle,
|
||
PredefinedPaths,
|
||
NumPredefinedPaths,
|
||
&DevicePath
|
||
);
|
||
}
|
||
|
||
//
|
||
// If both custom and normal found nothing, then nothing is blessed.
|
||
//
|
||
if (EFI_ERROR(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Since blessed paths can be multiple (e.g. when more than one macOS is present in the container).
|
||
//
|
||
Status = EFI_NOT_FOUND;
|
||
DevicePathWalker = DevicePath;
|
||
while (TRUE) {
|
||
NewDevicePath = GetNextDevicePathInstance (&DevicePathWalker, &NewDevicePathSize);
|
||
if (NewDevicePath == NULL) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Blessed path is obviously too short.
|
||
//
|
||
if (NewDevicePathSize - END_DEVICE_PATH_LENGTH < HdPrefixSize) {
|
||
FreePool (NewDevicePath);
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Blessed path does not prefix filesystem path.
|
||
//
|
||
CmpResult = CompareMem (
|
||
NewDevicePath,
|
||
HdDevicePath,
|
||
HdPrefixSize
|
||
);
|
||
if (CmpResult != 0) {
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Skipping handle %p instance due to self trust violation\n",
|
||
FileSystem->Handle
|
||
));
|
||
|
||
DebugPrintDevicePath (
|
||
DEBUG_INFO,
|
||
"OCB: Disk DP",
|
||
HdDevicePath
|
||
);
|
||
DebugPrintDevicePath (
|
||
DEBUG_INFO,
|
||
"OCB: Instance DP",
|
||
NewDevicePath
|
||
);
|
||
|
||
FreePool (NewDevicePath);
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Add blessed device path.
|
||
//
|
||
PrimaryStatus = AddBootEntryOnFileSystem (
|
||
BootContext,
|
||
FileSystem,
|
||
NewDevicePath,
|
||
FALSE,
|
||
Deduplicate
|
||
);
|
||
//
|
||
// Cannot free the failed device path now as it may have recovery.
|
||
//
|
||
|
||
//
|
||
// If the partition contains recovery on itself or recoveries are not requested,
|
||
// proceed to next entry.
|
||
//
|
||
// First part means that APFS recovery is irrelevant, these recoveries are actually
|
||
// on a different partition, but can only be pointed from Preboot.
|
||
// This way we will show any 'com.apple.recovery.boot' recovery physically present
|
||
// on the partition no more than once.
|
||
//
|
||
if (FileSystem->HasSelfRecovery || BootContext->PickerContext->HideAuxiliary) {
|
||
if (EFI_ERROR (PrimaryStatus)) {
|
||
FreePool (NewDevicePath);
|
||
}
|
||
Status = PrimaryStatus;
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Now add APFS recovery (from Recovery partition) right afterwards if present.
|
||
//
|
||
Status = OcBootPolicyGetApfsRecoveryFilePath (
|
||
NewDevicePath,
|
||
L"\\",
|
||
PredefinedPaths,
|
||
NumPredefinedPaths,
|
||
&RecoveryPath,
|
||
&RecoveryRoot,
|
||
&RecoveryDeviceHandle
|
||
);
|
||
|
||
//
|
||
// Can free the failed primary device path now.
|
||
//
|
||
if (EFI_ERROR (PrimaryStatus)) {
|
||
FreePool (NewDevicePath);
|
||
}
|
||
|
||
if (EFI_ERROR(Status)) {
|
||
DEBUG ((DEBUG_INFO, "OCB: APFS recovery is not present - %r\n", Status));
|
||
continue;
|
||
}
|
||
|
||
RecoveryRoot->Close (RecoveryRoot);
|
||
|
||
//
|
||
// Obtain recovery file system and ensure scan policy if it was not done before.
|
||
//
|
||
if (FileSystem->RecoveryFs == NULL) {
|
||
FileSystem->RecoveryFs = InternalFileSystemForHandle (BootContext, RecoveryDeviceHandle, LazyScan);
|
||
}
|
||
|
||
//
|
||
// If new recovery is not on the same volume or not allowed, then something went wrong, skip it.
|
||
// This is technically also a performance optimisation allowing us not to lookup recovery fs every time.
|
||
//
|
||
if (FileSystem->RecoveryFs == NULL || FileSystem->RecoveryFs->Handle != RecoveryDeviceHandle) {
|
||
FreePool (RecoveryPath);
|
||
continue;
|
||
}
|
||
|
||
NewDevicePath = FileDevicePath (RecoveryDeviceHandle, RecoveryPath);
|
||
FreePool (RecoveryPath);
|
||
if (NewDevicePath == NULL) {
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Add blessed device path.
|
||
//
|
||
Status = AddBootEntryOnFileSystem (
|
||
BootContext,
|
||
FileSystem,
|
||
NewDevicePath,
|
||
TRUE,
|
||
Deduplicate
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
FreePool (NewDevicePath);
|
||
}
|
||
}
|
||
|
||
FreePool (DevicePath);
|
||
|
||
return Status;
|
||
}
|
||
|
||
/**
|
||
Create bootable entries from recovery files (com.apple.boot.recovery) on the volume.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in,out] FileSystem Filesystem to scan for recovery.
|
||
|
||
@retval EFI_SUCCESS on success.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryFromSelfRecovery (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OUT OC_BOOT_FILESYSTEM *FileSystem
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
||
|
||
//
|
||
// If there is already one recovery (it may not be registered due to HideAuxiliary)
|
||
// or if there is HideAuxiliary, do not add recoveries at all.
|
||
//
|
||
if (FileSystem->HasSelfRecovery || BootContext->PickerContext->HideAuxiliary) {
|
||
return EFI_UNSUPPORTED;
|
||
}
|
||
|
||
Status = InternalGetRecoveryOsBooter (
|
||
FileSystem->Handle,
|
||
&DevicePath,
|
||
FALSE
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Returned device path is always on the same partition, thus no scan check.
|
||
//
|
||
Status = AddBootEntryOnFileSystem (
|
||
BootContext,
|
||
FileSystem,
|
||
DevicePath,
|
||
FALSE,
|
||
FALSE
|
||
);
|
||
|
||
if (EFI_ERROR(Status)) {
|
||
FreePool (DevicePath);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
/**
|
||
Create bootable entries from boot options.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in] BootOption Boot option number.
|
||
@param[in] LazyScan Lazy filesystem scanning.
|
||
|
||
@retval EFI_SUCCESS if at least one option was added.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddBootEntryFromBootOption (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN UINT16 BootOption,
|
||
IN BOOLEAN LazyScan
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
||
EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath;
|
||
EFI_HANDLE FileSystemHandle;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
UINTN DevicePathSize;
|
||
INTN NumPatchedNodes;
|
||
BOOLEAN IsAppleLegacy;
|
||
BOOLEAN IsRoot;
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Building entry from Boot%04x\n", BootOption));
|
||
|
||
//
|
||
// Obtain original device path.
|
||
// Discard load options for security reasons.
|
||
// Also discard boot name to avoid confusion.
|
||
//
|
||
DevicePath = InternalGetBootOptionData (
|
||
BootOption,
|
||
BootContext->BootVariableGuid,
|
||
NULL,
|
||
NULL,
|
||
NULL
|
||
);
|
||
if (DevicePath == NULL) {
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
|
||
//
|
||
// Get BootCamp device path stored in special variable.
|
||
// BootCamp device path will point to disk instead of partition.
|
||
//
|
||
IsAppleLegacy = InternalIsAppleLegacyLoadApp (DevicePath);
|
||
if (IsAppleLegacy) {
|
||
FreePool (DevicePath);
|
||
Status = GetVariable2 (
|
||
APPLE_BOOT_CAMP_HD_VARIABLE_NAME,
|
||
&gEfiAppleBootGuid,
|
||
(VOID **) &DevicePath,
|
||
&DevicePathSize
|
||
);
|
||
|
||
if (EFI_ERROR(Status) || !IsDevicePathValid (DevicePath, DevicePathSize)) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Legacy DP invalid - %r\n", Status));
|
||
if (!EFI_ERROR(Status)) {
|
||
FreePool (DevicePath);
|
||
}
|
||
return EFI_NOT_FOUND;
|
||
} else {
|
||
DebugPrintDevicePath (DEBUG_INFO, "OCB: Solved legacy DP", DevicePath);
|
||
}
|
||
}
|
||
|
||
FileSystem = NULL;
|
||
IsRoot = FALSE;
|
||
|
||
//
|
||
// Fixup device path if necessary.
|
||
//
|
||
RemainingDevicePath = DevicePath;
|
||
NumPatchedNodes = OcFixAppleBootDevicePath (&RemainingDevicePath);
|
||
if (NumPatchedNodes > 0) {
|
||
DebugPrintDevicePath (DEBUG_INFO, "OCB: Fixed DP", DevicePath);
|
||
}
|
||
|
||
//
|
||
// Expand BootCamp device path to EFI partition device path.
|
||
//
|
||
if (IsAppleLegacy) {
|
||
//
|
||
// BootCampHD always refers to a full Device Path. Failure to patch
|
||
// indicates an invalid Device Path.
|
||
//
|
||
if (NumPatchedNodes == -1) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Ignoring broken legacy DP\n"));
|
||
FreePool (DevicePath);
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
|
||
RemainingDevicePath = DevicePath;
|
||
DevicePath = OcDiskFindSystemPartitionPath (
|
||
DevicePath,
|
||
&DevicePathSize,
|
||
&FileSystemHandle
|
||
);
|
||
|
||
FreePool (RemainingDevicePath);
|
||
|
||
//
|
||
// This is obviously always a Root Device Path.
|
||
//
|
||
IsRoot = TRUE;
|
||
|
||
//
|
||
// Ensure that we are allowed to boot from this filesystem.
|
||
//
|
||
if (DevicePath != NULL) {
|
||
FileSystem = InternalFileSystemForHandle (BootContext, FileSystemHandle, LazyScan);
|
||
if (FileSystem == NULL) {
|
||
DevicePath = NULL;
|
||
}
|
||
}
|
||
|
||
//
|
||
// The Device Path returned by OcDiskFindSystemPartitionPath() is a pointer
|
||
// to an installed protocol. Duplicate it so we own the memory.
|
||
//
|
||
if (DevicePath != NULL) {
|
||
DevicePath = AllocateCopyPool (DevicePathSize, DevicePath);
|
||
}
|
||
|
||
if (DevicePath == NULL) {
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
|
||
//
|
||
// The Device Path must be entirely locatable (and hence full-form) as
|
||
// OcDiskFindSystemPartitionPath() guarantees to only return valid paths.
|
||
//
|
||
ASSERT (DevicePathSize > END_DEVICE_PATH_LENGTH);
|
||
DevicePathSize -= END_DEVICE_PATH_LENGTH;
|
||
RemainingDevicePath = (EFI_DEVICE_PATH_PROTOCOL *) ((UINTN) DevicePath + DevicePathSize);
|
||
} else if (DevicePath == RemainingDevicePath) {
|
||
//
|
||
// OcFixAppleBootDevicePath() did not advance the Device Path node, hence
|
||
// it cannot be located at all and may be a short-form Device Path.
|
||
// DevicePath has not been changed no matter success or failure.
|
||
//
|
||
DEBUG ((DEBUG_INFO, "OCB: Assuming DP is short-form (prefix)\n"));
|
||
|
||
//
|
||
// Expand and on failure fix the Device Path till both yields no new result.
|
||
//
|
||
RemainingDevicePath = DevicePath;
|
||
do {
|
||
//
|
||
// Expand the short-form Device Path.
|
||
//
|
||
DevicePath = ExpandShortFormBootPath (
|
||
BootContext,
|
||
RemainingDevicePath,
|
||
LazyScan,
|
||
&FileSystem,
|
||
&IsRoot
|
||
);
|
||
if (DevicePath != NULL) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// If short-form expansion failed, try to fix the short-form and re-try.
|
||
//
|
||
NumPatchedNodes = OcFixAppleBootDevicePathNode (RemainingDevicePath, NULL);
|
||
} while (NumPatchedNodes > 0);
|
||
|
||
FreePool (RemainingDevicePath);
|
||
|
||
if (DevicePath == NULL) {
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
} else if (NumPatchedNodes == -1) {
|
||
//
|
||
// OcFixAppleBootDevicePath() advanced the Device Path node and yet failed
|
||
// to locate the path, it is invalid.
|
||
//
|
||
DEBUG ((DEBUG_INFO, "OCB: Ignoring broken normal DP\n"));
|
||
FreePool (DevicePath);
|
||
return EFI_NOT_FOUND;
|
||
} else {
|
||
//
|
||
// OcFixAppleBootDevicePath() advanced the Device Path node and succeeded
|
||
// to locate the path, but it may still be a shot-form Device Path (lacking
|
||
// a suffix rather than prefix).
|
||
//
|
||
DEBUG ((DEBUG_INFO, "OCB: Assuming DP is full-form or lacks suffix\n"));
|
||
|
||
RemainingDevicePath = DevicePath;
|
||
DevicePath = ExpandShortFormBootPath (
|
||
BootContext,
|
||
RemainingDevicePath,
|
||
LazyScan,
|
||
&FileSystem,
|
||
&IsRoot
|
||
);
|
||
|
||
FreePool (RemainingDevicePath);
|
||
|
||
if (DevicePath == NULL) {
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If we reached here we have a filesystem and device path.
|
||
//
|
||
ASSERT (FileSystem != NULL);
|
||
ASSERT (DevicePath != NULL);
|
||
|
||
//
|
||
// We have a complete device path, just add this entry.
|
||
//
|
||
if (!IsRoot) {
|
||
Status = AddBootEntryOnFileSystem (
|
||
BootContext,
|
||
FileSystem,
|
||
DevicePath,
|
||
FALSE,
|
||
TRUE
|
||
);
|
||
} else {
|
||
Status = EFI_UNSUPPORTED;
|
||
}
|
||
|
||
if (EFI_ERROR(Status)) {
|
||
FreePool (DevicePath);
|
||
}
|
||
|
||
//
|
||
// We may have a Boot#### entry pointing to macOS with full DP (up to boot.efi),
|
||
// so IsRoot will be true. However, if this is APFS, we may still have:
|
||
// - Recovery for this macOS.
|
||
// - Another macOS installation.
|
||
// We can only detect them with bless, so we invoke bless in deduplication mode.
|
||
// We also detect only the Core Apple Boot Policy predefined booter paths to
|
||
// avoid detection of e.g. generic booters (such as BOOTx64) to avoid
|
||
// duplicates.
|
||
//
|
||
// The amount of paths depends on the kind of the entry.
|
||
// - If this is a root entry (i.e. it points to the partition)
|
||
// we invoke full bless, as it may be Windows entry created by legacy NVRAM script.
|
||
// - If this is a full entry (i.e. it points to the bootloader)
|
||
// we invoke partial bless, which ignores BOOTx64.efi.
|
||
// Ignoring BOOTx64.efi is important as we may already have bootmgfw.efi as our entry,
|
||
// and we do not want to see Windows added twice.
|
||
//
|
||
Status = AddBootEntryFromBless (
|
||
BootContext,
|
||
FileSystem,
|
||
gAppleBootPolicyPredefinedPaths,
|
||
IsRoot ? gAppleBootPolicyNumPredefinedPaths : gAppleBootPolicyCoreNumPredefinedPaths,
|
||
LazyScan,
|
||
TRUE
|
||
);
|
||
|
||
return Status;
|
||
}
|
||
|
||
/**
|
||
Release boot entry contents allocated from pool.
|
||
|
||
@param[in,out] BootEntry Located boot entry.
|
||
**/
|
||
STATIC
|
||
VOID
|
||
FreeBootEntry (
|
||
IN OC_BOOT_ENTRY *BootEntry
|
||
)
|
||
{
|
||
if (BootEntry->DevicePath != NULL) {
|
||
FreePool (BootEntry->DevicePath);
|
||
BootEntry->DevicePath = NULL;
|
||
}
|
||
|
||
if (BootEntry->Name != NULL) {
|
||
FreePool (BootEntry->Name);
|
||
BootEntry->Name = NULL;
|
||
}
|
||
|
||
if (BootEntry->PathName != NULL) {
|
||
FreePool (BootEntry->PathName);
|
||
BootEntry->PathName = NULL;
|
||
}
|
||
|
||
if (BootEntry->LoadOptions != NULL) {
|
||
FreePool (BootEntry->LoadOptions);
|
||
BootEntry->LoadOptions = NULL;
|
||
BootEntry->LoadOptionsSize = 0;
|
||
}
|
||
|
||
FreePool (BootEntry);
|
||
}
|
||
|
||
/**
|
||
Allocate a new filesystem entry in boot entries
|
||
in case it can be used according to current ScanPolicy.
|
||
|
||
@param[in,out] BootContext Context of filesystems.
|
||
@param[in] FileSystemHandle Filesystem handle.
|
||
@param[in] FileSystemEntry Resulting filesystem, optional.
|
||
|
||
@retval EFI_SUCCESS on success.
|
||
**/
|
||
STATIC
|
||
EFI_STATUS
|
||
AddFileSystemEntry (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN EFI_HANDLE FileSystemHandle,
|
||
OUT OC_BOOT_FILESYSTEM **FileSystemEntry OPTIONAL
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
EFI_STATUS TmpStatus;
|
||
BOOLEAN IsExternal;
|
||
BOOLEAN LoaderFs;
|
||
OC_BOOT_FILESYSTEM *Entry;
|
||
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
|
||
CHAR16 *TextDevicePath;
|
||
|
||
Status = InternalCheckScanPolicy (
|
||
FileSystemHandle,
|
||
BootContext->PickerContext->ScanPolicy,
|
||
&IsExternal
|
||
);
|
||
|
||
LoaderFs = BootContext->PickerContext->LoaderHandle == FileSystemHandle;
|
||
|
||
DEBUG_CODE_BEGIN ();
|
||
|
||
TmpStatus = gBS->HandleProtocol (
|
||
FileSystemHandle,
|
||
&gEfiDevicePathProtocolGuid,
|
||
(VOID **) &DevicePath
|
||
);
|
||
if (!EFI_ERROR (TmpStatus)) {
|
||
TextDevicePath = ConvertDevicePathToText (DevicePath, TRUE, FALSE);
|
||
} else {
|
||
TextDevicePath = NULL;
|
||
}
|
||
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Adding fs %p (E:%d|L:%d|P:%r) - %s\n",
|
||
FileSystemHandle,
|
||
IsExternal,
|
||
LoaderFs,
|
||
Status,
|
||
OC_HUMAN_STRING (TextDevicePath)
|
||
));
|
||
|
||
if (TextDevicePath != NULL) {
|
||
FreePool (TextDevicePath);
|
||
}
|
||
|
||
DEBUG_CODE_END ();
|
||
|
||
if (EFI_ERROR(Status)) {
|
||
return Status;
|
||
}
|
||
|
||
Entry = AllocatePool (sizeof (*Entry));
|
||
if (Entry == NULL) {
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
Entry->Handle = FileSystemHandle;
|
||
InitializeListHead (&Entry->BootEntries);
|
||
Entry->RecoveryFs = NULL;
|
||
Entry->External = IsExternal;
|
||
Entry->LoaderFs = LoaderFs;
|
||
Entry->HasSelfRecovery = FALSE;
|
||
InsertTailList (&BootContext->FileSystems, &Entry->Link);
|
||
++BootContext->FileSystemCount;
|
||
|
||
if (FileSystemEntry != NULL) {
|
||
*FileSystemEntry = Entry;
|
||
}
|
||
|
||
return EFI_SUCCESS;
|
||
}
|
||
|
||
STATIC
|
||
EFI_STATUS
|
||
AddFileSystemEntryForCustom (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
UINTN Index;
|
||
|
||
//
|
||
// When there are no custom entries and NVRAM reset is hidden
|
||
// we have no work to do.
|
||
//
|
||
if (BootContext->PickerContext->AllCustomEntryCount == 0
|
||
&& (!BootContext->PickerContext->ShowNvramReset
|
||
|| BootContext->PickerContext->HideAuxiliary)) {
|
||
return EFI_NOT_FOUND;
|
||
}
|
||
|
||
DEBUG ((
|
||
DEBUG_INFO,
|
||
"OCB: Adding fs %p for %u custom entries%a%a\n",
|
||
OC_CUSTOM_FS_HANDLE,
|
||
BootContext->PickerContext->AllCustomEntryCount,
|
||
BootContext->PickerContext->ShowNvramReset ? " and nvram reset" : "",
|
||
BootContext->PickerContext->HideAuxiliary ? " (aux hidden)" : " (aux shown)"
|
||
));
|
||
|
||
FileSystem = AllocateZeroPool (sizeof (*FileSystem));
|
||
if (FileSystem == NULL) {
|
||
return EFI_OUT_OF_RESOURCES;
|
||
}
|
||
|
||
FileSystem->Handle = OC_CUSTOM_FS_HANDLE;
|
||
InitializeListHead (&FileSystem->BootEntries);
|
||
InsertTailList (&BootContext->FileSystems, &FileSystem->Link);
|
||
++BootContext->FileSystemCount;
|
||
|
||
Status = EFI_NOT_FOUND;
|
||
for (Index = 0; Index < BootContext->PickerContext->AllCustomEntryCount; ++Index) {
|
||
Status = AddBootEntryFromCustomEntry (
|
||
BootContext,
|
||
FileSystem,
|
||
&BootContext->PickerContext->CustomEntries[Index]
|
||
);
|
||
}
|
||
|
||
if (BootContext->PickerContext->ShowNvramReset) {
|
||
Status = AddBootEntryFromSystemEntry (
|
||
BootContext,
|
||
FileSystem,
|
||
OC_MENU_RESET_NVRAM_ENTRY,
|
||
InternalSystemActionResetNvram
|
||
);
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
STATIC
|
||
VOID
|
||
FreeFileSystemEntry (
|
||
IN OUT OC_BOOT_CONTEXT *BootContext,
|
||
IN OC_BOOT_FILESYSTEM *FileSystemEntry
|
||
)
|
||
{
|
||
LIST_ENTRY *Link;
|
||
OC_BOOT_ENTRY *BootEntry;
|
||
|
||
RemoveEntryList (&FileSystemEntry->Link);
|
||
--BootContext->FileSystemCount;
|
||
|
||
while (!IsListEmpty (&FileSystemEntry->BootEntries)) {
|
||
Link = GetFirstNode (&FileSystemEntry->BootEntries);
|
||
BootEntry = BASE_CR (Link, OC_BOOT_ENTRY, Link);
|
||
RemoveEntryList (Link);
|
||
FreeBootEntry (BootEntry);
|
||
}
|
||
|
||
FreePool (FileSystemEntry);
|
||
}
|
||
|
||
OC_BOOT_FILESYSTEM *
|
||
InternalFileSystemForHandle (
|
||
IN OC_BOOT_CONTEXT *BootContext,
|
||
IN EFI_HANDLE FileSystemHandle,
|
||
IN BOOLEAN LazyScan
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
LIST_ENTRY *Link;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
|
||
for (
|
||
Link = GetFirstNode (&BootContext->FileSystems);
|
||
!IsNull (&BootContext->FileSystems, Link);
|
||
Link = GetNextNode (&BootContext->FileSystems, Link)) {
|
||
FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link);
|
||
|
||
if (FileSystem->Handle == FileSystemHandle) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Matched fs %p%a\n", FileSystemHandle, LazyScan ? " (lazy)" : ""));
|
||
return FileSystem;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Lazily check filesystem scan policy and add it in case it is ok.
|
||
//
|
||
if (!LazyScan) {
|
||
DEBUG ((DEBUG_INFO, "OCB: Restricted fs %p access\n", FileSystemHandle));
|
||
return NULL;
|
||
}
|
||
|
||
Status = AddFileSystemEntry (BootContext, FileSystemHandle, &FileSystem);
|
||
if (!EFI_ERROR(Status)) {
|
||
return FileSystem;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
STATIC
|
||
OC_BOOT_CONTEXT *
|
||
BuildFileSystemList (
|
||
IN OC_PICKER_CONTEXT *Context,
|
||
IN BOOLEAN Empty
|
||
)
|
||
{
|
||
OC_BOOT_CONTEXT *BootContext;
|
||
EFI_STATUS Status;
|
||
UINTN NoHandles;
|
||
EFI_HANDLE *Handles;
|
||
UINTN Index;
|
||
|
||
BootContext = AllocatePool (sizeof (*Context));
|
||
if (BootContext == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
BootContext->BootEntryCount = 0;
|
||
BootContext->FileSystemCount = 0;
|
||
InitializeListHead (&BootContext->FileSystems);
|
||
if (Context->CustomBootGuid) {
|
||
BootContext->BootVariableGuid = &gOcVendorVariableGuid;
|
||
} else {
|
||
BootContext->BootVariableGuid = &gEfiGlobalVariableGuid;
|
||
}
|
||
BootContext->DefaultEntry = NULL;
|
||
BootContext->PickerContext = Context;
|
||
|
||
if (Empty) {
|
||
return BootContext;
|
||
}
|
||
|
||
Status = gBS->LocateHandleBuffer (
|
||
ByProtocol,
|
||
&gEfiSimpleFileSystemProtocolGuid,
|
||
NULL,
|
||
&NoHandles,
|
||
&Handles
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
return BootContext;
|
||
}
|
||
|
||
for (Index = 0; Index < NoHandles; ++Index) {
|
||
AddFileSystemEntry (
|
||
BootContext,
|
||
Handles[Index],
|
||
NULL
|
||
);
|
||
}
|
||
|
||
FreePool (Handles);
|
||
return BootContext;
|
||
}
|
||
|
||
VOID
|
||
OcFreeBootContext (
|
||
IN OUT OC_BOOT_CONTEXT *Context
|
||
)
|
||
{
|
||
LIST_ENTRY *Link;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
|
||
while (!IsListEmpty (&Context->FileSystems)) {
|
||
Link = GetFirstNode (&Context->FileSystems);
|
||
FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link);
|
||
FreeFileSystemEntry (Context, FileSystem);
|
||
}
|
||
|
||
FreePool (Context);
|
||
}
|
||
|
||
OC_BOOT_CONTEXT *
|
||
OcScanForBootEntries (
|
||
IN OC_PICKER_CONTEXT *Context
|
||
)
|
||
{
|
||
OC_BOOT_CONTEXT *BootContext;
|
||
UINTN Index;
|
||
LIST_ENTRY *Link;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
|
||
//
|
||
// Obtain the list of filesystems filtered by scan policy.
|
||
//
|
||
BootContext = BuildFileSystemList (
|
||
Context,
|
||
FALSE
|
||
);
|
||
if (BootContext == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Found %u potentially bootable filesystems\n", (UINT32) BootContext->FileSystemCount));
|
||
|
||
//
|
||
// Create primary boot options from BootOrder.
|
||
//
|
||
if (Context->BootOrder == NULL) {
|
||
Context->BootOrder = InternalGetBootOrderForBooting (
|
||
BootContext->BootVariableGuid,
|
||
&Context->BootOrderCount
|
||
);
|
||
}
|
||
|
||
if (Context->BootOrder != NULL) {
|
||
for (Index = 0; Index < Context->BootOrderCount; ++Index) {
|
||
AddBootEntryFromBootOption (BootContext, Context->BootOrder[Index], FALSE);
|
||
}
|
||
}
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Processing blessed list\n"));
|
||
|
||
//
|
||
// Create primary boot options on filesystems without options
|
||
// and alternate boot options on all filesystems.
|
||
//
|
||
for (
|
||
Link = GetFirstNode (&BootContext->FileSystems);
|
||
!IsNull (&BootContext->FileSystems, Link);
|
||
Link = GetNextNode (&BootContext->FileSystems, Link)) {
|
||
FileSystem = BASE_CR (Link, OC_BOOT_FILESYSTEM, Link);
|
||
|
||
//
|
||
// No entries, so we process this directory with Apple Bless.
|
||
//
|
||
if (IsListEmpty (&FileSystem->BootEntries)) {
|
||
AddBootEntryFromBless (
|
||
BootContext,
|
||
FileSystem,
|
||
gAppleBootPolicyPredefinedPaths,
|
||
gAppleBootPolicyNumPredefinedPaths,
|
||
FALSE,
|
||
FALSE
|
||
);
|
||
}
|
||
|
||
//
|
||
// Record predefined recoveries.
|
||
//
|
||
AddBootEntryFromSelfRecovery (BootContext, FileSystem);
|
||
}
|
||
|
||
//
|
||
// Build custom and system options.
|
||
//
|
||
AddFileSystemEntryForCustom (BootContext);
|
||
|
||
if (BootContext->BootEntryCount == 0) {
|
||
OcFreeBootContext (BootContext);
|
||
return NULL;
|
||
}
|
||
|
||
return BootContext;
|
||
}
|
||
|
||
OC_BOOT_CONTEXT *
|
||
OcScanForDefaultBootEntry (
|
||
IN OC_PICKER_CONTEXT *Context
|
||
)
|
||
{
|
||
OC_BOOT_CONTEXT *BootContext;
|
||
UINTN Index;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
EFI_STATUS Status;
|
||
UINTN NoHandles;
|
||
EFI_HANDLE *Handles;
|
||
|
||
//
|
||
// Obtain empty list of filesystems.
|
||
//
|
||
BootContext = BuildFileSystemList (Context, TRUE);
|
||
if (BootContext == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Looking up for default entry\n"));
|
||
|
||
//
|
||
// Create primary boot options from BootOrder.
|
||
//
|
||
if (Context->BootOrder == NULL) {
|
||
Context->BootOrder = InternalGetBootOrderForBooting (
|
||
BootContext->BootVariableGuid,
|
||
&Context->BootOrderCount
|
||
);
|
||
}
|
||
|
||
if (Context->BootOrder != NULL) {
|
||
for (Index = 0; Index < Context->BootOrderCount; ++Index) {
|
||
AddBootEntryFromBootOption (BootContext, Context->BootOrder[Index], TRUE);
|
||
|
||
//
|
||
// Return as long as we are good.
|
||
//
|
||
if (BootContext->DefaultEntry != NULL) {
|
||
return BootContext;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Obtain filesystems and try processing the remainings.
|
||
//
|
||
NoHandles = 0;
|
||
Status = gBS->LocateHandleBuffer (
|
||
ByProtocol,
|
||
&gEfiSimpleFileSystemProtocolGuid,
|
||
NULL,
|
||
&NoHandles,
|
||
&Handles
|
||
);
|
||
|
||
DEBUG ((DEBUG_INFO, "OCB: Processing %u blessed list - %r\n", (UINT32) NoHandles, Status));
|
||
|
||
if (!EFI_ERROR(Status)) {
|
||
for (Index = 0; Index < NoHandles; ++Index) {
|
||
//
|
||
// Do not add filesystems twice.
|
||
//
|
||
if (InternalFileSystemForHandle (BootContext, Handles[Index], FALSE) != NULL) {
|
||
continue;
|
||
}
|
||
|
||
Status = AddFileSystemEntry (
|
||
BootContext,
|
||
Handles[Index],
|
||
&FileSystem
|
||
);
|
||
if (EFI_ERROR(Status)) {
|
||
continue;
|
||
}
|
||
|
||
AddBootEntryFromBless (
|
||
BootContext,
|
||
FileSystem,
|
||
gAppleBootPolicyPredefinedPaths,
|
||
gAppleBootPolicyNumPredefinedPaths,
|
||
FALSE,
|
||
FALSE
|
||
);
|
||
if (BootContext->DefaultEntry != NULL) {
|
||
FreePool (Handles);
|
||
return BootContext;
|
||
}
|
||
|
||
AddBootEntryFromSelfRecovery (BootContext, FileSystem);
|
||
if (BootContext->DefaultEntry != NULL) {
|
||
FreePool (Handles);
|
||
return BootContext;
|
||
}
|
||
}
|
||
|
||
FreePool (Handles);
|
||
}
|
||
|
||
//
|
||
// Build custom and system options.
|
||
//
|
||
AddFileSystemEntryForCustom (BootContext);
|
||
|
||
if (BootContext->DefaultEntry == NULL) {
|
||
OcFreeBootContext (BootContext);
|
||
return NULL;
|
||
}
|
||
|
||
ASSERT (BootContext->BootEntryCount > 0);
|
||
|
||
return BootContext;
|
||
}
|
||
|
||
OC_BOOT_ENTRY **
|
||
OcEnumerateEntries (
|
||
IN OC_BOOT_CONTEXT *BootContext
|
||
)
|
||
{
|
||
OC_BOOT_ENTRY **Entries;
|
||
UINT32 EntryIndex;
|
||
LIST_ENTRY *FsLink;
|
||
OC_BOOT_FILESYSTEM *FileSystem;
|
||
LIST_ENTRY *EnLink;
|
||
OC_BOOT_ENTRY *BootEntry;
|
||
|
||
Entries = AllocatePool (sizeof (*Entries) * BootContext->BootEntryCount);
|
||
if (Entries == NULL) {
|
||
return NULL;
|
||
}
|
||
|
||
EntryIndex = 0;
|
||
for (
|
||
FsLink = GetFirstNode (&BootContext->FileSystems);
|
||
!IsNull (&BootContext->FileSystems, FsLink);
|
||
FsLink = GetNextNode (&BootContext->FileSystems, FsLink)) {
|
||
FileSystem = BASE_CR (FsLink, OC_BOOT_FILESYSTEM, Link);
|
||
|
||
for (
|
||
EnLink = GetFirstNode (&FileSystem->BootEntries);
|
||
!IsNull (&FileSystem->BootEntries, EnLink);
|
||
EnLink = GetNextNode (&FileSystem->BootEntries, EnLink)) {
|
||
BootEntry = BASE_CR (EnLink, OC_BOOT_ENTRY, Link);
|
||
|
||
ASSERT (EntryIndex < BootContext->BootEntryCount);
|
||
Entries[EntryIndex] = BootEntry;
|
||
BootEntry->EntryIndex = ++EntryIndex;
|
||
}
|
||
}
|
||
|
||
ASSERT (EntryIndex == BootContext->BootEntryCount);
|
||
ASSERT (BootContext->DefaultEntry == NULL || BootContext->DefaultEntry->EntryIndex > 0);
|
||
return Entries;
|
||
}
|
||
|
||
EFI_STATUS
|
||
OcLoadBootEntry (
|
||
IN OC_PICKER_CONTEXT *Context,
|
||
IN OC_BOOT_ENTRY *BootEntry,
|
||
IN EFI_HANDLE ParentHandle
|
||
)
|
||
{
|
||
EFI_STATUS Status;
|
||
EFI_HANDLE EntryHandle;
|
||
INTERNAL_DMG_LOAD_CONTEXT DmgLoadContext;
|
||
|
||
if ((BootEntry->Type & OC_BOOT_SYSTEM) != 0) {
|
||
ASSERT (BootEntry->SystemAction != NULL);
|
||
return BootEntry->SystemAction ();
|
||
}
|
||
|
||
Status = InternalLoadBootEntry (
|
||
Context,
|
||
BootEntry,
|
||
ParentHandle,
|
||
&EntryHandle,
|
||
&DmgLoadContext
|
||
);
|
||
if (!EFI_ERROR(Status)) {
|
||
Status = Context->StartImage (BootEntry, EntryHandle, NULL, NULL);
|
||
if (EFI_ERROR(Status)) {
|
||
DEBUG ((DEBUG_ERROR, "OCB: StartImage failed - %r\n", Status));
|
||
//
|
||
// Unload dmg if any.
|
||
//
|
||
InternalUnloadDmg (&DmgLoadContext);
|
||
}
|
||
} else {
|
||
DEBUG ((DEBUG_WARN, "OCB: LoadImage failed - %r\n", Status));
|
||
}
|
||
|
||
return Status;
|
||
}
|