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