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 AsciiStrCpyS (Description
->Footer
.Filename
,
105 sizeof Description
->Footer
.Filename
, FileName
);
108 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET
;
109 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION
;
111 Description
->Footer
.Offset
= HW_IMAGE_FOOTER_OFFSET2
;
112 Description
->Footer
.Version
= HW_IMAGE_FOOTER_VERSION2
;
114 Description
->Footer
.FooterSignature1
= HW_IMAGE_FOOTER_SIGNATURE_1
;
115 Description
->Footer
.FooterSignature2
= HW_IMAGE_FOOTER_SIGNATURE_2
;
116 Description
->RegionCount
= 1;
117 Description
->Region
[0].Checksum
= 0;
118 Description
->Region
[0].Offset
= Description
->BlockStart
* BlockSize
;
119 Description
->Region
[0].Size
= DataSize
;
121 Status
= BootMonFsComputeFooterChecksum (Description
);
122 if (EFI_ERROR (Status
)) {
126 File
->HwDescAddress
= ((Description
->BlockEnd
+ 1) * BlockSize
) - sizeof (HW_IMAGE_DESCRIPTION
);
128 // Update the file description on the media
129 Status
= DiskIo
->WriteDisk (
131 File
->Instance
->Media
->MediaId
,
133 sizeof (HW_IMAGE_DESCRIPTION
),
136 ASSERT_EFI_ERROR (Status
);
141 // Find a space on media for a file that has not yet been flushed to disk.
142 // Just returns the first space that's big enough.
143 // This function could easily be adapted to:
144 // - Find space for moving an existing file that has outgrown its space
145 // (We do not currently move files, just return EFI_VOLUME_FULL)
146 // - Find space for a fragment of a file that has outgrown its space
147 // (We do not currently fragment files - it's not clear whether fragmentation
148 // is actually part of BootMonFs as there is no spec)
149 // - Be more clever about finding space (choosing the largest or smallest
152 // File - the new (not yet flushed) file for which we need to find space.
153 // FileStart - the position on media of the file (in bytes).
156 BootMonFsFindSpaceForNewFile (
157 IN BOOTMON_FS_FILE
*File
,
159 OUT UINT64
*FileStart
162 LIST_ENTRY
*FileLink
;
163 BOOTMON_FS_FILE
*RootFile
;
164 BOOTMON_FS_FILE
*FileEntry
;
166 EFI_BLOCK_IO_MEDIA
*Media
;
168 Media
= File
->Instance
->BlockIo
->Media
;
169 BlockSize
= Media
->BlockSize
;
170 RootFile
= File
->Instance
->RootFile
;
172 // This function must only be called for file which has not been flushed into
174 ASSERT (File
->HwDescription
.RegionCount
== 0);
177 // Go through all the files in the list
178 for (FileLink
= GetFirstNode (&RootFile
->Link
);
179 !IsNull (&RootFile
->Link
, FileLink
);
180 FileLink
= GetNextNode (&RootFile
->Link
, FileLink
)
183 FileEntry
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
184 // Skip files that aren't on disk yet
185 if (FileEntry
->HwDescription
.RegionCount
== 0) {
189 // If the free space preceding the file is big enough to contain the new
191 if (((FileEntry
->HwDescription
.BlockStart
* BlockSize
) - *FileStart
)
193 // The file list must be in disk-order
194 RemoveEntryList (&File
->Link
);
195 File
->Link
.BackLink
= FileLink
->BackLink
;
196 File
->Link
.ForwardLink
= FileLink
;
197 FileLink
->BackLink
->ForwardLink
= &File
->Link
;
198 FileLink
->BackLink
= &File
->Link
;
202 *FileStart
= (FileEntry
->HwDescription
.BlockEnd
+ 1) * BlockSize
;
205 // See if there's space after the last file
206 if ((((Media
->LastBlock
+ 1) * BlockSize
) - *FileStart
) >= FileSize
) {
209 return EFI_VOLUME_FULL
;
213 // Free the resources in the file's Region list.
217 IN BOOTMON_FS_FILE
*File
220 LIST_ENTRY
*RegionToFlushLink
;
221 BOOTMON_FS_FILE_REGION
*Region
;
223 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
224 while (!IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
)) {
225 // Repeatedly remove the first node from the list and free its resources.
226 Region
= (BOOTMON_FS_FILE_REGION
*) RegionToFlushLink
;
227 RemoveEntryList (RegionToFlushLink
);
228 FreePool (Region
->Buffer
);
231 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
236 Flush all modified data associated with a file to a device.
238 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the
239 file handle to flush.
241 @retval EFI_SUCCESS The data was flushed.
242 @retval EFI_ACCESS_DENIED The file was opened read-only.
243 @retval EFI_DEVICE_ERROR The device reported an error.
244 @retval EFI_VOLUME_FULL The volume is full.
245 @retval EFI_OUT_OF_RESOURCES Not enough resources were available to flush the data.
246 @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid.
252 IN EFI_FILE_PROTOCOL
*This
256 BOOTMON_FS_INSTANCE
*Instance
;
258 EFI_BLOCK_IO_PROTOCOL
*BlockIo
;
259 EFI_BLOCK_IO_MEDIA
*Media
;
260 EFI_DISK_IO_PROTOCOL
*DiskIo
;
262 CHAR8 AsciiFileName
[MAX_NAME_LENGTH
];
263 LIST_ENTRY
*RegionToFlushLink
;
264 BOOTMON_FS_FILE
*File
;
265 BOOTMON_FS_FILE
*NextFile
;
266 BOOTMON_FS_FILE_REGION
*Region
;
267 LIST_ENTRY
*FileLink
;
268 UINTN CurrentPhysicalSize
;
275 UINT64 EndOfAppendSpace
;
279 return EFI_INVALID_PARAMETER
;
282 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
283 if (File
->Info
== NULL
) {
284 return EFI_INVALID_PARAMETER
;
287 if (File
->OpenMode
== EFI_FILE_MODE_READ
) {
288 return EFI_ACCESS_DENIED
;
291 Instance
= File
->Instance
;
293 BlockIo
= Instance
->BlockIo
;
294 Media
= BlockIo
->Media
;
295 DiskIo
= Instance
->DiskIo
;
296 BlockSize
= Media
->BlockSize
;
298 UnicodeStrToAsciiStrS (Info
->FileName
, AsciiFileName
, MAX_NAME_LENGTH
);
300 // If the file doesn't exist then find a space for it
301 if (File
->HwDescription
.RegionCount
== 0) {
302 Status
= BootMonFsFindSpaceForNewFile (
304 Info
->FileSize
+ sizeof (HW_IMAGE_DESCRIPTION
),
307 if (EFI_ERROR (Status
)) {
311 FileStart
= File
->HwDescription
.BlockStart
* BlockSize
;
313 // FileEnd is the current NOR address of the end of the file's data
314 FileEnd
= FileStart
+ File
->HwDescription
.Region
[0].Size
;
316 for (RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
317 !IsNull (&File
->RegionToFlushLink
, RegionToFlushLink
);
318 RegionToFlushLink
= GetNextNode (&File
->RegionToFlushLink
, RegionToFlushLink
)
321 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
322 if (Region
->Size
== 0) {
326 // RegionStart and RegionEnd are the the intended NOR address of the
327 // start and end of the region
328 RegionStart
= FileStart
+ Region
->Offset
;
329 RegionEnd
= RegionStart
+ Region
->Size
;
331 if (RegionEnd
< FileEnd
) {
332 // Handle regions representing edits to existing portions of the file
333 // Write the region data straight into the file
334 Status
= DiskIo
->WriteDisk (DiskIo
,
340 if (EFI_ERROR (Status
)) {
344 // Handle regions representing appends to the file
346 // Note: Since seeking past the end of the file with SetPosition() is
347 // valid, it's possible there will be a gap between the current end of
348 // the file and the beginning of the new region. Since the UEFI spec
349 // says nothing about this case (except "a subsequent write would grow
350 // the file"), we just leave garbage in the gap.
352 // Check if there is space to append the new region
354 NewDataSize
= RegionEnd
- FileStart
;
355 NewFileSize
= NewDataSize
+ sizeof (HW_IMAGE_DESCRIPTION
);
356 CurrentPhysicalSize
= BootMonFsGetPhysicalSize (File
);
357 if (NewFileSize
<= CurrentPhysicalSize
) {
360 // Get the File Description for the next file
361 FileLink
= GetNextNode (&Instance
->RootFile
->Link
, &File
->Link
);
362 if (!IsNull (&Instance
->RootFile
->Link
, FileLink
)) {
363 NextFile
= BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink
);
365 // If there is space between the beginning of the current file and the
366 // beginning of the next file then use it
367 EndOfAppendSpace
= NextFile
->HwDescription
.BlockStart
* BlockSize
;
369 // We are flushing the last file.
370 EndOfAppendSpace
= (Media
->LastBlock
+ 1) * BlockSize
;
372 if (EndOfAppendSpace
- FileStart
>= NewFileSize
) {
377 if (HasSpace
== TRUE
) {
378 // Invalidate the current image description of the file if any.
379 if (File
->HwDescAddress
!= 0) {
380 Status
= InvalidateImageDescription (File
);
381 if (EFI_ERROR (Status
)) {
386 // Write the new file data
387 Status
= DiskIo
->WriteDisk (
394 if (EFI_ERROR (Status
)) {
398 Status
= WriteFileDescription (File
, AsciiFileName
, NewDataSize
, FileStart
);
399 if (EFI_ERROR (Status
)) {
404 // There isn't a space for the file.
405 // Options here are to move the file or fragment it. However as files
406 // may represent boot images at fixed positions, these options will
407 // break booting if the bootloader doesn't use BootMonFs to find the
410 return EFI_VOLUME_FULL
;
415 FreeFileRegions (File
);
416 Info
->PhysicalSize
= BootMonFsGetPhysicalSize (File
);
418 if ((AsciiStrCmp (AsciiFileName
, File
->HwDescription
.Footer
.Filename
) != 0) ||
419 (Info
->FileSize
!= File
->HwDescription
.Region
[0].Size
) ) {
420 Status
= WriteFileDescription (File
, AsciiFileName
, Info
->FileSize
, FileStart
);
421 if (EFI_ERROR (Status
)) {
426 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
427 // calling FlushBlocks on the same device's BlockIo).
428 BlockIo
->FlushBlocks (BlockIo
);
434 Close a specified file handle.
436 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file
439 @retval EFI_SUCCESS The file was closed.
440 @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open
447 IN EFI_FILE_PROTOCOL
*This
450 BOOTMON_FS_FILE
*File
;
453 return EFI_INVALID_PARAMETER
;
456 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
457 if (File
->Info
== NULL
) {
458 return EFI_INVALID_PARAMETER
;
461 // In the case of a file and not the root directory
462 if (This
!= &File
->Instance
->RootFile
->File
) {
464 FreePool (File
->Info
);
472 Open a file on the boot monitor file system.
474 The boot monitor file system does not allow for sub-directories. There is only
475 one directory, the root one. On any attempt to create a directory, the function
476 returns in error with the EFI_WRITE_PROTECTED error code.
478 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is
479 the file handle to source location.
480 @param[out] NewHandle A pointer to the location to return the opened
481 handle for the new file.
482 @param[in] FileName The Null-terminated string of the name of the file
484 @param[in] OpenMode The mode to open the file : Read or Read/Write or
486 @param[in] Attributes Attributes of the file in case of a file creation
488 @retval EFI_SUCCESS The file was open.
489 @retval EFI_NOT_FOUND The specified file could not be found or the specified
490 directory in which to create a file could not be found.
491 @retval EFI_DEVICE_ERROR The device reported an error.
492 @retval EFI_WRITE_PROTECTED Attempt to create a directory. This is not possible
493 with the Boot Monitor file system.
494 @retval EFI_OUT_OF_RESOURCES Not enough resources were available to open the file.
495 @retval EFI_INVALID_PARAMETER At least one of the parameters is invalid.
501 IN EFI_FILE_PROTOCOL
*This
,
502 OUT EFI_FILE_PROTOCOL
**NewHandle
,
509 BOOTMON_FS_FILE
*Directory
;
510 BOOTMON_FS_FILE
*File
;
511 BOOTMON_FS_INSTANCE
*Instance
;
515 CHAR8
*AsciiFileName
;
517 UINTN AsciiFileNameSize
;
520 return EFI_INVALID_PARAMETER
;
523 Directory
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
524 if (Directory
->Info
== NULL
) {
525 return EFI_INVALID_PARAMETER
;
528 if ((FileName
== NULL
) || (NewHandle
== NULL
)) {
529 return EFI_INVALID_PARAMETER
;
533 // The only valid modes are read, read/write, and read/write/create
535 if ( (OpenMode
!= EFI_FILE_MODE_READ
) &&
536 (OpenMode
!= (EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
)) &&
537 (OpenMode
!= (EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
| EFI_FILE_MODE_CREATE
)) ) {
538 return EFI_INVALID_PARAMETER
;
541 Instance
= Directory
->Instance
;
544 // If the instance has not been initialized yet then do it ...
546 if (!Instance
->Initialized
) {
547 Status
= BootMonFsInitialize (Instance
);
548 if (EFI_ERROR (Status
)) {
554 // Copy the file path to be able to work on it. We do not want to
555 // modify the input file name string "FileName".
557 Buf
= AllocateCopyPool (StrSize (FileName
), FileName
);
559 return EFI_OUT_OF_RESOURCES
;
562 AsciiFileName
= NULL
;
566 // Handle single periods, double periods and convert forward slashes '/'
567 // to backward '\' ones. Does not handle a '.' at the beginning of the
568 // path for the time being.
570 if (PathCleanUpDirectories (Path
) == NULL
) {
571 Status
= EFI_INVALID_PARAMETER
;
576 // Detect if the first component of the path refers to a directory.
577 // This is done to return the correct error code when trying to
578 // access or create a directory other than the root directory.
582 // Search for the '\\' sequence and if found return in error
583 // with the EFI_INVALID_PARAMETER error code. ere in the path.
585 if (StrStr (Path
, L
"\\\\") != NULL
) {
586 Status
= EFI_INVALID_PARAMETER
;
590 // Get rid of the leading '\' if any.
592 Path
+= (Path
[0] == L
'\\');
595 // Look for a '\' in the file path. If one is found then
596 // the first component of the path refers to a directory
597 // that is not the root directory.
599 Separator
= StrStr (Path
, L
"\\");
600 if (Separator
!= NULL
) {
602 // In the case '<dir name>\' and a creation, return
603 // EFI_WRITE_PROTECTED if this is for a directory
604 // creation, EFI_INVALID_PARAMETER otherwise.
606 if ((*(Separator
+ 1) == '\0') && ((OpenMode
& EFI_FILE_MODE_CREATE
) != 0)) {
607 if (Attributes
& EFI_FILE_DIRECTORY
) {
608 Status
= EFI_WRITE_PROTECTED
;
610 Status
= EFI_INVALID_PARAMETER
;
614 // Attempt to open a file or a directory that is not in the
615 // root directory or to open without creation a directory
616 // located in the root directory, returns EFI_NOT_FOUND.
618 Status
= EFI_NOT_FOUND
;
624 // BootMonFs interface requires ASCII filenames
626 AsciiFileNameSize
= StrLen (Path
) + 1;
627 if (AsciiFileNameSize
> MAX_NAME_LENGTH
) {
628 AsciiFileNameSize
= MAX_NAME_LENGTH
;
630 AsciiFileName
= AllocatePool (AsciiFileNameSize
);
631 if (AsciiFileName
== NULL
) {
632 Status
= EFI_OUT_OF_RESOURCES
;
635 UnicodeStrToAsciiStrS (Path
, AsciiFileName
, AsciiFileNameSize
);
637 if ((AsciiFileName
[0] == '\0') ||
638 (AsciiFileName
[0] == '.' ) ) {
640 // Opening the root directory
643 *NewHandle
= &Instance
->RootFile
->File
;
644 Instance
->RootFile
->Position
= 0;
645 Status
= EFI_SUCCESS
;
648 if ((OpenMode
& EFI_FILE_MODE_CREATE
) &&
649 (Attributes
& EFI_FILE_DIRECTORY
) ) {
650 Status
= EFI_WRITE_PROTECTED
;
655 // Allocate a buffer to store the characteristics of the file while the
656 // file is open. We allocate the maximum size to not have to reallocate
657 // if the file name is changed.
659 Info
= AllocateZeroPool (
660 SIZE_OF_EFI_FILE_INFO
+ (sizeof (CHAR16
) * MAX_NAME_LENGTH
));
662 Status
= EFI_OUT_OF_RESOURCES
;
667 // Open or create a file in the root directory.
670 Status
= BootMonGetFileFromAsciiFileName (Instance
, AsciiFileName
, &File
);
671 if (Status
== EFI_NOT_FOUND
) {
672 if ((OpenMode
& EFI_FILE_MODE_CREATE
) == 0) {
676 Status
= BootMonFsCreateFile (Instance
, &File
);
677 if (EFI_ERROR (Status
)) {
680 InsertHeadList (&Instance
->RootFile
->Link
, &File
->Link
);
681 Info
->Attribute
= Attributes
;
684 // File already open, not supported yet.
686 if (File
->Info
!= NULL
) {
687 Status
= EFI_UNSUPPORTED
;
692 Info
->FileSize
= BootMonFsGetImageLength (File
);
693 Info
->PhysicalSize
= BootMonFsGetPhysicalSize (File
);
694 AsciiStrToUnicodeStrS (AsciiFileName
, Info
->FileName
, MAX_NAME_LENGTH
);
699 File
->OpenMode
= OpenMode
;
701 *NewHandle
= &File
->File
;
707 if (AsciiFileName
!= NULL
) {
708 FreePool (AsciiFileName
);
717 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
720 BootMonFsDeleteFail (
721 IN EFI_FILE_PROTOCOL
*This
725 // You can't delete the root directory
726 return EFI_WARN_DELETE_FAILURE
;
730 Close and delete a file from the boot monitor file system.
732 @param[in] This A pointer to the EFI_FILE_PROTOCOL instance that is the file
735 @retval EFI_SUCCESS The file was closed and deleted.
736 @retval EFI_INVALID_PARAMETER The parameter "This" is NULL or is not an open
738 @retval EFI_WARN_DELETE_FAILURE The handle was closed, but the file was not deleted.
744 IN EFI_FILE_PROTOCOL
*This
748 BOOTMON_FS_FILE
*File
;
749 LIST_ENTRY
*RegionToFlushLink
;
750 BOOTMON_FS_FILE_REGION
*Region
;
753 return EFI_INVALID_PARAMETER
;
756 File
= BOOTMON_FS_FILE_FROM_FILE_THIS (This
);
757 if (File
->Info
== NULL
) {
758 return EFI_INVALID_PARAMETER
;
761 if (!IsListEmpty (&File
->RegionToFlushLink
)) {
762 // Free the entries from the Buffer List
763 RegionToFlushLink
= GetFirstNode (&File
->RegionToFlushLink
);
765 Region
= (BOOTMON_FS_FILE_REGION
*)RegionToFlushLink
;
768 // Get next element of the list before deleting the region description
769 // that contain the LIST_ENTRY structure.
771 RegionToFlushLink
= RemoveEntryList (RegionToFlushLink
);
774 FreePool (Region
->Buffer
);
776 } while (!IsListEmpty (&File
->RegionToFlushLink
));
779 // If (RegionCount is greater than 0) then the file already exists
780 if (File
->HwDescription
.RegionCount
> 0) {
781 // Invalidate the last Block
782 Status
= InvalidateImageDescription (File
);
783 ASSERT_EFI_ERROR (Status
);
784 if (EFI_ERROR (Status
)) {
785 return EFI_WARN_DELETE_FAILURE
;
789 // Remove the entry from the list
790 RemoveEntryList (&File
->Link
);
791 FreePool (File
->Info
);