]> code.delx.au - refind/blob - refind/main.c
PRELIMINARY workaround for Shim 0.8 bug.
[refind] / refind / main.c
1 /*
2 * refind/main.c
3 * Main code for the boot menu
4 *
5 * Copyright (c) 2006-2010 Christoph Pfisterer
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the
18 * distribution.
19 *
20 * * Neither the name of Christoph Pfisterer nor the names of the
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36 /*
37 * Modifications copyright (c) 2012-2015 Roderick W. Smith
38 *
39 * Modifications distributed under the terms of the GNU General Public
40 * License (GPL) version 3 (GPLv3), a copy of which must be distributed
41 * with this source code or binaries made from it.
42 *
43 */
44
45 #include "global.h"
46 #include "config.h"
47 #include "screen.h"
48 #include "legacy.h"
49 #include "lib.h"
50 #include "icns.h"
51 #include "menu.h"
52 #include "mok.h"
53 #include "gpt.h"
54 #include "security_policy.h"
55 #include "driver_support.h"
56 #include "../include/Handle.h"
57 #include "../include/refit_call_wrapper.h"
58 #include "../EfiLib/BdsHelper.h"
59 #include "../EfiLib/legacy.h"
60
61 #ifdef __MAKEWITH_GNUEFI
62 #ifndef EFI_SECURITY_VIOLATION
63 #define EFI_SECURITY_VIOLATION EFIERR (26)
64 #endif
65 #endif
66
67 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
68 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
69 #endif
70
71 #ifdef __MAKEWITH_TIANO
72 #define LibLocateHandle gBS->LocateHandleBuffer
73 #endif
74
75 //
76 // constants
77
78 #define MACOSX_LOADER_DIR L"System\\Library\\CoreServices"
79 #define MACOSX_LOADER_PATH ( MACOSX_LOADER_DIR L"\\boot.efi" )
80 #if defined (EFIX64)
81 #define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\EFI\\tools\\shellx64.efi,\\shell.efi,\\shellx64.efi"
82 #define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi,\\EFI\\tools\\gptsync_x64.efi"
83 #define GDISK_NAMES L"\\EFI\\tools\\gdisk.efi,\\EFI\\tools\\gdisk_x64.efi"
84 #define NETBOOT_NAMES L"\\EFI\\tools\\ipxe.efi"
85 #define MEMTEST_NAMES L"memtest86.efi,memtest86_x64.efi,memtest86x64.efi,bootx64.efi"
86 #define DRIVER_DIRS L"drivers,drivers_x64"
87 #define FALLBACK_FULLNAME L"EFI\\BOOT\\bootx64.efi"
88 #define FALLBACK_BASENAME L"bootx64.efi"
89 #define EFI_STUB_ARCH 0x8664
90 EFI_GUID gFreedesktopRootGuid = { 0x4f68bce3, 0xe8cd, 0x4db1, { 0x96, 0xe7, 0xfb, 0xca, 0xf9, 0x84, 0xb7, 0x09 }};
91 #elif defined (EFI32)
92 #define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\EFI\\tools\\shellia32.efi,\\shell.efi,\\shellia32.efi"
93 #define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi,\\EFI\\tools\\gptsync_ia32.efi"
94 #define GDISK_NAMES L"\\EFI\\tools\\gdisk.efi,\\EFI\\tools\\gdisk_ia32.efi"
95 #define NETBOOT_NAMES L"\\EFI\\tools\\ipxe.efi"
96 #define MEMTEST_NAMES L"memtest86.efi,memtest86_ia32.efi,memtest86ia32.efi,bootia32.efi"
97 #define DRIVER_DIRS L"drivers,drivers_ia32"
98 #define FALLBACK_FULLNAME L"EFI\\BOOT\\bootia32.efi"
99 #define FALLBACK_BASENAME L"bootia32.efi"
100 #define EFI_STUB_ARCH 0x014c
101 EFI_GUID gFreedesktopRootGuid = { 0x44479540, 0xf297, 0x41b2, { 0x9a, 0xf7, 0xd1, 0x31, 0xd5, 0xf0, 0x45, 0x8a }};
102 #else
103 #define SHELL_NAMES L"\\EFI\\tools\\shell.efi,\\shell.efi"
104 #define GPTSYNC_NAMES L"\\EFI\\tools\\gptsync.efi"
105 #define GDISK_NAMES L"\\EFI\\tools\\gdisk.efi"
106 #define NETBOOT_NAMES L"\\EFI\\tools\\ipxe.efi"
107 #define MEMTEST_NAMES L"memtest86.efi"
108 #define DRIVER_DIRS L"drivers"
109 #define FALLBACK_FULLNAME L"EFI\\BOOT\\boot.efi" /* Not really correct */
110 #define FALLBACK_BASENAME L"boot.efi" /* Not really correct */
111 // Below is GUID for ARM64
112 EFI_GUID gFreedesktopRootGuid = { 0xb921b045, 0x1df0, 0x41c3, { 0xaf, 0x44, 0x4c, 0x6f, 0x28, 0x0d, 0x3f, 0xae }};
113 #endif
114 #define FAT_ARCH 0x0ef1fab9 /* ID for Apple "fat" binary */
115
116 #define IPXE_DISCOVER_NAME L"\\efi\\tools\\ipxe_discover.efi"
117 #define IPXE_NAME L"\\efi\\tools\\ipxe.efi"
118
119 // Filename patterns that identify EFI boot loaders. Note that a single case (either L"*.efi" or
120 // L"*.EFI") is fine for most systems; but Gigabyte's buggy Hybrid EFI does a case-sensitive
121 // comparison when it should do a case-insensitive comparison, so I'm doubling this up. It does
122 // no harm on other computers, AFAIK. In theory, every case variation should be done for
123 // completeness, but that's ridiculous....
124 #define LOADER_MATCH_PATTERNS L"*.efi,*.EFI"
125
126 // Patterns that identify Linux kernels. Added to the loader match pattern when the
127 // scan_all_linux_kernels option is set in the configuration file. Causes kernels WITHOUT
128 // a ".efi" extension to be found when scanning for boot loaders.
129 #define LINUX_MATCH_PATTERNS L"vmlinuz*,bzImage*"
130
131 // Maximum length of a text string in certain menus
132 #define MAX_LINE_LENGTH 65
133
134 static REFIT_MENU_ENTRY MenuEntryAbout = { L"About rEFInd", TAG_ABOUT, 1, 0, 'A', NULL, NULL, NULL };
135 static REFIT_MENU_ENTRY MenuEntryReset = { L"Reboot Computer", TAG_REBOOT, 1, 0, 'R', NULL, NULL, NULL };
136 static REFIT_MENU_ENTRY MenuEntryShutdown = { L"Shut Down Computer", TAG_SHUTDOWN, 1, 0, 'U', NULL, NULL, NULL };
137 REFIT_MENU_ENTRY MenuEntryReturn = { L"Return to Main Menu", TAG_RETURN, 1, 0, 0, NULL, NULL, NULL };
138 static REFIT_MENU_ENTRY MenuEntryExit = { L"Exit rEFInd", TAG_EXIT, 1, 0, 0, NULL, NULL, NULL };
139 static REFIT_MENU_ENTRY MenuEntryFirmware = { L"Reboot to Computer Setup Utility", TAG_FIRMWARE, 1, 0, 0, NULL, NULL, NULL };
140
141 REFIT_MENU_SCREEN MainMenu = { L"Main Menu", NULL, 0, NULL, 0, NULL, 0, L"Automatic boot",
142 L"Use arrow keys to move cursor; Enter to boot;",
143 L"Insert or F2 for more options; Esc to refresh" };
144 static REFIT_MENU_SCREEN AboutMenu = { L"About", NULL, 0, NULL, 0, NULL, 0, NULL, L"Press Enter to return to main menu", L"" };
145
146 REFIT_CONFIG GlobalConfig = { FALSE, TRUE, FALSE, FALSE, TRUE, 0, 0, 0, DONT_CHANGE_TEXT_MODE, 20, 0, 0, GRAPHICS_FOR_OSX, LEGACY_TYPE_MAC,
147 0, 0, { DEFAULT_BIG_ICON_SIZE / 4, DEFAULT_SMALL_ICON_SIZE, DEFAULT_BIG_ICON_SIZE }, BANNER_NOSCALE,
148 NULL, NULL, NULL, NULL, CONFIG_FILE_NAME, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
149 { TAG_SHELL, TAG_MEMTEST, TAG_GDISK, TAG_APPLE_RECOVERY, TAG_WINDOWS_RECOVERY, TAG_MOK_TOOL,
150 TAG_ABOUT, TAG_SHUTDOWN, TAG_REBOOT, TAG_FIRMWARE, 0, 0, 0, 0, 0, 0, 0, 0 }
151 };
152
153 EFI_GUID GlobalGuid = EFI_GLOBAL_VARIABLE;
154 EFI_GUID RefindGuid = REFIND_GUID_VALUE;
155
156 GPT_DATA *gPartitions = NULL;
157
158 // Structure used to hold boot loader filenames and time stamps in
159 // a linked list; used to sort entries within a directory.
160 struct LOADER_LIST {
161 CHAR16 *FileName;
162 EFI_TIME TimeStamp;
163 struct LOADER_LIST *NextEntry;
164 };
165
166 //
167 // misc functions
168 //
169
170 static VOID AboutrEFInd(VOID)
171 {
172 CHAR16 *FirmwareVendor;
173
174 if (AboutMenu.EntryCount == 0) {
175 AboutMenu.TitleImage = BuiltinIcon(BUILTIN_ICON_FUNC_ABOUT);
176 AddMenuInfoLine(&AboutMenu, L"rEFInd Version 0.9.1.1");
177 AddMenuInfoLine(&AboutMenu, L"");
178 AddMenuInfoLine(&AboutMenu, L"Copyright (c) 2006-2010 Christoph Pfisterer");
179 AddMenuInfoLine(&AboutMenu, L"Copyright (c) 2012-2015 Roderick W. Smith");
180 AddMenuInfoLine(&AboutMenu, L"Portions Copyright (c) Intel Corporation and others");
181 AddMenuInfoLine(&AboutMenu, L"Distributed under the terms of the GNU GPLv3 license");
182 AddMenuInfoLine(&AboutMenu, L"");
183 AddMenuInfoLine(&AboutMenu, L"Running on:");
184 AddMenuInfoLine(&AboutMenu, PoolPrint(L" EFI Revision %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & ((1 << 16) - 1)));
185 #if defined(EFI32)
186 AddMenuInfoLine(&AboutMenu, PoolPrint(L" Platform: x86 (32 bit); Secure Boot %s",
187 secure_mode() ? L"active" : L"inactive"));
188 #elif defined(EFIX64)
189 AddMenuInfoLine(&AboutMenu, PoolPrint(L" Platform: x86_64 (64 bit); Secure Boot %s",
190 secure_mode() ? L"active" : L"inactive"));
191 #else
192 AddMenuInfoLine(&AboutMenu, L" Platform: unknown");
193 #endif
194 FirmwareVendor = StrDuplicate(ST->FirmwareVendor);
195 LimitStringLength(FirmwareVendor, MAX_LINE_LENGTH); // More than ~65 causes empty info page on 800x600 display
196 AddMenuInfoLine(&AboutMenu, PoolPrint(L" Firmware: %s %d.%02d", FirmwareVendor, ST->FirmwareRevision >> 16,
197 ST->FirmwareRevision & ((1 << 16) - 1)));
198 AddMenuInfoLine(&AboutMenu, PoolPrint(L" Screen Output: %s", egScreenDescription()));
199 AddMenuInfoLine(&AboutMenu, L"");
200 #if defined(__MAKEWITH_GNUEFI)
201 AddMenuInfoLine(&AboutMenu, L"Built with GNU-EFI");
202 #else
203 AddMenuInfoLine(&AboutMenu, L"Built with TianoCore EDK2");
204 #endif
205 AddMenuInfoLine(&AboutMenu, L"");
206 AddMenuInfoLine(&AboutMenu, L"For more information, see the rEFInd Web site:");
207 AddMenuInfoLine(&AboutMenu, L"http://www.rodsbooks.com/refind/");
208 AddMenuEntry(&AboutMenu, &MenuEntryReturn);
209 }
210
211 RunMenu(&AboutMenu, NULL);
212 } /* VOID AboutrEFInd() */
213
214 static VOID WarnSecureBootError(CHAR16 *Name, BOOLEAN Verbose) {
215 if (Name == NULL)
216 Name = L"the loader";
217
218 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_ERROR);
219 Print(L"Secure Boot validation failure loading %s!\n", Name);
220 refit_call2_wrapper(ST->ConOut->SetAttribute, ST->ConOut, ATTR_BASIC);
221 if (Verbose && secure_mode()) {
222 Print(L"\nThis computer is configured with Secure Boot active, but\n%s has failed validation.\n", Name);
223 Print(L"\nYou can:\n * Launch another boot loader\n");
224 Print(L" * Disable Secure Boot in your firmware\n");
225 Print(L" * Sign %s with a machine owner key (MOK)\n", Name);
226 Print(L" * Use a MOK utility (often present on the second row) to add a MOK with which\n");
227 Print(L" %s has already been signed.\n", Name);
228 Print(L" * Use a MOK utility to register %s (\"enroll its hash\") without\n", Name);
229 Print(L" signing it.\n");
230 Print(L"\nSee http://www.rodsbooks.com/refind/secureboot.html for more information\n");
231 PauseForKey();
232 } // if
233 } // VOID WarnSecureBootError()
234
235 // Returns TRUE if this file is a valid EFI loader file, and is proper ARCH
236 static BOOLEAN IsValidLoader(EFI_FILE *RootDir, CHAR16 *FileName) {
237 BOOLEAN IsValid = TRUE;
238 #if defined (EFIX64) | defined (EFI32)
239 EFI_STATUS Status;
240 EFI_FILE_HANDLE FileHandle;
241 CHAR8 Header[512];
242 UINTN Size = sizeof(Header);
243
244 if ((RootDir == NULL) || (FileName == NULL)) {
245 // Assume valid here, because Macs produce NULL RootDir (& maybe FileName)
246 // when launching from a Firewire drive. This should be handled better, but
247 // fix would have to be in StartEFIImageList() and/or in FindVolumeAndFilename().
248 return TRUE;
249 } // if
250
251 Status = refit_call5_wrapper(RootDir->Open, RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0);
252 if (EFI_ERROR(Status))
253 return FALSE;
254
255 Status = refit_call3_wrapper(FileHandle->Read, FileHandle, &Size, Header);
256 refit_call1_wrapper(FileHandle->Close, FileHandle);
257
258 IsValid = !EFI_ERROR(Status) &&
259 Size == sizeof(Header) &&
260 ((Header[0] == 'M' && Header[1] == 'Z' &&
261 (Size = *(UINT32 *)&Header[0x3c]) < 0x180 &&
262 Header[Size] == 'P' && Header[Size+1] == 'E' &&
263 Header[Size+2] == 0 && Header[Size+3] == 0 &&
264 *(UINT16 *)&Header[Size+4] == EFI_STUB_ARCH) ||
265 (*(UINT32 *)&Header == FAT_ARCH));
266 #endif
267 return IsValid;
268 } // BOOLEAN IsValidLoader()
269
270 // Launch an EFI binary.
271 EFI_STATUS StartEFIImageList(IN EFI_DEVICE_PATH **DevicePaths,
272 IN CHAR16 *LoadOptions, IN UINTN LoaderType,
273 IN CHAR16 *ImageTitle, IN CHAR8 OSType,
274 OUT UINTN *ErrorInStep,
275 IN BOOLEAN Verbose,
276 IN BOOLEAN IsDriver)
277 {
278 EFI_STATUS Status, ReturnStatus;
279 EFI_HANDLE ChildImageHandle, ChildImageHandle2;
280 EFI_LOADED_IMAGE *ChildLoadedImage = NULL;
281 REFIT_VOLUME *Volume = NULL;
282 UINTN DevicePathIndex;
283 CHAR16 ErrorInfo[256];
284 CHAR16 *FullLoadOptions = NULL;
285 CHAR16 *Filename = NULL;
286 CHAR16 *Temp;
287
288 if (ErrorInStep != NULL)
289 *ErrorInStep = 0;
290
291 // set load options
292 if (LoadOptions != NULL) {
293 FullLoadOptions = StrDuplicate(LoadOptions);
294 if ((LoaderType == TYPE_EFI) && (OSType == 'M')) {
295 MergeStrings(&FullLoadOptions, L" ", 0);
296 // NOTE: That last space is also added by the EFI shell and seems to be significant
297 // when passing options to Apple's boot.efi...
298 } // if
299 } // if (LoadOptions != NULL)
300 if (Verbose)
301 Print(L"Starting %s\nUsing load options '%s'\n", ImageTitle, FullLoadOptions ? FullLoadOptions : L"");
302
303 // load the image into memory
304 ReturnStatus = Status = EFI_NOT_FOUND; // in case the list is empty
305 for (DevicePathIndex = 0; DevicePaths[DevicePathIndex] != NULL; DevicePathIndex++) {
306 FindVolumeAndFilename(DevicePaths[DevicePathIndex], &Volume, &Filename);
307 // Some EFIs crash if attempting to load driver for invalid architecture, so
308 // protect for this condition; but sometimes Volume comes back NULL, so provide
309 // an exception. (TODO: Handle this special condition better.)
310 if ((LoaderType == TYPE_LEGACY) || (Volume == NULL) || IsValidLoader(Volume->RootDir, Filename)) {
311 if (Filename && (LoaderType != TYPE_LEGACY)) {
312 Temp = PoolPrint(L"\\%s %s", Filename, FullLoadOptions ? FullLoadOptions : L"");
313 if (Temp != NULL) {
314 MyFreePool(FullLoadOptions);
315 FullLoadOptions = Temp;
316 }
317 } // if (Filename)
318
319 // NOTE: Below commented-out line could be more efficient if file were read ahead of
320 // time and passed as a pre-loaded image to LoadImage(), but it doesn't work on my
321 // 32-bit Mac Mini or my 64-bit Intel box when launching a Linux kernel; the
322 // kernel returns a "Failed to handle fs_proto" error message.
323 // TODO: Track down the cause of this error and fix it, if possible.
324 // ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex],
325 // ImageData, ImageSize, &ChildImageHandle);
326 ReturnStatus = Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, DevicePaths[DevicePathIndex],
327 NULL, 0, &ChildImageHandle);
328 if (secure_mode() && ShimLoaded()) {
329 // Load ourself into memory. This is a trick to work around a bug in Shim 0.8,
330 // which ties itself into the BS->LoadImage() and BS->StartImage() functions and
331 // then unregisters itself from the EFI system table when its replacement
332 // StartImage() function is called *IF* the previous LoadImage() was for the same
333 // program. The result is that rEFInd can validate only the first program it
334 // launches (often a filesystem driver). Loading a second program (rEFInd itself,
335 // here, to keep it smaller than a kernel) works around this problem. See the
336 // replacements.c file in Shim, and especially its start_image() function, for
337 // the source of the problem.
338 // NOTE: This doesn't check the return status or handle errors. It could
339 // conceivably do weird things if, say, rEFInd were on a USB drive that the
340 // user pulls before launching a program.
341 refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, GlobalConfig.SelfDevicePath,
342 NULL, 0, &ChildImageHandle2);
343 }
344 } else {
345 Print(L"Invalid loader file!\n");
346 ReturnStatus = EFI_LOAD_ERROR;
347 }
348 if (ReturnStatus != EFI_NOT_FOUND) {
349 break;
350 }
351 } // for
352 if ((Status == EFI_ACCESS_DENIED) || (Status == EFI_SECURITY_VIOLATION)) {
353 WarnSecureBootError(ImageTitle, Verbose);
354 goto bailout;
355 }
356 SPrint(ErrorInfo, 255, L"while loading %s", ImageTitle);
357 if (CheckError(Status, ErrorInfo)) {
358 if (ErrorInStep != NULL)
359 *ErrorInStep = 1;
360 goto bailout;
361 }
362
363 ReturnStatus = Status = refit_call3_wrapper(BS->HandleProtocol, ChildImageHandle, &LoadedImageProtocol,
364 (VOID **) &ChildLoadedImage);
365 if (CheckError(Status, L"while getting a LoadedImageProtocol handle")) {
366 if (ErrorInStep != NULL)
367 *ErrorInStep = 2;
368 goto bailout_unload;
369 }
370 ChildLoadedImage->LoadOptions = (VOID *)FullLoadOptions;
371 ChildLoadedImage->LoadOptionsSize = FullLoadOptions ? ((UINT32)StrLen(FullLoadOptions) + 1) * sizeof(CHAR16) : 0;
372 // turn control over to the image
373 // TODO: (optionally) re-enable the EFI watchdog timer!
374
375 // close open file handles
376 UninitRefitLib();
377 ReturnStatus = Status = refit_call3_wrapper(BS->StartImage, ChildImageHandle, NULL, NULL);
378
379 // control returns here when the child image calls Exit()
380 SPrint(ErrorInfo, 255, L"returned from %s", ImageTitle);
381 if (CheckError(Status, ErrorInfo)) {
382 if (ErrorInStep != NULL)
383 *ErrorInStep = 3;
384 }
385
386 // re-open file handles
387 ReinitRefitLib();
388
389 bailout_unload:
390 // unload the image, we don't care if it works or not...
391 if (!IsDriver)
392 Status = refit_call1_wrapper(BS->UnloadImage, ChildImageHandle);
393
394 bailout:
395 MyFreePool(FullLoadOptions);
396 return ReturnStatus;
397 } /* EFI_STATUS StartEFIImageList() */
398
399 static EFI_STATUS StartEFIImage(IN EFI_DEVICE_PATH *DevicePath,
400 IN CHAR16 *LoadOptions, IN UINTN LoaderType,
401 IN CHAR16 *ImageTitle, IN CHAR8 OSType,
402 OUT UINTN *ErrorInStep,
403 IN BOOLEAN Verbose,
404 IN BOOLEAN IsDriver
405 )
406 {
407 EFI_DEVICE_PATH *DevicePaths[2];
408
409 DevicePaths[0] = DevicePath;
410 DevicePaths[1] = NULL;
411 return StartEFIImageList(DevicePaths, LoadOptions, LoaderType, ImageTitle, OSType, ErrorInStep, Verbose, IsDriver);
412 } /* static EFI_STATUS StartEFIImage() */
413
414 // From gummiboot: Reboot the computer into its built-in user interface
415 static EFI_STATUS RebootIntoFirmware(VOID) {
416 CHAR8 *b;
417 UINTN size;
418 UINT64 osind;
419 EFI_STATUS err;
420
421 osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
422
423 err = EfivarGetRaw(&GlobalGuid, L"OsIndications", &b, &size);
424 if (err == EFI_SUCCESS)
425 osind |= (UINT64)*b;
426 MyFreePool(b);
427
428 err = EfivarSetRaw(&GlobalGuid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE);
429 if (err != EFI_SUCCESS)
430 return err;
431
432 refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL);
433 Print(L"Error calling ResetSystem: %r", err);
434 PauseForKey();
435 return err;
436 }
437
438 // Record the value of the loader's name/description in rEFInd's "PreviousBoot" EFI variable,
439 // if it's different from what's already stored there.
440 VOID StoreLoaderName(IN CHAR16 *Name) {
441 EFI_STATUS Status;
442 CHAR16 *OldName = NULL;
443 UINTN Length;
444
445 if (Name) {
446 Status = EfivarGetRaw(&RefindGuid, L"PreviousBoot", (CHAR8**) &OldName, &Length);
447 if ((Status != EFI_SUCCESS) || (StrCmp(OldName, Name) != 0)) {
448 EfivarSetRaw(&RefindGuid, L"PreviousBoot", (CHAR8*) Name, StrLen(Name) * 2 + 2, TRUE);
449 } // if
450 MyFreePool(OldName);
451 } // if
452 } // VOID StoreLoaderName()
453
454 //
455 // EFI OS loader functions
456 //
457
458 // See http://www.thomas-krenn.com/en/wiki/Activating_the_Intel_VT_Virtualization_Feature
459 // for information on Intel VMX features
460 static VOID DoEnableAndLockVMX(VOID)
461 {
462 UINT32 msr = 0x3a;
463 UINT32 low_bits = 0, high_bits = 0;
464
465 // is VMX active ?
466 __asm__ volatile ("rdmsr" : "=a" (low_bits), "=d" (high_bits) : "c" (msr));
467
468 // enable and lock vmx if not locked
469 if ((low_bits & 1) == 0) {
470 high_bits = 0;
471 low_bits = 0x05;
472 msr = 0x3a;
473 __asm__ volatile ("wrmsr" : : "c" (msr), "a" (low_bits), "d" (high_bits));
474 }
475 } // VOID DoEnableAndLockVMX()
476
477 static VOID StartLoader(LOADER_ENTRY *Entry, CHAR16 *SelectionName)
478 {
479 UINTN ErrorInStep = 0;
480
481 if (GlobalConfig.EnableAndLockVMX) {
482 DoEnableAndLockVMX();
483 }
484
485 BeginExternalScreen(Entry->UseGraphicsMode, L"Booting OS");
486 StoreLoaderName(SelectionName);
487 StartEFIImage(Entry->DevicePath, Entry->LoadOptions, TYPE_EFI,
488 Basename(Entry->LoaderPath), Entry->OSType, &ErrorInStep, !Entry->UseGraphicsMode, FALSE);
489 FinishExternalScreen();
490 }
491
492 // Locate an initrd or initramfs file that matches the kernel specified by LoaderPath.
493 // The matching file has a name that begins with "init" and includes the same version
494 // number string as is found in LoaderPath -- but not a longer version number string.
495 // For instance, if LoaderPath is \EFI\kernels\bzImage-3.3.0.efi, and if \EFI\kernels
496 // has a file called initramfs-3.3.0.img, this function will return the string
497 // '\EFI\kernels\initramfs-3.3.0.img'. If the directory ALSO contains the file
498 // initramfs-3.3.0-rc7.img or initramfs-13.3.0.img, those files will NOT match;
499 // however, initmine-3.3.0.img might match. (FindInitrd() returns the first match it
500 // finds.) Thus, care should be taken to avoid placing duplicate matching files in
501 // the kernel's directory.
502 // If no matching init file can be found, returns NULL.
503 static CHAR16 * FindInitrd(IN CHAR16 *LoaderPath, IN REFIT_VOLUME *Volume) {
504 CHAR16 *InitrdName = NULL, *FileName, *KernelVersion, *InitrdVersion, *Path;
505 REFIT_DIR_ITER DirIter;
506 EFI_FILE_INFO *DirEntry;
507
508 FileName = Basename(LoaderPath);
509 KernelVersion = FindNumbers(FileName);
510 Path = FindPath(LoaderPath);
511
512 // Add trailing backslash for root directory; necessary on some systems, but must
513 // NOT be added to all directories, since on other systems, a trailing backslash on
514 // anything but the root directory causes them to flake out!
515 if (StrLen(Path) == 0) {
516 MergeStrings(&Path, L"\\", 0);
517 } // if
518 DirIterOpen(Volume->RootDir, Path, &DirIter);
519 // Now add a trailing backslash if it was NOT added earlier, for consistency in
520 // building the InitrdName later....
521 if ((StrLen(Path) > 0) && (Path[StrLen(Path) - 1] != L'\\'))
522 MergeStrings(&Path, L"\\", 0);
523 while ((DirIterNext(&DirIter, 2, L"init*", &DirEntry)) && (InitrdName == NULL)) {
524 InitrdVersion = FindNumbers(DirEntry->FileName);
525 if (KernelVersion != NULL) {
526 if (MyStriCmp(InitrdVersion, KernelVersion)) {
527 InitrdName = PoolPrint(L"%s%s", Path, DirEntry->FileName);
528 } // if
529 } else {
530 if (InitrdVersion == NULL) {
531 InitrdName = PoolPrint(L"%s%s", Path, DirEntry->FileName);
532 } // if
533 } // if/else
534 MyFreePool(InitrdVersion);
535 } // while
536 DirIterClose(&DirIter);
537
538 // Note: Don't FreePool(FileName), since Basename returns a pointer WITHIN the string it's passed.
539 MyFreePool(KernelVersion);
540 MyFreePool(Path);
541 return (InitrdName);
542 } // static CHAR16 * FindInitrd()
543
544 LOADER_ENTRY * AddPreparedLoaderEntry(LOADER_ENTRY *Entry) {
545 AddMenuEntry(&MainMenu, (REFIT_MENU_ENTRY *)Entry);
546
547 return(Entry);
548 } // LOADER_ENTRY * AddPreparedLoaderEntry()
549
550 // Creates a copy of a menu screen.
551 // Returns a pointer to the copy of the menu screen.
552 static REFIT_MENU_SCREEN* CopyMenuScreen(REFIT_MENU_SCREEN *Entry) {
553 REFIT_MENU_SCREEN *NewEntry;
554 UINTN i;
555
556 NewEntry = AllocateZeroPool(sizeof(REFIT_MENU_SCREEN));
557 if ((Entry != NULL) && (NewEntry != NULL)) {
558 CopyMem(NewEntry, Entry, sizeof(REFIT_MENU_SCREEN));
559 NewEntry->Title = (Entry->Title) ? StrDuplicate(Entry->Title) : NULL;
560 NewEntry->TimeoutText = (Entry->TimeoutText) ? StrDuplicate(Entry->TimeoutText) : NULL;
561 if (Entry->TitleImage != NULL) {
562 NewEntry->TitleImage = AllocatePool(sizeof(EG_IMAGE));
563 if (NewEntry->TitleImage != NULL)
564 CopyMem(NewEntry->TitleImage, Entry->TitleImage, sizeof(EG_IMAGE));
565 } // if
566 NewEntry->InfoLines = (CHAR16**) AllocateZeroPool(Entry->InfoLineCount * (sizeof(CHAR16*)));
567 for (i = 0; i < Entry->InfoLineCount && NewEntry->InfoLines; i++) {
568 NewEntry->InfoLines[i] = (Entry->InfoLines[i]) ? StrDuplicate(Entry->InfoLines[i]) : NULL;
569 } // for
570 NewEntry->Entries = (REFIT_MENU_ENTRY**) AllocateZeroPool(Entry->EntryCount * (sizeof (REFIT_MENU_ENTRY*)));
571 for (i = 0; i < Entry->EntryCount && NewEntry->Entries; i++) {
572 AddMenuEntry(NewEntry, Entry->Entries[i]);
573 } // for
574 NewEntry->Hint1 = (Entry->Hint1) ? StrDuplicate(Entry->Hint1) : NULL;
575 NewEntry->Hint2 = (Entry->Hint2) ? StrDuplicate(Entry->Hint2) : NULL;
576 } // if
577 return (NewEntry);
578 } // static REFIT_MENU_SCREEN* CopyMenuScreen()
579
580 // Creates a copy of a menu entry. Intended to enable moving a stack-based
581 // menu entry (such as the ones for the "reboot" and "exit" functions) to
582 // to the heap. This enables easier deletion of the whole set of menu
583 // entries when re-scanning.
584 // Returns a pointer to the copy of the menu entry.
585 static REFIT_MENU_ENTRY* CopyMenuEntry(REFIT_MENU_ENTRY *Entry) {
586 REFIT_MENU_ENTRY *NewEntry;
587
588 NewEntry = AllocateZeroPool(sizeof(REFIT_MENU_ENTRY));
589 if ((Entry != NULL) && (NewEntry != NULL)) {
590 CopyMem(NewEntry, Entry, sizeof(REFIT_MENU_ENTRY));
591 NewEntry->Title = (Entry->Title) ? StrDuplicate(Entry->Title) : NULL;
592 if (Entry->BadgeImage != NULL) {
593 NewEntry->BadgeImage = AllocatePool(sizeof(EG_IMAGE));
594 if (NewEntry->BadgeImage != NULL)
595 CopyMem(NewEntry->BadgeImage, Entry->BadgeImage, sizeof(EG_IMAGE));
596 }
597 if (Entry->Image != NULL) {
598 NewEntry->Image = AllocatePool(sizeof(EG_IMAGE));
599 if (NewEntry->Image != NULL)
600 CopyMem(NewEntry->Image, Entry->Image, sizeof(EG_IMAGE));
601 }
602 if (Entry->SubScreen != NULL) {
603 NewEntry->SubScreen = CopyMenuScreen(Entry->SubScreen);
604 }
605 } // if
606 return (NewEntry);
607 } // REFIT_MENU_ENTRY* CopyMenuEntry()
608
609 // Creates a new LOADER_ENTRY data structure and populates it with
610 // default values from the specified Entry, or NULL values if Entry
611 // is unspecified (NULL).
612 // Returns a pointer to the new data structure, or NULL if it
613 // couldn't be allocated
614 LOADER_ENTRY *InitializeLoaderEntry(IN LOADER_ENTRY *Entry) {
615 LOADER_ENTRY *NewEntry = NULL;
616
617 NewEntry = AllocateZeroPool(sizeof(LOADER_ENTRY));
618 if (NewEntry != NULL) {
619 NewEntry->me.Title = NULL;
620 NewEntry->me.Tag = TAG_LOADER;
621 NewEntry->Enabled = TRUE;
622 NewEntry->UseGraphicsMode = FALSE;
623 NewEntry->OSType = 0;
624 if (Entry != NULL) {
625 NewEntry->LoaderPath = (Entry->LoaderPath) ? StrDuplicate(Entry->LoaderPath) : NULL;
626 NewEntry->VolName = (Entry->VolName) ? StrDuplicate(Entry->VolName) : NULL;
627 NewEntry->DevicePath = Entry->DevicePath;
628 NewEntry->UseGraphicsMode = Entry->UseGraphicsMode;
629 NewEntry->LoadOptions = (Entry->LoadOptions) ? StrDuplicate(Entry->LoadOptions) : NULL;
630 NewEntry->InitrdPath = (Entry->InitrdPath) ? StrDuplicate(Entry->InitrdPath) : NULL;
631 }
632 } // if
633 return (NewEntry);
634 } // LOADER_ENTRY *InitializeLoaderEntry()
635
636 // Adds InitrdPath to Options, but only if Options doesn't already include an
637 // initrd= line. Done to enable overriding the default initrd selection in a
638 // refind_linux.conf file's options list.
639 // Returns a pointer to a new string. The calling function is responsible for
640 // freeing its memory.
641 static CHAR16 *AddInitrdToOptions(CHAR16 *Options, CHAR16 *InitrdPath) {
642 CHAR16 *NewOptions = NULL;
643
644 if (Options != NULL)
645 NewOptions = StrDuplicate(Options);
646 if ((InitrdPath != NULL) && !StriSubCmp(L"initrd=", Options)) {
647 MergeStrings(&NewOptions, L"initrd=", L' ');
648 MergeStrings(&NewOptions, InitrdPath, 0);
649 }
650 return NewOptions;
651 } // CHAR16 *AddInitrdToOptions()
652
653 // Prepare a REFIT_MENU_SCREEN data structure for a subscreen entry. This sets up
654 // the default entry that launches the boot loader using the same options as the
655 // main Entry does. Subsequent options can be added by the calling function.
656 // If a subscreen already exists in the Entry that's passed to this function,
657 // it's left unchanged and a pointer to it is returned.
658 // Returns a pointer to the new subscreen data structure, or NULL if there
659 // were problems allocating memory.
660 REFIT_MENU_SCREEN *InitializeSubScreen(IN LOADER_ENTRY *Entry) {
661 CHAR16 *FileName, *MainOptions = NULL;
662 REFIT_MENU_SCREEN *SubScreen = NULL;
663 LOADER_ENTRY *SubEntry;
664
665 FileName = Basename(Entry->LoaderPath);
666 if (Entry->me.SubScreen == NULL) { // No subscreen yet; initialize default entry....
667 SubScreen = AllocateZeroPool(sizeof(REFIT_MENU_SCREEN));
668 if (SubScreen != NULL) {
669 SubScreen->Title = AllocateZeroPool(sizeof(CHAR16) * 256);
670 SPrint(SubScreen->Title, 255, L"Boot Options for %s on %s",
671 (Entry->Title != NULL) ? Entry->Title : FileName, Entry->VolName);
672 SubScreen->TitleImage = Entry->me.Image;
673 // default entry
674 SubEntry = InitializeLoaderEntry(Entry);
675 if (SubEntry != NULL) {
676 SubEntry->me.Title = StrDuplicate(L"Boot using default options");
677 MainOptions = SubEntry->LoadOptions;
678 SubEntry->LoadOptions = AddInitrdToOptions(MainOptions, SubEntry->InitrdPath);
679 MyFreePool(MainOptions);
680 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
681 } // if (SubEntry != NULL)
682 SubScreen->Hint1 = StrDuplicate(SUBSCREEN_HINT1);
683 if (GlobalConfig.HideUIFlags & HIDEUI_FLAG_EDITOR) {
684 SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2_NO_EDITOR);
685 } else {
686 SubScreen->Hint2 = StrDuplicate(SUBSCREEN_HINT2);
687 } // if/else
688 } // if (SubScreen != NULL)
689 } else { // existing subscreen; less initialization, and just add new entry later....
690 SubScreen = Entry->me.SubScreen;
691 } // if/else
692 return SubScreen;
693 } // REFIT_MENU_SCREEN *InitializeSubScreen()
694
695 VOID GenerateSubScreen(LOADER_ENTRY *Entry, IN REFIT_VOLUME *Volume, IN BOOLEAN GenerateReturn) {
696 REFIT_MENU_SCREEN *SubScreen;
697 LOADER_ENTRY *SubEntry;
698 CHAR16 *InitrdName;
699 CHAR16 DiagsFileName[256];
700 REFIT_FILE *File;
701 UINTN TokenCount;
702 CHAR16 **TokenList;
703
704 // create the submenu
705 if (StrLen(Entry->Title) == 0) {
706 MyFreePool(Entry->Title);
707 Entry->Title = NULL;
708 }
709 SubScreen = InitializeSubScreen(Entry);
710
711 // loader-specific submenu entries
712 if (Entry->OSType == 'M') { // entries for Mac OS X
713 #if defined(EFIX64)
714 SubEntry = InitializeLoaderEntry(Entry);
715 if (SubEntry != NULL) {
716 SubEntry->me.Title = L"Boot Mac OS X with a 64-bit kernel";
717 SubEntry->LoadOptions = L"arch=x86_64";
718 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_OSX;
719 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
720 } // if
721
722 SubEntry = InitializeLoaderEntry(Entry);
723 if (SubEntry != NULL) {
724 SubEntry->me.Title = L"Boot Mac OS X with a 32-bit kernel";
725 SubEntry->LoadOptions = L"arch=i386";
726 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_OSX;
727 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
728 } // if
729 #endif
730
731 if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_SINGLEUSER)) {
732 SubEntry = InitializeLoaderEntry(Entry);
733 if (SubEntry != NULL) {
734 SubEntry->me.Title = L"Boot Mac OS X in verbose mode";
735 SubEntry->UseGraphicsMode = FALSE;
736 SubEntry->LoadOptions = L"-v";
737 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
738 } // if
739
740 #if defined(EFIX64)
741 SubEntry = InitializeLoaderEntry(Entry);
742 if (SubEntry != NULL) {
743 SubEntry->me.Title = L"Boot Mac OS X in verbose mode (64-bit)";
744 SubEntry->UseGraphicsMode = FALSE;
745 SubEntry->LoadOptions = L"-v arch=x86_64";
746 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
747 }
748
749 SubEntry = InitializeLoaderEntry(Entry);
750 if (SubEntry != NULL) {
751 SubEntry->me.Title = L"Boot Mac OS X in verbose mode (32-bit)";
752 SubEntry->UseGraphicsMode = FALSE;
753 SubEntry->LoadOptions = L"-v arch=i386";
754 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
755 }
756 #endif
757
758 SubEntry = InitializeLoaderEntry(Entry);
759 if (SubEntry != NULL) {
760 SubEntry->me.Title = L"Boot Mac OS X in single user mode";
761 SubEntry->UseGraphicsMode = FALSE;
762 SubEntry->LoadOptions = L"-v -s";
763 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
764 } // if
765 } // single-user mode allowed
766
767 if (!(GlobalConfig.HideUIFlags & HIDEUI_FLAG_SAFEMODE)) {
768 SubEntry = InitializeLoaderEntry(Entry);
769 if (SubEntry != NULL) {
770 SubEntry->me.Title = L"Boot Mac OS X in safe mode";
771 SubEntry->UseGraphicsMode = FALSE;
772 SubEntry->LoadOptions = L"-v -x";
773 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
774 } // if
775 } // safe mode allowed
776
777 // check for Apple hardware diagnostics
778 StrCpy(DiagsFileName, L"System\\Library\\CoreServices\\.diagnostics\\diags.efi");
779 if (FileExists(Volume->RootDir, DiagsFileName) && !(GlobalConfig.HideUIFlags & HIDEUI_FLAG_HWTEST)) {
780 SubEntry = InitializeLoaderEntry(Entry);
781 if (SubEntry != NULL) {
782 SubEntry->me.Title = L"Run Apple Hardware Test";
783 MyFreePool(SubEntry->LoaderPath);
784 SubEntry->LoaderPath = StrDuplicate(DiagsFileName);
785 SubEntry->DevicePath = FileDevicePath(Volume->DeviceHandle, SubEntry->LoaderPath);
786 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_OSX;
787 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
788 } // if
789 } // if diagnostics entry found
790
791 } else if (Entry->OSType == 'L') { // entries for Linux kernels with EFI stub loaders
792 File = ReadLinuxOptionsFile(Entry->LoaderPath, Volume);
793 if (File != NULL) {
794 InitrdName = FindInitrd(Entry->LoaderPath, Volume);
795 TokenCount = ReadTokenLine(File, &TokenList);
796 // first entry requires special processing, since it was initially set
797 // up with a default title but correct options by InitializeSubScreen(),
798 // earlier....
799 if ((TokenCount > 1) && (SubScreen->Entries != NULL) && (SubScreen->Entries[0] != NULL)) {
800 MyFreePool(SubScreen->Entries[0]->Title);
801 SubScreen->Entries[0]->Title = TokenList[0] ? StrDuplicate(TokenList[0]) : StrDuplicate(L"Boot Linux");
802 } // if
803 FreeTokenLine(&TokenList, &TokenCount);
804 while ((TokenCount = ReadTokenLine(File, &TokenList)) > 1) {
805 SubEntry = InitializeLoaderEntry(Entry);
806 SubEntry->me.Title = TokenList[0] ? StrDuplicate(TokenList[0]) : StrDuplicate(L"Boot Linux");
807 MyFreePool(SubEntry->LoadOptions);
808 SubEntry->LoadOptions = AddInitrdToOptions(TokenList[1], InitrdName);
809 FreeTokenLine(&TokenList, &TokenCount);
810 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_LINUX;
811 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
812 } // while
813 MyFreePool(InitrdName);
814 MyFreePool(File);
815 } // if
816
817 } else if (Entry->OSType == 'E') { // entries for ELILO
818 SubEntry = InitializeLoaderEntry(Entry);
819 if (SubEntry != NULL) {
820 SubEntry->me.Title = L"Run ELILO in interactive mode";
821 SubEntry->LoadOptions = L"-p";
822 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO;
823 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
824 }
825
826 SubEntry = InitializeLoaderEntry(Entry);
827 if (SubEntry != NULL) {
828 SubEntry->me.Title = L"Boot Linux for a 17\" iMac or a 15\" MacBook Pro (*)";
829 SubEntry->LoadOptions = L"-d 0 i17";
830 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO;
831 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
832 }
833
834 SubEntry = InitializeLoaderEntry(Entry);
835 if (SubEntry != NULL) {
836 SubEntry->me.Title = L"Boot Linux for a 20\" iMac (*)";
837 SubEntry->LoadOptions = L"-d 0 i20";
838 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO;
839 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
840 }
841
842 SubEntry = InitializeLoaderEntry(Entry);
843 if (SubEntry != NULL) {
844 SubEntry->me.Title = L"Boot Linux for a Mac Mini (*)";
845 SubEntry->LoadOptions = L"-d 0 mini";
846 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO;
847 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
848 }
849
850 AddMenuInfoLine(SubScreen, L"NOTE: This is an example. Entries");
851 AddMenuInfoLine(SubScreen, L"marked with (*) may not work.");
852
853 } else if (Entry->OSType == 'X') { // entries for xom.efi
854 // by default, skip the built-in selection and boot from hard disk only
855 Entry->LoadOptions = L"-s -h";
856
857 SubEntry = InitializeLoaderEntry(Entry);
858 if (SubEntry != NULL) {
859 SubEntry->me.Title = L"Boot Windows from Hard Disk";
860 SubEntry->LoadOptions = L"-s -h";
861 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_WINDOWS;
862 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
863 }
864
865 SubEntry = InitializeLoaderEntry(Entry);
866 if (SubEntry != NULL) {
867 SubEntry->me.Title = L"Boot Windows from CD-ROM";
868 SubEntry->LoadOptions = L"-s -c";
869 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_WINDOWS;
870 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
871 }
872
873 SubEntry = InitializeLoaderEntry(Entry);
874 if (SubEntry != NULL) {
875 SubEntry->me.Title = L"Run XOM in text mode";
876 SubEntry->LoadOptions = L"-v";
877 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_WINDOWS;
878 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
879 }
880 } // entries for xom.efi
881 if (GenerateReturn)
882 AddMenuEntry(SubScreen, &MenuEntryReturn);
883 Entry->me.SubScreen = SubScreen;
884 } // VOID GenerateSubScreen()
885
886 // Returns options for a Linux kernel. Reads them from an options file in the
887 // kernel's directory; and if present, adds an initrd= option for an initial
888 // RAM disk file with the same version number as the kernel file.
889 static CHAR16 * GetMainLinuxOptions(IN CHAR16 * LoaderPath, IN REFIT_VOLUME *Volume) {
890 CHAR16 *Options = NULL, *InitrdName, *FullOptions = NULL;
891
892 Options = GetFirstOptionsFromFile(LoaderPath, Volume);
893 InitrdName = FindInitrd(LoaderPath, Volume);
894 FullOptions = AddInitrdToOptions(Options, InitrdName);
895
896 MyFreePool(Options);
897 MyFreePool(InitrdName);
898 return (FullOptions);
899 } // static CHAR16 * GetMainLinuxOptions()
900
901 // Read the specified file and add values of "ID", "NAME", or "DISTRIB_ID" tokens to
902 // OSIconName list. Intended for adding Linux distribution clues gleaned from
903 // /etc/lsb-release and /etc/os-release files.
904 static VOID ParseReleaseFile(CHAR16 **OSIconName, REFIT_VOLUME *Volume, CHAR16 *FileName) {
905 UINTN FileSize = 0;
906 REFIT_FILE File;
907 CHAR16 **TokenList;
908 UINTN TokenCount = 0;
909
910 if ((Volume == NULL) || (FileName == NULL) || (OSIconName == NULL) || (*OSIconName == NULL))
911 return;
912
913 if (FileExists(Volume->RootDir, FileName) &&
914 (ReadFile(Volume->RootDir, FileName, &File, &FileSize) == EFI_SUCCESS)) {
915 do {
916 TokenCount = ReadTokenLine(&File, &TokenList);
917 if ((TokenCount > 1) && (MyStriCmp(TokenList[0], L"ID") ||
918 MyStriCmp(TokenList[0], L"NAME") ||
919 MyStriCmp(TokenList[0], L"DISTRIB_ID"))) {
920 MergeWords(OSIconName, TokenList[1], L',');
921 } // if
922 FreeTokenLine(&TokenList, &TokenCount);
923 } while (TokenCount > 0);
924 MyFreePool(File.Buffer);
925 } // if
926 } // VOID ParseReleaseFile()
927
928 // Try to guess the name of the Linux distribution & add that name to
929 // OSIconName list.
930 static VOID GuessLinuxDistribution(CHAR16 **OSIconName, REFIT_VOLUME *Volume, CHAR16 *LoaderPath) {
931 // If on Linux root fs, /etc/os-release or /etc/lsb-release file probably has clues....
932 ParseReleaseFile(OSIconName, Volume, L"etc\\lsb-release");
933 ParseReleaseFile(OSIconName, Volume, L"etc\\os-release");
934
935 // Search for clues in the kernel's filename....
936 if (StriSubCmp(L".fc", LoaderPath))
937 MergeStrings(OSIconName, L"fedora", L',');
938 if (StriSubCmp(L".el", LoaderPath))
939 MergeStrings(OSIconName, L"redhat", L',');
940 } // VOID GuessLinuxDistribution()
941
942 // Sets a few defaults for a loader entry -- mainly the icon, but also the OS type
943 // code and shortcut letter. For Linux EFI stub loaders, also sets kernel options
944 // that will (with luck) work fairly automatically.
945 VOID SetLoaderDefaults(LOADER_ENTRY *Entry, CHAR16 *LoaderPath, REFIT_VOLUME *Volume) {
946 CHAR16 *NameClues, *PathOnly, *NoExtension, *OSIconName = NULL, *Temp;
947 CHAR16 ShortcutLetter = 0;
948
949 NameClues = Basename(LoaderPath);
950 PathOnly = FindPath(LoaderPath);
951 NoExtension = StripEfiExtension(NameClues);
952
953 if (Volume->DiskKind == DISK_KIND_NET) {
954 MergeStrings(&NameClues, Entry->me.Title, L' ');
955 } else {
956 // locate a custom icon for the loader
957 // Anything found here takes precedence over the "hints" in the OSIconName variable
958 if (!Entry->me.Image) {
959 Entry->me.Image = egLoadIconAnyType(Volume->RootDir, PathOnly, NoExtension, GlobalConfig.IconSizes[ICON_SIZE_BIG]);
960 }
961 if (!Entry->me.Image) {
962 Entry->me.Image = egCopyImage(Volume->VolIconImage);
963 }
964
965 // Begin creating icon "hints" by using last part of directory path leading
966 // to the loader
967 Temp = FindLastDirName(LoaderPath);
968 MergeStrings(&OSIconName, Temp, L',');
969 MyFreePool(Temp);
970 Temp = NULL;
971 if (OSIconName != NULL) {
972 ShortcutLetter = OSIconName[0];
973 }
974
975 // Add every "word" in the volume label, delimited by spaces, dashes (-), or
976 // underscores (_), to the list of hints to be used in searching for OS
977 // icons.
978 MergeWords(&OSIconName, Volume->VolName, L',');
979 } // if/else network boot
980
981 // detect specific loaders
982 if (StriSubCmp(L"bzImage", NameClues) || StriSubCmp(L"vmlinuz", NameClues)) {
983 if (Volume->DiskKind != DISK_KIND_NET) {
984 GuessLinuxDistribution(&OSIconName, Volume, LoaderPath);
985 Entry->LoadOptions = GetMainLinuxOptions(LoaderPath, Volume);
986 }
987 MergeStrings(&OSIconName, L"linux", L',');
988 Entry->OSType = 'L';
989 if (ShortcutLetter == 0)
990 ShortcutLetter = 'L';
991 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_LINUX;
992 } else if (StriSubCmp(L"refit", LoaderPath)) {
993 MergeStrings(&OSIconName, L"refit", L',');
994 Entry->OSType = 'R';
995 ShortcutLetter = 'R';
996 } else if (StriSubCmp(L"refind", LoaderPath)) {
997 MergeStrings(&OSIconName, L"refind", L',');
998 Entry->OSType = 'R';
999 ShortcutLetter = 'R';
1000 } else if (MyStriCmp(LoaderPath, MACOSX_LOADER_PATH)) {
1001 MergeStrings(&OSIconName, L"mac", L',');
1002 Entry->OSType = 'M';
1003 ShortcutLetter = 'M';
1004 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_OSX;
1005 } else if (MyStriCmp(NameClues, L"diags.efi")) {
1006 MergeStrings(&OSIconName, L"hwtest", L',');
1007 } else if (MyStriCmp(NameClues, L"e.efi") || MyStriCmp(NameClues, L"elilo.efi") || StriSubCmp(L"elilo", NameClues)) {
1008 MergeStrings(&OSIconName, L"elilo,linux", L',');
1009 Entry->OSType = 'E';
1010 if (ShortcutLetter == 0)
1011 ShortcutLetter = 'L';
1012 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_ELILO;
1013 } else if (StriSubCmp(L"grub", NameClues)) {
1014 MergeStrings(&OSIconName, L"grub,linux", L',');
1015 Entry->OSType = 'G';
1016 ShortcutLetter = 'G';
1017 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_GRUB;
1018 } else if (MyStriCmp(NameClues, L"cdboot.efi") ||
1019 MyStriCmp(NameClues, L"bootmgr.efi") ||
1020 MyStriCmp(NameClues, L"bootmgfw.efi") ||
1021 MyStriCmp(NameClues, L"bkpbootmgfw.efi")) {
1022 MergeStrings(&OSIconName, L"win8", L',');
1023 Entry->OSType = 'W';
1024 ShortcutLetter = 'W';
1025 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_WINDOWS;
1026 } else if (MyStriCmp(NameClues, L"xom.efi")) {
1027 MergeStrings(&OSIconName, L"xom,win,win8", L',');
1028 Entry->OSType = 'X';
1029 ShortcutLetter = 'W';
1030 Entry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_WINDOWS;
1031 }
1032 else if (StriSubCmp(L"ipxe", NameClues)) {
1033 Entry->OSType = 'N';
1034 ShortcutLetter = 'N';
1035 MergeStrings(&OSIconName, L"network", L',');
1036 }
1037
1038 if ((ShortcutLetter >= 'a') && (ShortcutLetter <= 'z'))
1039 ShortcutLetter = ShortcutLetter - 'a' + 'A'; // convert lowercase to uppercase
1040 Entry->me.ShortcutLetter = ShortcutLetter;
1041 if (Entry->me.Image == NULL)
1042 Entry->me.Image = LoadOSIcon(OSIconName, L"unknown", FALSE);
1043 MyFreePool(PathOnly);
1044 } // VOID SetLoaderDefaults()
1045
1046 // Add a specified EFI boot loader to the list, using automatic settings
1047 // for icons, options, etc.
1048 static LOADER_ENTRY * AddLoaderEntry(IN CHAR16 *LoaderPath, IN CHAR16 *LoaderTitle, IN REFIT_VOLUME *Volume, IN BOOLEAN SubScreenReturn) {
1049 LOADER_ENTRY *Entry;
1050
1051 CleanUpPathNameSlashes(LoaderPath);
1052 Entry = InitializeLoaderEntry(NULL);
1053 if (Entry != NULL) {
1054 Entry->Title = StrDuplicate((LoaderTitle != NULL) ? LoaderTitle : LoaderPath);
1055 Entry->me.Title = AllocateZeroPool(sizeof(CHAR16) * 256);
1056 // Extra space at end of Entry->me.Title enables searching on Volume->VolName even if another volume
1057 // name is identical except for something added to the end (e.g., VolB1 vs. VolB12).
1058 // Note: Volume->VolName will be NULL for network boot programs.
1059 if (Volume->VolName)
1060 SPrint(Entry->me.Title, 255, L"Boot %s from %s ", (LoaderTitle != NULL) ? LoaderTitle : LoaderPath, Volume->VolName);
1061 else
1062 SPrint(Entry->me.Title, 255, L"Boot %s ", (LoaderTitle != NULL) ? LoaderTitle : LoaderPath);
1063 Entry->me.Row = 0;
1064 Entry->me.BadgeImage = Volume->VolBadgeImage;
1065 if ((LoaderPath != NULL) && (LoaderPath[0] != L'\\')) {
1066 Entry->LoaderPath = StrDuplicate(L"\\");
1067 } else {
1068 Entry->LoaderPath = NULL;
1069 }
1070 MergeStrings(&(Entry->LoaderPath), LoaderPath, 0);
1071 Entry->VolName = Volume->VolName;
1072 Entry->DevicePath = FileDevicePath(Volume->DeviceHandle, Entry->LoaderPath);
1073 SetLoaderDefaults(Entry, LoaderPath, Volume);
1074 GenerateSubScreen(Entry, Volume, SubScreenReturn);
1075 AddMenuEntry(&MainMenu, (REFIT_MENU_ENTRY *)Entry);
1076 }
1077
1078 return(Entry);
1079 } // LOADER_ENTRY * AddLoaderEntry()
1080
1081 // Add a Linux kernel as a submenu entry for another (pre-existing) Linux kernel entry.
1082 static VOID AddKernelToSubmenu(LOADER_ENTRY * TargetLoader, CHAR16 *FileName, REFIT_VOLUME *Volume) {
1083 REFIT_FILE *File;
1084 CHAR16 **TokenList = NULL, *InitrdName, *SubmenuName = NULL, *VolName = NULL, *Path = NULL, *Title;
1085 REFIT_MENU_SCREEN *SubScreen;
1086 LOADER_ENTRY *SubEntry;
1087 UINTN TokenCount;
1088
1089 File = ReadLinuxOptionsFile(TargetLoader->LoaderPath, Volume);
1090 if (File != NULL) {
1091 SubScreen = TargetLoader->me.SubScreen;
1092 InitrdName = FindInitrd(FileName, Volume);
1093 while ((TokenCount = ReadTokenLine(File, &TokenList)) > 1) {
1094 SubEntry = InitializeLoaderEntry(TargetLoader);
1095 SplitPathName(FileName, &VolName, &Path, &SubmenuName);
1096 MergeStrings(&SubmenuName, L": ", '\0');
1097 MergeStrings(&SubmenuName, TokenList[0] ? StrDuplicate(TokenList[0]) : StrDuplicate(L"Boot Linux"), '\0');
1098 Title = StrDuplicate(SubmenuName);
1099 LimitStringLength(Title, MAX_LINE_LENGTH);
1100 SubEntry->me.Title = Title;
1101 MyFreePool(SubEntry->LoadOptions);
1102 SubEntry->LoadOptions = AddInitrdToOptions(TokenList[1], InitrdName);
1103 MyFreePool(SubEntry->LoaderPath);
1104 SubEntry->LoaderPath = StrDuplicate(FileName);
1105 CleanUpPathNameSlashes(SubEntry->LoaderPath);
1106 SubEntry->DevicePath = FileDevicePath(Volume->DeviceHandle, SubEntry->LoaderPath);
1107 FreeTokenLine(&TokenList, &TokenCount);
1108 SubEntry->UseGraphicsMode = GlobalConfig.GraphicsFor & GRAPHICS_FOR_LINUX;
1109 AddMenuEntry(SubScreen, (REFIT_MENU_ENTRY *)SubEntry);
1110 } // while
1111 MyFreePool(VolName);
1112 MyFreePool(Path);
1113 MyFreePool(SubmenuName);
1114 MyFreePool(InitrdName);
1115 MyFreePool(File);
1116 } // if
1117 } // static VOID AddKernelToSubmenu()
1118
1119 // Returns -1 if (Time1 < Time2), +1 if (Time1 > Time2), or 0 if
1120 // (Time1 == Time2). Precision is only to the nearest second; since
1121 // this is used for sorting boot loader entries, differences smaller
1122 // than this are likely to be meaningless (and unlikely!).
1123 INTN TimeComp(IN EFI_TIME *Time1, IN EFI_TIME *Time2) {
1124 INT64 Time1InSeconds, Time2InSeconds;
1125
1126 // Following values are overestimates; I'm assuming 31 days in every month.
1127 // This is fine for the purpose of this function, which is limited
1128 Time1InSeconds = Time1->Second + (Time1->Minute * 60) + (Time1->Hour * 3600) + (Time1->Day * 86400) +
1129 (Time1->Month * 2678400) + ((Time1->Year - 1998) * 32140800);
1130 Time2InSeconds = Time2->Second + (Time2->Minute * 60) + (Time2->Hour * 3600) + (Time2->Day * 86400) +
1131 (Time2->Month * 2678400) + ((Time2->Year - 1998) * 32140800);
1132 if (Time1InSeconds < Time2InSeconds)
1133 return (-1);
1134 else if (Time1InSeconds > Time2InSeconds)
1135 return (1);
1136
1137 return 0;
1138 } // INTN TimeComp()
1139
1140 // Adds a loader list element, keeping it sorted by date. Returns the new
1141 // first element (the one with the most recent date).
1142 static struct LOADER_LIST * AddLoaderListEntry(struct LOADER_LIST *LoaderList, struct LOADER_LIST *NewEntry) {
1143 struct LOADER_LIST *LatestEntry, *CurrentEntry, *PrevEntry = NULL;
1144
1145 LatestEntry = CurrentEntry = LoaderList;
1146 if (LoaderList == NULL) {
1147 LatestEntry = NewEntry;
1148 } else {
1149 while ((CurrentEntry != NULL) && (TimeComp(&(NewEntry->TimeStamp), &(CurrentEntry->TimeStamp)) < 0)) {
1150 PrevEntry = CurrentEntry;
1151 CurrentEntry = CurrentEntry->NextEntry;
1152 } // while
1153 NewEntry->NextEntry = CurrentEntry;
1154 if (PrevEntry == NULL) {
1155 LatestEntry = NewEntry;
1156 } else {
1157 PrevEntry->NextEntry = NewEntry;
1158 } // if/else
1159 } // if/else
1160 return (LatestEntry);
1161 } // static VOID AddLoaderListEntry()
1162
1163 // Delete the LOADER_LIST linked list
1164 static VOID CleanUpLoaderList(struct LOADER_LIST *LoaderList) {
1165 struct LOADER_LIST *Temp;
1166
1167 while (LoaderList != NULL) {
1168 Temp = LoaderList;
1169 LoaderList = LoaderList->NextEntry;
1170 MyFreePool(Temp->FileName);
1171 MyFreePool(Temp);
1172 } // while
1173 } // static VOID CleanUpLoaderList()
1174
1175 // Returns FALSE if the specified file/volume matches the GlobalConfig.DontScanDirs
1176 // or GlobalConfig.DontScanVolumes specification, or if Path points to a volume
1177 // other than the one specified by Volume, or if the specified path is SelfDir.
1178 // Returns TRUE if none of these conditions is met -- that is, if the path is
1179 // eligible for scanning.
1180 static BOOLEAN ShouldScan(REFIT_VOLUME *Volume, CHAR16 *Path) {
1181 CHAR16 *VolName = NULL, *DontScanDir, *PathCopy = NULL;
1182 UINTN i = 0;
1183 BOOLEAN ScanIt = TRUE;
1184
1185 if ((IsIn(Volume->VolName, GlobalConfig.DontScanVolumes)) || (IsIn(Volume->PartName, GlobalConfig.DontScanVolumes)))
1186 return FALSE;
1187
1188 if (MyStriCmp(Path, SelfDirPath) && (Volume->DeviceHandle == SelfVolume->DeviceHandle))
1189 return FALSE;
1190
1191 // See if Path includes an explicit volume declaration that's NOT Volume....
1192 PathCopy = StrDuplicate(Path);
1193 if (SplitVolumeAndFilename(&PathCopy, &VolName)) {
1194 VolumeNumberToName(Volume, &VolName);
1195 if (VolName && !MyStriCmp(VolName, Volume->VolName)) {
1196 ScanIt = FALSE;
1197 } // if
1198 } // if Path includes volume specification
1199 MyFreePool(PathCopy);
1200 MyFreePool(VolName);
1201 VolName = NULL;
1202
1203 // See if Volume is in GlobalConfig.DontScanDirs....
1204 while (ScanIt && (DontScanDir = FindCommaDelimited(GlobalConfig.DontScanDirs, i++))) {
1205 SplitVolumeAndFilename(&DontScanDir, &VolName);
1206 CleanUpPathNameSlashes(DontScanDir);
1207 VolumeNumberToName(Volume, &VolName);
1208 if (VolName != NULL) {
1209 if (MyStriCmp(VolName, Volume->VolName) && MyStriCmp(DontScanDir, Path))
1210 ScanIt = FALSE;
1211 } else {
1212 if (MyStriCmp(DontScanDir, Path))
1213 ScanIt = FALSE;
1214 }
1215 MyFreePool(DontScanDir);
1216 MyFreePool(VolName);
1217 DontScanDir = NULL;
1218 VolName = NULL;
1219 } // while()
1220
1221 return ScanIt;
1222 } // BOOLEAN ShouldScan()
1223
1224 // Returns TRUE if the file is byte-for-byte identical with the fallback file
1225 // on the volume AND if the file is not itself the fallback file; returns
1226 // FALSE if the file is not identical to the fallback file OR if the file
1227 // IS the fallback file. Intended for use in excluding the fallback boot
1228 // loader when it's a duplicate of another boot loader.
1229 static BOOLEAN DuplicatesFallback(IN REFIT_VOLUME *Volume, IN CHAR16 *FileName) {
1230 CHAR8 *FileContents, *FallbackContents;
1231 EFI_FILE_HANDLE FileHandle, FallbackHandle;
1232 EFI_FILE_INFO *FileInfo, *FallbackInfo;
1233 UINTN FileSize = 0, FallbackSize = 0;
1234 EFI_STATUS Status;
1235 BOOLEAN AreIdentical = FALSE;
1236
1237 if (!FileExists(Volume->RootDir, FileName) || !FileExists(Volume->RootDir, FALLBACK_FULLNAME))
1238 return FALSE;
1239
1240 CleanUpPathNameSlashes(FileName);
1241
1242 if (MyStriCmp(FileName, FALLBACK_FULLNAME))
1243 return FALSE; // identical filenames, so not a duplicate....
1244
1245 Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0);
1246 if (Status == EFI_SUCCESS) {
1247 FileInfo = LibFileInfo(FileHandle);
1248 FileSize = FileInfo->FileSize;
1249 } else {
1250 return FALSE;
1251 }
1252
1253 Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FallbackHandle, FALLBACK_FULLNAME, EFI_FILE_MODE_READ, 0);
1254 if (Status == EFI_SUCCESS) {
1255 FallbackInfo = LibFileInfo(FallbackHandle);
1256 FallbackSize = FallbackInfo->FileSize;
1257 } else {
1258 refit_call1_wrapper(FileHandle->Close, FileHandle);
1259 return FALSE;
1260 }
1261
1262 if (FallbackSize != FileSize) { // not same size, so can't be identical
1263 AreIdentical = FALSE;
1264 } else { // could be identical; do full check....
1265 FileContents = AllocatePool(FileSize);
1266 FallbackContents = AllocatePool(FallbackSize);
1267 if (FileContents && FallbackContents) {
1268 Status = refit_call3_wrapper(FileHandle->Read, FileHandle, &FileSize, FileContents);
1269 if (Status == EFI_SUCCESS) {
1270 Status = refit_call3_wrapper(FallbackHandle->Read, FallbackHandle, &FallbackSize, FallbackContents);
1271 }
1272 if (Status == EFI_SUCCESS) {
1273 AreIdentical = (CompareMem(FileContents, FallbackContents, FileSize) == 0);
1274 } // if
1275 } // if
1276 MyFreePool(FileContents);
1277 MyFreePool(FallbackContents);
1278 } // if/else
1279
1280 // BUG ALERT: Some systems (e.g., DUET, some Macs with large displays) crash if the
1281 // following two calls are reversed. Go figure....
1282 refit_call1_wrapper(FileHandle->Close, FallbackHandle);
1283 refit_call1_wrapper(FileHandle->Close, FileHandle);
1284 return AreIdentical;
1285 } // BOOLEAN DuplicatesFallback()
1286
1287 // Returns FALSE if two measures of file size are identical for a single file,
1288 // TRUE if not or if the file can't be opened and the other measure is non-0.
1289 // Despite the function's name, this isn't really a direct test of symbolic
1290 // link status, since EFI doesn't officially support symlinks. It does seem
1291 // to be a reliable indicator, though. (OTOH, some disk errors might cause a
1292 // file to fail to open, which would return a false positive -- but as I use
1293 // this function to exclude symbolic links from the list of boot loaders,
1294 // that would be fine, since such boot loaders wouldn't work.)
1295 static BOOLEAN IsSymbolicLink(REFIT_VOLUME *Volume, CHAR16 *Path, EFI_FILE_INFO *DirEntry) {
1296 EFI_FILE_HANDLE FileHandle;
1297 EFI_FILE_INFO *FileInfo = NULL;
1298 EFI_STATUS Status;
1299 UINTN FileSize2 = 0;
1300 CHAR16 *FileName;
1301
1302 FileName = StrDuplicate(Path);
1303 MergeStrings(&FileName, DirEntry->FileName, L'\\');
1304 CleanUpPathNameSlashes(FileName);
1305
1306 Status = refit_call5_wrapper(Volume->RootDir->Open, Volume->RootDir, &FileHandle, FileName, EFI_FILE_MODE_READ, 0);
1307 if (Status == EFI_SUCCESS) {
1308 FileInfo = LibFileInfo(FileHandle);
1309 if (FileInfo != NULL)
1310 FileSize2 = FileInfo->FileSize;
1311 }
1312
1313 MyFreePool(FileName);
1314 MyFreePool(FileInfo);
1315
1316 return (DirEntry->FileSize != FileSize2);
1317 } // BOOLEAN IsSymbolicLink()
1318
1319 // Returns TRUE if a file with the same name as the original but with
1320 // ".efi.signed" is also present in the same directory. Ubuntu is using
1321 // this filename as a signed version of the original unsigned kernel, and
1322 // there's no point in cluttering the display with two kernels that will
1323 // behave identically on non-SB systems, or when one will fail when SB
1324 // is active.
1325 static BOOLEAN HasSignedCounterpart(IN REFIT_VOLUME *Volume, IN CHAR16 *Path, IN CHAR16 *Filename) {
1326 CHAR16 *NewFile = NULL;
1327 BOOLEAN retval = FALSE;
1328
1329 MergeStrings(&NewFile, Path, 0);
1330 MergeStrings(&NewFile, Filename, L'\\');
1331 MergeStrings(&NewFile, L".efi.signed", 0);
1332 if (NewFile != NULL) {
1333 CleanUpPathNameSlashes(NewFile);
1334 if (FileExists(Volume->RootDir, NewFile))
1335 retval = TRUE;
1336 MyFreePool(NewFile);
1337 } // if
1338
1339 return retval;
1340 } // BOOLEAN HasSignedCounterpart()
1341
1342 // Scan an individual directory for EFI boot loader files and, if found,
1343 // add them to the list. Exception: Ignores FALLBACK_FULLNAME, which is picked
1344 // up in ScanEfiFiles(). Sorts the entries within the loader directory so that
1345 // the most recent one appears first in the list.
1346 // Returns TRUE if a duplicate for FALLBACK_FILENAME was found, FALSE if not.
1347 static BOOLEAN ScanLoaderDir(IN REFIT_VOLUME *Volume, IN CHAR16 *Path, IN CHAR16 *Pattern)
1348 {
1349 EFI_STATUS Status;
1350 REFIT_DIR_ITER DirIter;
1351 EFI_FILE_INFO *DirEntry;
1352 CHAR16 FileName[256], *Extension;
1353 struct LOADER_LIST *LoaderList = NULL, *NewLoader;
1354 LOADER_ENTRY *FirstKernel = NULL, *LatestEntry = NULL;
1355 BOOLEAN FoundFallbackDuplicate = FALSE, IsLinux = FALSE, InSelfPath;
1356
1357 InSelfPath = MyStriCmp(Path, SelfDirPath);
1358 if ((!SelfDirPath || !Path || (InSelfPath && (Volume->DeviceHandle != SelfVolume->DeviceHandle)) ||
1359 (!InSelfPath)) && (ShouldScan(Volume, Path))) {
1360 // look through contents of the directory
1361 DirIterOpen(Volume->RootDir, Path, &DirIter);
1362 while (DirIterNext(&DirIter, 2, Pattern, &DirEntry)) {
1363 Extension = FindExtension(DirEntry->FileName);
1364 if (DirEntry->FileName[0] == '.' ||
1365 MyStriCmp(Extension, L".icns") ||
1366 MyStriCmp(Extension, L".png") ||
1367 (MyStriCmp(DirEntry->FileName, FALLBACK_BASENAME) && (MyStriCmp(Path, L"EFI\\BOOT"))) ||
1368 StriSubCmp(L"shell", DirEntry->FileName) ||
1369 IsSymbolicLink(Volume, Path, DirEntry) || /* is symbolic link */
1370 HasSignedCounterpart(Volume, Path, DirEntry->FileName) || /* a file with same name plus ".efi.signed" is present */
1371 FilenameIn(Volume, Path, DirEntry->FileName, GlobalConfig.DontScanFiles))
1372 continue; // skip this
1373
1374 if (Path)
1375 SPrint(FileName, 255, L"\\%s\\%s", Path, DirEntry->FileName);
1376 else
1377 SPrint(FileName, 255, L"\\%s", DirEntry->FileName);
1378 CleanUpPathNameSlashes(FileName);
1379
1380 if(!IsValidLoader(Volume->RootDir, FileName))
1381 continue;
1382
1383 NewLoader = AllocateZeroPool(sizeof(struct LOADER_LIST));
1384 if (NewLoader != NULL) {
1385 NewLoader->FileName = StrDuplicate(FileName);
1386 NewLoader->TimeStamp = DirEntry->ModificationTime;
1387 LoaderList = AddLoaderListEntry(LoaderList, NewLoader);
1388 if (DuplicatesFallback(Volume, FileName))
1389 FoundFallbackDuplicate = TRUE;
1390 } // if
1391 MyFreePool(Extension);
1392 } // while
1393
1394 NewLoader = LoaderList;
1395 while (NewLoader != NULL) {
1396 IsLinux = (StriSubCmp(L"bzImage", NewLoader->FileName) || StriSubCmp(L"vmlinuz", NewLoader->FileName));
1397 if ((FirstKernel != NULL) && IsLinux && GlobalConfig.FoldLinuxKernels) {
1398 AddKernelToSubmenu(FirstKernel, NewLoader->FileName, Volume);
1399 } else {
1400 LatestEntry = AddLoaderEntry(NewLoader->FileName, NULL, Volume, !(IsLinux && GlobalConfig.FoldLinuxKernels));
1401 if (IsLinux && (FirstKernel == NULL))
1402 FirstKernel = LatestEntry;
1403 }
1404 NewLoader = NewLoader->NextEntry;
1405 } // while
1406 if ((NewLoader != NULL) && (FirstKernel != NULL) && IsLinux && GlobalConfig.FoldLinuxKernels)
1407 AddMenuEntry(FirstKernel->me.SubScreen, &MenuEntryReturn);
1408
1409 CleanUpLoaderList(LoaderList);
1410 Status = DirIterClose(&DirIter);
1411 // NOTE: EFI_INVALID_PARAMETER really is an error that should be reported;
1412 // but I've gotten reports from users who are getting this error occasionally
1413 // and I can't find anything wrong or reproduce the problem, so I'm putting
1414 // it down to buggy EFI implementations and ignoring that particular error....
1415 if ((Status != EFI_NOT_FOUND) && (Status != EFI_INVALID_PARAMETER)) {
1416 if (Path)
1417 SPrint(FileName, 255, L"while scanning the %s directory", Path);
1418 else
1419 StrCpy(FileName, L"while scanning the root directory");
1420 CheckError(Status, FileName);
1421 } // if (Status != EFI_NOT_FOUND)
1422 } // if not scanning a blacklisted directory
1423
1424 return FoundFallbackDuplicate;
1425 } /* static VOID ScanLoaderDir() */
1426
1427 // Run the IPXE_DISCOVER_NAME program, which obtains the IP address of the boot
1428 // server and the name of the boot file it delivers.
1429 CHAR16* RuniPXEDiscover(EFI_HANDLE Volume)
1430 {
1431 EFI_STATUS Status;
1432 EFI_DEVICE_PATH *FilePath;
1433 EFI_HANDLE iPXEHandle;
1434 CHAR16 *boot_info = NULL;
1435 UINTN boot_info_size = 0;
1436
1437 FilePath = FileDevicePath (Volume, IPXE_DISCOVER_NAME);
1438 Status = refit_call6_wrapper(BS->LoadImage, FALSE, SelfImageHandle, FilePath, NULL, 0, &iPXEHandle);
1439 if (Status != 0)
1440 return NULL;
1441
1442 Status = refit_call3_wrapper(BS->StartImage, iPXEHandle, &boot_info_size, &boot_info);
1443
1444 return boot_info;
1445 } // RuniPXEDiscover()
1446
1447 // Scan for network (PXE) boot servers. This function relies on the presence
1448 // of the IPXE_DISCOVER_NAME and IPXE_NAME program files on the volume from
1449 // which rEFInd launched. As of December 6, 2014, these tools aren't entirely
1450 // reliable. See BUILDING.txt for information on building them.
1451 static VOID ScanNetboot() {
1452 CHAR16 *iPXEFileName = IPXE_NAME;
1453 CHAR16 *Location;
1454 REFIT_VOLUME *NetVolume;
1455
1456 if (FileExists(SelfVolume->RootDir, IPXE_DISCOVER_NAME) &&
1457 FileExists(SelfVolume->RootDir, IPXE_NAME) &&
1458 IsValidLoader(SelfVolume->RootDir, IPXE_DISCOVER_NAME) &&
1459 IsValidLoader(SelfVolume->RootDir, IPXE_NAME)) {
1460 Location = RuniPXEDiscover(SelfVolume->DeviceHandle);
1461 if (Location != NULL && FileExists(SelfVolume->RootDir, iPXEFileName)) {
1462 NetVolume = AllocatePool(sizeof(REFIT_VOLUME));
1463 CopyMem(NetVolume, SelfVolume, sizeof(REFIT_VOLUME));
1464 NetVolume->DiskKind = DISK_KIND_NET;
1465 NetVolume->VolBadgeImage = BuiltinIcon(BUILTIN_ICON_VOL_NET);
1466 NetVolume->PartName = NetVolume->VolName = NULL;
1467 AddLoaderEntry(iPXEFileName, Location, NetVolume, TRUE);
1468 MyFreePool(NetVolume);
1469 } // if support files exist and are valid
1470 }
1471 } // VOID ScanNetBoot()
1472
1473 static VOID ScanEfiFiles(REFIT_VOLUME *Volume) {
1474 EFI_STATUS Status;
1475 REFIT_DIR_ITER EfiDirIter;
1476 EFI_FILE_INFO *EfiDirEntry;
1477 CHAR16 FileName[256], *Directory = NULL, *MatchPatterns, *VolName = NULL, *SelfPath;
1478 UINTN i, Length;
1479 BOOLEAN ScanFallbackLoader = TRUE;
1480 BOOLEAN FoundBRBackup = FALSE;
1481
1482 if (Volume && (Volume->RootDir != NULL) && (Volume->VolName != NULL) && (Volume->IsReadable)) {
1483 MatchPatterns = StrDuplicate(LOADER_MATCH_PATTERNS);
1484 if (GlobalConfig.ScanAllLinux)
1485 MergeStrings(&MatchPatterns, LINUX_MATCH_PATTERNS, L',');
1486
1487 // check for Mac OS X boot loader
1488 if (ShouldScan(Volume, MACOSX_LOADER_DIR)) {
1489 StrCpy(FileName, MACOSX_LOADER_PATH);
1490 if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, MACOSX_LOADER_DIR, L"boot.efi", GlobalConfig.DontScanFiles)) {
1491 AddLoaderEntry(FileName, L"Mac OS X", Volume, TRUE);
1492 if (DuplicatesFallback(Volume, FileName))
1493 ScanFallbackLoader = FALSE;
1494 }
1495
1496 // check for XOM
1497 StrCpy(FileName, L"System\\Library\\CoreServices\\xom.efi");
1498 if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, MACOSX_LOADER_DIR, L"xom.efi", GlobalConfig.DontScanFiles)) {
1499 AddLoaderEntry(FileName, L"Windows XP (XoM)", Volume, TRUE);
1500 if (DuplicatesFallback(Volume, FileName))
1501 ScanFallbackLoader = FALSE;
1502 }
1503 } // if should scan Mac directory
1504
1505 // check for Microsoft boot loader/menu
1506 if (ShouldScan(Volume, L"EFI\\Microsoft\\Boot")) {
1507 StrCpy(FileName, L"EFI\\Microsoft\\Boot\\bkpbootmgfw.efi");
1508 if (FileExists(Volume->RootDir, FileName) && !FilenameIn(Volume, L"EFI\\Microsoft\\Boot", L"bkpbootmgfw.efi",
1509 GlobalConfig.DontScanFiles)) {
1510 AddLoaderEntry(FileName, L"Microsoft EFI boot (Boot Repair backup)", Volume, TRUE);
1511 FoundBRBackup = TRUE;
1512 if (DuplicatesFallback(Volume, FileName))
1513 ScanFallbackLoader = FALSE;
1514 }
1515 StrCpy(FileName, L"EFI\\Microsoft\\Boot\\bootmgfw.efi");
1516 if (FileExists(Volume->RootDir, FileName) &&
1517 !FilenameIn(Volume, L"EFI\\Microsoft\\Boot", L"bootmgfw.efi", GlobalConfig.DontScanFiles)) {
1518 if (FoundBRBackup)
1519 AddLoaderEntry(FileName, L"Supposed Microsoft EFI boot (probably GRUB)", Volume, TRUE);
1520 else
1521 AddLoaderEntry(FileName, L"Microsoft EFI boot", Volume, TRUE);
1522 if (DuplicatesFallback(Volume, FileName))
1523 ScanFallbackLoader = FALSE;
1524 }
1525 } // if
1526
1527 // scan the root directory for EFI executables
1528 if (ScanLoaderDir(Volume, L"\\", MatchPatterns))
1529 ScanFallbackLoader = FALSE;
1530
1531 // scan subdirectories of the EFI directory (as per the standard)
1532 DirIterOpen(Volume->RootDir, L"EFI", &EfiDirIter);
1533 while (DirIterNext(&EfiDirIter, 1, NULL, &EfiDirEntry)) {
1534 if (MyStriCmp(EfiDirEntry->FileName, L"tools") || EfiDirEntry->FileName[0] == '.')
1535 continue; // skip this, doesn't contain boot loaders or is scanned later
1536 SPrint(FileName, 255, L"EFI\\%s", EfiDirEntry->FileName);
1537 if (ScanLoaderDir(Volume, FileName, MatchPatterns))
1538 ScanFallbackLoader = FALSE;
1539 } // while()
1540 Status = DirIterClose(&EfiDirIter);
1541 if ((Status != EFI_NOT_FOUND) && (Status != EFI_INVALID_PARAMETER))
1542 CheckError(Status, L"while scanning the EFI directory");
1543
1544 // Scan user-specified (or additional default) directories....
1545 i = 0;
1546 while ((Directory = FindCommaDelimited(GlobalConfig.AlsoScan, i++)) != NULL) {
1547 if (ShouldScan(Volume, Directory)) {
1548 SplitVolumeAndFilename(&Directory, &VolName);
1549 CleanUpPathNameSlashes(Directory);
1550 Length = StrLen(Directory);
1551 if ((Length > 0) && ScanLoaderDir(Volume, Directory, MatchPatterns))
1552 ScanFallbackLoader = FALSE;
1553 MyFreePool(VolName);
1554 } // if should scan dir
1555 MyFreePool(Directory);
1556 } // while
1557
1558 // Don't scan the fallback loader if it's on the same volume and a duplicate of rEFInd itself....
1559 SelfPath = DevicePathToStr(SelfLoadedImage->FilePath);
1560 CleanUpPathNameSlashes(SelfPath);
1561 if ((Volume->DeviceHandle == SelfLoadedImage->DeviceHandle) && DuplicatesFallback(Volume, SelfPath))
1562 ScanFallbackLoader = FALSE;
1563
1564 // If not a duplicate & if it exists & if it's not us, create an entry
1565 // for the fallback boot loader
1566 if (ScanFallbackLoader && FileExists(Volume->RootDir, FALLBACK_FULLNAME) && ShouldScan(Volume, L"EFI\\BOOT")) {
1567 AddLoaderEntry(FALLBACK_FULLNAME, L"Fallback boot loader", Volume, TRUE);
1568 }
1569 } // if
1570 } // static VOID ScanEfiFiles()
1571
1572 // Scan internal disks for valid EFI boot loaders....
1573 static VOID ScanInternal(VOID) {
1574 UINTN VolumeIndex;
1575
1576 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
1577 if (Volumes[VolumeIndex]->DiskKind == DISK_KIND_INTERNAL) {
1578 ScanEfiFiles(Volumes[VolumeIndex]);
1579 }
1580 } // for
1581 } // static VOID ScanInternal()
1582
1583 // Scan external disks for valid EFI boot loaders....
1584 static VOID ScanExternal(VOID) {
1585 UINTN VolumeIndex;
1586
1587 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
1588 if (Volumes[VolumeIndex]->DiskKind == DISK_KIND_EXTERNAL) {
1589 ScanEfiFiles(Volumes[VolumeIndex]);
1590 }
1591 } // for
1592 } // static VOID ScanExternal()
1593
1594 // Scan internal disks for valid EFI boot loaders....
1595 static VOID ScanOptical(VOID) {
1596 UINTN VolumeIndex;
1597
1598 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
1599 if (Volumes[VolumeIndex]->DiskKind == DISK_KIND_OPTICAL) {
1600 ScanEfiFiles(Volumes[VolumeIndex]);
1601 }
1602 } // for
1603 } // static VOID ScanOptical()
1604
1605 // default volume badge icon based on disk kind
1606 EG_IMAGE * GetDiskBadge(IN UINTN DiskType) {
1607 EG_IMAGE * Badge = NULL;
1608
1609 switch (DiskType) {
1610 case BBS_HARDDISK:
1611 Badge = BuiltinIcon(BUILTIN_ICON_VOL_INTERNAL);
1612 break;
1613 case BBS_USB:
1614 Badge = BuiltinIcon(BUILTIN_ICON_VOL_EXTERNAL);
1615 break;
1616 case BBS_CDROM:
1617 Badge = BuiltinIcon(BUILTIN_ICON_VOL_OPTICAL);
1618 break;
1619 } // switch()
1620 return Badge;
1621 } // EG_IMAGE * GetDiskBadge()
1622
1623 //
1624 // pre-boot tool functions
1625 //
1626
1627 static VOID StartTool(IN LOADER_ENTRY *Entry)
1628 {
1629 BeginExternalScreen(Entry->UseGraphicsMode, Entry->me.Title + 6); // assumes "Start <title>" as assigned below
1630 StoreLoaderName(Entry->me.Title);
1631 StartEFIImage(Entry->DevicePath, Entry->LoadOptions, TYPE_EFI,
1632 Basename(Entry->LoaderPath), Entry->OSType, NULL, TRUE, FALSE);
1633 FinishExternalScreen();
1634 } /* static VOID StartTool() */
1635
1636 static LOADER_ENTRY * AddToolEntry(EFI_HANDLE DeviceHandle, IN CHAR16 *LoaderPath, IN CHAR16 *LoaderTitle, IN EG_IMAGE *Image,
1637 IN CHAR16 ShortcutLetter, IN BOOLEAN UseGraphicsMode)
1638 {
1639 LOADER_ENTRY *Entry;
1640 CHAR16 *TitleStr = NULL;
1641
1642 Entry = AllocateZeroPool(sizeof(LOADER_ENTRY));
1643
1644 TitleStr = PoolPrint(L"Start %s", LoaderTitle);
1645 Entry->me.Title = TitleStr;
1646 Entry->me.Tag = TAG_TOOL;
1647 Entry->me.Row = 1;
1648 Entry->me.ShortcutLetter = ShortcutLetter;
1649 Entry->me.Image = Image;
1650 Entry->LoaderPath = (LoaderPath) ? StrDuplicate(LoaderPath) : NULL;
1651 Entry->DevicePath = FileDevicePath(DeviceHandle, Entry->LoaderPath);
1652 Entry->UseGraphicsMode = UseGraphicsMode;
1653
1654 AddMenuEntry(&MainMenu, (REFIT_MENU_ENTRY *)Entry);
1655 return Entry;
1656 } /* static LOADER_ENTRY * AddToolEntry() */
1657
1658 //
1659 // pre-boot driver functions
1660 //
1661
1662 static UINTN ScanDriverDir(IN CHAR16 *Path)
1663 {
1664 EFI_STATUS Status;
1665 REFIT_DIR_ITER DirIter;
1666 UINTN NumFound = 0;
1667 EFI_FILE_INFO *DirEntry;
1668 CHAR16 FileName[256];
1669
1670 CleanUpPathNameSlashes(Path);
1671 // look through contents of the directory
1672 DirIterOpen(SelfRootDir, Path, &DirIter);
1673 while (DirIterNext(&DirIter, 2, LOADER_MATCH_PATTERNS, &DirEntry)) {
1674 if (DirEntry->FileName[0] == '.')
1675 continue; // skip this
1676
1677 SPrint(FileName, 255, L"%s\\%s", Path, DirEntry->FileName);
1678 NumFound++;
1679 Status = StartEFIImage(FileDevicePath(SelfLoadedImage->DeviceHandle, FileName),
1680 L"", TYPE_EFI, DirEntry->FileName, 0, NULL, FALSE, TRUE);
1681 }
1682 Status = DirIterClose(&DirIter);
1683 if ((Status != EFI_NOT_FOUND) && (Status != EFI_INVALID_PARAMETER)) {
1684 SPrint(FileName, 255, L"while scanning the %s directory", Path);
1685 CheckError(Status, FileName);
1686 }
1687 return (NumFound);
1688 }
1689
1690 #ifdef __MAKEWITH_GNUEFI
1691 static EFI_STATUS ConnectAllDriversToAllControllers(VOID)
1692 {
1693 EFI_STATUS Status;
1694 UINTN AllHandleCount;
1695 EFI_HANDLE *AllHandleBuffer;
1696 UINTN Index;
1697 UINTN HandleCount;
1698 EFI_HANDLE *HandleBuffer;
1699 UINT32 *HandleType;
1700 UINTN HandleIndex;
1701 BOOLEAN Parent;
1702 BOOLEAN Device;
1703
1704 Status = LibLocateHandle(AllHandles,
1705 NULL,
1706 NULL,
1707 &AllHandleCount,
1708 &AllHandleBuffer);
1709 if (EFI_ERROR(Status))
1710 return Status;
1711
1712 for (Index = 0; Index < AllHandleCount; Index++) {
1713 //
1714 // Scan the handle database
1715 //
1716 Status = LibScanHandleDatabase(NULL,
1717 NULL,
1718 AllHandleBuffer[Index],
1719 NULL,
1720 &HandleCount,
1721 &HandleBuffer,
1722 &HandleType);
1723 if (EFI_ERROR (Status))
1724 goto Done;
1725
1726 Device = TRUE;
1727 if (HandleType[Index] & EFI_HANDLE_TYPE_DRIVER_BINDING_HANDLE)
1728 Device = FALSE;
1729 if (HandleType[Index] & EFI_HANDLE_TYPE_IMAGE_HANDLE)
1730 Device = FALSE;
1731
1732 if (Device) {
1733 Parent = FALSE;
1734 for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
1735 if (HandleType[HandleIndex] & EFI_HANDLE_TYPE_PARENT_HANDLE)
1736 Parent = TRUE;
1737 } // for
1738
1739 if (!Parent) {
1740 if (HandleType[Index] & EFI_HANDLE_TYPE_DEVICE_HANDLE) {
1741 Status = refit_call4_wrapper(BS->ConnectController,
1742 AllHandleBuffer[Index],
1743 NULL,
1744 NULL,
1745 TRUE);
1746 }
1747 }
1748 }
1749
1750 MyFreePool (HandleBuffer);
1751 MyFreePool (HandleType);
1752 }
1753
1754 Done:
1755 MyFreePool (AllHandleBuffer);
1756 return Status;
1757 } /* EFI_STATUS ConnectAllDriversToAllControllers() */
1758 #else
1759 static EFI_STATUS ConnectAllDriversToAllControllers(VOID) {
1760 BdsLibConnectAllDriversToAllControllers();
1761 return 0;
1762 }
1763 #endif
1764
1765 // Load all EFI drivers from rEFInd's "drivers" subdirectory and from the
1766 // directories specified by the user in the "scan_driver_dirs" configuration
1767 // file line.
1768 static VOID LoadDrivers(VOID)
1769 {
1770 CHAR16 *Directory, *SelfDirectory;
1771 UINTN i = 0, Length, NumFound = 0;
1772
1773 // load drivers from the subdirectories of rEFInd's home directory specified
1774 // in the DRIVER_DIRS constant.
1775 while ((Directory = FindCommaDelimited(DRIVER_DIRS, i++)) != NULL) {
1776 SelfDirectory = SelfDirPath ? StrDuplicate(SelfDirPath) : NULL;
1777 CleanUpPathNameSlashes(SelfDirectory);
1778 MergeStrings(&SelfDirectory, Directory, L'\\');
1779 NumFound += ScanDriverDir(SelfDirectory);
1780 MyFreePool(Directory);
1781 MyFreePool(SelfDirectory);
1782 }
1783
1784 // Scan additional user-specified driver directories....
1785 i = 0;
1786 while ((Directory = FindCommaDelimited(GlobalConfig.DriverDirs, i++)) != NULL) {
1787 CleanUpPathNameSlashes(Directory);
1788 Length = StrLen(Directory);
1789 if (Length > 0) {
1790 NumFound += ScanDriverDir(Directory);
1791 } // if
1792 MyFreePool(Directory);
1793 } // while
1794
1795 // connect all devices
1796 if (NumFound > 0) {
1797 ConnectAllDriversToAllControllers();
1798 }
1799 } /* static VOID LoadDrivers() */
1800
1801 // Locates boot loaders. NOTE: This assumes that GlobalConfig.LegacyType is set correctly.
1802 static VOID ScanForBootloaders(VOID) {
1803 UINTN i;
1804 CHAR8 s;
1805 BOOLEAN ScanForLegacy = FALSE;
1806
1807 // Determine up-front if we'll be scanning for legacy loaders....
1808 for (i = 0; i < NUM_SCAN_OPTIONS; i++) {
1809 s = GlobalConfig.ScanFor[i];
1810 if ((s == 'c') || (s == 'C') || (s == 'h') || (s == 'H') || (s == 'b') || (s == 'B'))
1811 ScanForLegacy = TRUE;
1812 } // for
1813
1814 // If UEFI & scanning for legacy loaders & deep legacy scan, update NVRAM boot manager list
1815 if ((GlobalConfig.LegacyType == LEGACY_TYPE_UEFI) && ScanForLegacy && GlobalConfig.DeepLegacyScan) {
1816 BdsDeleteAllInvalidLegacyBootOptions();
1817 BdsAddNonExistingLegacyBootOptions();
1818 } // if
1819
1820 // scan for loaders and tools, add them to the menu
1821 for (i = 0; i < NUM_SCAN_OPTIONS; i++) {
1822 switch(GlobalConfig.ScanFor[i]) {
1823 case 'c': case 'C':
1824 ScanLegacyDisc();
1825 break;
1826 case 'h': case 'H':
1827 ScanLegacyInternal();
1828 break;
1829 case 'b': case 'B':
1830 ScanLegacyExternal();
1831 break;
1832 case 'm': case 'M':
1833 ScanUserConfigured(GlobalConfig.ConfigFilename);
1834 break;
1835 case 'e': case 'E':
1836 ScanExternal();
1837 break;
1838 case 'i': case 'I':
1839 ScanInternal();
1840 break;
1841 case 'o': case 'O':
1842 ScanOptical();
1843 break;
1844 case 'n': case 'N':
1845 ScanNetboot();
1846 break;
1847 } // switch()
1848 } // for
1849
1850 // assign shortcut keys
1851 for (i = 0; i < MainMenu.EntryCount && MainMenu.Entries[i]->Row == 0 && i < 9; i++)
1852 MainMenu.Entries[i]->ShortcutDigit = (CHAR16)('1' + i);
1853
1854 // wait for user ACK when there were errors
1855 FinishTextScreen(FALSE);
1856 } // static VOID ScanForBootloaders()
1857
1858 // Locate a single tool from the specified Locations using one of the
1859 // specified Names and add it to the menu.
1860 static VOID FindTool(CHAR16 *Locations, CHAR16 *Names, CHAR16 *Description, UINTN Icon) {
1861 UINTN j = 0, k, VolumeIndex;
1862 CHAR16 *DirName, *FileName, *PathName, FullDescription[256];
1863
1864 while ((DirName = FindCommaDelimited(Locations, j++)) != NULL) {
1865 k = 0;
1866 while ((FileName = FindCommaDelimited(Names, k++)) != NULL) {
1867 PathName = StrDuplicate(DirName);
1868 MergeStrings(&PathName, FileName, MyStriCmp(PathName, L"\\") ? 0 : L'\\');
1869 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
1870 if ((Volumes[VolumeIndex]->RootDir != NULL) &&
1871 (FileExists(Volumes[VolumeIndex]->RootDir, PathName)) &&
1872 IsValidLoader(Volumes[VolumeIndex]->RootDir, PathName)) {
1873 SPrint(FullDescription, 255, L"%s at %s on %s", Description, PathName, Volumes[VolumeIndex]->VolName);
1874 AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, PathName, FullDescription, BuiltinIcon(Icon), 'S', FALSE);
1875 } // if
1876 } // for
1877 MyFreePool(PathName);
1878 MyFreePool(FileName);
1879 } // while Names
1880 MyFreePool(DirName);
1881 } // while Locations
1882 } // VOID FindTool()
1883
1884 // Add the second-row tags containing built-in and external tools (EFI shell,
1885 // reboot, etc.)
1886 static VOID ScanForTools(VOID) {
1887 CHAR16 *FileName = NULL, *VolName = NULL, *MokLocations, Description[256];
1888 REFIT_MENU_ENTRY *TempMenuEntry;
1889 UINTN i, j, VolumeIndex;
1890 UINT64 osind;
1891 CHAR8 *b = 0;
1892
1893 MokLocations = StrDuplicate(MOK_LOCATIONS);
1894 if (MokLocations != NULL)
1895 MergeStrings(&MokLocations, SelfDirPath, L',');
1896
1897 for (i = 0; i < NUM_TOOLS; i++) {
1898 switch(GlobalConfig.ShowTools[i]) {
1899 // NOTE: Be sure that FileName is NULL at the end of each case.
1900 case TAG_SHUTDOWN:
1901 TempMenuEntry = CopyMenuEntry(&MenuEntryShutdown);
1902 TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_SHUTDOWN);
1903 AddMenuEntry(&MainMenu, TempMenuEntry);
1904 break;
1905
1906 case TAG_REBOOT:
1907 TempMenuEntry = CopyMenuEntry(&MenuEntryReset);
1908 TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_RESET);
1909 AddMenuEntry(&MainMenu, TempMenuEntry);
1910 break;
1911
1912 case TAG_ABOUT:
1913 TempMenuEntry = CopyMenuEntry(&MenuEntryAbout);
1914 TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_ABOUT);
1915 AddMenuEntry(&MainMenu, TempMenuEntry);
1916 break;
1917
1918 case TAG_EXIT:
1919 TempMenuEntry = CopyMenuEntry(&MenuEntryExit);
1920 TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_EXIT);
1921 AddMenuEntry(&MainMenu, TempMenuEntry);
1922 break;
1923
1924 case TAG_FIRMWARE:
1925 if (EfivarGetRaw(&GlobalGuid, L"OsIndicationsSupported", &b, &j) == EFI_SUCCESS) {
1926 osind = (UINT64)*b;
1927 if (osind & EFI_OS_INDICATIONS_BOOT_TO_FW_UI) {
1928 TempMenuEntry = CopyMenuEntry(&MenuEntryFirmware);
1929 TempMenuEntry->Image = BuiltinIcon(BUILTIN_ICON_FUNC_FIRMWARE);
1930 AddMenuEntry(&MainMenu, TempMenuEntry);
1931 } // if
1932 } // if
1933 break;
1934
1935 case TAG_SHELL:
1936 j = 0;
1937 while ((FileName = FindCommaDelimited(SHELL_NAMES, j++)) != NULL) {
1938 if (FileExists(SelfRootDir, FileName) && IsValidLoader(SelfRootDir, FileName)) {
1939 AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"EFI Shell", BuiltinIcon(BUILTIN_ICON_TOOL_SHELL),
1940 'S', FALSE);
1941 }
1942 MyFreePool(FileName);
1943 } // while
1944 break;
1945
1946 case TAG_GPTSYNC:
1947 j = 0;
1948 while ((FileName = FindCommaDelimited(GPTSYNC_NAMES, j++)) != NULL) {
1949 if (FileExists(SelfRootDir, FileName) && IsValidLoader(SelfRootDir, FileName)) {
1950 AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"Hybrid MBR tool", BuiltinIcon(BUILTIN_ICON_TOOL_PART),
1951 'P', FALSE);
1952 } // if
1953 MyFreePool(FileName);
1954 } // while
1955 FileName = NULL;
1956 break;
1957
1958 case TAG_GDISK:
1959 j = 0;
1960 while ((FileName = FindCommaDelimited(GDISK_NAMES, j++)) != NULL) {
1961 if (FileExists(SelfRootDir, FileName) && IsValidLoader(SelfRootDir, FileName)) {
1962 AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"disk partitioning tool",
1963 BuiltinIcon(BUILTIN_ICON_TOOL_PART), 'G', FALSE);
1964 } // if
1965 MyFreePool(FileName);
1966 } // while
1967 FileName = NULL;
1968 break;
1969
1970 case TAG_NETBOOT:
1971 j = 0;
1972 while ((FileName = FindCommaDelimited(NETBOOT_NAMES, j++)) != NULL) {
1973 if (FileExists(SelfRootDir, FileName) && IsValidLoader(SelfRootDir, FileName)) {
1974 AddToolEntry(SelfLoadedImage->DeviceHandle, FileName, L"Netboot",
1975 BuiltinIcon(BUILTIN_ICON_TOOL_NETBOOT), 'N', FALSE);
1976 } // if
1977 MyFreePool(FileName);
1978 } // while
1979 FileName = NULL;
1980 break;
1981
1982 case TAG_APPLE_RECOVERY:
1983 FileName = StrDuplicate(L"\\com.apple.recovery.boot\\boot.efi");
1984 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
1985 if ((Volumes[VolumeIndex]->RootDir != NULL) &&
1986 (FileExists(Volumes[VolumeIndex]->RootDir, FileName)) &&
1987 IsValidLoader(Volumes[VolumeIndex]->RootDir, FileName)) {
1988 SPrint(Description, 255, L"Apple Recovery on %s", Volumes[VolumeIndex]->VolName);
1989 AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, FileName, Description,
1990 BuiltinIcon(BUILTIN_ICON_TOOL_APPLE_RESCUE), 'R', TRUE);
1991 } // if
1992 } // for
1993 MyFreePool(FileName);
1994 FileName = NULL;
1995 break;
1996
1997 case TAG_WINDOWS_RECOVERY:
1998 j = 0;
1999 while ((FileName = FindCommaDelimited(GlobalConfig.WindowsRecoveryFiles, j++)) != NULL) {
2000 SplitVolumeAndFilename(&FileName, &VolName);
2001 for (VolumeIndex = 0; VolumeIndex < VolumesCount; VolumeIndex++) {
2002 if ((Volumes[VolumeIndex]->RootDir != NULL) &&
2003 (FileExists(Volumes[VolumeIndex]->RootDir, FileName)) &&
2004 IsValidLoader(Volumes[VolumeIndex]->RootDir, FileName) &&
2005 ((VolName == NULL) || MyStriCmp(VolName, Volumes[VolumeIndex]->VolName))) {
2006 SPrint(Description, 255, L"Microsoft Recovery on %s", Volumes[VolumeIndex]->VolName);
2007 AddToolEntry(Volumes[VolumeIndex]->DeviceHandle, FileName, Description,
2008 BuiltinIcon(BUILTIN_ICON_TOOL_WINDOWS_RESCUE), 'R', TRUE);
2009 } // if
2010 } // for
2011 } // while
2012 MyFreePool(FileName);
2013 FileName = NULL;
2014 MyFreePool(VolName);
2015 VolName = NULL;
2016 break;
2017
2018 case TAG_MOK_TOOL:
2019 FindTool(MokLocations, MOK_NAMES, L"MOK utility", BUILTIN_ICON_TOOL_MOK_TOOL);
2020 break;
2021
2022 case TAG_MEMTEST:
2023 FindTool(MEMTEST_LOCATIONS, MEMTEST_NAMES, L"Memory test utility", BUILTIN_ICON_TOOL_MEMTEST);
2024 break;
2025
2026 } // switch()
2027 } // for
2028 } // static VOID ScanForTools
2029
2030 // Rescan for boot loaders
2031 static VOID RescanAll(BOOLEAN DisplayMessage) {
2032 EG_PIXEL BGColor;
2033
2034 BGColor.b = 255;
2035 BGColor.g = 175;
2036 BGColor.r = 100;
2037 BGColor.a = 0;
2038 if (DisplayMessage)
2039 egDisplayMessage(L"Scanning for new boot loaders; please wait....", &BGColor);
2040 FreeList((VOID ***) &(MainMenu.Entries), &MainMenu.EntryCount);
2041 MainMenu.Entries = NULL;
2042 MainMenu.EntryCount = 0;
2043 ReadConfig(GlobalConfig.ConfigFilename);
2044 ConnectAllDriversToAllControllers();
2045 ScanVolumes();
2046 ScanForBootloaders();
2047 ScanForTools();
2048 SetupScreen();
2049 } // VOID RescanAll()
2050
2051 #ifdef __MAKEWITH_TIANO
2052
2053 // Minimal initialization function
2054 static VOID InitializeLib(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
2055 gST = SystemTable;
2056 // gImageHandle = ImageHandle;
2057 gBS = SystemTable->BootServices;
2058 // gRS = SystemTable->RuntimeServices;
2059 gRT = SystemTable->RuntimeServices; // Some BDS functions need gRT to be set
2060 EfiGetSystemConfigurationTable (&gEfiDxeServicesTableGuid, (VOID **) &gDS);
2061 }
2062
2063 #endif
2064
2065 // Set up our own Secure Boot extensions....
2066 // Returns TRUE on success, FALSE otherwise
2067 static BOOLEAN SecureBootSetup(VOID) {
2068 EFI_STATUS Status;
2069 BOOLEAN Success = FALSE;
2070
2071 if (secure_mode() && ShimLoaded()) {
2072 Status = security_policy_install();
2073 if (Status == EFI_SUCCESS) {
2074 Success = TRUE;
2075 } else {
2076 Print(L"Failed to install MOK Secure Boot extensions");
2077 PauseForKey();
2078 }
2079 }
2080 return Success;
2081 } // VOID SecureBootSetup()
2082
2083 // Remove our own Secure Boot extensions....
2084 // Returns TRUE on success, FALSE otherwise
2085 static BOOLEAN SecureBootUninstall(VOID) {
2086 EFI_STATUS Status;
2087 BOOLEAN Success = TRUE;
2088
2089 if (secure_mode()) {
2090 Status = security_policy_uninstall();
2091 if (Status != EFI_SUCCESS) {
2092 Success = FALSE;
2093 BeginTextScreen(L"Secure Boot Policy Failure");
2094 Print(L"Failed to uninstall MOK Secure Boot extensions; forcing a reboot.");
2095 PauseForKey();
2096 refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL);
2097 }
2098 }
2099 return Success;
2100 } // VOID SecureBootUninstall
2101
2102 // Sets the global configuration filename; will be CONFIG_FILE_NAME unless the
2103 // "-c" command-line option is set, in which case that takes precedence.
2104 // If an error is encountered, leaves the value alone (it should be set to
2105 // CONFIG_FILE_NAME when GlobalConfig is initialized).
2106 static VOID SetConfigFilename(EFI_HANDLE ImageHandle) {
2107 EFI_LOADED_IMAGE *Info;
2108 CHAR16 *Options, *FileName;
2109 EFI_STATUS Status;
2110 INTN Where;
2111
2112 Status = refit_call3_wrapper(BS->HandleProtocol, ImageHandle, &LoadedImageProtocol, (VOID **) &Info);
2113 if ((Status == EFI_SUCCESS) && (Info->LoadOptionsSize > 0)) {
2114 Options = (CHAR16 *) Info->LoadOptions;
2115 Where = FindSubString(L" -c ", Options);
2116 if (Where >= 0) {
2117 FileName = StrDuplicate(&Options[Where + 4]);
2118 Where = FindSubString(L" ", FileName);
2119 if (Where > 0)
2120 FileName[Where] = L'\0';
2121
2122 if (FileExists(SelfDir, FileName)) {
2123 GlobalConfig.ConfigFilename = FileName;
2124 } else {
2125 Print(L"Specified configuration file (%s) doesn't exist; using\n'refind.conf' default\n", FileName);
2126 MyFreePool(FileName);
2127 } // if/else
2128 } // if
2129 } // if
2130 } // VOID SetConfigFilename()
2131
2132 //
2133 // main entry point
2134 //
2135 EFI_STATUS
2136 EFIAPI
2137 efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
2138 {
2139 EFI_STATUS Status;
2140 BOOLEAN MainLoopRunning = TRUE;
2141 BOOLEAN MokProtocol;
2142 REFIT_MENU_ENTRY *ChosenEntry;
2143 UINTN MenuExit, i;
2144 CHAR16 *SelectionName = NULL;
2145 EG_PIXEL BGColor;
2146
2147 // bootstrap
2148 InitializeLib(ImageHandle, SystemTable);
2149 Status = InitRefitLib(ImageHandle);
2150 if (EFI_ERROR(Status))
2151 return Status;
2152
2153 // read configuration
2154 CopyMem(GlobalConfig.ScanFor, "ieom ", NUM_SCAN_OPTIONS);
2155 FindLegacyBootType();
2156 if (GlobalConfig.LegacyType == LEGACY_TYPE_MAC)
2157 CopyMem(GlobalConfig.ScanFor, "ihebocm ", NUM_SCAN_OPTIONS);
2158 SetConfigFilename(ImageHandle);
2159 ReadConfig(GlobalConfig.ConfigFilename);
2160
2161 InitScreen();
2162 WarnIfLegacyProblems();
2163 MainMenu.TimeoutSeconds = GlobalConfig.Timeout;
2164
2165 // disable EFI watchdog timer
2166 refit_call4_wrapper(BS->SetWatchdogTimer, 0x0000, 0x0000, 0x0000, NULL);
2167
2168 // further bootstrap (now with config available)
2169 MokProtocol = SecureBootSetup();
2170 LoadDrivers();
2171 ScanVolumes();
2172 ScanForBootloaders();
2173 ScanForTools();
2174 SetupScreen();
2175
2176 if (GlobalConfig.ScanDelay > 0) {
2177 BGColor.b = 255;
2178 BGColor.g = 175;
2179 BGColor.r = 100;
2180 BGColor.a = 0;
2181 if (GlobalConfig.ScanDelay > 1)
2182 egDisplayMessage(L"Pausing before disk scan; please wait....", &BGColor);
2183 for (i = 0; i < GlobalConfig.ScanDelay; i++)
2184 refit_call1_wrapper(BS->Stall, 1000000);
2185 RescanAll(GlobalConfig.ScanDelay > 1);
2186 } // if
2187
2188 if (GlobalConfig.DefaultSelection)
2189 SelectionName = StrDuplicate(GlobalConfig.DefaultSelection);
2190
2191 while (MainLoopRunning) {
2192 MenuExit = RunMainMenu(&MainMenu, &SelectionName, &ChosenEntry);
2193
2194 // The Escape key triggers a re-scan operation....
2195 if (MenuExit == MENU_EXIT_ESCAPE) {
2196 MenuExit = 0;
2197 RescanAll(TRUE);
2198 continue;
2199 }
2200
2201 switch (ChosenEntry->Tag) {
2202
2203 case TAG_REBOOT: // Reboot
2204 TerminateScreen();
2205 refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL);
2206 MainLoopRunning = FALSE; // just in case we get this far
2207 break;
2208
2209 case TAG_SHUTDOWN: // Shut Down
2210 TerminateScreen();
2211 refit_call4_wrapper(RT->ResetSystem, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
2212 MainLoopRunning = FALSE; // just in case we get this far
2213 break;
2214
2215 case TAG_ABOUT: // About rEFInd
2216 AboutrEFInd();
2217 break;
2218
2219 case TAG_LOADER: // Boot OS via .EFI loader
2220 StartLoader((LOADER_ENTRY *)ChosenEntry, SelectionName);
2221 break;
2222
2223 case TAG_LEGACY: // Boot legacy OS
2224 StartLegacy((LEGACY_ENTRY *)ChosenEntry, SelectionName);
2225 break;
2226
2227 case TAG_LEGACY_UEFI: // Boot a legacy OS on a non-Mac
2228 StartLegacyUEFI((LEGACY_ENTRY *)ChosenEntry, SelectionName);
2229 break;
2230
2231 case TAG_TOOL: // Start a EFI tool
2232 StartTool((LOADER_ENTRY *)ChosenEntry);
2233 break;
2234
2235 case TAG_EXIT: // Terminate rEFInd
2236 if ((MokProtocol) && !SecureBootUninstall()) {
2237 MainLoopRunning = FALSE; // just in case we get this far
2238 } else {
2239 BeginTextScreen(L" ");
2240 return EFI_SUCCESS;
2241 }
2242 break;
2243
2244 case TAG_FIRMWARE: // Reboot into firmware's user interface
2245 RebootIntoFirmware();
2246 break;
2247
2248 } // switch()
2249 } // while()
2250
2251 // If we end up here, things have gone wrong. Try to reboot, and if that
2252 // fails, go into an endless loop.
2253 refit_call4_wrapper(RT->ResetSystem, EfiResetCold, EFI_SUCCESS, 0, NULL);
2254 EndlessIdleLoop();
2255
2256 return EFI_SUCCESS;
2257 } /* efi_main() */