// 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)); }
/** Search for a file given its name coded in Ascii. When searching through the files of the volume, if a file is currently not open, its name was written on the media and is kept in RAM in the "HwDescription.Footer.Filename[]" field of the file's description. If a file is currently open, its name might not have been written on the media yet, and as the "HwDescription" is a mirror in RAM of what is on the media the "HwDescription.Footer.Filename[]" might be outdated. In that case, the up to date name of the file is stored in the "Info" field of the file's description. @param[in] Instance Pointer to the description of the volume in which the file has to be search for. @param[in] AsciiFileName Name of the file. @param[out] File Pointer to the description of the file if the file was found. @retval EFI_SUCCESS The file was found. @retval EFI_NOT_FOUND The file was not found. **/ EFI_STATUS BootMonGetFileFromAsciiFileName ( IN BOOTMON_FS_INSTANCE *Instance, IN CHAR8* AsciiFileName, OUT BOOTMON_FS_FILE **File ) { LIST_ENTRY *Entry; BOOTMON_FS_FILE *FileEntry; CHAR8 OpenFileAsciiFileName[MAX_NAME_LENGTH]; CHAR8 *AsciiFileNameToCompare; // Go through all the files in the list and return the file handle for (Entry = GetFirstNode (&Instance->RootFile->Link); !IsNull (&Instance->RootFile->Link, Entry); Entry = GetNextNode (&Instance->RootFile->Link, Entry) ) { FileEntry = BOOTMON_FS_FILE_FROM_LINK_THIS (Entry); if (FileEntry->Info != NULL) { UnicodeStrToAsciiStr (FileEntry->Info->FileName, OpenFileAsciiFileName); AsciiFileNameToCompare = OpenFileAsciiFileName; } else { AsciiFileNameToCompare = FileEntry->HwDescription.Footer.Filename; } if (AsciiStrCmp (AsciiFileNameToCompare, AsciiFileName) == 0) { *File = FileEntry; return EFI_SUCCESS; } } return EFI_NOT_FOUND; }
EFI_STATUS BootMonGetFileFromPosition ( IN BOOTMON_FS_INSTANCE *Instance, IN UINTN Position, OUT BOOTMON_FS_FILE **File ) { LIST_ENTRY *Entry; BOOTMON_FS_FILE *FileEntry; // Go through all the files in the list and return the file handle for (Entry = GetFirstNode (&Instance->RootFile->Link); !IsNull (&Instance->RootFile->Link, Entry) && (&Instance->RootFile->Link != Entry); Entry = GetNextNode (&Instance->RootFile->Link, Entry) ) { if (Position == 0) { FileEntry = BOOTMON_FS_FILE_FROM_LINK_THIS (Entry); *File = FileEntry; return EFI_SUCCESS; } Position--; } return EFI_NOT_FOUND; }
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; }
EFIAPI EFI_STATUS BootMonFsFlushFile ( IN EFI_FILE_PROTOCOL *This ) { EFI_STATUS Status; BOOTMON_FS_INSTANCE *Instance; LIST_ENTRY *RegionToFlushLink; BOOTMON_FS_FILE *File; BOOTMON_FS_FILE *NextFile; BOOTMON_FS_FILE_REGION *Region; LIST_ENTRY *FileLink; UINTN CurrentPhysicalSize; UINTN BlockSize; UINT64 FileStart; UINT64 FileEnd; UINT64 RegionStart; UINT64 RegionEnd; UINT64 NewFileSize; UINT64 EndOfAppendSpace; BOOLEAN HasSpace; EFI_DISK_IO_PROTOCOL *DiskIo; EFI_BLOCK_IO_PROTOCOL *BlockIo; Status = EFI_SUCCESS; FileStart = 0; File = BOOTMON_FS_FILE_FROM_FILE_THIS (This); if (File == NULL) { return EFI_INVALID_PARAMETER; } // Check if the file needs to be flushed if (!BootMonFsFileNeedFlush (File)) { return Status; } Instance = File->Instance; BlockIo = Instance->BlockIo; DiskIo = Instance->DiskIo; BlockSize = BlockIo->Media->BlockSize; // If the file doesn't exist then find a space for it if (File->HwDescription.RegionCount == 0) { Status = BootMonFsFindSpaceForNewFile (File, &FileStart); // FileStart has changed so we need to recompute RegionEnd if (EFI_ERROR (Status)) { return Status; } } else { FileStart = File->HwDescription.BlockStart * BlockSize; } // FileEnd is the NOR address of the end of the file's data FileEnd = FileStart + BootMonFsGetImageLength (File); for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink); !IsNull (&File->RegionToFlushLink, RegionToFlushLink); RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink) ) { Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink; // 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, BlockIo->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; NewFileSize = (RegionEnd - FileStart) + 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 = (BlockIo->Media->LastBlock + 1) * BlockSize; } if (EndOfAppendSpace - FileStart >= NewFileSize) { HasSpace = TRUE; } } if (HasSpace == TRUE) { Status = FlushAppendRegion (File, Region, NewFileSize, 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); // 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 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, OUT UINT64 *FileStart ) { LIST_ENTRY *FileLink; BOOTMON_FS_FILE *RootFile; BOOTMON_FS_FILE *FileEntry; UINTN BlockSize; UINT64 FileSize; EFI_BLOCK_IO_MEDIA *Media; Media = File->Instance->BlockIo->Media; BlockSize = Media->BlockSize; RootFile = File->Instance->RootFile; if (IsListEmpty (&RootFile->Link)) { return EFI_SUCCESS; } // This function must only be called for file which has not been flushed into // Flash yet ASSERT (File->HwDescription.RegionCount == 0); // Find out how big the file will be FileSize = BootMonFsGetImageLength (File); // Add the file header to the file FileSize += sizeof (HW_IMAGE_DESCRIPTION); *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); // 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; } }