/** @file vmlinuz and initramfs/initrd autodetect. Copyright (c) 2021, Mike Beaton. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include "LinuxBootInternal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define GRUB_DEFAULT_FILE L"\\etc\\default\\grub" #define OS_RELEASE_FILE L"\\etc\\os-release" #define AUTODETECT_DIR L"\\boot" #define ROOT_DIR L"\\" #define ROOT_FS_FILE L"\\bin\\sh" STATIC OC_FLEX_ARRAY *mVmlinuzFiles; STATIC OC_FLEX_ARRAY *mInitrdFiles; STATIC OC_FLEX_ARRAY *mEtcOsReleaseOptions; STATIC CHAR8 *mEtcOsReleaseFileContents; STATIC CHAR8 *mPrettyName; STATIC CHAR8 *mDiskLabel; STATIC OC_FLEX_ARRAY *mEtcDefaultGrubOptions; STATIC CHAR8 *mEtcDefaultGrubFileContents; STATIC OC_FLEX_ARRAY *mPerPartuuidAutoOpts; STATIC CHAR16 *mCurrentPartuuidAutoOpts; STATIC CHAR16 *mCurrentPartuuidAutoOptsPlus; STATIC CHAR16 *mGlobalAutoOpts; STATIC CHAR16 *mGlobalAutoOptsPlus; STATIC EFI_STATUS ProcessVmlinuzFile ( EFI_FILE_HANDLE Directory, EFI_FILE_INFO *FileInfo, UINTN FileInfoSize, VOID *Context OPTIONAL ) { CHAR16 *Dash; VMLINUZ_FILE *VmlinuzFile; if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) { return EFI_NOT_FOUND; } // // Do not use files without '-' in the name, i.e. we do not need and // do not try to use `vmlinuz` or `initrd` symlinks even if present // (and even though we can in fact specify them as filenames and boot // fine from them). // Dash = OcStrChr (FileInfo->FileName, L'-'); if ((Dash == NULL) || (Dash[1] == L'\0')) { return EFI_NOT_FOUND; } if (StrnCmp (L"vmlinuz", FileInfo->FileName, L_STR_LEN (L"vmlinuz")) == 0) { VmlinuzFile = OcFlexArrayAddItem (mVmlinuzFiles); } else if (StrnCmp (L"init", FileInfo->FileName, L_STR_LEN (L"init")) == 0) { // // initrd* or initramfs* // VmlinuzFile = OcFlexArrayAddItem (mInitrdFiles); } else { return EFI_NOT_FOUND; } DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Found %s...\n", FileInfo->FileName )); if (VmlinuzFile == NULL) { return EFI_OUT_OF_RESOURCES; } VmlinuzFile->FileName = AllocateCopyPool (StrSize (FileInfo->FileName), FileInfo->FileName); if (VmlinuzFile->FileName == NULL) { return EFI_OUT_OF_RESOURCES; } VmlinuzFile->Version = &VmlinuzFile->FileName[&Dash[1] - FileInfo->FileName]; VmlinuzFile->StrLen = StrLen (FileInfo->FileName); return EFI_SUCCESS; } STATIC EFI_STATUS CreateAsciiRelativePath ( CHAR8 **Dest, CHAR16 *DirectoryPath, UINTN DirectoryPathLength, CHAR16 *FilePath, UINTN FilePathLength ) { UINTN Size; BOOLEAN UseDir; UseDir = !(DirectoryPathLength == 1 && DirectoryPath[0] == L'\\'); Size = (UseDir ? DirectoryPathLength : 0) + FilePathLength + 2; *Dest = AllocatePool (Size); if (*Dest == NULL) { return EFI_OUT_OF_RESOURCES; } AsciiSPrint (*Dest, Size, "%s\\%s", UseDir ? DirectoryPath : L"", FilePath); return EFI_SUCCESS; } STATIC EFI_STATUS CreateRootPartuuid ( CHAR8 **Dest ) { UINTN Length; UINTN NumPrinted; Length = L_STR_LEN ("root=PARTUUID=") + GUID_STRING_LENGTH; *Dest = AllocatePool (Length + 1); if (*Dest == NULL) { return EFI_OUT_OF_RESOURCES; } NumPrinted = AsciiSPrint (*Dest, Length + 1, "%a%g", "root=PARTUUID=", gPartuuid); ASSERT (NumPrinted == Length); // // Value is case-sensitive and must be lower case. // OcAsciiToLower (&(*Dest)[L_STR_LEN ("root=PARTUUID=")]); return EFI_SUCCESS; } STATIC VOID AutodetectTitle ( VOID ) { UINTN Index; BOOLEAN Found; if (mEtcOsReleaseOptions != NULL) { // // If neither are present, default title gets set later to "Linux". // Found = FALSE; for (Index = 0; Index < 2; Index++) { if ( OcParsedVarsGetAsciiStr ( mEtcOsReleaseOptions, (Index == 0) ? "PRETTY_NAME" : "NAME", &mPrettyName ) && (mPrettyName != NULL)) { Found = TRUE; break; } } if (Found) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Found distro %a\n", mPrettyName )); } else { DEBUG ((DEBUG_WARN, "LNX: Neither %a nor %a found in %s\n", "PRETTY_NAME", "NAME", OS_RELEASE_FILE)); } } } STATIC EFI_STATUS LoadOsRelease ( IN CONST EFI_FILE_PROTOCOL *RootDirectory, IN CONST BOOLEAN IsStandaloneBoot ) { EFI_STATUS Status; BOOLEAN TryReading; mEtcOsReleaseOptions = NULL; mEtcOsReleaseFileContents = NULL; mPrettyName = NULL; TryReading = (mDiskLabel == NULL); DEBUG_CODE_BEGIN (); TryReading = TRUE; DEBUG_CODE_END (); if (IsStandaloneBoot) { TryReading = FALSE; } // // Load distro name from /etc/os-release. // if (TryReading) { mEtcOsReleaseFileContents = OcReadFileFromDirectory (RootDirectory, OS_RELEASE_FILE, NULL, 0); if (mEtcOsReleaseFileContents == NULL) { DEBUG ((DEBUG_WARN, "LNX: %s not found\n", OS_RELEASE_FILE)); } else { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Reading %s\n", OS_RELEASE_FILE )); Status = OcParseVars (mEtcOsReleaseFileContents, &mEtcOsReleaseOptions, OcStringFormatAscii); if (EFI_ERROR (Status)) { FreePool (mEtcOsReleaseFileContents); mEtcOsReleaseFileContents = NULL; DEBUG ((DEBUG_WARN, "LNX: Cannot parse %s - %r\n", OS_RELEASE_FILE, Status)); return Status; } // // Do this early to give a nicer log entry order: distro name from os-release is logged // before reports about distro (e.g. before the error below if it is not GRUB-based). // AutodetectTitle (); } } if (mDiskLabel != NULL) { mPrettyName = mDiskLabel; } return EFI_SUCCESS; } STATIC EFI_STATUS LoadDefaultGrub ( IN CONST EFI_FILE_PROTOCOL *RootDirectory, IN CONST BOOLEAN IsStandaloneBoot ) { EFI_STATUS Status; mEtcDefaultGrubOptions = NULL; mEtcDefaultGrubFileContents = NULL; if (IsStandaloneBoot) { return EFI_SUCCESS; } // // Load kernel options from /etc/default/grub. // mEtcDefaultGrubFileContents = OcReadFileFromDirectory (RootDirectory, GRUB_DEFAULT_FILE, NULL, 0); if (mEtcDefaultGrubFileContents == NULL) { DEBUG ((DEBUG_INFO, "LNX: %s not found (bootloader is not GRUB?)\n", GRUB_DEFAULT_FILE)); } else { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Reading %s\n", GRUB_DEFAULT_FILE )); Status = OcParseVars (mEtcDefaultGrubFileContents, &mEtcDefaultGrubOptions, OcStringFormatAscii); if (EFI_ERROR (Status)) { FreePool (mEtcDefaultGrubFileContents); mEtcDefaultGrubFileContents = NULL; DEBUG ((DEBUG_WARN, "LNX: Cannot parse %s - %r\n", GRUB_DEFAULT_FILE, Status)); return Status; } } return EFI_SUCCESS; } STATIC VOID LoadAppleDiskLabel ( IN CONST EFI_FILE_PROTOCOL *VmlinuzDirectory ) { mDiskLabel = NULL; if ((gPickerContext->PickerAttributes & OC_ATTR_USE_DISK_LABEL_FILE) != 0) { mDiskLabel = OcReadFileFromDirectory (VmlinuzDirectory, L".contentDetails", NULL, 0); if (mDiskLabel == NULL) { mDiskLabel = OcReadFileFromDirectory (VmlinuzDirectory, L".disk_label.contentDetails", NULL, 0); } if (mDiskLabel == NULL) { DEBUG ((DEBUG_INFO, "LNX: %s %s not present\n", L".contentDetails", L".disk_label.contentDetails")); } else { DEBUG ((DEBUG_INFO, "LNX: Found disk label '%a'\n", mDiskLabel)); } } } STATIC VOID FreeEtcFiles ( VOID ) { // // If non-null, refers to string inside mEtcOsReleaseFileContents or copy of mDiskLabel. // mPrettyName = NULL; if (mDiskLabel != NULL) { FreePool (mDiskLabel); mDiskLabel = NULL; } OcFlexArrayFree (&mEtcOsReleaseOptions); if (mEtcOsReleaseFileContents != NULL) { FreePool (mEtcOsReleaseFileContents); mEtcOsReleaseFileContents = NULL; } OcFlexArrayFree (&mEtcDefaultGrubOptions); if (mEtcDefaultGrubFileContents != NULL) { FreePool (mEtcDefaultGrubFileContents); mEtcDefaultGrubFileContents = NULL; } } STATIC EFI_STATUS InsertOption ( IN CONST UINTN InsertIndex, IN OC_FLEX_ARRAY *Options, IN CONST VOID *Value, IN CONST OC_STRING_FORMAT StringFormat ) { EFI_STATUS Status; UINTN OptionsLength; UINTN CopiedLength; CHAR8 **Option; if (StringFormat == OcStringFormatUnicode) { OptionsLength = StrLen (Value); } else { OptionsLength = AsciiStrLen (Value); } if (OptionsLength > 0) { Option = OcFlexArrayInsertItem (Options, InsertIndex); if (Option == NULL) { return EFI_OUT_OF_RESOURCES; } if (StringFormat == OcStringFormatUnicode) { *Option = AllocatePool ((OptionsLength + 1) * sizeof (CHAR16)); if (*Option == NULL) { return EFI_OUT_OF_RESOURCES; } Status = UnicodeStrnToAsciiStrS (Value, OptionsLength, *Option, OptionsLength + 1, &CopiedLength); ASSERT (!EFI_ERROR (Status)); ASSERT (CopiedLength == OptionsLength); } else { *Option = AllocateCopyPool (OptionsLength + 1, Value); if (*Option == NULL) { return EFI_OUT_OF_RESOURCES; } } } return EFI_SUCCESS; } STATIC EFI_STATUS AddOption ( IN OC_FLEX_ARRAY *Options, IN CONST VOID *Value, IN CONST OC_STRING_FORMAT StringFormat ) { return InsertOption (Options->Count, Options, Value, StringFormat); } EFI_STATUS InsertRootOption ( IN OC_FLEX_ARRAY *Options ) { CHAR8 **NewOption; DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Creating \"root=PARTUUID=%g\"\n", gPartuuid )); NewOption = OcFlexArrayInsertItem (Options, 0); if (NewOption == NULL) { return EFI_OUT_OF_RESOURCES; } return CreateRootPartuuid (NewOption); } STATIC VOID GetCurrentPartuuidAutoOpts ( VOID ) { UINTN Index; AUTOOPTS *AutoOpts; mCurrentPartuuidAutoOpts = NULL; mCurrentPartuuidAutoOptsPlus = NULL; for (Index = 0; Index < mPerPartuuidAutoOpts->Count; Index++) { AutoOpts = OcFlexArrayItemAt (mPerPartuuidAutoOpts, Index); if (CompareMem (&gPartuuid, &AutoOpts->Guid, sizeof (EFI_GUID)) == 0) { if (AutoOpts->PlusOpts) { mCurrentPartuuidAutoOptsPlus = AutoOpts->Opts; } else { mCurrentPartuuidAutoOpts = AutoOpts->Opts; } } } } EFI_STATUS InternalPreloadAutoOpts ( IN OC_FLEX_ARRAY *Options ) { EFI_STATUS Status; UINTN Index; OC_PARSED_VAR *Option; AUTOOPTS *AutoOpts; mGlobalAutoOpts = NULL; mGlobalAutoOptsPlus = NULL; mPerPartuuidAutoOpts = OcFlexArrayInit (sizeof (AUTOOPTS), NULL); if (mPerPartuuidAutoOpts == NULL) { return EFI_OUT_OF_RESOURCES; } if (Options == NULL) { return EFI_SUCCESS; } // // Look for autoopts. // Remember that although args are ASCII in the OC config file, they are // Unicode by the time they get passed as UEFI LoadOptions. // for (Index = 0; Index < Options->Count; Index++) { Option = OcFlexArrayItemAt (Options, Index); // // autoopts:{partuuid}[+]="...": user options for specified partuuid. // if (OcUnicodeStartsWith (Option->Unicode.Name, L"autoopts:", TRUE)) { if (Option->Unicode.Value == NULL) { DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); continue; } AutoOpts = OcFlexArrayAddItem (mPerPartuuidAutoOpts); Status = StrToGuid (&Option->Unicode.Name[L_STR_LEN (L"autoopts:")], &AutoOpts->Guid); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "LNX: Cannot parse partuuid from %s - %r\n", Option->Unicode.Name, Status)); OcFlexArrayDiscardItem (mPerPartuuidAutoOpts, FALSE); continue; } AutoOpts->Opts = Option->Unicode.Value; AutoOpts->PlusOpts = OcUnicodeEndsWith (Option->Unicode.Name, L"+", FALSE); } else if (StrCmp (Option->Unicode.Name, L"autoopts") == 0) { if (Option->Unicode.Value == NULL) { DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); continue; } mGlobalAutoOpts = Option->Unicode.Value; } else if (StrCmp (Option->Unicode.Name, L"autoopts+") == 0) { if (Option->Unicode.Value == NULL) { DEBUG ((DEBUG_WARN, "LNX: Missing value for %s\n", Option->Unicode.Name)); continue; } mGlobalAutoOptsPlus = Option->Unicode.Value; } } return EFI_SUCCESS; } // // TODO: Options for rescue versions. Would it be better e.g. just to add "ro" and nothing else? // However on some installs (e.g. where modules to load are specified in the kernel opts) this // would not boot at all. // Maybe upgrade to autoopts:{partuuid}r="...": user options for rescue kernels on specified partuuid? // STATIC EFI_STATUS AutodetectBootOptions ( IN CONST BOOLEAN IsRescue, IN CONST BOOLEAN IsStandaloneBoot, IN OC_FLEX_ARRAY *Options ) { EFI_STATUS Status; UINTN Index; UINTN InsertIndex; CHAR8 *AsciiStrValue; CHAR8 *GrubVarName; BOOLEAN FoundOptions; CHAR8 *AddRxOption; FoundOptions = FALSE; // // Do we have user-specified options for this partuuid? // if (mCurrentPartuuidAutoOpts) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Using autoopts:%g%a=\"%s\"\n", &gPartuuid, "", mCurrentPartuuidAutoOpts )); Status = AddOption (Options, mCurrentPartuuidAutoOpts, OcStringFormatUnicode); return Status; } if (mCurrentPartuuidAutoOptsPlus) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Using autoopts:%g%a=\"%s\"\n", &gPartuuid, "+", mCurrentPartuuidAutoOptsPlus )); Status = AddOption (Options, mCurrentPartuuidAutoOptsPlus, OcStringFormatUnicode); if (EFI_ERROR (Status)) { return Status; } FoundOptions = TRUE; } // // Don't use autoopts if partition specific autoopts:{partuuid} already found. // if (!FoundOptions && mGlobalAutoOpts) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Using autoopts%a=\"%s\"\n", "", mGlobalAutoOpts )); Status = AddOption (Options, mGlobalAutoOpts, OcStringFormatUnicode); return Status; } else if (mGlobalAutoOptsPlus) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Using autoopts%a=\"%s\"\n", "+", mGlobalAutoOptsPlus )); Status = AddOption (Options, mGlobalAutoOptsPlus, OcStringFormatUnicode); if (EFI_ERROR (Status)) { return Status; } FoundOptions = TRUE; } // // Should only have attempted to detect kernels on standalone boot partition if we had full user options. // ASSERT (!IsStandaloneBoot); // // Code only reaches here and below if has been nothing or only += options above. // Use options from GRUB default location. // if (mEtcDefaultGrubOptions != NULL) { // // Insert these after "ro" but before any user specified opts. // InsertIndex = 0; // // If both are present both should be added, standard grub scripts add them // in this order. // Rescue should only use GRUB_CMDLINE_LINUX so this is correct as // far as it goes; however note that rescue options are unfortunately not // normally stored here, but are generated in the depths of grub scripts. // for (Index = 0; Index < (IsRescue ? 1u : 2u); Index++) { if (Index == 0) { GrubVarName = "GRUB_CMDLINE_LINUX"; } else { GrubVarName = "GRUB_CMDLINE_LINUX_DEFAULT"; } if ( OcParsedVarsGetAsciiStr ( mEtcDefaultGrubOptions, GrubVarName, &AsciiStrValue ) && (AsciiStrValue != NULL)) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Using %a=\"%a\"\n", GrubVarName, AsciiStrValue )); if (AsciiStrValue[0] != '\0') { Status = InsertOption (InsertIndex, Options, AsciiStrValue, OcStringFormatAscii); if (EFI_ERROR (Status)) { return Status; } // // Must not increment insert index if empty option. // InsertIndex++; } // // Empty string value is good enough for found: we are operating // from GRUB cfg files rather than pure guesswork. // FoundOptions = TRUE; } } } // // It might be valid to have no options for some kernels or distros, but at least // empty (not missing) user specified options or GRUB_CMDLINE_LINUX[_DEFAULT] needs // to be present in that case or we stop. // if (!FoundOptions) { DEBUG ((DEBUG_WARN, "LNX: No grub default or user defined options - aborting\n")); return EFI_INVALID_PARAMETER; } #if !defined (LINUX_ALLOW_MBR) if (CompareGuid (&gPartuuid, &gEfiPartTypeUnusedGuid)) { Status = EFI_UNSUPPORTED; DEBUG ((DEBUG_WARN, "LNX: Cannot autodetect root on MBR partition - %r\n", Status)); return Status; } #endif // // Insert "root=PARTUUID=..." option, followed by "ro" if requested, only if we get to here. // Status = InsertRootOption (Options); if (EFI_ERROR (Status)) { return Status; } InsertIndex = 1; AddRxOption = NULL; if ((gLinuxBootFlags & LINUX_BOOT_ADD_RW) != 0) { AddRxOption = "rw"; } else if ((gLinuxBootFlags & LINUX_BOOT_ADD_RO) != 0) { AddRxOption = "ro"; } if (AddRxOption != NULL) { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Adding \"%a\"\n", AddRxOption )); Status = InsertOption (InsertIndex, Options, AddRxOption, OcStringFormatAscii); } return Status; } STATIC EFI_STATUS GenerateEntriesForVmlinuzFiles ( IN CHAR16 *DirectoryPath, IN CONST BOOLEAN IsStandaloneBoot ) { EFI_STATUS Status; UINTN VmlinuzIndex; UINTN InitrdIndex; UINTN ShortestMatch; UINTN DirectoryPathLength; LOADER_ENTRY *Entry; VMLINUZ_FILE *VmlinuzFile; VMLINUZ_FILE *InitrdFile; VMLINUZ_FILE *InitrdMatch; CHAR8 **Option; BOOLEAN IsRescue; ASSERT (DirectoryPath != NULL); ASSERT (mVmlinuzFiles->Count > 0); DirectoryPathLength = StrLen (DirectoryPath); for (VmlinuzIndex = 0; VmlinuzIndex < mVmlinuzFiles->Count; VmlinuzIndex++) { VmlinuzFile = OcFlexArrayItemAt (mVmlinuzFiles, VmlinuzIndex); IsRescue = FALSE; if ( OcUnicodeStartsWith (VmlinuzFile->Version, L"0", FALSE) || (StrStr (VmlinuzFile->Version, L"rescue") != NULL)) { // // We might have to scan /boot/grb/grub.cfg as grub os-prober does if // we want to find rescue version options, or we need to find a way // for user to pass these in, since they are generated in the depths // of the grub scripts, and in typical distros are not present in // /etc/default/grub, even though it looks as if they could be. // IsRescue = TRUE; DEBUG ((DEBUG_INFO, "LNX: %s=rescue\n", VmlinuzFile->Version)); } ShortestMatch = MAX_UINTN; InitrdMatch = NULL; // // Find shortest init* filename containing the same version string. // for (InitrdIndex = 0; InitrdIndex < mInitrdFiles->Count; InitrdIndex++) { InitrdFile = OcFlexArrayItemAt (mInitrdFiles, InitrdIndex); if (InitrdFile->StrLen < ShortestMatch) { if (StrStr (InitrdFile->Version, VmlinuzFile->Version) != NULL) { InitrdMatch = InitrdFile; ShortestMatch = InitrdFile->StrLen; } } } Entry = InternalAllocateLoaderEntry (); if (Entry == NULL) { return EFI_OUT_OF_RESOURCES; } // // Linux. // Status = CreateAsciiRelativePath ( &Entry->Linux, DirectoryPath, DirectoryPathLength, VmlinuzFile->FileName, VmlinuzFile->StrLen ); if (EFI_ERROR (Status)) { return Status; } // // Id and version from filename. // Status = InternalIdVersionFromFileName (Entry, VmlinuzFile->FileName); if (EFI_ERROR (Status)) { return Status; } // // Use title from .contentDetails or os-release file. // if (mPrettyName != NULL) { Entry->Title = AllocateCopyPool (AsciiStrSize (mPrettyName), mPrettyName); if (Entry->Title == NULL) { return EFI_OUT_OF_RESOURCES; } } // // Initrd. // if (InitrdMatch == NULL) { // // Note that where initrd was required user will see clear (and safe, i.e. will // not just boot incorrectly) warning from Linux kernel as well. // DEBUG ((DEBUG_WARN, "LNX: No matching initrd/initramfs file found for %a\n", Entry->Linux)); } else { Option = OcFlexArrayAddItem (Entry->Initrds); if (Option == NULL) { return EFI_OUT_OF_RESOURCES; } Status = CreateAsciiRelativePath ( Option, DirectoryPath, DirectoryPathLength, InitrdMatch->FileName, InitrdMatch->StrLen ); if (EFI_ERROR (Status)) { return Status; } } // // Add all options. // Status = AutodetectBootOptions (IsRescue, IsStandaloneBoot, Entry->Options); if (EFI_ERROR (Status)) { return Status; } } return EFI_SUCCESS; } STATIC EFI_STATUS AutodetectLinuxAtDirectory ( IN EFI_FILE_PROTOCOL *RootDirectory, IN EFI_FILE_PROTOCOL *VmlinuzDirectory, IN CHAR16 *AutodetectDir, IN CONST BOOLEAN IsStandaloneBoot, OUT OC_PICKER_ENTRY **Entries, OUT UINTN *NumEntries ) { EFI_STATUS Status; EFI_FILE_PROTOCOL *RootFsFile; mVmlinuzFiles = NULL; mInitrdFiles = NULL; Status = EFI_SUCCESS; if (!IsStandaloneBoot) { Status = OcSafeFileOpen (RootDirectory, &RootFsFile, ROOT_FS_FILE, EFI_FILE_MODE_READ, 0); if (!EFI_ERROR (Status)) { Status = OcEnsureDirectoryFile (RootFsFile, FALSE); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "LNX: %s found but not a %a - %r\n", ROOT_FS_FILE, "file", Status)); } RootFsFile->Close (RootFsFile); } if (EFI_ERROR (Status)) { DEBUG ((DEBUG_WARN, "LNX: AutodetectLinux not root fs - %r\n", Status)); } } if (!EFI_ERROR (Status)) { mVmlinuzFiles = OcFlexArrayInit (sizeof (VMLINUZ_FILE), OcFlexArrayFreePointerItem); if (mVmlinuzFiles == NULL) { Status = EFI_OUT_OF_RESOURCES; } } if (!EFI_ERROR (Status)) { mInitrdFiles = OcFlexArrayInit (sizeof (VMLINUZ_FILE), OcFlexArrayFreePointerItem); if (mInitrdFiles == NULL) { Status = EFI_OUT_OF_RESOURCES; } } // // Place vmlinuz* and init* files into arrays. // if (!EFI_ERROR (Status)) { Status = OcScanDirectory (VmlinuzDirectory, ProcessVmlinuzFile, NULL); if (!EFI_ERROR (Status) && (mVmlinuzFiles->Count == 0)) { // // If initrd files but no vmlinuz files are present in autodetect dir. // Status = EFI_NOT_FOUND; } } if (!EFI_ERROR (Status)) { gLoaderEntries = OcFlexArrayInit ( sizeof (LOADER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM)InternalFreeLoaderEntry ); if (gLoaderEntries == NULL) { Status = EFI_OUT_OF_RESOURCES; } else { LoadAppleDiskLabel (VmlinuzDirectory); Status = LoadOsRelease (RootDirectory, IsStandaloneBoot); if (!EFI_ERROR (Status)) { Status = LoadDefaultGrub (RootDirectory, IsStandaloneBoot); } if (!EFI_ERROR (Status)) { Status = GenerateEntriesForVmlinuzFiles (AutodetectDir, IsStandaloneBoot); } FreeEtcFiles (); } if (!EFI_ERROR (Status)) { Status = InternalConvertLoaderEntriesToBootEntries ( RootDirectory, Entries, NumEntries ); } OcFlexArrayFree (&gLoaderEntries); } if (mVmlinuzFiles != NULL) { OcFlexArrayFree (&mVmlinuzFiles); } if (mInitrdFiles != NULL) { OcFlexArrayFree (&mInitrdFiles); } return Status; } STATIC EFI_STATUS DoAutodetectLinux ( IN EFI_FILE_PROTOCOL *RootDirectory, IN EFI_FILE_PROTOCOL *VmlinuzDirectory, IN CHAR16 *AutodetectDir, IN CONST BOOLEAN IsStandaloneBoot, OUT OC_PICKER_ENTRY **Entries, OUT UINTN *NumEntries ) { EFI_STATUS Status; Status = AutodetectLinuxAtDirectory (RootDirectory, VmlinuzDirectory, AutodetectDir, IsStandaloneBoot, Entries, NumEntries); DEBUG (( (EFI_ERROR (Status) && Status != EFI_NOT_FOUND) ? DEBUG_WARN : DEBUG_INFO, "LNX: AutodetectLinux %s - %r\n", AutodetectDir, Status )); return Status; } EFI_STATUS InternalAutodetectLinux ( IN EFI_FILE_PROTOCOL *RootDirectory, OUT OC_PICKER_ENTRY **Entries, OUT UINTN *NumEntries ) { EFI_STATUS Status; EFI_FILE_PROTOCOL *VmlinuzDirectory; GetCurrentPartuuidAutoOpts (); // // Autodetect at /boot if present. // Status = OcSafeFileOpen (RootDirectory, &VmlinuzDirectory, AUTODETECT_DIR, EFI_FILE_MODE_READ, 0); if (!EFI_ERROR (Status)) { Status = DoAutodetectLinux (RootDirectory, VmlinuzDirectory, AUTODETECT_DIR, FALSE, Entries, NumEntries); VmlinuzDirectory->Close (VmlinuzDirectory); } // // Try to autodetect kernels at / only if detecting at /boot failed and we have full user-specified // options, since we can't autodetect any options on standalone /boot (unless we parse grub.cfg for // boot entries, and even then not all standalone boot setups have it). // if (EFI_ERROR (Status)) { if ((mCurrentPartuuidAutoOpts != NULL) || ((mGlobalAutoOpts != NULL) && (mCurrentPartuuidAutoOptsPlus == NULL))) { Status = DoAutodetectLinux (RootDirectory, RootDirectory, ROOT_DIR, TRUE, Entries, NumEntries); } else { DEBUG (( (gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO, "LNX: Not trying to autodetect kernel on possible standalone /boot partition without full autoopts\n" )); } } return Status; }