| /** @file |
| File IO routines inspired by Streams with an EFI flavor |
| |
| Copyright (c) 2007, Intel Corporation<BR> |
| Portions copyright (c) 2008-2009, Apple Inc. All rights reserved. |
| |
| All rights reserved. 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 insensative 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; |
| |
| |
| #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 reclaculating 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)) { |
| CopyMem (&File->FsBlockIoMedia, BlkIo->Media, sizeof (EFI_BLOCK_IO_MEDIA)); |
| |
| // 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; |
| |
| |
| Status = gBS->HandleProtocol (File->EfiHandle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&File->Fv); |
| if (EFI_ERROR (Status)) { |
| return Status; |
| } |
| |
| DevicePath = DevicePathFromHandle (File->EfiHandle); |
| |
| if (*FileName == '\0') { |
| File->DevicePath = DuplicateDevicePath (DevicePath); |
| } 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)) { |
| Section = NULL; |
| |
| // Compare GUID first |
| Status = CompareGuidToString (&File->FvNameGuid, FileName); |
| if (!EFI_ERROR(Status)) { |
| break; |
| } |
| |
| 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; |
| } |
| |
| File->MaxPosition = File->Size; |
| EfiInitializeFwVolDevicepathNode (&DevicePathNode, &File->FvNameGuid); |
| File->DevicePath = AppendDevicePathNode (DevicePath, (EFI_DEVICE_PATH_PROTOCOL *)&DevicePathNode); |
| } |
| |
| |
| // 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)) { |
| for (Lba = 0, File->FvSize = 0; ; File->FvSize += (BlockSize * NumberOfBlocks), Lba += NumberOfBlocks) { |
| Status = Fvb->GetBlockSize (Fvb, Lba, &BlockSize, &NumberOfBlocks); |
| if (EFI_ERROR (Status)) { |
| break; |
| } |
| } |
| } |
| } |
| |
| // 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 seperated 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 |
| secitons 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; |
| |
| EblUpdateDeviceLists (); |
| |
| File = &FileData; |
| ZeroMem (File, sizeof (EFI_OPEN_FILE)); |
| File->FvSectionType = SectionType; |
| |
| 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 diretory 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 recusre 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++; |
| } |
| Status = EblFvFileDevicePath (File, &PathName[FileStart], OpenMode); |
| } |
| } 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\n"); |
| goto Exit; |
| } |
| |
| Status = EfiWrite(Destination, Buffer, &Chunk); |
| if (EFI_ERROR(Status)) { |
| AsciiPrint("Write file error\n"); |
| 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))) { |
| 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 locaiton 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 doe 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 || File->Type == EfiOpenFirmwareVolume) { |
| // LoadFile and FV do 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 locaiton 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 EfiOpenMemoryBuffer: |
| CopyMem (Buffer, File->Buffer + File->CurrentPosition, *BufferSize); |
| File->CurrentPosition += *BufferSize; |
| Status = EFI_SUCCESS; |
| break; |
| |
| 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 (File->FvSectionType == EFI_SECTION_ALL) { |
| Status = File->Fv->ReadFile ( |
| File->Fv, |
| &File->FvNameGuid, |
| &Buffer, |
| BufferSize, |
| &File->FvType, |
| &File->FvAttributes, |
| &AuthenticationStatus |
| ); |
| } else { |
| Status = File->Fv->ReadSection ( |
| File->Fv, |
| &File->FvNameGuid, |
| File->FvSectionType, |
| 0, |
| &Buffer, |
| BufferSize, |
| &AuthenticationStatus |
| ); |
| } |
| 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 flie 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; |
| UINTN 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 Curent 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) + 1); |
| 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 Curent 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 funciton. |
| |
| @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; |
| } |
| |
| |