3 * Copyright (c) 2012-2015, ARM Limited. All rights reserved.
5 * This program and the accompanying materials
6 * are licensed and made available under the terms and conditions of the BSD License
7 * which accompanies this distribution. The full text of the license may be found at
8 * http://opensource.org/licenses/bsd-license.php
10 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
15 #include "BootMonFsInternal.h"
17 // Clear a file's image description on storage media:
18 // UEFI allows you to seek past the end of a file, a subsequent write will grow
19 // the file. It does not specify how space between the former end of the file
20 // and the beginning of the write should be filled. It's therefore possible that
21 // BootMonFs metadata, that comes after the end of a file, could be left there
22 // and wrongly detected by BootMonFsImageInBlock.
25 InvalidateImageDescription (
26 IN BOOTMON_FS_FILE
*File
29 EFI_DISK_IO_PROTOCOL
*DiskIo
;
30 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
35 DiskIo
= File
->Instance
->DiskIo
;
36 BlockIo
= File
->Instance
->BlockIo
;
37 MediaId
= BlockIo
->Media
->MediaId
;
39 Buffer
= AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION
));
42 return EFI_OUT_OF_RESOURCES
;
45 Status
= DiskIo
->WriteDisk (DiskIo
,
48 sizeof (HW_IMAGE_DESCRIPTION
),
58 Write the description of a file to storage media.
60 This function uses DiskIo to write to the media, so call BlockIo->FlushBlocks()
61 after calling it to ensure the data are written on the media.
63 @param[in] File Description of the file whose description on the
64 storage media has to be updated.
65 @param[in] FileName Name of the file. Its length is assumed to be
66 lower than MAX_NAME_LENGTH.
67 @param[in] DataSize Number of data bytes of the file.
68 @param[in] FileStart File's starting position on media. FileStart must
69 be aligned to the media's block size.
71 @retval EFI_WRITE_PROTECTED The device cannot be written to.
72 @retval EFI_DEVICE_ERROR The device reported an error while performing
78 WriteFileDescription (
79 IN BOOTMON_FS_FILE
*File
,
86 EFI_DISK_IO_PROTOCOL
*DiskIo
;
89 HW_IMAGE_DESCRIPTION
*Description
;
91 DiskIo
= File
->Instance
->DiskIo
;
92 BlockSize
= File
->Instance
->BlockIo
->Media
->BlockSize
;
93 ASSERT (FileStart
% BlockSize
== 0);
96 // Construct the file description
99 FileSize
= DataSize
+ sizeof (HW_IMAGE_DESCRIPTION
);
100 Description
= &File
->HwDescription
;
101 Description
->Attributes
= 1;
102 Description
->BlockStart
= FileStart
/ BlockSize
;
103 Description
->BlockEnd
= Description
->BlockStart
+ (FileSize
/ BlockSize
);
104 AsciiStrCpy (Description
->Footer
.Filename
, FileName
);
107 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET
;
108 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION
;
110 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET2
;
111 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION2
;
113 Description
->Footer
.FooterSignature1
= HW_IMAGE_FOOTER_SIGNATURE_1
;
114 Description
->Footer
.FooterSignature2
= HW_IMAGE_FOOTER_SIGNATURE_2
;
115 Description
->RegionCount
= 1;
116 Description
->Region
[0].Checksum
= 0;
117 Description
->Region
[0].Offset
= Description
->BlockStart
* BlockSize
;
118 Description
->Region
[0].Size
= DataSize
;
120 Status
= BootMonFsComputeFooterChecksum (Description
);
121 if (EFI_ERROR (Status
)) {
125 File
->HwDescAddress
= ((Description
->BlockEnd
+ 1) * BlockSize
) - sizeof (HW_IMAGE_DESCRIPTION
);
127 // Update the file description on the media
128 Status
= DiskIo
->WriteDisk (
130 File
->Instance
->Media
->MediaId
,
132 sizeof (HW_IMAGE_DESCRIPTION
),
135 ASSERT_EFI_ERROR (Status
);
140 // Find a space on media for a file that has not yet been flushed to disk.
141 // Just returns the first space that's big enough.
142 // This function could easily be adapted to:
143 // - Find space for moving an existing file that has outgrown its space
144 // (We do not currently move files, just return EFI_VOLUME_FULL)
145 // - Find space for a fragment of a file that has outgrown its space
146 // (We do not currently fragment files - it's not clear whether fragmentation
147 // is actually part of BootMonFs as there is no spec)
148 // - Be more clever about finding space (choosing the largest or smallest
151 // File - the new (not yet flushed) file for which we need to find space.
152 // FileStart - the position on media of the file (in bytes).
155 BootMonFsFindSpaceForNewFile (
156 IN BOOTMON_FS_FILE
*File
,
158 OUT UINT64
*FileStart
161 LIST_ENTRY
*FileLink
;
162 BOOTMON_FS_FILE
*RootFile
;
163 BOOTMON_FS_FILE
*FileEntry
;
165 EFI_BLOCK_IO_MEDIA
*Media
;
167 Media
= File
->Instance
->BlockIo
->Media
;
168 BlockSize
= Media
->BlockSize
;
169 RootFile
= File
->Instance
->RootFile
;
171 // This function must only be called for file which has not been flushed into
173 ASSERT (File
->HwDescription
.RegionCount
== 0);
176 // Go through all the files in the list
177 for (FileLink
= GetFirstNode (&RootFile
->Link
);
178 !IsNull (&RootFile
->Link
, FileLink
);
179 FileLink
= GetNextNode (&RootFile
->Link
, FileLink
)
182 FileEntry
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
183 // Skip files that aren't on disk yet
184 if (FileEntry
->HwDescription
.RegionCount
== 0) {
188 // If the free space preceding the file is big enough to contain the new
190 if (((FileEntry
->HwDescription
.BlockStart
* BlockSize
) - *FileStart
)
192 // The file list must be in disk-order
193 RemoveEntryList (&File
->Link
);
194 File
->Link
.BackLink
= FileLink
->BackLink
;
195 File
->Link
.ForwardLink
= FileLink
;
196 FileLink
->BackLink
->ForwardLink
= &File
->Link
;
197 FileLink
->BackLink
= &File
->Link
;
201 *FileStart
= (FileEntry
->HwDescription
.BlockEnd
+ 1) * BlockSize
;
204 // See if there's space after the last file
205 if ((((Media
->LastBlock
+ 1) * BlockSize
) - *FileStart
) >= FileSize
) {
208 return EFI_VOLUME_FULL
;
212 // Free the resources in the file's Region list.
216 IN BOOTMON_FS_FILE
*File
219 LIST_ENTRY
*RegionToFlushLink
;
220 BOOTMON_FS_FILE_REGION
*Region
;
222 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
223 while (!IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
)) {
224 // Repeatedly remove the first node from the list and free its resources.
225 Region
= (BOOTMON_FS_FILE_REGION
*) RegionToFlushLink
;
226 RemoveEntryList (RegionToFlushLink
);
227 FreePool (Region
->Buffer
);
230 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
235 Flush all modified data associated with a file to a device.
237 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the
238 file handle to flush.
240 @retval EFI_SUCCESS The data was flushed.
241 @retval EFI_ACCESS_DENIED The file was opened read-only.
242 @retval EFI_DEVICE_ERROR The device reported an error.
243 @retval EFI_VOLUME_FULL The volume is full.
244 @retval EFI_OUT_OF_RESOURCES Not enough resources were available to flush the data.
245 @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid.
251 IN EFI_FILE_PROTOCOL
*This
255 BOOTMON_FS_INSTANCE
*Instance
;
257 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
258 EFI_BLOCK_IO_MEDIA
*Media
;
259 EFI_DISK_IO_PROTOCOL
*DiskIo
;
261 CHAR8 AsciiFileName
[MAX_NAME_LENGTH
];
262 LIST_ENTRY
*RegionToFlushLink
;
263 BOOTMON_FS_FILE
*File
;
264 BOOTMON_FS_FILE
*NextFile
;
265 BOOTMON_FS_FILE_REGION
*Region
;
266 LIST_ENTRY
*FileLink
;
267 UINTN CurrentPhysicalSize
;
274 UINT64 EndOfAppendSpace
;
278 return EFI_INVALID_PARAMETER
;
281 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
282 if (File
->Info
== NULL
) {
283 return EFI_INVALID_PARAMETER
;
286 if (File
->OpenMode
== EFI_FILE_MODE_READ
) {
287 return EFI_ACCESS_DENIED
;
290 Instance
= File
->Instance
;
292 BlockIo
= Instance
->BlockIo
;
293 Media
= BlockIo
->Media
;
294 DiskIo
= Instance
->DiskIo
;
295 BlockSize
= Media
->BlockSize
;
297 UnicodeStrToAsciiStr (Info
->FileName
, AsciiFileName
);
299 // If the file doesn't exist then find a space for it
300 if (File
->HwDescription
.RegionCount
== 0) {
301 Status
= BootMonFsFindSpaceForNewFile (
303 Info
->FileSize
+ sizeof (HW_IMAGE_DESCRIPTION
),
306 if (EFI_ERROR (Status
)) {
310 FileStart
= File
->HwDescription
.BlockStart
* BlockSize
;
312 // FileEnd is the current NOR address of the end of the file's data
313 FileEnd
= FileStart
+ File
->HwDescription
.Region
[0].Size
;
315 for (RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
316 !IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
);
317 RegionToFlushLink
= GetNextNode (&File
->RegionToFlushLink
, RegionToFlushLink
)
320 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
321 if (Region
->Size
== 0) {
325 // RegionStart and RegionEnd are the the intended NOR address of the
326 // start and end of the region
327 RegionStart
= FileStart
+ Region
->Offset
;
328 RegionEnd
= RegionStart
+ Region
->Size
;
330 if (RegionEnd
< FileEnd
) {
331 // Handle regions representing edits to existing portions of the file
332 // Write the region data straight into the file
333 Status
= DiskIo
->WriteDisk (DiskIo
,
339 if (EFI_ERROR (Status
)) {
343 // Handle regions representing appends to the file
345 // Note: Since seeking past the end of the file with SetPosition() is
346 // valid, it's possible there will be a gap between the current end of
347 // the file and the beginning of the new region. Since the UEFI spec
348 // says nothing about this case (except "a subsequent write would grow
349 // the file"), we just leave garbage in the gap.
351 // Check if there is space to append the new region
353 NewDataSize
= RegionEnd
- FileStart
;
354 NewFileSize
= NewDataSize
+ sizeof (HW_IMAGE_DESCRIPTION
);
355 CurrentPhysicalSize
= BootMonFsGetPhysicalSize (File
);
356 if (NewFileSize
<= CurrentPhysicalSize
) {
359 // Get the File Description for the next file
360 FileLink
= GetNextNode (&Instance
->RootFile
->Link
, &File
->Link
);
361 if (!IsNull (&Instance
->RootFile
->Link
, FileLink
)) {
362 NextFile
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
364 // If there is space between the beginning of the current file and the
365 // beginning of the next file then use it
366 EndOfAppendSpace
= NextFile
->HwDescription
.BlockStart
* BlockSize
;
368 // We are flushing the last file.
369 EndOfAppendSpace
= (Media
->LastBlock
+ 1) * BlockSize
;
371 if (EndOfAppendSpace
- FileStart
>= NewFileSize
) {
376 if (HasSpace
== TRUE
) {
377 // Invalidate the current image description of the file if any.
378 if (File
->HwDescAddress
!= 0) {
379 Status
= InvalidateImageDescription (File
);
380 if (EFI_ERROR (Status
)) {
385 // Write the new file data
386 Status
= DiskIo
->WriteDisk (
393 if (EFI_ERROR (Status
)) {
397 Status
= WriteFileDescription (File
, AsciiFileName
, NewDataSize
, FileStart
);
398 if (EFI_ERROR (Status
)) {
403 // There isn't a space for the file.
404 // Options here are to move the file or fragment it. However as files
405 // may represent boot images at fixed positions, these options will
406 // break booting if the bootloader doesn't use BootMonFs to find the
409 return EFI_VOLUME_FULL
;
414 FreeFileRegions (File
);
415 Info
->PhysicalSize
= BootMonFsGetPhysicalSize (File
);
417 if ((AsciiStrCmp (AsciiFileName
, File
->HwDescription
.Footer
.Filename
) != 0) ||
418 (Info
->FileSize
!= File
->HwDescription
.Region
[0].Size
) ) {
419 Status
= WriteFileDescription (File
, AsciiFileName
, Info
->FileSize
, FileStart
);
420 if (EFI_ERROR (Status
)) {
425 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
426 // calling FlushBlocks on the same device's BlockIo).
427 BlockIo
->FlushBlocks (BlockIo
);
433 Close a specified file handle.
435 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file
438 @retval EFI_SUCCESS The file was closed.
439 @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open
446 IN EFI_FILE_PROTOCOL
*This
449 BOOTMON_FS_FILE
*File
;
452 return EFI_INVALID_PARAMETER
;
455 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
456 if (File
->Info
== NULL
) {
457 return EFI_INVALID_PARAMETER
;
460 // In the case of a file and not the root directory
461 if (This
!= &File
->Instance
->RootFile
->File
) {
463 FreePool (File
->Info
);
471 Open a file on the boot monitor file system.
473 The boot monitor file system does not allow for sub-directories. There is only
474 one directory, the root one. On any attempt to create a directory, the function
475 returns in error with the EFI_WRITE_PROTECTED error code.
477 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is
478 the file handle to source location.
479 @param[out] NewHandle A pointer to the location to return the opened
480 handle for the new file.
481 @param[in] FileName The Null-terminated string of the name of the file
483 @param[in] OpenMode The mode to open the file : Read or Read/Write or
485 @param[in] Attributes Attributes of the file in case of a file creation
487 @retval EFI_SUCCESS The file was open.
488 @retval EFI_NOT_FOUND The specified file could not be found or the specified
489 directory in which to create a file could not be found.
490 @retval EFI_DEVICE_ERROR The device reported an error.
491 @retval EFI_WRITE_PROTECTED Attempt to create a directory. This is not possible
492 with the Boot Monitor file system.
493 @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
494 @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid.
500 IN EFI_FILE_PROTOCOL
*This
,
501 OUT EFI_FILE_PROTOCOL
**NewHandle
,
508 BOOTMON_FS_FILE
*Directory
;
509 BOOTMON_FS_FILE
*File
;
510 BOOTMON_FS_INSTANCE
*Instance
;
514 CHAR8
*AsciiFileName
;
518 return EFI_INVALID_PARAMETER
;
521 Directory
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
522 if (Directory
->Info
== NULL
) {
523 return EFI_INVALID_PARAMETER
;
526 if ((FileName
== NULL
) || (NewHandle
== NULL
)) {
527 return EFI_INVALID_PARAMETER
;
531 // The only valid modes are read, read/write, and read/write/create
533 if ( (OpenMode
!= EFI_FILE_MODE_READ
) &&
534 (OpenMode
!= (EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
)) &&
535 (OpenMode
!= (EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
| EFI_FILE_MODE_CREATE
)) ) {
536 return EFI_INVALID_PARAMETER
;
539 Instance
= Directory
->Instance
;
542 // If the instance has not been initialized yet then do it ...
544 if (!Instance
->Initialized
) {
545 Status
= BootMonFsInitialize (Instance
);
546 if (EFI_ERROR (Status
)) {
552 // Copy the file path to be able to work on it. We do not want to
553 // modify the input file name string "FileName".
555 Buf
= AllocateCopyPool (StrSize (FileName
), FileName
);
557 return EFI_OUT_OF_RESOURCES
;
560 AsciiFileName
= NULL
;
564 // Handle single periods, double periods and convert forward slashes '/'
565 // to backward '\' ones. Does not handle a '.' at the beginning of the
566 // path for the time being.
568 if (PathCleanUpDirectories (Path
) == NULL
) {
569 Status
= EFI_INVALID_PARAMETER
;
574 // Detect if the first component of the path refers to a directory.
575 // This is done to return the correct error code when trying to
576 // access or create a directory other than the root directory.
580 // Search for the '\\' sequence and if found return in error
581 // with the EFI_INVALID_PARAMETER error code. ere in the path.
583 if (StrStr (Path
, L
"\\\\") != NULL
) {
584 Status
= EFI_INVALID_PARAMETER
;
588 // Get rid of the leading '\' if any.
590 Path
+= (Path
[0] == L
'\\');
593 // Look for a '\' in the file path. If one is found then
594 // the first component of the path refers to a directory
595 // that is not the root directory.
597 Separator
= StrStr (Path
, L
"\\");
598 if (Separator
!= NULL
) {
600 // In the case '<dir name>\' and a creation, return
601 // EFI_WRITE_PROTECTED if this is for a directory
602 // creation, EFI_INVALID_PARAMETER otherwise.
604 if ((*(Separator
+ 1) == '\0') && ((OpenMode
& EFI_FILE_MODE_CREATE
) != 0)) {
605 if (Attributes
& EFI_FILE_DIRECTORY
) {
606 Status
= EFI_WRITE_PROTECTED
;
608 Status
= EFI_INVALID_PARAMETER
;
612 // Attempt to open a file or a directory that is not in the
613 // root directory or to open without creation a directory
614 // located in the root directory, returns EFI_NOT_FOUND.
616 Status
= EFI_NOT_FOUND
;
622 // BootMonFs interface requires ASCII filenames
624 AsciiFileName
= AllocatePool (StrLen (Path
) + 1);
625 if (AsciiFileName
== NULL
) {
626 Status
= EFI_OUT_OF_RESOURCES
;
629 UnicodeStrToAsciiStr (Path
, AsciiFileName
);
630 if (AsciiStrSize (AsciiFileName
) > MAX_NAME_LENGTH
) {
631 AsciiFileName
[MAX_NAME_LENGTH
- 1] = '\0';
634 if ((AsciiFileName
[0] == '\0') ||
635 (AsciiFileName
[0] == '.' ) ) {
637 // Opening the root directory
640 *NewHandle
= &Instance
->RootFile
->File
;
641 Instance
->RootFile
->Position
= 0;
642 Status
= EFI_SUCCESS
;
645 if ((OpenMode
& EFI_FILE_MODE_CREATE
) &&
646 (Attributes
& EFI_FILE_DIRECTORY
) ) {
647 Status
= EFI_WRITE_PROTECTED
;
652 // Allocate a buffer to store the characteristics of the file while the
653 // file is open. We allocate the maximum size to not have to reallocate
654 // if the file name is changed.
656 Info
= AllocateZeroPool (
657 SIZE_OF_EFI_FILE_INFO
+ (sizeof (CHAR16
) * MAX_NAME_LENGTH
));
659 Status
= EFI_OUT_OF_RESOURCES
;
664 // Open or create a file in the root directory.
667 Status
= BootMonGetFileFromAsciiFileName (Instance
, AsciiFileName
, &File
);
668 if (Status
== EFI_NOT_FOUND
) {
669 if ((OpenMode
& EFI_FILE_MODE_CREATE
) == 0) {
673 Status
= BootMonFsCreateFile (Instance
, &File
);
674 if (EFI_ERROR (Status
)) {
677 InsertHeadList (&Instance
->RootFile
->Link
, &File
->Link
);
678 Info
->Attribute
= Attributes
;
681 // File already open, not supported yet.
683 if (File
->Info
!= NULL
) {
684 Status
= EFI_UNSUPPORTED
;
689 Info
->FileSize
= BootMonFsGetImageLength (File
);
690 Info
->PhysicalSize
= BootMonFsGetPhysicalSize (File
);
691 AsciiStrToUnicodeStr (AsciiFileName
, Info
->FileName
);
696 File
->OpenMode
= OpenMode
;
698 *NewHandle
= &File
->File
;
704 if (AsciiFileName
!= NULL
) {
705 FreePool (AsciiFileName
);
714 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
717 BootMonFsDeleteFail (
718 IN EFI_FILE_PROTOCOL
*This
722 // You can't delete the root directory
723 return EFI_WARN_DELETE_FAILURE
;
727 Close and delete a file from the boot monitor file system.
729 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file
732 @retval EFI_SUCCESS The file was closed and deleted.
733 @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open
735 @retval EFI_WARN_DELETE_FAILURE The handle was closed, but the file was not deleted.
741 IN EFI_FILE_PROTOCOL
*This
745 BOOTMON_FS_FILE
*File
;
746 LIST_ENTRY
*RegionToFlushLink
;
747 BOOTMON_FS_FILE_REGION
*Region
;
750 return EFI_INVALID_PARAMETER
;
753 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
754 if (File
->Info
== NULL
) {
755 return EFI_INVALID_PARAMETER
;
758 if (!IsListEmpty (&File
->RegionToFlushLink
)) {
759 // Free the entries from the Buffer List
760 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
762 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
765 // Get next element of the list before deleting the region description
766 // that contain the LIST_ENTRY structure.
768 RegionToFlushLink
= RemoveEntryList (RegionToFlushLink
);
771 FreePool (Region
->Buffer
);
773 } while (!IsListEmpty (&File
->RegionToFlushLink
));
776 // If (RegionCount is greater than 0) then the file already exists
777 if (File
->HwDescription
.RegionCount
> 0) {
778 // Invalidate the last Block
779 Status
= InvalidateImageDescription (File
);
780 ASSERT_EFI_ERROR (Status
);
781 if (EFI_ERROR (Status
)) {
782 return EFI_WARN_DELETE_FAILURE
;
786 // Remove the entry from the list
787 RemoveEntryList (&File
->Link
);
788 FreePool (File
->Info
);