blob: af2fe514f0440221d1ff710d58fe08b904ae8e58 [file] [log] [blame]
/** @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);
AsciiStrCpy (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;
UnicodeStrToAsciiStr (Info->FileName, AsciiFileName);
// 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;
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 '<dir name>\' 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
//
AsciiFileName = AllocatePool (StrLen (Path) + 1);
if (AsciiFileName == NULL) {
Status = EFI_OUT_OF_RESOURCES;
goto Error;
}
UnicodeStrToAsciiStr (Path, AsciiFileName);
if (AsciiStrSize (AsciiFileName) > MAX_NAME_LENGTH) {
AsciiFileName[MAX_NAME_LENGTH - 1] = '\0';
}
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);
AsciiStrToUnicodeStr (AsciiFileName, Info->FileName);
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;
}