/** @file File explorer related functions. Copyright (c) 2004 - 2019, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "FileExplorer.h" EFI_GUID FileExplorerGuid = EFI_FILE_EXPLORE_FORMSET_GUID; /// /// File system selection menu /// MENU_OPTION mFsOptionMenu = { MENU_OPTION_SIGNATURE, {NULL}, 0, FALSE }; FILE_EXPLORER_CALLBACK_DATA gFileExplorerPrivate = { FILE_EXPLORER_CALLBACK_DATA_SIGNATURE, NULL, NULL, { LibExtractConfig, LibRouteConfig, LibCallback }, NULL, &mFsOptionMenu, 0 }; HII_VENDOR_DEVICE_PATH *gHiiVendorDevicePath; HII_VENDOR_DEVICE_PATH FeHiiVendorDevicePath = { { { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { (UINT8) (sizeof (VENDOR_DEVICE_PATH)), (UINT8) ((sizeof (VENDOR_DEVICE_PATH)) >> 8) } }, // // Will be replace with gEfiCallerIdGuid in code. // { 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } } }, { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { (UINT8) (END_DEVICE_PATH_LENGTH), (UINT8) ((END_DEVICE_PATH_LENGTH) >> 8) } } }; VOID *mLibStartOpCodeHandle = NULL; VOID *mLibEndOpCodeHandle = NULL; EFI_IFR_GUID_LABEL *mLibStartLabel = NULL; EFI_IFR_GUID_LABEL *mLibEndLabel = NULL; UINT16 mQuestionIdUpdate; CHAR16 mNewFileName[MAX_FILE_NAME_LEN]; CHAR16 mNewFolderName[MAX_FOLDER_NAME_LEN]; UINTN mNewFileQuestionId = NEW_FILE_QUESTION_ID_BASE; UINTN mNewFolderQuestionId = NEW_FOLDER_QUESTION_ID_BASE; /** Create a new file or folder in current directory. @param FileName Point to the fileNmae or folder. @param CreateFile CreateFile== TRUE means create a new file. CreateFile== FALSE means create a new Folder. **/ EFI_STATUS LibCreateNewFile ( IN CHAR16 *FileName, IN BOOLEAN CreateFile ); /** This function allows a caller to extract the current configuration for one or more named elements from the target driver. @param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL. @param Request A null-terminated Unicode string in format. @param Progress On return, points to a character in the Request string. Points to the string's null terminator if request was successful. Points to the most recent '&' before the first failing name/value pair (or the beginning of the string if the failure is in the first name/value pair) if the request was not successful. @param Results A null-terminated Unicode string in format which has all values filled in for the names in the Request string. String to be allocated by the called function. @retval EFI_INVALID_PARAMETER Request is illegal syntax, or unknown name. @retval EFI_NOT_FOUND Routing data doesn't match any storage in this driver. **/ EFI_STATUS EFIAPI LibExtractConfig ( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN CONST EFI_STRING Request, OUT EFI_STRING *Progress, OUT EFI_STRING *Results ) { if (Progress == NULL || Results == NULL) { return EFI_INVALID_PARAMETER; } *Progress = Request; return EFI_NOT_FOUND; } /** This function processes the results of changes in configuration. @param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL. @param Configuration A null-terminated Unicode string in format. @param Progress A pointer to a string filled in with the offset of the most recent '&' before the first failing name/value pair (or the beginning of the string if the failure is in the first name/value pair) or the terminating NULL if all was successful. @retval EFI_INVALID_PARAMETER Configuration is NULL. @retval EFI_NOT_FOUND Routing data doesn't match any storage in this driver. **/ EFI_STATUS EFIAPI LibRouteConfig ( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN CONST EFI_STRING Configuration, OUT EFI_STRING *Progress ) { if (Configuration == NULL || Progress == NULL) { return EFI_INVALID_PARAMETER; } *Progress = Configuration; return EFI_NOT_FOUND; } /** This function processes the results of changes in configuration. When user select a interactive opcode, this callback will be triggered. Based on the Question(QuestionId) that triggers the callback, the corresponding actions is performed. It handles: 1) Process the axtra action or exit file explorer when user select one file . 2) update of file content if a dir is selected. @param This Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL. @param Action Specifies the type of action taken by the browser. @param QuestionId A unique value which is sent to the original exporting driver so that it can identify the type of data to expect. @param Type The type of value for the question. @param Value A pointer to the data being sent to the original exporting driver. @param ActionRequest On return, points to the action requested by the callback function. @retval EFI_SUCCESS The callback successfully handled the action. @retval other error Error occur when parse one directory. **/ EFI_STATUS EFIAPI LibCallback ( IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL *This, IN EFI_BROWSER_ACTION Action, IN EFI_QUESTION_ID QuestionId, IN UINT8 Type, IN EFI_IFR_TYPE_VALUE *Value, OUT EFI_BROWSER_ACTION_REQUEST *ActionRequest ) { EFI_STATUS Status; BOOLEAN NeedExit; CHAR16 *NewFileName; CHAR16 *NewFolderName; NeedExit = TRUE; NewFileName = NULL; NewFolderName = NULL; if (Action != EFI_BROWSER_ACTION_CHANGING && Action != EFI_BROWSER_ACTION_CHANGED) { // // Do nothing for other UEFI Action. Only do call back when data is changed. // return EFI_UNSUPPORTED; } if (Action == EFI_BROWSER_ACTION_CHANGED) { if ((Value == NULL) || (ActionRequest == NULL)) { return EFI_INVALID_PARAMETER; } if (QuestionId == KEY_VALUE_CREATE_FILE_AND_EXIT) { *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT; if (!IsZeroBuffer (mNewFileName, sizeof (mNewFileName))) { Status = LibCreateNewFile (mNewFileName,TRUE); ZeroMem (mNewFileName,sizeof (mNewFileName)); } } if (QuestionId == KEY_VALUE_NO_CREATE_FILE_AND_EXIT) { ZeroMem (mNewFileName,sizeof (mNewFileName)); *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT; } if (QuestionId == KEY_VALUE_CREATE_FOLDER_AND_EXIT) { *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT; if (!IsZeroBuffer (mNewFolderName, sizeof (mNewFolderName))) { Status = LibCreateNewFile (mNewFolderName, FALSE); ZeroMem (mNewFolderName,sizeof (mNewFolderName)); } } if (QuestionId == KEY_VALUE_NO_CREATE_FOLDER_AND_EXIT) { ZeroMem (mNewFolderName,sizeof (mNewFolderName)); *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT; } if (QuestionId == NEW_FILE_NAME_ID) { NewFileName = HiiGetString (gFileExplorerPrivate.FeHiiHandle, Value->string, NULL); if (NewFileName != NULL) { StrCpyS (mNewFileName, MAX_FILE_NAME_LEN, NewFileName); FreePool (NewFileName); NewFileName = NULL; } else { return EFI_INVALID_PARAMETER; } } if (QuestionId == NEW_FOLDER_NAME_ID) { NewFolderName = HiiGetString (gFileExplorerPrivate.FeHiiHandle, Value->string, NULL); if (NewFolderName != NULL) { StrCpyS (mNewFolderName, MAX_FOLDER_NAME_LEN, NewFolderName); FreePool (NewFolderName); NewFolderName = NULL; } else { return EFI_INVALID_PARAMETER; } } if (QuestionId >= FILE_OPTION_OFFSET) { LibGetDevicePath(QuestionId); // // Process the extra action. // if (gFileExplorerPrivate.ChooseHandler != NULL) { NeedExit = gFileExplorerPrivate.ChooseHandler (gFileExplorerPrivate.RetDevicePath); } if (NeedExit) { *ActionRequest = EFI_BROWSER_ACTION_REQUEST_EXIT; } } } else if (Action == EFI_BROWSER_ACTION_CHANGING) { if (Value == NULL) { return EFI_INVALID_PARAMETER; } if (QuestionId >= FILE_OPTION_OFFSET) { LibGetDevicePath(QuestionId); Status = LibUpdateFileExplorer (QuestionId); if (EFI_ERROR (Status)) { return Status; } } } return EFI_SUCCESS; } /** Create a menu entry by given menu type. @retval NULL If failed to create the menu. @return the new menu entry. **/ MENU_ENTRY * LibCreateMenuEntry ( VOID ) { MENU_ENTRY *MenuEntry; // // Create new menu entry // MenuEntry = AllocateZeroPool (sizeof (MENU_ENTRY)); if (MenuEntry == NULL) { return NULL; } MenuEntry->VariableContext = AllocateZeroPool (sizeof (FILE_CONTEXT)); if (MenuEntry->VariableContext == NULL) { FreePool (MenuEntry); return NULL; } MenuEntry->Signature = MENU_ENTRY_SIGNATURE; return MenuEntry; } /** Get the Menu Entry from the list in Menu Entry List. If MenuNumber is great or equal to the number of Menu Entry in the list, then ASSERT. @param MenuOption The Menu Entry List to read the menu entry. @param MenuNumber The index of Menu Entry. @return The Menu Entry. **/ MENU_ENTRY * LibGetMenuEntry ( MENU_OPTION *MenuOption, UINTN MenuNumber ) { MENU_ENTRY *NewMenuEntry; UINTN Index; LIST_ENTRY *List; ASSERT (MenuNumber < MenuOption->MenuNumber); List = MenuOption->Head.ForwardLink; for (Index = 0; Index < MenuNumber; Index++) { List = List->ForwardLink; } NewMenuEntry = CR (List, MENU_ENTRY, Link, MENU_ENTRY_SIGNATURE); return NewMenuEntry; } /** Free up all resource allocated for a BM_MENU_ENTRY. @param MenuEntry A pointer to BM_MENU_ENTRY. **/ VOID LibDestroyMenuEntry ( MENU_ENTRY *MenuEntry ) { FILE_CONTEXT *FileContext; FileContext = (FILE_CONTEXT *) MenuEntry->VariableContext; if (!FileContext->IsRoot) { if (FileContext->DevicePath != NULL) { FreePool (FileContext->DevicePath); } } else { if (FileContext->FileHandle != NULL) { FileContext->FileHandle->Close (FileContext->FileHandle); } } if (FileContext->FileName != NULL) { FreePool (FileContext->FileName); } FreePool (FileContext); if (MenuEntry->DisplayString != NULL) { FreePool (MenuEntry->DisplayString); } if (MenuEntry->HelpString != NULL) { FreePool (MenuEntry->HelpString); } FreePool (MenuEntry); } /** Free resources allocated in Allocate Rountine. @param FreeMenu Menu to be freed **/ VOID LibFreeMenu ( MENU_OPTION *FreeMenu ) { MENU_ENTRY *MenuEntry; while (!IsListEmpty (&FreeMenu->Head)) { MenuEntry = CR ( FreeMenu->Head.ForwardLink, MENU_ENTRY, Link, MENU_ENTRY_SIGNATURE ); RemoveEntryList (&MenuEntry->Link); LibDestroyMenuEntry (MenuEntry); } FreeMenu->MenuNumber = 0; } /** Function opens and returns a file handle to the root directory of a volume. @param DeviceHandle A handle for a device @return A valid file handle or NULL is returned **/ EFI_FILE_HANDLE LibOpenRoot ( IN EFI_HANDLE DeviceHandle ) { EFI_STATUS Status; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Volume; EFI_FILE_HANDLE File; File = NULL; // // File the file system interface to the device // Status = gBS->HandleProtocol ( DeviceHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID *) &Volume ); // // Open the root directory of the volume // if (!EFI_ERROR (Status)) { Status = Volume->OpenVolume ( Volume, &File ); } // // Done // return EFI_ERROR (Status) ? NULL : File; } /** This function converts an input device structure to a Unicode string. @param DevPath A pointer to the device path structure. @return A new allocated Unicode string that represents the device path. **/ CHAR16 * LibDevicePathToStr ( IN EFI_DEVICE_PATH_PROTOCOL *DevPath ) { EFI_STATUS Status; CHAR16 *ToText; EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *DevPathToText; if (DevPath == NULL) { return NULL; } Status = gBS->LocateProtocol ( &gEfiDevicePathToTextProtocolGuid, NULL, (VOID **) &DevPathToText ); ASSERT_EFI_ERROR (Status); ToText = DevPathToText->ConvertDevicePathToText ( DevPath, FALSE, TRUE ); ASSERT (ToText != NULL); return ToText; } /** Duplicate a string. @param Src The source. @return A new string which is duplicated copy of the source. @retval NULL If there is not enough memory. **/ CHAR16 * LibStrDuplicate ( IN CHAR16 *Src ) { CHAR16 *Dest; UINTN Size; Size = StrSize (Src); Dest = AllocateZeroPool (Size); ASSERT (Dest != NULL); if (Dest != NULL) { CopyMem (Dest, Src, Size); } return Dest; } /** Function gets the file information from an open file descriptor, and stores it in a buffer allocated from pool. @param FHand File Handle. @param InfoType Info type need to get. @retval A pointer to a buffer with file information or NULL is returned **/ VOID * LibFileInfo ( IN EFI_FILE_HANDLE FHand, IN EFI_GUID *InfoType ) { EFI_STATUS Status; EFI_FILE_INFO *Buffer; UINTN BufferSize; Buffer = NULL; BufferSize = 0; Status = FHand->GetInfo ( FHand, InfoType, &BufferSize, Buffer ); if (Status == EFI_BUFFER_TOO_SMALL) { Buffer = AllocatePool (BufferSize); ASSERT (Buffer != NULL); } Status = FHand->GetInfo ( FHand, InfoType, &BufferSize, Buffer ); return Buffer; } /** Get file type base on the file name. Just cut the file name, from the ".". eg ".efi" @param FileName File need to be checked. @retval the file type string. **/ CHAR16* LibGetTypeFromName ( IN CHAR16 *FileName ) { UINTN Index; Index = StrLen (FileName) - 1; while ((FileName[Index] != L'.') && (Index != 0)) { Index--; } return Index == 0 ? NULL : &FileName[Index]; } /** Converts the unicode character of the string from uppercase to lowercase. This is a internal function. @param ConfigString String to be converted **/ VOID LibToLowerString ( IN CHAR16 *String ) { CHAR16 *TmpStr; for (TmpStr = String; *TmpStr != L'\0'; TmpStr++) { if (*TmpStr >= L'A' && *TmpStr <= L'Z') { *TmpStr = (CHAR16) (*TmpStr - L'A' + L'a'); } } } /** Check whether current FileName point to a valid Efi Image File. @param FileName File need to be checked. @retval TRUE Is Efi Image @retval FALSE Not a valid Efi Image **/ BOOLEAN LibIsSupportedFileType ( IN UINT16 *FileName ) { CHAR16 *InputFileType; CHAR16 *TmpStr; BOOLEAN IsSupported; if (gFileExplorerPrivate.FileType == NULL) { return TRUE; } InputFileType = LibGetTypeFromName (FileName); // // If the file not has *.* style, always return TRUE. // if (InputFileType == NULL) { return TRUE; } TmpStr = AllocateCopyPool (StrSize (InputFileType), InputFileType); ASSERT(TmpStr != NULL); LibToLowerString(TmpStr); IsSupported = (StrStr (gFileExplorerPrivate.FileType, TmpStr) == NULL ? FALSE : TRUE); FreePool (TmpStr); return IsSupported; } /** Append file name to existing file name. @param Str1 The existing file name @param Str2 The file name to be appended @return Allocate a new string to hold the appended result. Caller is responsible to free the returned string. **/ CHAR16 * LibAppendFileName ( IN CHAR16 *Str1, IN CHAR16 *Str2 ) { UINTN Size1; UINTN Size2; UINTN MaxLen; CHAR16 *Str; CHAR16 *TmpStr; CHAR16 *Ptr; CHAR16 *LastSlash; Size1 = StrSize (Str1); Size2 = StrSize (Str2); // // Check overflow // if (((MAX_UINTN - Size1) < Size2) || ((MAX_UINTN - Size1 - Size2) < sizeof(CHAR16))) { return NULL; } MaxLen = (Size1 + Size2 + sizeof (CHAR16))/ sizeof (CHAR16); Str = AllocateZeroPool (Size1 + Size2 + sizeof (CHAR16)); ASSERT (Str != NULL); TmpStr = AllocateZeroPool (Size1 + Size2 + sizeof (CHAR16)); ASSERT (TmpStr != NULL); StrCpyS (Str, MaxLen, Str1); if (!((*Str == '\\') && (*(Str + 1) == 0))) { StrCatS (Str, MaxLen, L"\\"); } StrCatS (Str, MaxLen, Str2); Ptr = Str; LastSlash = Str; while (*Ptr != 0) { if (*Ptr == '\\' && *(Ptr + 1) == '.' && *(Ptr + 2) == '.' && *(Ptr + 3) == L'\\') { // // Convert "\Name\..\" to "\" // DO NOT convert the .. if it is at the end of the string. This will // break the .. behavior in changing directories. // // // Use TmpStr as a backup, as StrCpyS in BaseLib does not handle copy of two strings // that overlap. // StrCpyS (TmpStr, MaxLen, Ptr + 3); StrCpyS (LastSlash, MaxLen - ((UINTN) LastSlash - (UINTN) Str) / sizeof (CHAR16), TmpStr); Ptr = LastSlash; } else if (*Ptr == '\\' && *(Ptr + 1) == '.' && *(Ptr + 2) == '\\') { // // Convert a "\.\" to a "\" // // // Use TmpStr as a backup, as StrCpyS in BaseLib does not handle copy of two strings // that overlap. // StrCpyS (TmpStr, MaxLen, Ptr + 2); StrCpyS (Ptr, MaxLen - ((UINTN) Ptr - (UINTN) Str) / sizeof (CHAR16), TmpStr); Ptr = LastSlash; } else if (*Ptr == '\\') { LastSlash = Ptr; } Ptr++; } FreePool (TmpStr); return Str; } /** This function build the FsOptionMenu list which records all available file system in the system. They includes all instances of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL, all instances of EFI_LOAD_FILE_SYSTEM. @retval EFI_SUCCESS Success find the file system @retval EFI_OUT_OF_RESOURCES Can not create menu entry **/ EFI_STATUS LibFindFileSystem ( VOID ) { UINTN NoSimpleFsHandles; EFI_HANDLE *SimpleFsHandle; UINT16 *VolumeLabel; UINTN Index; EFI_STATUS Status; MENU_ENTRY *MenuEntry; FILE_CONTEXT *FileContext; UINTN OptionNumber; EFI_FILE_SYSTEM_VOLUME_LABEL *Info; NoSimpleFsHandles = 0; OptionNumber = 0; // // Locate Handles that support Simple File System protocol // Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &NoSimpleFsHandles, &SimpleFsHandle ); if (!EFI_ERROR (Status)) { // // Find all the instances of the File System prototocol // for (Index = 0; Index < NoSimpleFsHandles; Index++) { // // Allocate pool for this load option // MenuEntry = LibCreateMenuEntry (); if (NULL == MenuEntry) { FreePool (SimpleFsHandle); return EFI_OUT_OF_RESOURCES; } FileContext = (FILE_CONTEXT *) MenuEntry->VariableContext; FileContext->DeviceHandle = SimpleFsHandle[Index]; FileContext->FileHandle = LibOpenRoot (FileContext->DeviceHandle); if (FileContext->FileHandle == NULL) { LibDestroyMenuEntry (MenuEntry); continue; } MenuEntry->HelpString = LibDevicePathToStr (DevicePathFromHandle (FileContext->DeviceHandle)); FileContext->FileName = LibStrDuplicate (L"\\"); FileContext->DevicePath = FileDevicePath (FileContext->DeviceHandle, FileContext->FileName); FileContext->IsDir = TRUE; FileContext->IsRoot = TRUE; // // Get current file system's Volume Label // Info = (EFI_FILE_SYSTEM_VOLUME_LABEL *) LibFileInfo (FileContext->FileHandle, &gEfiFileSystemVolumeLabelInfoIdGuid); if (Info == NULL) { VolumeLabel = L"NO FILE SYSTEM INFO"; } else { if (Info->VolumeLabel == NULL) { VolumeLabel = L"NULL VOLUME LABEL"; } else { VolumeLabel = Info->VolumeLabel; if (*VolumeLabel == 0x0000) { VolumeLabel = L"NO VOLUME LABEL"; } } } MenuEntry->DisplayString = AllocateZeroPool (MAX_CHAR); ASSERT (MenuEntry->DisplayString != NULL); UnicodeSPrint ( MenuEntry->DisplayString, MAX_CHAR, L"%s, [%s]", VolumeLabel, MenuEntry->HelpString ); MenuEntry->DisplayStringToken = HiiSetString ( gFileExplorerPrivate.FeHiiHandle, 0, MenuEntry->DisplayString, NULL ); if (Info != NULL) FreePool (Info); OptionNumber++; InsertTailList (&gFileExplorerPrivate.FsOptionMenu->Head, &MenuEntry->Link); } } if (NoSimpleFsHandles != 0) { FreePool (SimpleFsHandle); } gFileExplorerPrivate.FsOptionMenu->MenuNumber = OptionNumber; return EFI_SUCCESS; } /** Find the file handle from the input menu info. @param MenuEntry Input Menu info. @param RetFileHandle Return the file handle for the input device path. @retval EFI_SUCESS Find the file handle success. @retval Other Find the file handle failure. **/ EFI_STATUS LibGetFileHandleFromMenu ( IN MENU_ENTRY *MenuEntry, OUT EFI_FILE_HANDLE *RetFileHandle ) { EFI_FILE_HANDLE Dir; EFI_FILE_HANDLE NewDir; FILE_CONTEXT *FileContext; EFI_STATUS Status; FileContext = (FILE_CONTEXT *) MenuEntry->VariableContext; Dir = FileContext->FileHandle; // // Open current directory to get files from it // Status = Dir->Open ( Dir, &NewDir, FileContext->FileName, EFI_FILE_READ_ONLY, 0 ); if (EFI_ERROR (Status)) { return Status; } if (!FileContext->IsRoot) { Dir->Close (Dir); } *RetFileHandle = NewDir; return EFI_SUCCESS; } /** Find the file handle from the input device path info. @param RootDirectory Device path info. @param RetFileHandle Return the file handle for the input device path. @param ParentFileName Parent file name. @param DeviceHandle Driver handle for this partition. @retval EFI_SUCESS Find the file handle success. @retval Other Find the file handle failure. **/ EFI_STATUS LibGetFileHandleFromDevicePath ( IN EFI_DEVICE_PATH_PROTOCOL *RootDirectory, OUT EFI_FILE_HANDLE *RetFileHandle, OUT UINT16 **ParentFileName, OUT EFI_HANDLE *DeviceHandle ) { EFI_DEVICE_PATH_PROTOCOL *DevicePathNode; EFI_DEVICE_PATH_PROTOCOL *TempDevicePathNode; EFI_STATUS Status; EFI_HANDLE Handle; EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Volume; EFI_FILE_HANDLE FileHandle; EFI_FILE_HANDLE LastHandle; CHAR16 *TempPath; *ParentFileName = NULL; // // Attempt to access the file via a file system interface // DevicePathNode = RootDirectory; Status = gBS->LocateDevicePath (&gEfiSimpleFileSystemProtocolGuid, &DevicePathNode, &Handle); if (EFI_ERROR (Status)) { return Status; } Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID**)&Volume); if (EFI_ERROR (Status)) { return Status; } // // Open the Volume to get the File System handle // Status = Volume->OpenVolume (Volume, &FileHandle); if (EFI_ERROR (Status)) { return Status; } *DeviceHandle = Handle; if (IsDevicePathEnd(DevicePathNode)) { *ParentFileName = AllocateCopyPool (StrSize (L"\\"), L"\\"); *RetFileHandle = FileHandle; return EFI_SUCCESS; } // // Duplicate the device path to avoid the access to unaligned device path node. // Because the device path consists of one or more FILE PATH MEDIA DEVICE PATH // nodes, It assures the fields in device path nodes are 2 byte aligned. // TempDevicePathNode = DuplicateDevicePath (DevicePathNode); if (TempDevicePathNode == NULL) { // // Setting Status to an EFI_ERROR value will cause the rest of // the file system support below to be skipped. // Status = EFI_OUT_OF_RESOURCES; goto Done; } // // Parse each MEDIA_FILEPATH_DP node. There may be more than one, since the // directory information and filename can be seperate. The goal is to inch // our way down each device path node and close the previous node // DevicePathNode = TempDevicePathNode; while (!EFI_ERROR (Status) && !IsDevicePathEnd (DevicePathNode)) { if (DevicePathType (DevicePathNode) != MEDIA_DEVICE_PATH || DevicePathSubType (DevicePathNode) != MEDIA_FILEPATH_DP) { Status = EFI_UNSUPPORTED; goto Done; } LastHandle = FileHandle; FileHandle = NULL; Status = LastHandle->Open ( LastHandle, &FileHandle, ((FILEPATH_DEVICE_PATH *) DevicePathNode)->PathName, EFI_FILE_MODE_READ, 0 ); if (*ParentFileName == NULL) { *ParentFileName = AllocateCopyPool (StrSize (((FILEPATH_DEVICE_PATH *) DevicePathNode)->PathName), ((FILEPATH_DEVICE_PATH *) DevicePathNode)->PathName); } else { TempPath = LibAppendFileName (*ParentFileName, ((FILEPATH_DEVICE_PATH *) DevicePathNode)->PathName); if (TempPath == NULL) { LastHandle->Close (LastHandle); Status = EFI_OUT_OF_RESOURCES; goto Done; } FreePool (*ParentFileName); *ParentFileName = TempPath; } // // Close the previous node // LastHandle->Close (LastHandle); DevicePathNode = NextDevicePathNode (DevicePathNode); } if (EFI_ERROR (Status)) { goto Done; } *RetFileHandle = FileHandle; Status = EFI_SUCCESS; Done: if (TempDevicePathNode != NULL) { FreePool (TempDevicePathNode); } if ((FileHandle != NULL) && (EFI_ERROR (Status))) { FileHandle->Close (FileHandle); } return Status; } /** Create a new file or folder in current directory. @param FileName Point to the fileNmae or folder name. @param CreateFile CreateFile== TRUE means create a new file. CreateFile== FALSE means create a new Folder. **/ EFI_STATUS LibCreateNewFile ( IN CHAR16 *FileName, IN BOOLEAN CreateFile ) { EFI_FILE_HANDLE FileHandle; EFI_FILE_HANDLE NewHandle; EFI_HANDLE DeviceHandle; EFI_STATUS Status; CHAR16 *ParentName; CHAR16 *FullFileName; NewHandle = NULL; FullFileName = NULL; LibGetFileHandleFromDevicePath(gFileExplorerPrivate.RetDevicePath, &FileHandle, &ParentName, &DeviceHandle); FullFileName = LibAppendFileName (ParentName, FileName); if (FullFileName == NULL) { return EFI_OUT_OF_RESOURCES; } if (CreateFile) { Status = FileHandle->Open( FileHandle, &NewHandle, FullFileName, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE| EFI_FILE_MODE_CREATE, 0 ); if (EFI_ERROR (Status)) { FileHandle->Close (FileHandle); return Status; } } else { Status = FileHandle->Open( FileHandle, &NewHandle, FullFileName, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE| EFI_FILE_MODE_CREATE, EFI_FILE_DIRECTORY ); if (EFI_ERROR (Status)) { FileHandle->Close (FileHandle); return Status; } } FileHandle->Close (FileHandle); // // Return the DevicePath of the new created file or folder. // gFileExplorerPrivate.RetDevicePath = FileDevicePath (DeviceHandle, FullFileName); return EFI_SUCCESS; } /** Find files under current directory. All files and sub-directories in current directory will be stored in DirectoryMenu for future use. @param FileHandle Parent file handle. @param FileName Parent file name. @param DeviceHandle Driver handle for this partition. @retval EFI_SUCCESS Get files from current dir successfully. @return Other value if can't get files from current dir. **/ EFI_STATUS LibFindFiles ( IN EFI_FILE_HANDLE FileHandle, IN UINT16 *FileName, IN EFI_HANDLE DeviceHandle ) { EFI_FILE_INFO *DirInfo; UINTN BufferSize; UINTN DirBufferSize; MENU_ENTRY *NewMenuEntry; FILE_CONTEXT *NewFileContext; UINTN Pass; EFI_STATUS Status; UINTN OptionNumber; OptionNumber = 0; DirBufferSize = sizeof (EFI_FILE_INFO) + 1024; DirInfo = AllocateZeroPool (DirBufferSize); if (DirInfo == NULL) { return EFI_OUT_OF_RESOURCES; } // // Get all files in current directory // Pass 1 to get Directories // Pass 2 to get files that are EFI images // Status = EFI_SUCCESS; for (Pass = 1; Pass <= 2; Pass++) { FileHandle->SetPosition (FileHandle, 0); for (;;) { BufferSize = DirBufferSize; Status = FileHandle->Read (FileHandle, &BufferSize, DirInfo); if (EFI_ERROR (Status) || BufferSize == 0) { Status = EFI_SUCCESS; break; } if (((DirInfo->Attribute & EFI_FILE_DIRECTORY) != 0 && Pass == 2) || ((DirInfo->Attribute & EFI_FILE_DIRECTORY) == 0 && Pass == 1) ) { // // Pass 1 is for Directories // Pass 2 is for file names // continue; } if (!((DirInfo->Attribute & EFI_FILE_DIRECTORY) != 0 || LibIsSupportedFileType (DirInfo->FileName))) { // // Slip file unless it is a directory entry or a .EFI file // continue; } NewMenuEntry = LibCreateMenuEntry (); if (NULL == NewMenuEntry) { Status = EFI_OUT_OF_RESOURCES; goto Done; } NewFileContext = (FILE_CONTEXT *) NewMenuEntry->VariableContext; NewFileContext->DeviceHandle = DeviceHandle; NewFileContext->FileName = LibAppendFileName (FileName, DirInfo->FileName); if (NewFileContext->FileName == NULL) { LibDestroyMenuEntry (NewMenuEntry); Status = EFI_OUT_OF_RESOURCES; goto Done; } NewFileContext->FileHandle = FileHandle; NewFileContext->DevicePath = FileDevicePath (NewFileContext->DeviceHandle, NewFileContext->FileName); NewMenuEntry->HelpString = NULL; NewFileContext->IsDir = (BOOLEAN) ((DirInfo->Attribute & EFI_FILE_DIRECTORY) == EFI_FILE_DIRECTORY); if (NewFileContext->IsDir) { BufferSize = StrLen (DirInfo->FileName) * 2 + 6; NewMenuEntry->DisplayString = AllocateZeroPool (BufferSize); UnicodeSPrint ( NewMenuEntry->DisplayString, BufferSize, L"<%s>", DirInfo->FileName ); } else { NewMenuEntry->DisplayString = LibStrDuplicate (DirInfo->FileName); } NewMenuEntry->DisplayStringToken = HiiSetString ( gFileExplorerPrivate.FeHiiHandle, 0, NewMenuEntry->DisplayString, NULL ); NewFileContext->IsRoot = FALSE; OptionNumber++; InsertTailList (&gFileExplorerPrivate.FsOptionMenu->Head, &NewMenuEntry->Link); } } gFileExplorerPrivate.FsOptionMenu->MenuNumber = OptionNumber; Done: FreePool (DirInfo); return Status; } /** Refresh the global UpdateData structure. **/ VOID LibRefreshUpdateData ( VOID ) { // // Free current updated date // if (mLibStartOpCodeHandle != NULL) { HiiFreeOpCodeHandle (mLibStartOpCodeHandle); } if (mLibEndOpCodeHandle != NULL) { HiiFreeOpCodeHandle (mLibEndOpCodeHandle); } // // Create new OpCode Handle // mLibStartOpCodeHandle = HiiAllocateOpCodeHandle (); mLibEndOpCodeHandle = HiiAllocateOpCodeHandle (); // // Create Hii Extend Label OpCode as the start opcode // mLibStartLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode ( mLibStartOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL) ); mLibStartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL; mLibStartLabel->Number = FORM_FILE_EXPLORER_ID; // // Create Hii Extend Label OpCode as the start opcode // mLibEndLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode ( mLibEndOpCodeHandle, &gEfiIfrTianoGuid, NULL, sizeof (EFI_IFR_GUID_LABEL) ); mLibEndLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL; mLibEndLabel->Number = LABEL_END; } /** Update the File Explore page. **/ VOID LibUpdateFileExplorePage ( VOID ) { UINTN Index; MENU_ENTRY *NewMenuEntry; FILE_CONTEXT *NewFileContext; MENU_OPTION *MenuOption; BOOLEAN CreateNewFile; NewMenuEntry = NULL; NewFileContext = NULL; CreateNewFile = FALSE; LibRefreshUpdateData (); MenuOption = gFileExplorerPrivate.FsOptionMenu; mQuestionIdUpdate += QUESTION_ID_UPDATE_STEP; for (Index = 0; Index < MenuOption->MenuNumber; Index++) { NewMenuEntry = LibGetMenuEntry (MenuOption, Index); NewFileContext = (FILE_CONTEXT *) NewMenuEntry->VariableContext; if (!NewFileContext->IsRoot && !CreateNewFile) { HiiCreateGotoOpCode ( mLibStartOpCodeHandle, FORM_ADD_NEW_FILE_ID, STRING_TOKEN (STR_NEW_FILE), STRING_TOKEN (STR_NEW_FILE_HELP), EFI_IFR_FLAG_CALLBACK, (UINT16) (mNewFileQuestionId++) ); HiiCreateGotoOpCode ( mLibStartOpCodeHandle, FORM_ADD_NEW_FOLDER_ID, STRING_TOKEN (STR_NEW_FOLDER), STRING_TOKEN (STR_NEW_FOLDER_HELP), EFI_IFR_FLAG_CALLBACK, (UINT16) (mNewFolderQuestionId++) ); HiiCreateTextOpCode( mLibStartOpCodeHandle, STRING_TOKEN (STR_NULL_STRING), STRING_TOKEN (STR_NULL_STRING), 0 ); CreateNewFile = TRUE; } if (!NewFileContext->IsDir) { // // Create Text opcode for directory, also create Text opcode for file in FileExplorerStateBootFromFile. // HiiCreateActionOpCode ( mLibStartOpCodeHandle, (UINT16) (FILE_OPTION_OFFSET + Index + mQuestionIdUpdate), NewMenuEntry->DisplayStringToken, STRING_TOKEN (STR_NULL_STRING), EFI_IFR_FLAG_CALLBACK, 0 ); } else { // // Create Goto opcode for file in FileExplorerStateAddBootOption or FileExplorerStateAddDriverOptionState. // HiiCreateGotoOpCode ( mLibStartOpCodeHandle, FORM_FILE_EXPLORER_ID, NewMenuEntry->DisplayStringToken, STRING_TOKEN (STR_NULL_STRING), EFI_IFR_FLAG_CALLBACK, (UINT16) (FILE_OPTION_OFFSET + Index + mQuestionIdUpdate) ); } } HiiUpdateForm ( gFileExplorerPrivate.FeHiiHandle, &FileExplorerGuid, FORM_FILE_EXPLORER_ID, mLibStartOpCodeHandle, // Label FORM_FILE_EXPLORER_ID mLibEndOpCodeHandle // LABEL_END ); } /** Update the file explower page with the refershed file system. @param KeyValue Key value to identify the type of data to expect. @retval EFI_SUCCESS Update the file explorer form success. @retval other errors Error occur when parse one directory. **/ EFI_STATUS LibUpdateFileExplorer ( IN UINT16 KeyValue ) { UINT16 FileOptionMask; MENU_ENTRY *NewMenuEntry; FILE_CONTEXT *NewFileContext; EFI_STATUS Status; EFI_FILE_HANDLE FileHandle; Status = EFI_SUCCESS; FileOptionMask = (UINT16) (FILE_OPTION_MASK & KeyValue) - mQuestionIdUpdate; NewMenuEntry = LibGetMenuEntry (gFileExplorerPrivate.FsOptionMenu, FileOptionMask); NewFileContext = (FILE_CONTEXT *) NewMenuEntry->VariableContext; if (NewFileContext->IsDir) { RemoveEntryList (&NewMenuEntry->Link); LibFreeMenu (gFileExplorerPrivate.FsOptionMenu); LibGetFileHandleFromMenu (NewMenuEntry, &FileHandle); Status = LibFindFiles (FileHandle, NewFileContext->FileName, NewFileContext->DeviceHandle); if (!EFI_ERROR (Status)) { LibUpdateFileExplorePage (); } else { LibFreeMenu (gFileExplorerPrivate.FsOptionMenu); } LibDestroyMenuEntry (NewMenuEntry); } return Status; } /** Get the device path info saved in the menu structure. @param KeyValue Key value to identify the type of data to expect. **/ VOID LibGetDevicePath ( IN UINT16 KeyValue ) { UINT16 FileOptionMask; MENU_ENTRY *NewMenuEntry; FILE_CONTEXT *NewFileContext; FileOptionMask = (UINT16) (FILE_OPTION_MASK & KeyValue) - mQuestionIdUpdate; NewMenuEntry = LibGetMenuEntry (gFileExplorerPrivate.FsOptionMenu, FileOptionMask); NewFileContext = (FILE_CONTEXT *) NewMenuEntry->VariableContext; if (gFileExplorerPrivate.RetDevicePath != NULL) { FreePool (gFileExplorerPrivate.RetDevicePath); } gFileExplorerPrivate.RetDevicePath = DuplicateDevicePath (NewFileContext->DevicePath); } /** Choose a file in the specified directory. If user input NULL for the RootDirectory, will choose file in the system. If user input *File != NULL, function will return the allocate device path info for the choosed file, caller has to free the memory after use it. @param RootDirectory Pointer to the root directory. @param FileType The file type need to choose. @param ChooseHandler Function pointer to the extra task need to do after choose one file. @param File Return the device path for the last time chosed file. @retval EFI_SUCESS Choose file success. @retval EFI_INVALID_PARAMETER Both ChooseHandler and return device path are NULL One of them must not NULL. @retval Other errors Choose file failed. **/ EFI_STATUS EFIAPI ChooseFile ( IN EFI_DEVICE_PATH_PROTOCOL *RootDirectory, IN CHAR16 *FileType, OPTIONAL IN CHOOSE_HANDLER ChooseHandler, OPTIONAL OUT EFI_DEVICE_PATH_PROTOCOL **File OPTIONAL ) { EFI_FILE_HANDLE FileHandle; EFI_STATUS Status; UINT16 *FileName; EFI_HANDLE DeviceHandle; if ((ChooseHandler == NULL) && (File == NULL)) { return EFI_INVALID_PARAMETER; } mQuestionIdUpdate = 0; FileName = NULL; gFileExplorerPrivate.RetDevicePath = NULL; gFileExplorerPrivate.ChooseHandler = ChooseHandler; if (FileType != NULL) { gFileExplorerPrivate.FileType = AllocateCopyPool (StrSize (FileType), FileType); ASSERT(gFileExplorerPrivate.FileType != NULL); LibToLowerString(gFileExplorerPrivate.FileType); } else { gFileExplorerPrivate.FileType = NULL; } if (RootDirectory == NULL) { Status = LibFindFileSystem(); } else { Status = LibGetFileHandleFromDevicePath(RootDirectory, &FileHandle, &FileName, &DeviceHandle); if (EFI_ERROR (Status)) { goto Done; } Status = LibFindFiles (FileHandle, FileName, DeviceHandle); } if (EFI_ERROR (Status)) { goto Done; } LibUpdateFileExplorePage(); gFileExplorerPrivate.FormBrowser2->SendForm ( gFileExplorerPrivate.FormBrowser2, &gFileExplorerPrivate.FeHiiHandle, 1, &FileExplorerGuid, 0, NULL, NULL ); Done: if ((Status == EFI_SUCCESS) && (File != NULL)) { *File = gFileExplorerPrivate.RetDevicePath; } else if (gFileExplorerPrivate.RetDevicePath != NULL) { FreePool (gFileExplorerPrivate.RetDevicePath); } if (gFileExplorerPrivate.FileType != NULL) { FreePool (gFileExplorerPrivate.FileType); } LibFreeMenu (gFileExplorerPrivate.FsOptionMenu); if (FileName != NULL) { FreePool (FileName); } return Status; } /** Install Boot Manager Menu driver. @param ImageHandle The image handle. @param SystemTable The system table. @retval EFI_SUCEESS Install File explorer library success. **/ EFI_STATUS EFIAPI FileExplorerLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; gHiiVendorDevicePath = (HII_VENDOR_DEVICE_PATH*) DuplicateDevicePath ((EFI_DEVICE_PATH_PROTOCOL*)&FeHiiVendorDevicePath); ASSERT (gHiiVendorDevicePath != NULL); CopyGuid (&gHiiVendorDevicePath->VendorDevicePath.Guid, &gEfiCallerIdGuid); // // Install Device Path Protocol and Config Access protocol to driver handle // Status = gBS->InstallMultipleProtocolInterfaces ( &gFileExplorerPrivate.FeDriverHandle, &gEfiDevicePathProtocolGuid, gHiiVendorDevicePath, &gEfiHiiConfigAccessProtocolGuid, &gFileExplorerPrivate.FeConfigAccess, NULL ); if (Status == EFI_ALREADY_STARTED) { return EFI_SUCCESS; } if (EFI_ERROR (Status)) { return Status; } // // Post our File Explorer VFR binary to the HII database. // gFileExplorerPrivate.FeHiiHandle = HiiAddPackages ( &FileExplorerGuid, gFileExplorerPrivate.FeDriverHandle, FileExplorerVfrBin, FileExplorerLibStrings, NULL ); ASSERT (gFileExplorerPrivate.FeHiiHandle != NULL); // // Locate Formbrowser2 protocol // Status = gBS->LocateProtocol (&gEfiFormBrowser2ProtocolGuid, NULL, (VOID **) &gFileExplorerPrivate.FormBrowser2); ASSERT_EFI_ERROR (Status); InitializeListHead (&gFileExplorerPrivate.FsOptionMenu->Head); return EFI_SUCCESS; } /** Unloads the application and its installed protocol. @param[in] ImageHandle Handle that identifies the image to be unloaded. @param[in] SystemTable The system table. @retval EFI_SUCCESS The image has been unloaded. **/ EFI_STATUS EFIAPI FileExplorerLibDestructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; ASSERT (gHiiVendorDevicePath != NULL); if (gFileExplorerPrivate.FeDriverHandle != NULL) { Status = gBS->UninstallMultipleProtocolInterfaces ( gFileExplorerPrivate.FeDriverHandle, &gEfiDevicePathProtocolGuid, gHiiVendorDevicePath, &gEfiHiiConfigAccessProtocolGuid, &gFileExplorerPrivate.FeConfigAccess, NULL ); ASSERT_EFI_ERROR (Status); HiiRemovePackages (gFileExplorerPrivate.FeHiiHandle); gFileExplorerPrivate.FeDriverHandle = NULL; } FreePool (gHiiVendorDevicePath); return EFI_SUCCESS; }