mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
In GRUB2+blscfg mode: - Allow grub vars in 'initrd' as well as in 'options' - Allow multiple initrd files on one 'initrd' line - Initialise empty $tuned_params and $tuned_initrd grub vars if no values present, on an optional flag enabled by default (since we want to make booting major distros easy) In GRUB2+blscfg mode (seem to be allowed now, not required for fix): - Allow multiple 'initrd' lines - Allow multiple 'options' lines Add variant of OcParseVars which can parse as value-only tokens. Signed-off-by: Mike Beaton <mjsbeaton@gmail.com>
1553 lines
37 KiB
C
1553 lines
37 KiB
C
/** @file
|
|
Boot Loader Spec / Grub2 blscfg module loader entry parser.
|
|
|
|
Copyright (c) 2021, Mike Beaton. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-3-Clause
|
|
**/
|
|
|
|
#include "LinuxBootInternal.h"
|
|
|
|
#include <Uefi.h>
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/DevicePathLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/OcBootManagementLib.h>
|
|
#include <Library/OcDebugLogLib.h>
|
|
#include <Library/OcFileLib.h>
|
|
#include <Library/OcFlexArrayLib.h>
|
|
#include <Library/OcStringLib.h>
|
|
#include <Library/PrintLib.h>
|
|
#include <Library/SortLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
|
|
#include <Protocol/OcBootEntry.h>
|
|
|
|
//
|
|
// Vars which require blank values if not present after GRUB2+blscfg var parsing, in order
|
|
// to fix up the fact that TuneD does not always initialise them.
|
|
//
|
|
STATIC CHAR8 *mTuneDVars[] = {
|
|
"tuned_params",
|
|
"tuned_initrd"
|
|
};
|
|
|
|
//
|
|
// Root.
|
|
//
|
|
#define ROOT_DIR L"\\"
|
|
//
|
|
// Required where the BTRFS subvolume is /boot, as this looks like a
|
|
// normal directory within EFI. Note that scanning / and then /boot
|
|
// is how blscfg behaves by default too.
|
|
//
|
|
#define BOOT_DIR L"\\boot"
|
|
//
|
|
// Don't add missing root= option unless this directory is present at root.
|
|
//
|
|
#define OSTREE_DIR L"\\ostree"
|
|
|
|
//
|
|
// No leading slash so they can be relative to root or
|
|
// additional scan dir.
|
|
//
|
|
#define LOADER_ENTRIES_DIR L"loader\\entries"
|
|
#define GRUB2_GRUB_CFG L"grub2\\grub.cfg"
|
|
#define GRUB2_GRUBENV L"grub2\\grubenv"
|
|
#define GRUB2_GRUBENV_SIZE SIZE_1KB
|
|
|
|
#define BLSPEC_SUFFIX_CONF L".conf"
|
|
#define BLSPEC_PREFIX_AUTO L"auto-"
|
|
|
|
//
|
|
// Put a limit on entry name length, since the base part of the filename
|
|
// is used for the id, i.e. may be stored in BOOT#### entry in NVRAM.
|
|
// We then re-use the same filename length limit for vmlinuz and initrd files.
|
|
// Since we are using pure EFISTUB loading, initrd file paths have to be passed
|
|
// in kernel args (which have an unknown max. length of 256-4096 bytes).
|
|
//
|
|
#define MAX_LOADER_ENTRY_NAME_LEN (127)
|
|
#define MAX_LOADER_ENTRY_FILE_INFO_SIZE ( \
|
|
SIZE_OF_EFI_FILE_INFO + \
|
|
(MAX_LOADER_ENTRY_NAME_LEN + L_STR_LEN (BLSPEC_SUFFIX_CONF) + 1) * sizeof (CHAR16) \
|
|
)
|
|
|
|
//
|
|
// Might as well put an upper limit for some kind of sanity check.
|
|
// Typical files are ~350 bytes.
|
|
//
|
|
#define MAX_LOADER_ENTRY_FILE_SIZE SIZE_4KB
|
|
|
|
//
|
|
// grub2/grub.cfg was found, we treat this as enough to show that /loader/entries
|
|
// we have found are a GRUB2+blscfg setup.
|
|
//
|
|
STATIC
|
|
BOOLEAN
|
|
mIsGrub2;
|
|
|
|
/*
|
|
loader entry processing states
|
|
*/
|
|
typedef enum ENTRY_PARSE_STATE_ {
|
|
ENTRY_LEADING_SPACE,
|
|
ENTRY_COMMENT,
|
|
ENTRY_KEY,
|
|
ENTRY_KEY_VALUE_SPACE,
|
|
ENTRY_VALUE
|
|
} ENTRY_PARSE_STATE;
|
|
|
|
//
|
|
// First match, therefore Ubuntu should come after similar variants,
|
|
// and probably very short strings should come last in case they
|
|
// occur elsewhere in another kernel version string.
|
|
// Note: Should be kept in sync with Flavours.md.
|
|
//
|
|
STATIC
|
|
CHAR8 *
|
|
mLinuxVariants[] = {
|
|
"Arch",
|
|
"Astra",
|
|
"CentOS",
|
|
"Debian",
|
|
"Deepin",
|
|
"elementaryOS",
|
|
"Endless",
|
|
"Gentoo",
|
|
"Fedora",
|
|
"KDEneon",
|
|
"Kali",
|
|
"Mageia",
|
|
"Manjaro",
|
|
"Mint",
|
|
"openSUSE",
|
|
"Oracle",
|
|
"PopOS",
|
|
"RHEL",
|
|
"Rocky",
|
|
"Solus",
|
|
"Lubuntu",
|
|
"UbuntuMATE",
|
|
"Xubuntu",
|
|
"Ubuntu",
|
|
"Void",
|
|
"Zorin",
|
|
"MX"
|
|
};
|
|
|
|
STATIC
|
|
CHAR8 *
|
|
ExtractVariantFrom (
|
|
IN CHAR8 *String
|
|
)
|
|
{
|
|
UINTN Index;
|
|
CHAR8 *Variant;
|
|
|
|
Variant = NULL;
|
|
for (Index = 0; Index < ARRAY_SIZE (mLinuxVariants); Index++) {
|
|
if (OcAsciiStriStr (String, mLinuxVariants[Index]) != NULL) {
|
|
Variant = mLinuxVariants[Index];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Variant;
|
|
}
|
|
|
|
//
|
|
// Skips comment lines, and lines with key but no value
|
|
// EFI_LOAD_ERROR - File has invalid chars
|
|
//
|
|
STATIC
|
|
EFI_STATUS
|
|
GetLoaderEntryLine (
|
|
IN CONST CHAR16 *FileName,
|
|
IN OUT CHAR8 *Content,
|
|
IN OUT UINTN *Pos,
|
|
OUT CHAR8 **Key,
|
|
OUT CHAR8 **Value
|
|
)
|
|
{
|
|
ENTRY_PARSE_STATE State;
|
|
CHAR8 *LastSpace;
|
|
CHAR8 Ch;
|
|
BOOLEAN IsComplete;
|
|
|
|
*Key = NULL;
|
|
*Value = NULL;
|
|
State = ENTRY_LEADING_SPACE;
|
|
IsComplete = FALSE;
|
|
|
|
do {
|
|
Ch = Content[*Pos];
|
|
|
|
if (!((Ch == '\0') || (Ch == '\t') || (Ch == '\n') || ((Ch >= 20) && (Ch < 128)))) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Invalid char 0x%x in %s\n", Ch, FileName));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
switch (State) {
|
|
case ENTRY_LEADING_SPACE:
|
|
if ((Ch == '\n') || (Ch == '\0')) {
|
|
//
|
|
// Skip empty line
|
|
//
|
|
} else if ((Ch == ' ') || (Ch == '\t')) {
|
|
} else if (Ch == '#') {
|
|
State = ENTRY_COMMENT;
|
|
} else {
|
|
*Key = &Content[*Pos];
|
|
State = ENTRY_KEY;
|
|
}
|
|
|
|
break;
|
|
|
|
case ENTRY_COMMENT:
|
|
if (Ch == '\n') {
|
|
State = ENTRY_LEADING_SPACE;
|
|
}
|
|
|
|
break;
|
|
|
|
case ENTRY_KEY:
|
|
if ((Ch == '\n') || (Ch == '\0')) {
|
|
//
|
|
// No value, skip line
|
|
//
|
|
} else if ((Ch == ' ') || (Ch == '\t')) {
|
|
Content[*Pos] = '\0';
|
|
State = ENTRY_KEY_VALUE_SPACE;
|
|
}
|
|
|
|
break;
|
|
|
|
case ENTRY_KEY_VALUE_SPACE:
|
|
if ((Ch == '\n') || (Ch == '\0')) {
|
|
//
|
|
// No value, skip line
|
|
//
|
|
} else if ((Ch == ' ') || (Ch == '\t')) {
|
|
} else {
|
|
*Value = &Content[*Pos];
|
|
State = ENTRY_VALUE;
|
|
LastSpace = NULL;
|
|
}
|
|
|
|
break;
|
|
|
|
case ENTRY_VALUE:
|
|
if ((Ch == '\n') || (Ch == '\0')) {
|
|
if (LastSpace != NULL) {
|
|
*LastSpace = '\0';
|
|
} else {
|
|
Content[*Pos] = '\0';
|
|
}
|
|
|
|
IsComplete = TRUE;
|
|
} else if ((Ch == ' ') || (Ch == '\t')) {
|
|
LastSpace = &Content[*Pos];
|
|
} else {
|
|
LastSpace = NULL;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
ASSERT (FALSE);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Ch != '\0') {
|
|
++(*Pos);
|
|
}
|
|
} while (Ch != '\0' && !IsComplete);
|
|
|
|
if (!IsComplete) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
LOADER_ENTRY *
|
|
InternalAllocateLoaderEntry (
|
|
VOID
|
|
)
|
|
{
|
|
LOADER_ENTRY *Entry;
|
|
|
|
Entry = OcFlexArrayAddItem (gLoaderEntries);
|
|
|
|
if (Entry != NULL) {
|
|
Entry->Options = OcFlexArrayInit (sizeof (CHAR8 *), OcFlexArrayFreePointerItem);
|
|
if (Entry->Options == NULL) {
|
|
InternalFreeLoaderEntry (Entry);
|
|
Entry = NULL;
|
|
}
|
|
}
|
|
|
|
if (Entry != NULL) {
|
|
Entry->Initrds = OcFlexArrayInit (sizeof (CHAR8 *), OcFlexArrayFreePointerItem);
|
|
if (Entry->Initrds == NULL) {
|
|
InternalFreeLoaderEntry (Entry);
|
|
Entry = NULL;
|
|
}
|
|
}
|
|
|
|
return Entry;
|
|
}
|
|
|
|
VOID
|
|
InternalFreeLoaderEntry (
|
|
LOADER_ENTRY *Entry
|
|
)
|
|
{
|
|
ASSERT (Entry != NULL);
|
|
|
|
if (Entry == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (Entry->Title) {
|
|
FreePool (Entry->Title);
|
|
}
|
|
|
|
if (Entry->Version) {
|
|
FreePool (Entry->Version);
|
|
}
|
|
|
|
if (Entry->Linux) {
|
|
FreePool (Entry->Linux);
|
|
}
|
|
|
|
OcFlexArrayFree (&Entry->Initrds);
|
|
OcFlexArrayFree (&Entry->Options);
|
|
if (Entry->OcId != NULL) {
|
|
FreePool (Entry->OcId);
|
|
}
|
|
|
|
if (Entry->OcFlavour != NULL) {
|
|
FreePool (Entry->OcFlavour);
|
|
}
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
EntryCopySingleValue (
|
|
IN CONST BOOLEAN Grub2,
|
|
IN OUT CHAR8 **Target,
|
|
IN CONST CHAR8 *Value
|
|
)
|
|
{
|
|
if (!Grub2 || (*Target == NULL)) {
|
|
if (*Target != NULL) {
|
|
FreePool (*Target);
|
|
}
|
|
|
|
*Target = AllocateCopyPool (AsciiStrSize (Value), Value);
|
|
if (*Target == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
EntryCopyMultipleValue (
|
|
IN CONST BOOLEAN Grub2,
|
|
IN OC_FLEX_ARRAY *Array,
|
|
IN CONST CHAR8 *Value
|
|
)
|
|
{
|
|
VOID **NewItem;
|
|
|
|
if (Grub2 && (Array->Items != NULL)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
NewItem = OcFlexArrayAddItem (Array);
|
|
if (NewItem == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
*NewItem = AllocateCopyPool (AsciiStrSize (Value), Value);
|
|
if (*NewItem == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
InternalProcessLoaderEntryFile (
|
|
IN CONST CHAR16 *FileName,
|
|
IN OUT CHAR8 *Content,
|
|
OUT LOADER_ENTRY *Entry,
|
|
IN CONST BOOLEAN Grub2
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Pos;
|
|
CHAR8 *Key;
|
|
CHAR8 *Value;
|
|
|
|
ASSERT (Content != NULL);
|
|
|
|
Status = EFI_SUCCESS;
|
|
Pos = 0;
|
|
|
|
//
|
|
// Grub2 blscfg module uses first only (even for options,
|
|
// which should allow more than one according to BL Spec).
|
|
// systemd-boot uses multiple (concatenated with a space)
|
|
// for options, and last only for everything which should
|
|
// have only one.
|
|
// Both use multiple for initrd.
|
|
//
|
|
while (TRUE) {
|
|
Status = GetLoaderEntryLine (FileName, Content, &Pos, &Key, &Value);
|
|
if (Status == EFI_NOT_FOUND) {
|
|
break;
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
if (AsciiStrCmp (Key, "title") == 0) {
|
|
Status = EntryCopySingleValue (Grub2, &Entry->Title, Value);
|
|
} else if (AsciiStrCmp (Key, "version") == 0) {
|
|
Status = EntryCopySingleValue (Grub2, &Entry->Version, Value);
|
|
} else if (AsciiStrCmp (Key, "linux") == 0) {
|
|
Status = EntryCopySingleValue (Grub2, &Entry->Linux, Value);
|
|
} else if (AsciiStrCmp (Key, "options") == 0) {
|
|
Status = EntryCopyMultipleValue (Grub2, Entry->Options, Value);
|
|
} else if (AsciiStrCmp (Key, "initrd") == 0) {
|
|
Status = EntryCopyMultipleValue (FALSE, Entry->Initrds, Value);
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
ExpandReplaceOptions (
|
|
IN OUT LOADER_ENTRY *Entry
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR8 **Options;
|
|
CHAR8 *NewOptions;
|
|
GRUB_VAR *DefaultOptionsVar;
|
|
|
|
if (Entry->Options->Count > 0) {
|
|
Status = InternalExpandGrubVarsForArray (Entry->Options);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
} else {
|
|
//
|
|
// This is what grub2 blscfg does if there are no options.
|
|
//
|
|
DefaultOptionsVar = InternalGetGrubVar ("default_kernelopts");
|
|
if (DefaultOptionsVar != NULL) {
|
|
if (DefaultOptionsVar->Errors != 0) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Unusable grub var $%a - 0x%x\n", "default_kernelopts", DefaultOptionsVar->Errors));
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
DEBUG ((DEBUG_INFO, "LNX: Using $%a\n", "default_kernelopts"));
|
|
|
|
//
|
|
// Blscfg expands $default_kernelopts, and we expand it even
|
|
// if !HasVars since we need the string to be realloced.
|
|
//
|
|
Status = InternalExpandGrubVars (DefaultOptionsVar->Value, &NewOptions);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Options = OcFlexArrayAddItem (Entry->Options);
|
|
if (Options == NULL) {
|
|
FreePool (NewOptions);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
*Options = NewOptions;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Expand grub vars, and expand multiple initrds per line to multiple initrd lines.
|
|
// Do this before checking for files, etc.
|
|
//
|
|
STATIC
|
|
EFI_STATUS
|
|
ExpandInitrds (
|
|
IN OUT LOADER_ENTRY *Entry
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN OptionsIndex;
|
|
CHAR8 **Options;
|
|
OC_FLEX_ARRAY *ExpandedInitrds;
|
|
OC_FLEX_ARRAY *SplitInitrds;
|
|
UINTN SplitInitrdsIndex;
|
|
|
|
if (Entry->Initrds->Count == 0) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
Status = InternalExpandGrubVarsForArray (Entry->Initrds);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
ExpandedInitrds = OcFlexArrayInit (sizeof (CHAR8 *), OcFlexArrayFreePointerItem);
|
|
|
|
for (OptionsIndex = 0; OptionsIndex < Entry->Initrds->Count; OptionsIndex++) {
|
|
Options = OcFlexArrayItemAt (Entry->Initrds, OptionsIndex);
|
|
|
|
Status = OcParseVars (*Options, &SplitInitrds, OcStringFormatAscii, TRUE);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
for (SplitInitrdsIndex = 0; SplitInitrdsIndex < SplitInitrds->Count; SplitInitrdsIndex++) {
|
|
Status = EntryCopyMultipleValue (FALSE, ExpandedInitrds, OcParsedVarsItemAt (SplitInitrds, SplitInitrdsIndex)->Ascii.Value);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
OcFlexArrayFree (&SplitInitrds);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayFree (&ExpandedInitrds);
|
|
return Status;
|
|
}
|
|
|
|
ASSERT (ExpandedInitrds->Count >= Entry->Initrds->Count);
|
|
|
|
OcFlexArrayFree (&Entry->Initrds);
|
|
Entry->Initrds = ExpandedInitrds;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
DoFilterLoaderEntry (
|
|
EFI_FILE_HANDLE Directory,
|
|
EFI_FILE_INFO *FileInfo,
|
|
UINTN FileInfoSize,
|
|
VOID *Context OPTIONAL
|
|
)
|
|
{
|
|
if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Skip ".*" and case sensitive "auto-*" files,
|
|
// follows systemd-boot logic.
|
|
//
|
|
if ((FileInfo->FileName[0] == L'.') ||
|
|
(StrnCmp (FileInfo->FileName, BLSPEC_PREFIX_AUTO, L_STR_LEN (BLSPEC_PREFIX_AUTO)) == 0))
|
|
{
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if (!OcUnicodeEndsWith (FileInfo->FileName, BLSPEC_SUFFIX_CONF, TRUE)) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Works for vmlinuz-{version} and {machine-id}-{version}.conf.
|
|
// If blspec/blscfg style then we want .conf files with the same machine-id
|
|
// to count as alternate kernels of the same install; but there might be
|
|
// different installs, and the same kernel might be on different installs;
|
|
// so {machine-id} is good as the shared id (and it would not be sufficient
|
|
// to just use {version} when generating a non-shared id).
|
|
// If autodetect, we assume there is only one install in the directory,
|
|
// so {vmlinuz} is good as the shared id.
|
|
//
|
|
EFI_STATUS
|
|
InternalIdVersionFromFileName (
|
|
IN OUT LOADER_ENTRY *Entry,
|
|
IN CHAR16 *FileName
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN NumCopied;
|
|
|
|
CHAR16 *Split;
|
|
CHAR16 *IdEnd;
|
|
CHAR16 *VersionStart;
|
|
CHAR16 *VersionEnd;
|
|
CHAR8 *OcId;
|
|
CHAR8 *Version;
|
|
|
|
ASSERT (Entry != NULL);
|
|
ASSERT (FileName != NULL);
|
|
|
|
Split = NULL;
|
|
|
|
Split = OcStrChr (FileName, L'-');
|
|
|
|
VersionEnd = &FileName[StrLen (FileName)];
|
|
if (OcUnicodeEndsWith (FileName, BLSPEC_SUFFIX_CONF, TRUE)) {
|
|
VersionEnd -= L_STR_LEN (BLSPEC_SUFFIX_CONF);
|
|
}
|
|
|
|
//
|
|
// Will not happen with sane filenames.
|
|
//
|
|
if ((Split != NULL) && ((Split == FileName) || (VersionEnd == Split + 1))) {
|
|
Split = NULL;
|
|
}
|
|
|
|
if (Split == NULL) {
|
|
VersionStart = FileName;
|
|
IdEnd = VersionEnd;
|
|
} else {
|
|
VersionStart = Split + 1;
|
|
|
|
if ((gLinuxBootFlags & LINUX_BOOT_USE_LATEST) != 0) {
|
|
IdEnd = Split;
|
|
} else {
|
|
IdEnd = VersionEnd;
|
|
}
|
|
}
|
|
|
|
OcId = AllocatePool (IdEnd - FileName + 1);
|
|
if (OcId == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = UnicodeStrnToAsciiStrS (FileName, IdEnd - FileName, OcId, IdEnd - FileName + 1, &NumCopied);
|
|
ASSERT_EFI_ERROR (Status);
|
|
ASSERT (NumCopied == (UINTN)(IdEnd - FileName));
|
|
|
|
Entry->OcId = OcId;
|
|
|
|
if (Entry->Version == NULL) {
|
|
Version = AllocatePool (VersionEnd - VersionStart + 1);
|
|
if (Version == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = UnicodeStrnToAsciiStrS (VersionStart, VersionEnd - VersionStart, Version, VersionEnd - VersionStart + 1, &NumCopied);
|
|
ASSERT_EFI_ERROR (Status);
|
|
ASSERT (NumCopied == (UINTN)(VersionEnd - VersionStart));
|
|
|
|
Entry->Version = Version;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// We do not need to warn about missing files: OC warns about
|
|
// missing bootable file (vmlinuz) and kernel stops and warns
|
|
// about missing initrd. However, some linuxes (e.g. Endless)
|
|
// make the filepaths specified in an entry relative to /boot,
|
|
// not relative to / as it should be, so we have to check which
|
|
// it is. Only for this reason, we end up warning here if we
|
|
// can't find it at all.
|
|
//
|
|
STATIC
|
|
EFI_STATUS
|
|
FindLoaderFile (
|
|
IN CONST EFI_FILE_HANDLE Directory,
|
|
IN CONST CHAR16 *DirName,
|
|
IN OUT CHAR8 **FileName
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL *File;
|
|
UINTN FileNameLen;
|
|
UINTN DirNameLen;
|
|
CHAR16 *Path;
|
|
UINTN MaxPathSize;
|
|
|
|
ASSERT (DirName != NULL);
|
|
ASSERT (FileName != NULL);
|
|
ASSERT (*FileName != NULL);
|
|
|
|
FileNameLen = AsciiStrLen (*FileName);
|
|
DirNameLen = StrLen (DirName);
|
|
|
|
MaxPathSize = (DirNameLen + FileNameLen + 1) * sizeof (CHAR16);
|
|
|
|
Path = AllocatePool (MaxPathSize);
|
|
if (Path == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
UnicodeSPrintAsciiFormat (Path, MaxPathSize, "%a", *FileName);
|
|
UnicodeUefiSlashes (Path);
|
|
|
|
Status = OcSafeFileOpen (Directory, &File, Path, EFI_FILE_MODE_READ, 0);
|
|
if (!EFI_ERROR (Status)) {
|
|
File->Close (File);
|
|
FreePool (Path);
|
|
return Status;
|
|
}
|
|
|
|
if (DirNameLen <= 1) {
|
|
DEBUG ((DEBUG_WARN, "LNX: %a not found - %r\n", *FileName, Status));
|
|
FreePool (Path);
|
|
return Status;
|
|
}
|
|
|
|
UnicodeSPrintAsciiFormat (Path, MaxPathSize, "%s%a", DirName, *FileName);
|
|
UnicodeUefiSlashes (Path);
|
|
|
|
Status = OcSafeFileOpen (Directory, &File, Path, EFI_FILE_MODE_READ, 0);
|
|
if (!EFI_ERROR (Status)) {
|
|
//
|
|
// Found at 'wrong' location - re-use allocated path
|
|
//
|
|
File->Close (File);
|
|
AsciiSPrint ((CHAR8 *)Path, MaxPathSize, "%s%a", DirName, *FileName);
|
|
AsciiUnixSlashes ((CHAR8 *)Path);
|
|
FreePool (*FileName);
|
|
*FileName = (CHAR8 *)Path;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
DEBUG ((DEBUG_WARN, "LNX: %a not found (searched %s and %s) - %r\n", *FileName, ROOT_DIR, DirName, Status));
|
|
FreePool (Path);
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
HasRootOption (
|
|
IN OC_FLEX_ARRAY *Options
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
CHAR8 **Option;
|
|
CHAR8 *OptionCopy;
|
|
OC_FLEX_ARRAY *ParsedVars;
|
|
BOOLEAN HasRoot;
|
|
|
|
ASSERT (Options != NULL);
|
|
|
|
//
|
|
// TRUE on errors: do not attempt to add root.
|
|
//
|
|
for (Index = 0; Index < Options->Count; Index++) {
|
|
Option = OcFlexArrayItemAt (Options, Index);
|
|
ASSERT (Option != NULL);
|
|
ASSERT (*Option != NULL);
|
|
|
|
OptionCopy = AllocateCopyPool (AsciiStrSize (*Option), *Option);
|
|
if (OptionCopy == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
Status = OcParseVars (OptionCopy, &ParsedVars, OcStringFormatAscii, FALSE);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Error parsing Options[%u]=<%a> - %r\n", Index, *Option, Status));
|
|
FreePool (OptionCopy);
|
|
return TRUE;
|
|
}
|
|
|
|
HasRoot = OcHasParsedVar (ParsedVars, "root", OcStringFormatAscii);
|
|
|
|
FreePool (ParsedVars);
|
|
FreePool (OptionCopy);
|
|
|
|
if (HasRoot) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
STATIC
|
|
BOOLEAN
|
|
HasOstreeDir (
|
|
EFI_FILE_HANDLE Directory
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL *OstreeFile;
|
|
|
|
Status = OcSafeFileOpen (Directory, &OstreeFile, OSTREE_DIR, EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR (Status)) {
|
|
return FALSE;
|
|
}
|
|
|
|
Status = OcEnsureDirectoryFile (OstreeFile, TRUE);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_WARN, "LNX: %s found but not a %a - %r\n", OSTREE_DIR, "directory", Status));
|
|
}
|
|
|
|
OstreeFile->Close (OstreeFile);
|
|
|
|
return !EFI_ERROR (Status);
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
DoProcessLoaderEntry (
|
|
EFI_FILE_HANDLE Directory,
|
|
EFI_FILE_INFO *FileInfo,
|
|
UINTN FileInfoSize,
|
|
VOID *Context OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CHAR8 *Content;
|
|
LOADER_ENTRY *Entry;
|
|
CHAR16 SaveChar;
|
|
CHAR16 *DirName;
|
|
CHAR8 **Initrd;
|
|
UINTN Index;
|
|
|
|
ASSERT (Context != NULL);
|
|
DirName = Context;
|
|
|
|
if (FileInfoSize > MAX_LOADER_ENTRY_FILE_INFO_SIZE) {
|
|
SaveChar = FileInfo->FileName[MAX_LOADER_ENTRY_NAME_LEN];
|
|
FileInfo->FileName[MAX_LOADER_ENTRY_NAME_LEN] = CHAR_NULL;
|
|
DEBUG ((DEBUG_WARN, "LNX: Entry filename overlong: %s...\n", FileInfo->FileName));
|
|
FileInfo->FileName[MAX_LOADER_ENTRY_NAME_LEN] = SaveChar;
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if (FileInfo->FileSize > MAX_LOADER_ENTRY_FILE_SIZE) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Entry file size overlong: %s\n", FileInfo->FileName));
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
DEBUG ((
|
|
(gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO,
|
|
"LNX: Reading %s...\n",
|
|
FileInfo->FileName
|
|
));
|
|
|
|
Content = OcReadFileFromDirectory (Directory, FileInfo->FileName, NULL, 0);
|
|
if (Content == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Entry = InternalAllocateLoaderEntry ();
|
|
if (Entry == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = InternalProcessLoaderEntryFile (FileInfo->FileName, Content, Entry, mIsGrub2);
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return Status;
|
|
}
|
|
|
|
if (Entry->Linux == NULL) {
|
|
DEBUG ((
|
|
(gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO,
|
|
"LNX: No linux line, ignoring\n"
|
|
));
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
if (mIsGrub2) {
|
|
Status = ExpandReplaceOptions (Entry);
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = ExpandInitrds (Entry);
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check all files exist, see comment on FindLoaderFile.
|
|
//
|
|
Status = FindLoaderFile (Directory, DirName, &Entry->Linux);
|
|
if (!EFI_ERROR (Status)) {
|
|
for (Index = 0; Index < Entry->Initrds->Count; Index++) {
|
|
Initrd = OcFlexArrayItemAt (Entry->Initrds, Index);
|
|
Status = FindLoaderFile (Directory, DirName, Initrd);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Need to understand other reasons to apply this fix (if any),
|
|
// so not automatically applying unless we recognise the layout.
|
|
//
|
|
if ( ((gLinuxBootFlags & LINUX_BOOT_ALLOW_CONF_AUTO_ROOT) != 0)
|
|
&& !HasRootOption (Entry->Options))
|
|
{
|
|
if (!HasOstreeDir (Directory)) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Missing root option, %s %afound - %afixing\n", OSTREE_DIR, "not ", "not "));
|
|
} else {
|
|
DEBUG ((DEBUG_INFO, "LNX: Missing root option, %s %afound - %afixing\n", OSTREE_DIR, "", ""));
|
|
Status = InsertRootOption (Entry->Options);
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Id, and version if not already set within .conf data, from filename.
|
|
//
|
|
Status = InternalIdVersionFromFileName (Entry, FileInfo->FileName);
|
|
if (EFI_ERROR (Status)) {
|
|
OcFlexArrayDiscardItem (gLoaderEntries, TRUE);
|
|
return Status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
ProcessLoaderEntry (
|
|
EFI_FILE_HANDLE Directory,
|
|
EFI_FILE_INFO *FileInfo,
|
|
UINTN FileInfoSize,
|
|
VOID *Context OPTIONAL
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
Status = DoFilterLoaderEntry (Directory, FileInfo, FileInfoSize, Context);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = DoProcessLoaderEntry (Directory, FileInfo, FileInfoSize, Context);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_WARN, "LNX: Error processing %s - %r\n", FileInfo->FileName, Status));
|
|
|
|
//
|
|
// Continue to use earlier or later .conf files which we can process -
|
|
// more chance of a way to boot into Linux for end user.
|
|
//
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
FixTuneDVars (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
|
|
STATIC_ASSERT (ARRAY_SIZE (mTuneDVars) > 0, "No TuneD vars to set");
|
|
|
|
Status = EFI_SUCCESS;
|
|
for (Index = 0; Index < ARRAY_SIZE (mTuneDVars); Index++) {
|
|
if (InternalGetGrubVar (mTuneDVars[Index]) == NULL) {
|
|
Status = InternalSetGrubVar (mTuneDVars[Index], "", VAR_ERR_NONE);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
ScanLoaderEntriesAtDirectory (
|
|
IN EFI_FILE_PROTOCOL *RootDirectory,
|
|
IN CHAR16 *DirName,
|
|
OUT OC_PICKER_ENTRY **Entries,
|
|
OUT UINTN *NumEntries
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL *EntriesDirectory;
|
|
CHAR8 *GrubCfg;
|
|
CHAR8 *GrubEnv;
|
|
GRUB_VAR *EarlyInitrdVar;
|
|
|
|
Status = OcSafeFileOpen (RootDirectory, &EntriesDirectory, LOADER_ENTRIES_DIR, EFI_FILE_MODE_READ, 0);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = EFI_SUCCESS;
|
|
GrubEnv = NULL;
|
|
mIsGrub2 = FALSE;
|
|
gLoaderEntries = NULL;
|
|
|
|
//
|
|
// Treat /loader/entries as being GRUB2+blscfg style if /grub2/grub.cfg exists.
|
|
//
|
|
GrubCfg = OcReadFileFromDirectory (RootDirectory, GRUB2_GRUB_CFG, NULL, 0);
|
|
if (GrubCfg == NULL) {
|
|
DEBUG ((DEBUG_INFO, "LNX: %s not found\n", GRUB2_GRUB_CFG));
|
|
} else {
|
|
mIsGrub2 = TRUE;
|
|
Status = InternalInitGrubVars ();
|
|
if (!EFI_ERROR (Status)) {
|
|
//
|
|
// Read grubenv first, since vars in grub.cfg should overwrite it.
|
|
//
|
|
GrubEnv = OcReadFileFromDirectory (RootDirectory, GRUB2_GRUBENV, NULL, GRUB2_GRUBENV_SIZE);
|
|
if (GrubEnv == NULL) {
|
|
DEBUG ((DEBUG_WARN, "LNX: %s not found\n", GRUB2_GRUBENV));
|
|
} else {
|
|
DEBUG ((
|
|
(gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO,
|
|
"LNX: Reading %s\n",
|
|
GRUB2_GRUBENV
|
|
));
|
|
Status = InternalProcessGrubEnv (GrubEnv, GRUB2_GRUBENV_SIZE);
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
(gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO,
|
|
"LNX: Reading %s\n",
|
|
GRUB2_GRUB_CFG
|
|
));
|
|
Status = InternalProcessGrubCfg (GrubCfg);
|
|
}
|
|
|
|
if ( !EFI_ERROR (Status)
|
|
&& ((gLinuxBootFlags & LINUX_BOOT_FIX_TUNED) != 0))
|
|
{
|
|
DEBUG ((
|
|
(gLinuxBootFlags & LINUX_BOOT_LOG_VERBOSE) == 0 ? DEBUG_VERBOSE : DEBUG_INFO,
|
|
"LNX: Fix TuneD vars\n"
|
|
));
|
|
Status = FixTuneDVars ();
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are grub2 and $early_initrd exists, then warn and halt (blscfg logic is to use it).
|
|
// Would not be hard to implement in ExpandInitrds if required. This is a space separated list
|
|
// of filenames to use first as initrds.
|
|
//
|
|
if (!EFI_ERROR (Status)) {
|
|
if (mIsGrub2) {
|
|
EarlyInitrdVar = InternalGetGrubVar ("early_initrd");
|
|
if ((EarlyInitrdVar != NULL) &&
|
|
(EarlyInitrdVar->Value != NULL) &&
|
|
(EarlyInitrdVar->Value[0] != '\0')
|
|
)
|
|
{
|
|
DEBUG ((DEBUG_INFO, "LNX: grub var $%a is present but currently unsupported - aborting\n", "early_initrd"));
|
|
Status = EFI_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
gLoaderEntries = OcFlexArrayInit (sizeof (LOADER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM)InternalFreeLoaderEntry);
|
|
if (gLoaderEntries == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
} else {
|
|
Status = OcScanDirectory (EntriesDirectory, ProcessLoaderEntry, DirName);
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
ASSERT (gLoaderEntries->Count > 0);
|
|
|
|
Status = InternalConvertLoaderEntriesToBootEntries (
|
|
RootDirectory,
|
|
Entries,
|
|
NumEntries
|
|
);
|
|
}
|
|
|
|
OcFlexArrayFree (&gLoaderEntries);
|
|
}
|
|
|
|
InternalFreeGrubVars ();
|
|
|
|
if (GrubEnv != NULL) {
|
|
FreePool (GrubEnv);
|
|
}
|
|
|
|
if (GrubCfg != NULL) {
|
|
FreePool (GrubCfg);
|
|
}
|
|
|
|
EntriesDirectory->Close (EntriesDirectory);
|
|
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
DoScanLoaderEntries (
|
|
IN EFI_FILE_PROTOCOL *Directory,
|
|
IN CHAR16 *DirName,
|
|
OUT OC_PICKER_ENTRY **Entries,
|
|
OUT UINTN *NumEntries
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
Status = ScanLoaderEntriesAtDirectory (Directory, DirName, Entries, NumEntries);
|
|
|
|
DEBUG ((
|
|
(EFI_ERROR (Status) && Status != EFI_NOT_FOUND) ? DEBUG_WARN : DEBUG_INFO,
|
|
"LNX: ScanLoaderEntries %s - %r\n",
|
|
DirName,
|
|
Status
|
|
));
|
|
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
InternalScanLoaderEntries (
|
|
IN EFI_FILE_PROTOCOL *RootDirectory,
|
|
OUT OC_PICKER_ENTRY **Entries,
|
|
OUT UINTN *NumEntries
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL *AdditionalScanDirectory;
|
|
|
|
Status = DoScanLoaderEntries (RootDirectory, ROOT_DIR, Entries, NumEntries);
|
|
if (EFI_ERROR (Status)) {
|
|
Status = OcSafeFileOpen (RootDirectory, &AdditionalScanDirectory, BOOT_DIR, EFI_FILE_MODE_READ, 0);
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = DoScanLoaderEntries (AdditionalScanDirectory, BOOT_DIR, Entries, NumEntries);
|
|
AdditionalScanDirectory->Close (AdditionalScanDirectory);
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
DoConvertLoaderEntriesToBootEntries (
|
|
OUT OC_PICKER_ENTRY **Entries,
|
|
OUT UINTN *NumEntries
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
UINTN OptionsIndex;
|
|
LOADER_ENTRY *Entry;
|
|
OC_FLEX_ARRAY *PickerEntries;
|
|
OC_PICKER_ENTRY *PickerEntry;
|
|
OC_ASCII_STRING_BUFFER *StringBuffer;
|
|
CHAR8 **Options;
|
|
UINTN OptionsLength;
|
|
|
|
StringBuffer = NULL;
|
|
|
|
PickerEntries = OcFlexArrayInit (sizeof (OC_PICKER_ENTRY), (OC_FLEX_ARRAY_FREE_ITEM)InternalFreePickerEntry);
|
|
if (PickerEntries == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = EFI_SUCCESS;
|
|
for (Index = 0; Index < gLoaderEntries->Count; Index++) {
|
|
Entry = OcFlexArrayItemAt (gLoaderEntries, Index);
|
|
|
|
PickerEntry = OcFlexArrayAddItem (PickerEntries);
|
|
if (PickerEntry == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
PickerEntry->Path = AllocateCopyPool (AsciiStrSize (Entry->Linux), Entry->Linux);
|
|
if (PickerEntry->Path == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Arguments.
|
|
//
|
|
StringBuffer = OcAsciiStringBufferInit ();
|
|
if (StringBuffer == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
for (OptionsIndex = 0; OptionsIndex < Entry->Initrds->Count; OptionsIndex++) {
|
|
Options = OcFlexArrayItemAt (Entry->Initrds, OptionsIndex);
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, "initrd=");
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, *Options);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, " ");
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
for (OptionsIndex = 0; OptionsIndex < Entry->Options->Count; OptionsIndex++) {
|
|
Options = OcFlexArrayItemAt (Entry->Options, OptionsIndex);
|
|
OptionsLength = AsciiStrLen (*Options);
|
|
ASSERT (OptionsLength != 0);
|
|
if (OptionsLength == 0) {
|
|
continue;
|
|
}
|
|
|
|
if ((*Options)[OptionsLength - 1] == ' ') {
|
|
--OptionsLength;
|
|
}
|
|
|
|
Status = OcAsciiStringBufferAppendN (StringBuffer, *Options, OptionsLength);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
if (OptionsIndex < Entry->Options->Count - 1) {
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, " ");
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
PickerEntry->Arguments = OcAsciiStringBufferFreeContainer (&StringBuffer);
|
|
|
|
//
|
|
// Name.
|
|
//
|
|
StringBuffer = OcAsciiStringBufferInit ();
|
|
if (StringBuffer == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
if ((gLinuxBootFlags & LINUX_BOOT_ADD_DEBUG_INFO) != 0) {
|
|
//
|
|
// Show file system type and enough of PARTUUID to help distinguish one partition from another.
|
|
//
|
|
Status = OcAsciiStringBufferSPrint (StringBuffer, "%a-%08x", gFileSystemType, gPartuuid.Data1);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
OcAsciiToLower (&StringBuffer->String[StringBuffer->StringLength] - sizeof (gPartuuid.Data1) * 2);
|
|
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, ": ");
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Status = OcAsciiStringBufferAppend (StringBuffer, Entry->Title);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
|
|
PickerEntry->Name = OcAsciiStringBufferFreeContainer (&StringBuffer);
|
|
|
|
ASSERT (Entry->OcFlavour != NULL);
|
|
PickerEntry->Flavour = AllocateCopyPool (AsciiStrSize (Entry->OcFlavour), Entry->OcFlavour);
|
|
if (PickerEntry->Flavour == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
ASSERT (Entry->OcId != NULL);
|
|
PickerEntry->Id = AllocateCopyPool (AsciiStrSize (Entry->OcId), Entry->OcId);
|
|
if (PickerEntry->Id == NULL) {
|
|
Status = EFI_OUT_OF_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
PickerEntry->Auxiliary = Entry->OcAuxiliary;
|
|
|
|
PickerEntry->RealPath = TRUE;
|
|
PickerEntry->TextMode = FALSE;
|
|
PickerEntry->Tool = FALSE;
|
|
}
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
if (StringBuffer != NULL) {
|
|
OcAsciiStringBufferFree (&StringBuffer);
|
|
}
|
|
|
|
OcFlexArrayFree (&PickerEntries);
|
|
} else {
|
|
ASSERT (StringBuffer == NULL);
|
|
OcFlexArrayFreeContainer (&PickerEntries, (VOID **)Entries, NumEntries);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
AppendVersion (
|
|
LOADER_ENTRY *Entry
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
OC_ASCII_STRING_BUFFER *StringBuffer;
|
|
|
|
ASSERT (Entry->Title != NULL);
|
|
|
|
if ((Entry->Version == NULL) || (AsciiStrStr (Entry->Title, Entry->Version) != NULL)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
StringBuffer = OcAsciiStringBufferInit ();
|
|
|
|
Status = OcAsciiStringBufferSPrint (StringBuffer, "%a (%a)", Entry->Title, Entry->Version);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
FreePool (Entry->Title);
|
|
Entry->Title = OcAsciiStringBufferFreeContainer (&StringBuffer);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
AppendVersions (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
LOADER_ENTRY *Entry;
|
|
|
|
if (gPickerContext->HideAuxiliary && ((gLinuxBootFlags & LINUX_BOOT_USE_LATEST) != 0)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
for (Index = 0; Index < gLoaderEntries->Count; Index++) {
|
|
Entry = OcFlexArrayItemAt (gLoaderEntries, Index);
|
|
|
|
Status = AppendVersion (Entry);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
DisambiguateDuplicates (
|
|
VOID
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN MatchIndex;
|
|
LOADER_ENTRY *Entry;
|
|
LOADER_ENTRY *MatchEntry;
|
|
|
|
//
|
|
// This check should not be strictly necessary, since there shouldn't
|
|
// (normally) be any duplicate ids if this flag is not set.
|
|
//
|
|
if ((gLinuxBootFlags & LINUX_BOOT_USE_LATEST) == 0) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
for (Index = 0; Index < gLoaderEntries->Count - 1; Index++) {
|
|
Entry = OcFlexArrayItemAt (gLoaderEntries, Index);
|
|
|
|
if (Entry->DuplicateIdScanned) {
|
|
continue;
|
|
}
|
|
|
|
for (MatchIndex = Index + 1; MatchIndex < gLoaderEntries->Count; MatchIndex++) {
|
|
MatchEntry = OcFlexArrayItemAt (gLoaderEntries, MatchIndex);
|
|
|
|
if (!MatchEntry->DuplicateIdScanned) {
|
|
if (AsciiStrCmp (Entry->OcId, MatchEntry->OcId) == 0) {
|
|
//
|
|
// Everything but the first id duplicate becomes auxiliary.
|
|
//
|
|
MatchEntry->OcAuxiliary = TRUE;
|
|
MatchEntry->DuplicateIdScanned = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
EntryApplyDefaults (
|
|
IN EFI_FILE_PROTOCOL *RootDirectory,
|
|
IN LOADER_ENTRY *Entry
|
|
)
|
|
{
|
|
CHAR8 *Variant;
|
|
UINTN Length;
|
|
UINTN NumPrinted;
|
|
|
|
Variant = NULL;
|
|
|
|
if (Entry->Title != NULL) {
|
|
Variant = ExtractVariantFrom (Entry->Title);
|
|
}
|
|
|
|
ASSERT (Entry->OcFlavour == NULL);
|
|
|
|
if (Variant != NULL) {
|
|
Length = AsciiStrLen (Variant) + L_STR_LEN ("Linux") + 1;
|
|
|
|
Entry->OcFlavour = AllocatePool (Length + 1);
|
|
if (Entry->OcFlavour == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
NumPrinted = AsciiSPrint (Entry->OcFlavour, Length + 1, "%a:%a", Variant, "Linux");
|
|
ASSERT (NumPrinted == Length);
|
|
} else {
|
|
Variant = "Linux";
|
|
Entry->OcFlavour = AllocateCopyPool (AsciiStrSize (Variant), Variant);
|
|
if (Entry->OcFlavour == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
if (Entry->Title == NULL) {
|
|
Entry->Title = AllocateCopyPool (AsciiStrSize (Variant), Variant);
|
|
if (Entry->Title == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
ApplyDefaults (
|
|
IN EFI_FILE_PROTOCOL *RootDirectory
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Index;
|
|
LOADER_ENTRY *Entry;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
for (Index = 0; Index < gLoaderEntries->Count; Index++) {
|
|
Entry = OcFlexArrayItemAt (gLoaderEntries, Index);
|
|
|
|
Status = EntryApplyDefaults (RootDirectory, Entry);
|
|
if (EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Also use for loader entries generated by autodetect.
|
|
//
|
|
EFI_STATUS
|
|
InternalConvertLoaderEntriesToBootEntries (
|
|
IN EFI_FILE_PROTOCOL *RootDirectory,
|
|
OUT OC_PICKER_ENTRY **Entries,
|
|
OUT UINTN *NumEntries
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
//
|
|
// Sort entries by version descending.
|
|
//
|
|
PerformQuickSort (gLoaderEntries->Items, gLoaderEntries->Count, gLoaderEntries->ItemSize, InternalReverseVersionCompare);
|
|
|
|
//
|
|
// Autodetect good title and flavour, if needed.
|
|
//
|
|
Status = ApplyDefaults (RootDirectory);
|
|
|
|
//
|
|
// Always append version to titles when !HideAuxiliary.
|
|
// We previously implemented a pure-BLSpec process of disambiguating by appending version
|
|
// only if the title is ambiguous with others on the same partition, but it is more natural
|
|
// to do it always.
|
|
//
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = AppendVersions ();
|
|
}
|
|
|
|
//
|
|
// Make duplicates by id after the first into auxiliary entries.
|
|
//
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = DisambiguateDuplicates ();
|
|
}
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
Status = DoConvertLoaderEntriesToBootEntries (
|
|
Entries,
|
|
NumEntries
|
|
);
|
|
}
|
|
|
|
return Status;
|
|
}
|