X-Git-Url: https://code.delx.au/refind/blobdiff_plain/5b953a001484b2f92ff019366f13397d3d5a68c0..cff588e1b33df1c292b3ae11a880e89092221b5f:/refind/main.c diff --git a/refind/main.c b/refind/main.c index 0158401..3581067 100644 --- a/refind/main.c +++ b/refind/main.c @@ -34,7 +34,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* - * Modifications copyright (c) 2012 Roderick W. Smith + * Modifications copyright (c) 2012-2013 Roderick W. Smith * * Modifications distributed under the terms of the GNU General Public * License (GPL) version 3 (GPLv3), a copy of which must be distributed @@ -49,31 +49,54 @@ #include "icns.h" #include "menu.h" #include "mok.h" +#include "security_policy.h" #include "../include/Handle.h" #include "../include/refit_call_wrapper.h" #include "driver_support.h" #include "../include/syslinux_mbr.h" -#ifdef __MAKEWITH_TIANO +#ifdef __MAKEWITH_GNUEFI +#ifndef EFI_SECURITY_VIOLATION +#define EFI_SECURITY_VIOLATION EFIERR (26) +#endif +#else #include "../EfiLib/BdsHelper.h" -#endif // __MAKEWITH_TIANO +#include "../EfiLib/legacy.h" +#endif // __MAKEWITH_GNUEFI -// -// variables +#ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL +#endif -#define MACOSX_LOADER_PATH L"\\System\\Library\\CoreServices\\boot.efi" +// +// constants + +#define MACOSX_LOADER_PATH L"System\\Library\\CoreServices\\boot.efi" #if defined (EFIX64) -#define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\shellx64.efi" +#define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\EFI\\tools\\shellx64.efi,\\shell.efi,\\shellx64.efi" +#define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi,\\EFI\\tools\\gptsync_x64.efi" +#define MEMTEST_NAMES L"memtest86.efi,memtest86_x64.efi,memtest86x64.efi,bootx64.efi" #define DRIVER_DIRS L"drivers,drivers_x64" +#define FALLBACK_FULLNAME L"EFI\\BOOT\\bootx64.efi" +#define FALLBACK_BASENAME L"bootx64.efi" +#define EFI_STUB_ARCH 0x8664 #elif defined (EFI32) -#define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\shellia32.efi" +#define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\EFI\\tools\\shellia32.efi,\\shell.efi,\\shellia32.efi" +#define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi,\\EFI\\tools\\gptsync_ia32.efi" +#define MEMTEST_NAMES L"memtest86.efi,memtest86_ia32.efi,memtest86ia32.efi,bootia32.efi" #define DRIVER_DIRS L"drivers,drivers_ia32" +#define FALLBACK_FULLNAME L"EFI\\BOOT\\bootia32.efi" +#define FALLBACK_BASENAME L"bootia32.efi" +#define EFI_STUB_ARCH 0x014c #else -#define SHELL_NAMES L"\\EFI\\tools\\shell.efi" +#define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\shell.efi" +#define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi" +#define MEMTEST_NAMES L"memtest86.efi" #define DRIVER_DIRS L"drivers" +#define FALLBACK_FULLNAME L"EFI\\BOOT\\boot.efi" /* Not really correct */ +#define FALLBACK_BASENAME L"boot.efi" /* Not really correct */ #endif - -#define MOK_NAMES L"\\EFI\\tools\\MokManager.efi,\\EFI\\redhat\\MokManager.efi,\\EFI\\ubuntu\\MokManager.efi,\\EFI\\suse\\MokManager" +#define FAT_ARCH 0x0ef1fab9 /* ID for Apple "fat" binary */ // Filename patterns that identify EFI boot loaders. Note that a single case (either L"*.efi" or // L"*.EFI") is fine for most systems; but Gigabyte's buggy Hybrid EFI does a case-sensitive @@ -87,18 +110,35 @@ // a ".efi" extension to be found when scanning for boot loaders. #define LINUX_MATCH_PATTERNS L"vmlinuz*,bzImage*" +// Default hint text for program-launch submenus +#define SUBSCREEN_HINT1 L"Use arrow keys to move cursor; Enter to boot;" +#define SUBSCREEN_HINT2 L"Insert or F2 to edit options; Esc to return to main menu" +#define SUBSCREEN_HINT2_NO_EDITOR L"Esc to return to main menu" + +// Load types +#define TYPE_EFI 1 +#define TYPE_LEGACY 2 + static REFIT_MENU_ENTRY MenuEntryAbout = { L"About rEFInd", TAG_ABOUT, 1, 0, 'A', NULL, NULL, NULL }; static REFIT_MENU_ENTRY MenuEntryReset = { L"Reboot Computer", TAG_REBOOT, 1, 0, 'R', NULL, NULL, NULL }; static REFIT_MENU_ENTRY MenuEntryShutdown = { L"Shut Down Computer", TAG_SHUTDOWN, 1, 0, 'U', NULL, NULL, NULL }; -static REFIT_MENU_ENTRY MenuEntryReturn = { L"Return to Main Menu", TAG_RETURN, 0, 0, 0, NULL, NULL, NULL }; +static REFIT_MENU_ENTRY MenuEntryReturn = { L"Return to Main Menu", TAG_RETURN, 1, 0, 0, NULL, NULL, NULL }; static REFIT_MENU_ENTRY MenuEntryExit = { L"Exit rEFInd", TAG_EXIT, 1, 0, 0, NULL, NULL, NULL }; +static REFIT_MENU_ENTRY MenuEntryFirmware = { L"Reboot to Computer Setup Utility", TAG_FIRMWARE, 1, 0, 0, NULL, NULL, NULL }; -static REFIT_MENU_SCREEN MainMenu = { L"Main Menu", NULL, 0, NULL, 0, NULL, 0, L"Automatic boot" }; -static REFIT_MENU_SCREEN AboutMenu = { L"About", NULL, 0, NULL, 0, NULL, 0, NULL }; +static REFIT_MENU_SCREEN MainMenu = { L"Main Menu", NULL, 0, NULL, 0, NULL, 0, L"Automatic boot", + L"Use arrow keys to move cursor; Enter to boot;", + L"Insert or F2 for more options; Esc to refresh" }; +static REFIT_MENU_SCREEN AboutMenu = { L"About", NULL, 0, NULL, 0, NULL, 0, NULL, L"Press Enter to return to main menu", L"" }; -REFIT_CONFIG GlobalConfig = { FALSE, FALSE, 0, 0, 20, 0, 0, GRAPHICS_FOR_OSX, LEGACY_TYPE_MAC, 0, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - {TAG_SHELL, TAG_APPLE_RECOVERY, TAG_MOK_TOOL, TAG_ABOUT, TAG_SHUTDOWN, TAG_REBOOT, 0, 0, 0, 0, 0 }}; +REFIT_CONFIG GlobalConfig = { FALSE, FALSE, 0, 0, 0, DONT_CHANGE_TEXT_MODE, 20, 0, 0, GRAPHICS_FOR_OSX, LEGACY_TYPE_MAC, 0, 0, + BANNER_NOSCALE, + NULL, NULL, CONFIG_FILE_NAME, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + { TAG_SHELL, TAG_MEMTEST, TAG_APPLE_RECOVERY, TAG_WINDOWS_RECOVERY, TAG_MOK_TOOL, TAG_ABOUT, + TAG_SHUTDOWN, TAG_REBOOT, TAG_FIRMWARE, 0, 0, 0, 0, 0, 0 } + }; + +EFI_GUID GlobalGuid = EFI_GLOBAL_VARIABLE; // Structure used to hold boot loader filenames and time stamps in // a linked list; used to sort entries within a directory. @@ -114,37 +154,28 @@ struct LOADER_LIST { static VOID AboutrEFInd(VOID) { - CHAR16 *TempStr; // Note: Don't deallocate; moved to menu structure - if (AboutMenu.EntryCount == 0) { AboutMenu.TitleImage = BuiltinIcon(BUILTIN_ICON_FUNC_ABOUT); - AddMenuInfoLine(&AboutMenu, L"rEFInd Version 0.5.1"); + AddMenuInfoLine(&AboutMenu, L"rEFInd Version 0.7.7"); AddMenuInfoLine(&AboutMenu, L""); AddMenuInfoLine(&AboutMenu, L"Copyright (c) 2006-2010 Christoph Pfisterer"); - AddMenuInfoLine(&AboutMenu, L"Copyright (c) 2012 Roderick W. Smith"); + AddMenuInfoLine(&AboutMenu, L"Copyright (c) 2012-2013 Roderick W. Smith"); AddMenuInfoLine(&AboutMenu, L"Portions Copyright (c) Intel Corporation and others"); AddMenuInfoLine(&AboutMenu, L"Distributed under the terms of the GNU GPLv3 license"); AddMenuInfoLine(&AboutMenu, L""); AddMenuInfoLine(&AboutMenu, L"Running on:"); - TempStr = AllocateZeroPool(256 * sizeof(CHAR16)); - SPrint(TempStr, 255, L" EFI Revision %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & ((1 << 16) - 1)); - AddMenuInfoLine(&AboutMenu, TempStr); + AddMenuInfoLine(&AboutMenu, PoolPrint(L" EFI Revision %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & ((1 << 16) - 1))); #if defined(EFI32) AddMenuInfoLine(&AboutMenu, L" Platform: x86 (32 bit)"); #elif defined(EFIX64) - TempStr = AllocateZeroPool(256 * sizeof(CHAR16)); - SPrint(TempStr, 255, L" Platform: x86_64 (64 bit); Secure Boot %s", secure_mode() ? L"active" : L"inactive"); - AddMenuInfoLine(&AboutMenu, TempStr); + AddMenuInfoLine(&AboutMenu, PoolPrint(L" Platform: x86_64 (64 bit); Secure Boot %s", + secure_mode() ? L"active" : L"inactive")); #else AddMenuInfoLine(&AboutMenu, L" Platform: unknown"); #endif - TempStr = AllocateZeroPool(256 * sizeof(CHAR16)); - SPrint(TempStr, 255, L" Firmware: %s %d.%02d", - ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & ((1 << 16) - 1)); - AddMenuInfoLine(&AboutMenu, TempStr); - TempStr = AllocateZeroPool(256 * sizeof(CHAR16)); - SPrint(TempStr, 255, L" Screen Output: %s", egScreenDescription()); - AddMenuInfoLine(&AboutMenu, TempStr); + AddMenuInfoLine(&AboutMenu, PoolPrint(L" Firmware: %s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, + ST->FirmwareRevision & ((1 << 16) - 1))); + AddMenuInfoLine(&AboutMenu, PoolPrint(L" Screen Output: %s", egScreenDescription())); AddMenuInfoLine(&AboutMenu, L""); #if defined(__MAKEWITH_GNUEFI) AddMenuInfoLine(&AboutMenu, L"Built with GNU-EFI"); @@ -160,9 +191,65 @@ static VOID AboutrEFInd(VOID) RunMenu(&AboutMenu, NULL); } /* VOID AboutrEFInd() */ +static VOID WarnSecureBootError(CHAR16 *Name, BOOLEAN Verbose) { + if (Name == NULL) + Name = L"the loader"; + + refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_ERROR); + Print(L"Secure Boot validation failure loading %s!\n", Name); + refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC); + if (Verbose && secure_mode()) { + Print(L"\nThis computer is configured with Secure Boot active, but\n%s has failed validation.\n", Name); + Print(L"\nYou can:\n * Launch another boot loader\n"); + Print(L" * Disable Secure Boot in your firmware\n"); + Print(L" * Sign %s with a machine owner key (MOK)\n", Name); + Print(L" * Use a MOK utility (often present on the second row) to add a MOK with which\n"); + Print(L" %s has already been signed.\n", Name); + Print(L" * Use a MOK utility to register %s (\"enroll its hash\") without\n", Name); + Print(L" signing it.\n"); + Print(L"\nSee http://www.rodsbooks.com/refind/secureboot.html for more information\n"); + PauseForKey(); + } // if +} // VOID WarnSecureBootError() + +// Returns TRUE if this file is a valid EFI loader file, and is proper ARCH +static BOOLEAN IsValidLoader(EFI_FILE *RootDir, CHAR16 *FileName) { + BOOLEAN IsValid = TRUE; +#if defined (EFIX64) | defined (EFI32) + EFI_STATUS Status; + EFI_FILE_HANDLE FileHandle; + CHAR8 Header[512]; + UINTN Size = sizeof(Header); + + if ((RootDir == NULL) || (FileName == NULL)) { + // Assume valid here, because Macs produce NULL RootDir (& maybe FileName) + // when launching from a Firewire drive. This should be handled better, but + // fix would have to be in StartEFIImageList() and/or in FindVolumeAndFilename(). + return TRUE; + } // if + + Status = refit_call5_wrapper(RootDir->Open, RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(Status)) + return FALSE; + + Status = refit_call3_wrapper(FileHandle->Read, FileHandle, &Size, Header); + refit_call1_wrapper(FileHandle->Close, FileHandle); + + IsValid = !EFI_ERROR(Status) && + Size == sizeof(Header) && + ((Header[0] == 'M' && Header[1] == 'Z' && + (Size = *(UINT32 *)&Header[0x3c]) < 0x180 && + Header[Size] == 'P' && Header[Size+1] == 'E' && + Header[Size+2] == 0 && Header[Size+3] == 0 && + *(UINT16 *)&Header[Size+4] == EFI_STUB_ARCH) || + (*(UINT32 *)&Header == FAT_ARCH)); +#endif + return IsValid; +} // BOOLEAN IsValidLoader() + // Launch an EFI binary. static EFI_STATUS StartEFIImageList(IN EFI_DEVICE_PATH **DevicePaths, - IN CHAR16 *LoadOptions, IN CHAR16 *LoadOptionsPrefix, + IN CHAR16 *LoadOptions, IN UINTN LoaderType, IN CHAR16 *ImageTitle, IN CHAR8 OSType, OUT UINTN *ErrorInStep, IN BOOLEAN Verbose) @@ -170,76 +257,65 @@ static EFI_STATUS StartEFIImageList(IN EFI_DEVICE_PATH **DevicePaths, EFI_STATUS Status, ReturnStatus; EFI_HANDLE ChildImageHandle; EFI_LOADED_IMAGE *ChildLoadedImage = NULL; - REFIT_FILE File; - VOID *ImageData = NULL; - UINTN ImageSize; - REFIT_VOLUME *DeviceVolume = NULL; + REFIT_VOLUME *Volume = NULL; UINTN DevicePathIndex; CHAR16 ErrorInfo[256]; CHAR16 *FullLoadOptions = NULL; - CHAR16 *loader = NULL; - BOOLEAN UseMok = FALSE; + CHAR16 *Filename = NULL; + CHAR16 *Temp; if (ErrorInStep != NULL) *ErrorInStep = 0; // set load options if (LoadOptions != NULL) { - if (LoadOptionsPrefix != NULL) { - MergeStrings(&FullLoadOptions, LoadOptionsPrefix, 0); - MergeStrings(&FullLoadOptions, LoadOptions, L' '); - if (OSType == 'M') { - MergeStrings(&FullLoadOptions, L" ", 0); - // NOTE: That last space is also added by the EFI shell and seems to be significant - // when passing options to Apple's boot.efi... - } // if - } else { - MergeStrings(&FullLoadOptions, LoadOptions, 0); - } // if/else - } else { // LoadOptions == NULL - // NOTE: We provide a non-null string when no options are specified for safety; - // some systems (at least DUET) can hang when launching some programs (such as - // an EFI shell) without this. - FullLoadOptions = StrDuplicate(L" "); - } + FullLoadOptions = StrDuplicate(LoadOptions); + if ((LoaderType == TYPE_EFI) && (OSType == 'M')) { + MergeStrings(&FullLoadOptions, L" ", 0); + // NOTE: That last space is also added by the EFI shell and seems to be significant + // when passing options to Apple's boot.efi... + } // if + } // if (LoadOptions != NULL) if (Verbose) - Print(L"Starting %s\nUsing load options '%s'\n", ImageTitle, FullLoadOptions); + Print(L"Starting %s\nUsing load options '%s'\n", ImageTitle, FullLoadOptions ? FullLoadOptions : L""); // load the image into memory (and execute it, in the case of a shim/MOK image). ReturnStatus = Status = EFI_NOT_FOUND; // in case the list is empty for (DevicePathIndex = 0; DevicePaths[DevicePathIndex] != NULL; DevicePathIndex++) { - // NOTE: Below commented-out line could be more efficient if the ReadFile() and - // FindVolumeAndFilename() calls were moved earlier, but it doesn't work on my - // 32-bit Mac Mini or my 64-bit Intel box when launching a Linux kernel; the - // kernel returns a "Failed to handle fs_proto" error message. - // TODO: Track down the cause of this error and fix it, if possible. - // ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex], - // ImageData, ImageSize, &ChildImageHandle); - ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex], - NULL, 0, &ChildImageHandle); - if ((Status == EFI_ACCESS_DENIED) && (ShimLoaded())) { - FindVolumeAndFilename(DevicePaths[DevicePathIndex], &DeviceVolume, &loader); - if (DeviceVolume != NULL) { - Status = ReadFile(DeviceVolume->RootDir, loader, &File, &ImageSize); - ImageData = File.Buffer; - } else { - Status = EFI_NOT_FOUND; - Print(L"Error: device volume not found!\n"); - } // if/else - if (Status != EFI_NOT_FOUND) { - ReturnStatus = Status = start_image(SelfImageHandle, loader, ImageData, ImageSize, FullLoadOptions, - DeviceVolume, FileDevicePath(DeviceVolume->DeviceHandle, loader)); -// ReturnStatus = Status = start_image(SelfImageHandle, loader, ImageData, ImageSize, FullLoadOptions, -// DeviceVolume, DevicePaths[DevicePathIndex]); - } - if (ReturnStatus == EFI_SUCCESS) { - UseMok = TRUE; - } // if - } // if (UEFI SB failed; use shim) + FindVolumeAndFilename(DevicePaths[DevicePathIndex], &Volume, &Filename); + // Some EFIs crash if attempting to load driver for invalid architecture, so + // protect for this condition; but sometimes Volume comes back NULL, so provide + // an exception. (TODO: Handle this special condition better.) + if ((LoaderType == TYPE_LEGACY) || (Volume == NULL) || IsValidLoader(Volume->RootDir, Filename)) { + if (Filename && (LoaderType != TYPE_LEGACY)) { + Temp = PoolPrint(L"\\%s %s", Filename, FullLoadOptions ? FullLoadOptions : L""); + if (Temp != NULL) { + MyFreePool(FullLoadOptions); + FullLoadOptions = Temp; + } + } // if (Filename) + + // NOTE: Below commented-out line could be more efficient if file were read ahead of + // time and passed as a pre-loaded image to LoadImage(), but it doesn't work on my + // 32-bit Mac Mini or my 64-bit Intel box when launching a Linux kernel; the + // kernel returns a "Failed to handle fs_proto" error message. + // TODO: Track down the cause of this error and fix it, if possible. + // ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex], + // ImageData, ImageSize, &ChildImageHandle); + ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex], + NULL, 0, &ChildImageHandle); + } else { + Print(L"Invalid loader file!\n"); + ReturnStatus = EFI_LOAD_ERROR; + } if (ReturnStatus != EFI_NOT_FOUND) { break; } } + if ((Status == EFI_ACCESS_DENIED) || (Status == EFI_SECURITY_VIOLATION)) { + WarnSecureBootError(ImageTitle, Verbose); + goto bailout; + } SPrint(ErrorInfo, 255, L"while loading %s", ImageTitle); if (CheckError(Status, ErrorInfo)) { if (ErrorInStep != NULL) @@ -247,37 +323,35 @@ static EFI_STATUS StartEFIImageList(IN EFI_DEVICE_PATH **DevicePaths, goto bailout; } - if (!UseMok) { - ReturnStatus = Status = refit_call3_wrapper(BS->HandleProtocol, ChildImageHandle, &LoadedImageProtocol, - (VOID **) &ChildLoadedImage); - if (CheckError(Status, L"while getting a LoadedImageProtocol handle")) { - if (ErrorInStep != NULL) - *ErrorInStep = 2; - goto bailout_unload; - } - ChildLoadedImage->LoadOptions = (VOID *)FullLoadOptions; - ChildLoadedImage->LoadOptionsSize = ((UINT32)StrLen(FullLoadOptions) + 1) * sizeof(CHAR16); - // turn control over to the image - // TODO: (optionally) re-enable the EFI watchdog timer! - - // close open file handles - UninitRefitLib(); - ReturnStatus = Status = refit_call3_wrapper(BS->StartImage, ChildImageHandle, NULL, NULL); - // control returns here when the child image calls Exit() - SPrint(ErrorInfo, 255, L"returned from %s", ImageTitle); - if (CheckError(Status, ErrorInfo)) { - if (ErrorInStep != NULL) - *ErrorInStep = 3; - } + ReturnStatus = Status = refit_call3_wrapper(BS->HandleProtocol, ChildImageHandle, &LoadedImageProtocol, + (VOID **) &ChildLoadedImage); + if (CheckError(Status, L"while getting a LoadedImageProtocol handle")) { + if (ErrorInStep != NULL) + *ErrorInStep = 2; + goto bailout_unload; + } + ChildLoadedImage->LoadOptions = (VOID *)FullLoadOptions; + ChildLoadedImage->LoadOptionsSize = FullLoadOptions ? ((UINT32)StrLen(FullLoadOptions) + 1) * sizeof(CHAR16) : 0; + // turn control over to the image + // TODO: (optionally) re-enable the EFI watchdog timer! - // re-open file handles - ReinitRefitLib(); - } // if + // close open file handles + UninitRefitLib(); + ReturnStatus = Status = refit_call3_wrapper(BS->StartImage, ChildImageHandle, NULL, NULL); + + // control returns here when the child image calls Exit() + SPrint(ErrorInfo, 255, L"returned from %s", ImageTitle); + if (CheckError(Status, ErrorInfo)) { + if (ErrorInStep != NULL) + *ErrorInStep = 3; + } + + // re-open file handles + ReinitRefitLib(); bailout_unload: // unload the image, we don't care if it works or not... - if (!UseMok) - Status = refit_call1_wrapper(BS->UnloadImage, ChildImageHandle); + Status = refit_call1_wrapper(BS->UnloadImage, ChildImageHandle); bailout: MyFreePool(FullLoadOptions); @@ -285,7 +359,7 @@ bailout: } /* static EFI_STATUS StartEFIImageList() */ static EFI_STATUS StartEFIImage(IN EFI_DEVICE_PATH *DevicePath, - IN CHAR16 *LoadOptions, IN CHAR16 *LoadOptionsPrefix, + IN CHAR16 *LoadOptions, IN UINTN LoaderType, IN CHAR16 *ImageTitle, IN CHAR8 OSType, OUT UINTN *ErrorInStep, IN BOOLEAN Verbose) @@ -294,19 +368,77 @@ static EFI_STATUS StartEFIImage(IN EFI_DEVICE_PATH *DevicePath, DevicePaths[0] = DevicePath; DevicePaths[1] = NULL; - return StartEFIImageList(DevicePaths, LoadOptions, LoadOptionsPrefix, ImageTitle, OSType, ErrorInStep, Verbose); + return StartEFIImageList(DevicePaths, LoadOptions, LoaderType, ImageTitle, OSType, ErrorInStep, Verbose); } /* static EFI_STATUS StartEFIImage() */ +// From gummiboot: Retrieve a raw EFI variable. +// Returns EFI status +static EFI_STATUS EfivarGetRaw(EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) { + CHAR8 *buf; + UINTN l; + EFI_STATUS err; + + l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE; + buf = AllocatePool(l); + if (!buf) + return EFI_OUT_OF_RESOURCES; + + err = refit_call5_wrapper(RT->GetVariable, name, vendor, NULL, &l, buf); + if (EFI_ERROR(err) == EFI_SUCCESS) { + *buffer = buf; + if (size) + *size = l; + } else + MyFreePool(buf); + return err; +} // EFI_STATUS EfivarGetRaw() + +// From gummiboot: Set an EFI variable +static EFI_STATUS EfivarSetRaw(EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) { + UINT32 flags; + + flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + if (persistent) + flags |= EFI_VARIABLE_NON_VOLATILE; + + return refit_call5_wrapper(RT->SetVariable, name, vendor, flags, size, buf); +} // EFI_STATUS EfivarSetRaw() + +// From gummiboot: Reboot the computer into its built-in user interface +static EFI_STATUS RebootIntoFirmware(VOID) { + CHAR8 *b; + UINTN size; + UINT64 osind; + EFI_STATUS err; + + osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + + err = EfivarGetRaw(&GlobalGuid, L"OsIndications", &b, &size); + if (err == EFI_SUCCESS) + osind |= (UINT64)*b; + MyFreePool(b); + + err = EfivarSetRaw(&GlobalGuid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE); + if (err != EFI_SUCCESS) + return err; + + refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL); + Print(L"Error calling ResetSystem: %r", err); + PauseForKey(); + return err; +} + + // // EFI OS loader functions // -static VOID StartLoader(IN LOADER_ENTRY *Entry) +static VOID StartLoader(LOADER_ENTRY *Entry) { UINTN ErrorInStep = 0; BeginExternalScreen(Entry->UseGraphicsMode, L"Booting OS"); - StartEFIImage(Entry->DevicePath, Entry->LoadOptions, Basename(Entry->LoaderPath), + StartEFIImage(Entry->DevicePath, Entry->LoadOptions, TYPE_EFI, Basename(Entry->LoaderPath), Entry->OSType, &ErrorInStep, !Entry->UseGraphicsMode); FinishExternalScreen(); } @@ -346,13 +478,11 @@ static CHAR16 * FindInitrd(IN CHAR16 *LoaderPath, IN REFIT_VOLUME *Volume) { InitrdVersion = FindNumbers(DirEntry->FileName); if (KernelVersion != NULL) { if (StriCmp(InitrdVersion, KernelVersion) == 0) { - MergeStrings(&InitrdName, Path, 0); - MergeStrings(&InitrdName, DirEntry->FileName, 0); + InitrdName = PoolPrint(L"%s%s", Path, DirEntry->FileName); } // if } else { if (InitrdVersion == NULL) { - MergeStrings(&InitrdName, Path, 0); - MergeStrings(&InitrdName, DirEntry->FileName, 0); + InitrdName = PoolPrint(L"%s%s", Path, DirEntry->FileName); } // if } // if/else MyFreePool(InitrdVersion); @@ -395,6 +525,8 @@ static REFIT_MENU_SCREEN* CopyMenuScreen(REFIT_MENU_SCREEN *Entry) { for (i = 0; i < Entry->EntryCount && NewEntry->Entries; i++) { AddMenuEntry(NewEntry, Entry->Entries[i]); } // for + NewEntry->Hint1 = (Entry->Hint1) ? StrDuplicate(Entry->Hint1) : NULL; + NewEntry->Hint2 = (Entry->Hint2) ? StrDuplicate(Entry->Hint2) : NULL; } // if return (NewEntry); } // static REFIT_MENU_SCREEN* CopyMenuScreen() @@ -455,6 +587,23 @@ LOADER_ENTRY *InitializeLoaderEntry(IN LOADER_ENTRY *Entry) { return (NewEntry); } // LOADER_ENTRY *InitializeLoaderEntry() +// Adds InitrdPath to Options, but only if Options doesn't already include an +// initrd= line. Done to enable overriding the default initrd selection in a +// refind_linux.conf file's options list. +// Returns a pointer to a new string. The calling function is responsible for +// freeing its memory. +static CHAR16 *AddInitrdToOptions(CHAR16 *Options, CHAR16 *InitrdPath) { + CHAR16 *NewOptions = NULL; + + if (Options != NULL) + NewOptions = StrDuplicate(Options); + if ((InitrdPath != NULL) && !StriSubCmp(L"initrd=", Options)) { + MergeStrings(&NewOptions, L"initrd=", L' '); + MergeStrings(&NewOptions, InitrdPath, 0); + } + return NewOptions; +} // CHAR16 *AddInitrdToOptions() + // Prepare a REFIT_MENU_SCREEN data structure for a subscreen entry. This sets up // the default entry that launches the boot loader using the same options as the // main Entry does. Subsequent options can be added by the calling function. @@ -463,7 +612,7 @@ LOADER_ENTRY *InitializeLoaderEntry(IN LOADER_ENTRY *Entry) { // Returns a pointer to the new subscreen data structure, or NULL if there // were problems allocating memory. REFIT_MENU_SCREEN *InitializeSubScreen(IN LOADER_ENTRY *Entry) { - CHAR16 *FileName, *Temp = NULL; + CHAR16 *FileName, *MainOptions = NULL; REFIT_MENU_SCREEN *SubScreen = NULL; LOADER_ENTRY *SubEntry; @@ -478,15 +627,18 @@ REFIT_MENU_SCREEN *InitializeSubScreen(IN LOADER_ENTRY *Entry) { // default entry SubEntry = InitializeLoaderEntry(Entry); if (SubEntry != NULL) { - SubEntry->me.Title = L"Boot using default options"; - if ((SubEntry->InitrdPath != NULL) && (StrLen(SubEntry->InitrdPath) > 0) && (!StriSubCmp(L"initrd", SubEntry->LoadOptions))) { - MergeStrings(&Temp, L"initrd=", 0); - MergeStrings(&Temp, SubEntry->InitrdPath, 0); - MergeStrings(&SubEntry->LoadOptions, Temp, L' '); - MyFreePool(Temp); - } // if + SubEntry->me.Title = StrDuplicate(L"Boot using default options"); + MainOptions = SubEntry->LoadOptions; + SubEntry->LoadOptions = AddInitrdToOptions(MainOptions, SubEntry->InitrdPath); + MyFreePool(MainOptions); AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry); } // if (SubEntry != NULL) + SubScreen->Hint1 = StrDuplicate(SUBSCREEN_HINT1); + if (GlobalConfig.HideUIFlags & HIDEUI_FLAG_EDITOR) { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2_NO_EDITOR); + } else { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2); + } // if/else } // if (SubScreen != NULL) } else { // existing subscreen; less initialization, and just add new entry later.... SubScreen = Entry->me.SubScreen; @@ -497,7 +649,7 @@ REFIT_MENU_SCREEN *InitializeSubScreen(IN LOADER_ENTRY *Entry) { VOID GenerateSubScreen(LOADER_ENTRY *Entry, IN REFIT_VOLUME *Volume) { REFIT_MENU_SCREEN *SubScreen; LOADER_ENTRY *SubEntry; - CHAR16 *InitrdOption = NULL, *Temp; + CHAR16 *InitrdName; CHAR16 DiagsFileName[256]; REFIT_FILE *File; UINTN TokenCount; @@ -564,10 +716,20 @@ VOID GenerateSubScreen(LOADER_ENTRY *Entry, IN REFIT_VOLUME *Volume) { SubEntry->LoadOptions = L"-v -s"; AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry); } // if - } // not single-user + } // single-user mode allowed + + if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_SAFEMODE)) { + SubEntry = InitializeLoaderEntry(Entry); + if (SubEntry != NULL) { + SubEntry->me.Title = L"Boot Mac OS X in safe mode"; + SubEntry->UseGraphicsMode = FALSE; + SubEntry->LoadOptions = L"-v -x"; + AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry); + } // if + } // safe mode allowed // check for Apple hardware diagnostics - StrCpy(DiagsFileName, L"\\System\\Library\\CoreServices\\.diagnostics\\diags.efi"); + StrCpy(DiagsFileName, L"System\\Library\\CoreServices\\.diagnostics\\diags.efi"); if (FileExists(Volume->RootDir, DiagsFileName) && !(GlobalConfig.HideUIFlags & HIDEUI_FLAG_HWTEST)) { SubEntry = InitializeLoaderEntry(Entry); if (SubEntry != NULL) { @@ -583,26 +745,28 @@ VOID GenerateSubScreen(LOADER_ENTRY *Entry, IN REFIT_VOLUME *Volume) { } else if (Entry->OSType == 'L') { // entries for Linux kernels with EFI stub loaders File = ReadLinuxOptionsFile(Entry->LoaderPath, Volume); if (File != NULL) { - if ((Temp = FindInitrd(Entry->LoaderPath, Volume)) != NULL) { - MergeStrings(&InitrdOption, L"initrd=", 0); - MergeStrings(&InitrdOption, Temp, 0); - } - TokenCount = ReadTokenLine(File, &TokenList); // read and discard first entry, since it's - FreeTokenLine(&TokenList, &TokenCount); // set up by InitializeSubScreen(), earlier.... + InitrdName = FindInitrd(Entry->LoaderPath, Volume); + TokenCount = ReadTokenLine(File, &TokenList); + // first entry requires special processing, since it was initially set + // up with a default title but correct options by InitializeSubScreen(), + // earlier.... + if ((SubScreen->Entries != NULL) && (SubScreen->Entries[0] != NULL)) { + MyFreePool(SubScreen->Entries[0]->Title); + SubScreen->Entries[0]->Title = TokenList[0] ? StrDuplicate(TokenList[0]) : StrDuplicate(L"Boot Linux"); + } // if + FreeTokenLine(&TokenList, &TokenCount); while ((TokenCount = ReadTokenLine(File, &TokenList)) > 1) { SubEntry = InitializeLoaderEntry(Entry); - SubEntry->me.Title = StrDuplicate(TokenList[0]); + SubEntry->me.Title = TokenList[0] ? StrDuplicate(TokenList[0]) : StrDuplicate(L"Boot Linux"); MyFreePool(SubEntry->LoadOptions); - SubEntry->LoadOptions = StrDuplicate(TokenList[1]); - MergeStrings(&SubEntry->LoadOptions, InitrdOption, L' '); + SubEntry->LoadOptions = AddInitrdToOptions(TokenList[1], InitrdName); FreeTokenLine(&TokenList, &TokenCount); SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_LINUX; AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry); } // while - MyFreePool(InitrdOption); - MyFreePool(Temp); + MyFreePool(InitrdName); MyFreePool(File); - } // if Linux options file exists + } // if } else if (Entry->OSType == 'E') { // entries for ELILO SubEntry = InitializeLoaderEntry(Entry); @@ -680,40 +844,66 @@ VOID GenerateSubScreen(LOADER_ENTRY *Entry, IN REFIT_VOLUME *Volume) { // kernel's directory; and if present, adds an initrd= option for an initial // RAM disk file with the same version number as the kernel file. static CHAR16 * GetMainLinuxOptions(IN CHAR16 * LoaderPath, IN REFIT_VOLUME *Volume) { - CHAR16 *Options = NULL, *InitrdName, *InitrdOption = NULL; + CHAR16 *Options = NULL, *InitrdName, *FullOptions = NULL; Options = GetFirstOptionsFromFile(LoaderPath, Volume); InitrdName = FindInitrd(LoaderPath, Volume); - if (InitrdName != NULL) { - MergeStrings(&InitrdOption, L"initrd=", 0); - MergeStrings(&InitrdOption, InitrdName, 0); - } // if - MergeStrings(&Options, InitrdOption, ' '); - MyFreePool(InitrdOption); + FullOptions = AddInitrdToOptions(Options, InitrdName); + + MyFreePool(Options); MyFreePool(InitrdName); - return (Options); + return (FullOptions); } // static CHAR16 * GetMainLinuxOptions() +// Try to guess the name of the Linux distribution & add that name to +// OSIconName list. +static VOID GuessLinuxDistribution(CHAR16 **OSIconName, REFIT_VOLUME *Volume, CHAR16 *LoaderPath) { + UINTN FileSize = 0; + REFIT_FILE File; + CHAR16** TokenList; + UINTN TokenCount = 0; + + // If on Linux root fs, /etc/os-release file probably has clues.... + if (FileExists(Volume->RootDir, L"etc\\os-release") && + (ReadFile(Volume->RootDir, L"etc\\os-release", &File, &FileSize) == EFI_SUCCESS)) { + do { + TokenCount = ReadTokenLine(&File, &TokenList); + if ((TokenCount > 1) && ((StriCmp(TokenList[0], L"ID") == 0) || (StriCmp(TokenList[0], L"NAME") == 0))) { + MergeStrings(OSIconName, TokenList[1], L','); + } // if + FreeTokenLine(&TokenList, &TokenCount); + } while (TokenCount > 0); + MyFreePool(File.Buffer); + } // if + + // Search for clues in the kernel's filename.... + if (StriSubCmp(L".fc", LoaderPath)) + MergeStrings(OSIconName, L"fedora", L','); + if (StriSubCmp(L".el", LoaderPath)) + MergeStrings(OSIconName, L"redhat", L','); +} // VOID GuessLinuxDistribution() + // Sets a few defaults for a loader entry -- mainly the icon, but also the OS type // code and shortcut letter. For Linux EFI stub loaders, also sets kernel options // that will (with luck) work fairly automatically. -VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, IN REFIT_VOLUME *Volume) { - CHAR16 IconFileName[256]; - CHAR16 *FileName, *PathOnly, *OSIconName = NULL, *Temp; - CHAR16 ShortcutLetter = 0; +VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, REFIT_VOLUME *Volume) { + CHAR16 *FileName, *PathOnly, *NoExtension, *OSIconName = NULL, *Temp, *SubString; + CHAR16 ShortcutLetter = 0; + UINTN i = 0, Length; FileName = Basename(LoaderPath); PathOnly = FindPath(LoaderPath); + NoExtension = StripEfiExtension(FileName); // locate a custom icon for the loader - StrCpy(IconFileName, LoaderPath); - ReplaceEfiExtension(IconFileName, L".icns"); - if (FileExists(Volume->RootDir, IconFileName)) { - Entry->me.Image = LoadIcns(Volume->RootDir, IconFileName, 128); - } else if ((StrLen(PathOnly) == 0) && (Volume->VolIconImage != NULL)) { - Entry->me.Image = Volume->VolIconImage; - } // icon matched to loader or volume - + // Anything found here takes precedence over the "hints" in the OSIconName variable + if (!Entry->me.Image) + Entry->me.Image = egLoadIconAnyType(Volume->RootDir, PathOnly, NoExtension, 128); + if (!Entry->me.Image) + Entry->me.Image = egCopyImage(Volume->VolIconImage); + + // Begin creating icon "hints" by using last part of directory path leading + // to the loader Temp = FindLastDirName(LoaderPath); MergeStrings(&OSIconName, Temp, L','); MyFreePool(Temp); @@ -722,8 +912,29 @@ VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, IN REFIT_VOLUME ShortcutLetter = OSIconName[0]; } + // Add every "word" in the volume label, delimited by spaces, dashes (-), or + // underscores (_), to the list of hints to be used in searching for OS + // icons. + if ((Volume->VolName) && (StrLen(Volume->VolName) > 0)) { + Temp = SubString = StrDuplicate(Volume->VolName); + if (Temp != NULL) { + Length = StrLen(Temp); + for (i = 0; i < Length; i++) { + if ((Temp[i] == L' ') || (Temp[i] == L'_') || (Temp[i] == L'-')) { + Temp[i] = 0; + if (StrLen(SubString) > 0) + MergeStrings(&OSIconName, SubString, L','); + SubString = Temp + i + 1; + } // if + } // for + MergeStrings(&OSIconName, SubString, L','); + MyFreePool(Temp); + } // if + } // if + // detect specific loaders - if (StriSubCmp(L"bzImage", LoaderPath) || StriSubCmp(L"vmlinuz", LoaderPath)) { + if (StriSubCmp(L"bzImage", FileName) || StriSubCmp(L"vmlinuz", FileName)) { + GuessLinuxDistribution(&OSIconName, Volume, LoaderPath); MergeStrings(&OSIconName, L"linux", L','); Entry->OSType = 'L'; if (ShortcutLetter == 0) @@ -734,6 +945,10 @@ VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, IN REFIT_VOLUME MergeStrings(&OSIconName, L"refit", L','); Entry->OSType = 'R'; ShortcutLetter = 'R'; + } else if (StriSubCmp(L"refind", LoaderPath)) { + MergeStrings(&OSIconName, L"refind", L','); + Entry->OSType = 'R'; + ShortcutLetter = 'R'; } else if (StriCmp(LoaderPath, MACOSX_LOADER_PATH) == 0) { if (Volume->VolIconImage != NULL) { // custom icon file found Entry->me.Image = Volume->VolIconImage; @@ -747,12 +962,6 @@ VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, IN REFIT_VOLUME } else if (StriCmp(FileName, L"e.efi") == 0 || StriCmp(FileName, L"elilo.efi") == 0 || StriSubCmp(L"elilo", FileName)) { MergeStrings(&OSIconName, L"elilo,linux", L','); Entry->OSType = 'E'; -// if (secure_mode()) { // hack to enable ELILO to boot in secure mode -// Temp = StrDuplicate(L"-C "); -// MergeStrings(&Temp, PathOnly, 0); -// MergeStrings(&Temp, L"elilo.conf", L'\\'); -// Entry->LoadOptions = Temp; -// } if (ShortcutLetter == 0) ShortcutLetter = 'L'; Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO; @@ -762,7 +971,8 @@ VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, IN REFIT_VOLUME Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_GRUB; } else if (StriCmp(FileName, L"cdboot.efi") == 0 || StriCmp(FileName, L"bootmgr.efi") == 0 || - StriCmp(FileName, L"Bootmgfw.efi") == 0) { + StriCmp(FileName, L"bootmgfw.efi") == 0 || + StriCmp(FileName, L"bkpbootmgfw.efi") == 0) { MergeStrings(&OSIconName, L"win", L','); Entry->OSType = 'W'; ShortcutLetter = 'W'; @@ -793,7 +1003,7 @@ LOADER_ENTRY * AddLoaderEntry(IN CHAR16 *LoaderPath, IN CHAR16 *LoaderTitle, IN if (Entry != NULL) { Entry->Title = StrDuplicate((LoaderTitle != NULL) ? LoaderTitle : LoaderPath); Entry->me.Title = AllocateZeroPool(sizeof(CHAR16) * 256); - SPrint(Entry->me.Title, 255, L"Boot %s from %s", (LoaderTitle != NULL) ? LoaderTitle : LoaderPath, Volume->VolName); + SPrint(Entry->me.Title, 255, L"Boot %s from %s ", (LoaderTitle != NULL) ? LoaderTitle : LoaderPath, Volume->VolName); Entry->me.Row = 0; Entry->me.BadgeImage = Volume->VolBadgeImage; if ((LoaderPath != NULL) && (LoaderPath[0] != L'\\')) { @@ -816,12 +1026,11 @@ LOADER_ENTRY * AddLoaderEntry(IN CHAR16 *LoaderPath, IN CHAR16 *LoaderTitle, IN // (Time1 == Time2). Precision is only to the nearest second; since // this is used for sorting boot loader entries, differences smaller // than this are likely to be meaningless (and unlikely!). -INTN TimeComp(EFI_TIME *Time1, EFI_TIME *Time2) { +INTN TimeComp(IN EFI_TIME *Time1, IN EFI_TIME *Time2) { INT64 Time1InSeconds, Time2InSeconds; // Following values are overestimates; I'm assuming 31 days in every month. - // This is fine for the purpose of this function, which has a limited - // purpose. + // This is fine for the purpose of this function, which is limited Time1InSeconds = Time1->Second + (Time1->Minute * 60) + (Time1->Hour * 3600) + (Time1->Day * 86400) + (Time1->Month * 2678400) + ((Time1->Year - 1998) * 32140800); Time2InSeconds = Time2->Second + (Time2->Minute * 60) + (Time2->Hour * 3600) + (Time2->Day * 86400) + @@ -869,102 +1078,287 @@ static VOID CleanUpLoaderList(struct LOADER_LIST *LoaderList) { } // while } // static VOID CleanUpLoaderList() +// Returns FALSE if the specified file/volume matches the GlobalConfig.DontScanDirs +// or GlobalConfig.DontScanVolumes specification, or if Path points to a volume +// other than the one specified by Volume, or if the specified path is SelfDir. +// Returns TRUE if none of these conditions is met -- that is, if the path is +// eligible for scanning. +static BOOLEAN ShouldScan(REFIT_VOLUME *Volume, CHAR16 *Path) { + CHAR16 *VolName = NULL, *DontScanDir, *PathCopy = NULL; + UINTN i = 0; + BOOLEAN ScanIt = TRUE; + + if (IsIn(Volume->VolName, GlobalConfig.DontScanVolumes)) + return FALSE; + + if ((StriCmp(Path, SelfDirPath) == 0) && (Volume->DeviceHandle == SelfVolume->DeviceHandle)) + return FALSE; + + // See if Path includes an explicit volume declaration that's NOT Volume.... + PathCopy = StrDuplicate(Path); + if (SplitVolumeAndFilename(&PathCopy, &VolName)) { + VolumeNumberToName(Volume, &VolName); + if (VolName && StriCmp(VolName, Volume->VolName) != 0) { + ScanIt = FALSE; + } // if + } // if Path includes volume specification + MyFreePool(PathCopy); + MyFreePool(VolName); + VolName = NULL; + + // See if Volume is in GlobalConfig.DontScanDirs.... + while ((DontScanDir = FindCommaDelimited(GlobalConfig.DontScanDirs, i++)) && ScanIt) { + SplitVolumeAndFilename(&DontScanDir, &VolName); + CleanUpPathNameSlashes(DontScanDir); + VolumeNumberToName(Volume, &VolName); + if (VolName != NULL) { + if ((StriCmp(VolName, Volume->VolName) == 0) && (StriCmp(DontScanDir, Path) == 0)) + ScanIt = FALSE; + } else { + if (StriCmp(DontScanDir, Path) == 0) + ScanIt = FALSE; + } + MyFreePool(DontScanDir); + MyFreePool(VolName); + DontScanDir = NULL; + } // while() + + return ScanIt; +} // BOOLEAN ShouldScan() + +// Returns TRUE if the file is byte-for-byte identical with the fallback file +// on the volume AND if the file is not itself the fallback file; returns +// FALSE if the file is not identical to the fallback file OR if the file +// IS the fallback file. Intended for use in excluding the fallback boot +// loader when it's a duplicate of another boot loader. +static BOOLEAN DuplicatesFallback(IN REFIT_VOLUME *Volume, IN CHAR16 *FileName) { + CHAR8 *FileContents, *FallbackContents; + EFI_FILE_HANDLE FileHandle, FallbackHandle; + EFI_FILE_INFO *FileInfo, *FallbackInfo; + UINTN FileSize = 0, FallbackSize = 0; + EFI_STATUS Status; + BOOLEAN AreIdentical = FALSE; + + if (!FileExists(Volume->RootDir, FileName) || !FileExists(Volume->RootDir, FALLBACK_FULLNAME)) + return FALSE; + + CleanUpPathNameSlashes(FileName); + + if (StriCmp(FileName, FALLBACK_FULLNAME) == 0) + return FALSE; // identical filenames, so not a duplicate.... + + Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0); + if (Status == EFI_SUCCESS) { + FileInfo = LibFileInfo(FileHandle); + FileSize = FileInfo->FileSize; + } else { + return FALSE; + } + + Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FallbackHandle, FALLBACK_FULLNAME, EFI_FILE_MODE_READ, 0); + if (Status == EFI_SUCCESS) { + FallbackInfo = LibFileInfo(FallbackHandle); + FallbackSize = FallbackInfo->FileSize; + } else { + refit_call1_wrapper(FileHandle->Close, FileHandle); + return FALSE; + } + + if (FallbackSize != FileSize) { // not same size, so can't be identical + AreIdentical = FALSE; + } else { // could be identical; do full check.... + FileContents = AllocatePool(FileSize); + FallbackContents = AllocatePool(FallbackSize); + if (FileContents && FallbackContents) { + Status = refit_call3_wrapper(FileHandle->Read, FileHandle, &FileSize, FileContents); + if (Status == EFI_SUCCESS) { + Status = refit_call3_wrapper(FallbackHandle->Read, FallbackHandle, &FallbackSize, FallbackContents); + } + if (Status == EFI_SUCCESS) { + AreIdentical = (CompareMem(FileContents, FallbackContents, FileSize) == 0); + } // if + } // if + MyFreePool(FileContents); + MyFreePool(FallbackContents); + } // if/else + + // BUG ALERT: Some systems (e.g., DUET, some Macs with large displays) crash if the + // following two calls are reversed. Go figure.... + refit_call1_wrapper(FileHandle->Close, FallbackHandle); + refit_call1_wrapper(FileHandle->Close, FileHandle); + return AreIdentical; +} // BOOLEAN DuplicatesFallback() + +// Returns FALSE if two measures of file size are identical for a single file, +// TRUE if not or if the file can't be opened and the other measure is non-0. +// Despite the function's name, this isn't really a direct test of symbolic +// link status, since EFI doesn't officially support symlinks. It does seem +// to be a reliable indicator, though. (OTOH, some disk errors might cause a +// file to fail to open, which would return a false positive -- but as I use +// this function to exclude symbolic links from the list of boot loaders, +// that would be fine, since such boot loaders wouldn't work.) +static BOOLEAN IsSymbolicLink(REFIT_VOLUME *Volume, CHAR16 *Path, EFI_FILE_INFO *DirEntry) { + EFI_FILE_HANDLE FileHandle; + EFI_FILE_INFO *FileInfo = NULL; + EFI_STATUS Status; + UINTN FileSize2 = 0; + CHAR16 *FileName; + + FileName = StrDuplicate(Path); + MergeStrings(&FileName, DirEntry->FileName, L'\\'); + CleanUpPathNameSlashes(FileName); + + Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0); + if (Status == EFI_SUCCESS) { + FileInfo = LibFileInfo(FileHandle); + if (FileInfo != NULL) + FileSize2 = FileInfo->FileSize; + } + + MyFreePool(FileName); + MyFreePool(FileInfo); + + return (DirEntry->FileSize != FileSize2); +} // BOOLEAN IsSymbolicLink() + // Scan an individual directory for EFI boot loader files and, if found, -// add them to the list. Sorts the entries within the loader directory -// so that the most recent one appears first in the list. -static VOID ScanLoaderDir(IN REFIT_VOLUME *Volume, IN CHAR16 *Path, IN CHAR16 *Pattern) +// add them to the list. Exception: Ignores FALLBACK_FULLNAME, which is picked +// up in ScanEfiFiles(). Sorts the entries within the loader directory so that +// the most recent one appears first in the list. +// Returns TRUE if a duplicate for FALLBACK_FILENAME was found, FALSE if not. +static BOOLEAN ScanLoaderDir(IN REFIT_VOLUME *Volume, IN CHAR16 *Path, IN CHAR16 *Pattern) { EFI_STATUS Status; REFIT_DIR_ITER DirIter; EFI_FILE_INFO *DirEntry; CHAR16 FileName[256], *Extension; struct LOADER_LIST *LoaderList = NULL, *NewLoader; + BOOLEAN FoundFallbackDuplicate = FALSE; - if ((!SelfDirPath || !Path || ((StriCmp(Path, SelfDirPath) == 0) && Volume->DeviceHandle != SelfVolume->DeviceHandle) || - (StriCmp(Path, SelfDirPath) != 0)) && (!IsIn(Path, GlobalConfig.DontScanDirs))) { + if ((!SelfDirPath || !Path || ((StriCmp(Path, SelfDirPath) == 0) && (Volume->DeviceHandle != SelfVolume->DeviceHandle)) || + (StriCmp(Path, SelfDirPath) != 0)) && + (ShouldScan(Volume, Path))) { // look through contents of the directory DirIterOpen(Volume->RootDir, Path, &DirIter); while (DirIterNext(&DirIter, 2, Pattern, &DirEntry)) { Extension = FindExtension(DirEntry->FileName); if (DirEntry->FileName[0] == '.' || StriCmp(Extension, L".icns") == 0 || + StriCmp(Extension, L".png") == 0 || + (StriCmp(DirEntry->FileName, FALLBACK_BASENAME) == 0 && (StriCmp(Path, L"EFI\\BOOT") == 0)) || StriSubCmp(L"shell", DirEntry->FileName) || - IsIn(DirEntry->FileName, GlobalConfig.DontScanFiles)) + IsSymbolicLink(Volume, Path, DirEntry) || /* is symbolic link */ + FilenameIn(Volume, Path, DirEntry->FileName, GlobalConfig.DontScanFiles)) continue; // skip this if (Path) - SPrint(FileName, 255, L"\\%s\\%s", Path, DirEntry->FileName); + SPrint(FileName, 255, L"\\%s\\%s", Path, DirEntry->FileName); else - SPrint(FileName, 255, L"\\%s", DirEntry->FileName); + SPrint(FileName, 255, L"\\%s", DirEntry->FileName); CleanUpPathNameSlashes(FileName); + + if(!IsValidLoader(Volume->RootDir, FileName)) + continue; + NewLoader = AllocateZeroPool(sizeof(struct LOADER_LIST)); if (NewLoader != NULL) { NewLoader->FileName = StrDuplicate(FileName); NewLoader->TimeStamp = DirEntry->ModificationTime; LoaderList = AddLoaderListEntry(LoaderList, NewLoader); + if (DuplicatesFallback(Volume, FileName)) + FoundFallbackDuplicate = TRUE; } // if MyFreePool(Extension); } // while + NewLoader = LoaderList; while (NewLoader != NULL) { AddLoaderEntry(NewLoader->FileName, NULL, Volume); NewLoader = NewLoader->NextEntry; } // while + CleanUpLoaderList(LoaderList); Status = DirIterClose(&DirIter); - if (Status != EFI_NOT_FOUND) { + // NOTE: EFI_INVALID_PARAMETER really is an error that should be reported; + // but I've gotten reports from users who are getting this error occasionally + // and I can't find anything wrong or reproduce the problem, so I'm putting + // it down to buggy EFI implementations and ignoring that particular error.... + if ((Status != EFI_NOT_FOUND) && (Status != EFI_INVALID_PARAMETER)) { if (Path) SPrint(FileName, 255, L"while scanning the %s directory", Path); else StrCpy(FileName, L"while scanning the root directory"); CheckError(Status, FileName); } // if (Status != EFI_NOT_FOUND) - } // if not scanning our own directory + } // if not scanning a blacklisted directory + + return FoundFallbackDuplicate; } /* static VOID ScanLoaderDir() */ static VOID ScanEfiFiles(REFIT_VOLUME *Volume) { EFI_STATUS Status; REFIT_DIR_ITER EfiDirIter; EFI_FILE_INFO *EfiDirEntry; - CHAR16 FileName[256], *Directory, *MatchPatterns; + CHAR16 FileName[256], *Directory = NULL, *MatchPatterns, *VolName = NULL, *SelfPath; UINTN i, Length; + BOOLEAN ScanFallbackLoader = TRUE; + BOOLEAN FoundBRBackup = FALSE; MatchPatterns = StrDuplicate(LOADER_MATCH_PATTERNS); if (GlobalConfig.ScanAllLinux) MergeStrings(&MatchPatterns, LINUX_MATCH_PATTERNS, L','); - if ((Volume->RootDir != NULL) && (Volume->VolName != NULL)) { + if ((Volume->RootDir != NULL) && (Volume->VolName != NULL) && (Volume->IsReadable)) { // check for Mac OS X boot loader - if (!IsIn(L"\\System\\Library\\CoreServices", GlobalConfig.DontScanDirs)) { + if (ShouldScan(Volume, L"System\\Library\\CoreServices")) { StrCpy(FileName, MACOSX_LOADER_PATH); - if (FileExists(Volume->RootDir, FileName) && !IsIn(L"boot.efi", GlobalConfig.DontScanFiles)) { + if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, Directory, L"boot.efi", GlobalConfig.DontScanFiles)) { AddLoaderEntry(FileName, L"Mac OS X", Volume); + if (DuplicatesFallback(Volume, FileName)) + ScanFallbackLoader = FALSE; } // check for XOM - StrCpy(FileName, L"\\System\\Library\\CoreServices\\xom.efi"); - if (FileExists(Volume->RootDir, FileName) && !IsIn(L"boot.efi", GlobalConfig.DontScanFiles)) { + StrCpy(FileName, L"System\\Library\\CoreServices\\xom.efi"); + if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, Directory, L"boot.efi", GlobalConfig.DontScanFiles)) { AddLoaderEntry(FileName, L"Windows XP (XoM)", Volume); + if (DuplicatesFallback(Volume, FileName)) + ScanFallbackLoader = FALSE; } - } // if Mac directory not in GlobalConfig.DontScanDirs list + } // if should scan Mac directory // check for Microsoft boot loader/menu - StrCpy(FileName, L"\\EFI\\Microsoft\\Boot\\Bootmgfw.efi"); - if (FileExists(Volume->RootDir, FileName) && !IsIn(L"\\EFI\\Microsoft\\Boot", GlobalConfig.DontScanDirs) && - !IsIn(L"bootmgfw.efi", GlobalConfig.DontScanFiles)) { - AddLoaderEntry(FileName, L"Microsoft EFI boot", Volume); - } + if (ShouldScan(Volume, L"EFI\\Microsoft\\Boot")) { + StrCpy(FileName, L"EFI\\Microsoft\\Boot\\bkpbootmgfw.efi"); + if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, Directory, L"bkpbootmgfw.efi", GlobalConfig.DontScanFiles)) { + AddLoaderEntry(FileName, L"Microsoft EFI boot (Boot Repair backup)", Volume); + FoundBRBackup = TRUE; + if (DuplicatesFallback(Volume, FileName)) + ScanFallbackLoader = FALSE; + } + StrCpy(FileName, L"EFI\\Microsoft\\Boot\\bootmgfw.efi"); + if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, Directory, L"bootmgfw.efi", GlobalConfig.DontScanFiles)) { + if (FoundBRBackup) + AddLoaderEntry(FileName, L"Supposed Microsoft EFI boot (probably GRUB)", Volume); + else + AddLoaderEntry(FileName, L"Microsoft EFI boot", Volume); + if (DuplicatesFallback(Volume, FileName)) + ScanFallbackLoader = FALSE; + } + } // if // scan the root directory for EFI executables - ScanLoaderDir(Volume, L"\\", MatchPatterns); + if (ScanLoaderDir(Volume, L"\\", MatchPatterns)) + ScanFallbackLoader = FALSE; // scan subdirectories of the EFI directory (as per the standard) DirIterOpen(Volume->RootDir, L"EFI", &EfiDirIter); while (DirIterNext(&EfiDirIter, 1, NULL, &EfiDirEntry)) { if (StriCmp(EfiDirEntry->FileName, L"tools") == 0 || EfiDirEntry->FileName[0] == '.') - continue; // skip this, doesn't contain boot loaders - SPrint(FileName, 255, L"\\EFI\\%s", EfiDirEntry->FileName); - ScanLoaderDir(Volume, FileName, MatchPatterns); + continue; // skip this, doesn't contain boot loaders or is scanned later + SPrint(FileName, 255, L"EFI\\%s", EfiDirEntry->FileName); + if (ScanLoaderDir(Volume, FileName, MatchPatterns)) + ScanFallbackLoader = FALSE; } // while() Status = DirIterClose(&EfiDirIter); if (Status != EFI_NOT_FOUND) @@ -973,12 +1367,27 @@ static VOID ScanEfiFiles(REFIT_VOLUME *Volume) { // Scan user-specified (or additional default) directories.... i = 0; while ((Directory = FindCommaDelimited(GlobalConfig.AlsoScan, i++)) != NULL) { - CleanUpPathNameSlashes(Directory); - Length = StrLen(Directory); - if (Length > 0) - ScanLoaderDir(Volume, Directory, MatchPatterns); + if (ShouldScan(Volume, Directory)) { + SplitVolumeAndFilename(&Directory, &VolName); + CleanUpPathNameSlashes(Directory); + Length = StrLen(Directory); + if ((Length > 0) && ScanLoaderDir(Volume, Directory, MatchPatterns)) + ScanFallbackLoader = FALSE; + MyFreePool(VolName); + } // if should scan dir MyFreePool(Directory); } // while + + // Don't scan the fallback loader if it's on the same volume and a duplicate of rEFInd itself.... + SelfPath = DevicePathToStr(SelfLoadedImage->FilePath); + CleanUpPathNameSlashes(SelfPath); + if ((Volume->DeviceHandle == SelfLoadedImage->DeviceHandle) && DuplicatesFallback(Volume, SelfPath)) + ScanFallbackLoader = FALSE; + + // If not a duplicate & if it exists & if it's not us, create an entry + // for the fallback boot loader + if (ScanFallbackLoader && FileExists(Volume->RootDir, FALLBACK_FULLNAME) && ShouldScan(Volume, L"EFI\\BOOT")) + AddLoaderEntry(FALLBACK_FULLNAME, L"Fallback boot loader", Volume); } // if } // static VOID ScanEfiFiles() @@ -1195,7 +1604,7 @@ static VOID StartLegacy(IN LEGACY_ENTRY *Entry) ExtractLegacyLoaderPaths(DiscoveredPathList, MAX_DISCOVERED_PATHS, LegacyLoaderList); - Status = StartEFIImageList(DiscoveredPathList, Entry->LoadOptions, NULL, L"legacy loader", 0, &ErrorInStep, TRUE); + Status = StartEFIImageList(DiscoveredPathList, Entry->LoadOptions, TYPE_LEGACY, L"legacy loader", 0, &ErrorInStep, TRUE); if (Status == EFI_NOT_FOUND) { if (ErrorInStep == 1) { Print(L"\nPlease make sure that you have the latest firmware update installed.\n"); @@ -1209,10 +1618,18 @@ static VOID StartLegacy(IN LEGACY_ENTRY *Entry) // Start a device on a non-Mac using the EFI_LEGACY_BIOS_PROTOCOL #ifdef __MAKEWITH_TIANO -static VOID StartLegacyUEFI(IN LEGACY_ENTRY *Entry) +static VOID StartLegacyUEFI(LEGACY_ENTRY *Entry) { +// UINTN ExitDataSize = 0; +// CHAR16 *ExitData = NULL; +// EFI_STATUS Status; + BeginExternalScreen(TRUE, L"Booting Legacy OS (UEFI mode)"); +// Print(L"Launching from '%s'\n", DevicePathToStr(Entry->BdsOption->DevicePath)); +// PauseForKey(); +// Status = BdsLibBootViaBootOption(Entry->BdsOption, Entry->BdsOption->DevicePath, &ExitDataSize, &ExitData); +// Print(L"BdsLibBootViaBootOption() returned %d\n", Status); BdsLibConnectDevicePath (Entry->BdsOption->DevicePath); BdsLibDoLegacyBoot(Entry->BdsOption); @@ -1262,6 +1679,12 @@ static LEGACY_ENTRY * AddLegacyEntry(IN CHAR16 *LoaderTitle, IN REFIT_VOLUME *Vo SubScreen->Title = AllocateZeroPool(256 * sizeof(CHAR16)); SPrint(SubScreen->Title, 255, L"Boot Options for %s on %s", LoaderTitle, VolDesc); SubScreen->TitleImage = Entry->me.Image; + SubScreen->Hint1 = StrDuplicate(SUBSCREEN_HINT1); + if (GlobalConfig.HideUIFlags & HIDEUI_FLAG_EDITOR) { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2_NO_EDITOR); + } else { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2); + } // if/else // default entry SubEntry = AllocateZeroPool(sizeof(LEGACY_ENTRY)); @@ -1279,7 +1702,9 @@ static LEGACY_ENTRY * AddLegacyEntry(IN CHAR16 *LoaderTitle, IN REFIT_VOLUME *Vo } /* static LEGACY_ENTRY * AddLegacyEntry() */ -#ifdef __MAKEWITH_TIANO +#ifdef __MAKEWITH_GNUEFI +static VOID ScanLegacyUEFI(IN UINTN DiskType){} +#else // default volume badge icon based on disk kind static EG_IMAGE * GetDiskBadge(IN UINTN DiskType) { EG_IMAGE * Badge = NULL; @@ -1308,8 +1733,6 @@ static LEGACY_ENTRY * AddLegacyEntryUEFI(BDS_COMMON_OPTION *BdsOption, IN UINT16 CHAR16 ShortcutLetter = 0; CHAR16 *LegacyDescription = BdsOption->Description; -// ScanVolume(Volume); - // prepare the menu entry Entry = AllocateZeroPool(sizeof(LEGACY_ENTRY)); Entry->me.Title = AllocateZeroPool(256 * sizeof(CHAR16)); @@ -1321,7 +1744,6 @@ static LEGACY_ENTRY * AddLegacyEntryUEFI(BDS_COMMON_OPTION *BdsOption, IN UINT16 Entry->LoadOptions = (DiskType == BBS_CDROM) ? L"CD" : ((DiskType == BBS_USB) ? L"USB" : L"HD"); Entry->me.BadgeImage = GetDiskBadge(DiskType); -// Entry->me.BadgeImage = Volume->VolBadgeImage; Entry->BdsOption = BdsOption; Entry->Enabled = TRUE; @@ -1330,6 +1752,12 @@ static LEGACY_ENTRY * AddLegacyEntryUEFI(BDS_COMMON_OPTION *BdsOption, IN UINT16 SubScreen->Title = AllocateZeroPool(256 * sizeof(CHAR16)); SPrint(SubScreen->Title, 255, L"No boot options for legacy target"); SubScreen->TitleImage = Entry->me.Image; + SubScreen->Hint1 = StrDuplicate(SUBSCREEN_HINT1); + if (GlobalConfig.HideUIFlags & HIDEUI_FLAG_EDITOR) { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2_NO_EDITOR); + } else { + SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2); + } // if/else // default entry SubEntry = AllocateZeroPool(sizeof(LEGACY_ENTRY)); @@ -1363,7 +1791,6 @@ static VOID ScanLegacyUEFI(IN UINTN DiskType) BDS_COMMON_OPTION *BdsOption; LIST_ENTRY TempList; BBS_BBS_DEVICE_PATH * BbsDevicePath = NULL; -// REFIT_VOLUME Volume; InitializeListHead (&TempList); ZeroMem (Buffer, sizeof (Buffer)); @@ -1403,9 +1830,7 @@ static VOID ScanLegacyUEFI(IN UINTN DiskType) Index++; } } /* static VOID ScanLegacyUEFI() */ -#else -static VOID ScanLegacyUEFI(IN UINTN DiskType){} -#endif // __MAKEWITH_TIANO +#endif // __MAKEWITH_GNUEFI static VOID ScanLegacyVolume(REFIT_VOLUME *Volume, UINTN VolumeIndex) { UINTN VolumeIndex2; @@ -1428,7 +1853,7 @@ static VOID ScanLegacyVolume(REFIT_VOLUME *Volume, UINTN VolumeIndex) { // check for other bootable entries on the same disk for (VolumeIndex2 = 0; VolumeIndex2 < VolumesCount; VolumeIndex2++) { if (VolumeIndex2 != VolumeIndex && Volumes[VolumeIndex2]->HasBootCode && - Volumes[VolumeIndex2]->WholeDiskBlockIO == Volume->WholeDiskBlockIO) + Volumes[VolumeIndex2]->WholeDiskBlockIO == Volume->WholeDiskBlockIO) ShowVolume = FALSE; } } @@ -1498,7 +1923,7 @@ static VOID ScanLegacyExternal(VOID) static VOID StartTool(IN LOADER_ENTRY *Entry) { BeginExternalScreen(Entry->UseGraphicsMode, Entry->me.Title + 6); // assumes "Start " as assigned below - StartEFIImage(Entry->DevicePath, Entry->LoadOptions, Basename(Entry->LoaderPath), + StartEFIImage(Entry->DevicePath, Entry->LoadOptions, TYPE_EFI, Basename(Entry->LoaderPath), Entry->OSType, NULL, TRUE); FinishExternalScreen(); } /* static VOID StartTool() */ @@ -1511,8 +1936,7 @@ static LOADER_ENTRY * AddToolEntry(EFI_HANDLE DeviceHandle, IN CHAR16 *LoaderPat Entry = AllocateZeroPool(sizeof(LOADER_ENTRY)); - MergeStrings(&TitleStr, L"Start ", 0); - MergeStrings(&TitleStr, LoaderTitle, 0); + TitleStr = PoolPrint(L"Start %s", LoaderTitle); Entry->me.Title = TitleStr; Entry->me.Tag = TAG_TOOL; Entry->me.Row = 1; @@ -1548,7 +1972,7 @@ static UINTN ScanDriverDir(IN CHAR16 *Path) SPrint(FileName, 255, L"%s\\%s", Path, DirEntry->FileName); NumFound++; Status = StartEFIImage(FileDevicePath(SelfLoadedImage->DeviceHandle, FileName), - L"", DirEntry->FileName, DirEntry->FileName, 0, NULL, FALSE); + L"", TYPE_EFI, DirEntry->FileName, 0, NULL, FALSE); } Status = DirIterClose(&DirIter); if (Status != EFI_NOT_FOUND) { @@ -1678,7 +2102,7 @@ static VOID FindLegacyBootType(VOID) { GlobalConfig.LegacyType = LEGACY_TYPE_NONE; // UEFI-style legacy BIOS support is available only with the TianoCore EDK2 - // build environment, and then only with some implementations.... + // build environment, and then only with some EFI implementations.... #ifdef __MAKEWITH_TIANO Status = gBS->LocateProtocol (&gEfiLegacyBootProtocolGuid, NULL, (VOID **) &LegacyBios); if (!EFI_ERROR (Status)) @@ -1725,7 +2149,18 @@ static VOID WarnIfLegacyProblems() { // Locates boot loaders. NOTE: This assumes that GlobalConfig.LegacyType is set correctly. static VOID ScanForBootloaders(VOID) { - UINTN i; + UINTN i; + +// if (GlobalConfig.LegacyType == LEGACY_TYPE_UEFI) { +// Print(L"About to call BdsDeleteAllInvalidLegacyBootOptions()\n"); +// BdsDeleteAllInvalidLegacyBootOptions(); +// Print(L"About to call BdsAddNonExistingLegacyBootOptions()\n"); +// BdsAddNonExistingLegacyBootOptions(); +// Print(L"About to call BdsUpdateLegacyDevOrder()\n"); +// // BdsUpdateLegacyDevOrder(); // EXTREME CAUTION: HOSED ONE FIRMWARE! +// Print(L"Done with legacy boot updates!\n"); +// PauseForKey(); +// } ScanVolumes(); @@ -1742,7 +2177,7 @@ static VOID ScanForBootloaders(VOID) { ScanLegacyExternal(); break; case 'm': case 'M': - ScanUserConfigured(); + ScanUserConfigured(GlobalConfig.ConfigFilename); break; case 'e': case 'E': ScanExternal(); @@ -1764,87 +2199,145 @@ static VOID ScanForBootloaders(VOID) { FinishTextScreen(FALSE); } // static VOID ScanForBootloaders() +// Locate a single tool from the specified Locations using one of the +// specified Names and add it to the menu. +static VOID FindTool(CHAR16 *Locations, CHAR16 *Names, CHAR16 *Description, UINTN Icon) { + UINTN j = 0, k, VolumeIndex; + CHAR16 *DirName, *FileName, *PathName, FullDescription[256]; + + while ((DirName = FindCommaDelimited(Locations, j++)) != NULL) { + k = 0; + while ((FileName = FindCommaDelimited(Names, k++)) != NULL) { + PathName = StrDuplicate(DirName); + MergeStrings(&PathName, FileName, (StriCmp(PathName, L"\\") == 0) ? 0 : L'\\'); + for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) { + if ((Volumes[VolumeIndex]->RootDir != NULL) && (FileExists(Volumes[VolumeIndex]->RootDir, PathName))) { + SPrint(FullDescription, 255, L"%s at %s on %s", Description, PathName, Volumes[VolumeIndex]->VolName); + AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, PathName, FullDescription, BuiltinIcon(Icon), 'S', FALSE); + } // if + } // for + MyFreePool(PathName); + MyFreePool(FileName); + } // while Names + MyFreePool(DirName); + } // while Locations +} // VOID FindTool() + // Add the second-row tags containing built-in and external tools (EFI shell, // reboot, etc.) static VOID ScanForTools(VOID) { - CHAR16 *FileName = NULL, Description[256]; + CHAR16 *FileName = NULL, *VolName = NULL, *MokLocations, Description[256]; REFIT_MENU_ENTRY *TempMenuEntry; UINTN i, j, VolumeIndex; + UINT64 osind; + CHAR8 *b = 0; + + MokLocations = StrDuplicate(MOK_LOCATIONS); + if (MokLocations != NULL) + MergeStrings(&MokLocations, SelfDirPath, L','); for (i = 0; i < NUM_TOOLS; i++) { switch(GlobalConfig.ShowTools[i]) { + // NOTE: Be sure that FileName is NULL at the end of each case. case TAG_SHUTDOWN: TempMenuEntry = CopyMenuEntry(&MenuEntryShutdown); TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_SHUTDOWN); AddMenuEntry(&MainMenu, TempMenuEntry); break; + case TAG_REBOOT: TempMenuEntry = CopyMenuEntry(&MenuEntryReset); TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_RESET); AddMenuEntry(&MainMenu, TempMenuEntry); break; + case TAG_ABOUT: TempMenuEntry = CopyMenuEntry(&MenuEntryAbout); TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_ABOUT); AddMenuEntry(&MainMenu, TempMenuEntry); break; + case TAG_EXIT: TempMenuEntry = CopyMenuEntry(&MenuEntryExit); TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_EXIT); AddMenuEntry(&MainMenu, TempMenuEntry); break; + + case TAG_FIRMWARE: + if (EfivarGetRaw(&GlobalGuid, L"OsIndicationsSupported", &b, &j) == EFI_SUCCESS) { + osind = (UINT64)*b; + if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) { + TempMenuEntry = CopyMenuEntry(&MenuEntryFirmware); + TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_FIRMWARE); + AddMenuEntry(&MainMenu, TempMenuEntry); + } // if + } // if + break; + case TAG_SHELL: j = 0; - MyFreePool(FileName); while ((FileName = FindCommaDelimited(SHELL_NAMES, j++)) != NULL) { if (FileExists(SelfRootDir, FileName)) { AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"EFI Shell", BuiltinIcon(BUILTIN_ICON_TOOL_SHELL), 'S', FALSE); } + MyFreePool(FileName); } // while break; + case TAG_GPTSYNC: - MyFreePool(FileName); + j = 0; + while ((FileName = FindCommaDelimited(GPTSYNC_NAMES, j++)) != NULL) { + if (FileExists(SelfRootDir, FileName)) { + AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"Hybrid MBR tool", BuiltinIcon(BUILTIN_ICON_TOOL_PART), + 'P', FALSE); + } // if + MyFreePool(FileName); + } // while FileName = NULL; - MergeStrings(&FileName, L"\\efi\\tools\\gptsync.efi", 0); - if (FileExists(SelfRootDir, FileName)) { - AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"Make Hybrid MBR", BuiltinIcon(BUILTIN_ICON_TOOL_PART), 'P', FALSE); - } break; + case TAG_APPLE_RECOVERY: - MyFreePool(FileName); - FileName = NULL; - MergeStrings(&FileName, L"\\com.apple.recovery.boot\\boot.efi", 0); + FileName = StrDuplicate(L"\\com.apple.recovery.boot\\boot.efi"); for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) { if ((Volumes[VolumeIndex]->RootDir != NULL) && (FileExists(Volumes[VolumeIndex]->RootDir, FileName))) { SPrint(Description, 255, L"Apple Recovery on %s", Volumes[VolumeIndex]->VolName); AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, FileName, Description, BuiltinIcon(BUILTIN_ICON_TOOL_APPLE_RESCUE), 'R', TRUE); - } + } // if } // for + MyFreePool(FileName); + FileName = NULL; break; - case TAG_MOK_TOOL: + + case TAG_WINDOWS_RECOVERY: j = 0; - MyFreePool(FileName); - while ((FileName = FindCommaDelimited(MOK_NAMES, j++)) != NULL) { - if (FileExists(SelfRootDir, FileName)) { - SPrint(Description, 255, L"MOK Key Manager at %s", FileName); - AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, Description, - BuiltinIcon(BUILTIN_ICON_TOOL_MOK_TOOL), 'S', FALSE); - } + while ((FileName = FindCommaDelimited(GlobalConfig.WindowsRecoveryFiles, j++)) != NULL) { + SplitVolumeAndFilename(&FileName, &VolName); + for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) { + if ((Volumes[VolumeIndex]->RootDir != NULL) && (FileExists(Volumes[VolumeIndex]->RootDir, FileName)) && + ((VolName == NULL) || (StriCmp(VolName, Volumes[VolumeIndex]->VolName) == 0))) { + SPrint(Description, 255, L"Microsoft Recovery on %s", Volumes[VolumeIndex]->VolName); + AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, FileName, Description, + BuiltinIcon(BUILTIN_ICON_TOOL_WINDOWS_RESCUE), 'R', TRUE); + } // if + } // for } // while - if (FileExists(SelfDir, L"MokManager.efi")) { - MyFreePool(FileName); - FileName = StrDuplicate(SelfDirPath); - MergeStrings(&FileName, L"\\MokManager.efi", 0); - SPrint(Description, 255, L"MOK Key Manager at %s", FileName); - AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, Description, - BuiltinIcon(BUILTIN_ICON_TOOL_MOK_TOOL), 'S', FALSE); - } + MyFreePool(FileName); + FileName = NULL; + MyFreePool(VolName); + VolName = NULL; break; + + case TAG_MOK_TOOL: + FindTool(MokLocations, MOK_NAMES, L"MOK utility utility", BUILTIN_ICON_TOOL_MOK_TOOL); + break; + + case TAG_MEMTEST: + FindTool(MEMTEST_LOCATIONS, MEMTEST_NAMES, L"Memory test utility", BUILTIN_ICON_TOOL_MEMTEST); + break; + } // switch() - MyFreePool(FileName); - FileName = NULL; } // for } // static VOID ScanForTools @@ -1860,7 +2353,7 @@ VOID RescanAll(VOID) { FreeList((VOID ***) &(MainMenu.Entries), &MainMenu.EntryCount); MainMenu.Entries = NULL; MainMenu.EntryCount = 0; - ReadConfig(CONFIG_FILE_NAME); + ReadConfig(GlobalConfig.ConfigFilename); ConnectAllDriversToAllControllers(); ScanVolumes(); ScanForBootloaders(); @@ -1868,7 +2361,7 @@ VOID RescanAll(VOID) { SetupScreen(); } // VOID RescanAll() -#ifndef __MAKEWITH_GNUEFI +#ifdef __MAKEWITH_TIANO // Minimal initialization function static VOID InitializeLib(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) { @@ -1884,23 +2377,90 @@ static VOID InitializeLib(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *System #endif +// Set up our own Secure Boot extensions.... +// Returns TRUE on success, FALSE otherwise +static BOOLEAN SecureBootSetup(VOID) { + EFI_STATUS Status; + BOOLEAN Success = FALSE; + + if (secure_mode() && ShimLoaded()) { + Status = security_policy_install(); + if (Status == EFI_SUCCESS) { + Success = TRUE; + } else { + Print(L"Failed to install MOK Secure Boot extensions"); + } + } + return Success; +} // VOID SecureBootSetup() + +// Remove our own Secure Boot extensions.... +// Returns TRUE on success, FALSE otherwise +static BOOLEAN SecureBootUninstall(VOID) { + EFI_STATUS Status; + BOOLEAN Success = TRUE; + + if (secure_mode()) { + Status = security_policy_uninstall(); + if (Status != EFI_SUCCESS) { + Success = FALSE; + BeginTextScreen(L"Secure Boot Policy Failure"); + Print(L"Failed to uninstall MOK Secure Boot extensions; forcing a reboot."); + PauseForKey(); + refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL); + } + } + return Success; +} // VOID SecureBootUninstall + +// Sets the global configuration filename; will be CONFIG_FILE_NAME unless the +// "-c" command-line option is set, in which case that takes precedence. +// If an error is encountered, leaves the value alone (it should be set to +// CONFIG_FILE_NAME when GlobalConfig is initialized). +static VOID SetConfigFilename(EFI_HANDLE ImageHandle) { + EFI_LOADED_IMAGE *Info; + CHAR16 *Options, *FileName; + EFI_STATUS Status; + INTN Where; + + Status = uefi_call_wrapper(BS->HandleProtocol, 3, ImageHandle, + &LoadedImageProtocol, (VOID **) &Info); + if ((Status == EFI_SUCCESS) && (Info->LoadOptionsSize > 0)) { + Options = (CHAR16 *) Info->LoadOptions; + Where = FindSubString(L" -c ", Options); + if (Where >= 0) { + FileName = StrDuplicate(&Options[Where + 4]); + Where = FindSubString(L" ", FileName); + if (Where > 0) + FileName[Where] = L'\0'; + + if (FileExists(SelfDir, FileName)) { + GlobalConfig.ConfigFilename = FileName; + } else { + Print(L"Specified configuration file (%s) doesn't exist; using\n'refind.conf' default\n", FileName); + MyFreePool(FileName); + } // if/else + } // if + } // if +} // VOID SetConfigFilename() + // // main entry point // EFI_STATUS EFIAPI -efi_main (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) +efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { EFI_STATUS Status; BOOLEAN MainLoopRunning = TRUE; + BOOLEAN MokProtocol; REFIT_MENU_ENTRY *ChosenEntry; UINTN MenuExit, i; - CHAR16 *Selection; + CHAR16 *Selection = NULL; EG_PIXEL BGColor; // bootstrap InitializeLib(ImageHandle, SystemTable); - InitScreen(); Status = InitRefitLib(ImageHandle); if (EFI_ERROR(Status)) return Status; @@ -1910,7 +2470,11 @@ efi_main (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) FindLegacyBootType(); if (GlobalConfig.LegacyType == LEGACY_TYPE_MAC) CopyMem(GlobalConfig.ScanFor, "ihebocm ", NUM_SCAN_OPTIONS); - ReadConfig(CONFIG_FILE_NAME); + SetConfigFilename(ImageHandle); + ReadConfig(GlobalConfig.ConfigFilename); + ScanVolumes(); + + InitScreen(); WarnIfLegacyProblems(); MainMenu.TimeoutSeconds = GlobalConfig.Timeout; @@ -1918,11 +2482,11 @@ efi_main (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) refit_call4_wrapper(BS->SetWatchdogTimer, 0x0000, 0x0000, 0x0000, NULL); // further bootstrap (now with config available) - SetupScreen(); - ScanVolumes(); + MokProtocol = SecureBootSetup(); LoadDrivers(); ScanForBootloaders(); ScanForTools(); + SetupScreen(); if (GlobalConfig.ScanDelay > 0) { BGColor.b = 255; @@ -1935,7 +2499,9 @@ efi_main (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) RescanAll(); } // if - Selection = StrDuplicate(GlobalConfig.DefaultSelection); + if (GlobalConfig.DefaultSelection) + Selection = StrDuplicate(GlobalConfig.DefaultSelection); + while (MainLoopRunning) { MenuExit = RunMainMenu(&MainMenu, Selection, &ChosenEntry); @@ -1975,15 +2541,23 @@ efi_main (IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) case TAG_LEGACY_UEFI: // Boot a legacy OS on a non-Mac StartLegacyUEFI((LEGACY_ENTRY *)ChosenEntry); break; -#endif // __MAKEWITH_TIANO +#endif case TAG_TOOL: // Start a EFI tool StartTool((LOADER_ENTRY *)ChosenEntry); break; case TAG_EXIT: // Terminate rEFInd - BeginTextScreen(L" "); - return EFI_SUCCESS; + if ((MokProtocol) && !SecureBootUninstall()) { + MainLoopRunning = FALSE; // just in case we get this far + } else { + BeginTextScreen(L" "); + return EFI_SUCCESS; + } + break; + + case TAG_FIRMWARE: // Reboot into firmware's user interface + RebootIntoFirmware(); break; } // switch()