/** @file | |
File IO routines inspired by Streams with an EFI flavor | |
Copyright (c) 2007, Intel Corporation. All rights reserved.<BR> | |
Portions copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR> | |
This program and the accompanying materials | |
are licensed and made available under the terms and conditions of the BSD License | |
which accompanies this distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
Basic support for opening files on different device types. The device string | |
is in the form of DevType:Path. Current DevType is required as there is no | |
current mounted device concept of current working directory concept implement | |
by this library. | |
Device names are case insensitive and only check the leading characters for | |
unique matches. Thus the following are all the same: | |
LoadFile0: | |
l0: | |
L0: | |
Lo0: | |
Supported Device Names: | |
A0x1234:0x12 - A memory buffer starting at address 0x1234 for 0x12 bytes | |
l1: - EFI LoadFile device one. | |
B0: - EFI BlockIo zero. | |
fs3: - EFI Simple File System device 3 | |
Fv2: - EFI Firmware VOlume device 2 | |
10.0.1.102: - TFTP service IP followed by the file name | |
**/ | |
#include <PiDxe.h> | |
#include <Protocol/BlockIo.h> | |
#include <Protocol/DiskIo.h> | |
#include <Protocol/SimpleFileSystem.h> | |
#include <Protocol/FirmwareVolume2.h> | |
#include <Protocol/LoadFile.h> | |
#include <Protocol/FirmwareVolumeBlock.h> | |
#include <Guid/FileInfo.h> | |
#include <Library/BaseLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/PrintLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/UefiLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiRuntimeServicesTableLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/EfiFileLib.h> | |
#include <Library/PcdLib.h> | |
#include <Library/EblNetworkLib.h> | |
CHAR8 *gCwd = NULL; | |
CONST EFI_GUID gZeroGuid = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } }; | |
#define EFI_OPEN_FILE_GUARD_HEADER 0x4B4D4641 | |
#define EFI_OPEN_FILE_GUARD_FOOTER 0x444D5A56 | |
// Need to defend against this overflowing | |
#define MAX_CMD_LINE 0x200 | |
typedef struct { | |
UINT32 Header; | |
EFI_OPEN_FILE File; | |
UINT32 Footer; | |
} EFI_OPEN_FILE_GUARD; | |
// globals to store current open device info | |
EFI_HANDLE *mBlkIo = NULL; | |
UINTN mBlkIoCount = 0; | |
EFI_HANDLE *mFs = NULL; | |
UINTN mFsCount = 0; | |
// mFsInfo[] array entries must match mFs[] handles | |
EFI_FILE_SYSTEM_INFO **mFsInfo = NULL; | |
EFI_HANDLE *mFv = NULL; | |
UINTN mFvCount = 0; | |
EFI_HANDLE *mLoadFile = NULL; | |
UINTN mLoadFileCount = 0; | |
/** | |
Internal worker function to validate a File handle. | |
@param File Open File Handle | |
@return TRUE File is valid | |
@return FALSE File is not valid | |
**/ | |
BOOLEAN | |
FileHandleValid ( | |
IN EFI_OPEN_FILE *File | |
) | |
{ | |
EFI_OPEN_FILE_GUARD *GuardFile; | |
// Look right before and after file structure for the correct signatures | |
GuardFile = BASE_CR (File, EFI_OPEN_FILE_GUARD, File); | |
if ((GuardFile->Header != EFI_OPEN_FILE_GUARD_HEADER) || | |
(GuardFile->Footer != EFI_OPEN_FILE_GUARD_FOOTER) ) { | |
return FALSE; | |
} | |
return TRUE; | |
} | |
/** | |
Internal worker function. If Buffer is not NULL free it. | |
@param Buffer Buffer to FreePool() | |
**/ | |
VOID | |
EblFreePool ( | |
IN VOID *Buffer | |
) | |
{ | |
if (Buffer != NULL) { | |
FreePool (Buffer); | |
} | |
} | |
/** | |
Update Device List Global Variables | |
**/ | |
VOID | |
EblUpdateDeviceLists ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Size; | |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; | |
EFI_FILE_HANDLE Root; | |
UINTN Index; | |
if (mBlkIo != NULL) { | |
FreePool (mBlkIo); | |
} | |
gBS->LocateHandleBuffer (ByProtocol, &gEfiBlockIoProtocolGuid, NULL, &mBlkIoCount, &mBlkIo); | |
if (mFv != NULL) { | |
FreePool (mFv); | |
} | |
gBS->LocateHandleBuffer (ByProtocol, &gEfiFirmwareVolume2ProtocolGuid, NULL, &mFvCount, &mFv); | |
if (mLoadFile != NULL) { | |
FreePool (mLoadFile); | |
} | |
gBS->LocateHandleBuffer (ByProtocol, &gEfiLoadFileProtocolGuid, NULL, &mLoadFileCount, &mLoadFile); | |
if (mFs != NULL) { | |
FreePool (mFs); | |
} | |
if (&mFsInfo[0] != NULL) { | |
// Need to Free the mFsInfo prior to recalculating mFsCount so don't move this code | |
for (Index = 0; Index < mFsCount; Index++) { | |
if (mFsInfo[Index] != NULL) { | |
FreePool (mFsInfo[Index]); | |
} | |
} | |
FreePool (mFsInfo); | |
} | |
gBS->LocateHandleBuffer (ByProtocol, &gEfiSimpleFileSystemProtocolGuid, NULL, &mFsCount, &mFs); | |
mFsInfo = AllocateZeroPool (mFsCount * sizeof (EFI_FILE_SYSTEM_INFO *)); | |
if (mFsInfo == NULL) { | |
// If we can't do this then we can't support file system entries | |
mFsCount = 0; | |
} else { | |
// Loop through all the file system structures and cache the file system info data | |
for (Index =0; Index < mFsCount; Index++) { | |
Status = gBS->HandleProtocol (mFs[Index], &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); | |
if (!EFI_ERROR (Status)) { | |
Status = Fs->OpenVolume (Fs, &Root); | |
if (!EFI_ERROR (Status)) { | |
// Get information about the volume | |
Size = 0; | |
Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, mFsInfo[Index]); | |
if (Status == EFI_BUFFER_TOO_SMALL) { | |
mFsInfo[Index] = AllocatePool (Size); | |
Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, mFsInfo[Index]); | |
} | |
Root->Close (Root); | |
} | |
} | |
} | |
} | |
} | |
/** | |
PathName is in the form <device name>:<path> for example fs1:\ or ROOT:\. | |
Return TRUE if the <devce name> prefix of PathName matches a file system | |
Volume Name. MatchIndex is the array index in mFsInfo[] of the match, | |
and it can be used with mFs[] to find the handle that needs to be opened | |
@param PathName PathName to check | |
@param FileStart Index of the first character of the <path> | |
@param MatchIndex Index in mFsInfo[] that matches | |
@return TRUE PathName matches a Volume Label and MatchIndex is valid | |
@return FALSE PathName does not match a Volume Label MatchIndex undefined | |
**/ | |
BOOLEAN | |
EblMatchVolumeName ( | |
IN CHAR8 *PathName, | |
IN UINTN FileStart, | |
OUT UINTN *MatchIndex | |
) | |
{ | |
UINTN Index; | |
UINTN Compare; | |
UINTN VolStrLen; | |
BOOLEAN Match; | |
for (Index =0; Index < mFsCount; Index++) { | |
if (mFsInfo[Index] == NULL) { | |
// FsInfo is not valid so skip it | |
continue; | |
} | |
VolStrLen = StrLen (mFsInfo[Index]->VolumeLabel); | |
for (Compare = 0, Match = TRUE; Compare < (FileStart - 1); Compare++) { | |
if (Compare > VolStrLen) { | |
Match = FALSE; | |
break; | |
} | |
if (PathName[Compare] != (CHAR8)mFsInfo[Index]->VolumeLabel[Compare]) { | |
// If the VolumeLabel has a space allow a _ to match with it in addition to ' ' | |
if (!((PathName[Compare] == '_') && (mFsInfo[Index]->VolumeLabel[Compare] == L' '))) { | |
Match = FALSE; | |
break; | |
} | |
} | |
} | |
if (Match) { | |
*MatchIndex = Index; | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/** | |
Return the number of devices of the current type active in the system | |
@param Type Device type to check | |
@return 0 Invalid type | |
**/ | |
UINTN | |
EfiGetDeviceCounts ( | |
IN EFI_OPEN_FILE_TYPE DeviceType | |
) | |
{ | |
switch (DeviceType) { | |
case EfiOpenLoadFile: | |
return mLoadFileCount; | |
case EfiOpenFirmwareVolume: | |
return mFvCount; | |
case EfiOpenFileSystem: | |
return mFsCount; | |
case EfiOpenBlockIo: | |
return mBlkIoCount; | |
default: | |
return 0; | |
} | |
} | |
EFI_STATUS | |
ConvertIpStringToEfiIp ( | |
IN CHAR8 *PathName, | |
OUT EFI_IP_ADDRESS *ServerIp | |
) | |
{ | |
CHAR8 *Str; | |
Str = PathName; | |
ServerIp->v4.Addr[0] = (UINT8)AsciiStrDecimalToUintn (Str); | |
Str = AsciiStrStr (Str, "."); | |
if (Str == NULL) { | |
return EFI_DEVICE_ERROR; | |
} | |
ServerIp->v4.Addr[1] = (UINT8)AsciiStrDecimalToUintn (++Str); | |
Str = AsciiStrStr (Str, "."); | |
if (Str == NULL) { | |
return EFI_DEVICE_ERROR; | |
} | |
ServerIp->v4.Addr[2] = (UINT8)AsciiStrDecimalToUintn (++Str); | |
Str = AsciiStrStr (Str, "."); | |
if (Str == NULL) { | |
return EFI_DEVICE_ERROR; | |
} | |
ServerIp->v4.Addr[3] = (UINT8)AsciiStrDecimalToUintn (++Str); | |
return EFI_SUCCESS; | |
} | |
/** | |
Internal work function to extract a device number from a string skipping | |
text. Easy way to extract numbers from strings like blk7:. | |
@param Str String to extract device number form | |
@return -1 Device string is not valid | |
@return Device # | |
**/ | |
UINTN | |
EblConvertDevStringToNumber ( | |
IN CHAR8 *Str | |
) | |
{ | |
UINTN Max; | |
UINTN Index; | |
// Find the first digit | |
Max = AsciiStrLen (Str); | |
for (Index = 0; !((*Str >= '0') && (*Str <= '9')) && (Index < Max); Index++) { | |
Str++; | |
} | |
if (Index == Max) { | |
return (UINTN)-1; | |
} | |
return AsciiStrDecimalToUintn (Str); | |
} | |
/** | |
Internal work function to fill in EFI_OPEN_FILE information for the Fs and BlkIo | |
@param File Open file handle | |
@param FileName Name of file after device stripped off | |
**/ | |
EFI_STATUS | |
EblFileDevicePath ( | |
IN OUT EFI_OPEN_FILE *File, | |
IN CHAR8 *FileName, | |
IN CONST UINT64 OpenMode | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Size; | |
FILEPATH_DEVICE_PATH *FilePath; | |
EFI_DEVICE_PATH_PROTOCOL *FileDevicePath; | |
CHAR16 UnicodeFileName[MAX_PATHNAME]; | |
EFI_BLOCK_IO_PROTOCOL *BlkIo; | |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; | |
EFI_FILE_HANDLE Root; | |
if ( *FileName != 0 ) { | |
AsciiStrToUnicodeStr (FileName, UnicodeFileName); | |
} else { | |
AsciiStrToUnicodeStr ("\\", UnicodeFileName); | |
} | |
Size = StrSize (UnicodeFileName); | |
FileDevicePath = AllocatePool (Size + SIZE_OF_FILEPATH_DEVICE_PATH + sizeof (EFI_DEVICE_PATH_PROTOCOL)); | |
if (FileDevicePath != NULL) { | |
FilePath = (FILEPATH_DEVICE_PATH *) FileDevicePath; | |
FilePath->Header.Type = MEDIA_DEVICE_PATH; | |
FilePath->Header.SubType = MEDIA_FILEPATH_DP; | |
CopyMem (&FilePath->PathName, UnicodeFileName, Size); | |
SetDevicePathNodeLength (&FilePath->Header, Size + SIZE_OF_FILEPATH_DEVICE_PATH); | |
SetDevicePathEndNode (NextDevicePathNode (&FilePath->Header)); | |
if (File->EfiHandle != NULL) { | |
File->DevicePath = DevicePathFromHandle (File->EfiHandle); | |
} | |
File->DevicePath = AppendDevicePath (File->DevicePath, FileDevicePath); | |
FreePool (FileDevicePath); | |
} | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiBlockIoProtocolGuid, (VOID **)&BlkIo); | |
if (!EFI_ERROR (Status)) { | |
File->FsBlockIoMedia = BlkIo->Media; | |
File->FsBlockIo = BlkIo; | |
// If we are not opening the device this will get over written with file info | |
File->MaxPosition = MultU64x32 (BlkIo->Media->LastBlock + 1, BlkIo->Media->BlockSize); | |
} | |
if (File->Type == EfiOpenFileSystem) { | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); | |
if (!EFI_ERROR (Status)) { | |
Status = Fs->OpenVolume (Fs, &Root); | |
if (!EFI_ERROR (Status)) { | |
// Get information about the volume | |
Size = 0; | |
Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, File->FsInfo); | |
if (Status == EFI_BUFFER_TOO_SMALL) { | |
File->FsInfo = AllocatePool (Size); | |
Status = Root->GetInfo (Root, &gEfiFileSystemInfoGuid, &Size, File->FsInfo); | |
} | |
// Get information about the file | |
Status = Root->Open (Root, &File->FsFileHandle, UnicodeFileName, OpenMode, 0); | |
if (!EFI_ERROR (Status)) { | |
Size = 0; | |
Status = File->FsFileHandle->GetInfo (File->FsFileHandle, &gEfiFileInfoGuid, &Size, NULL); | |
if (Status == EFI_BUFFER_TOO_SMALL) { | |
File->FsFileInfo = AllocatePool (Size); | |
Status = File->FsFileHandle->GetInfo (File->FsFileHandle, &gEfiFileInfoGuid, &Size, File->FsFileInfo); | |
if (!EFI_ERROR (Status)) { | |
File->Size = (UINTN)File->FsFileInfo->FileSize; | |
File->MaxPosition = (UINT64)File->Size; | |
} | |
} | |
} | |
Root->Close (Root); | |
} | |
} | |
} else if (File->Type == EfiOpenBlockIo) { | |
File->Size = (UINTN)File->MaxPosition; | |
} | |
return Status; | |
} | |
#define ToUpper(a) ((((a) >= 'a') && ((a) <= 'z')) ? ((a) - 'a' + 'A') : (a)) | |
EFI_STATUS | |
CompareGuidToString ( | |
IN EFI_GUID *Guid, | |
IN CHAR8 *String | |
) | |
{ | |
CHAR8 AsciiGuid[64]; | |
CHAR8 *StringPtr; | |
CHAR8 *GuidPtr; | |
AsciiSPrint (AsciiGuid, sizeof(AsciiGuid), "%g", Guid); | |
StringPtr = String; | |
GuidPtr = AsciiGuid; | |
while ((*StringPtr != '\0') && (*GuidPtr != '\0')) { | |
// Skip dashes | |
if (*StringPtr == '-') { | |
StringPtr++; | |
continue; | |
} | |
if (*GuidPtr == '-') { | |
GuidPtr++; | |
continue; | |
} | |
if (ToUpper(*StringPtr) != ToUpper(*GuidPtr)) { | |
return EFI_NOT_FOUND; | |
} | |
StringPtr++; | |
GuidPtr++; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Internal work function to fill in EFI_OPEN_FILE information for the FV | |
@param File Open file handle | |
@param FileName Name of file after device stripped off | |
**/ | |
EFI_STATUS | |
EblFvFileDevicePath ( | |
IN OUT EFI_OPEN_FILE *File, | |
IN CHAR8 *FileName, | |
IN CONST UINT64 OpenMode | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS GetNextFileStatus; | |
MEDIA_FW_VOL_FILEPATH_DEVICE_PATH DevicePathNode; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
UINTN Key; | |
UINT32 AuthenticationStatus; | |
CHAR8 AsciiSection[MAX_PATHNAME]; | |
VOID *Section; | |
UINTN SectionSize; | |
EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; | |
EFI_LBA Lba; | |
UINTN BlockSize; | |
UINTN NumberOfBlocks; | |
EFI_FIRMWARE_VOLUME_HEADER *FvHeader = NULL; | |
UINTN Index; | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&File->Fv); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Get FVB Info about the handle | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiFirmwareVolumeBlockProtocolGuid, (VOID **)&Fvb); | |
if (!EFI_ERROR (Status)) { | |
Status = Fvb->GetPhysicalAddress (Fvb, &File->FvStart); | |
if (!EFI_ERROR (Status)) { | |
FvHeader = (EFI_FIRMWARE_VOLUME_HEADER *)(UINTN)File->FvStart; | |
File->FvHeaderSize = sizeof (EFI_FIRMWARE_VOLUME_HEADER); | |
for (Index = 0; FvHeader->BlockMap[Index].Length !=0; Index++) { | |
File->FvHeaderSize += sizeof (EFI_FV_BLOCK_MAP_ENTRY); | |
} | |
for (Lba = 0, File->FvSize = 0, NumberOfBlocks = 0; ; File->FvSize += (BlockSize * NumberOfBlocks), Lba += NumberOfBlocks) { | |
Status = Fvb->GetBlockSize (Fvb, Lba, &BlockSize, &NumberOfBlocks); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
} | |
} | |
} | |
DevicePath = DevicePathFromHandle (File->EfiHandle); | |
if (*FileName == '\0') { | |
File->DevicePath = DuplicateDevicePath (DevicePath); | |
File->Size = File->FvSize; | |
File->MaxPosition = File->Size; | |
} else { | |
Key = 0; | |
do { | |
File->FvType = EFI_FV_FILETYPE_ALL; | |
GetNextFileStatus = File->Fv->GetNextFile ( | |
File->Fv, | |
&Key, | |
&File->FvType, | |
&File->FvNameGuid, | |
&File->FvAttributes, | |
&File->Size | |
); | |
if (!EFI_ERROR (GetNextFileStatus)) { | |
// Compare GUID first | |
Status = CompareGuidToString (&File->FvNameGuid, FileName); | |
if (!EFI_ERROR(Status)) { | |
break; | |
} | |
Section = NULL; | |
Status = File->Fv->ReadSection ( | |
File->Fv, | |
&File->FvNameGuid, | |
EFI_SECTION_USER_INTERFACE, | |
0, | |
&Section, | |
&SectionSize, | |
&AuthenticationStatus | |
); | |
if (!EFI_ERROR (Status)) { | |
UnicodeStrToAsciiStr (Section, AsciiSection); | |
if (AsciiStriCmp (FileName, AsciiSection) == 0) { | |
FreePool (Section); | |
break; | |
} | |
FreePool (Section); | |
} | |
} | |
} while (!EFI_ERROR (GetNextFileStatus)); | |
if (EFI_ERROR (GetNextFileStatus)) { | |
return GetNextFileStatus; | |
} | |
if (OpenMode != EFI_SECTION_ALL) { | |
// Calculate the size of the section we are targeting | |
Section = NULL; | |
File->Size = 0; | |
Status = File->Fv->ReadSection ( | |
File->Fv, | |
&File->FvNameGuid, | |
(EFI_SECTION_TYPE)OpenMode, | |
0, | |
&Section, | |
&File->Size, | |
&AuthenticationStatus | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
File->MaxPosition = File->Size; | |
EfiInitializeFwVolDevicepathNode (&DevicePathNode, &File->FvNameGuid); | |
File->DevicePath = AppendDevicePathNode (DevicePath, (EFI_DEVICE_PATH_PROTOCOL *)&DevicePathNode); | |
} | |
// FVB not required if FV was soft loaded... | |
return EFI_SUCCESS; | |
} | |
/** | |
Open a device named by PathName. The PathName includes a device name and | |
path separated by a :. See file header for more details on the PathName | |
syntax. There is no checking to prevent a file from being opened more than | |
one type. | |
SectionType is only used to open an FV. Each file in an FV contains multiple | |
sections and only the SectionType section is opened. | |
For any file that is opened with EfiOpen() must be closed with EfiClose(). | |
@param PathName Path to parse to open | |
@param OpenMode Same as EFI_FILE.Open() | |
@param SectionType Section in FV to open. | |
@return NULL Open failed | |
@return Valid EFI_OPEN_FILE handle | |
**/ | |
EFI_OPEN_FILE * | |
EfiOpen ( | |
IN CHAR8 *PathName, | |
IN CONST UINT64 OpenMode, | |
IN CONST EFI_SECTION_TYPE SectionType | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_OPEN_FILE *File; | |
EFI_OPEN_FILE FileData; | |
UINTN StrLen; | |
UINTN FileStart; | |
UINTN DevNumber = 0; | |
EFI_OPEN_FILE_GUARD *GuardFile; | |
BOOLEAN VolumeNameMatch; | |
EFI_DEVICE_PATH_PROTOCOL *DevicePath; | |
UINTN Size; | |
EFI_IP_ADDRESS Ip; | |
CHAR8 *CwdPlusPathName; | |
UINTN Index; | |
EFI_SECTION_TYPE ModifiedSectionType; | |
EblUpdateDeviceLists (); | |
File = &FileData; | |
ZeroMem (File, sizeof (EFI_OPEN_FILE)); | |
StrLen = AsciiStrSize (PathName); | |
if (StrLen <= 1) { | |
// Smallest valid path is 1 char and a null | |
return NULL; | |
} | |
for (FileStart = 0; FileStart < StrLen; FileStart++) { | |
if (PathName[FileStart] == ':') { | |
FileStart++; | |
break; | |
} | |
} | |
// | |
// Matching volume name has precedence over handle based names | |
// | |
VolumeNameMatch = EblMatchVolumeName (PathName, FileStart, &DevNumber); | |
if (!VolumeNameMatch) { | |
if (FileStart == StrLen) { | |
// No Volume name or device name, so try Current Working Directory | |
if (gCwd == NULL) { | |
// No CWD | |
return NULL; | |
} | |
// We could add a current working directory concept | |
CwdPlusPathName = AllocatePool (AsciiStrSize (gCwd) + AsciiStrSize (PathName)); | |
if (CwdPlusPathName == NULL) { | |
return NULL; | |
} | |
if ((PathName[0] == '/') || (PathName[0] == '\\')) { | |
// PathName starts in / so this means we go to the root of the device in the CWD. | |
CwdPlusPathName[0] = '\0'; | |
for (FileStart = 0; gCwd[FileStart] != '\0'; FileStart++) { | |
CwdPlusPathName[FileStart] = gCwd[FileStart]; | |
if (gCwd[FileStart] == ':') { | |
FileStart++; | |
CwdPlusPathName[FileStart] = '\0'; | |
break; | |
} | |
} | |
} else { | |
AsciiStrCpy (CwdPlusPathName, gCwd); | |
StrLen = AsciiStrLen (gCwd); | |
if ((*PathName != '/') && (*PathName != '\\') && (gCwd[StrLen-1] != '/') && (gCwd[StrLen-1] != '\\')) { | |
AsciiStrCat (CwdPlusPathName, "\\"); | |
} | |
} | |
AsciiStrCat (CwdPlusPathName, PathName); | |
if (AsciiStrStr (CwdPlusPathName, ":") == NULL) { | |
// Extra error check to make sure we don't recurse and blow stack | |
return NULL; | |
} | |
File = EfiOpen (CwdPlusPathName, OpenMode, SectionType); | |
FreePool (CwdPlusPathName); | |
return File; | |
} | |
DevNumber = EblConvertDevStringToNumber ((CHAR8 *)PathName); | |
} | |
File->DeviceName = AllocatePool (StrLen); | |
AsciiStrCpy (File->DeviceName, PathName); | |
File->DeviceName[FileStart - 1] = '\0'; | |
File->FileName = &File->DeviceName[FileStart]; | |
if (File->FileName[0] == '\0') { | |
// if it is just a file name use / as root | |
File->FileName = "\\"; | |
} | |
// | |
// Use best match algorithm on the dev names so we only need to look at the | |
// first few charters to match the full device name. Short name forms are | |
// legal from the caller. | |
// | |
Status = EFI_SUCCESS; | |
if (*PathName == 'f' || *PathName == 'F' || VolumeNameMatch) { | |
if (PathName[1] == 's' || PathName[1] == 'S' || VolumeNameMatch) { | |
if (DevNumber >= mFsCount) { | |
goto ErrorExit; | |
} | |
File->Type = EfiOpenFileSystem; | |
File->EfiHandle = mFs[DevNumber]; | |
Status = EblFileDevicePath (File, &PathName[FileStart], OpenMode); | |
} else if (PathName[1] == 'v' || PathName[1] == 'V') { | |
if (DevNumber >= mFvCount) { | |
goto ErrorExit; | |
} | |
File->Type = EfiOpenFirmwareVolume; | |
File->EfiHandle = mFv[DevNumber]; | |
if ((PathName[FileStart] == '/') || (PathName[FileStart] == '\\')) { | |
// Skip leading / as its not really needed for the FV since no directories are supported | |
FileStart++; | |
} | |
// Check for 2nd : | |
ModifiedSectionType = SectionType; | |
for (Index = FileStart; PathName[Index] != '\0'; Index++) { | |
if (PathName[Index] == ':') { | |
// Support fv0:\DxeCore:0x10 | |
// This means open the PE32 Section of the file | |
ModifiedSectionType = (EFI_SECTION_TYPE)AsciiStrHexToUintn (&PathName[Index + 1]); | |
PathName[Index] = '\0'; | |
} | |
} | |
File->FvSectionType = ModifiedSectionType; | |
Status = EblFvFileDevicePath (File, &PathName[FileStart], ModifiedSectionType); | |
} | |
} else if ((*PathName == 'A') || (*PathName == 'a')) { | |
// Handle a:0x10000000:0x1234 address form a:ADDRESS:SIZE | |
File->Type = EfiOpenMemoryBuffer; | |
// 1st colon is at PathName[FileStart - 1] | |
File->Buffer = (VOID *)AsciiStrHexToUintn (&PathName[FileStart]); | |
// Find 2nd colon | |
while ((PathName[FileStart] != ':') && (PathName[FileStart] != '\0')) { | |
FileStart++; | |
} | |
// If we ran out of string, there's no extra data | |
if (PathName[FileStart] == '\0') { | |
File->Size = 0; | |
} else { | |
File->Size = AsciiStrHexToUintn (&PathName[FileStart + 1]); | |
} | |
// if there's no number after the second colon, default | |
// the end of memory | |
if (File->Size == 0) { | |
File->Size = (UINTN)(0 - (UINTN)File->Buffer); | |
} | |
File->MaxPosition = File->Size; | |
File->BaseOffset = (UINTN)File->Buffer; | |
} else if (*PathName== 'l' || *PathName == 'L') { | |
if (DevNumber >= mLoadFileCount) { | |
goto ErrorExit; | |
} | |
File->Type = EfiOpenLoadFile; | |
File->EfiHandle = mLoadFile[DevNumber]; | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiLoadFileProtocolGuid, (VOID **)&File->LoadFile); | |
if (EFI_ERROR (Status)) { | |
goto ErrorExit; | |
} | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiDevicePathProtocolGuid, (VOID **)&DevicePath); | |
if (EFI_ERROR (Status)) { | |
goto ErrorExit; | |
} | |
File->DevicePath = DuplicateDevicePath (DevicePath); | |
} else if (*PathName == 'b' || *PathName == 'B') { | |
// Handle b#:0x10000000:0x1234 address form b#:ADDRESS:SIZE | |
if (DevNumber >= mBlkIoCount) { | |
goto ErrorExit; | |
} | |
File->Type = EfiOpenBlockIo; | |
File->EfiHandle = mBlkIo[DevNumber]; | |
EblFileDevicePath (File, "", OpenMode); | |
// 1st colon is at PathName[FileStart - 1] | |
File->DiskOffset = AsciiStrHexToUintn (&PathName[FileStart]); | |
// Find 2nd colon | |
while ((PathName[FileStart] != ':') && (PathName[FileStart] != '\0')) { | |
FileStart++; | |
} | |
// If we ran out of string, there's no extra data | |
if (PathName[FileStart] == '\0') { | |
Size = 0; | |
} else { | |
Size = AsciiStrHexToUintn (&PathName[FileStart + 1]); | |
} | |
// if a zero size is passed in (or the size is left out entirely), | |
// go to the end of the device. | |
if (Size == 0) { | |
File->Size = File->Size - File->DiskOffset; | |
} else { | |
File->Size = Size; | |
} | |
File->MaxPosition = File->Size; | |
File->BaseOffset = File->DiskOffset; | |
} else if ((*PathName) >= '0' && (*PathName <= '9')) { | |
// Get current IP address | |
Status = EblGetCurrentIpAddress (&Ip); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Device IP Address is not configured.\n"); | |
goto ErrorExit; | |
} | |
// Parse X.X.X.X:Filename, only support IPv4 TFTP for now... | |
File->Type = EfiOpenTftp; | |
File->IsDirty = FALSE; | |
File->IsBufferValid = FALSE; | |
Status = ConvertIpStringToEfiIp (PathName, &File->ServerIp); | |
} | |
if (EFI_ERROR (Status)) { | |
goto ErrorExit; | |
} | |
GuardFile = (EFI_OPEN_FILE_GUARD *)AllocateZeroPool (sizeof (EFI_OPEN_FILE_GUARD)); | |
if (GuardFile == NULL) { | |
goto ErrorExit; | |
} | |
GuardFile->Header = EFI_OPEN_FILE_GUARD_HEADER; | |
CopyMem (&(GuardFile->File), &FileData, sizeof (EFI_OPEN_FILE)); | |
GuardFile->Footer = EFI_OPEN_FILE_GUARD_FOOTER; | |
return &(GuardFile->File); | |
ErrorExit: | |
FreePool (File->DeviceName); | |
return NULL; | |
} | |
#define FILE_COPY_CHUNK 0x01000000 | |
EFI_STATUS | |
EfiCopyFile ( | |
IN CHAR8 *DestinationFile, | |
IN CHAR8 *SourceFile | |
) | |
{ | |
EFI_OPEN_FILE *Source = NULL; | |
EFI_OPEN_FILE *Destination = NULL; | |
EFI_STATUS Status = EFI_SUCCESS; | |
VOID *Buffer = NULL; | |
UINTN Size; | |
UINTN Offset; | |
UINTN Chunk = FILE_COPY_CHUNK; | |
Source = EfiOpen (SourceFile, EFI_FILE_MODE_READ, 0); | |
if (Source == NULL) { | |
AsciiPrint("Source file open error.\n"); | |
Status = EFI_NOT_FOUND; | |
goto Exit; | |
} | |
Destination = EfiOpen (DestinationFile, EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, 0); | |
if (Destination == NULL) { | |
AsciiPrint("Destination file open error.\n"); | |
Status = EFI_NOT_FOUND; | |
goto Exit; | |
} | |
Buffer = AllocatePool(FILE_COPY_CHUNK); | |
if (Buffer == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Exit; | |
} | |
Size = EfiTell(Source, NULL); | |
for (Offset = 0; Offset + FILE_COPY_CHUNK <= Size; Offset += Chunk) { | |
Chunk = FILE_COPY_CHUNK; | |
Status = EfiRead(Source, Buffer, &Chunk); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Read file error %r\n", Status); | |
goto Exit; | |
} | |
Status = EfiWrite(Destination, Buffer, &Chunk); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Write file error %r\n", Status); | |
goto Exit; | |
} | |
} | |
// Any left over? | |
if (Offset < Size) { | |
Chunk = Size - Offset; | |
Status = EfiRead(Source, Buffer, &Chunk); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Read file error\n"); | |
goto Exit; | |
} | |
Status = EfiWrite(Destination, Buffer, &Chunk); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Write file error\n"); | |
goto Exit; | |
} | |
} | |
Exit: | |
if (Source != NULL) { | |
Status = EfiClose(Source); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Source close error"); | |
} | |
} | |
if (Destination != NULL) { | |
Status = EfiClose(Destination); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("Destination close error"); | |
} | |
} | |
if (Buffer != NULL) { | |
FreePool(Buffer); | |
} | |
return Status; | |
} | |
/** | |
Use DeviceType and Index to form a valid PathName and try and open it. | |
@param DeviceType Device type to open | |
@param Index Device Index to use. Zero relative. | |
@return NULL Open failed | |
@return Valid EFI_OPEN_FILE handle | |
**/ | |
EFI_OPEN_FILE * | |
EfiDeviceOpenByType ( | |
IN EFI_OPEN_FILE_TYPE DeviceType, | |
IN UINTN Index | |
) | |
{ | |
CHAR8 *DevStr; | |
CHAR8 Path[MAX_CMD_LINE]; | |
switch (DeviceType) { | |
case EfiOpenLoadFile: | |
DevStr = "loadfile%d:"; | |
break; | |
case EfiOpenFirmwareVolume: | |
DevStr = "fv%d:"; | |
break; | |
case EfiOpenFileSystem: | |
DevStr = "fs%d:"; | |
break; | |
case EfiOpenBlockIo: | |
DevStr = "blk%d:"; | |
break; | |
case EfiOpenMemoryBuffer: | |
DevStr = "a%d:"; | |
break; | |
default: | |
return NULL; | |
} | |
AsciiSPrint (Path, MAX_PATHNAME, DevStr, Index); | |
return EfiOpen (Path, EFI_FILE_MODE_READ, 0); | |
} | |
/** | |
Close a file handle opened by EfiOpen() and free all resources allocated by | |
EfiOpen(). | |
@param Stream Open File Handle | |
@return EFI_INVALID_PARAMETER Stream is not an Open File | |
@return EFI_SUCCESS Steam closed | |
**/ | |
EFI_STATUS | |
EfiClose ( | |
IN EFI_OPEN_FILE *File | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 TftpBufferSize; | |
if (!FileHandleValid (File)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
//Write the buffer contents to TFTP file. | |
if ((File->Type == EfiOpenTftp) && (File->IsDirty)) { | |
TftpBufferSize = File->Size; | |
Status = EblMtftp ( | |
EFI_PXE_BASE_CODE_TFTP_WRITE_FILE, | |
File->Buffer, | |
TRUE, | |
&TftpBufferSize, | |
NULL, | |
&File->ServerIp, | |
(UINT8 *)File->FileName, | |
NULL, | |
FALSE | |
); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("TFTP error during APPLE_NSP_TFTP_WRITE_FILE: %r\n", Status); | |
return Status; | |
} | |
} | |
if ((File->Type == EfiOpenLoadFile) || | |
((File->Type == EfiOpenTftp) && (File->IsBufferValid == TRUE)) || | |
((File->Type == EfiOpenFirmwareVolume) && (File->IsBufferValid == TRUE))) { | |
EblFreePool(File->Buffer); | |
} | |
EblFreePool (File->DevicePath); | |
EblFreePool (File->DeviceName); | |
EblFreePool (File->FsFileInfo); | |
EblFreePool (File->FsInfo); | |
if (File->FsFileHandle != NULL) { | |
File->FsFileHandle->Close (File->FsFileHandle); | |
} | |
// Need to free File and it's Guard structures | |
EblFreePool (BASE_CR (File, EFI_OPEN_FILE_GUARD, File)); | |
return EFI_SUCCESS; | |
} | |
/** | |
Return the size of the file represented by Stream. Also return the current | |
Seek position. Opening a file will enable a valid file size to be returned. | |
LoadFile is an exception as a load file size is set to zero. | |
@param Stream Open File Handle | |
@return 0 Stream is not an Open File or a valid LoadFile handle | |
**/ | |
UINTN | |
EfiTell ( | |
IN EFI_OPEN_FILE *File, | |
OUT EFI_LBA *CurrentPosition OPTIONAL | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 BufferSize = 0; | |
if (!FileHandleValid (File)) { | |
return 0; | |
} | |
if (CurrentPosition != NULL) { | |
*CurrentPosition = File->CurrentPosition; | |
} | |
if (File->Type == EfiOpenLoadFile) { | |
// Figure out the File->Size | |
File->Buffer = NULL; | |
File->Size = 0; | |
Status = File->LoadFile->LoadFile (File->LoadFile, File->DevicePath, FALSE, &File->Size, File->Buffer); | |
if (Status != EFI_BUFFER_TOO_SMALL) { | |
return 0; | |
} | |
File->MaxPosition = (UINT64)File->Size; | |
} else if (File->Type == EfiOpenTftp) { | |
Status = EblMtftp ( | |
EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE, | |
NULL, | |
FALSE, | |
&BufferSize, | |
NULL, | |
&File->ServerIp, | |
(UINT8 *)File->FileName, | |
NULL, | |
TRUE | |
); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("TFTP error during APPLE_NSP_TFTP_GET_FILE_SIZE: %r\n", Status); | |
return 0; | |
} | |
File->Size = (UINTN)BufferSize; | |
File->MaxPosition = File->Size; | |
} | |
return File->Size; | |
} | |
/** | |
Seek to the Offset location in the file. LoadFile and FV device types do | |
not support EfiSeek(). It is not possible to grow the file size using | |
EfiSeek(). | |
SeekType defines how use Offset to calculate the new file position: | |
EfiSeekStart : Position = Offset | |
EfiSeekCurrent: Position is Offset bytes from the current position | |
EfiSeekEnd : Only supported if Offset is zero to seek to end of file. | |
@param Stream Open File Handle | |
@param Offset Offset to seek too. | |
@param SeekType Type of seek to perform | |
@return EFI_INVALID_PARAMETER Stream is not an Open File | |
@return EFI_UNSUPPORTED LoadFile and FV do not support Seek | |
@return EFI_NOT_FOUND Seek past the end of the file. | |
@return EFI_SUCCESS Steam closed | |
**/ | |
EFI_STATUS | |
EfiSeek ( | |
IN EFI_OPEN_FILE *File, | |
IN EFI_LBA Offset, | |
IN EFI_SEEK_TYPE SeekType | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 CurrentPosition; | |
if (!FileHandleValid (File)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (File->Type == EfiOpenLoadFile) { | |
// LoadFile does not support Seek | |
return EFI_UNSUPPORTED; | |
} | |
CurrentPosition = File->CurrentPosition; | |
switch (SeekType) { | |
case EfiSeekStart: | |
if (Offset > File->MaxPosition) { | |
return EFI_NOT_FOUND; | |
} | |
CurrentPosition = Offset; | |
break; | |
case EfiSeekCurrent: | |
if ((File->CurrentPosition + Offset) > File->MaxPosition) { | |
return EFI_NOT_FOUND; | |
} | |
CurrentPosition += Offset; | |
break; | |
case EfiSeekEnd: | |
if (Offset != 0) { | |
// We don't support growing file size via seeking past end of file | |
return EFI_UNSUPPORTED; | |
} | |
CurrentPosition = File->MaxPosition; | |
break; | |
default: | |
return EFI_NOT_FOUND; | |
} | |
Status = EFI_SUCCESS; | |
if (File->FsFileHandle != NULL) { | |
Status = File->FsFileHandle->SetPosition (File->FsFileHandle, CurrentPosition); | |
} | |
if (!EFI_ERROR (Status)) { | |
File->CurrentPosition = CurrentPosition; | |
} | |
return Status; | |
} | |
EFI_STATUS | |
CacheTftpFile ( | |
IN OUT EFI_OPEN_FILE *File | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 TftpBufferSize; | |
if (File->IsBufferValid) { | |
return EFI_SUCCESS; | |
} | |
// Make sure the file size is set. | |
EfiTell (File, NULL); | |
//Allocate a buffer to hold the whole file. | |
File->Buffer = AllocatePool(File->Size); | |
if (File->Buffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
TftpBufferSize = File->Size; | |
Status = EblMtftp ( | |
EFI_PXE_BASE_CODE_TFTP_READ_FILE, | |
File->Buffer, | |
FALSE, | |
&TftpBufferSize, | |
NULL, | |
&File->ServerIp, | |
(UINT8 *)File->FileName, | |
NULL, | |
FALSE); | |
if (EFI_ERROR(Status)) { | |
AsciiPrint("TFTP error during APPLE_NSP_TFTP_READ_FILE: %r\n", Status); | |
FreePool(File->Buffer); | |
return Status; | |
} | |
// Set the buffer valid flag. | |
File->IsBufferValid = TRUE; | |
return Status; | |
} | |
/** | |
Read BufferSize bytes from the current location in the file. For load file, | |
FV, and TFTP case you must read the entire file. | |
@param Stream Open File Handle | |
@param Buffer Caller allocated buffer. | |
@param BufferSize Size of buffer in bytes. | |
@return EFI_SUCCESS Stream is not an Open File | |
@return EFI_END_OF_FILE Tried to read past the end of the file | |
@return EFI_INVALID_PARAMETER Stream is not an open file handle | |
@return EFI_BUFFER_TOO_SMALL Buffer is not big enough to do the read | |
@return "other" Error returned from device read | |
**/ | |
EFI_STATUS | |
EfiRead ( | |
IN EFI_OPEN_FILE *File, | |
OUT VOID *Buffer, | |
OUT UINTN *BufferSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 AuthenticationStatus; | |
EFI_DISK_IO_PROTOCOL *DiskIo; | |
if (!FileHandleValid (File)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// Don't read past the end of the file. | |
if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) { | |
return EFI_END_OF_FILE; | |
} | |
switch (File->Type) { | |
case EfiOpenLoadFile: | |
// Figure out the File->Size | |
EfiTell (File, NULL); | |
Status = File->LoadFile->LoadFile (File->LoadFile, File->DevicePath, FALSE, BufferSize, Buffer); | |
break; | |
case EfiOpenFirmwareVolume: | |
if (CompareGuid (&File->FvNameGuid, &gZeroGuid)) { | |
// This is the entire FV device, so treat like a memory buffer | |
CopyMem (Buffer, (VOID *)(UINTN)(File->FvStart + File->CurrentPosition), *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
Status = EFI_SUCCESS; | |
} else { | |
if (File->Buffer == NULL) { | |
if (File->FvSectionType == EFI_SECTION_ALL) { | |
Status = File->Fv->ReadFile ( | |
File->Fv, | |
&File->FvNameGuid, | |
(VOID **)&File->Buffer, | |
&File->Size, | |
&File->FvType, | |
&File->FvAttributes, | |
&AuthenticationStatus | |
); | |
} else { | |
Status = File->Fv->ReadSection ( | |
File->Fv, | |
&File->FvNameGuid, | |
File->FvSectionType, | |
0, | |
(VOID **)&File->Buffer, | |
&File->Size, | |
&AuthenticationStatus | |
); | |
} | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
File->IsBufferValid = TRUE; | |
} | |
// Operate on the cached buffer so Seek will work | |
CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
Status = EFI_SUCCESS; | |
} | |
break; | |
case EfiOpenMemoryBuffer: | |
CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
Status = EFI_SUCCESS; | |
break; | |
case EfiOpenFileSystem: | |
Status = File->FsFileHandle->Read (File->FsFileHandle, BufferSize, Buffer); | |
File->CurrentPosition += *BufferSize; | |
break; | |
case EfiOpenBlockIo: | |
Status = gBS->HandleProtocol(File->EfiHandle, &gEfiDiskIoProtocolGuid, (VOID **)&DiskIo); | |
if (!EFI_ERROR(Status)) { | |
Status = DiskIo->ReadDisk(DiskIo, File->FsBlockIoMedia->MediaId, File->DiskOffset + File->CurrentPosition, *BufferSize, Buffer); | |
} | |
File->CurrentPosition += *BufferSize; | |
break; | |
case EfiOpenTftp: | |
// Cache the file if it hasn't been cached yet. | |
if (File->IsBufferValid == FALSE) { | |
Status = CacheTftpFile (File); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
} | |
// Copy out the requested data | |
CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
Status = EFI_SUCCESS; | |
break; | |
default: | |
return EFI_INVALID_PARAMETER; | |
}; | |
return Status; | |
} | |
/** | |
Read the entire file into a buffer. This routine allocates the buffer and | |
returns it to the user full of the read data. | |
This is very useful for load file where it's hard to know how big the buffer | |
must be. | |
@param Stream Open File Handle | |
@param Buffer Pointer to buffer to return. | |
@param BufferSize Pointer to Size of buffer return.. | |
@return EFI_SUCCESS Stream is not an Open File | |
@return EFI_END_OF_FILE Tried to read past the end of the file | |
@return EFI_INVALID_PARAMETER Stream is not an open file handle | |
@return EFI_BUFFER_TOO_SMALL Buffer is not big enough to do the read | |
@return "other" Error returned from device read | |
**/ | |
EFI_STATUS | |
EfiReadAllocatePool ( | |
IN EFI_OPEN_FILE *File, | |
OUT VOID **Buffer, | |
OUT UINTN *BufferSize | |
) | |
{ | |
if (!FileHandleValid (File)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// Loadfile defers file size determination on Open so use tell to find it | |
EfiTell (File, NULL); | |
*BufferSize = File->Size; | |
*Buffer = AllocatePool (*BufferSize); | |
if (*Buffer == NULL) { | |
return EFI_NOT_FOUND; | |
} | |
return EfiRead (File, *Buffer, BufferSize); | |
} | |
/** | |
Write data back to the file. For TFTP case you must write the entire file. | |
@param Stream Open File Handle | |
@param Buffer Pointer to buffer to return. | |
@param BufferSize Pointer to Size of buffer return.. | |
@return EFI_SUCCESS Stream is not an Open File | |
@return EFI_END_OF_FILE Tried to read past the end of the file | |
@return EFI_INVALID_PARAMETER Stream is not an open file handle | |
@return EFI_BUFFER_TOO_SMALL Buffer is not big enough to do the read | |
@return "other" Error returned from device write | |
**/ | |
EFI_STATUS | |
EfiWrite ( | |
IN EFI_OPEN_FILE *File, | |
OUT VOID *Buffer, | |
OUT UINTN *BufferSize | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_FV_WRITE_FILE_DATA FileData; | |
EFI_DISK_IO_PROTOCOL *DiskIo; | |
if (!FileHandleValid (File)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
switch (File->Type) { | |
case EfiOpenMemoryBuffer: | |
if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) { | |
return EFI_END_OF_FILE; | |
} | |
CopyMem (File->Buffer + File->CurrentPosition, Buffer, *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
Status = EFI_SUCCESS; | |
case EfiOpenLoadFile: | |
// LoadFile device is read only be definition | |
Status = EFI_UNSUPPORTED; | |
case EfiOpenFirmwareVolume: | |
if (File->FvSectionType != EFI_SECTION_ALL) { | |
// Writes not support to a specific section. You have to update entire file | |
return EFI_UNSUPPORTED; | |
} | |
FileData.NameGuid = &(File->FvNameGuid); | |
FileData.Type = File->FvType; | |
FileData.FileAttributes = File->FvAttributes; | |
FileData.Buffer = Buffer; | |
FileData.BufferSize = (UINT32)*BufferSize; | |
Status = File->Fv->WriteFile (File->Fv, 1, EFI_FV_UNRELIABLE_WRITE, &FileData); | |
break; | |
case EfiOpenFileSystem: | |
Status = File->FsFileHandle->Write (File->FsFileHandle, BufferSize, Buffer); | |
File->CurrentPosition += *BufferSize; | |
break; | |
case EfiOpenBlockIo: | |
if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) { | |
return EFI_END_OF_FILE; | |
} | |
Status = gBS->HandleProtocol (File->EfiHandle, &gEfiDiskIoProtocolGuid, (VOID **)&DiskIo); | |
if (!EFI_ERROR(Status)) { | |
Status = DiskIo->WriteDisk (DiskIo, File->FsBlockIoMedia->MediaId, File->DiskOffset + File->CurrentPosition, *BufferSize, Buffer); | |
} | |
File->CurrentPosition += *BufferSize; | |
break; | |
case EfiOpenTftp: | |
// Cache the file if it hasn't been cached yet. | |
if (File->IsBufferValid == FALSE) { | |
Status = CacheTftpFile(File); | |
if (EFI_ERROR(Status)) { | |
return Status; | |
} | |
} | |
// Don't overwrite the buffer | |
if ((File->CurrentPosition + *BufferSize) > File->MaxPosition) { | |
UINT8 *TempBuffer; | |
TempBuffer = File->Buffer; | |
File->Buffer = AllocatePool ((UINTN)(File->CurrentPosition + *BufferSize)); | |
if (File->Buffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (File->Buffer, TempBuffer, File->Size); | |
FreePool (TempBuffer); | |
File->Size = (UINTN)(File->CurrentPosition + *BufferSize); | |
File->MaxPosition = (UINT64)File->Size; | |
} | |
// Copy in the requested data | |
CopyMem (File->Buffer + File->CurrentPosition, Buffer, *BufferSize); | |
File->CurrentPosition += *BufferSize; | |
// Mark the file dirty | |
File->IsDirty = TRUE; | |
Status = EFI_SUCCESS; | |
break; | |
default: | |
Status = EFI_INVALID_PARAMETER; | |
}; | |
return Status; | |
} | |
/** | |
Given Cwd expand Path to remove .. and replace them with real | |
directory names. | |
@param Cwd Current Working Directory | |
@param Path Path to expand | |
@return NULL Cwd or Path are not valid | |
@return 'other' Path with .. expanded | |
**/ | |
CHAR8 * | |
ExpandPath ( | |
IN CHAR8 *Cwd, | |
IN CHAR8 *Path | |
) | |
{ | |
CHAR8 *NewPath; | |
CHAR8 *Work, *Start, *End; | |
UINTN StrLen; | |
INTN i; | |
if (Cwd == NULL || Path == NULL) { | |
return NULL; | |
} | |
StrLen = AsciiStrSize (Cwd); | |
if (StrLen <= 2) { | |
// Smallest valid path is 1 char and a null | |
return NULL; | |
} | |
StrLen = AsciiStrSize (Path); | |
NewPath = AllocatePool (AsciiStrSize (Cwd) + StrLen + 1); | |
if (NewPath == NULL) { | |
return NULL; | |
} | |
AsciiStrCpy (NewPath, Cwd); | |
End = Path + StrLen; | |
for (Start = Path ;;) { | |
Work = AsciiStrStr (Start, "..") ; | |
if (Work == NULL) { | |
// Remaining part of Path contains no more .. | |
break; | |
} | |
// append path prior to .. | |
AsciiStrnCat (NewPath, Start, Work - Start); | |
StrLen = AsciiStrLen (NewPath); | |
for (i = StrLen; i >= 0; i--) { | |
if (NewPath[i] == ':') { | |
// too many .. | |
return NULL; | |
} | |
if (NewPath[i] == '/' || NewPath[i] == '\\') { | |
if ((i > 0) && (NewPath[i-1] == ':')) { | |
// leave the / before a : | |
NewPath[i+1] = '\0'; | |
} else { | |
// replace / will Null to remove trailing file/dir reference | |
NewPath[i] = '\0'; | |
} | |
break; | |
} | |
} | |
Start = Work + 3; | |
} | |
// Handle the path that remains after the .. | |
AsciiStrnCat (NewPath, Start, End - Start); | |
return NewPath; | |
} | |
/** | |
Set the Current Working Directory (CWD). If a call is made to EfiOpen () and | |
the path does not contain a device name, The CWD is prepended to the path. | |
@param Cwd Current Working Directory to set | |
@return EFI_SUCCESS CWD is set | |
@return EFI_INVALID_PARAMETER Cwd is not a valid device:path | |
**/ | |
EFI_STATUS | |
EfiSetCwd ( | |
IN CHAR8 *Cwd | |
) | |
{ | |
EFI_OPEN_FILE *File; | |
UINTN Len; | |
CHAR8 *Path; | |
if (Cwd == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (AsciiStrCmp (Cwd, ".") == 0) { | |
// cd . is a no-op | |
return EFI_SUCCESS; | |
} | |
Path = Cwd; | |
if (AsciiStrStr (Cwd, "..") != NULL) { | |
if (gCwd == NULL) { | |
// no parent | |
return EFI_SUCCESS; | |
} | |
Len = AsciiStrLen (gCwd); | |
if ((gCwd[Len-2] == ':') && ((gCwd[Len-1] == '/') || (gCwd[Len-1] == '\\'))) { | |
// parent is device so nothing to do | |
return EFI_SUCCESS; | |
} | |
// Expand .. in Cwd, given we know current working directory | |
Path = ExpandPath (gCwd, Cwd); | |
if (Path == NULL) { | |
return EFI_NOT_FOUND; | |
} | |
} | |
File = EfiOpen (Path, EFI_FILE_MODE_READ, 0); | |
if (File == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (gCwd != NULL) { | |
FreePool (gCwd); | |
} | |
// Use the info returned from EfiOpen as it can add in CWD if needed. So Cwd could be | |
// relative to the current gCwd or not. | |
gCwd = AllocatePool (AsciiStrSize (File->DeviceName) + AsciiStrSize (File->FileName) + 10); | |
if (gCwd == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
AsciiStrCpy (gCwd, File->DeviceName); | |
if (File->FileName == NULL) { | |
AsciiStrCat (gCwd, ":\\"); | |
} else { | |
AsciiStrCat (gCwd, ":"); | |
AsciiStrCat (gCwd, File->FileName); | |
} | |
EfiClose (File); | |
if (Path != Cwd) { | |
FreePool (Path); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Set the Current Working Directory (CWD). If a call is made to EfiOpen () and | |
the path does not contain a device name, The CWD is prepended to the path. | |
The CWD buffer is only valid until a new call is made to EfiSetCwd(). After | |
a call to EfiSetCwd() it is not legal to use the pointer returned by | |
this function. | |
@param Cwd Current Working Directory | |
@return "" No CWD set | |
@return 'other' Returns buffer that contains CWD. | |
**/ | |
CHAR8 * | |
EfiGetCwd ( | |
VOID | |
) | |
{ | |
if (gCwd == NULL) { | |
return ""; | |
} | |
return gCwd; | |
} | |