mirror of
https://github.com/acidanthera/OpenCorePkg.git
synced 2025-12-08 19:25:01 +00:00
519 lines
13 KiB
C
519 lines
13 KiB
C
/** @file
|
|
Copyright (C) 2019, vit9696. All rights reserved.
|
|
|
|
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 <Uefi.h>
|
|
#include <Guid/FileInfo.h>
|
|
#include <Protocol/SimpleFileSystem.h>
|
|
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/OcFileLib.h>
|
|
#include <Library/UefiBootServicesTableLib.h>
|
|
|
|
// Define EPOCH (1970-JANUARY-01) in the Julian Date representation
|
|
#define EPOCH_JULIAN_DATE 2440588
|
|
|
|
// Seconds per unit
|
|
#define SEC_PER_MIN ((UINTN) 60)
|
|
#define SEC_PER_HOUR ((UINTN) 3600)
|
|
#define SEC_PER_DAY ((UINTN) 86400)
|
|
#define SEC_PER_MONTH ((UINTN) 2,592,000)
|
|
#define SEC_PER_YEAR ((UINTN) 31,536,000)
|
|
|
|
STATIC
|
|
UINTN
|
|
EfiGetEpochDays (
|
|
IN EFI_TIME *Time
|
|
)
|
|
{
|
|
UINTN a;
|
|
UINTN y;
|
|
UINTN m;
|
|
UINTN JulianDate; // Absolute Julian Date representation of the supplied Time
|
|
UINTN EpochDays; // Number of days elapsed since EPOCH_JULIAN_DAY
|
|
|
|
a = (14 - Time->Month) / 12 ;
|
|
y = Time->Year + 4800 - a;
|
|
m = Time->Month + (12*a) - 3;
|
|
|
|
JulianDate = Time->Day + ((153*m + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045;
|
|
|
|
ASSERT (JulianDate >= EPOCH_JULIAN_DATE);
|
|
EpochDays = JulianDate - EPOCH_JULIAN_DATE;
|
|
|
|
return EpochDays;
|
|
}
|
|
|
|
STATIC
|
|
UINT32
|
|
EfiTimeToEpoch (
|
|
IN EFI_TIME *Time
|
|
)
|
|
{
|
|
UINT32 EpochDays; // Number of days elapsed since EPOCH_JULIAN_DAY
|
|
UINT32 EpochSeconds;
|
|
|
|
EpochDays = (UINT32)EfiGetEpochDays (Time);
|
|
|
|
EpochSeconds = (EpochDays * SEC_PER_DAY) + ((UINTN)Time->Hour * SEC_PER_HOUR) + (Time->Minute * SEC_PER_MIN) + Time->Second;
|
|
|
|
return EpochSeconds;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GetFileData (
|
|
IN EFI_FILE_PROTOCOL *File,
|
|
IN UINT32 Position,
|
|
IN UINT32 Size,
|
|
OUT UINT8 *Buffer
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN ReadSize;
|
|
UINTN RequestedSize;
|
|
|
|
while (Size > 0) {
|
|
Status = File->SetPosition (File, Position);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// We are required to read in 1 MB portions, because otherwise some
|
|
// systems namely MacBook7,1 will not read file data from APFS volumes
|
|
// but will pretend they did. Repeoduced with BootKernelExtensions.kc.
|
|
//
|
|
ReadSize = RequestedSize = MIN (Size, BASE_1MB);
|
|
Status = File->Read (File, &ReadSize, Buffer);
|
|
if (EFI_ERROR (Status)) {
|
|
File->SetPosition (File, 0);
|
|
return Status;
|
|
}
|
|
|
|
if (ReadSize != RequestedSize) {
|
|
File->SetPosition (File, 0);
|
|
return EFI_BAD_BUFFER_SIZE;
|
|
}
|
|
|
|
Position += (UINT32) ReadSize;
|
|
Buffer += ReadSize;
|
|
Size -= (UINT32) ReadSize;
|
|
}
|
|
|
|
File->SetPosition (File, 0);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GetFileSize (
|
|
IN EFI_FILE_PROTOCOL *File,
|
|
OUT UINT32 *Size
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT64 Position;
|
|
EFI_FILE_INFO *FileInfo;
|
|
|
|
Status = File->SetPosition (File, 0xFFFFFFFFFFFFFFFFULL);
|
|
if (EFI_ERROR (Status)) {
|
|
//
|
|
// Some drivers, like EfiFs, return EFI_UNSUPPORTED when trying to seek
|
|
// past the file size. Use slow method via attributes for them.
|
|
//
|
|
FileInfo = GetFileInfo (File, &gEfiFileInfoGuid, sizeof (*FileInfo), NULL);
|
|
if (FileInfo != NULL) {
|
|
if ((UINT32) FileInfo->FileSize == FileInfo->FileSize) {
|
|
*Size = (UINT32) FileInfo->FileSize;
|
|
Status = EFI_SUCCESS;
|
|
}
|
|
FreePool (FileInfo);
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
Status = File->GetPosition (File, &Position);
|
|
File->SetPosition (File, 0);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
if ((UINT32) Position != Position) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
*Size = (UINT32) Position;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
GetFileModificationTime (
|
|
IN EFI_FILE_PROTOCOL *File,
|
|
OUT EFI_TIME *Time
|
|
)
|
|
{
|
|
EFI_FILE_INFO *FileInfo;
|
|
|
|
FileInfo = GetFileInfo (File, &gEfiFileInfoGuid, 0, NULL);
|
|
if (FileInfo == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
CopyMem (Time, &FileInfo->ModificationTime, sizeof (*Time));
|
|
FreePool (FileInfo);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
FindWritableFileSystem (
|
|
IN OUT EFI_FILE_PROTOCOL **WritableFs
|
|
)
|
|
{
|
|
EFI_HANDLE *HandleBuffer;
|
|
UINTN HandleCount;
|
|
UINTN Index;
|
|
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFs;
|
|
EFI_FILE_PROTOCOL *Fs;
|
|
EFI_FILE_PROTOCOL *File;
|
|
|
|
//
|
|
// Locate all the simple file system devices in the system.
|
|
//
|
|
EFI_STATUS Status = gBS->LocateHandleBuffer (
|
|
ByProtocol,
|
|
&gEfiSimpleFileSystemProtocolGuid,
|
|
NULL,
|
|
&HandleCount,
|
|
&HandleBuffer
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
for (Index = 0; Index < HandleCount; ++Index) {
|
|
Status = gBS->HandleProtocol (
|
|
HandleBuffer[Index],
|
|
&gEfiSimpleFileSystemProtocolGuid,
|
|
(VOID **) &SimpleFs
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
"OCFS: FindWritableFileSystem gBS->HandleProtocol[%u] returned %r\n",
|
|
(UINT32) Index,
|
|
Status
|
|
));
|
|
continue;
|
|
}
|
|
|
|
Status = SimpleFs->OpenVolume (SimpleFs, &Fs);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
"OCFS: FindWritableFileSystem SimpleFs->OpenVolume[%u] returned %r\n",
|
|
(UINT32) Index,
|
|
Status
|
|
));
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// We cannot test if the file system is writeable without attempting to create some file.
|
|
//
|
|
Status = SafeFileOpen (
|
|
Fs,
|
|
&File,
|
|
L"octest.fil",
|
|
EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
|
|
0
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
"OCFS: FindWritableFileSystem Fs->Open[%u] returned %r\n",
|
|
(UINT32) Index,
|
|
Status
|
|
));
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Delete the temporary file and report the found file system.
|
|
//
|
|
Fs->Delete (File);
|
|
*WritableFs = Fs;
|
|
break;
|
|
}
|
|
|
|
gBS->FreePool (HandleBuffer);
|
|
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
SetFileData (
|
|
IN EFI_FILE_PROTOCOL *WritableFs OPTIONAL,
|
|
IN CONST CHAR16 *FileName,
|
|
IN CONST VOID *Buffer,
|
|
IN UINT32 Size
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_PROTOCOL *Fs;
|
|
EFI_FILE_PROTOCOL *File;
|
|
UINTN WrittenSize;
|
|
|
|
ASSERT (FileName != NULL);
|
|
ASSERT (Buffer != NULL);
|
|
|
|
if (WritableFs == NULL) {
|
|
Status = FindWritableFileSystem (&Fs);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: WriteFileData can't find writable FS\n"));
|
|
return Status;
|
|
}
|
|
} else {
|
|
Fs = WritableFs;
|
|
}
|
|
|
|
Status = SafeFileOpen (
|
|
Fs,
|
|
&File,
|
|
(CHAR16 *) FileName,
|
|
EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
|
|
0
|
|
);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
WrittenSize = Size;
|
|
Status = File->Write (File, &WrittenSize, (VOID *) Buffer);
|
|
Fs->Close (File);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: WriteFileData file->Write returned %r\n", Status));
|
|
} else if (WrittenSize != Size) {
|
|
DEBUG ((
|
|
DEBUG_VERBOSE,
|
|
"WriteFileData: File->Write truncated %u to %u\n",
|
|
Status,
|
|
Size,
|
|
(UINT32) WrittenSize
|
|
));
|
|
Status = EFI_BAD_BUFFER_SIZE;
|
|
}
|
|
} else {
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: WriteFileData Fs->Open of %s returned %r\n", FileName, Status));
|
|
}
|
|
|
|
if (WritableFs == NULL) {
|
|
Fs->Close (Fs);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
EFI_STATUS
|
|
AllocateCopyFileData (
|
|
IN EFI_FILE_PROTOCOL *File,
|
|
OUT UINT8 **Buffer,
|
|
OUT UINT32 *BufferSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINT8 *FileBuffer;
|
|
UINT32 ReadSize;
|
|
|
|
//
|
|
// Get full file data.
|
|
//
|
|
Status = GetFileSize (File, &ReadSize);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
FileBuffer = AllocatePool (ReadSize);
|
|
if (FileBuffer == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
Status = GetFileData (File, 0, ReadSize, FileBuffer);
|
|
if (EFI_ERROR (Status)) {
|
|
FreePool (FileBuffer);
|
|
return Status;
|
|
}
|
|
|
|
*Buffer = FileBuffer;
|
|
*BufferSize = ReadSize;
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
DirectorySeachContextInit (
|
|
IN OUT DIRECTORY_SEARCH_CONTEXT *Context
|
|
)
|
|
{
|
|
ASSERT (Context != NULL);
|
|
|
|
ZeroMem (Context, sizeof (*Context));
|
|
}
|
|
|
|
EFI_STATUS
|
|
GetNewestFileFromDirectory (
|
|
IN OUT DIRECTORY_SEARCH_CONTEXT *Context,
|
|
IN EFI_FILE_PROTOCOL *Directory,
|
|
IN CHAR16 *FileNameStartsWith OPTIONAL,
|
|
OUT EFI_FILE_INFO **FileInfo
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_FILE_INFO *FileInfoCurrent;
|
|
EFI_FILE_INFO *FileInfoLatest;
|
|
UINTN FileInfoSize;
|
|
UINT32 EpochCurrent;
|
|
UINTN Index;
|
|
|
|
UINTN LatestIndex;
|
|
UINT32 LatestEpoch;
|
|
|
|
ASSERT (Context != NULL);
|
|
ASSERT (Directory != NULL);
|
|
ASSERT (FileInfo != NULL);
|
|
|
|
LatestIndex = 0;
|
|
LatestEpoch = 0;
|
|
|
|
//
|
|
// Ensure this is a directory.
|
|
//
|
|
FileInfoCurrent = GetFileInfo (Directory, &gEfiFileInfoGuid, 0, NULL);
|
|
if (FileInfoCurrent == NULL) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
if (!(FileInfoCurrent->Attribute & EFI_FILE_DIRECTORY)) {
|
|
FreePool (FileInfoCurrent);
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
FreePool (FileInfoCurrent);
|
|
|
|
//
|
|
// Allocate two FILE_INFO structures.
|
|
// The first is used for all entries, and the second is used only for the
|
|
// latest, and eventually returned to the caller.
|
|
//
|
|
FileInfoCurrent = AllocatePool (SIZE_1KB);
|
|
if (FileInfoCurrent == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
FileInfoLatest = AllocateZeroPool (SIZE_1KB);
|
|
if (FileInfoLatest == NULL) {
|
|
FreePool (FileInfoCurrent);
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Directory->SetPosition (Directory, 0);
|
|
Index = 0;
|
|
|
|
do {
|
|
//
|
|
// Apple's HFS+ driver does not adhere to the spec and will return zero for
|
|
// EFI_BUFFER_TOO_SMALL. EFI_FILE_INFO structures larger than 1KB are
|
|
// unrealistic as the filename is the only variable.
|
|
//
|
|
FileInfoSize = SIZE_1KB - sizeof (CHAR16);
|
|
Status = Directory->Read (Directory, &FileInfoSize, FileInfoCurrent);
|
|
if (EFI_ERROR (Status)) {
|
|
Directory->SetPosition (Directory, 0);
|
|
FreePool (FileInfoCurrent);
|
|
FreePool (FileInfoLatest);
|
|
return Status;
|
|
}
|
|
|
|
if (FileInfoSize > 0) {
|
|
//
|
|
// Skip any files that do not start with the desired filename.
|
|
//
|
|
if (FileNameStartsWith != NULL) {
|
|
if (StrnCmp (FileInfoCurrent->FileName, FileNameStartsWith, StrLen (FileNameStartsWith)) != 0) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get time of current entry.
|
|
// We want to skip any entries that have been returned as "latest" in prior calls.
|
|
//
|
|
EpochCurrent = EfiTimeToEpoch (&FileInfoCurrent->ModificationTime);
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: Current file %s with time %u\n", FileInfoCurrent->FileName, EpochCurrent));
|
|
|
|
//
|
|
// Skip any entries that are newer than our previously matched entry.
|
|
//
|
|
if (Context->PreviousTime > 0 && EpochCurrent > Context->PreviousTime) {
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: Skipping file %s due to time %u > %u\n", FileInfoCurrent->FileName, EpochCurrent, Context->PreviousTime));
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Skip any entries that have the same time as our previously
|
|
// matched entry and were found previously.
|
|
//
|
|
// ASSUMPTION: Entries are in the same order each time the directory is iterated.
|
|
//
|
|
if (EpochCurrent == Context->PreviousTime) {
|
|
if (Index <= Context->PreviousIndex) {
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: Skipping file %s with due to index %u <= %u\n", FileInfoCurrent->FileName, Index, Context->PreviousIndex));
|
|
Index++;
|
|
continue;
|
|
}
|
|
} else {
|
|
//
|
|
// Reset index counter if the time is different from the last.
|
|
//
|
|
Index = 0;
|
|
}
|
|
|
|
//
|
|
// Store latest entry.
|
|
//
|
|
if (FileInfoLatest->FileName[0] == '\0' || EpochCurrent > EfiTimeToEpoch (&FileInfoLatest->ModificationTime)) {
|
|
CopyMem (FileInfoLatest, FileInfoCurrent, FileInfoSize);
|
|
LatestIndex = Index;
|
|
LatestEpoch = EfiTimeToEpoch (&FileInfoLatest->ModificationTime);
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: Stored newest file %s\n", FileInfoCurrent->FileName));
|
|
}
|
|
}
|
|
} while (FileInfoSize > 0);
|
|
|
|
Directory->SetPosition (Directory, 0);
|
|
FreePool (FileInfoCurrent);
|
|
|
|
if (FileInfoLatest->FileName[0] == '\0') {
|
|
FreePool (FileInfoLatest);
|
|
|
|
DEBUG ((DEBUG_VERBOSE, "OCFS: No matching files found\n"));
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
*FileInfo = FileInfoLatest;
|
|
Context->PreviousIndex = LatestIndex;
|
|
Context->PreviousTime = LatestEpoch;
|
|
|
|
return EFI_SUCCESS;
|
|
}
|