/** @file * * Copyright (c) 2012-2014, ARM Limited. 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. * **/ #include "BootMonFsInternal.h" EFIAPI EFI_STATUS OpenBootMonFsOpenVolume ( IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This, OUT EFI_FILE_PROTOCOL **Root ) { BOOTMON_FS_INSTANCE *Instance; Instance = BOOTMON_FS_FROM_FS_THIS (This); if (Instance == NULL) { return EFI_DEVICE_ERROR; } Instance->RootFile->Info->Attribute = EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY; *Root = &Instance->RootFile->File; return EFI_SUCCESS; } UINT32 BootMonFsGetImageLength ( IN BOOTMON_FS_FILE *File ) { UINT32 Index; UINT32 FileSize; LIST_ENTRY *RegionToFlushLink; BOOTMON_FS_FILE_REGION *Region; FileSize = 0; // Look at all Flash areas to determine file size for (Index = 0; Index < HW_IMAGE_DESCRIPTION_REGION_MAX; Index++) { FileSize += File->HwDescription.Region[Index].Size; } // Add the regions that have not been flushed yet for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); !IsNull (&File->RegionToFlushLink, RegionToFlushLink); RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink) ) { Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink; if (Region->Offset + Region->Size > FileSize) { FileSize += Region->Offset + Region->Size; } } return FileSize; } UINTN BootMonFsGetPhysicalSize ( IN BOOTMON_FS_FILE* File ) { // Return 0 for files that haven't yet been flushed to media if (File->HwDescription.RegionCount == 0) { return 0; } return ((File->HwDescription.BlockEnd - File->HwDescription.BlockStart) + 1 ) * File->Instance->Media->BlockSize; } EFIAPI EFI_STATUS BootMonFsSetDirPosition ( IN EFI_FILE_PROTOCOL *This, IN UINT64 Position ) { BOOTMON_FS_FILE *File; File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File == NULL) { return EFI_INVALID_PARAMETER; } // UEFI Spec section 12.5: // "The seek request for nonzero is not valid on open directories." if (Position != 0) { return EFI_UNSUPPORTED; } File->Position = Position; return EFI_SUCCESS; } EFI_STATUS BootMonFsOpenDirectory ( OUT EFI_FILE_PROTOCOL **NewHandle, IN CHAR16 *FileName, IN BOOTMON_FS_INSTANCE *Volume ) { ASSERT(0); return EFI_UNSUPPORTED; } STATIC EFI_STATUS GetFileSystemVolumeLabelInfo ( IN BOOTMON_FS_INSTANCE *Instance, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { UINTN Size; EFI_FILE_SYSTEM_VOLUME_LABEL *Label; EFI_STATUS Status; Label = Buffer; // Value returned by StrSize includes null terminator. Size = SIZE_OF_EFI_FILE_SYSTEM_VOLUME_LABEL + StrSize (Instance->FsInfo.VolumeLabel); if (*BufferSize >= Size) { CopyMem (&Label->VolumeLabel, &Instance->FsInfo.VolumeLabel, Size); Status = EFI_SUCCESS; } else { Status = EFI_BUFFER_TOO_SMALL; } *BufferSize = Size; return Status; } // Helper function that calculates a rough "free space" by: // - Taking the media size // - Subtracting the sum of all file sizes // - Subtracting the block size times the number of files // (To account for the blocks containing the HW_IMAGE_INFO STATIC UINT64 ComputeFreeSpace ( IN BOOTMON_FS_INSTANCE *Instance ) { LIST_ENTRY *FileLink; UINT64 FileSizeSum; UINT64 MediaSize; UINTN NumFiles; EFI_BLOCK_IO_MEDIA *Media; BOOTMON_FS_FILE *File; Media = Instance->BlockIo->Media; MediaSize = Media->BlockSize * (Media->LastBlock + 1); NumFiles = 0; FileSizeSum = 0; for (FileLink = GetFirstNode (&Instance->RootFile->Link); !IsNull (&Instance->RootFile->Link, FileLink); FileLink = GetNextNode (&Instance->RootFile->Link, FileLink) ) { File = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink); FileSizeSum += BootMonFsGetImageLength (File); NumFiles++; } return MediaSize - (FileSizeSum + (Media->BlockSize + NumFiles)); } STATIC EFI_STATUS GetFilesystemInfo ( IN BOOTMON_FS_INSTANCE *Instance, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { EFI_STATUS Status; if (*BufferSize >= Instance->FsInfo.Size) { Instance->FsInfo.FreeSpace = ComputeFreeSpace (Instance); CopyMem (Buffer, &Instance->FsInfo, Instance->FsInfo.Size); Status = EFI_SUCCESS; } else { Status = EFI_BUFFER_TOO_SMALL; } *BufferSize = Instance->FsInfo.Size; return Status; } STATIC EFI_STATUS GetFileInfo ( IN BOOTMON_FS_INSTANCE *Instance, IN BOOTMON_FS_FILE *File, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { EFI_FILE_INFO *Info; UINTN ResultSize; ResultSize = SIZE_OF_EFI_FILE_INFO + StrSize (File->Info->FileName); if (*BufferSize < ResultSize) { *BufferSize = ResultSize; return EFI_BUFFER_TOO_SMALL; } Info = Buffer; CopyMem (Info, File->Info, ResultSize); // Size of the information Info->Size = ResultSize; *BufferSize = ResultSize; return EFI_SUCCESS; } STATIC EFI_STATUS GetBootMonFsFileInfo ( IN BOOTMON_FS_INSTANCE *Instance, IN BOOTMON_FS_FILE *File, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { EFI_STATUS Status; BOOTMON_FS_FILE_INFO *Info; UINTN ResultSize; UINTN Index; if (File == Instance->RootFile) { Status = EFI_UNSUPPORTED; } else { ResultSize = SIZE_OF_BOOTMON_FS_FILE_INFO; if (*BufferSize < ResultSize) { *BufferSize = ResultSize; Status = EFI_BUFFER_TOO_SMALL; } else { Info = Buffer; // Zero out the structure ZeroMem (Info, ResultSize); // Fill in the structure Info->Size = ResultSize; Info->EntryPoint = File->HwDescription.EntryPoint; Info->RegionCount = File->HwDescription.RegionCount; for (Index = 0; Index < File->HwDescription.RegionCount; Index++) { Info->Region[Index].LoadAddress = File->HwDescription.Region[Index].LoadAddress; Info->Region[Index].Size = File->HwDescription.Region[Index].Size; Info->Region[Index].Offset = File->HwDescription.Region[Index].Offset; Info->Region[Index].Checksum = File->HwDescription.Region[Index].Checksum; } *BufferSize = ResultSize; Status = EFI_SUCCESS; } } return Status; } /** Set the name of a file. This is a helper function for SetFileInfo(). @param[in] Instance A pointer to the description of the volume the file belongs to. @param[in] File A pointer to the description of the file. @param[in] FileName A pointer to the new name of the file. @retval EFI_SUCCESS The name was set. @retval EFI_ACCESS_DENIED An attempt is made to change the name of a file to a file that is already present. **/ STATIC EFI_STATUS SetFileName ( IN BOOTMON_FS_INSTANCE *Instance, IN BOOTMON_FS_FILE *File, IN CONST CHAR16 *FileName ) { CHAR16 TruncFileName[MAX_NAME_LENGTH]; CHAR8 AsciiFileName[MAX_NAME_LENGTH]; BOOTMON_FS_FILE *SameFile; // If the file path start with a \ strip it. The EFI Shell may // insert a \ in front of the file name. if (FileName[0] == L'\\') { FileName++; } StrnCpy (TruncFileName, FileName, MAX_NAME_LENGTH - 1); TruncFileName[MAX_NAME_LENGTH - 1] = 0; UnicodeStrToAsciiStr (TruncFileName, AsciiFileName); if (BootMonGetFileFromAsciiFileName ( File->Instance, AsciiFileName, &SameFile ) != EFI_NOT_FOUND) { // A file with that name already exists. return EFI_ACCESS_DENIED; } else { // OK, change the filename. AsciiStrToUnicodeStr (AsciiFileName, File->Info->FileName); return EFI_SUCCESS; } } /** Set the size of a file. This is a helper function for SetFileInfo(). @param[in] Instance A pointer to the description of the volume the file belongs to. @param[in] File A pointer to the description of the file. @param[in] NewSize The requested new size for the file. @retval EFI_SUCCESS The size was set. @retval EFI_OUT_OF_RESOURCES An allocation needed to process the request failed. **/ STATIC EFI_STATUS SetFileSize ( IN BOOTMON_FS_INSTANCE *Instance, IN BOOTMON_FS_FILE *BootMonFsFile, IN UINTN NewSize ) { EFI_STATUS Status; UINT32 OldSize; LIST_ENTRY *RegionToFlushLink; LIST_ENTRY *NextRegionToFlushLink; BOOTMON_FS_FILE_REGION *Region; EFI_FILE_PROTOCOL *File; CHAR8 *Buffer; UINTN BufferSize; UINT64 StoredPosition; OldSize = BootMonFsFile->Info->FileSize; // // In case of file truncation, force the regions waiting for writing to // not overflow the new size of the file. // if (NewSize < OldSize) { for (RegionToFlushLink = GetFirstNode (&BootMonFsFile->RegionToFlushLink); !IsNull (&BootMonFsFile->RegionToFlushLink, RegionToFlushLink); ) { NextRegionToFlushLink = GetNextNode (&BootMonFsFile->RegionToFlushLink, RegionToFlushLink); Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink; if (Region->Offset > NewSize) { RemoveEntryList (RegionToFlushLink); FreePool (Region->Buffer); FreePool (Region); } else { Region->Size = MIN (Region->Size, NewSize - Region->Offset); } RegionToFlushLink = NextRegionToFlushLink; } } else if (NewSize > OldSize) { // Increasing a file's size is potentially complicated as it may require // moving the image description on media. The simplest way to do it is to // seek past the end of the file (which is valid in UEFI) and perform a // Write. File = &BootMonFsFile->File; // Save position Status = File->GetPosition (File, &StoredPosition); if (EFI_ERROR (Status)) { return Status; } // Set position at the end of the file Status = File->SetPosition (File, OldSize); if (EFI_ERROR (Status)) { return Status; } BufferSize = NewSize - OldSize; Buffer = AllocateZeroPool (BufferSize); if (Buffer == NULL) { return EFI_OUT_OF_RESOURCES; } Status = File->Write (File, &BufferSize, Buffer); FreePool (Buffer); if (EFI_ERROR (Status)) { return Status; } // Restore saved position Status = File->SetPosition (File, StoredPosition); if (EFI_ERROR (Status)) { return Status; } } BootMonFsFile->Info->FileSize = NewSize; return EFI_SUCCESS; } /** Set information about a file. @param[in] Instance A pointer to the description of the volume the file belongs to. @param[in] File A pointer to the description of the file. @param[in] Info A pointer to the file information to write. @retval EFI_SUCCESS The information was set. @retval EFI_ACCESS_DENIED An attempt is being made to change the EFI_FILE_DIRECTORY Attribute. @retval EFI_ACCESS_DENIED The file was opened in read-only mode and an attempt is being made to modify a field other than Attribute. @retval EFI_ACCESS_DENIED An attempt is made to change the name of a file to a file that is already present. @retval EFI_WRITE_PROTECTED An attempt is being made to modify a read-only attribute. @retval EFI_OUT_OF_RESOURCES An allocation needed to process the request failed. **/ STATIC EFI_STATUS SetFileInfo ( IN BOOTMON_FS_INSTANCE *Instance, IN BOOTMON_FS_FILE *File, IN EFI_FILE_INFO *Info ) { EFI_STATUS Status; BOOLEAN FileSizeIsDifferent; BOOLEAN FileNameIsDifferent; BOOLEAN TimeIsDifferent; // // A directory can not be changed to a file and a file can // not be changed to a directory. // if ((Info->Attribute & EFI_FILE_DIRECTORY) != (File->Info->Attribute & EFI_FILE_DIRECTORY) ) { return EFI_ACCESS_DENIED; } FileSizeIsDifferent = (Info->FileSize != File->Info->FileSize); FileNameIsDifferent = (StrnCmp ( Info->FileName, File->Info->FileName, MAX_NAME_LENGTH - 1 ) != 0); // // Check if the CreateTime, LastAccess or ModificationTime // have been changed. The file system does not support file // timestamps thus the three times in "File->Info" are // always equal to zero. The following comparison actually // checks if all three times are still equal to 0 or not. // TimeIsDifferent = CompareMem ( &Info->CreateTime, &File->Info->CreateTime, 3 * sizeof (EFI_TIME) ) != 0; // // For a file opened in read-only mode, only the Attribute field can be // modified. The root directory open mode is forced to read-only at opening // thus the following test protects the root directory to be somehow modified. // if (File->OpenMode == EFI_FILE_MODE_READ) { if (FileSizeIsDifferent || FileNameIsDifferent || TimeIsDifferent) { return EFI_ACCESS_DENIED; } } if (TimeIsDifferent) { return EFI_WRITE_PROTECTED; } if (FileSizeIsDifferent) { Status = SetFileSize (Instance, File, Info->FileSize); if (EFI_ERROR (Status)) { return Status; } } // // Note down in RAM the Attribute field but we can not // ask to store it in flash for the time being. // File->Info->Attribute = Info->Attribute; if (FileNameIsDifferent) { Status = SetFileName (Instance, File, Info->FileName); if (EFI_ERROR (Status)) { return Status; } } return EFI_SUCCESS; } EFIAPI EFI_STATUS BootMonFsGetInfo ( IN EFI_FILE_PROTOCOL *This, IN EFI_GUID *InformationType, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { EFI_STATUS Status; BOOTMON_FS_FILE *File; BOOTMON_FS_INSTANCE *Instance; if ((This == NULL) || (InformationType == NULL) || (BufferSize == NULL) || ((Buffer == NULL) && (*BufferSize > 0)) ) { return EFI_INVALID_PARAMETER; } File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File->Info == NULL) { return EFI_INVALID_PARAMETER; } Instance = File->Instance; // If the instance has not been initialized yet then do it ... if (!Instance->Initialized) { Status = BootMonFsInitialize (Instance); } else { Status = EFI_SUCCESS; } if (!EFI_ERROR (Status)) { if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid) != 0) { Status = GetFileSystemVolumeLabelInfo (Instance, BufferSize, Buffer); } else if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid) != 0) { Status = GetFilesystemInfo (Instance, BufferSize, Buffer); } else if (CompareGuid (InformationType, &gEfiFileInfoGuid) != 0) { Status = GetFileInfo (Instance, File, BufferSize, Buffer); } else if (CompareGuid (InformationType, &gArmBootMonFsFileInfoGuid) != 0) { Status = GetBootMonFsFileInfo (Instance, File, BufferSize, Buffer); } else { Status = EFI_UNSUPPORTED; } } return Status; } /** Set information about a file or a volume. @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle the information is for. @param[in] InformationType The type identifier for the information being set : EFI_FILE_INFO_ID or EFI_FILE_SYSTEM_INFO_ID or EFI_FILE_SYSTEM_VOLUME_LABEL_ID @param[in] BufferSize The size, in bytes, of Buffer. @param[in] Buffer A pointer to the data buffer to write. The type of the data inside the buffer is indicated by InformationType. @retval EFI_SUCCESS The information was set. @retval EFI_UNSUPPORTED The InformationType is not known. @retval EFI_DEVICE_ERROR The last issued semi-hosting operation failed. @retval EFI_ACCESS_DENIED An attempt is made to change the name of a file to a file that is already present. @retval EFI_ACCESS_DENIED An attempt is being made to change the EFI_FILE_DIRECTORY Attribute. @retval EFI_ACCESS_DENIED InformationType is EFI_FILE_INFO_ID and the file was opened in read-only mode and an attempt is being made to modify a field other than Attribute. @retval EFI_WRITE_PROTECTED An attempt is being made to modify a read-only attribute. @retval EFI_BAD_BUFFER_SIZE The size of the buffer is lower than that indicated by the data inside the buffer. @retval EFI_OUT_OF_RESOURCES A allocation needed to process the request failed. @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid. **/ EFIAPI EFI_STATUS BootMonFsSetInfo ( IN EFI_FILE_PROTOCOL *This, IN EFI_GUID *InformationType, IN UINTN BufferSize, IN VOID *Buffer ) { BOOTMON_FS_FILE *File; EFI_FILE_INFO *Info; EFI_FILE_SYSTEM_INFO *SystemInfo; if ((This == NULL) || (InformationType == NULL) || (Buffer == NULL) ) { return EFI_INVALID_PARAMETER; } File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File->Info == NULL) { return EFI_INVALID_PARAMETER; } if (CompareGuid (InformationType, &gEfiFileInfoGuid)) { Info = Buffer; if (Info->Size < (SIZE_OF_EFI_FILE_INFO + StrSize (Info->FileName))) { return EFI_INVALID_PARAMETER; } if (BufferSize < Info->Size) { return EFI_BAD_BUFFER_SIZE; } return (SetFileInfo (File->Instance, File, Info)); } // // The only writable field in the other two information types // (i.e. EFI_FILE_SYSTEM_INFO and EFI_FILE_SYSTEM_VOLUME_LABEL) is the // filesystem volume label. This can be retrieved with GetInfo, but it is // hard-coded into this driver, not stored on media. // if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) { SystemInfo = Buffer; if (SystemInfo->Size < (SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (SystemInfo->VolumeLabel))) { return EFI_INVALID_PARAMETER; } if (BufferSize < SystemInfo->Size) { return EFI_BAD_BUFFER_SIZE; } return EFI_WRITE_PROTECTED; } if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) { return EFI_WRITE_PROTECTED; } return EFI_UNSUPPORTED; } EFIAPI EFI_STATUS BootMonFsReadDirectory ( IN EFI_FILE_PROTOCOL *This, IN OUT UINTN *BufferSize, OUT VOID *Buffer ) { BOOTMON_FS_INSTANCE *Instance; BOOTMON_FS_FILE *RootFile; BOOTMON_FS_FILE *File; EFI_FILE_INFO *Info; UINTN NameSize; UINTN ResultSize; EFI_STATUS Status; UINTN Index; RootFile = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (RootFile == NULL) { return EFI_INVALID_PARAMETER; } Instance = RootFile->Instance; Status = BootMonGetFileFromPosition (Instance, RootFile->Position, &File); if (EFI_ERROR (Status)) { // No more file *BufferSize = 0; return EFI_SUCCESS; } NameSize = AsciiStrLen (File->HwDescription.Footer.Filename) + 1; ResultSize = SIZE_OF_EFI_FILE_INFO + (NameSize * sizeof (CHAR16)); if (*BufferSize < ResultSize) { *BufferSize = ResultSize; return EFI_BUFFER_TOO_SMALL; } // Zero out the structure Info = Buffer; ZeroMem (Info, ResultSize); // Fill in the structure Info->Size = ResultSize; Info->FileSize = BootMonFsGetImageLength (File); Info->PhysicalSize = BootMonFsGetPhysicalSize (File); for (Index = 0; Index < NameSize; Index++) { Info->FileName[Index] = File->HwDescription.Footer.Filename[Index]; } *BufferSize = ResultSize; RootFile->Position++; return EFI_SUCCESS; } EFIAPI EFI_STATUS BootMonFsFlushDirectory ( IN EFI_FILE_PROTOCOL *This ) { BOOTMON_FS_FILE *RootFile; LIST_ENTRY *ListFiles; LIST_ENTRY *Link; BOOTMON_FS_FILE *File; RootFile = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (RootFile == NULL) { return EFI_INVALID_PARAMETER; } ListFiles = &RootFile->Link; if (IsListEmpty (ListFiles)) { return EFI_SUCCESS; } // // Flush all the files that need to be flushed // // Go through all the list of files to flush them for (Link = GetFirstNode (ListFiles); !IsNull (ListFiles, Link); Link = GetNextNode (ListFiles, Link) ) { File = BOOTMON_FS_FILE_FROM_LINK_THIS (Link); File->File.Flush (&File->File); } return EFI_SUCCESS; }