/** @file | |
Provides interface to shell internal functions for shell commands. | |
Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR> | |
(C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P.<BR> | |
(C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "UefiShellCommandLib.h" | |
// STATIC local variables | |
STATIC SHELL_COMMAND_INTERNAL_LIST_ENTRY mCommandList; | |
STATIC SCRIPT_FILE_LIST mScriptList; | |
STATIC ALIAS_LIST mAliasList; | |
STATIC BOOLEAN mEchoState; | |
STATIC BOOLEAN mExitRequested; | |
STATIC UINT64 mExitCode; | |
STATIC BOOLEAN mExitScript; | |
STATIC CHAR16 *mProfileList; | |
STATIC UINTN mProfileListSize; | |
STATIC UINTN mFsMaxCount = 0; | |
STATIC UINTN mBlkMaxCount = 0; | |
STATIC BUFFER_LIST mFileHandleList; | |
STATIC CONST CHAR8 Hex[] = { | |
'0', | |
'1', | |
'2', | |
'3', | |
'4', | |
'5', | |
'6', | |
'7', | |
'8', | |
'9', | |
'A', | |
'B', | |
'C', | |
'D', | |
'E', | |
'F' | |
}; | |
// global variables required by library class. | |
EFI_UNICODE_COLLATION_PROTOCOL *gUnicodeCollation = NULL; | |
SHELL_MAP_LIST gShellMapList; | |
SHELL_MAP_LIST *gShellCurMapping = NULL; | |
CONST CHAR16* SupportLevel[] = { | |
L"Minimal", | |
L"Scripting", | |
L"Basic", | |
L"Interactive" | |
}; | |
/** | |
Function to make sure that the global protocol pointers are valid. | |
must be called after constructor before accessing the pointers. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
CommandInit( | |
VOID | |
) | |
{ | |
UINTN NumHandles; | |
EFI_HANDLE *Handles; | |
EFI_UNICODE_COLLATION_PROTOCOL *Uc; | |
CHAR8 *BestLanguage; | |
UINTN Index; | |
EFI_STATUS Status; | |
CHAR8 *PlatformLang; | |
if (gUnicodeCollation == NULL) { | |
GetEfiGlobalVariable2 (EFI_PLATFORM_LANG_VARIABLE_NAME, (VOID**)&PlatformLang, NULL); | |
Status = gBS->LocateHandleBuffer ( | |
ByProtocol, | |
&gEfiUnicodeCollation2ProtocolGuid, | |
NULL, | |
&NumHandles, | |
&Handles | |
); | |
if (EFI_ERROR (Status)) { | |
NumHandles = 0; | |
Handles = NULL; | |
} | |
for (Index = 0; Index < NumHandles; Index++) { | |
// | |
// Open Unicode Collation Protocol | |
// | |
Status = gBS->OpenProtocol ( | |
Handles[Index], | |
&gEfiUnicodeCollation2ProtocolGuid, | |
(VOID **) &Uc, | |
gImageHandle, | |
NULL, | |
EFI_OPEN_PROTOCOL_GET_PROTOCOL | |
); | |
if (EFI_ERROR (Status)) { | |
continue; | |
} | |
// | |
// Without clue provided use the first Unicode Collation2 protocol. | |
// This may happen when PlatformLang is NULL or when no installed Unicode | |
// Collation2 protocol instance supports PlatformLang. | |
// | |
if (gUnicodeCollation == NULL) { | |
gUnicodeCollation = Uc; | |
} | |
if (PlatformLang == NULL) { | |
break; | |
} | |
// | |
// Find the best matching matching language from the supported languages | |
// of Unicode Collation2 protocol. | |
// | |
BestLanguage = GetBestLanguage ( | |
Uc->SupportedLanguages, | |
FALSE, | |
PlatformLang, | |
NULL | |
); | |
if (BestLanguage != NULL) { | |
FreePool (BestLanguage); | |
gUnicodeCollation = Uc; | |
break; | |
} | |
} | |
if (Handles != NULL) { | |
FreePool (Handles); | |
} | |
if (PlatformLang != NULL) { | |
FreePool (PlatformLang); | |
} | |
} | |
return (gUnicodeCollation == NULL) ? EFI_UNSUPPORTED : EFI_SUCCESS; | |
} | |
/** | |
Constructor for the Shell Command library. | |
Initialize the library and determine if the underlying is a UEFI Shell 2.0 or an EFI shell. | |
@param ImageHandle the image handle of the process | |
@param SystemTable the EFI System Table pointer | |
@retval EFI_SUCCESS the initialization was complete sucessfully | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ShellCommandLibConstructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
InitializeListHead(&gShellMapList.Link); | |
InitializeListHead(&mCommandList.Link); | |
InitializeListHead(&mAliasList.Link); | |
InitializeListHead(&mScriptList.Link); | |
InitializeListHead(&mFileHandleList.Link); | |
mEchoState = TRUE; | |
mExitRequested = FALSE; | |
mExitScript = FALSE; | |
mProfileListSize = 0; | |
mProfileList = NULL; | |
Status = CommandInit (); | |
if (EFI_ERROR (Status)) { | |
return EFI_DEVICE_ERROR; | |
} | |
return (RETURN_SUCCESS); | |
} | |
/** | |
Frees list of file handles. | |
@param[in] List The list to free. | |
**/ | |
VOID | |
FreeFileHandleList ( | |
IN BUFFER_LIST *List | |
) | |
{ | |
BUFFER_LIST *BufferListEntry; | |
if (List == NULL){ | |
return; | |
} | |
// | |
// enumerate through the buffer list and free all memory | |
// | |
for ( BufferListEntry = ( BUFFER_LIST *)GetFirstNode(&List->Link) | |
; !IsListEmpty (&List->Link) | |
; BufferListEntry = (BUFFER_LIST *)GetFirstNode(&List->Link) | |
){ | |
RemoveEntryList(&BufferListEntry->Link); | |
ASSERT(BufferListEntry->Buffer != NULL); | |
SHELL_FREE_NON_NULL(((SHELL_COMMAND_FILE_HANDLE*)(BufferListEntry->Buffer))->Path); | |
SHELL_FREE_NON_NULL(BufferListEntry->Buffer); | |
SHELL_FREE_NON_NULL(BufferListEntry); | |
} | |
} | |
/** | |
Destructor for the library. free any resources. | |
@param ImageHandle the image handle of the process | |
@param SystemTable the EFI System Table pointer | |
@retval RETURN_SUCCESS this function always returns success | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ShellCommandLibDestructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
ALIAS_LIST *Node2; | |
SCRIPT_FILE_LIST *Node3; | |
SHELL_MAP_LIST *MapNode; | |
// | |
// enumerate throught the list and free all the memory | |
// | |
while (!IsListEmpty (&mCommandList.Link)) { | |
Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode(&mCommandList.Link); | |
RemoveEntryList(&Node->Link); | |
SHELL_FREE_NON_NULL(Node->CommandString); | |
FreePool(Node); | |
DEBUG_CODE(Node = NULL;); | |
} | |
// | |
// enumerate through the alias list and free all memory | |
// | |
while (!IsListEmpty (&mAliasList.Link)) { | |
Node2 = (ALIAS_LIST *)GetFirstNode(&mAliasList.Link); | |
RemoveEntryList(&Node2->Link); | |
SHELL_FREE_NON_NULL(Node2->CommandString); | |
SHELL_FREE_NON_NULL(Node2->Alias); | |
SHELL_FREE_NON_NULL(Node2); | |
DEBUG_CODE(Node2 = NULL;); | |
} | |
// | |
// enumerate throught the list and free all the memory | |
// | |
while (!IsListEmpty (&mScriptList.Link)) { | |
Node3 = (SCRIPT_FILE_LIST *)GetFirstNode(&mScriptList.Link); | |
RemoveEntryList(&Node3->Link); | |
DeleteScriptFileStruct(Node3->Data); | |
FreePool(Node3); | |
} | |
// | |
// enumerate throught the mappings list and free all the memory | |
// | |
if (!IsListEmpty(&gShellMapList.Link)) { | |
for (MapNode = (SHELL_MAP_LIST *)GetFirstNode(&gShellMapList.Link) | |
; !IsListEmpty (&gShellMapList.Link) | |
; MapNode = (SHELL_MAP_LIST *)GetFirstNode(&gShellMapList.Link) | |
){ | |
ASSERT(MapNode != NULL); | |
RemoveEntryList(&MapNode->Link); | |
SHELL_FREE_NON_NULL(MapNode->DevicePath); | |
SHELL_FREE_NON_NULL(MapNode->MapName); | |
SHELL_FREE_NON_NULL(MapNode->CurrentDirectoryPath); | |
FreePool(MapNode); | |
} | |
} | |
if (!IsListEmpty(&mFileHandleList.Link)){ | |
FreeFileHandleList(&mFileHandleList); | |
} | |
if (mProfileList != NULL) { | |
FreePool(mProfileList); | |
} | |
gUnicodeCollation = NULL; | |
gShellCurMapping = NULL; | |
return (RETURN_SUCCESS); | |
} | |
/** | |
Find a dynamic command protocol instance given a command name string. | |
@param CommandString the command name string | |
@return instance the command protocol instance, if dynamic command instance found | |
@retval NULL no dynamic command protocol instance found for name | |
**/ | |
CONST EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL * | |
ShellCommandFindDynamicCommand ( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE *CommandHandleList; | |
EFI_HANDLE *NextCommand; | |
EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *DynamicCommand; | |
CommandHandleList = GetHandleListByProtocol(&gEfiShellDynamicCommandProtocolGuid); | |
if (CommandHandleList == NULL) { | |
// | |
// not found or out of resources | |
// | |
return NULL; | |
} | |
for (NextCommand = CommandHandleList; *NextCommand != NULL; NextCommand++) { | |
Status = gBS->HandleProtocol( | |
*NextCommand, | |
&gEfiShellDynamicCommandProtocolGuid, | |
(VOID **)&DynamicCommand | |
); | |
if (EFI_ERROR(Status)) { | |
continue; | |
} | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)CommandString, | |
(CHAR16*)DynamicCommand->CommandName) == 0 | |
){ | |
FreePool(CommandHandleList); | |
return (DynamicCommand); | |
} | |
} | |
FreePool(CommandHandleList); | |
return (NULL); | |
} | |
/** | |
Checks if a command exists as a dynamic command protocol instance | |
@param[in] CommandString The command string to check for on the list. | |
**/ | |
BOOLEAN | |
ShellCommandDynamicCommandExists ( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
return (BOOLEAN) ((ShellCommandFindDynamicCommand(CommandString) != NULL)); | |
} | |
/** | |
Checks if a command is already on the internal command list. | |
@param[in] CommandString The command string to check for on the list. | |
**/ | |
BOOLEAN | |
ShellCommandIsCommandOnInternalList( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
// | |
// assert for NULL parameter | |
// | |
ASSERT(CommandString != NULL); | |
// | |
// check for the command | |
// | |
for ( Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode(&mCommandList.Link) | |
; !IsNull(&mCommandList.Link, &Node->Link) | |
; Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetNextNode(&mCommandList.Link, &Node->Link) | |
){ | |
ASSERT(Node->CommandString != NULL); | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)CommandString, | |
Node->CommandString) == 0 | |
){ | |
return (TRUE); | |
} | |
} | |
return (FALSE); | |
} | |
/** | |
Checks if a command exists, either internally or through the dynamic command protocol. | |
@param[in] CommandString The command string to check for on the list. | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellCommandIsCommandOnList( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
if (ShellCommandIsCommandOnInternalList(CommandString)) { | |
return TRUE; | |
} | |
return ShellCommandDynamicCommandExists(CommandString); | |
} | |
/** | |
Get the help text for a dynamic command. | |
@param[in] CommandString The command name. | |
@retval NULL No help text was found. | |
@return String of help text. Caller required to free. | |
**/ | |
CHAR16* | |
ShellCommandGetDynamicCommandHelp( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *DynamicCommand; | |
DynamicCommand = (EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *)ShellCommandFindDynamicCommand(CommandString); | |
if (DynamicCommand == NULL) { | |
return (NULL); | |
} | |
// | |
// TODO: how to get proper language? | |
// | |
return DynamicCommand->GetHelp(DynamicCommand, "en"); | |
} | |
/** | |
Get the help text for an internal command. | |
@param[in] CommandString The command name. | |
@retval NULL No help text was found. | |
@return String of help text. Caller reuiqred to free. | |
**/ | |
CHAR16* | |
ShellCommandGetInternalCommandHelp( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
// | |
// assert for NULL parameter | |
// | |
ASSERT(CommandString != NULL); | |
// | |
// check for the command | |
// | |
for ( Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode(&mCommandList.Link) | |
; !IsNull(&mCommandList.Link, &Node->Link) | |
; Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetNextNode(&mCommandList.Link, &Node->Link) | |
){ | |
ASSERT(Node->CommandString != NULL); | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)CommandString, | |
Node->CommandString) == 0 | |
){ | |
return (HiiGetString(Node->HiiHandle, Node->ManFormatHelp, NULL)); | |
} | |
} | |
return (NULL); | |
} | |
/** | |
Get the help text for a command. | |
@param[in] CommandString The command name. | |
@retval NULL No help text was found. | |
@return String of help text.Caller reuiqred to free. | |
**/ | |
CHAR16* | |
EFIAPI | |
ShellCommandGetCommandHelp ( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
CHAR16 *HelpStr; | |
HelpStr = ShellCommandGetInternalCommandHelp(CommandString); | |
if (HelpStr == NULL) { | |
HelpStr = ShellCommandGetDynamicCommandHelp(CommandString); | |
} | |
return HelpStr; | |
} | |
/** | |
Registers handlers of type SHELL_RUN_COMMAND and | |
SHELL_GET_MAN_FILENAME for each shell command. | |
If the ShellSupportLevel is greater than the value of the | |
PcdShellSupportLevel then return RETURN_UNSUPPORTED. | |
Registers the handlers specified by GetHelpInfoHandler and CommandHandler | |
with the command specified by CommandString. If the command named by | |
CommandString has already been registered, then return | |
RETURN_ALREADY_STARTED. | |
If there are not enough resources available to register the handlers then | |
RETURN_OUT_OF_RESOURCES is returned. | |
If CommandString is NULL, then ASSERT(). | |
If GetHelpInfoHandler is NULL, then ASSERT(). | |
If CommandHandler is NULL, then ASSERT(). | |
If ProfileName is NULL, then ASSERT(). | |
@param[in] CommandString Pointer to the command name. This is the | |
name to look for on the command line in | |
the shell. | |
@param[in] CommandHandler Pointer to a function that runs the | |
specified command. | |
@param[in] GetManFileName Pointer to a function that provides man | |
filename. | |
@param[in] ShellMinSupportLevel minimum Shell Support Level which has this | |
function. | |
@param[in] ProfileName profile name to require for support of this | |
function. | |
@param[in] CanAffectLE indicates whether this command's return value | |
can change the LASTERROR environment variable. | |
@param[in] HiiHandle Handle of this command's HII entry. | |
@param[in] ManFormatHelp HII locator for the help text. | |
@retval RETURN_SUCCESS The handlers were registered. | |
@retval RETURN_OUT_OF_RESOURCES There are not enough resources available to | |
register the shell command. | |
@retval RETURN_UNSUPPORTED the ShellMinSupportLevel was higher than the | |
currently allowed support level. | |
@retval RETURN_ALREADY_STARTED The CommandString represents a command that | |
is already registered. Only 1 handler set for | |
a given command is allowed. | |
@sa SHELL_GET_MAN_FILENAME | |
@sa SHELL_RUN_COMMAND | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ShellCommandRegisterCommandName ( | |
IN CONST CHAR16 *CommandString, | |
IN SHELL_RUN_COMMAND CommandHandler, | |
IN SHELL_GET_MAN_FILENAME GetManFileName, | |
IN UINT32 ShellMinSupportLevel, | |
IN CONST CHAR16 *ProfileName, | |
IN CONST BOOLEAN CanAffectLE, | |
IN CONST EFI_HII_HANDLE HiiHandle, | |
IN CONST EFI_STRING_ID ManFormatHelp | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Command; | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *PrevCommand; | |
INTN LexicalMatchValue; | |
// | |
// Initialize local variables. | |
// | |
Command = NULL; | |
PrevCommand = NULL; | |
LexicalMatchValue = 0; | |
// | |
// ASSERTs for NULL parameters | |
// | |
ASSERT(CommandString != NULL); | |
ASSERT(GetManFileName != NULL); | |
ASSERT(CommandHandler != NULL); | |
ASSERT(ProfileName != NULL); | |
// | |
// check for shell support level | |
// | |
if (PcdGet8(PcdShellSupportLevel) < ShellMinSupportLevel) { | |
return (RETURN_UNSUPPORTED); | |
} | |
// | |
// check for already on the list | |
// | |
if (ShellCommandIsCommandOnList(CommandString)) { | |
return (RETURN_ALREADY_STARTED); | |
} | |
// | |
// allocate memory for new struct | |
// | |
Node = AllocateZeroPool(sizeof(SHELL_COMMAND_INTERNAL_LIST_ENTRY)); | |
if (Node == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
Node->CommandString = AllocateCopyPool(StrSize(CommandString), CommandString); | |
if (Node->CommandString == NULL) { | |
FreePool (Node); | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
Node->GetManFileName = GetManFileName; | |
Node->CommandHandler = CommandHandler; | |
Node->LastError = CanAffectLE; | |
Node->HiiHandle = HiiHandle; | |
Node->ManFormatHelp = ManFormatHelp; | |
if ( StrLen(ProfileName)>0 | |
&& ((mProfileList != NULL | |
&& StrStr(mProfileList, ProfileName) == NULL) || mProfileList == NULL) | |
){ | |
ASSERT((mProfileList == NULL && mProfileListSize == 0) || (mProfileList != NULL)); | |
if (mProfileList == NULL) { | |
// | |
// If this is the first make a leading ';' | |
// | |
StrnCatGrow(&mProfileList, &mProfileListSize, L";", 0); | |
} | |
StrnCatGrow(&mProfileList, &mProfileListSize, ProfileName, 0); | |
StrnCatGrow(&mProfileList, &mProfileListSize, L";", 0); | |
} | |
// | |
// Insert a new entry on top of the list | |
// | |
InsertHeadList (&mCommandList.Link, &Node->Link); | |
// | |
// Move a new registered command to its sorted ordered location in the list | |
// | |
for (Command = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode (&mCommandList.Link), | |
PrevCommand = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode (&mCommandList.Link) | |
; !IsNull (&mCommandList.Link, &Command->Link) | |
; Command = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetNextNode (&mCommandList.Link, &Command->Link)) { | |
// | |
// Get Lexical Comparison Value between PrevCommand and Command list entry | |
// | |
LexicalMatchValue = gUnicodeCollation->StriColl ( | |
gUnicodeCollation, | |
PrevCommand->CommandString, | |
Command->CommandString | |
); | |
// | |
// Swap PrevCommand and Command list entry if PrevCommand list entry | |
// is alphabetically greater than Command list entry | |
// | |
if (LexicalMatchValue > 0){ | |
Command = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *) SwapListEntries (&PrevCommand->Link, &Command->Link); | |
} else if (LexicalMatchValue < 0) { | |
// | |
// PrevCommand entry is lexically lower than Command entry | |
// | |
break; | |
} | |
} | |
return (RETURN_SUCCESS); | |
} | |
/** | |
Function to get the current Profile string. | |
@retval NULL There are no installed profiles. | |
@return A semi-colon delimited list of profiles. | |
**/ | |
CONST CHAR16 * | |
EFIAPI | |
ShellCommandGetProfileList ( | |
VOID | |
) | |
{ | |
return (mProfileList); | |
} | |
/** | |
Checks if a command string has been registered for CommandString and if so it runs | |
the previously registered handler for that command with the command line. | |
If CommandString is NULL, then ASSERT(). | |
If Sections is specified, then each section name listed will be compared in a casesensitive | |
manner, to the section names described in Appendix B UEFI Shell 2.0 spec. If the section exists, | |
it will be appended to the returned help text. If the section does not exist, no | |
information will be returned. If Sections is NULL, then all help text information | |
available will be returned. | |
@param[in] CommandString Pointer to the command name. This is the name | |
found on the command line in the shell. | |
@param[in, out] RetVal Pointer to the return vaule from the command handler. | |
@param[in, out] CanAffectLE indicates whether this command's return value | |
needs to be placed into LASTERROR environment variable. | |
@retval RETURN_SUCCESS The handler was run. | |
@retval RETURN_NOT_FOUND The CommandString did not match a registered | |
command name. | |
@sa SHELL_RUN_COMMAND | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ShellCommandRunCommandHandler ( | |
IN CONST CHAR16 *CommandString, | |
IN OUT SHELL_STATUS *RetVal, | |
IN OUT BOOLEAN *CanAffectLE OPTIONAL | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *DynamicCommand; | |
// | |
// assert for NULL parameters | |
// | |
ASSERT(CommandString != NULL); | |
// | |
// check for the command | |
// | |
for ( Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode(&mCommandList.Link) | |
; !IsNull(&mCommandList.Link, &Node->Link) | |
; Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetNextNode(&mCommandList.Link, &Node->Link) | |
){ | |
ASSERT(Node->CommandString != NULL); | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)CommandString, | |
Node->CommandString) == 0 | |
){ | |
if (CanAffectLE != NULL) { | |
*CanAffectLE = Node->LastError; | |
} | |
if (RetVal != NULL) { | |
*RetVal = Node->CommandHandler(NULL, gST); | |
} else { | |
Node->CommandHandler(NULL, gST); | |
} | |
return (RETURN_SUCCESS); | |
} | |
} | |
// | |
// An internal command was not found, try to find a dynamic command | |
// | |
DynamicCommand = (EFI_SHELL_DYNAMIC_COMMAND_PROTOCOL *)ShellCommandFindDynamicCommand(CommandString); | |
if (DynamicCommand != NULL) { | |
if (RetVal != NULL) { | |
*RetVal = DynamicCommand->Handler(DynamicCommand, gST, gEfiShellParametersProtocol, gEfiShellProtocol); | |
} else { | |
DynamicCommand->Handler(DynamicCommand, gST, gEfiShellParametersProtocol, gEfiShellProtocol); | |
} | |
return (RETURN_SUCCESS); | |
} | |
return (RETURN_NOT_FOUND); | |
} | |
/** | |
Checks if a command string has been registered for CommandString and if so it | |
returns the MAN filename specified for that command. | |
If CommandString is NULL, then ASSERT(). | |
@param[in] CommandString Pointer to the command name. This is the name | |
found on the command line in the shell.\ | |
@retval NULL the commandString was not a registered command. | |
@return other the name of the MAN file. | |
@sa SHELL_GET_MAN_FILENAME | |
**/ | |
CONST CHAR16* | |
EFIAPI | |
ShellCommandGetManFileNameHandler ( | |
IN CONST CHAR16 *CommandString | |
) | |
{ | |
SHELL_COMMAND_INTERNAL_LIST_ENTRY *Node; | |
// | |
// assert for NULL parameters | |
// | |
ASSERT(CommandString != NULL); | |
// | |
// check for the command | |
// | |
for ( Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetFirstNode(&mCommandList.Link) | |
; !IsNull(&mCommandList.Link, &Node->Link) | |
; Node = (SHELL_COMMAND_INTERNAL_LIST_ENTRY *)GetNextNode(&mCommandList.Link, &Node->Link) | |
){ | |
ASSERT(Node->CommandString != NULL); | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)CommandString, | |
Node->CommandString) == 0 | |
){ | |
return (Node->GetManFileName()); | |
} | |
} | |
return (NULL); | |
} | |
/** | |
Get the list of all available shell internal commands. This is a linked list | |
(via LIST_ENTRY structure). enumerate through it using the BaseLib linked | |
list functions. do not modify the values. | |
@param[in] Sort TRUE to alphabetically sort the values first. FALSE otherwise. | |
@return a Linked list of all available shell commands. | |
**/ | |
CONST COMMAND_LIST* | |
EFIAPI | |
ShellCommandGetCommandList ( | |
IN CONST BOOLEAN Sort | |
) | |
{ | |
// if (!Sort) { | |
// return ((COMMAND_LIST*)(&mCommandList)); | |
// } | |
return ((COMMAND_LIST*)(&mCommandList)); | |
} | |
/** | |
Registers aliases to be set as part of the initialization of the shell application. | |
If Command is NULL, then ASSERT(). | |
If Alias is NULL, then ASSERT(). | |
@param[in] Command Pointer to the Command | |
@param[in] Alias Pointer to Alias | |
@retval RETURN_SUCCESS The handlers were registered. | |
@retval RETURN_OUT_OF_RESOURCES There are not enough resources available to | |
register the shell command. | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
ShellCommandRegisterAlias ( | |
IN CONST CHAR16 *Command, | |
IN CONST CHAR16 *Alias | |
) | |
{ | |
ALIAS_LIST *Node; | |
ALIAS_LIST *CommandAlias; | |
ALIAS_LIST *PrevCommandAlias; | |
INTN LexicalMatchValue; | |
// | |
// Asserts for NULL | |
// | |
ASSERT(Command != NULL); | |
ASSERT(Alias != NULL); | |
// | |
// allocate memory for new struct | |
// | |
Node = AllocateZeroPool(sizeof(ALIAS_LIST)); | |
if (Node == NULL) { | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
Node->CommandString = AllocateCopyPool(StrSize(Command), Command); | |
if (Node->CommandString == NULL) { | |
FreePool (Node); | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
Node->Alias = AllocateCopyPool(StrSize(Alias), Alias); | |
if (Node->Alias == NULL) { | |
FreePool (Node->CommandString); | |
FreePool (Node); | |
return RETURN_OUT_OF_RESOURCES; | |
} | |
InsertHeadList (&mAliasList.Link, &Node->Link); | |
// | |
// Move a new pre-defined registered alias to its sorted ordered location in the list | |
// | |
for ( CommandAlias = (ALIAS_LIST *)GetFirstNode (&mAliasList.Link), | |
PrevCommandAlias = (ALIAS_LIST *)GetFirstNode (&mAliasList.Link) | |
; !IsNull (&mAliasList.Link, &CommandAlias->Link) | |
; CommandAlias = (ALIAS_LIST *) GetNextNode (&mAliasList.Link, &CommandAlias->Link) ) { | |
// | |
// Get Lexical comparison value between PrevCommandAlias and CommandAlias List Entry | |
// | |
LexicalMatchValue = gUnicodeCollation->StriColl ( | |
gUnicodeCollation, | |
PrevCommandAlias->Alias, | |
CommandAlias->Alias | |
); | |
// | |
// Swap PrevCommandAlias and CommandAlias list entry if PrevCommandAlias list entry | |
// is alphabetically greater than CommandAlias list entry | |
// | |
if (LexicalMatchValue > 0) { | |
CommandAlias = (ALIAS_LIST *) SwapListEntries (&PrevCommandAlias->Link, &CommandAlias->Link); | |
} else if (LexicalMatchValue < 0) { | |
// | |
// PrevCommandAlias entry is lexically lower than CommandAlias entry | |
// | |
break; | |
} | |
} | |
return (RETURN_SUCCESS); | |
} | |
/** | |
Get the list of all shell alias commands. This is a linked list | |
(via LIST_ENTRY structure). enumerate through it using the BaseLib linked | |
list functions. do not modify the values. | |
@return a Linked list of all requested shell alias'. | |
**/ | |
CONST ALIAS_LIST* | |
EFIAPI | |
ShellCommandGetInitAliasList ( | |
VOID | |
) | |
{ | |
return (&mAliasList); | |
} | |
/** | |
Determine if a given alias is on the list of built in alias'. | |
@param[in] Alias The alias to test for | |
@retval TRUE The alias is a built in alias | |
@retval FALSE The alias is not a built in alias | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellCommandIsOnAliasList( | |
IN CONST CHAR16 *Alias | |
) | |
{ | |
ALIAS_LIST *Node; | |
// | |
// assert for NULL parameter | |
// | |
ASSERT(Alias != NULL); | |
// | |
// check for the Alias | |
// | |
for ( Node = (ALIAS_LIST *)GetFirstNode(&mAliasList.Link) | |
; !IsNull(&mAliasList.Link, &Node->Link) | |
; Node = (ALIAS_LIST *)GetNextNode(&mAliasList.Link, &Node->Link) | |
){ | |
ASSERT(Node->CommandString != NULL); | |
ASSERT(Node->Alias != NULL); | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)Alias, | |
Node->CommandString) == 0 | |
){ | |
return (TRUE); | |
} | |
if (gUnicodeCollation->StriColl( | |
gUnicodeCollation, | |
(CHAR16*)Alias, | |
Node->Alias) == 0 | |
){ | |
return (TRUE); | |
} | |
} | |
return (FALSE); | |
} | |
/** | |
Function to determine current state of ECHO. Echo determines if lines from scripts | |
and ECHO commands are enabled. | |
@retval TRUE Echo is currently enabled | |
@retval FALSE Echo is currently disabled | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellCommandGetEchoState( | |
VOID | |
) | |
{ | |
return (mEchoState); | |
} | |
/** | |
Function to set current state of ECHO. Echo determines if lines from scripts | |
and ECHO commands are enabled. | |
If State is TRUE, Echo will be enabled. | |
If State is FALSE, Echo will be disabled. | |
@param[in] State How to set echo. | |
**/ | |
VOID | |
EFIAPI | |
ShellCommandSetEchoState( | |
IN BOOLEAN State | |
) | |
{ | |
mEchoState = State; | |
} | |
/** | |
Indicate that the current shell or script should exit. | |
@param[in] ScriptOnly TRUE if exiting a script; FALSE otherwise. | |
@param[in] ErrorCode The 64 bit error code to return. | |
**/ | |
VOID | |
EFIAPI | |
ShellCommandRegisterExit ( | |
IN BOOLEAN ScriptOnly, | |
IN CONST UINT64 ErrorCode | |
) | |
{ | |
mExitRequested = (BOOLEAN)(!mExitRequested); | |
if (mExitRequested) { | |
mExitScript = ScriptOnly; | |
} else { | |
mExitScript = FALSE; | |
} | |
mExitCode = ErrorCode; | |
} | |
/** | |
Retrieve the Exit indicator. | |
@retval TRUE Exit was indicated. | |
@retval FALSE Exis was not indicated. | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellCommandGetExit ( | |
VOID | |
) | |
{ | |
return (mExitRequested); | |
} | |
/** | |
Retrieve the Exit code. | |
If ShellCommandGetExit returns FALSE than the return from this is undefined. | |
@return the value passed into RegisterExit. | |
**/ | |
UINT64 | |
EFIAPI | |
ShellCommandGetExitCode ( | |
VOID | |
) | |
{ | |
return (mExitCode); | |
} | |
/** | |
Retrieve the Exit script indicator. | |
If ShellCommandGetExit returns FALSE than the return from this is undefined. | |
@retval TRUE ScriptOnly was indicated. | |
@retval FALSE ScriptOnly was not indicated. | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellCommandGetScriptExit ( | |
VOID | |
) | |
{ | |
return (mExitScript); | |
} | |
/** | |
Function to cleanup all memory from a SCRIPT_FILE structure. | |
@param[in] Script The pointer to the structure to cleanup. | |
**/ | |
VOID | |
EFIAPI | |
DeleteScriptFileStruct ( | |
IN SCRIPT_FILE *Script | |
) | |
{ | |
UINT8 LoopVar; | |
if (Script == NULL) { | |
return; | |
} | |
for (LoopVar = 0 ; LoopVar < Script->Argc ; LoopVar++) { | |
SHELL_FREE_NON_NULL(Script->Argv[LoopVar]); | |
} | |
if (Script->Argv != NULL) { | |
SHELL_FREE_NON_NULL(Script->Argv); | |
} | |
Script->CurrentCommand = NULL; | |
while (!IsListEmpty (&Script->CommandList)) { | |
Script->CurrentCommand = (SCRIPT_COMMAND_LIST *)GetFirstNode(&Script->CommandList); | |
if (Script->CurrentCommand != NULL) { | |
RemoveEntryList(&Script->CurrentCommand->Link); | |
if (Script->CurrentCommand->Cl != NULL) { | |
SHELL_FREE_NON_NULL(Script->CurrentCommand->Cl); | |
} | |
if (Script->CurrentCommand->Data != NULL) { | |
SHELL_FREE_NON_NULL(Script->CurrentCommand->Data); | |
} | |
SHELL_FREE_NON_NULL(Script->CurrentCommand); | |
} | |
} | |
SHELL_FREE_NON_NULL(Script->ScriptName); | |
SHELL_FREE_NON_NULL(Script); | |
} | |
/** | |
Function to return a pointer to the currently running script file object. | |
@retval NULL A script file is not currently running. | |
@return A pointer to the current script file object. | |
**/ | |
SCRIPT_FILE* | |
EFIAPI | |
ShellCommandGetCurrentScriptFile ( | |
VOID | |
) | |
{ | |
SCRIPT_FILE_LIST *List; | |
if (IsListEmpty (&mScriptList.Link)) { | |
return (NULL); | |
} | |
List = ((SCRIPT_FILE_LIST*)GetFirstNode(&mScriptList.Link)); | |
return (List->Data); | |
} | |
/** | |
Function to set a new script as the currently running one. | |
This function will correctly stack and unstack nested scripts. | |
@param[in] Script Pointer to new script information structure. if NULL | |
will remove and de-allocate the top-most Script structure. | |
@return A pointer to the current running script file after this | |
change. NULL if removing the final script. | |
**/ | |
SCRIPT_FILE* | |
EFIAPI | |
ShellCommandSetNewScript ( | |
IN SCRIPT_FILE *Script OPTIONAL | |
) | |
{ | |
SCRIPT_FILE_LIST *Node; | |
if (Script == NULL) { | |
if (IsListEmpty (&mScriptList.Link)) { | |
return (NULL); | |
} | |
Node = (SCRIPT_FILE_LIST *)GetFirstNode(&mScriptList.Link); | |
RemoveEntryList(&Node->Link); | |
DeleteScriptFileStruct(Node->Data); | |
FreePool(Node); | |
} else { | |
Node = AllocateZeroPool(sizeof(SCRIPT_FILE_LIST)); | |
if (Node == NULL) { | |
return (NULL); | |
} | |
Node->Data = Script; | |
InsertHeadList(&mScriptList.Link, &Node->Link); | |
} | |
return (ShellCommandGetCurrentScriptFile()); | |
} | |
/** | |
Function to generate the next default mapping name. | |
If the return value is not NULL then it must be callee freed. | |
@param Type What kind of mapping name to make. | |
@retval NULL a memory allocation failed. | |
@return a new map name string | |
**/ | |
CHAR16* | |
EFIAPI | |
ShellCommandCreateNewMappingName( | |
IN CONST SHELL_MAPPING_TYPE Type | |
) | |
{ | |
CHAR16 *String; | |
ASSERT(Type < MappingTypeMax); | |
String = NULL; | |
String = AllocateZeroPool(PcdGet8(PcdShellMapNameLength) * sizeof(String[0])); | |
UnicodeSPrint( | |
String, | |
PcdGet8(PcdShellMapNameLength) * sizeof(String[0]), | |
Type == MappingTypeFileSystem?L"FS%d:":L"BLK%d:", | |
Type == MappingTypeFileSystem?mFsMaxCount++:mBlkMaxCount++); | |
return (String); | |
} | |
/** | |
Function to add a map node to the list of map items and update the "path" environment variable (optionally). | |
If Path is TRUE (during initialization only), the path environment variable will also be updated to include | |
default paths on the new map name... | |
Path should be FALSE when this function is called from the protocol SetMap function. | |
@param[in] Name The human readable mapped name. | |
@param[in] DevicePath The Device Path for this map. | |
@param[in] Flags The Flags attribute for this map item. | |
@param[in] Path TRUE to update path, FALSE to skip this step (should only be TRUE during initialization). | |
@retval EFI_SUCCESS The addition was sucessful. | |
@retval EFI_OUT_OF_RESOURCES A memory allocation failed. | |
@retval EFI_INVALID_PARAMETER A parameter was invalid. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
ShellCommandAddMapItemAndUpdatePath( | |
IN CONST CHAR16 *Name, | |
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath, | |
IN CONST UINT64 Flags, | |
IN CONST BOOLEAN Path | |
) | |
{ | |
EFI_STATUS Status; | |
SHELL_MAP_LIST *MapListNode; | |
CONST CHAR16 *OriginalPath; | |
CHAR16 *NewPath; | |
UINTN NewPathSize; | |
NewPathSize = 0; | |
NewPath = NULL; | |
OriginalPath = NULL; | |
Status = EFI_SUCCESS; | |
MapListNode = AllocateZeroPool(sizeof(SHELL_MAP_LIST)); | |
if (MapListNode == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
} else { | |
MapListNode->Flags = Flags; | |
MapListNode->MapName = AllocateCopyPool(StrSize(Name), Name); | |
MapListNode->DevicePath = DuplicateDevicePath(DevicePath); | |
if ((MapListNode->MapName == NULL) || (MapListNode->DevicePath == NULL)){ | |
Status = EFI_OUT_OF_RESOURCES; | |
} else { | |
InsertTailList(&gShellMapList.Link, &MapListNode->Link); | |
} | |
} | |
if (EFI_ERROR(Status)) { | |
if (MapListNode != NULL) { | |
if (MapListNode->DevicePath != NULL) { | |
FreePool(MapListNode->DevicePath); | |
} | |
if (MapListNode->MapName != NULL) { | |
FreePool(MapListNode->MapName); | |
} | |
FreePool(MapListNode); | |
} | |
} else if (Path) { | |
// | |
// Since there was no error and Path was TRUE | |
// Now add the correct path for that mapping | |
// | |
OriginalPath = gEfiShellProtocol->GetEnv(L"path"); | |
ASSERT((NewPath == NULL && NewPathSize == 0) || (NewPath != NULL)); | |
if (OriginalPath != NULL) { | |
StrnCatGrow(&NewPath, &NewPathSize, OriginalPath, 0); | |
StrnCatGrow(&NewPath, &NewPathSize, L";", 0); | |
} | |
StrnCatGrow(&NewPath, &NewPathSize, Name, 0); | |
StrnCatGrow(&NewPath, &NewPathSize, L"\\efi\\tools\\;", 0); | |
StrnCatGrow(&NewPath, &NewPathSize, Name, 0); | |
StrnCatGrow(&NewPath, &NewPathSize, L"\\efi\\boot\\;", 0); | |
StrnCatGrow(&NewPath, &NewPathSize, Name, 0); | |
StrnCatGrow(&NewPath, &NewPathSize, L"\\", 0); | |
Status = gEfiShellProtocol->SetEnv(L"path", NewPath, TRUE); | |
ASSERT_EFI_ERROR(Status); | |
FreePool(NewPath); | |
} | |
return (Status); | |
} | |
/** | |
Creates the default map names for each device path in the system with | |
a protocol depending on the Type. | |
Creates the consistent map names for each device path in the system with | |
a protocol depending on the Type. | |
Note: This will reset all mappings in the system("map -r"). | |
Also sets up the default path environment variable if Type is FileSystem. | |
@retval EFI_SUCCESS All map names were created sucessfully. | |
@retval EFI_NOT_FOUND No protocols were found in the system. | |
@return Error returned from gBS->LocateHandle(). | |
@sa LocateHandle | |
**/ | |
EFI_STATUS | |
EFIAPI | |
ShellCommandCreateInitialMappingsAndPaths( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE *HandleList; | |
UINTN Count; | |
EFI_DEVICE_PATH_PROTOCOL **DevicePathList; | |
CHAR16 *NewDefaultName; | |
CHAR16 *NewConsistName; | |
EFI_DEVICE_PATH_PROTOCOL **ConsistMappingTable; | |
SHELL_MAP_LIST *MapListNode; | |
CONST CHAR16 *CurDir; | |
CHAR16 *SplitCurDir; | |
CHAR16 *MapName; | |
SHELL_MAP_LIST *MapListItem; | |
SplitCurDir = NULL; | |
MapName = NULL; | |
MapListItem = NULL; | |
HandleList = NULL; | |
// | |
// Reset the static members back to zero | |
// | |
mFsMaxCount = 0; | |
mBlkMaxCount = 0; | |
gEfiShellProtocol->SetEnv(L"path", L"", TRUE); | |
// | |
// First empty out the existing list. | |
// | |
if (!IsListEmpty(&gShellMapList.Link)) { | |
for ( MapListNode = (SHELL_MAP_LIST *)GetFirstNode(&gShellMapList.Link) | |
; !IsListEmpty(&gShellMapList.Link) | |
; MapListNode = (SHELL_MAP_LIST *)GetFirstNode(&gShellMapList.Link) | |
){ | |
RemoveEntryList(&MapListNode->Link); | |
SHELL_FREE_NON_NULL(MapListNode->DevicePath); | |
SHELL_FREE_NON_NULL(MapListNode->MapName); | |
SHELL_FREE_NON_NULL(MapListNode->CurrentDirectoryPath); | |
FreePool(MapListNode); | |
} // for loop | |
} | |
// | |
// Find each handle with Simple File System | |
// | |
HandleList = GetHandleListByProtocol(&gEfiSimpleFileSystemProtocolGuid); | |
if (HandleList != NULL) { | |
// | |
// Do a count of the handles | |
// | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++); | |
// | |
// Get all Device Paths | |
// | |
DevicePathList = AllocateZeroPool(sizeof(EFI_DEVICE_PATH_PROTOCOL*) * Count); | |
if (DevicePathList == NULL) { | |
SHELL_FREE_NON_NULL (HandleList); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++) { | |
DevicePathList[Count] = DevicePathFromHandle(HandleList[Count]); | |
} | |
// | |
// Sort all DevicePaths | |
// | |
PerformQuickSort(DevicePathList, Count, sizeof(EFI_DEVICE_PATH_PROTOCOL*), DevicePathCompare); | |
ShellCommandConsistMappingInitialize(&ConsistMappingTable); | |
// | |
// Assign new Mappings to all... | |
// | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++) { | |
// | |
// Get default name first | |
// | |
NewDefaultName = ShellCommandCreateNewMappingName(MappingTypeFileSystem); | |
ASSERT(NewDefaultName != NULL); | |
Status = ShellCommandAddMapItemAndUpdatePath(NewDefaultName, DevicePathList[Count], 0, TRUE); | |
ASSERT_EFI_ERROR(Status); | |
FreePool(NewDefaultName); | |
// | |
// Now do consistent name | |
// | |
NewConsistName = ShellCommandConsistMappingGenMappingName(DevicePathList[Count], ConsistMappingTable); | |
if (NewConsistName != NULL) { | |
Status = ShellCommandAddMapItemAndUpdatePath(NewConsistName, DevicePathList[Count], 0, FALSE); | |
ASSERT_EFI_ERROR(Status); | |
FreePool(NewConsistName); | |
} | |
} | |
ShellCommandConsistMappingUnInitialize(ConsistMappingTable); | |
SHELL_FREE_NON_NULL(HandleList); | |
SHELL_FREE_NON_NULL(DevicePathList); | |
HandleList = NULL; | |
// | |
//gShellCurMapping point to node of current file system in the gShellMapList. When reset all mappings, | |
//all nodes in the gShellMapList will be free. Then gShellCurMapping will be a dangling pointer, So, | |
//after created new mappings, we should reset the gShellCurMapping pointer back to node of current file system. | |
// | |
if (gShellCurMapping != NULL) { | |
gShellCurMapping = NULL; | |
CurDir = gEfiShellProtocol->GetEnv(L"cwd"); | |
if (CurDir != NULL) { | |
MapName = AllocateCopyPool (StrSize(CurDir), CurDir); | |
if (MapName == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
SplitCurDir = StrStr (MapName, L":"); | |
if (SplitCurDir == NULL) { | |
SHELL_FREE_NON_NULL (MapName); | |
return EFI_UNSUPPORTED; | |
} | |
*(SplitCurDir + 1) = CHAR_NULL; | |
MapListItem = ShellCommandFindMapItem (MapName); | |
if (MapListItem != NULL) { | |
gShellCurMapping = MapListItem; | |
} | |
SHELL_FREE_NON_NULL (MapName); | |
} | |
} | |
} else { | |
Count = (UINTN)-1; | |
} | |
// | |
// Find each handle with Block Io | |
// | |
HandleList = GetHandleListByProtocol(&gEfiBlockIoProtocolGuid); | |
if (HandleList != NULL) { | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++); | |
// | |
// Get all Device Paths | |
// | |
DevicePathList = AllocateZeroPool(sizeof(EFI_DEVICE_PATH_PROTOCOL*) * Count); | |
if (DevicePathList == NULL) { | |
SHELL_FREE_NON_NULL (HandleList); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++) { | |
DevicePathList[Count] = DevicePathFromHandle(HandleList[Count]); | |
} | |
// | |
// Sort all DevicePaths | |
// | |
PerformQuickSort(DevicePathList, Count, sizeof(EFI_DEVICE_PATH_PROTOCOL*), DevicePathCompare); | |
// | |
// Assign new Mappings to all... | |
// | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++) { | |
// | |
// Get default name first | |
// | |
NewDefaultName = ShellCommandCreateNewMappingName(MappingTypeBlockIo); | |
ASSERT(NewDefaultName != NULL); | |
Status = ShellCommandAddMapItemAndUpdatePath(NewDefaultName, DevicePathList[Count], 0, FALSE); | |
ASSERT_EFI_ERROR(Status); | |
FreePool(NewDefaultName); | |
} | |
SHELL_FREE_NON_NULL(HandleList); | |
SHELL_FREE_NON_NULL(DevicePathList); | |
} else if (Count == (UINTN)-1) { | |
return (EFI_NOT_FOUND); | |
} | |
return (EFI_SUCCESS); | |
} | |
/** | |
Add mappings for any devices without one. Do not change any existing maps. | |
@retval EFI_SUCCESS The operation was successful. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
ShellCommandUpdateMapping ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_HANDLE *HandleList; | |
UINTN Count; | |
EFI_DEVICE_PATH_PROTOCOL **DevicePathList; | |
CHAR16 *NewDefaultName; | |
CHAR16 *NewConsistName; | |
EFI_DEVICE_PATH_PROTOCOL **ConsistMappingTable; | |
HandleList = NULL; | |
Status = EFI_SUCCESS; | |
// | |
// remove mappings that represent removed devices. | |
// | |
// | |
// Find each handle with Simple File System | |
// | |
HandleList = GetHandleListByProtocol(&gEfiSimpleFileSystemProtocolGuid); | |
if (HandleList != NULL) { | |
// | |
// Do a count of the handles | |
// | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++); | |
// | |
// Get all Device Paths | |
// | |
DevicePathList = AllocateZeroPool(sizeof(EFI_DEVICE_PATH_PROTOCOL*) * Count); | |
if (DevicePathList == NULL) { | |
return (EFI_OUT_OF_RESOURCES); | |
} | |
for (Count = 0 ; HandleList[Count] != NULL ; Count++) { | |
DevicePathList[Count] = DevicePathFromHandle(HandleList[Count]); | |
} | |
// | |
// Sort all DevicePaths | |
// | |
PerformQuickSort(DevicePathList, Count, sizeof(EFI_DEVICE_PATH_PROTOCOL*), DevicePathCompare); | |
ShellCommandConsistMappingInitialize(&ConsistMappingTable); | |
// | |
// Assign new Mappings to remainders | |
// | |
for (Count = 0 ; !EFI_ERROR(Status) && HandleList[Count] != NULL && !EFI_ERROR(Status); Count++) { | |
// | |
// Skip ones that already have | |
// | |
if (gEfiShellProtocol->GetMapFromDevicePath(&DevicePathList[Count]) != NULL) { | |
continue; | |
} | |
// | |
// Get default name | |
// | |
NewDefaultName = ShellCommandCreateNewMappingName(MappingTypeFileSystem); | |
if (NewDefaultName == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
break; | |
} | |
// | |
// Call shell protocol SetMap function now... | |
// | |
Status = gEfiShellProtocol->SetMap(DevicePathList[Count], NewDefaultName); | |
if (!EFI_ERROR(Status)) { | |
// | |
// Now do consistent name | |
// | |
NewConsistName = ShellCommandConsistMappingGenMappingName(DevicePathList[Count], ConsistMappingTable); | |
if (NewConsistName != NULL) { | |
Status = gEfiShellProtocol->SetMap(DevicePathList[Count], NewConsistName); | |
FreePool(NewConsistName); | |
} | |
} | |
FreePool(NewDefaultName); | |
} | |
ShellCommandConsistMappingUnInitialize(ConsistMappingTable); | |
SHELL_FREE_NON_NULL(HandleList); | |
SHELL_FREE_NON_NULL(DevicePathList); | |
HandleList = NULL; | |
} else { | |
Count = (UINTN)-1; | |
} | |
// | |
// Do it all over again for gEfiBlockIoProtocolGuid | |
// | |
return (Status); | |
} | |
/** | |
Converts a SHELL_FILE_HANDLE to an EFI_FILE_PROTOCOL*. | |
@param[in] Handle The SHELL_FILE_HANDLE to convert. | |
@return a EFI_FILE_PROTOCOL* representing the same file. | |
**/ | |
EFI_FILE_PROTOCOL* | |
EFIAPI | |
ConvertShellHandleToEfiFileProtocol( | |
IN CONST SHELL_FILE_HANDLE Handle | |
) | |
{ | |
return ((EFI_FILE_PROTOCOL*)(Handle)); | |
} | |
/** | |
Converts a EFI_FILE_PROTOCOL* to an SHELL_FILE_HANDLE. | |
@param[in] Handle The pointer to EFI_FILE_PROTOCOL to convert. | |
@param[in] Path The path to the file for verification. | |
@return A SHELL_FILE_HANDLE representing the same file. | |
@retval NULL There was not enough memory. | |
**/ | |
SHELL_FILE_HANDLE | |
EFIAPI | |
ConvertEfiFileProtocolToShellHandle( | |
IN CONST EFI_FILE_PROTOCOL *Handle, | |
IN CONST CHAR16 *Path | |
) | |
{ | |
SHELL_COMMAND_FILE_HANDLE *Buffer; | |
BUFFER_LIST *NewNode; | |
if (Path != NULL) { | |
Buffer = AllocateZeroPool(sizeof(SHELL_COMMAND_FILE_HANDLE)); | |
if (Buffer == NULL) { | |
return (NULL); | |
} | |
NewNode = AllocateZeroPool(sizeof(BUFFER_LIST)); | |
if (NewNode == NULL) { | |
SHELL_FREE_NON_NULL(Buffer); | |
return (NULL); | |
} | |
Buffer->FileHandle = (EFI_FILE_PROTOCOL*)Handle; | |
Buffer->Path = StrnCatGrow(&Buffer->Path, NULL, Path, 0); | |
if (Buffer->Path == NULL) { | |
SHELL_FREE_NON_NULL(NewNode); | |
SHELL_FREE_NON_NULL(Buffer); | |
return (NULL); | |
} | |
NewNode->Buffer = Buffer; | |
InsertHeadList(&mFileHandleList.Link, &NewNode->Link); | |
} | |
return ((SHELL_FILE_HANDLE)(Handle)); | |
} | |
/** | |
Find the path that was logged with the specified SHELL_FILE_HANDLE. | |
@param[in] Handle The SHELL_FILE_HANDLE to query on. | |
@return A pointer to the path for the file. | |
**/ | |
CONST CHAR16* | |
EFIAPI | |
ShellFileHandleGetPath( | |
IN CONST SHELL_FILE_HANDLE Handle | |
) | |
{ | |
BUFFER_LIST *Node; | |
for (Node = (BUFFER_LIST*)GetFirstNode(&mFileHandleList.Link) | |
; !IsNull(&mFileHandleList.Link, &Node->Link) | |
; Node = (BUFFER_LIST*)GetNextNode(&mFileHandleList.Link, &Node->Link) | |
){ | |
if ((Node->Buffer) && (((SHELL_COMMAND_FILE_HANDLE *)Node->Buffer)->FileHandle == Handle)){ | |
return (((SHELL_COMMAND_FILE_HANDLE *)Node->Buffer)->Path); | |
} | |
} | |
return (NULL); | |
} | |
/** | |
Remove a SHELL_FILE_HANDLE from the list of SHELL_FILE_HANDLES. | |
@param[in] Handle The SHELL_FILE_HANDLE to remove. | |
@retval TRUE The item was removed. | |
@retval FALSE The item was not found. | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellFileHandleRemove( | |
IN CONST SHELL_FILE_HANDLE Handle | |
) | |
{ | |
BUFFER_LIST *Node; | |
for (Node = (BUFFER_LIST*)GetFirstNode(&mFileHandleList.Link) | |
; !IsNull(&mFileHandleList.Link, &Node->Link) | |
; Node = (BUFFER_LIST*)GetNextNode(&mFileHandleList.Link, &Node->Link) | |
){ | |
if ((Node->Buffer) && (((SHELL_COMMAND_FILE_HANDLE *)Node->Buffer)->FileHandle == Handle)){ | |
RemoveEntryList(&Node->Link); | |
SHELL_FREE_NON_NULL(((SHELL_COMMAND_FILE_HANDLE *)Node->Buffer)->Path); | |
SHELL_FREE_NON_NULL(Node->Buffer); | |
SHELL_FREE_NON_NULL(Node); | |
return (TRUE); | |
} | |
} | |
return (FALSE); | |
} | |
/** | |
Function to determine if a SHELL_FILE_HANDLE is at the end of the file. | |
This will NOT work on directories. | |
If Handle is NULL, then ASSERT. | |
@param[in] Handle the file handle | |
@retval TRUE the position is at the end of the file | |
@retval FALSE the position is not at the end of the file | |
**/ | |
BOOLEAN | |
EFIAPI | |
ShellFileHandleEof( | |
IN SHELL_FILE_HANDLE Handle | |
) | |
{ | |
EFI_FILE_INFO *Info; | |
UINT64 Pos; | |
BOOLEAN RetVal; | |
// | |
// ASSERT if Handle is NULL | |
// | |
ASSERT(Handle != NULL); | |
gEfiShellProtocol->GetFilePosition(Handle, &Pos); | |
Info = gEfiShellProtocol->GetFileInfo (Handle); | |
gEfiShellProtocol->SetFilePosition(Handle, Pos); | |
if (Info == NULL) { | |
return (FALSE); | |
} | |
if (Pos == Info->FileSize) { | |
RetVal = TRUE; | |
} else { | |
RetVal = FALSE; | |
} | |
FreePool (Info); | |
return (RetVal); | |
} | |
/** | |
Frees any BUFFER_LIST defined type. | |
@param[in] List The BUFFER_LIST object to free. | |
**/ | |
VOID | |
EFIAPI | |
FreeBufferList ( | |
IN BUFFER_LIST *List | |
) | |
{ | |
BUFFER_LIST *BufferListEntry; | |
if (List == NULL){ | |
return; | |
} | |
// | |
// enumerate through the buffer list and free all memory | |
// | |
for ( BufferListEntry = ( BUFFER_LIST *)GetFirstNode(&List->Link) | |
; !IsListEmpty (&List->Link) | |
; BufferListEntry = (BUFFER_LIST *)GetFirstNode(&List->Link) | |
){ | |
RemoveEntryList(&BufferListEntry->Link); | |
if (BufferListEntry->Buffer != NULL) { | |
FreePool(BufferListEntry->Buffer); | |
} | |
FreePool(BufferListEntry); | |
} | |
} | |
/** | |
Dump some hexadecimal data to the screen. | |
@param[in] Indent How many spaces to indent the output. | |
@param[in] Offset The offset of the printing. | |
@param[in] DataSize The size in bytes of UserData. | |
@param[in] UserData The data to print out. | |
**/ | |
VOID | |
EFIAPI | |
DumpHex ( | |
IN UINTN Indent, | |
IN UINTN Offset, | |
IN UINTN DataSize, | |
IN VOID *UserData | |
) | |
{ | |
UINT8 *Data; | |
CHAR8 Val[50]; | |
CHAR8 Str[20]; | |
UINT8 TempByte; | |
UINTN Size; | |
UINTN Index; | |
Data = UserData; | |
while (DataSize != 0) { | |
Size = 16; | |
if (Size > DataSize) { | |
Size = DataSize; | |
} | |
for (Index = 0; Index < Size; Index += 1) { | |
TempByte = Data[Index]; | |
Val[Index * 3 + 0] = Hex[TempByte >> 4]; | |
Val[Index * 3 + 1] = Hex[TempByte & 0xF]; | |
Val[Index * 3 + 2] = (CHAR8) ((Index == 7) ? '-' : ' '); | |
Str[Index] = (CHAR8) ((TempByte < ' ' || TempByte > '~') ? '.' : TempByte); | |
} | |
Val[Index * 3] = 0; | |
Str[Index] = 0; | |
ShellPrintEx(-1, -1, L"%*a%08X: %-48a *%a*\r\n", Indent, "", Offset, Val, Str); | |
Data += Size; | |
Offset += Size; | |
DataSize -= Size; | |
} | |
} | |
/** | |
Dump HEX data into buffer. | |
@param[in] Buffer HEX data to be dumped in Buffer. | |
@param[in] Indent How many spaces to indent the output. | |
@param[in] Offset The offset of the printing. | |
@param[in] DataSize The size in bytes of UserData. | |
@param[in] UserData The data to print out. | |
**/ | |
CHAR16* | |
EFIAPI | |
CatSDumpHex ( | |
IN CHAR16 *Buffer, | |
IN UINTN Indent, | |
IN UINTN Offset, | |
IN UINTN DataSize, | |
IN VOID *UserData | |
) | |
{ | |
UINT8 *Data; | |
UINT8 TempByte; | |
UINTN Size; | |
UINTN Index; | |
CHAR8 Val[50]; | |
CHAR8 Str[20]; | |
CHAR16 *RetVal; | |
CHAR16 *TempRetVal; | |
Data = UserData; | |
RetVal = Buffer; | |
while (DataSize != 0) { | |
Size = 16; | |
if (Size > DataSize) { | |
Size = DataSize; | |
} | |
for (Index = 0; Index < Size; Index += 1) { | |
TempByte = Data[Index]; | |
Val[Index * 3 + 0] = Hex[TempByte >> 4]; | |
Val[Index * 3 + 1] = Hex[TempByte & 0xF]; | |
Val[Index * 3 + 2] = (CHAR8) ((Index == 7) ? '-' : ' '); | |
Str[Index] = (CHAR8) ((TempByte < ' ' || TempByte > 'z') ? '.' : TempByte); | |
} | |
Val[Index * 3] = 0; | |
Str[Index] = 0; | |
TempRetVal = CatSPrint (RetVal, L"%*a%08X: %-48a *%a*\r\n", Indent, "", Offset, Val, Str); | |
SHELL_FREE_NON_NULL (RetVal); | |
RetVal = TempRetVal; | |
Data += Size; | |
Offset += Size; | |
DataSize -= Size; | |
} | |
return RetVal; | |
} | |
/** | |
ORDERED_COLLECTION_USER_COMPARE function for SHELL_SORT_UNIQUE_NAME objects. | |
@param[in] Unique1AsVoid The first SHELL_SORT_UNIQUE_NAME object (Unique1), | |
passed in as a pointer-to-VOID. | |
@param[in] Unique2AsVoid The second SHELL_SORT_UNIQUE_NAME object (Unique2), | |
passed in as a pointer-to-VOID. | |
@retval <0 If Unique1 compares less than Unique2. | |
@retval 0 If Unique1 compares equal to Unique2. | |
@retval >0 If Unique1 compares greater than Unique2. | |
**/ | |
STATIC | |
INTN | |
EFIAPI | |
UniqueNameCompare ( | |
IN CONST VOID *Unique1AsVoid, | |
IN CONST VOID *Unique2AsVoid | |
) | |
{ | |
CONST SHELL_SORT_UNIQUE_NAME *Unique1; | |
CONST SHELL_SORT_UNIQUE_NAME *Unique2; | |
Unique1 = Unique1AsVoid; | |
Unique2 = Unique2AsVoid; | |
// | |
// We need to cast away CONST for EFI_UNICODE_COLLATION_STRICOLL. | |
// | |
return gUnicodeCollation->StriColl ( | |
gUnicodeCollation, | |
(CHAR16 *)Unique1->Alias, | |
(CHAR16 *)Unique2->Alias | |
); | |
} | |
/** | |
ORDERED_COLLECTION_KEY_COMPARE function for SHELL_SORT_UNIQUE_NAME objects. | |
@param[in] UniqueAliasAsVoid The CHAR16 string UniqueAlias, passed in as a | |
pointer-to-VOID. | |
@param[in] UniqueAsVoid The SHELL_SORT_UNIQUE_NAME object (Unique), | |
passed in as a pointer-to-VOID. | |
@retval <0 If UniqueAlias compares less than Unique->Alias. | |
@retval 0 If UniqueAlias compares equal to Unique->Alias. | |
@retval >0 If UniqueAlias compares greater than Unique->Alias. | |
**/ | |
STATIC | |
INTN | |
EFIAPI | |
UniqueNameAliasCompare ( | |
IN CONST VOID *UniqueAliasAsVoid, | |
IN CONST VOID *UniqueAsVoid | |
) | |
{ | |
CONST CHAR16 *UniqueAlias; | |
CONST SHELL_SORT_UNIQUE_NAME *Unique; | |
UniqueAlias = UniqueAliasAsVoid; | |
Unique = UniqueAsVoid; | |
// | |
// We need to cast away CONST for EFI_UNICODE_COLLATION_STRICOLL. | |
// | |
return gUnicodeCollation->StriColl ( | |
gUnicodeCollation, | |
(CHAR16 *)UniqueAlias, | |
(CHAR16 *)Unique->Alias | |
); | |
} | |
/** | |
Sort an EFI_SHELL_FILE_INFO list, optionally moving duplicates to a separate | |
list. | |
@param[in,out] FileList The list of EFI_SHELL_FILE_INFO objects to sort. | |
If FileList is NULL on input, then FileList is | |
considered an empty, hence already sorted, list. | |
Otherwise, if (*FileList) is NULL on input, then | |
EFI_INVALID_PARAMETER is returned. | |
Otherwise, the caller is responsible for having | |
initialized (*FileList)->Link with | |
InitializeListHead(). No other fields in the | |
(**FileList) head element are accessed by this | |
function. | |
On output, (*FileList) is sorted according to Order. | |
If Duplicates is NULL on input, then duplicate | |
elements are preserved, sorted stably, on | |
(*FileList). If Duplicates is not NULL on input, | |
then duplicates are moved (stably sorted) to the | |
new, dynamically allocated (*Duplicates) list. | |
@param[out] Duplicates If Duplicates is NULL on input, (*FileList) will be | |
a monotonically ordered list on output, with | |
duplicates stably sorted. | |
If Duplicates is not NULL on input, (*FileList) will | |
be a strictly monotonically oredered list on output, | |
with duplicates separated (stably sorted) to | |
(*Duplicates). All fields except Link will be | |
zero-initialized in the (**Duplicates) head element. | |
If no duplicates exist, then (*Duplicates) is set to | |
NULL on output. | |
@param[in] Order Determines the comparison operation between | |
EFI_SHELL_FILE_INFO objects. | |
@retval EFI_INVALID_PARAMETER (UINTN)Order is greater than or equal to | |
(UINTN)ShellSortFileListMax. Neither the | |
(*FileList) nor the (*Duplicates) list has | |
been modified. | |
@retval EFI_INVALID_PARAMETER (*FileList) was NULL on input. Neither the | |
(*FileList) nor the (*Duplicates) list has | |
been modified. | |
@retval EFI_OUT_OF_RESOURCES Memory allocation failed. Neither the | |
(*FileList) nor the (*Duplicates) list has | |
been modified. | |
@retval EFI_SUCCESS Sorting successful, including the case when | |
FileList is NULL on input. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
ShellSortFileList ( | |
IN OUT EFI_SHELL_FILE_INFO **FileList, | |
OUT EFI_SHELL_FILE_INFO **Duplicates OPTIONAL, | |
IN SHELL_SORT_FILE_LIST Order | |
) | |
{ | |
LIST_ENTRY *FilesHead; | |
ORDERED_COLLECTION *Sort; | |
LIST_ENTRY *FileEntry; | |
EFI_SHELL_FILE_INFO *FileInfo; | |
SHELL_SORT_UNIQUE_NAME *Unique; | |
EFI_STATUS Status; | |
EFI_SHELL_FILE_INFO *Dupes; | |
LIST_ENTRY *NextFileEntry; | |
CONST CHAR16 *Alias; | |
ORDERED_COLLECTION_ENTRY *SortEntry; | |
LIST_ENTRY *TargetFileList; | |
ORDERED_COLLECTION_ENTRY *NextSortEntry; | |
VOID *UniqueAsVoid; | |
if ((UINTN)Order >= (UINTN)ShellSortFileListMax) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (FileList == NULL) { | |
// | |
// FileList is considered empty, hence already sorted, with no duplicates. | |
// | |
if (Duplicates != NULL) { | |
*Duplicates = NULL; | |
} | |
return EFI_SUCCESS; | |
} | |
if (*FileList == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
FilesHead = &(*FileList)->Link; | |
// | |
// Collect all the unique names. | |
// | |
Sort = OrderedCollectionInit (UniqueNameCompare, UniqueNameAliasCompare); | |
if (Sort == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
BASE_LIST_FOR_EACH (FileEntry, FilesHead) { | |
FileInfo = (EFI_SHELL_FILE_INFO *)FileEntry; | |
// | |
// Try to record the name of this file as a unique name. | |
// | |
Unique = AllocatePool (sizeof (*Unique)); | |
if (Unique == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto UninitSort; | |
} | |
Unique->Alias = ((Order == ShellSortFileListByFileName) ? | |
FileInfo->FileName : | |
FileInfo->FullName); | |
InitializeListHead (&Unique->SameNameList); | |
Status = OrderedCollectionInsert (Sort, NULL, Unique); | |
if (EFI_ERROR (Status)) { | |
// | |
// Only two errors are possible: memory allocation failed, or this name | |
// has been encountered before. In either case, the | |
// SHELL_SORT_UNIQUE_NAME object being constructed has to be released. | |
// | |
FreePool (Unique); | |
// | |
// Memory allocation failure is fatal, while having seen the same name | |
// before is normal. | |
// | |
if (Status == EFI_OUT_OF_RESOURCES) { | |
goto UninitSort; | |
} | |
ASSERT (Status == EFI_ALREADY_STARTED); | |
} | |
} | |
// | |
// Set Dupes to suppress incorrect compiler/analyzer warnings. | |
// | |
Dupes = NULL; | |
// | |
// If separation of duplicates has been requested, allocate the list for | |
// them. | |
// | |
if (Duplicates != NULL) { | |
Dupes = AllocateZeroPool (sizeof (*Dupes)); | |
if (Dupes == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto UninitSort; | |
} | |
InitializeListHead (&Dupes->Link); | |
} | |
// | |
// No memory allocation beyond this point; thus, no chance to fail. We can | |
// now migrate the EFI_SHELL_FILE_INFO objects from (*FileList) to Sort. | |
// | |
BASE_LIST_FOR_EACH_SAFE (FileEntry, NextFileEntry, FilesHead) { | |
FileInfo = (EFI_SHELL_FILE_INFO *)FileEntry; | |
// | |
// Look up the SHELL_SORT_UNIQUE_NAME that matches FileInfo's name. | |
// | |
Alias = ((Order == ShellSortFileListByFileName) ? | |
FileInfo->FileName : | |
FileInfo->FullName); | |
SortEntry = OrderedCollectionFind (Sort, Alias); | |
ASSERT (SortEntry != NULL); | |
Unique = OrderedCollectionUserStruct (SortEntry); | |
// | |
// Move FileInfo from (*FileList) to the end of the list of files whose | |
// names all compare identical to FileInfo's name. | |
// | |
RemoveEntryList (&FileInfo->Link); | |
InsertTailList (&Unique->SameNameList, &FileInfo->Link); | |
} | |
// | |
// All EFI_SHELL_FILE_INFO objects originally in (*FileList) have been | |
// distributed to Sort. Now migrate them back to (*FileList), advancing in | |
// unique name order. | |
// | |
for (SortEntry = OrderedCollectionMin (Sort); | |
SortEntry != NULL; | |
SortEntry = OrderedCollectionNext (SortEntry)) { | |
Unique = OrderedCollectionUserStruct (SortEntry); | |
// | |
// The first FileInfo encountered for each unique name goes back on | |
// (*FileList) unconditionally. Further FileInfo instances for the same | |
// unique name -- that is, duplicates -- are either returned to (*FileList) | |
// or separated, dependent on the caller's request. | |
// | |
TargetFileList = FilesHead; | |
BASE_LIST_FOR_EACH_SAFE (FileEntry, NextFileEntry, &Unique->SameNameList) { | |
RemoveEntryList (FileEntry); | |
InsertTailList (TargetFileList, FileEntry); | |
if (Duplicates != NULL) { | |
TargetFileList = &Dupes->Link; | |
} | |
} | |
} | |
// | |
// We're done. If separation of duplicates has been requested, output the | |
// list of duplicates -- and free that list at once, if it's empty (i.e., if | |
// no duplicates have been found). | |
// | |
if (Duplicates != NULL) { | |
if (IsListEmpty (&Dupes->Link)) { | |
FreePool (Dupes); | |
*Duplicates = NULL; | |
} else { | |
*Duplicates = Dupes; | |
} | |
} | |
Status = EFI_SUCCESS; | |
// | |
// Fall through. | |
// | |
UninitSort: | |
for (SortEntry = OrderedCollectionMin (Sort); | |
SortEntry != NULL; | |
SortEntry = NextSortEntry) { | |
NextSortEntry = OrderedCollectionNext (SortEntry); | |
OrderedCollectionDelete (Sort, SortEntry, &UniqueAsVoid); | |
Unique = UniqueAsVoid; | |
ASSERT (IsListEmpty (&Unique->SameNameList)); | |
FreePool (Unique); | |
} | |
OrderedCollectionUninit (Sort); | |
return Status; | |
} |