CloverBootloader/rEFIt_UEFI/entry_scan/common.c

555 lines
18 KiB
C

/*
* refit/scan/common.c
*
* Copyright (c) 2006-2010 Christoph Pfisterer
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Christoph Pfisterer nor the names of the
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "entry_scan.h"
#ifndef DEBUG_ALL
#define DEBUG_COMMON_MENU 1
#else
#define DEBUG_COMMON_MENU DEBUG_ALL
#endif
#if DEBUG_COMMON_MENU == 0
#define DBG(...)
#else
#define DBG(...) DebugLog(DEBUG_COMMON_MENU, __VA_ARGS__)
#endif
static CHAR16 *BuiltinIconNames[] = {
/*
L"About",
L"Options",
L"Clover",
L"Reset",
L"Shutdown",
L"Help",
L"Shell",
L"Part",
L"Rescue",
L"Pointer",
*/
L"Internal",
L"External",
L"Optical",
L"FireWire",
L"Boot",
L"HFS",
L"APFS",
L"NTFS",
L"EXT",
L"Recovery",
};
static const UINTN BuiltinIconNamesCount = (sizeof(BuiltinIconNames) / sizeof(CHAR16 *));
EG_IMAGE *LoadBuiltinIcon(IN CHAR16 *IconName)
{
UINTN Index = 0;
if (IconName == NULL) {
return NULL;
}
while (Index < BuiltinIconNamesCount) {
if (StriCmp(IconName, BuiltinIconNames[Index]) == 0) {
return BuiltinIcon(BUILTIN_ICON_VOL_INTERNAL + Index);
}
++Index;
}
return NULL;
}
EG_IMAGE* ScanVolumeDefaultIcon(REFIT_VOLUME *Volume, IN UINT8 OSType, IN EFI_DEVICE_PATH_PROTOCOL *DevicePath) //IN UINT8 DiskKind)
{
UINTN IconNum = 0;
// default volume icon based on disk kind
switch (Volume->DiskKind) {
case DISK_KIND_INTERNAL:
switch (OSType) {
case OSTYPE_OSX:
case OSTYPE_OSX_INSTALLER:
while (!IsDevicePathEndType(DevicePath) &&
!(DevicePathType(DevicePath) == MEDIA_DEVICE_PATH && DevicePathSubType (DevicePath) == MEDIA_VENDOR_DP)) {
DevicePath = NextDevicePathNode(DevicePath);
}
if (DevicePathType(DevicePath) == MEDIA_DEVICE_PATH && DevicePathSubType (DevicePath) == MEDIA_VENDOR_DP) {
if (StriCmp(GuidLEToStr((EFI_GUID *)((UINT8 *)DevicePath+0x04)),GuidLEToStr(&APFSSignature)) == 0 ) {
IconNum = BUILTIN_ICON_VOL_INTERNAL_APFS;
}
} else {
IconNum = BUILTIN_ICON_VOL_INTERNAL_HFS;
}
break;
case OSTYPE_RECOVERY:
IconNum = BUILTIN_ICON_VOL_INTERNAL_REC;
break;
case OSTYPE_LIN:
case OSTYPE_LINEFI:
IconNum = BUILTIN_ICON_VOL_INTERNAL_EXT3;
break;
case OSTYPE_WIN:
case OSTYPE_WINEFI:
IconNum = BUILTIN_ICON_VOL_INTERNAL_NTFS;
break;
default:
IconNum = BUILTIN_ICON_VOL_INTERNAL;
break;
}
return BuiltinIcon(IconNum);
case DISK_KIND_EXTERNAL:
return BuiltinIcon(BUILTIN_ICON_VOL_EXTERNAL);
case DISK_KIND_OPTICAL:
return BuiltinIcon(BUILTIN_ICON_VOL_OPTICAL);
case DISK_KIND_FIREWIRE:
return BuiltinIcon(BUILTIN_ICON_VOL_FIREWIRE);
case DISK_KIND_BOOTER:
return BuiltinIcon(BUILTIN_ICON_VOL_BOOTER);
default:
break;
}
return NULL;
}
extern BOOLEAN CopyKernelAndKextPatches(IN OUT KERNEL_AND_KEXT_PATCHES *Dst, IN KERNEL_AND_KEXT_PATCHES *Src);
LOADER_ENTRY * DuplicateLoaderEntry(IN LOADER_ENTRY *Entry)
{
LOADER_ENTRY *DuplicateEntry;
if(Entry == NULL) {
return NULL;
}
DuplicateEntry = (__typeof__(DuplicateEntry))AllocateZeroPool(sizeof(LOADER_ENTRY));
if (DuplicateEntry) {
DuplicateEntry->me.Tag = Entry->me.Tag;
DuplicateEntry->me.AtClick = ActionEnter;
DuplicateEntry->Volume = Entry->Volume;
DuplicateEntry->DevicePathString = EfiStrDuplicate(Entry->DevicePathString);
DuplicateEntry->LoadOptions = EfiStrDuplicate(Entry->LoadOptions);
DuplicateEntry->LoaderPath = EfiStrDuplicate(Entry->LoaderPath);
DuplicateEntry->VolName = EfiStrDuplicate(Entry->VolName);
DuplicateEntry->DevicePath = Entry->DevicePath;
DuplicateEntry->Flags = Entry->Flags;
DuplicateEntry->LoaderType = Entry->LoaderType;
DuplicateEntry->OSVersion = Entry->OSVersion;
DuplicateEntry->BuildVersion = Entry->BuildVersion;
DuplicateEntry->KernelAndKextPatches = Entry->KernelAndKextPatches;
}
return DuplicateEntry;
}
CHAR16 *AddLoadOption(IN CHAR16 *LoadOptions, IN CHAR16 *LoadOption)
{
// If either option strings are null nothing to do
if (LoadOptions == NULL)
{
if (LoadOption == NULL) return NULL;
// Duplicate original options as nothing to add
return EfiStrDuplicate(LoadOption);
}
// If there is no option or it is already present duplicate original
else if ((LoadOption == NULL) || StrStr(LoadOptions, LoadOption)) return EfiStrDuplicate(LoadOptions);
// Otherwise add option
return PoolPrint(L"%s %s", LoadOptions, LoadOption);
}
CHAR16 *RemoveLoadOption(IN CHAR16 *LoadOptions, IN CHAR16 *LoadOption)
{
CHAR16 *Placement;
CHAR16 *NewLoadOptions;
UINTN Length, Offset, OptionLength;
//DBG("LoadOptions: '%s', remove LoadOption: '%s'\n", LoadOptions, LoadOption);
// If there are no options then nothing to do
if (LoadOptions == NULL) return NULL;
// If there is no option to remove then duplicate original
if (LoadOption == NULL) return EfiStrDuplicate(LoadOptions);
// If not present duplicate original
Placement = StrStr(LoadOptions, LoadOption);
if (Placement == NULL) return EfiStrDuplicate(LoadOptions);
// Get placement of option in original options
Offset = (Placement - LoadOptions);
Length = StrLen(LoadOptions);
OptionLength = StrLen(LoadOption);
// If this is just part of some larger option (contains non-space at the beginning or end)
if ((Offset > 0 && LoadOptions[Offset - 1] != L' ') ||
((Offset + OptionLength) < Length && LoadOptions[Offset + OptionLength] != L' ')) {
return EfiStrDuplicate(LoadOptions);
}
// Consume preceeding spaces
while (Offset > 0 && LoadOptions[Offset - 1] == L' ') {
OptionLength++;
Offset--;
}
// Consume following spaces
while (LoadOptions[Offset + OptionLength] == L' ') {
OptionLength++;
}
// If it's the whole string return NULL
if (OptionLength == Length) return NULL;
if (Offset == 0) {
// Simple case - we just need substring after OptionLength position
NewLoadOptions = EfiStrDuplicate(LoadOptions + OptionLength);
} else {
// The rest of LoadOptions is Length - OptionLength, but we may need additional space and ending 0
NewLoadOptions = (__typeof__(NewLoadOptions))AllocateZeroPool((Length - OptionLength + 2) * sizeof(CHAR16));
// Copy preceeding substring
CopyMem(NewLoadOptions, LoadOptions, Offset * sizeof(CHAR16));
if ((Offset + OptionLength) < Length) {
// Copy following substring, but include one space also
OptionLength--;
CopyMem(NewLoadOptions + Offset, LoadOptions + Offset + OptionLength, (Length - OptionLength - Offset) * sizeof(CHAR16));
}
}
return NewLoadOptions;
}
#define TO_LOWER(ch) (((ch >= L'A') && (ch <= L'Z')) ? ((ch - L'A') + L'a') : ch)
INTN StrniCmp(IN CHAR16 *Str1,
IN CHAR16 *Str2,
IN UINTN Count)
{
CHAR16 Ch1, Ch2;
if (Count == 0) {
return 0;
}
if (Str1 == NULL) {
if (Str2 == NULL) {
return 0;
} else {
return -1;
}
} else if (Str2 == NULL) {
return 1;
}
do {
Ch1 = TO_LOWER(*Str1);
Ch2 = TO_LOWER(*Str2);
Str1++;
Str2++;
if (Ch1 != Ch2) {
return (Ch1 - Ch2);
}
if (Ch1 == 0) {
return 0;
}
} while (--Count > 0);
return 0;
}
CHAR16 *StriStr(IN CHAR16 *Str,
IN CHAR16 *SearchFor)
{
CHAR16 *End;
UINTN Length;
UINTN SearchLength;
if ((Str == NULL) || (SearchFor == NULL)) {
return NULL;
}
Length = StrLen(Str);
if (Length == 0) {
return NULL;
}
SearchLength = StrLen(SearchFor);
if (SearchLength > Length) {
return NULL;
}
End = Str + (Length - SearchLength) + 1;
while (Str < End) {
if (StrniCmp(Str, SearchFor, SearchLength) == 0) {
return Str;
}
++Str;
}
return NULL;
}
VOID StrToLower(IN CHAR16 *Str)
{
while (*Str) {
*Str = TO_LOWER(*Str);
++Str;
}
}
STATIC CHAR16 **CreateInfoLines(IN CHAR16 *Message, OUT UINTN *Count)
{
CHAR16 *Ptr, **Information;
UINTN Index = 0, Total = 0;
UINTN Length = ((Message == NULL) ? 0 : StrLen(Message));
// Check parameters
if (Length == 0) {
return NULL;
}
// Count how many new lines
Ptr = Message - 1;
while (Ptr != NULL) {
++Total;
Ptr = StrStr(++Ptr, L"\n");
}
// Create information
Information = (CHAR16 **)AllocatePool((Total * sizeof(CHAR16 *)) + ((Length + 1) * sizeof(CHAR16)));
if (Information == NULL) {
return NULL;
}
// Copy strings
Information[Index++] = Ptr = (CHAR16 *)(Information + Total);
StrCpyS(Ptr, Length + 1, Message);
while ((Index < Total) &&
((Ptr = StrStr(Ptr, L"\n")) != NULL)) {
*Ptr++ = 0;
Information[Index++] = Ptr;
}
// Return the info lines
if (Count != NULL) {
*Count = Total;
}
return Information;
}
extern REFIT_MENU_ENTRY MenuEntryReturn;
STATIC REFIT_MENU_ENTRY *AlertMessageEntries[] = { &MenuEntryReturn };
STATIC REFIT_MENU_SCREEN AlertMessageMenu = {0, NULL, NULL, 0, NULL, 1, AlertMessageEntries,
0, NULL, NULL, FALSE, FALSE, 0, 0, 0, 0,
{ 0, 0, 0, 0 } , NULL };
// Display an alert message
VOID AlertMessage(IN CHAR16 *Title, IN CHAR16 *Message)
{
UINTN Count = 0;
// Break message into info lines
CHAR16 **Information = CreateInfoLines(Message, &Count);
// Check parameters
if (Information != NULL) {
if (Count > 0) {
// Display the alert message
AlertMessageMenu.InfoLineCount = Count;
AlertMessageMenu.InfoLines = Information;
AlertMessageMenu.Title = Title;
RunMenu(&AlertMessageMenu, NULL);
}
FreePool(Information);
}
}
#define TAG_YES 1
#define TAG_NO 2
STATIC REFIT_MENU_ENTRY YesMessageEntry = { L"Yes", TAG_YES, 0, 0, 0, NULL, NULL, NULL,
{ 0, 0, 0, 0 }, ActionEnter, ActionNone, ActionNone, ActionNone, NULL };
STATIC REFIT_MENU_ENTRY NoMessageEntry = { L"No", TAG_NO, 0, 0, 0, NULL, NULL, NULL,
{ 0, 0, 0, 0 }, ActionEnter, ActionNone, ActionNone, ActionNone, NULL };
STATIC REFIT_MENU_ENTRY *YesNoMessageEntries[] = { &YesMessageEntry, &NoMessageEntry };
STATIC REFIT_MENU_SCREEN YesNoMessageMenu = {0, NULL, NULL, 0, NULL, 2, YesNoMessageEntries,
0, NULL, NULL, FALSE, FALSE, 0, 0, 0, 0,
{ 0, 0, 0, 0 } , NULL };
// Display a yes/no prompt
BOOLEAN YesNoMessage(IN CHAR16 *Title, IN CHAR16 *Message)
{
BOOLEAN Result = FALSE;
UINTN Count = 0, MenuExit;
// Break message into info lines
CHAR16 **Information = CreateInfoLines(Message, &Count);
// Display the yes/no message
YesNoMessageMenu.InfoLineCount = Count;
YesNoMessageMenu.InfoLines = Information;
YesNoMessageMenu.Title = Title;
do
{
REFIT_MENU_ENTRY *ChosenEntry = NULL;
MenuExit = RunMenu(&YesNoMessageMenu, &ChosenEntry);
if ((ChosenEntry != NULL) && (ChosenEntry->Tag == TAG_YES) &&
((MenuExit == MENU_EXIT_ENTER) || (MenuExit == MENU_EXIT_DETAILS))) {
Result = TRUE;
MenuExit = MENU_EXIT_ENTER;
}
} while (MenuExit != MENU_EXIT_ENTER);
if (Information != NULL) {
FreePool(Information);
}
return Result;
}
// Ask user for file path from directory menu
BOOLEAN AskUserForFilePathFromDir(IN CHAR16 *Title OPTIONAL, IN REFIT_VOLUME *Volume,
IN CHAR16 *ParentPath OPTIONAL, IN EFI_FILE *Dir,
OUT EFI_DEVICE_PATH_PROTOCOL **Result)
{
//REFIT_MENU_SCREEN Menu = { 0, L"Please Select File...", NULL, 0, NULL, 0, NULL,
// 0, NULL, FALSE, FALSE, 0, 0, 0, 0, { 0, 0, 0, 0 }, NULL};
// Check parameters
if ((Volume == NULL) || (Dir == NULL) || (Result == NULL)) {
return FALSE;
}
// TODO: Generate directory menu
return FALSE;
}
#define TAG_OFFSET 1000
STATIC REFIT_MENU_SCREEN InitialMenu = {0, L"Please Select File...", NULL, 0, NULL, 0, NULL,
0, NULL, NULL, FALSE, FALSE, 0, 0, 0, 0,
{ 0, 0, 0, 0 }, NULL};
// Ask user for file path from volumes menu
BOOLEAN AskUserForFilePathFromVolumes(IN CHAR16 *Title OPTIONAL, OUT EFI_DEVICE_PATH_PROTOCOL **Result)
{
REFIT_MENU_SCREEN Menu;
REFIT_MENU_ENTRY **Entries;
REFIT_MENU_ENTRY *EntryPtr;
UINTN Index = 0, Count = 0, MenuExit;
BOOLEAN Responded = FALSE;
if (Result == NULL) {
return FALSE;
}
// Allocate entries
Entries = (REFIT_MENU_ENTRY **)AllocateZeroPool(sizeof(REFIT_MENU_ENTRY *) + ((sizeof(REFIT_MENU_ENTRY *) + sizeof(REFIT_MENU_ENTRY)) * VolumesCount));
if (Entries == NULL) {
return FALSE;
}
EntryPtr = (REFIT_MENU_ENTRY *)(Entries + (VolumesCount + 1));
// Create volume entries
for (Index = 0; Index < VolumesCount; ++Index) {
REFIT_MENU_ENTRY *Entry;
REFIT_VOLUME *Volume = Volumes[Index];
if ((Volume == NULL) || (Volume->RootDir == NULL) ||
((Volume->DevicePathString == NULL) && (Volume->VolName == NULL))) {
continue;
}
Entry = Entries[Count++] = EntryPtr++;
Entry->Title = (Volume->VolName == NULL) ? Volume->DevicePathString : Volume->VolName;
Entry->Tag = TAG_OFFSET + Index;
Entry->AtClick = MENU_EXIT_ENTER;
}
// Setup menu
CopyMem(&Menu, &InitialMenu, sizeof(REFIT_MENU_SCREEN));
Entries[Count++] = &MenuEntryReturn;
Menu.EntryCount = Count;
Menu.Entries = Entries;
Menu.Title = Title;
do
{
REFIT_MENU_ENTRY *ChosenEntry = NULL;
// Run the volume chooser menu
MenuExit = RunMenu(&Menu, &ChosenEntry);
if ((ChosenEntry != NULL) &&
((MenuExit == MENU_EXIT_ENTER) || (MenuExit == MENU_EXIT_DETAILS))) {
if (ChosenEntry->Tag >= TAG_OFFSET) {
Index = (ChosenEntry->Tag - TAG_OFFSET);
if (Index < VolumesCount) {
// Run directory chooser menu
if (!AskUserForFilePathFromDir(Title, Volumes[Index], NULL, Volumes[Index]->RootDir, Result)) {
continue;
}
Responded = TRUE;
}
}
break;
}
} while (MenuExit != MENU_EXIT_ESCAPE);
FreePool(Entries);
return Responded;
}
// Ask user for file path
BOOLEAN AskUserForFilePath(IN CHAR16 *Title OPTIONAL, IN EFI_DEVICE_PATH_PROTOCOL *Root OPTIONAL, OUT EFI_DEVICE_PATH_PROTOCOL **Result)
{
EFI_FILE *Dir = NULL;
if (Result == NULL) {
return FALSE;
}
if (Root != NULL) {
// Get the file path
CHAR16 *DevicePathStr = FileDevicePathToStr(Root);
if (DevicePathStr != NULL) {
UINTN Index = 0;
// Check the volumes for a match
for (Index = 0; Index < VolumesCount; ++Index) {
REFIT_VOLUME *Volume = Volumes[Index];
UINTN Length;
if ((Volume == NULL) || (Volume->RootDir == NULL) ||
(Volume->DevicePathString == NULL)) {
continue;
}
Length = StrLen(Volume->DevicePathString);
if (Length == 0) {
continue;
}
// If the path begins with this volumes path it matches
if (StrniCmp(DevicePathStr, Volume->DevicePathString, Length)) {
// Need to
CHAR16 *FilePath = DevicePathStr + Length;
UINTN FileLength = StrLen(FilePath);
if (FileLength == 0) {
// If there is no path left then open the root
return AskUserForFilePathFromDir(Title, Volume, NULL, Volume->RootDir, Result);
} else {
// Check to make sure this is directory
if (!EFI_ERROR(Volume->RootDir->Open(Volume->RootDir, &Dir, FilePath, EFI_FILE_MODE_READ, 0)) &&
(Dir != NULL)) {
// Get file information
EFI_FILE_INFO *Info = EfiLibFileInfo(Dir);
if (Info != NULL) {
// Check if the file is a directory
if ((Info->Attribute & EFI_FILE_DIRECTORY) == 0) {
// Return the passed device path if it is a file
FreePool(Info);
Dir->Close(Dir);
*Result = Root;
return TRUE;
} else {
// Ask user other wise
BOOLEAN Success = AskUserForFilePathFromDir(Title, Volume, FilePath, Dir, Result);
FreePool(Info);
Dir->Close(Dir);
return Success;
}
//FreePool(Info);
}
Dir->Close(Dir);
}
}
}
}
FreePool(DevicePathStr);
}
}
return AskUserForFilePathFromVolumes(Title, Result);
}