/** @file * * Copyright (c) 2012-2015, 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" // Clear a file's image description on storage media: // UEFI allows you to seek past the end of a file, a subsequent write will grow // the file. It does not specify how space between the former end of the file // and the beginning of the write should be filled. It's therefore possible that // BootMonFs metadata, that comes after the end of a file, could be left there // and wrongly detected by BootMonFsImageInBlock. STATIC EFI_STATUS InvalidateImageDescription ( IN BOOTMON_FS_FILE *File ) { EFI_DISK_IO_PROTOCOL *DiskIo; EFI_BLOCK_IO_PROTOCOL *BlockIo; UINT32 MediaId; VOID *Buffer; EFI_STATUS Status; DiskIo = File->Instance->DiskIo; BlockIo = File->Instance->BlockIo; MediaId = BlockIo->Media->MediaId; Buffer = AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION)); if (Buffer == NULL) { return EFI_OUT_OF_RESOURCES; } Status = DiskIo->WriteDisk (DiskIo, MediaId, File->HwDescAddress, sizeof (HW_IMAGE_DESCRIPTION), Buffer ); FreePool(Buffer); return Status; } /** Write the description of a file to storage media. This function uses DiskIo to write to the media, so call BlockIo->FlushBlocks() after calling it to ensure the data are written on the media. @param[in] File Description of the file whose description on the storage media has to be updated. @param[in] FileName Name of the file. Its length is assumed to be lower than MAX_NAME_LENGTH. @param[in] DataSize Number of data bytes of the file. @param[in] FileStart File's starting position on media. FileStart must be aligned to the media's block size. @retval EFI_WRITE_PROTECTED The device cannot be written to. @retval EFI_DEVICE_ERROR The device reported an error while performing the write operation. **/ STATIC EFI_STATUS WriteFileDescription ( IN BOOTMON_FS_FILE *File, IN CHAR8 *FileName, IN UINT32 DataSize, IN UINT64 FileStart ) { EFI_STATUS Status; EFI_DISK_IO_PROTOCOL *DiskIo; UINTN BlockSize; UINT32 FileSize; HW_IMAGE_DESCRIPTION *Description; DiskIo = File->Instance->DiskIo; BlockSize = File->Instance->BlockIo->Media->BlockSize; ASSERT (FileStart % BlockSize == 0); // // Construct the file description // FileSize = DataSize + sizeof (HW_IMAGE_DESCRIPTION); Description = &File->HwDescription; Description->Attributes = 1; Description->BlockStart = FileStart / BlockSize; Description->BlockEnd = Description->BlockStart + (FileSize / BlockSize); AsciiStrCpyS (Description->Footer.Filename, sizeof Description->Footer.Filename, FileName); #ifdef MDE_CPU_ARM Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET; Description->Footer.Version = HW_IMAGE_FOOTER_VERSION; #else Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET2; Description->Footer.Version = HW_IMAGE_FOOTER_VERSION2; #endif Description->Footer.FooterSignature1 = HW_IMAGE_FOOTER_SIGNATURE_1; Description->Footer.FooterSignature2 = HW_IMAGE_FOOTER_SIGNATURE_2; Description->RegionCount = 1; Description->Region[0].Checksum = 0; Description->Region[0].Offset = Description->BlockStart * BlockSize; Description->Region[0].Size = DataSize; Status = BootMonFsComputeFooterChecksum (Description); if (EFI_ERROR (Status)) { return Status; } File->HwDescAddress = ((Description->BlockEnd + 1) * BlockSize) - sizeof (HW_IMAGE_DESCRIPTION); // Update the file description on the media Status = DiskIo->WriteDisk ( DiskIo, File->Instance->Media->MediaId, File->HwDescAddress, sizeof (HW_IMAGE_DESCRIPTION), Description ); ASSERT_EFI_ERROR (Status); return Status; } // Find a space on media for a file that has not yet been flushed to disk. // Just returns the first space that's big enough. // This function could easily be adapted to: // - Find space for moving an existing file that has outgrown its space // (We do not currently move files, just return EFI_VOLUME_FULL) // - Find space for a fragment of a file that has outgrown its space // (We do not currently fragment files - it's not clear whether fragmentation // is actually part of BootMonFs as there is no spec) // - Be more clever about finding space (choosing the largest or smallest // suitable space) // Parameters: // File - the new (not yet flushed) file for which we need to find space. // FileStart - the position on media of the file (in bytes). STATIC EFI_STATUS BootMonFsFindSpaceForNewFile ( IN BOOTMON_FS_FILE *File, IN UINT64 FileSize, OUT UINT64 *FileStart ) { LIST_ENTRY *FileLink; BOOTMON_FS_FILE *RootFile; BOOTMON_FS_FILE *FileEntry; UINTN BlockSize; EFI_BLOCK_IO_MEDIA *Media; Media = File->Instance->BlockIo->Media; BlockSize = Media->BlockSize; RootFile = File->Instance->RootFile; // This function must only be called for file which has not been flushed into // Flash yet ASSERT (File->HwDescription.RegionCount == 0); *FileStart = 0; // Go through all the files in the list for (FileLink = GetFirstNode (&RootFile->Link); !IsNull (&RootFile->Link, FileLink); FileLink = GetNextNode (&RootFile->Link, FileLink) ) { FileEntry = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink); // Skip files that aren't on disk yet if (FileEntry->HwDescription.RegionCount == 0) { continue; } // If the free space preceding the file is big enough to contain the new // file then use it! if (((FileEntry->HwDescription.BlockStart * BlockSize) - *FileStart) >= FileSize) { // The file list must be in disk-order RemoveEntryList (&File->Link); File->Link.BackLink = FileLink->BackLink; File->Link.ForwardLink = FileLink; FileLink->BackLink->ForwardLink = &File->Link; FileLink->BackLink = &File->Link; return EFI_SUCCESS; } else { *FileStart = (FileEntry->HwDescription.BlockEnd + 1) * BlockSize; } } // See if there's space after the last file if ((((Media->LastBlock + 1) * BlockSize) - *FileStart) >= FileSize) { return EFI_SUCCESS; } else { return EFI_VOLUME_FULL; } } // Free the resources in the file's Region list. STATIC VOID FreeFileRegions ( IN BOOTMON_FS_FILE *File ) { LIST_ENTRY *RegionToFlushLink; BOOTMON_FS_FILE_REGION *Region; RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); while (!IsNull (&File->RegionToFlushLink, RegionToFlushLink)) { // Repeatedly remove the first node from the list and free its resources. Region = (BOOTMON_FS_FILE_REGION *) RegionToFlushLink; RemoveEntryList (RegionToFlushLink); FreePool (Region->Buffer); FreePool (Region); RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); } } /** Flush all modified data associated with a file to a device. @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle to flush. @retval EFI_SUCCESS The data was flushed. @retval EFI_ACCESS_DENIED The file was opened read-only. @retval EFI_DEVICE_ERROR The device reported an error. @retval EFI_VOLUME_FULL The volume is full. @retval EFI_OUT_OF_RESOURCES Not enough resources were available to flush the data. @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid. **/ EFIAPI EFI_STATUS BootMonFsFlushFile ( IN EFI_FILE_PROTOCOL *This ) { EFI_STATUS Status; BOOTMON_FS_INSTANCE *Instance; EFI_FILE_INFO *Info; EFI_BLOCK_IO_PROTOCOL *BlockIo; EFI_BLOCK_IO_MEDIA *Media; EFI_DISK_IO_PROTOCOL *DiskIo; UINTN BlockSize; CHAR8 AsciiFileName[MAX_NAME_LENGTH]; LIST_ENTRY *RegionToFlushLink; BOOTMON_FS_FILE *File; BOOTMON_FS_FILE *NextFile; BOOTMON_FS_FILE_REGION *Region; LIST_ENTRY *FileLink; UINTN CurrentPhysicalSize; UINT64 FileStart; UINT64 FileEnd; UINT64 RegionStart; UINT64 RegionEnd; UINT64 NewDataSize; UINT64 NewFileSize; UINT64 EndOfAppendSpace; BOOLEAN HasSpace; if (This == NULL) { return EFI_INVALID_PARAMETER; } File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File->Info == NULL) { return EFI_INVALID_PARAMETER; } if (File->OpenMode == EFI_FILE_MODE_READ) { return EFI_ACCESS_DENIED; } Instance = File->Instance; Info = File->Info; BlockIo = Instance->BlockIo; Media = BlockIo->Media; DiskIo = Instance->DiskIo; BlockSize = Media->BlockSize; UnicodeStrToAsciiStrS (Info->FileName, AsciiFileName, MAX_NAME_LENGTH); // If the file doesn't exist then find a space for it if (File->HwDescription.RegionCount == 0) { Status = BootMonFsFindSpaceForNewFile ( File, Info->FileSize + sizeof (HW_IMAGE_DESCRIPTION), &FileStart ); if (EFI_ERROR (Status)) { return Status; } } else { FileStart = File->HwDescription.BlockStart * BlockSize; } // FileEnd is the current NOR address of the end of the file's data FileEnd = FileStart + File->HwDescription.Region[0].Size; for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); !IsNull (&File->RegionToFlushLink, RegionToFlushLink); RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink) ) { Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink; if (Region->Size == 0) { continue; } // RegionStart and RegionEnd are the the intended NOR address of the // start and end of the region RegionStart = FileStart + Region->Offset; RegionEnd = RegionStart + Region->Size; if (RegionEnd < FileEnd) { // Handle regions representing edits to existing portions of the file // Write the region data straight into the file Status = DiskIo->WriteDisk (DiskIo, Media->MediaId, RegionStart, Region->Size, Region->Buffer ); if (EFI_ERROR (Status)) { return Status; } } else { // Handle regions representing appends to the file // // Note: Since seeking past the end of the file with SetPosition() is // valid, it's possible there will be a gap between the current end of // the file and the beginning of the new region. Since the UEFI spec // says nothing about this case (except "a subsequent write would grow // the file"), we just leave garbage in the gap. // Check if there is space to append the new region HasSpace = FALSE; NewDataSize = RegionEnd - FileStart; NewFileSize = NewDataSize + sizeof (HW_IMAGE_DESCRIPTION); CurrentPhysicalSize = BootMonFsGetPhysicalSize (File); if (NewFileSize <= CurrentPhysicalSize) { HasSpace = TRUE; } else { // Get the File Description for the next file FileLink = GetNextNode (&Instance->RootFile->Link, &File->Link); if (!IsNull (&Instance->RootFile->Link, FileLink)) { NextFile = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink); // If there is space between the beginning of the current file and the // beginning of the next file then use it EndOfAppendSpace = NextFile->HwDescription.BlockStart * BlockSize; } else { // We are flushing the last file. EndOfAppendSpace = (Media->LastBlock + 1) * BlockSize; } if (EndOfAppendSpace - FileStart >= NewFileSize) { HasSpace = TRUE; } } if (HasSpace == TRUE) { // Invalidate the current image description of the file if any. if (File->HwDescAddress != 0) { Status = InvalidateImageDescription (File); if (EFI_ERROR (Status)) { return Status; } } // Write the new file data Status = DiskIo->WriteDisk ( DiskIo, Media->MediaId, RegionStart, Region->Size, Region->Buffer ); if (EFI_ERROR (Status)) { return Status; } Status = WriteFileDescription (File, AsciiFileName, NewDataSize, FileStart); if (EFI_ERROR (Status)) { return Status; } } else { // There isn't a space for the file. // Options here are to move the file or fragment it. However as files // may represent boot images at fixed positions, these options will // break booting if the bootloader doesn't use BootMonFs to find the // image. return EFI_VOLUME_FULL; } } } FreeFileRegions (File); Info->PhysicalSize = BootMonFsGetPhysicalSize (File); if ((AsciiStrCmp (AsciiFileName, File->HwDescription.Footer.Filename) != 0) || (Info->FileSize != File->HwDescription.Region[0].Size) ) { Status = WriteFileDescription (File, AsciiFileName, Info->FileSize, FileStart); if (EFI_ERROR (Status)) { return Status; } } // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by // calling FlushBlocks on the same device's BlockIo). BlockIo->FlushBlocks (BlockIo); return EFI_SUCCESS; } /** Close a specified file handle. @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle to close. @retval EFI_SUCCESS The file was closed. @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open file handle. **/ EFIAPI EFI_STATUS BootMonFsCloseFile ( IN EFI_FILE_PROTOCOL *This ) { BOOTMON_FS_FILE *File; if (This == NULL) { return EFI_INVALID_PARAMETER; } File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File->Info == NULL) { return EFI_INVALID_PARAMETER; } // In the case of a file and not the root directory if (This != &File->Instance->RootFile->File) { This->Flush (This); FreePool (File->Info); File->Info = NULL; } return EFI_SUCCESS; } /** Open a file on the boot monitor file system. The boot monitor file system does not allow for sub-directories. There is only one directory, the root one. On any attempt to create a directory, the function returns in error with the EFI_WRITE_PROTECTED error code. @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle to source location. @param[out] NewHandle A pointer to the location to return the opened handle for the new file. @param[in] FileName The Null-terminated string of the name of the file to be opened. @param[in] OpenMode The mode to open the file : Read or Read/Write or Read/Write/Create @param[in] Attributes Attributes of the file in case of a file creation @retval EFI_SUCCESS The file was open. @retval EFI_NOT_FOUND The specified file could not be found or the specified directory in which to create a file could not be found. @retval EFI_DEVICE_ERROR The device reported an error. @retval EFI_WRITE_PROTECTED Attempt to create a directory. This is not possible with the Boot Monitor file system. @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file. @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid. **/ EFIAPI EFI_STATUS BootMonFsOpenFile ( IN EFI_FILE_PROTOCOL *This, OUT EFI_FILE_PROTOCOL **NewHandle, IN CHAR16 *FileName, IN UINT64 OpenMode, IN UINT64 Attributes ) { EFI_STATUS Status; BOOTMON_FS_FILE *Directory; BOOTMON_FS_FILE *File; BOOTMON_FS_INSTANCE *Instance; CHAR8 *Buf; CHAR16 *Path; CHAR16 *Separator; CHAR8 *AsciiFileName; EFI_FILE_INFO *Info; UINTN AsciiFileNameSize; if (This == NULL) { return EFI_INVALID_PARAMETER; } Directory = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (Directory->Info == NULL) { return EFI_INVALID_PARAMETER; } if ((FileName == NULL) || (NewHandle == NULL)) { return EFI_INVALID_PARAMETER; } // // The only valid modes are read, read/write, and read/write/create // if ( (OpenMode != EFI_FILE_MODE_READ) && (OpenMode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE)) && (OpenMode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE)) ) { return EFI_INVALID_PARAMETER; } Instance = Directory->Instance; // // If the instance has not been initialized yet then do it ... // if (!Instance->Initialized) { Status = BootMonFsInitialize (Instance); if (EFI_ERROR (Status)) { return Status; } } // // Copy the file path to be able to work on it. We do not want to // modify the input file name string "FileName". // Buf = AllocateCopyPool (StrSize (FileName), FileName); if (Buf == NULL) { return EFI_OUT_OF_RESOURCES; } Path = (CHAR16*)Buf; AsciiFileName = NULL; Info = NULL; // // Handle single periods, double periods and convert forward slashes '/' // to backward '\' ones. Does not handle a '.' at the beginning of the // path for the time being. // if (PathCleanUpDirectories (Path) == NULL) { Status = EFI_INVALID_PARAMETER; goto Error; } // // Detect if the first component of the path refers to a directory. // This is done to return the correct error code when trying to // access or create a directory other than the root directory. // // // Search for the '\\' sequence and if found return in error // with the EFI_INVALID_PARAMETER error code. ere in the path. // if (StrStr (Path, L"\\\\") != NULL) { Status = EFI_INVALID_PARAMETER; goto Error; } // // Get rid of the leading '\' if any. // Path += (Path[0] == L'\\'); // // Look for a '\' in the file path. If one is found then // the first component of the path refers to a directory // that is not the root directory. // Separator = StrStr (Path, L"\\"); if (Separator != NULL) { // // In the case '\' and a creation, return // EFI_WRITE_PROTECTED if this is for a directory // creation, EFI_INVALID_PARAMETER otherwise. // if ((*(Separator + 1) == '\0') && ((OpenMode & EFI_FILE_MODE_CREATE) != 0)) { if (Attributes & EFI_FILE_DIRECTORY) { Status = EFI_WRITE_PROTECTED; } else { Status = EFI_INVALID_PARAMETER; } } else { // // Attempt to open a file or a directory that is not in the // root directory or to open without creation a directory // located in the root directory, returns EFI_NOT_FOUND. // Status = EFI_NOT_FOUND; } goto Error; } // // BootMonFs interface requires ASCII filenames // AsciiFileNameSize = StrLen (Path) + 1; if (AsciiFileNameSize > MAX_NAME_LENGTH) { AsciiFileNameSize = MAX_NAME_LENGTH; } AsciiFileName = AllocatePool (AsciiFileNameSize); if (AsciiFileName == NULL) { Status = EFI_OUT_OF_RESOURCES; goto Error; } UnicodeStrToAsciiStrS (Path, AsciiFileName, AsciiFileNameSize); if ((AsciiFileName[0] == '\0') || (AsciiFileName[0] == '.' ) ) { // // Opening the root directory // *NewHandle = &Instance->RootFile->File; Instance->RootFile->Position = 0; Status = EFI_SUCCESS; } else { if ((OpenMode & EFI_FILE_MODE_CREATE) && (Attributes & EFI_FILE_DIRECTORY) ) { Status = EFI_WRITE_PROTECTED; goto Error; } // // Allocate a buffer to store the characteristics of the file while the // file is open. We allocate the maximum size to not have to reallocate // if the file name is changed. // Info = AllocateZeroPool ( SIZE_OF_EFI_FILE_INFO + (sizeof (CHAR16) * MAX_NAME_LENGTH)); if (Info == NULL) { Status = EFI_OUT_OF_RESOURCES; goto Error; } // // Open or create a file in the root directory. // Status = BootMonGetFileFromAsciiFileName (Instance, AsciiFileName, &File); if (Status == EFI_NOT_FOUND) { if ((OpenMode & EFI_FILE_MODE_CREATE) == 0) { goto Error; } Status = BootMonFsCreateFile (Instance, &File); if (EFI_ERROR (Status)) { goto Error; } InsertHeadList (&Instance->RootFile->Link, &File->Link); Info->Attribute = Attributes; } else { // // File already open, not supported yet. // if (File->Info != NULL) { Status = EFI_UNSUPPORTED; goto Error; } } Info->FileSize = BootMonFsGetImageLength (File); Info->PhysicalSize = BootMonFsGetPhysicalSize (File); AsciiStrToUnicodeStrS (AsciiFileName, Info->FileName, MAX_NAME_LENGTH); File->Info = Info; Info = NULL; File->Position = 0; File->OpenMode = OpenMode; *NewHandle = &File->File; } Error: FreePool (Buf); if (AsciiFileName != NULL) { FreePool (AsciiFileName); } if (Info != NULL) { FreePool (Info); } return Status; } // Delete() for the root directory's EFI_FILE_PROTOCOL instance EFIAPI EFI_STATUS BootMonFsDeleteFail ( IN EFI_FILE_PROTOCOL *This ) { This->Close(This); // You can't delete the root directory return EFI_WARN_DELETE_FAILURE; } /** Close and delete a file from the boot monitor file system. @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file handle to delete. @retval EFI_SUCCESS The file was closed and deleted. @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open file handle. @retval EFI_WARN_DELETE_FAILURE The handle was closed, but the file was not deleted. **/ EFIAPI EFI_STATUS BootMonFsDelete ( IN EFI_FILE_PROTOCOL *This ) { EFI_STATUS Status; BOOTMON_FS_FILE *File; LIST_ENTRY *RegionToFlushLink; BOOTMON_FS_FILE_REGION *Region; if (This == NULL) { return EFI_INVALID_PARAMETER; } File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File->Info == NULL) { return EFI_INVALID_PARAMETER; } if (!IsListEmpty (&File->RegionToFlushLink)) { // Free the entries from the Buffer List RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); do { Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink; // // Get next element of the list before deleting the region description // that contain the LIST_ENTRY structure. // RegionToFlushLink = RemoveEntryList (RegionToFlushLink); // Free the buffers FreePool (Region->Buffer); FreePool (Region); } while (!IsListEmpty (&File->RegionToFlushLink)); } // If (RegionCount is greater than 0) then the file already exists if (File->HwDescription.RegionCount > 0) { // Invalidate the last Block Status = InvalidateImageDescription (File); ASSERT_EFI_ERROR (Status); if (EFI_ERROR (Status)) { return EFI_WARN_DELETE_FAILURE; } } // Remove the entry from the list RemoveEntryList (&File->Link); FreePool (File->Info); FreePool (File); return EFI_SUCCESS; }