OpenNtfsDxe: Implement NTFS read-only driver. (#332)

This commit is contained in:
MikhailKrichanov 2022-05-15 00:55:52 +03:00 committed by GitHub
parent d0b7eb3603
commit e9136a3a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 5655 additions and 0 deletions

1
.gitignore vendored
View File

@ -80,3 +80,4 @@ Utilities/TestMacho/Macho
Utilities/TestRsaPreprocess/RsaPreprocess
Utilities/TestSmbios/Smbios
Utilities/TestPeCoff/PeCoff
Utilities/TestNtfsDxe/TestNtfsDxe

View File

@ -6378,6 +6378,9 @@ even cause permanent firmware damage. Some of the known drivers are listed below
& \hyperref[uefilinux]{OpenCore plugin} implementing \texttt{OC\_BOOT\_ENTRY\_PROTOCOL}
to allow direct detection and booting of Linux distributiuons from OpenCore, without
chainloading via GRUB. \\
\href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenNtfsDxe}}\textbf{*}
& New Technologies File System (NTFS) read-only driver.
NTFS is the primary file system for Microsoft Windows versions that are based on Windows NT. \\
\href{https://github.com/acidanthera/OpenCorePkg}{\texttt{OpenUsbKbDxe}}\textbf{*}
& USB keyboard driver adding support for \texttt{AppleKeyMapAggregator} protocols
on top of a custom USB keyboard driver implementation. This is an alternative to

View File

@ -268,6 +268,7 @@
OpenCorePkg/Platform/CrScreenshotDxe/CrScreenshotDxe.inf
OpenCorePkg/Platform/OpenCanopy/OpenCanopy.inf
OpenCorePkg/Platform/OpenLinuxBoot/OpenLinuxBoot.inf
OpenCorePkg/Platform/OpenNtfsDxe/OpenNtfsDxe.inf
OpenCorePkg/Platform/OpenPartitionDxe/PartitionDxe.inf
OpenCorePkg/Platform/OpenRuntime/OpenRuntime.inf
OpenCorePkg/Platform/OpenUsbKbDxe/UsbKbDxe.inf

View File

@ -0,0 +1,693 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
#define IS_COMPRESSED_BLOCK 0x8000
#define BLOCK_LENGTH_BITS 0xFFF
#define UNIT_MASK 0xF
extern UINT64 mUnitSize;
STATIC UINT64 mBufferSize;
STATIC
EFI_STATUS
GetNextCluster (
IN OUT COMPRESSED *Clusters
)
{
EFI_STATUS Status;
UINTN ClusterSize;
ClusterSize = Clusters->FileSystem->ClusterSize;
if (Clusters->Head >= Clusters->Tail) {
DEBUG ((DEBUG_INFO, "NTFS: Compression block overflown\n"));
return EFI_VOLUME_CORRUPTED;
}
Status = DiskRead (
Clusters->FileSystem,
(Clusters->Elements[Clusters->Head].Lcn - Clusters->Elements[Clusters->Head].Vcn + Clusters->CurrentVcn) * ClusterSize,
ClusterSize,
Clusters->Cluster
);
if (EFI_ERROR (Status)) {
return Status;
}
++Clusters->CurrentVcn;
if (Clusters->CurrentVcn >= Clusters->Elements[Clusters->Head].Vcn) {
++Clusters->Head;
}
Clusters->ClusterOffset = 0;
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
GetDataRunByte (
IN COMPRESSED *Clusters,
OUT UINT8 *Result
)
{
EFI_STATUS Status;
ASSERT (Clusters != NULL);
ASSERT (Result != NULL);
if (Clusters->ClusterOffset >= Clusters->FileSystem->ClusterSize) {
Status = GetNextCluster (Clusters);
if (EFI_ERROR (Status)) {
return Status;
}
}
*Result = Clusters->Cluster[Clusters->ClusterOffset++];
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
GetTwoDataRunBytes (
IN COMPRESSED *Clusters,
OUT UINT16 *Result
)
{
EFI_STATUS Status;
UINT8 ByteLow;
UINT8 ByteHigh;
ASSERT (Clusters != NULL);
ASSERT (Result != NULL);
Status = GetDataRunByte (Clusters, &ByteLow);
if (EFI_ERROR (Status)) {
return Status;
}
Status = GetDataRunByte (Clusters, &ByteHigh);
if (EFI_ERROR (Status)) {
return Status;
}
*Result = (((UINT16)ByteHigh) << 8U) | ByteLow;
return EFI_SUCCESS;
}
/**
* The basic idea is that substrings of the block which have been seen before
are compressed by referencing the string rather than mentioning it again.
#include <ntfs.h>\n
#include <stdio.h>\n
This is compressed to #include <ntfs.h>\n(-18,10)stdio(-17,4)
* Pairs like (-18,10) are recorded in two bytes. ->
The shortest possible substring is 3 bytes long. ->
One can subtract 3 from the length before encoding it.
* The references are always backward, and never 0. ->
One can store them as positive numbers, and subtract one. ->
(-18,10) -> (17,7); (-17,4) -> (16,1).
* Given that a block is 4096 in size, you might need 12 bits to encode the back reference.
This means that you have only 4 bits left to encode the length.
* Dynamic allocation of bits for the back-reference.
for (i = clear_pos - 1, lmask = 0xFFF, dshift = 12; i >= 0x10; i >>= 1) {
lmask >>= 1; // bit mask for length
dshift--; // shift width for delta
}
* Now that we can encode a (offset,length) pair as 2 bytes,
we still have to know whether a token is back-reference, or plain-text.
This is 1 bit per token. 8 tokens are grouped together
and preceded with the tags byte.
">\n(-18,10)stdio" would be encoded as "00000100"
(the 1 bit indicates the back reference).
"00000000"#include"00000000" <ntfs.h"00000100">\n(17,7)stdio"00000001"(16,1)
* As a compression unit consists of 16 clusters (default), it usually contains more than one of these blocks.
If you want to access the second block, it would be a waste of time to decompress the first one.
Instead, each block is preceded by a 2-byte length.
The lower 12 bits are the length, the higher 4 bits are of unknown purpose.
Actually, (n-1) is stored in the low 12 bits.
* The compression method is based on independently compressing blocks of X clusters,
where X = 2 ^ ATTR_HEADER_NONRES.CompressionUnitSize.
* If the block grows in size, it will be stored uncompressed.
A length of exactly 4095 is used to indicate this case.
* Bit 0x8000 is the flag specifying that the block is compressed.
**/
STATIC
EFI_STATUS
DecompressBlock (
IN COMPRESSED *Clusters,
OUT UINT8 *Dest OPTIONAL
)
{
EFI_STATUS Status;
UINT16 BlockParameters;
UINTN BlockLength;
UINT8 TagsByte;
UINT8 Tokens;
UINT16 ClearTextPointer;
UINT8 PlainText;
UINT16 Index;
UINT16 Length;
UINT16 Lmask;
UINT16 Delta;
UINT16 Dshift;
UINT16 BackReference;
UINTN SpareBytes;
ASSERT (Clusters != NULL);
Status = GetTwoDataRunBytes (Clusters, &BlockParameters);
if (EFI_ERROR (Status)) {
return Status;
}
BlockLength = (BlockParameters & BLOCK_LENGTH_BITS) + 1U;
if (Dest != NULL) {
if ((BlockParameters & IS_COMPRESSED_BLOCK) != 0) {
ClearTextPointer = 0;
Tokens = 0;
TagsByte = 0;
while (BlockLength > 0) {
if (ClearTextPointer > COMPRESSION_BLOCK) {
DEBUG ((DEBUG_INFO, "NTFS: Compression block too large\n"));
return EFI_VOLUME_CORRUPTED;
}
if (Tokens == 0) {
Status = GetDataRunByte (Clusters, &TagsByte);
if (EFI_ERROR (Status)) {
return Status;
}
Tokens = 8U;
--BlockLength;
if (BlockLength == 0) {
break;
}
}
if ((TagsByte & 1U) != 0) {
//
// Back-reference
//
Status = GetTwoDataRunBytes (Clusters, &BackReference);
if (EFI_ERROR (Status)) {
return Status;
}
BlockLength -= 2U;
if (ClearTextPointer == 0) {
DEBUG ((DEBUG_INFO, "NTFS: Nontext window empty\n"));
return EFI_VOLUME_CORRUPTED;
}
Lmask = BLOCK_LENGTH_BITS;
Dshift = 12U;
for (Index = ClearTextPointer - 1U; Index >= 0x10U; Index >>= 1U) {
Lmask >>= 1U;
--Dshift;
}
Delta = BackReference >> Dshift;
Length = (BackReference & Lmask) + 3U;
if ((Delta > (ClearTextPointer - 1U)) || (Length >= COMPRESSION_BLOCK)) {
DEBUG ((DEBUG_INFO, "NTFS: Invalid back-reference.\n"));
return EFI_VOLUME_CORRUPTED;
}
if (mBufferSize < Length) {
DEBUG ((DEBUG_INFO, "NTFS: (DecompressBlock #1) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
for (Index = 0; Index < Length; ++Index) {
Dest[ClearTextPointer] = Dest[ClearTextPointer - Delta - 1U];
++ClearTextPointer;
}
mBufferSize -= Length;
} else {
//
// Plain text
//
Status = GetDataRunByte (Clusters, &PlainText);
if (EFI_ERROR (Status)) {
return Status;
}
if (mBufferSize == 0) {
DEBUG ((DEBUG_INFO, "NTFS: (DecompressBlock #2) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
Dest[ClearTextPointer++] = PlainText;
--BlockLength;
--mBufferSize;
}
TagsByte >>= 1U;
--Tokens;
}
return EFI_SUCCESS;
}
if (BlockLength != COMPRESSION_BLOCK) {
DEBUG ((DEBUG_INFO, "NTFS: Invalid compression block size %d\n", BlockLength));
return EFI_VOLUME_CORRUPTED;
}
}
while (BlockLength > 0) {
SpareBytes = Clusters->FileSystem->ClusterSize - Clusters->ClusterOffset;
if (SpareBytes > BlockLength) {
SpareBytes = BlockLength;
}
if ((Dest != NULL) && (SpareBytes != 0)) {
if (mBufferSize < SpareBytes) {
DEBUG ((DEBUG_INFO, "NTFS: (DecompressBlock #3) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
CopyMem (Dest, &Clusters->Cluster[Clusters->ClusterOffset], SpareBytes);
Dest += SpareBytes;
mBufferSize -= SpareBytes;
}
BlockLength -= SpareBytes;
Clusters->ClusterOffset += SpareBytes;
if (BlockLength != 0) {
Status = GetNextCluster (Clusters);
if (EFI_ERROR (Status)) {
return Status;
}
}
}
return EFI_SUCCESS;
}
/**
* The set of VCNs containing the stream of a compressed file attribute is divided
in compression units (also called chunks) of 16 cluster.
* The alpha stage: if all the 16 clusters of a compression unit are full of zeroes,
this compression unit is called a Sparse unit and is not physically stored.
Instead, an element with no Offset field (F=0, the Offset is assumed to be 0 too)
and a Length of 16 clusters is put in the Runlist.
* The beta stage: if the compression of the unit is possible,
N (< 16) clusters are physically stored, and an element with a Length of N
is put in the Runlist, followed by another element with no Offset field
(F=0, the Offset is assumed to be 0 too) and a Length of 16 - N.
* If the unit is not compressed, 16 clusters are physically stored,
and an element with a Length of 16 is put in the Runlist.
Example
Runlist:
21 14 00 01 11 10 18 11 05 15 01 27 11 20 05
Decode
0x14 at 0x100
0x10 at + 0x18
0x05 at + 0x15
0x27 at + none
0x20 at + 0x05
Absolute LCNs
0x14 at 0x100
0x10 at 0x118
0x05 at 0x12D
0x27 at none
0x20 at 0x132
Regroup
0x10 at 0x100 Unit not compressed
0x04 at 0x110 Unit not compressed
0x0C at 0x118
0x04 at 0x118 Compressed unit
0x05 at 0x12D
0x07 at none
0x10 at none Sparse unit
0x10 at none Sparse unit
0x10 at 0x132 Unit not compressed
0x10 at 0x142 Unit not compressed
**/
STATIC
EFI_STATUS
ReadCompressedBlock (
IN RUNLIST *Runlist,
OUT UINT8 *Dest OPTIONAL,
IN UINTN BlocksTotal
)
{
EFI_STATUS Status;
UINTN SpareBlocks;
UINT64 SpareClusters;
UINT64 BlocksPerCluster;
UINT64 ClustersPerBlock;
UINT64 ClearTextClusters;
UINTN ClusterSize;
ASSERT (Runlist != NULL);
BlocksPerCluster = 0;
ClustersPerBlock = 0;
ClusterSize = Runlist->Unit.FileSystem->ClusterSize;
mBufferSize = BlocksTotal * COMPRESSION_BLOCK;
if (ClusterSize >= COMPRESSION_BLOCK) {
BlocksPerCluster = DivU64x64Remainder (ClusterSize, COMPRESSION_BLOCK, NULL);
} else {
ClustersPerBlock = DivU64x64Remainder (COMPRESSION_BLOCK, ClusterSize, NULL);
}
while (BlocksTotal != 0) {
if ((Runlist->TargetVcn & UNIT_MASK) == 0) {
if ((Runlist->Unit.Head != Runlist->Unit.Tail) && (Runlist->IsSparse == FALSE)) {
DEBUG ((DEBUG_INFO, "NTFS: Invalid compression block\n"));
return EFI_VOLUME_CORRUPTED;
}
Runlist->Unit.Head = Runlist->Unit.Tail = 0;
Runlist->Unit.CurrentVcn = Runlist->TargetVcn;
Runlist->Unit.ClusterOffset = ClusterSize;
if (Runlist->TargetVcn >= Runlist->NextVcn) {
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
return Status;
}
}
while ((Runlist->TargetVcn + mUnitSize) > Runlist->NextVcn) {
if (Runlist->IsSparse) {
break;
}
Runlist->Unit.Elements[Runlist->Unit.Tail].Vcn = Runlist->NextVcn;
Runlist->Unit.Elements[Runlist->Unit.Tail].Lcn = Runlist->CurrentLcn + Runlist->NextVcn - Runlist->CurrentVcn;
++Runlist->Unit.Tail;
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
return Status;
}
}
}
if (ClusterSize >= COMPRESSION_BLOCK) {
SpareBlocks = (UINTN)((mUnitSize - (Runlist->TargetVcn & UNIT_MASK)) * BlocksPerCluster);
} else {
SpareBlocks = (UINTN)DivU64x64Remainder (mUnitSize - (Runlist->TargetVcn & UNIT_MASK), ClustersPerBlock, NULL);
}
if (SpareBlocks > BlocksTotal) {
SpareBlocks = BlocksTotal;
}
BlocksTotal -= SpareBlocks;
if (Runlist->IsSparse) {
if (ClusterSize >= COMPRESSION_BLOCK) {
Runlist->TargetVcn += DivU64x64Remainder (SpareBlocks, BlocksPerCluster, NULL);
} else {
Runlist->TargetVcn += SpareBlocks * ClustersPerBlock;
}
if (Runlist->Unit.Tail == 0) {
if (Dest != NULL) {
if (mBufferSize < (SpareBlocks * COMPRESSION_BLOCK)) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadCompressedBlock #1) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
ZeroMem (Dest, SpareBlocks * COMPRESSION_BLOCK);
Dest += SpareBlocks * COMPRESSION_BLOCK;
mBufferSize -= SpareBlocks * COMPRESSION_BLOCK;
}
} else {
while (SpareBlocks != 0) {
Status = DecompressBlock (&Runlist->Unit, Dest);
if (EFI_ERROR (Status)) {
return Status;
}
if (Dest != NULL) {
Dest += COMPRESSION_BLOCK;
}
--SpareBlocks;
}
}
} else {
if (ClusterSize >= COMPRESSION_BLOCK) {
SpareClusters = DivU64x64Remainder (SpareBlocks, BlocksPerCluster, NULL);
} else {
SpareClusters = SpareBlocks * ClustersPerBlock;
}
while ((Runlist->Unit.Head < Runlist->Unit.Tail) && (SpareClusters != 0)) {
ClearTextClusters = Runlist->Unit.Elements[Runlist->Unit.Head].Vcn - Runlist->TargetVcn;
if (ClearTextClusters > SpareClusters) {
ClearTextClusters = SpareClusters;
}
Runlist->TargetVcn += ClearTextClusters;
if (Dest != NULL) {
if (mBufferSize < (ClearTextClusters * ClusterSize)) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadCompressedBlock #2) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
Status = DiskRead (
Runlist->Unit.FileSystem,
Runlist->Unit.Elements[Runlist->Unit.Head].Lcn * ClusterSize,
(UINTN)(ClearTextClusters * ClusterSize),
Dest
);
if (EFI_ERROR (Status)) {
return Status;
}
Dest += ClearTextClusters * ClusterSize;
mBufferSize -= ClearTextClusters * ClusterSize;
}
SpareClusters -= ClearTextClusters;
++Runlist->Unit.Head;
}
if (SpareClusters != 0) {
if (Dest != NULL) {
if (mBufferSize < (SpareClusters * ClusterSize)) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadCompressedBlock #3) Buffer overflow.\n"));
return EFI_VOLUME_CORRUPTED;
}
Status = DiskRead (
Runlist->Unit.FileSystem,
(Runlist->TargetVcn - Runlist->CurrentVcn + Runlist->CurrentLcn) * ClusterSize,
(UINTN)(SpareClusters * ClusterSize),
Dest
);
if (EFI_ERROR (Status)) {
return Status;
}
Dest += SpareClusters * ClusterSize;
mBufferSize -= SpareClusters * ClusterSize;
}
Runlist->TargetVcn += SpareClusters;
}
}
}
return EFI_SUCCESS;
}
EFI_STATUS
Decompress (
IN RUNLIST *Runlist,
IN UINT64 Offset,
IN UINTN Length,
OUT UINT8 *Dest
)
{
EFI_STATUS Status;
UINT64 Vcn;
UINT64 Target;
UINTN SpareBytes;
UINTN Residual;
UINT64 BlocksPerCluster;
UINT64 ClustersPerBlock;
UINTN ClusterSize;
ASSERT (Runlist != NULL);
ASSERT (Dest != NULL);
ClusterSize = Runlist->Unit.FileSystem->ClusterSize;
if (Runlist->Unit.ClearTextBlock != NULL) {
if ((Offset & (~(COMPRESSION_BLOCK - 1U))) == Runlist->Unit.SavedPosition) {
Residual = (UINTN)(COMPRESSION_BLOCK - (Offset - Runlist->Unit.SavedPosition));
if (Residual > Length) {
Residual = Length;
}
CopyMem (Dest, Runlist->Unit.ClearTextBlock + Offset - Runlist->Unit.SavedPosition, Residual);
if (Residual == Length) {
return EFI_SUCCESS;
}
Dest += Residual;
Length -= Residual;
Offset += Residual;
}
} else {
Runlist->Unit.ClearTextBlock = AllocateZeroPool (COMPRESSION_BLOCK);
if (Runlist->Unit.ClearTextBlock == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Runlist->Unit.SavedPosition = 1U;
}
Vcn = Runlist->TargetVcn;
Runlist->TargetVcn &= ~(mUnitSize - 1U);
while (Runlist->NextVcn <= Runlist->TargetVcn) {
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
FreePool (Runlist->Unit.ClearTextBlock);
return Status;
}
}
Runlist->Unit.Head = Runlist->Unit.Tail = 0;
Runlist->Unit.Cluster = AllocateZeroPool (ClusterSize);
if (Runlist->Unit.Cluster == NULL) {
FreePool (Runlist->Unit.ClearTextBlock);
return EFI_OUT_OF_RESOURCES;
}
if (Vcn > Runlist->TargetVcn) {
if (ClusterSize >= COMPRESSION_BLOCK) {
BlocksPerCluster = DivU64x64Remainder (ClusterSize, COMPRESSION_BLOCK, NULL);
Status = ReadCompressedBlock (
Runlist,
NULL,
(UINTN)((Vcn - Runlist->TargetVcn) * BlocksPerCluster)
);
} else {
ClustersPerBlock = DivU64x64Remainder (COMPRESSION_BLOCK, ClusterSize, NULL);
Status = ReadCompressedBlock (
Runlist,
NULL,
(UINTN)DivU64x64Remainder (Vcn - Runlist->TargetVcn, ClustersPerBlock, NULL)
);
}
if (EFI_ERROR (Status)) {
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return Status;
}
}
if ((Offset % COMPRESSION_BLOCK) != 0) {
Target = Runlist->TargetVcn * ClusterSize;
Status = ReadCompressedBlock (Runlist, Runlist->Unit.ClearTextBlock, 1U);
if (EFI_ERROR (Status)) {
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return Status;
}
Runlist->Unit.SavedPosition = Target;
Residual = (UINTN)(Offset % COMPRESSION_BLOCK);
SpareBytes = COMPRESSION_BLOCK - Residual;
if (SpareBytes > Length) {
SpareBytes = Length;
}
CopyMem (Dest, &Runlist->Unit.ClearTextBlock[Residual], SpareBytes);
if (SpareBytes == Length) {
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return EFI_SUCCESS;
}
Dest += SpareBytes;
Length -= SpareBytes;
}
Status = ReadCompressedBlock (Runlist, Dest, Length / COMPRESSION_BLOCK);
if (EFI_ERROR (Status)) {
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return Status;
}
Dest += (Length / COMPRESSION_BLOCK) * COMPRESSION_BLOCK;
Length %= COMPRESSION_BLOCK;
if (Length != 0) {
Target = Runlist->TargetVcn * ClusterSize;
Status = ReadCompressedBlock (Runlist, Runlist->Unit.ClearTextBlock, 1U);
if (EFI_ERROR (Status)) {
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return Status;
}
Runlist->Unit.SavedPosition = Target;
CopyMem (Dest, Runlist->Unit.ClearTextBlock, Length);
}
FreePool (Runlist->Unit.ClearTextBlock);
FreePool (Runlist->Unit.Cluster);
return EFI_SUCCESS;
}

629
Platform/OpenNtfsDxe/Data.c Normal file
View File

@ -0,0 +1,629 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
UINT64 mUnitSize;
STATIC
UINT64
GetLcn (
IN RUNLIST *Runlist,
IN UINT64 Vcn
)
{
EFI_STATUS Status;
UINT64 Delta;
ASSERT (Runlist != NULL);
if (Vcn >= Runlist->NextVcn) {
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
return (UINT64)(-1);
}
return Runlist->CurrentLcn;
}
Delta = Vcn - Runlist->CurrentVcn;
return Runlist->IsSparse ? 0 : (Runlist->CurrentLcn + Delta);
}
STATIC
EFI_STATUS
ReadClusters (
IN RUNLIST *Runlist,
IN UINT64 Offset,
IN UINTN Length,
OUT UINT8 *Dest
)
{
EFI_STATUS Status;
UINT64 Index;
UINT64 ClustersTotal;
UINT64 Cluster;
UINT64 OffsetInsideCluster;
UINTN Size;
UINTN ClusterSize;
ASSERT (Runlist != NULL);
ASSERT (Dest != NULL);
ClusterSize = Runlist->Unit.FileSystem->ClusterSize;
OffsetInsideCluster = Offset & (ClusterSize - 1U);
Size = ClusterSize;
ClustersTotal = DivU64x64Remainder (Length + Offset + ClusterSize - 1U, ClusterSize, NULL);
for (Index = Runlist->TargetVcn; Index < ClustersTotal; ++Index) {
Cluster = GetLcn (Runlist, Index);
if (Cluster == (UINT64)(-1)) {
return EFI_DEVICE_ERROR;
}
Cluster *= ClusterSize;
if (Index == (ClustersTotal - 1U)) {
Size = (UINTN)((Length + Offset) & (ClusterSize - 1U));
if (Size == 0) {
Size = ClusterSize;
}
if (Index != Runlist->TargetVcn) {
OffsetInsideCluster = 0;
}
}
if (Index == Runlist->TargetVcn) {
Size -= (UINTN)OffsetInsideCluster;
}
if ((Index != Runlist->TargetVcn) && (Index != (ClustersTotal - 1U))) {
Size = ClusterSize;
OffsetInsideCluster = 0;
}
if (Cluster != 0) {
Status = DiskRead (
Runlist->Unit.FileSystem,
Cluster + OffsetInsideCluster,
Size,
Dest
);
if (EFI_ERROR (Status)) {
return Status;
}
} else {
SetMem (Dest, Size, 0);
}
Dest += Size;
}
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
DiskRead (
IN EFI_FS *FileSystem,
IN UINT64 Offset,
IN UINTN Size,
IN OUT VOID *Buffer
)
{
EFI_STATUS Status;
EFI_BLOCK_IO_MEDIA *Media;
ASSERT (FileSystem != NULL);
ASSERT (FileSystem->DiskIo != NULL);
ASSERT (FileSystem->BlockIo != NULL);
Media = FileSystem->BlockIo->Media;
Status = FileSystem->DiskIo->ReadDisk (
FileSystem->DiskIo,
Media->MediaId,
Offset,
Size,
Buffer
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read disk at address %Lx\n", Offset));
}
return Status;
}
EFI_STATUS
EFIAPI
ReadMftRecord (
IN EFI_NTFS_FILE *File,
OUT UINT8 *Buffer,
IN UINT64 RecordNumber
)
{
EFI_STATUS Status;
UINTN FileRecordSize;
ASSERT (File != NULL);
ASSERT (Buffer != NULL);
FileRecordSize = File->FileSystem->FileRecordSize;
Status = ReadAttr (
&File->MftFile.Attr,
Buffer,
RecordNumber * FileRecordSize,
FileRecordSize
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read MFT Record 0x%Lx\n", RecordNumber));
FreeAttr (&File->MftFile.Attr);
return Status;
}
return Fixup (
Buffer,
FileRecordSize,
SIGNATURE_32 ('F', 'I', 'L', 'E'),
File->FileSystem->SectorSize
);
}
EFI_STATUS
EFIAPI
ReadAttr (
IN NTFS_ATTR *Attr,
OUT UINT8 *Dest,
IN UINT64 Offset,
IN UINTN Length
)
{
EFI_STATUS Status;
UINT8 *Current;
UINT32 Type;
UINT8 *AttrStart;
UINT64 Vcn;
ATTR_LIST_RECORD *Record;
ATTR_HEADER_RES *Res;
UINTN ClusterSize;
ASSERT (Attr != NULL);
ASSERT (Dest != NULL);
Current = Attr->Current;
Attr->Next = Attr->Current;
ClusterSize = Attr->BaseMftRecord->File->FileSystem->ClusterSize;
if ((Attr->Flags & NTFS_AF_ALST) != 0) {
if (Attr->Last < (Attr->Next + sizeof (ATTR_LIST_RECORD))) {
DEBUG ((DEBUG_INFO, "NTFS: $ATTRIBUTE_LIST does not contain even a single record.\n"));
return EFI_VOLUME_CORRUPTED;
}
Record = (ATTR_LIST_RECORD *)Attr->Next;
Type = Record->Type;
Vcn = DivU64x64Remainder (Offset, ClusterSize, NULL);
if (COMPRESSION_BLOCK >= ClusterSize) {
Vcn &= ~0xFULL;
}
Record = (ATTR_LIST_RECORD *)((UINT8 *)Record + Record->RecordLength);
while (((UINT8 *)Record + sizeof (ATTR_LIST_RECORD)) <= Attr->Last) {
if (Record->Type != Type) {
break;
}
if (Record->StartingVCN > Vcn) {
break;
}
if (Record->RecordLength == 0) {
return EFI_VOLUME_CORRUPTED;
}
Attr->Next = (UINT8 *)Record;
Record = (ATTR_LIST_RECORD *)((UINT8 *)Record + Record->RecordLength);
}
} else {
Res = (ATTR_HEADER_RES *)Attr->Next;
Type = Res->Type;
}
AttrStart = FindAttr (Attr, Type);
if (AttrStart != NULL) {
Status = ReadData (Attr, AttrStart, Dest, Offset, Length);
} else {
DEBUG ((DEBUG_INFO, "NTFS: Attribute not found\n"));
Status = EFI_VOLUME_CORRUPTED;
}
Attr->Current = Current;
return Status;
}
EFI_STATUS
EFIAPI
ReadData (
IN NTFS_ATTR *Attr,
IN UINT8 *AttrStart,
OUT UINT8 *Dest,
IN UINT64 Offset,
IN UINTN Length
)
{
EFI_STATUS Status;
RUNLIST *Runlist;
ATTR_HEADER_NONRES *NonRes;
ATTR_HEADER_RES *Res;
UINT64 Sector0;
UINT64 Sector1;
UINT64 OffsetInsideCluster;
UINT64 BufferSize;
UINTN FileRecordSize;
UINTN SectorSize;
UINTN ClusterSize;
if (Length == 0) {
return EFI_SUCCESS;
}
FileRecordSize = Attr->BaseMftRecord->File->FileSystem->FileRecordSize;
SectorSize = Attr->BaseMftRecord->File->FileSystem->SectorSize;
ClusterSize = Attr->BaseMftRecord->File->FileSystem->ClusterSize;
BufferSize = FileRecordSize - (Attr->Current - Attr->BaseMftRecord->FileRecord);
//
// Resident Attribute
//
if (BufferSize < sizeof (ATTR_HEADER_RES)) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadData #1) Attribute is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
Res = (ATTR_HEADER_RES *)AttrStart;
if (Res->NonResFlag == 0) {
if ((Offset + Length) > Res->InfoLength) {
DEBUG ((DEBUG_INFO, "NTFS: Read out of range\n"));
return EFI_VOLUME_CORRUPTED;
}
if (BufferSize < (Res->InfoOffset + Offset + Length)) {
DEBUG ((DEBUG_INFO, "NTFS: Read out of buffer.\n"));
return EFI_VOLUME_CORRUPTED;
}
CopyMem (Dest, (UINT8 *)Res + Res->InfoOffset + Offset, Length);
return EFI_SUCCESS;
}
//
// Non-Resident Attribute
//
if (BufferSize < sizeof (ATTR_HEADER_NONRES)) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadData #2) Attribute is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
NonRes = (ATTR_HEADER_NONRES *)AttrStart;
Runlist = (RUNLIST *)AllocateZeroPool (sizeof (RUNLIST));
if (Runlist == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Runlist->Attr = Attr;
Runlist->Unit.FileSystem = Attr->BaseMftRecord->File->FileSystem;
if ( (NonRes->DataRunsOffset > BufferSize)
|| (NonRes->DataRunsOffset > NonRes->RealSize)
|| (NonRes->RealSize > NonRes->AllocatedSize))
{
DEBUG ((DEBUG_INFO, "NTFS: Non-Resident Attribute is corrupted.\n"));
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
Runlist->NextDataRun = (UINT8 *)NonRes + NonRes->DataRunsOffset;
Runlist->NextVcn = NonRes->StartingVCN;
Runlist->CurrentLcn = 0;
Runlist->TargetVcn = NonRes->StartingVCN + DivU64x64Remainder (Offset, ClusterSize, NULL);
if (Runlist->TargetVcn < NonRes->StartingVCN) {
DEBUG ((DEBUG_INFO, "NTFS: Overflow: StartingVCN is too large.\n"));
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
if ((Offset + Length) > NonRes->RealSize) {
DEBUG ((DEBUG_INFO, "NTFS: Read out of range\n"));
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
if ( ((NonRes->Flags & FLAG_COMPRESSED) != 0)
&& ((Attr->Flags & NTFS_AF_GPOS) == 0)
&& (NonRes->Type == AT_DATA))
{
if (NonRes->CompressionUnitSize != 4U) {
DEBUG ((DEBUG_INFO, "NTFS: Invalid copmression unit size.\n"));
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
mUnitSize = LShiftU64 (1ULL, NonRes->CompressionUnitSize);
Status = Decompress (Runlist, Offset, Length, Dest);
FreePool (Runlist);
return Status;
}
while (Runlist->NextVcn <= Runlist->TargetVcn) {
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
}
if (Attr->Flags & NTFS_AF_GPOS) {
OffsetInsideCluster = Offset & (ClusterSize - 1U);
Sector0 = DivU64x64Remainder (
(Runlist->TargetVcn - Runlist->CurrentVcn + Runlist->CurrentLcn) * ClusterSize + OffsetInsideCluster,
SectorSize,
NULL
);
Sector1 = Sector0 + 1U;
if (Sector1 == DivU64x64Remainder (
(Runlist->NextVcn - Runlist->CurrentVcn + Runlist->CurrentLcn) * ClusterSize,
SectorSize,
NULL
))
{
Status = ReadRunListElement (Runlist);
if (EFI_ERROR (Status)) {
FreePool (Runlist);
return EFI_VOLUME_CORRUPTED;
}
Sector1 = DivU64x64Remainder (
Runlist->CurrentLcn * ClusterSize,
SectorSize,
NULL
);
}
WriteUnaligned32 ((UINT32 *)Dest, (UINT32)Sector0);
WriteUnaligned32 ((UINT32 *)(Dest + 4U), (UINT32)Sector1);
FreePool (Runlist);
return EFI_SUCCESS;
}
Status = ReadClusters (
Runlist,
Offset,
Length,
Dest
);
FreePool (Runlist);
return Status;
}
STATIC
UINT64
ReadField (
IN CONST UINT8 *Run,
IN UINT8 FieldSize,
IN BOOLEAN Signed
)
{
UINT64 Value;
ASSERT (Run != NULL);
Value = 0;
//
// Offset to the starting LCN of the previous element is a signed value.
// So we must check the most significant bit.
//
if (Signed && (FieldSize != 0) && ((Run[FieldSize - 1U] & 0x80U) != 0)) {
Value = (UINT64)(-1);
}
CopyMem (&Value, Run, FieldSize);
return Value;
}
/**
Table 4.10. Layout of a Data Run
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0 | 0.5 | F=Size of the Offset field
0.5 | 0.5 | L=Size of the Length field
1 | L | Length of the run
1+L | F | Offset to the starting LCN of the previous element (signed)
--------------------------------------------------------------------
**/
EFI_STATUS
EFIAPI
ReadRunListElement (
IN OUT RUNLIST *Runlist
)
{
UINT8 LengthSize;
UINT8 OffsetSize;
UINT64 OffsetLcn;
UINT8 *Run;
ATTR_HEADER_NONRES *Attr;
UINT64 BufferSize;
UINTN FileRecordSize;
ASSERT (Runlist != NULL);
Run = Runlist->NextDataRun;
FileRecordSize = Runlist->Attr->BaseMftRecord->File->FileSystem->FileRecordSize;
BufferSize = FileRecordSize - (Run - Runlist->Attr->BaseMftRecord->FileRecord);
retry:
if (BufferSize == 0) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #1) Runlist is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
LengthSize = *Run & 0xFU;
OffsetSize = (*Run >> 4U) & 0xFU;
++Run;
--BufferSize;
if ( (LengthSize > 8U)
|| (OffsetSize > 8U)
|| ((LengthSize == 0) && (OffsetSize != 0)))
{
DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #2) Runlist is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
//
// End of Runlist: LengthSize == 0, OffsetSize == 0
//
if ((LengthSize == 0) && (OffsetSize == 0)) {
if ( (Runlist->Attr != NULL)
&& ((Runlist->Attr->Flags & NTFS_AF_ALST) != 0))
{
Attr = (ATTR_HEADER_NONRES *)Runlist->Attr->Current;
Attr = (ATTR_HEADER_NONRES *)FindAttr (Runlist->Attr, Attr->Type);
if (Attr != NULL) {
if (Attr->NonResFlag == 0) {
DEBUG ((DEBUG_INFO, "NTFS: $DATA should be non-resident\n"));
return EFI_VOLUME_CORRUPTED;
}
Run = (UINT8 *)Attr + Attr->DataRunsOffset;
Runlist->CurrentLcn = 0;
BufferSize = FileRecordSize - Attr->DataRunsOffset;
goto retry;
}
}
DEBUG ((DEBUG_INFO, "NTFS: Run list overflown\n"));
return EFI_VOLUME_CORRUPTED;
}
Runlist->CurrentVcn = Runlist->NextVcn;
if (BufferSize < LengthSize) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #3) Runlist is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
Runlist->NextVcn += ReadField (Run, LengthSize, FALSE);
if (Runlist->NextVcn <= Runlist->CurrentVcn) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #3.1) Runlist is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
Run += LengthSize;
BufferSize -= LengthSize;
if (BufferSize < OffsetSize) {
DEBUG ((DEBUG_INFO, "NTFS: (ReadRunListElement #4) Runlist is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
OffsetLcn = ReadField (Run, OffsetSize, TRUE);
Runlist->CurrentLcn += OffsetLcn;
Run += OffsetSize;
BufferSize -= OffsetSize;
Runlist->NextDataRun = Run;
Runlist->IsSparse = (OffsetLcn == 0);
return EFI_SUCCESS;
}
CHAR16 *
ReadSymlink (
IN NTFS_FILE *File
)
{
EFI_STATUS Status;
SYMLINK Symlink;
CHAR16 *Substitute;
CHAR16 *Letter;
UINT64 Offset;
ASSERT (File != NULL);
File->FileRecord = AllocateZeroPool (File->File->FileSystem->FileRecordSize);
if (File->FileRecord == NULL) {
return NULL;
}
Status = ReadMftRecord (File->File, File->FileRecord, File->Inode);
if (EFI_ERROR (Status)) {
return NULL;
}
if (LocateAttr (&File->Attr, File, AT_SYMLINK) == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: no $SYMLINK in MFT 0x%Lx\n", File->Inode));
return NULL;
}
Status = ReadAttr (&File->Attr, (UINT8 *)&Symlink, 0, sizeof (Symlink));
if (EFI_ERROR (Status)) {
FreeAttr (&File->Attr);
return NULL;
}
switch (Symlink.Type) {
case (IS_ALIAS | IS_MICROSOFT | S_SYMLINK):
Offset = sizeof (Symlink) + 4U + (UINT64)Symlink.SubstituteOffset;
break;
case (IS_ALIAS | IS_MICROSOFT | S_FILENAME):
Offset = sizeof (Symlink) + (UINT64)Symlink.SubstituteOffset;
break;
default:
DEBUG ((DEBUG_INFO, "NTFS: Symlink type invalid (%x)\n", Symlink.Type));
return NULL;
}
Substitute = AllocateZeroPool (Symlink.SubstituteLength + sizeof (CHAR16));
if (Substitute == NULL) {
return NULL;
}
Status = ReadAttr (&File->Attr, (UINT8 *)Substitute, Offset, Symlink.SubstituteLength);
if (EFI_ERROR (Status)) {
FreeAttr (&File->Attr);
FreePool (Substitute);
return NULL;
}
for (Letter = Substitute; *Letter != L'\0'; ++Letter) {
if (*Letter == L'\\') {
*Letter = L'/';
}
}
return Substitute;
}

825
Platform/OpenNtfsDxe/Disc.c Normal file
View File

@ -0,0 +1,825 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
EFI_STATUS
NtfsDir (
IN EFI_FS *FileSystem,
IN CONST CHAR16 *Path,
OUT EFI_NTFS_FILE *File,
IN FUNCTION_TYPE FunctionType
)
{
EFI_STATUS Status;
NTFS_FILE *Dir;
ASSERT (FileSystem != NULL);
ASSERT (Path != NULL);
ASSERT (File != NULL);
Dir = NULL;
CopyMem (&File->RootFile, FileSystem->RootIndex, sizeof (File->RootFile));
CopyMem (&File->MftFile, FileSystem->MftStart, sizeof (File->MftFile));
Status = FsHelpFindFile (
Path,
&File->RootFile,
&Dir,
FSHELP_DIR
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = IterateDir (Dir, File, FunctionType);
if (Dir != &File->RootFile) {
FreeFile (Dir);
FreePool (Dir);
}
return Status;
}
EFI_STATUS
NtfsOpen (
IN EFI_NTFS_FILE *File
)
{
EFI_STATUS Status;
NTFS_FILE *BaseMftRecord;
ASSERT (File != NULL);
BaseMftRecord = NULL;
CopyMem (&File->RootFile, File->FileSystem->RootIndex, sizeof (File->RootFile));
CopyMem (&File->MftFile, File->FileSystem->MftStart, sizeof (File->MftFile));
Status = FsHelpFindFile (
File->Path,
&File->RootFile,
&BaseMftRecord,
FSHELP_REG
);
if (EFI_ERROR (Status)) {
return Status;
}
if (BaseMftRecord != &File->RootFile) {
CopyMem (&File->RootFile, BaseMftRecord, sizeof (*BaseMftRecord));
if (!File->RootFile.InodeRead) {
Status = InitFile (&File->RootFile, File->RootFile.Inode);
}
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
}
File->Offset = 0;
return Status;
}
EFI_STATUS
NtfsMount (
IN EFI_FS *FileSystem
)
{
EFI_STATUS Status;
BOOT_FILE_DATA Boot;
UINTN Size;
EFI_NTFS_FILE *RootFile;
ASSERT (FileSystem != NULL);
Status = DiskRead (FileSystem, 0, sizeof (Boot), &Boot);
if (EFI_ERROR (Status)) {
return Status;
}
if ( (Boot.SystemId[0] != SIGNATURE_32 ('N', 'T', 'F', 'S'))
|| (Boot.SectorsPerCluster == 0)
|| ((Boot.SectorsPerCluster & (Boot.SectorsPerCluster - 1U)) != 0)
|| (Boot.BytesPerSector == 0)
|| ((Boot.BytesPerSector & (Boot.BytesPerSector - 1U)) != 0))
{
DEBUG ((DEBUG_INFO, "NTFS: (NtfsMount #1) BIOS Parameter Block is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
FileSystem->SectorSize = (UINTN)Boot.BytesPerSector;
FileSystem->ClusterSize = (UINTN)Boot.SectorsPerCluster * FileSystem->SectorSize;
if (Boot.MftRecordClusters > 0) {
Size = (UINTN)Boot.MftRecordClusters * FileSystem->ClusterSize;
} else if (-Boot.MftRecordClusters >= 31) {
DEBUG ((DEBUG_INFO, "NTFS: (NtfsMount #2) BIOS Parameter Block is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
} else {
Size = (UINTN)LShiftU64 (1ULL, -Boot.MftRecordClusters);
}
FileSystem->FileRecordSize = Size;
if (FileSystem->FileRecordSize < FileSystem->SectorSize) {
DEBUG ((DEBUG_INFO, "NTFS: File Record is smaller than Sector.\n"));
return EFI_VOLUME_CORRUPTED;
}
if (Boot.IndexRecordClusters > 0) {
Size = (UINTN)Boot.IndexRecordClusters * FileSystem->ClusterSize;
} else if (-Boot.IndexRecordClusters >= 31) {
DEBUG ((DEBUG_INFO, "NTFS: (NtfsMount #3) BIOS Parameter Block is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
} else {
Size = (UINTN)LShiftU64 (1ULL, -Boot.IndexRecordClusters);
}
FileSystem->IndexRecordSize = Size;
if (FileSystem->IndexRecordSize < FileSystem->SectorSize) {
DEBUG ((DEBUG_INFO, "NTFS: Index Record is smaller than Sector.\n"));
return EFI_VOLUME_CORRUPTED;
}
FileSystem->FirstMftRecord = Boot.MftLcn * FileSystem->ClusterSize;
//
// Driver limitations
//
if ( (FileSystem->FileRecordSize > NTFS_MAX_MFT)
|| (FileSystem->IndexRecordSize > NTFS_MAX_IDX))
{
DEBUG ((DEBUG_INFO, "NTFS: (NtfsMount #4) BIOS Parameter Block is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
RootFile = AllocateZeroPool (sizeof (*RootFile));
if (RootFile == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (RootFile, &FileSystem->EfiFile, sizeof (FileSystem->EfiFile));
RootFile->IsDir = TRUE;
RootFile->Path = L"/";
RootFile->RefCount = 1U;
RootFile->FileSystem = FileSystem;
RootFile->RootFile.File = RootFile;
RootFile->MftFile.File = RootFile;
RootFile->MftFile.FileRecord = AllocateZeroPool (FileSystem->FileRecordSize);
if (RootFile->MftFile.FileRecord == NULL) {
FreePool (RootFile);
return EFI_OUT_OF_RESOURCES;
}
Status = DiskRead (
FileSystem,
FileSystem->FirstMftRecord,
FileSystem->FileRecordSize,
RootFile->MftFile.FileRecord
);
if (EFI_ERROR (Status)) {
FreePool (RootFile->MftFile.FileRecord);
FreePool (RootFile);
return Status;
}
Status = Fixup (
RootFile->MftFile.FileRecord,
FileSystem->FileRecordSize,
SIGNATURE_32 ('F', 'I', 'L', 'E'),
FileSystem->SectorSize
);
if (EFI_ERROR (Status)) {
FreePool (RootFile->MftFile.FileRecord);
FreePool (RootFile);
return Status;
}
if (LocateAttr (&RootFile->MftFile.Attr, &RootFile->MftFile, AT_DATA) == NULL) {
FreePool (RootFile->MftFile.FileRecord);
FreePool (RootFile);
return EFI_VOLUME_CORRUPTED;
}
Status = InitFile (&RootFile->RootFile, ROOT_FILE);
if (EFI_ERROR (Status)) {
FreePool (RootFile->MftFile.FileRecord);
FreePool (RootFile);
return Status;
}
FileSystem->RootIndex = &RootFile->RootFile;
FileSystem->MftStart = &RootFile->MftFile;
return EFI_SUCCESS;
}
/**
Table 4.21. Layout of a File Record
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 4 | Magic number 'FILE'
0x04 | 2 | Offset to the update sequence
0x06 | 2 | Size in words of Update Sequence Number & Array (S)
...
| 2 | Update Sequence Number (a)
| 2S-2 | Update Sequence Array (a)
--------------------------------------------------------------------
**/
EFI_STATUS
EFIAPI
Fixup (
IN UINT8 *Buffer,
IN UINT64 Length,
IN UINT32 Magic,
IN UINTN SectorSize
)
{
FILE_RECORD_HEADER *Record;
UINT8 *UpdateSequencePointer;
UINT16 UpdateSequenceNumber;
UINT64 USCounter;
UINT8 *BufferEnd;
ASSERT (Buffer != NULL);
Record = (FILE_RECORD_HEADER *)Buffer;
if (Length < sizeof (*Record)) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #1) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
if (Record->Magic != Magic) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #2) Record is corrupted.\n"));
return EFI_NOT_FOUND;
}
if ((Record->UpdateSequenceOffset + sizeof (UINT16)) > Length) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #3) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
if (((UINT64)Record->S_Size - 1U) != DivU64x64Remainder (Length, SectorSize, NULL)) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #4) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
UpdateSequencePointer = Buffer + Record->UpdateSequenceOffset;
UpdateSequenceNumber = ReadUnaligned16 ((UINT16 *)UpdateSequencePointer);
USCounter = Record->UpdateSequenceOffset;
if (Length < (SectorSize - sizeof (UINT16))) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #5) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
BufferEnd = Buffer + Length;
Buffer += SectorSize - sizeof (UINT16);
while ((Buffer + sizeof (UINT16)) <= BufferEnd) {
UpdateSequencePointer += sizeof (UINT16);
USCounter += sizeof (UINT16);
if ((USCounter + sizeof (UINT16)) > Length) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #6) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
if (ReadUnaligned16 ((UINT16 *)Buffer) != UpdateSequenceNumber) {
DEBUG ((DEBUG_INFO, "NTFS: (Fixup #7) Record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
Buffer[0] = UpdateSequencePointer[0];
Buffer[1] = UpdateSequencePointer[1];
Buffer += SectorSize;
}
return EFI_SUCCESS;
}
EFI_STATUS
InitAttr (
OUT NTFS_ATTR *Attr,
IN NTFS_FILE *File
)
{
FILE_RECORD_HEADER *Record;
UINT64 AttrEnd;
ASSERT (Attr != NULL);
ASSERT (File != NULL);
Record = (FILE_RECORD_HEADER *)File->FileRecord;
Attr->BaseMftRecord = File;
Attr->Flags = (File == &File->File->MftFile) ? NTFS_AF_MFT_FILE : 0;
AttrEnd = Record->AttributeOffset + sizeof (ATTR_HEADER_RES);
if ( (AttrEnd > File->File->FileSystem->FileRecordSize)
|| (AttrEnd > Record->RealSize)
|| (Record->RealSize > Record->AllocatedSize))
{
DEBUG ((DEBUG_INFO, "NTFS: (InitAttr) File record is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
Attr->Next = File->FileRecord + Record->AttributeOffset;
Attr->Last = NULL;
Attr->ExtensionMftRecord = NULL;
Attr->NonResAttrList = NULL;
return EFI_SUCCESS;
}
UINT8 *
LocateAttr (
IN NTFS_ATTR *Attr,
IN NTFS_FILE *Mft,
IN UINT32 Type
)
{
EFI_STATUS Status;
UINT8 *AttrStart;
ASSERT (Attr != NULL);
ASSERT (Mft != NULL);
Status = InitAttr (Attr, Mft);
if (EFI_ERROR (Status)) {
return NULL;
}
AttrStart = FindAttr (Attr, Type);
if (AttrStart == NULL) {
FreeAttr (Attr);
return NULL;
}
if ((Attr->Flags & NTFS_AF_ALST) == 0) {
while (TRUE) {
AttrStart = FindAttr (Attr, Type);
if (AttrStart == NULL) {
break;
}
if ((Attr->Flags & NTFS_AF_ALST) != 0) {
return AttrStart;
}
}
FreeAttr (Attr);
Status = InitAttr (Attr, Mft);
if (EFI_ERROR (Status)) {
return NULL;
}
AttrStart = FindAttr (Attr, Type);
if (AttrStart == NULL) {
FreeAttr (Attr);
return NULL;
}
}
return AttrStart;
}
UINT8 *
FindAttr (
IN NTFS_ATTR *Attr,
IN UINT32 Type
)
{
EFI_STATUS Status;
UINT8 *AttrStart;
ATTR_HEADER_RES *Res;
ATTR_HEADER_NONRES *NonRes;
ATTR_LIST_RECORD *LRecord;
FILE_RECORD_HEADER *FRecord;
UINT64 BufferSize;
UINTN FileRecordSize;
UINTN SectorSize;
ASSERT (Attr != NULL);
BufferSize = 0;
FileRecordSize = Attr->BaseMftRecord->File->FileSystem->FileRecordSize;
SectorSize = Attr->BaseMftRecord->File->FileSystem->SectorSize;
if ((Attr->Flags & NTFS_AF_ALST) != 0) {
retry:
while ((Attr->Next + sizeof (*LRecord)) <= Attr->Last) {
Attr->Current = Attr->Next;
LRecord = (ATTR_LIST_RECORD *)Attr->Current;
if (BufferSize < LRecord->RecordLength) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #0) $ATTRIBUTE_LIST is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
Attr->Next += LRecord->RecordLength;
BufferSize -= LRecord->RecordLength;
if (Attr->Next <= Attr->Current) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #1) $ATTRIBUTE_LIST is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if ((LRecord->Type == Type) || (Type == 0)) {
if (Attr->Flags & NTFS_AF_MFT_FILE) {
Status = DiskRead (
Attr->BaseMftRecord->File->FileSystem,
ReadUnaligned32 ((UINT32 *)(Attr->Current + 0x10U)),
FileRecordSize / 2U,
Attr->ExtensionMftRecord
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read first part of extension record.\n"));
FreeAttr (Attr);
return NULL;
}
Status = DiskRead (
Attr->BaseMftRecord->File->FileSystem,
ReadUnaligned32 ((UINT32 *)(Attr->Current + 0x14U)),
FileRecordSize / 2U,
Attr->ExtensionMftRecord + FileRecordSize / 2U
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read second part of extension record.\n"));
FreeAttr (Attr);
return NULL;
}
Status = Fixup (
Attr->ExtensionMftRecord,
FileRecordSize,
SIGNATURE_32 ('F', 'I', 'L', 'E'),
Attr->BaseMftRecord->File->FileSystem->SectorSize
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Fixup failed.\n"));
FreeAttr (Attr);
return NULL;
}
} else {
Status = ReadMftRecord (
Attr->BaseMftRecord->File,
Attr->ExtensionMftRecord,
(UINT32)LRecord->BaseFileReference
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read extension record.\n"));
FreeAttr (Attr);
return NULL;
}
}
FRecord = (FILE_RECORD_HEADER *)Attr->ExtensionMftRecord;
BufferSize = FileRecordSize;
if ((FRecord->AttributeOffset + sizeof (*Res)) <= BufferSize) {
Res = (ATTR_HEADER_RES *)((UINT8 *)FRecord + FRecord->AttributeOffset);
BufferSize -= FRecord->AttributeOffset;
} else {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #1) Extension record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
while ((BufferSize >= sizeof (UINT32)) && (Res->Type != ATTRIBUTES_END_MARKER)) {
if (BufferSize < sizeof (*Res)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #2) Extension record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if ( (Res->Type == LRecord->Type)
&& (Res->AttributeId == LRecord->AttributeId))
{
return (UINT8 *)Res;
}
if ((Res->Length == 0) || (Res->Length >= BufferSize)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #3) Extension record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
BufferSize -= Res->Length;
Res = (ATTR_HEADER_RES *)((UINT8 *)Res + Res->Length);
}
DEBUG ((DEBUG_INFO, "NTFS: Can\'t find 0x%X in attribute list\n", Attr->Current));
FreeAttr (Attr);
return NULL;
}
}
FreeAttr (Attr);
return NULL;
}
Attr->Current = Attr->Next;
Res = (ATTR_HEADER_RES *)Attr->Current;
BufferSize = FileRecordSize - (Attr->Current - Attr->BaseMftRecord->FileRecord);
while ((BufferSize >= sizeof (UINT32)) && (Res->Type != ATTRIBUTES_END_MARKER)) {
if (BufferSize < sizeof (*Res)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #1) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if ((Res->Length == 0) || (Res->Length >= BufferSize)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #2) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
BufferSize -= Res->Length;
Attr->Next += Res->Length;
if (Res->Type == AT_ATTRIBUTE_LIST) {
Attr->Last = Attr->Current;
}
if ((Res->Type == Type) || (Type == 0)) {
return Attr->Current;
}
Attr->Current = Attr->Next;
Res = (ATTR_HEADER_RES *)Attr->Current;
}
//
// Continue search in $ATTRIBUTE_LIST
//
if (Attr->Last != NULL) {
if (Attr->ExtensionMftRecord != NULL) {
FreePool (Attr->ExtensionMftRecord);
}
Attr->ExtensionMftRecord = AllocateZeroPool (FileRecordSize);
if (Attr->ExtensionMftRecord == NULL) {
FreeAttr (Attr);
return NULL;
}
AttrStart = Attr->Last;
Res = (ATTR_HEADER_RES *)Attr->Last;
BufferSize = FileRecordSize - (Attr->Last - Attr->BaseMftRecord->FileRecord);
if (BufferSize < sizeof (*Res)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #3) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if (Res->NonResFlag != 0) {
NonRes = (ATTR_HEADER_NONRES *)Res;
Attr->Current = (UINT8 *)NonRes;
if (BufferSize < sizeof (*NonRes)) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #4) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if (NonRes->RealSize > MAX_FILE_SIZE) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr) File is too huge.\n"));
FreeAttr (Attr);
return NULL;
}
if (Attr->NonResAttrList != NULL) {
FreePool (Attr->NonResAttrList);
}
Attr->NonResAttrList = AllocateZeroPool ((UINTN)NonRes->RealSize);
if (Attr->NonResAttrList == NULL) {
FreeAttr (Attr);
return NULL;
}
Status = ReadData (Attr, AttrStart, Attr->NonResAttrList, 0, (UINTN)NonRes->RealSize);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Failed to read non-resident attribute list\n"));
FreeAttr (Attr);
return NULL;
}
Attr->Next = Attr->NonResAttrList;
Attr->Last = Attr->NonResAttrList + NonRes->RealSize;
BufferSize = NonRes->RealSize;
} else {
if ((Res->InfoOffset < Res->Length) && (Res->Length <= BufferSize)) {
Attr->Next = (UINT8 *)Res + Res->InfoOffset;
Attr->Last = (UINT8 *)Res + Res->Length;
BufferSize -= Res->InfoOffset;
} else {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #5) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
}
Attr->Flags |= NTFS_AF_ALST;
LRecord = (ATTR_LIST_RECORD *)Attr->Next;
while ((Attr->Next + sizeof (*LRecord)) < Attr->Last) {
if ((LRecord->Type == Type) || (Type == 0)) {
break;
}
if (BufferSize < LRecord->RecordLength) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #2) $ATTRIBUTE_LIST is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
if (LRecord->RecordLength == 0) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #3) $ATTRIBUTE_LIST is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
Attr->Next += LRecord->RecordLength;
BufferSize -= LRecord->RecordLength;
LRecord = (ATTR_LIST_RECORD *)Attr->Next;
}
if ((Attr->Next + sizeof (*LRecord)) >= Attr->Last) {
FreeAttr (Attr);
return NULL;
}
if ((Attr->Flags & NTFS_AF_MFT_FILE) && (Type == AT_DATA)) {
Attr->Flags |= NTFS_AF_GPOS;
Attr->Current = Attr->Next;
AttrStart = Attr->Current;
if (BufferSize >= 0x18U) {
WriteUnaligned32 (
(UINT32 *)(AttrStart + 0x10U),
(UINT32)DivU64x64Remainder (Attr->BaseMftRecord->File->FileSystem->FirstMftRecord, SectorSize, NULL)
);
WriteUnaligned32 (
(UINT32 *)(AttrStart + 0x14U),
(UINT32)DivU64x64Remainder (Attr->BaseMftRecord->File->FileSystem->FirstMftRecord, SectorSize, NULL) + 1U
);
} else {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #7) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
AttrStart = Attr->Next + ReadUnaligned16 ((UINT16 *)(AttrStart + 0x04U));
while ((AttrStart + sizeof (UINT32) + sizeof (UINT16)) < Attr->Last) {
if (ReadUnaligned32 ((UINT32 *)AttrStart) != Type) {
break;
}
Status = ReadAttr (
Attr,
AttrStart + 0x10U,
ReadUnaligned32 ((UINT32 *)(AttrStart + 0x10U)) * FileRecordSize,
FileRecordSize
);
if (EFI_ERROR (Status)) {
FreeAttr (Attr);
return NULL;
}
if (ReadUnaligned16 ((UINT16 *)(AttrStart + 4U)) == 0) {
DEBUG ((DEBUG_INFO, "NTFS: (FindAttr #8) File record is corrupted.\n"));
FreeAttr (Attr);
return NULL;
}
AttrStart += ReadUnaligned16 ((UINT16 *)(AttrStart + 4U));
}
Attr->Next = Attr->Current;
Attr->Flags &= ~NTFS_AF_GPOS;
}
goto retry;
}
FreeAttr (Attr);
return NULL;
}
EFI_STATUS
EFIAPI
InitFile (
IN OUT NTFS_FILE *File,
IN UINT64 RecordNumber
)
{
EFI_STATUS Status;
ATTR_HEADER_RES *Attr;
FILE_RECORD_HEADER *Record;
ASSERT (File != NULL);
File->InodeRead = TRUE;
File->FileRecord = AllocateZeroPool (File->File->FileSystem->FileRecordSize);
if (File->FileRecord == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Status = ReadMftRecord (File->File, File->FileRecord, RecordNumber);
if (EFI_ERROR (Status)) {
FreePool (File->FileRecord);
File->FileRecord = NULL;
return Status;
}
Record = (FILE_RECORD_HEADER *)File->FileRecord;
if ((Record->Flags & IS_IN_USE) == 0) {
DEBUG ((DEBUG_INFO, "NTFS: MFT Record 0x%Lx is not in use\n", RecordNumber));
FreePool (File->FileRecord);
File->FileRecord = NULL;
return EFI_VOLUME_CORRUPTED;
}
if ((Record->Flags & IS_A_DIRECTORY) == 0) {
Attr = (ATTR_HEADER_RES *)LocateAttr (&File->Attr, File, AT_DATA);
if (Attr == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: No $DATA in MFT Record 0x%Lx\n", RecordNumber));
FreePool (File->FileRecord);
File->FileRecord = NULL;
return EFI_VOLUME_CORRUPTED;
}
if (Attr->NonResFlag == 0) {
File->DataAttributeSize = Attr->InfoLength;
} else {
File->DataAttributeSize = ((ATTR_HEADER_NONRES *)Attr)->RealSize;
}
if ((File->Attr.Flags & NTFS_AF_ALST) == 0) {
File->Attr.Last = 0;
}
} else {
Status = InitAttr (&File->Attr, File);
if (EFI_ERROR (Status)) {
FreePool (File->FileRecord);
File->FileRecord = NULL;
return Status;
}
}
return EFI_SUCCESS;
}
VOID
FreeAttr (
IN NTFS_ATTR *Attr
)
{
ASSERT (Attr != NULL);
if (Attr->ExtensionMftRecord != NULL) {
FreePool (Attr->ExtensionMftRecord);
Attr->ExtensionMftRecord = NULL;
}
if (Attr->NonResAttrList != NULL) {
FreePool (Attr->NonResAttrList);
Attr->NonResAttrList = NULL;
}
}
VOID
FreeFile (
IN NTFS_FILE *File
)
{
ASSERT (File != NULL);
FreeAttr (&File->Attr);
if ( (File->FileRecord != NULL)
&& (File->FileRecord != File->File->FileSystem->RootIndex->FileRecord)
&& (File->FileRecord != File->File->FileSystem->MftStart->FileRecord))
{
FreePool (File->FileRecord);
File->FileRecord = NULL;
}
}

View File

@ -0,0 +1,682 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#ifndef DRIVER_H
#define DRIVER_H
#define NTFS_MAX_MFT 4096
#define NTFS_MAX_IDX 16384
#define COMPRESSION_BLOCK 4096
#define NTFS_DRIVER_VERSION 0x00020000
#define MAX_PATH 1024
#define MINIMUM_INFO_LENGTH (sizeof (EFI_FILE_INFO) + MAX_PATH * sizeof(CHAR16))
#define MINIMUM_FS_INFO_LENGTH (sizeof (EFI_FILE_SYSTEM_INFO) + MAX_PATH * sizeof(CHAR16))
#define ATTRIBUTES_END_MARKER 0xFFFFFFFF
#define MAX_FILE_SIZE (MAX_UINT32 & ~7ULL)
#define S_FILENAME 0x3
#define S_SYMLINK 0xC
/**
************
*** FILE ***
************
* Everything on an NTFS volume is a File. Files are composed of Attributes
(usually 4: $STANDARD_INFORMATION, $FILE_NAME, $SECURITY_DESCRIPTOR, $DATA).
* Information about all the Attributes available in a volume is stored in
a File named $AttrDef.
* File named $MFT (Master File Table) is an index of every File on the volume.
The description of each File is packed into FILE Records.
A FILE Record is built up from a header, several variable length Attributes
and an end marker (simply 0xFFFFFFFF).
The first FILE Record that describes a given file is called
the Base FILE Record and the others are called Extension FILE Records.
Only the Base FILE Record is used for referencing the File it describes.
Attribute named $ATTRIBUTE_LIST provides the references
to all the Extension FILE Records.
* The $MFT lists the Boot Sector File ($Boot), located at the beginning of the disk.
$Boot also lists where to find the $MFT. The $MFT also lists itself.
* Every Attribute has a standard header and specific fields.
The header stores information about the Attribute's type, size,
name (optional) and whether it is resident (in $MFT), or not.
* Non-resident Attributes are stored in intervals of Clusters called Data Runs.
Each Data Run is represented by its starting Cluster and its length.
* The number of sectors that make up a Cluster is always a power of 2,
and this number is fixed when the volume is formatted.
The Cluster Size for a volume is stored in a File named $Boot.
* Each Cluster in a volume is given a sequential number.
This is its Logical Cluster Number.
LCN 0 (zero) refers to the first cluster in the volume (the Boot Sector).
* Each Cluster of a non-resident stream is given a sequential number.
This is its Virtual Cluster Number.
VCN 0 (zero) refers to the first cluster of the stream.
*************
*** INDEX ***
*************
* Under NTFS every object on the volume is a file, even directories.
A directory is a sequence of Index Entries containing a filename Attribute.
An Index Entry is created for each $FILE_NAME Attribute
of each file contained in the folder.
* $INDEX_ROOT Attribute is the root node of the B+ tree that implements
an index (e.g. a directory). This file attribute is always resident.
* Every Index Record has a standard header and a set of blocks containing
an Index Key and Index Data. The size of an Index Record is defined
in $Boot File and always seems to be 4KB.
* $INDEX_ALLOCATION Attribute is the storage location for all sub-nodes
of the B+ tree that implements an index (e.g. a directory). It is simply a
sequence of all Index Records that belong to the Index.
This file attribute is never resident.
* A sequence of Index Entries follows each INDEX_HEADER structure.
Together they make up a complete index. The index follows either
an $INDEX_ROOT Attribute or an $INDEX_ALLOCATION Attribute.
***************
*** RUNLIST ***
***************
* Runlist is a table written in the content part of a non-resident file Attribute,
which allows to have access to its stream. (Table 4.10.)
* The Runlist is a sequence of elements: each element stores an offset to the
starting LCN of the previous element and the length in clusters of a Data Run.
* The layout of the Runlist must take account of the data compression:
the set of VCNs containing the stream of a compressed file attribute
is divided in compression units (also called chunks) of 16 clusters.
* Data are compressed using a modified LZ77 algorithm.
The basic idea is that substrings of the block which have been seen before
are compressed by referencing the string rather than mentioning it again.
* Only $DATA Attribute can be compressed, or sparse, and only when it is non-resident.
**/
///
/// Table 2.1. Standard NTFS Attribute Types
///
enum {
AT_STANDARD_INFORMATION = 0x10,
AT_ATTRIBUTE_LIST = 0x20,
AT_FILENAME = 0x30,
AT_VOLUME_VERSION = 0x40,
AT_SECURITY_DESCRIPTOR = 0x50,
AT_VOLUME_NAME = 0x60,
AT_VOLUME_INFORMATION = 0x70,
AT_DATA = 0x80,
AT_INDEX_ROOT = 0x90,
AT_INDEX_ALLOCATION = 0xA0,
AT_BITMAP = 0xB0,
AT_SYMLINK = 0xC0,
AT_EA_INFORMATION = 0xD0,
AT_EA = 0xE0,
AT_PROPERTY_SET = 0xF0,
};
///
/// Table 2.6. File Flags (Also called attributes in DOS terminology).
///
enum {
ATTR_READ_ONLY = 0x1,
ATTR_HIDDEN = 0x2,
ATTR_SYSTEM = 0x4,
ATTR_ARCHIVE = 0x20,
ATTR_DEVICE = 0x40,
ATTR_NORMAL = 0x80,
ATTR_TEMPORARY = 0x100,
ATTR_SPARSE = 0x200,
ATTR_REPARSE = 0x400,
ATTR_COMPRESSED = 0x800,
ATTR_OFFLINE = 0x1000,
ATTR_NOT_INDEXED = 0x2000,
ATTR_ENCRYPTED = 0x4000,
ATTR_DIRECTORY = 0x10000000,
ATTR_INDEX_VIEW = 0x20000000
};
///
/// Table 3.1. Layout of files on the Volume (Inodes).
///
enum {
MFT_FILE = 0,
MFTMIRR_FILE = 1,
LOGFILE_FILE = 2,
VOLUME_FILE = 3,
ATTRDEF_FILE = 4,
ROOT_FILE = 5,
BITMAP_FILE = 6,
BOOT_FILE = 7,
BADCLUS_FILE = 8,
QUOTA_FILE = 9,
UPCASE_FILE = 10,
};
enum {
NTFS_AF_ALST = 1,
NTFS_AF_MFT_FILE = 2,
NTFS_AF_GPOS = 4,
};
///
/// Table 4.6. Attribute flags
///
enum {
FLAG_COMPRESSED = 0x0001,
FLAG_ENCRYPTED = 0x4000,
FLAG_SPARSE = 0x8000
};
///
/// Table 4.22. File record flags
///
enum {
IS_IN_USE = 0x01,
IS_A_DIRECTORY = 0x02,
};
///
/// Table 2.30. Data entry flags
///
enum {
SUB_NODE = 0x01,
LAST_INDEX_ENTRY = 0x02,
};
///
/// 13.2. Possible Namespaces
///
enum {
POSIX = 0,
WINDOWS32 = 1,
DOS = 2,
WIN32_DOS = 3,
};
///
/// Table 2.36. Reparse Tag Flags
///
enum {
IS_ALIAS = 0x20000000,
IS_HIGH_LATENCY = 0x40000000,
IS_MICROSOFT = 0x80000000,
NSS = 0x68000005,
NSS_RECOVER = 0x68000006,
SIS = 0x68000007,
DFS = 0x68000008,
MOUNT_POINT = 0x88000003,
HSM = 0xA8000004,
SYMBOLIC_LINK = 0xE8000000,
};
#pragma pack(1)
/**
Table 4.21. Layout of a File Record
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 4 | Magic number 'FILE'
0x04 | 2 | Offset to the update sequence
0x06 | 2 | Size in words of Update Sequence Number & Array (S)
0x08 | 8 | $LogFile Sequence Number (LSN)
0x10 | 2 | Sequence number
0x12 | 2 | Hard link count
0x14 | 2 | Offset to the first Attribute
0x16 | 2 | Flags
0x18 | 4 | Real size of the FILE record
0x1C | 4 | Allocated size of the FILE record (a multiple of the Cluster size)
0x20 | 8 | File reference to the base FILE record
0x28 | 2 | Next Attribute Id
| 2 | Update Sequence Number (a)
| 2S-2 | Update Sequence Array (a)
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Magic;
UINT16 UpdateSequenceOffset;
UINT16 S_Size;
UINT64 LSN;
UINT16 SequenceNumber;
UINT16 HardLinkCount;
UINT16 AttributeOffset;
UINT16 Flags;
UINT32 RealSize;
UINT32 AllocatedSize;
UINT64 BaseFileRecord;
UINT16 NextAttributeId;
} FILE_RECORD_HEADER;
/**
Table 4.3. Layout of a resident named Attribute Header
____________________________________________________________________
Offset | Size | Value | Description
--------------------------------------------------------------------
0x00 | 4 | | Attribute Type (e.g. 0x90, 0xB0)
0x04 | 4 | | Length (including this header)
0x08 | 1 | 0x00 | Non-resident flag
0x09 | 1 | N | Name length
0x0A | 2 | 0x18 | Offset to the Name
0x0C | 2 | 0x00 | Flags
0x0E | 2 | | Attribute Id (a)
0x10 | 4 | L | Length of the Attribute
0x14 | 2 |2N+0x18| Offset to the Attribute (b)
0x16 | 1 | | Indexed flag
0x17 | 1 | 0x00 | Padding
0x18 | 2N |Unicode| The Attribute's Name
2N+0x18| L | | The Attribute (c)
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Type;
UINT32 Length;
UINT8 NonResFlag;
UINT8 NameLength;
UINT16 NameOffset;
UINT16 Flags;
UINT16 AttributeId;
UINT32 InfoLength;
UINT16 InfoOffset;
UINT8 IndexedFlag;
UINT8 Padding;
} ATTR_HEADER_RES;
/**
Table 4.5. Layout of a non-resident named Attribute Header
____________________________________________________________________
Offset | Size | Value | Description
--------------------------------------------------------------------
0x00 | 4 | | Attribute Type (e.g. 0x80, 0xA0)
0x04 | 4 | | Length (including this header)
0x08 | 1 | 0x01 | Non-resident flag
0x09 | 1 | N | Name length
0x0A | 2 | 0x40 | Offset to the Name
0x0C | 2 | | Flags
0x0E | 2 | | Attribute Id (a)
0x10 | 8 | | Starting VCN
0x18 | 8 | | Last VCN
0x20 | 2 |2N+0x40| Offset to the Data Runs (b)
0x22 | 2 | | Compression Unit Size (c)
0x24 | 4 | 0x00 | Padding
0x28 | 8 | | Allocated size of the attribute (a multiple of the Cluster size)
0x30 | 8 | | Real size of the attribute
0x38 | 8 | | Initialized data size of the stream (e)
0x40 | 2N |Unicode| The Attribute's Name
2N+0x40| ... | | Data Runs (b)
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Type;
UINT32 Length;
UINT8 NonResFlag;
UINT8 NameLength;
UINT16 NameOffset;
UINT16 Flags;
UINT16 AttributeId;
UINT64 StartingVCN;
UINT64 LastVCN;
UINT16 DataRunsOffset;
UINT16 CompressionUnitSize;
UINT32 Padding;
UINT64 AllocatedSize;
UINT64 RealSize;
UINT64 InitializedDataSize;
} ATTR_HEADER_NONRES;
/**
Table 2.4. Layout of the $ATTRIBUTE_LIST (0x20) attribute
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
~ | ~ | Standard Attribute Header
0x00 | 4 | Type
0x04 | 2 | Record length
0x06 | 1 | Name length (N)
0x07 | 1 | Offset to Name (a)
0x08 | 8 | Starting VCN (b)
0x10 | 8 | Base File Reference of the attribute (Table 4.23)
0x18 | 2 | Attribute Id (c)
0x1A | 2N | Name in Unicode (if N > 0)
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Type;
UINT16 RecordLength;
UINT8 NameLength;
UINT8 NameOffset;
UINT64 StartingVCN;
UINT64 BaseFileReference;
UINT16 AttributeId;
} ATTR_LIST_RECORD;
/**
* Table 4.23. Layout of a file reference
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 6 | FILE record number
0x06 | 2 | Sequence number
--------------------------------------------------------------------
* Table 2.29. Index Entry
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 8 | File reference
0x08 | 2 | Length of the index entry (L)
0x0A | 2 | Length of the stream (S)
0x0C | 1 | Flags
--------------------------------------------------------------------
The next field is only present when the last entry flag is not set
--------------------------------------------------------------------
0x10 | S | Stream - A copy of the body of the indexed attribute
0x10+S | ? | Align 8
--------------------------------------------------------------------
The next field is only present when the sub-node flag is set
--------------------------------------------------------------------
L-8 | 8 | VCN of the sub-node in the Index Allocation
--------------------------------------------------------------------
**/
typedef struct {
UINT8 FileRecordNumber[6];
UINT16 SequenceNumber;
UINT16 IndexEntryLength;
UINT16 StreamLength;
UINT8 Flags;
UINT8 Padding[3];
} INDEX_ENTRY;
/**
Table 4.26. Layout of a Standard Index Header
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 4 | Magic number 'INDX'
0x04 | 2 | Offset to the Update Sequence
0x06 | 2 | Size in words of the Update Sequence (S)
0x08 | 8 | $LogFile sequence number
0x10 | 8 | VCN of this Index record in the Index Allocation
0x18 | 4 | Offset to the Index Entries (a)
0x1C | 4 | Size of Index Entries (a)
0x20 | 4 | Allocated size of the Index Entries (a)
0x24 | 1 | 1 if not leaf node (b)
0x25 | 3 | Padding (always zero)
0x28 | 2 | Update sequence
0x2A | 2S-2 | Update sequence array
X | Y | Index Entry
X+Y | Z | Next Index Entry
~ | ~ | ...
--------------------------------------------------------------------
(a) These values are relative to 0x18
**/
typedef struct {
UINT32 Magic;
UINT16 UpdateSequenceOffset;
UINT16 S_Size;
UINT64 LSN;
UINT64 IndexRecordVCN;
} INDEX_HEADER;
typedef struct {
INDEX_HEADER Header;
UINT32 IndexEntriesOffset;
UINT32 IndexEntriesSize;
UINT32 IndexEntriesAllocated;
UINT8 IsNotLeafNode;
UINT8 Padding[3];
} INDEX_RECORD_HEADER;
/**
* Table 2.24. Layout of the $INDEX_ROOT (0x90) Attribute (Index Root)
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
~ | ~ | Standard Attribute Header
0x00 | 4 | Attribute Type
0x04 | 4 | Collation Rule
0x08 | 4 | Size of Index Allocation Entry (bytes)
0x0C | 1 | Clusters per Index Record
0x0D | 3 | Padding (Align to 8 bytes)
--------------------------------------------------------------------
* Table 2.25. Layout of the $INDEX_ROOT (0x90) Attribute (Index Header)
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 4 | Offset to first Index Entry
0x04 | 4 | Total size of the Index Entries
0x08 | 4 | Allocated size of the Index Entries
0x0C | 1 | Flags
0x0D | 3 | Padding (align to 8 bytes)
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Type;
UINT32 CollationRule;
UINT32 IndexAllocationSize;
UINT8 IndexRecordClusters;
UINT8 Padding[3];
} INDEX_ROOT;
typedef struct {
INDEX_ROOT Root;
UINT32 FirstEntryOffset;
UINT32 EntriesTotalSize;
UINT32 EntriesAllocatedSize;
UINT8 Flags;
UINT8 Padding[3];
} ATTR_INDEX_ROOT;
/**
Table 2.5. Layout of the $FILE_NAME (0x30) attribute
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
~ | ~ | Standard Attribute Header
0x00 | 8 | File reference to the base record of the parent directory.
0x08 | 8 | C Time - File Creation
0x10 | 8 | A Time - File Altered
0x18 | 8 | M Time - MFT Changed
0x20 | 8 | R Time - File Read
0x28 | 8 | Allocated size of the file (a multiple of the Cluster size)
0x30 | 8 | Real size of the file (size of the unnamed $Data Attribute)
0x38 | 4 | Flags, e.g. Directory, compressed, hidden
0x3c | 4 | Used by EAs and Reparse
0x40 | 1 | Filename length in characters (L)
0x41 | 1 | Filename namespace
0x42 | 2L | File name in Unicode (not null terminated)
--------------------------------------------------------------------
**/
typedef struct {
UINT64 ParentDir;
UINT64 CreationTime;
UINT64 AlteredTime;
UINT64 ChangedMftTime;
UINT64 ReadTime;
UINT64 AllocatedSize;
UINT64 RealSize;
UINT32 Flags;
UINT32 Reparse;
UINT8 FilenameLen;
UINT8 Namespace;
} ATTR_FILE_NAME;
/**
Table 3.19. Layout of the $Boot File's $DATA Attribute
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x0000 | 3 | Jump to the boot loader routine
0x0003 | 8 | System Id: "NTFS "
0x000B | 2 | Bytes per sector
0x000D | 1 | Sectors per cluster
0x000E | 7 | Unused
0x0015 | 1 | Media descriptor (a)
0x0016 | 2 | Unused
0x0018 | 2 | Sectors per track
0x001A | 2 | Number of heads
0x001C | 8 | Unused
0x0024 | 4 | Usually 80 00 80 00 (b)
0x0028 | 8 | Number of sectors in the volume
0x0030 | 8 | LCN of VCN 0 of the $MFT
0x0038 | 8 | LCN of VCN 0 of the $MFTMirr
0x0040 | 4 | Clusters per MFT Record (c)
0x0044 | 4 | Clusters per Index Record (c)
0x0048 | 8 | Volume serial number
~ | ~ | ~
0x0200 | | Windows NT Loader
--------------------------------------------------------------------
**/
typedef struct {
UINT8 BootLoaderJump[3];
UINT32 SystemId[2];
UINT16 BytesPerSector;
UINT8 SectorsPerCluster;
UINT8 Unused1[7];
UINT8 MediaDescriptor;
UINT16 Unused2;
UINT16 SectorsPerTrack;
UINT16 HeadsNumber;
UINT64 Unused3;
UINT32 Usually;
UINT64 VolumeSectorsNumber;
UINT64 MftLcn;
UINT64 MftMirrLcn;
INT8 MftRecordClusters;
UINT8 Unused4[3];
INT8 IndexRecordClusters;
UINT8 Unused5[3];
UINT64 VolumeSerialNumber;
} BOOT_FILE_DATA;
/**
* Table 2.32. Layout of the $REPARSE_POINT (0xC0) attribute (Microsoft Reparse Point)
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
~ | ~ | Standard Attribute Header
0x00 | 4 | Reparse Type (and Flags)
0x04 | 2 | Reparse Data Length
0x06 | 2 | Padding (align to 8 bytes)
0x08 | V | Reparse Data (a)
--------------------------------------------------------------------
* Table 2.34. Symbolic Link Reparse Data
____________________________________________________________________
Offset | Size | Description
--------------------------------------------------------------------
0x00 | 2 | Substitute Name Offset
0x02 | 2 | Substitute Name Length
0x04 | 2 | Print Name Offset
0x08 | 2 | Print Name Length
0x10 | V | Path Buffer
--------------------------------------------------------------------
**/
typedef struct {
UINT32 Type;
UINT16 DataLength;
UINT16 Padding;
UINT16 SubstituteOffset;
UINT16 SubstituteLength;
UINT16 PrintOffset;
UINT16 PrintLength;
} SYMLINK;
#pragma pack()
typedef struct _NTFS_FILE NTFS_FILE;
typedef struct _EFI_NTFS_FILE EFI_NTFS_FILE;
typedef struct _EFI_FS EFI_FS;
typedef struct {
INT32 Flags;
UINT8 *ExtensionMftRecord;
UINT8 *NonResAttrList;
UINT8 *Current;
UINT8 *Next;
UINT8 *Last;
NTFS_FILE *BaseMftRecord;
} NTFS_ATTR;
typedef struct _NTFS_FILE {
UINT8 *FileRecord;
UINT64 DataAttributeSize;
UINT64 CreationTime;
UINT64 AlteredTime;
UINT64 ReadTime;
UINT64 Inode;
BOOLEAN InodeRead;
NTFS_ATTR Attr;
EFI_NTFS_FILE *File;
} NTFS_FILE;
typedef struct _EFI_NTFS_FILE {
EFI_FILE_PROTOCOL EfiFile;
BOOLEAN IsDir;
UINT64 DirIndex;
CHAR16 *Path;
CHAR16 *BaseName;
UINT64 Offset;
UINT32 RefCount;
NTFS_FILE RootFile;
NTFS_FILE MftFile;
EFI_FS *FileSystem;
} EFI_NTFS_FILE;
typedef struct _EFI_FS {
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL FileIoInterface;
EFI_FILE_PROTOCOL EfiFile;
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_DISK_IO_PROTOCOL *DiskIo;
EFI_DEVICE_PATH *DevicePath;
UINT64 FirstMftRecord;
NTFS_FILE *RootIndex;
NTFS_FILE *MftStart;
UINTN FileRecordSize;
UINTN IndexRecordSize;
UINTN SectorSize;
UINTN ClusterSize;
} EFI_FS;
typedef struct {
UINT64 Vcn;
UINT64 Lcn;
} UNIT_ELEMENT;
typedef struct {
EFI_FS *FileSystem;
UINT8 Head;
UINT8 Tail;
UNIT_ELEMENT Elements[16];
UINT64 CurrentVcn;
UINT8 *Cluster;
UINTN ClusterOffset;
UINT64 SavedPosition;
UINT8 *ClearTextBlock;
} COMPRESSED;
typedef struct {
BOOLEAN IsSparse;
UINT64 TargetVcn;
UINT64 CurrentVcn;
UINT64 CurrentLcn;
UINT64 NextVcn;
UINT8 *NextDataRun;
NTFS_ATTR *Attr;
COMPRESSED Unit;
} RUNLIST;
#endif // DRIVER_H

View File

@ -0,0 +1,205 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#ifndef HELPER_H
#define HELPER_H
#include "Driver.h"
#define FSHELP_TYPE_MASK 0xff
#define FSHELP_CASE_INSENSITIVE 0x100
//
// Leap year: is every 4 years, except every 100th isn't, and every 400th is.
//
#define LEAP_YEAR ((Year % 400U == 0) || ((Year % 4U == 0) && (Year % 100U != 0)))
#define YEAR_IN_100NS (365ULL * DAY_IN_100NS)
#define DAY_IN_100NS (24ULL * HOUR_IN_100NS)
#define HOUR_IN_100NS (60ULL * MINUTE_IN_100NS)
#define MINUTE_IN_100NS (60U * SECOND_IN_100NS)
#define SECOND_IN_100NS 10000000U
#define UNIT_IN_NS 100U
#define GREGORIAN_START 1601U
typedef enum {
FSHELP_UNKNOWN,
FSHELP_REG,
FSHELP_DIR,
FSHELP_SYMLINK
} FSHELP_FILETYPE;
typedef struct _STACK_ELEMENT {
struct _STACK_ELEMENT *Parent;
NTFS_FILE *Node;
FSHELP_FILETYPE Type;
} STACK_ELEMENT;
typedef struct {
CONST CHAR16 *Path;
NTFS_FILE *RootNode;
UINT32 SymlinkDepth;
STACK_ELEMENT *CurrentNode;
} FSHELP_CTX;
typedef struct {
CONST CHAR16 *Name;
NTFS_FILE **FoundNode;
FSHELP_FILETYPE *FoundType;
} FSHELP_ITER_CTX;
typedef enum {
INFO_HOOK,
DIR_HOOK,
FILE_ITER,
} FUNCTION_TYPE;
VOID
FreeAttr (
IN NTFS_ATTR *Attr
);
VOID
FreeFile (
IN NTFS_FILE *File
);
EFI_STATUS
EFIAPI
DiskRead (
IN EFI_FS *FileSystem,
IN UINT64 Offset,
IN UINTN Size,
IN OUT VOID *Buffer
);
EFI_STATUS
EFIAPI
ReadMftRecord (
IN EFI_NTFS_FILE *File,
OUT UINT8 *Buffer,
IN UINT64 RecordNumber
);
EFI_STATUS
EFIAPI
ReadAttr (
IN NTFS_ATTR *NtfsAttr,
OUT UINT8 *Dest,
IN UINT64 Offset,
IN UINTN Length
);
EFI_STATUS
EFIAPI
ReadData (
IN NTFS_ATTR *NtfsAttr,
IN UINT8 *pa,
OUT UINT8 *Dest,
IN UINT64 Offset,
IN UINTN Length
);
EFI_STATUS
EFIAPI
ReadRunListElement (
IN OUT RUNLIST *Runlist
);
EFI_STATUS
NtfsDir (
IN EFI_FS *FileSystem,
IN CONST CHAR16 *Path,
OUT EFI_NTFS_FILE *File,
IN FUNCTION_TYPE FunctionType
);
EFI_STATUS
NtfsOpen (
IN EFI_NTFS_FILE *File
);
EFI_STATUS
NtfsMount (
IN EFI_FS *FileSystem
);
EFI_STATUS
EFIAPI
Fixup (
IN UINT8 *Buffer,
IN UINT64 Length,
IN UINT32 Magic,
IN UINTN SectorSize
);
EFI_STATUS
InitAttr (
OUT NTFS_ATTR *Attr,
IN NTFS_FILE *File
);
UINT8 *
LocateAttr (
IN NTFS_ATTR *Attr,
IN NTFS_FILE *Mft,
IN UINT32 Type
);
UINT8 *
FindAttr (
IN NTFS_ATTR *Attr,
IN UINT32 Type
);
EFI_STATUS
EFIAPI
InitFile (
IN OUT NTFS_FILE *File,
IN UINT64 RecordNumber
);
EFI_STATUS
FsHelpFindFile (
IN CONST CHAR16 *Path,
IN NTFS_FILE *RootNode,
OUT NTFS_FILE **FoundNode,
IN FSHELP_FILETYPE Type
);
EFI_STATUS
IterateDir (
IN NTFS_FILE *dir,
IN VOID *FileOrCtx,
IN FUNCTION_TYPE FunctionType
);
EFI_STATUS
RelativeToAbsolute (
OUT CHAR16 *Dest,
IN CHAR16 *Source
);
CHAR16 *
ReadSymlink (
IN NTFS_FILE *Node
);
EFI_STATUS
Decompress (
IN RUNLIST *Runlist,
IN UINT64 Offset,
IN UINTN Length,
OUT UINT8 *Dest
);
VOID
NtfsToEfiTime (
EFI_TIME *EfiTime,
UINT64 NtfsTime
);
#endif // HELPER_H

View File

@ -0,0 +1,998 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
STATIC UINT64 mBufferSize;
STATIC UINT8 mDaysPerMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 };
INT64 mIndexCounter;
STATIC
VOID
FreeNode (
IN OUT NTFS_FILE *Node OPTIONAL,
IN FSHELP_CTX *Context
)
{
ASSERT (Context != NULL);
if ((Node != NULL) && (Node != Context->RootNode)) {
FreeFile (Node);
FreePool (Node);
}
}
STATIC
VOID
PopElement (
IN OUT FSHELP_CTX *Context
)
{
STACK_ELEMENT *Element;
ASSERT (Context != NULL);
Element = Context->CurrentNode;
if (Element != NULL) {
Context->CurrentNode = Element->Parent;
FreeNode (Element->Node, Context);
FreePool (Element);
}
}
STATIC
VOID
FreeStack (
IN OUT FSHELP_CTX *Context
)
{
ASSERT (Context != NULL);
while (Context->CurrentNode != NULL) {
PopElement (Context);
}
}
STATIC
VOID
GoUpALevel (
IN OUT FSHELP_CTX *Context
)
{
ASSERT (Context != NULL);
if (Context->CurrentNode->Parent == NULL) {
return;
}
PopElement (Context);
}
STATIC
EFI_STATUS
PushNode (
IN OUT FSHELP_CTX *Context,
IN NTFS_FILE *Node,
IN FSHELP_FILETYPE FileType
)
{
STACK_ELEMENT *Next;
ASSERT (Context != NULL);
ASSERT (Node != NULL);
Next = AllocateZeroPool (sizeof (*Next));
if (Next == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Next->Node = Node;
Next->Type = FileType & ~FSHELP_CASE_INSENSITIVE;
Next->Parent = Context->CurrentNode;
Context->CurrentNode = Next;
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
GoToRoot (
IN OUT FSHELP_CTX *Context
)
{
ASSERT (Context != NULL);
FreeStack (Context);
return PushNode (Context, Context->RootNode, FSHELP_DIR);
}
STATIC
EFI_STATUS
FindFileIter (
IN CHAR16 *Name,
IN FSHELP_FILETYPE FileType,
IN NTFS_FILE *Node,
IN FSHELP_ITER_CTX *Context
)
{
INTN Result;
ASSERT (Name != NULL);
ASSERT (Node != NULL);
ASSERT (Context != NULL);
if (FileType == FSHELP_UNKNOWN) {
FreePool (Node);
return EFI_NOT_FOUND;
}
if ((FileType & FSHELP_CASE_INSENSITIVE) != 0) {
Result = OcStriCmp (Context->Name, Name);
} else {
Result = StrCmp (Context->Name, Name);
}
if (Result != 0) {
FreePool (Node);
return EFI_NOT_FOUND;
}
*Context->FoundNode = Node;
*Context->FoundType = FileType;
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
NtfsDirHook (
IN CHAR16 *Name,
IN FSHELP_FILETYPE FileType,
IN NTFS_FILE *Node,
IN OUT EFI_FILE_INFO *Info
)
{
EFI_STATUS Status;
ASSERT (Name != NULL);
ASSERT (Node != NULL);
ASSERT (Info != NULL);
if ( (Name[0] == L'.')
&& ((Name[1] == 0) || ( (Name[1] == L'.')
&& (Name[2] == 0))))
{
FreeFile (Node);
return EFI_NOT_FOUND;
}
if ((mIndexCounter)-- != 0) {
FreeFile (Node);
return EFI_NOT_FOUND;
}
Status = StrnCpyS (
Info->FileName,
MAX_PATH,
Name,
(UINTN)((Info->Size - sizeof (*Info)) / sizeof (CHAR16))
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not copy string.\n"));
FreeFile (Node);
return Status;
}
Info->Size = sizeof (*Info) + StrnLenS (Info->FileName, MAX_PATH) * sizeof (CHAR16);
NtfsToEfiTime (&Info->CreateTime, Node->CreationTime);
NtfsToEfiTime (&Info->LastAccessTime, Node->ReadTime);
NtfsToEfiTime (&Info->ModificationTime, Node->AlteredTime);
Info->Attribute = EFI_FILE_READ_ONLY;
if ((FileType & FSHELP_TYPE_MASK) == FSHELP_DIR) {
Info->Attribute |= EFI_FILE_DIRECTORY;
}
FreeFile (Node);
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
NtfsDirIter (
IN CHAR16 *FileName,
IN FSHELP_FILETYPE FileType,
IN NTFS_FILE *Node,
IN EFI_NTFS_FILE *File
)
{
ASSERT (FileName != NULL);
ASSERT (Node != NULL);
ASSERT (File != NULL);
if (StrCmp (FileName, File->BaseName) != 0) {
FreeFile (Node);
return EFI_NOT_FOUND;
}
File->IsDir = (BOOLEAN)((FileType & FSHELP_TYPE_MASK) == FSHELP_DIR);
FreeFile (Node);
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
ListFile (
IN NTFS_FILE *Dir,
IN UINT8 *Position,
OUT VOID *FileOrCtx,
IN FUNCTION_TYPE FunctionType
)
{
EFI_STATUS Status;
CHAR16 *Filename;
FSHELP_FILETYPE Type;
NTFS_FILE *DirFile;
INDEX_ENTRY *IndexEntry;
ATTR_FILE_NAME *AttrFileName;
ASSERT (Dir != NULL);
ASSERT (Position != NULL);
ASSERT (FileOrCtx != NULL);
IndexEntry = (INDEX_ENTRY *)Position;
while (TRUE) {
if (mBufferSize < sizeof (*IndexEntry)) {
DEBUG ((DEBUG_INFO, "NTFS: (ListFile #1) INDEX_ENTRY is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
if ((IndexEntry->Flags & LAST_INDEX_ENTRY) != 0) {
break;
}
if (mBufferSize < (sizeof (*IndexEntry) + sizeof (*AttrFileName))) {
DEBUG ((DEBUG_INFO, "NTFS: (ListFile #2) INDEX_ENTRY is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
AttrFileName = (ATTR_FILE_NAME *)((UINT8 *)IndexEntry + sizeof (*IndexEntry));
//
// Ignore files in DOS namespace, as they will reappear as Win32 names.
//
if ((AttrFileName->FilenameLen != 0) && (AttrFileName->Namespace != DOS)) {
if ((AttrFileName->Flags & ATTR_REPARSE) != 0) {
Type = FSHELP_SYMLINK;
} else if ((AttrFileName->Flags & ATTR_DIRECTORY) != 0) {
Type = FSHELP_DIR;
} else {
Type = FSHELP_REG;
}
DirFile = AllocateZeroPool (sizeof (*DirFile));
if (DirFile == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Could not allocate space for DirFile\n"));
return EFI_OUT_OF_RESOURCES;
}
DirFile->File = Dir->File;
CopyMem (&DirFile->Inode, IndexEntry->FileRecordNumber, 6U);
DirFile->CreationTime = AttrFileName->CreationTime;
DirFile->AlteredTime = AttrFileName->AlteredTime;
DirFile->ReadTime = AttrFileName->ReadTime;
if (mBufferSize < (sizeof (*IndexEntry) + sizeof (*AttrFileName) + AttrFileName->FilenameLen * sizeof (CHAR16))) {
DEBUG ((DEBUG_INFO, "NTFS: (ListFile #3) INDEX_ENTRY is corrupted.\n"));
FreePool (DirFile);
return EFI_VOLUME_CORRUPTED;
}
Filename = AllocateZeroPool ((AttrFileName->FilenameLen + 1U) * sizeof (CHAR16));
if (Filename == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Failed to allocate buffer for Filename\n"));
FreePool (DirFile);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (
Filename,
(UINT8 *)AttrFileName + sizeof (*AttrFileName),
AttrFileName->FilenameLen * sizeof (CHAR16)
);
if (AttrFileName->Namespace != POSIX) {
Type |= FSHELP_CASE_INSENSITIVE;
}
switch (FunctionType) {
case INFO_HOOK:
Status = NtfsDirIter (Filename, Type, DirFile, FileOrCtx);
FreePool (DirFile);
break;
case DIR_HOOK:
Status = NtfsDirHook (Filename, Type, DirFile, FileOrCtx);
FreePool (DirFile);
break;
case FILE_ITER:
Status = FindFileIter (Filename, Type, DirFile, FileOrCtx);
break;
}
FreePool (Filename);
if (Status == EFI_SUCCESS) {
return EFI_SUCCESS;
}
}
if ( (mBufferSize < IndexEntry->IndexEntryLength)
|| (IndexEntry->IndexEntryLength == 0))
{
DEBUG ((DEBUG_INFO, "NTFS: (ListFile #4) INDEX_ENTRY is corrupted.\n"));
return EFI_VOLUME_CORRUPTED;
}
mBufferSize -= IndexEntry->IndexEntryLength;
IndexEntry = (INDEX_ENTRY *)((UINT8 *)IndexEntry + IndexEntry->IndexEntryLength);
}
return EFI_NOT_FOUND;
}
STATIC
EFI_STATUS
FindFile (
IN CHAR16 *CurrentPath,
IN FSHELP_CTX *Context
)
{
EFI_STATUS Status;
CHAR16 *Name;
CHAR16 *Next;
NTFS_FILE *FoundNode;
FSHELP_FILETYPE FoundType;
FSHELP_ITER_CTX IterCtx;
CHAR16 *Symlink;
CHAR16 *PathPart;
ASSERT (CurrentPath != NULL);
ASSERT (Context != NULL);
for (Name = CurrentPath; ; Name = Next) {
FoundNode = NULL;
FoundType = FSHELP_UNKNOWN;
while (*Name == L'/') {
++Name;
}
if (*Name == L'\0') {
return EFI_SUCCESS;
}
for (Next = Name; (*Next != L'\0') && (*Next != L'/'); ++Next) {
//
// Search for L'\0' or L'/'.
//
}
if (Context->CurrentNode->Type != FSHELP_DIR) {
DEBUG ((DEBUG_INFO, "NTFS: Not a directory\n"));
return EFI_INVALID_PARAMETER;
}
if ((Next - Name == 1U) && (Name[0] == L'.')) {
continue;
}
if ((Next - Name == 2U) && (Name[0] == L'.') && (Name[1] == L'.')) {
GoUpALevel (Context);
continue;
}
PathPart = AllocateZeroPool ((Next - Name + 1U) * sizeof (CHAR16));
if (PathPart == NULL) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (PathPart, Name, (Next - Name) * sizeof (CHAR16));
IterCtx.Name = PathPart;
IterCtx.FoundNode = &FoundNode;
IterCtx.FoundType = &FoundType;
Status = IterateDir (Context->CurrentNode->Node, &IterCtx, FILE_ITER);
FreePool (PathPart);
if (EFI_ERROR (Status)) {
return Status;
}
if (FoundNode == NULL) {
break;
}
Status = PushNode (Context, FoundNode, FoundType);
if (EFI_ERROR (Status)) {
return Status;
}
if (Context->CurrentNode->Type == FSHELP_SYMLINK) {
if (++Context->SymlinkDepth == 8U) {
DEBUG ((DEBUG_INFO, "NTFS: Too deep nesting of symlinks\n"));
return EFI_INVALID_PARAMETER;
}
Symlink = ReadSymlink (Context->CurrentNode->Node);
if (Symlink == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Symlink leeds nowhere\n"));
return EFI_INVALID_PARAMETER;
}
if (Symlink[0] == L'/') {
//
// Symlink is an absolute path
//
Status = GoToRoot (Context);
if (EFI_ERROR (Status)) {
return Status;
}
} else {
GoUpALevel (Context);
}
Status = FindFile (Symlink, Context);
FreePool (Symlink);
if (EFI_ERROR (Status)) {
return Status;
}
}
}
DEBUG ((DEBUG_INFO, "NTFS: File `%s' not found\n", IterCtx.Name));
return EFI_NOT_FOUND;
}
EFI_STATUS
FsHelpFindFile (
IN CONST CHAR16 *Path,
IN NTFS_FILE *RootNode,
OUT NTFS_FILE **FoundNode,
IN FSHELP_FILETYPE Type
)
{
EFI_STATUS Status;
FSHELP_CTX Context;
FSHELP_FILETYPE FoundType;
ASSERT (Path != NULL);
ASSERT (RootNode != NULL);
ASSERT (FoundNode != NULL);
Context.Path = Path;
Context.RootNode = RootNode;
Context.SymlinkDepth = 0;
Context.CurrentNode = NULL;
if (Path[0] != L'/') {
DEBUG ((DEBUG_INFO, "NTFS: Invalid file name `%s'\n", Path));
return EFI_INVALID_PARAMETER;
}
Status = GoToRoot (&Context);
if (EFI_ERROR (Status)) {
return Status;
}
Status = FindFile ((CHAR16 *)Path, &Context);
if (EFI_ERROR (Status)) {
FreeStack (&Context);
return Status;
}
*FoundNode = Context.CurrentNode->Node;
FoundType = Context.CurrentNode->Type;
//
// Do not free the node
//
Context.CurrentNode->Node = NULL;
FreeStack (&Context);
if (FoundType != Type) {
if (*FoundNode != RootNode) {
FreeFile (*FoundNode);
FreePool (*FoundNode);
}
if (Type == FSHELP_REG) {
DEBUG ((DEBUG_INFO, "NTFS: Not a regular file\n"));
} else if (Type == FSHELP_DIR) {
DEBUG ((DEBUG_INFO, "NTFS: Not a directory\n"));
}
Status = EFI_VOLUME_CORRUPTED;
}
return Status;
}
EFI_STATUS
IterateDir (
IN NTFS_FILE *Dir,
IN VOID *FileOrCtx,
IN FUNCTION_TYPE FunctionType
)
{
EFI_STATUS Status;
NTFS_ATTR Attr;
ATTR_HEADER_RES *Res;
ATTR_HEADER_NONRES *Non;
ATTR_INDEX_ROOT *Index;
INDEX_RECORD_HEADER *IndexRecord;
UINT8 *BitIndex;
UINT8 *BitMap;
UINTN BitMapLen;
UINT8 Bit;
UINTN Number;
UINTN FileRecordSize;
UINTN IndexRecordSize;
ASSERT (Dir != NULL);
ASSERT (FileOrCtx != NULL);
FileRecordSize = Dir->File->FileSystem->FileRecordSize;
IndexRecordSize = Dir->File->FileSystem->IndexRecordSize;
if (!Dir->InodeRead) {
Status = InitFile (Dir, Dir->Inode);
if (EFI_ERROR (Status)) {
return Status;
}
}
IndexRecord = NULL;
BitMap = NULL;
Status = InitAttr (&Attr, Dir);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Search in $INDEX_ROOT
//
while (TRUE) {
Res = (ATTR_HEADER_RES *)FindAttr (&Attr, AT_INDEX_ROOT);
if (Res == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: no $INDEX_ROOT\n"));
FreeAttr (&Attr);
return EFI_VOLUME_CORRUPTED;
}
mBufferSize = FileRecordSize - (Attr.Current - Attr.BaseMftRecord->FileRecord);
if ( (mBufferSize < sizeof (*Res))
|| (mBufferSize < (Res->NameOffset + 8U))
|| (mBufferSize < Res->InfoOffset))
{
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #1) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
return EFI_VOLUME_CORRUPTED;
}
mBufferSize -= Res->InfoOffset;
if ( (Res->NonResFlag != 0)
|| (Res->NameLength != 4U)
|| (Res->NameOffset != sizeof (*Res))
|| (CompareMem ((UINT8 *)Res + Res->NameOffset, L"$I30", 8U) != 0))
{
continue;
}
if (mBufferSize < sizeof (*Index)) {
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #1.1) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
return EFI_VOLUME_CORRUPTED;
}
Index = (ATTR_INDEX_ROOT *)((UINT8 *)Res + Res->InfoOffset);
if (Index->Root.Type != AT_FILENAME) {
continue;
}
break;
}
if (mBufferSize < (sizeof (INDEX_ROOT) + Index->FirstEntryOffset)) {
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #2) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
return EFI_VOLUME_CORRUPTED;
}
mBufferSize -= sizeof (INDEX_ROOT) + Index->FirstEntryOffset;
Status = ListFile (
Dir,
(UINT8 *)Index + sizeof (INDEX_ROOT) + Index->FirstEntryOffset,
FileOrCtx,
FunctionType
);
if (!EFI_ERROR (Status)) {
FreeAttr (&Attr);
return EFI_SUCCESS;
}
//
// Search in $INDEX_ALLOCATION
//
BitIndex = NULL;
BitMapLen = 0;
FreeAttr (&Attr);
Status = InitAttr (&Attr, Dir);
if (EFI_ERROR (Status)) {
return Status;
}
while ((Non = (ATTR_HEADER_NONRES *)FindAttr (&Attr, AT_BITMAP)) != NULL) {
mBufferSize = FileRecordSize - (Attr.Current - Attr.BaseMftRecord->FileRecord);
if ( (mBufferSize < sizeof (*Non))
|| (mBufferSize < (Non->NameOffset + 8U)))
{
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #3) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
return EFI_VOLUME_CORRUPTED;
}
if ( (Non->NameLength == 4U)
&& (CompareMem ((UINT8 *)Non + Non->NameOffset, L"$I30", 8U) == 0))
{
BitMapLen = (Non->NonResFlag == 0) ?
((ATTR_HEADER_RES *)Non)->InfoLength :
(UINTN)Non->AllocatedSize;
if (BitMapLen > MAX_FILE_SIZE) {
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir) File is too huge.\n"));
return EFI_OUT_OF_RESOURCES;
}
BitMap = AllocateZeroPool (BitMapLen);
if (BitMap == NULL) {
FreeAttr (&Attr);
return EFI_OUT_OF_RESOURCES;
}
if (Non->NonResFlag == 0) {
if (mBufferSize < (((ATTR_HEADER_RES *)Non)->InfoOffset + BitMapLen)) {
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #4) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
FreePool (BitMap);
return EFI_VOLUME_CORRUPTED;
}
CopyMem (
BitMap,
(UINT8 *)Non + ((ATTR_HEADER_RES *)Non)->InfoOffset,
BitMapLen
);
} else {
Status = ReadData (&Attr, (UINT8 *)Non, BitMap, 0, BitMapLen);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Failed to read non-resident $BITMAP\n"));
FreeAttr (&Attr);
FreePool (BitMap);
return Status;
}
BitMapLen = (UINTN)Non->RealSize;
}
BitIndex = BitMap;
break;
}
}
FreeAttr (&Attr);
Non = (ATTR_HEADER_NONRES *)LocateAttr (&Attr, Dir, AT_INDEX_ALLOCATION);
while (Non != NULL) {
mBufferSize = FileRecordSize - (Attr.Current - Attr.BaseMftRecord->FileRecord);
if ( (mBufferSize < sizeof (*Non))
|| (mBufferSize < (Non->NameOffset + 8U)))
{
DEBUG ((DEBUG_INFO, "NTFS: (IterateDir #5) $INDEX_ROOT is corrupted.\n"));
FreeAttr (&Attr);
if (BitIndex != NULL) {
FreePool (BitMap);
}
return EFI_VOLUME_CORRUPTED;
}
if ( (Non->NonResFlag == 1U)
&& (Non->NameLength == 4U)
&& (Non->NameOffset == sizeof (*Non))
&& (CompareMem ((UINT8 *)Non + Non->NameOffset, L"$I30", 8U) == 0))
{
break;
}
Non = (ATTR_HEADER_NONRES *)FindAttr (&Attr, AT_INDEX_ALLOCATION);
}
if ((Non == NULL) && (BitIndex != NULL)) {
DEBUG ((DEBUG_INFO, "NTFS: $BITMAP without $INDEX_ALLOCATION\n"));
FreeAttr (&Attr);
FreePool (BitMap);
return EFI_VOLUME_CORRUPTED;
}
if (BitIndex != NULL) {
IndexRecord = AllocateZeroPool (IndexRecordSize);
if (IndexRecord == NULL) {
FreeAttr (&Attr);
FreePool (BitMap);
return EFI_OUT_OF_RESOURCES;
}
Bit = 1U;
for (Number = 0; Number < (BitMapLen * 8U); Number++) {
if ((*BitIndex & Bit) != 0) {
Status = ReadAttr (
&Attr,
(UINT8 *)IndexRecord,
Number * IndexRecordSize,
IndexRecordSize
);
if (EFI_ERROR (Status)) {
FreeAttr (&Attr);
FreePool (BitMap);
FreePool (IndexRecord);
return Status;
}
Status = Fixup (
(UINT8 *)IndexRecord,
IndexRecordSize,
SIGNATURE_32 ('I', 'N', 'D', 'X'),
Dir->File->FileSystem->SectorSize
);
if (EFI_ERROR (Status)) {
FreeAttr (&Attr);
FreePool (BitMap);
FreePool (IndexRecord);
return Status;
}
if ( (IndexRecordSize < sizeof (*IndexRecord))
|| (IndexRecordSize < (sizeof (INDEX_HEADER) + IndexRecord->IndexEntriesOffset)))
{
DEBUG ((DEBUG_INFO, "NTFS: $INDEX_ALLOCATION is corrupted.\n"));
FreeAttr (&Attr);
FreePool (BitMap);
FreePool (IndexRecord);
return EFI_VOLUME_CORRUPTED;
}
mBufferSize = IndexRecordSize - (sizeof (INDEX_HEADER) + IndexRecord->IndexEntriesOffset);
Status = ListFile (
Dir,
(UINT8 *)IndexRecord + sizeof (INDEX_HEADER) + IndexRecord->IndexEntriesOffset,
FileOrCtx,
FunctionType
);
if (!EFI_ERROR (Status)) {
FreeAttr (&Attr);
FreePool (BitMap);
FreePool (IndexRecord);
return Status;
}
}
Bit <<= 1U;
if (Bit == 0) {
Bit = 1U;
++BitIndex;
}
}
FreeAttr (&Attr);
FreePool (BitMap);
FreePool (IndexRecord);
}
return Status;
}
EFI_STATUS
RelativeToAbsolute (
OUT CHAR16 *Dest,
IN CHAR16 *Source
)
{
CHAR16 *Buffer;
CHAR16 *BPointer;
CHAR16 *Start;
CHAR16 *End;
UINT32 Skip;
ASSERT (Dest != NULL);
ASSERT (Source != NULL);
Skip = 0;
Buffer = AllocateZeroPool (StrSize (Source));
BPointer = Buffer;
End = Source + StrLen (Source);
Start = End;
while (Start > Source) {
while (*Start != L'/') {
--Start;
}
if ((Start[1] == L'.') && ((Start[2] == L'/') || (Start[2] == L'\0'))) {
End = Start;
--Start;
continue;
}
if ((Start[1] == L'.') && (Start[2] == L'.') && ((Start[3] == L'/') || (Start[3] == L'\0'))) {
End = Start;
--Start;
++Skip;
continue;
}
if (Skip > 0) {
End = Start;
--Start;
--Skip;
continue;
}
CopyMem (
BPointer,
Start + 1U,
(End - Start - 1U) * sizeof (CHAR16)
);
BPointer += End - Start - 1U;
*BPointer = L'/';
++BPointer;
End = Start;
--Start;
}
if (Skip > 0) {
DEBUG ((DEBUG_INFO, "NTFS: Invalid path: root has no parent.\n"));
FreePool (Buffer);
return EFI_DEVICE_ERROR;
}
End = BPointer - 1U;
Start = End - 1U;
while (Start > Buffer) {
while ((Start >= Buffer) && (*Start != L'/')) {
--Start;
}
*Dest = L'/';
++Dest;
CopyMem (
Dest,
Start + 1U,
(End - Start - 1U) * sizeof (CHAR16)
);
Dest += End - Start - 1U;
End = Start;
--Start;
}
FreePool (Buffer);
return EFI_SUCCESS;
}
/**
NTFS Time is the number of 100ns units since Jan 1, 1601.
The signifigance of this date is that it is the beginning
of the first full century of the Gregorian Calendar.
The time displayed in Shell will only match the time
displayed in Windows if the Windows time zone is set to
Coordinated Universal Time (UTC). Otherwise, it will be
skewed by the time zone setting.
**/
VOID
NtfsToEfiTime (
EFI_TIME *EfiTime,
UINT64 NtfsTime
)
{
UINT64 Remainder64;
UINT32 Remainder32;
UINT16 Year;
UINT8 Month;
UINT32 Day;
UINT32 LastDay;
UINT32 PrevLD;
UINT8 Index;
UINT64 Temp;
ASSERT (EfiTime != NULL);
EfiTime->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
EfiTime->Pad1 = 0;
EfiTime->Daylight = 0;
EfiTime->Pad2 = 0;
//
// Because calendars are 1-based (there is no day 0), we have to add
// a day's worth of 100-ns units to make these calculations come out correct.
//
Year = GREGORIAN_START;
for (Temp = NtfsTime + DAY_IN_100NS; Temp > YEAR_IN_100NS; Temp -= YEAR_IN_100NS) {
if (LEAP_YEAR) {
//
// Subtract an extra day for leap year
//
Temp -= DAY_IN_100NS;
}
++Year;
}
//
// From what's left, get the day, hour, minute, second and nanosecond.
//
Day = (UINT32)DivU64x64Remainder (Temp, DAY_IN_100NS, &Remainder64);
if (Day == 0) {
//
// Special handling for last day of year
//
Year -= 1U;
Day = LEAP_YEAR ? 366U : 365U;
}
EfiTime->Year = Year;
EfiTime->Hour = (UINT8)DivU64x64Remainder (Remainder64, HOUR_IN_100NS, &Remainder64);
EfiTime->Minute = (UINT8)DivU64x32Remainder (Remainder64, MINUTE_IN_100NS, &Remainder32);
EfiTime->Second = (UINT8)(Remainder32 / SECOND_IN_100NS);
Remainder32 %= SECOND_IN_100NS;
EfiTime->Nanosecond = Remainder32 * UNIT_IN_NS;
//
// "Day" now contains the ordinal date. We have to convert that to month and day.
//
Month = 1U;
LastDay = 31U;
PrevLD = 0;
for (Index = 1U; Index < 13U; ++Index) {
if (Day > LastDay) {
Month += 1U;
PrevLD = LastDay;
LastDay += mDaysPerMonth[Index];
if ((Index == 1U) && LEAP_YEAR) {
++LastDay;
}
}
}
EfiTime->Month = Month;
EfiTime->Day = (UINT8)(Day - PrevLD);
}

251
Platform/OpenNtfsDxe/Info.c Normal file
View File

@ -0,0 +1,251 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
STATIC
EFI_STATUS
GetLabel (
IN CONST EFI_FS *FileSystem,
IN OUT CHAR16 **Label
)
{
EFI_STATUS Status;
NTFS_FILE *BaseMftRecord;
ATTR_HEADER_RES *Res;
ASSERT (FileSystem != NULL);
ASSERT (Label != NULL);
*Label = NULL;
BaseMftRecord = NULL;
Status = FsHelpFindFile (L"/$Volume", FileSystem->RootIndex, &BaseMftRecord, FSHELP_REG);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not find $Volume file.\n"));
return Status;
}
if (!BaseMftRecord->InodeRead) {
BaseMftRecord->FileRecord = AllocateZeroPool (FileSystem->FileRecordSize);
if (BaseMftRecord->FileRecord == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Failed to allocate buffer for FileRecord.\n"));
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return EFI_OUT_OF_RESOURCES;
}
Status = ReadMftRecord (BaseMftRecord->File, BaseMftRecord->FileRecord, BaseMftRecord->Inode);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read Mft Record #%d.\n", BaseMftRecord->Inode));
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return Status;
}
}
Status = InitAttr (&BaseMftRecord->Attr, BaseMftRecord);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not initialize attribute.\n"));
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return Status;
}
Res = (ATTR_HEADER_RES *)FindAttr (&BaseMftRecord->Attr, AT_VOLUME_NAME);
if ((Res != NULL) && (Res->NonResFlag == 0) && (Res->InfoLength != 0)) {
//
// The Volume Name is not terminated with a Unicode NULL.
// Its name's length is the size of the attribute as stored in the header.
//
if (Res->InfoLength > (MAX_FILE_SIZE - sizeof (CHAR16))) {
DEBUG ((DEBUG_INFO, "NTFS: Volume Name is too huge.\n"));
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return EFI_VOLUME_CORRUPTED;
}
*Label = AllocateZeroPool (Res->InfoLength + sizeof (CHAR16));
if (*Label == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Failed to allocate buffer for *Label\n"));
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (*Label, (UINT8 *)Res + Res->InfoOffset, Res->InfoLength);
}
FreeFile (BaseMftRecord);
FreePool (BaseMftRecord);
return EFI_SUCCESS;
}
/**
Returns file info, system info, or the label of a volume
depending on information type specified.
@param This Pointer to this instance of file protocol
@param Type Pointer to information type requested
@param Len Pointer to size of buffer
@param Data Pointer to buffer for returned info
@retval EFI_STATUS Status of the operation
**/
EFI_STATUS
EFIAPI
FileGetInfo (
IN EFI_FILE_PROTOCOL *This,
IN EFI_GUID *Type,
IN OUT UINTN *Len,
OUT VOID *Data
)
{
EFI_STATUS Status;
EFI_NTFS_FILE *File;
EFI_FILE_INFO *Info;
EFI_FILE_SYSTEM_INFO *FSInfo;
EFI_FILE_SYSTEM_VOLUME_LABEL *VLInfo;
CHAR16 *Label;
UINTN Length;
ASSERT (This != NULL);
ASSERT (Type != NULL);
ASSERT (Len != NULL);
ASSERT ((Data != NULL) || (*Len == 0));
File = (EFI_NTFS_FILE *)This;
Length = 0;
if (CompareMem (Type, &gEfiFileInfoGuid, sizeof (*Type)) == 0) {
//
// Fill in file information
//
Info = (EFI_FILE_INFO *)Data;
if (File->BaseName != NULL) {
Length = sizeof (EFI_FILE_INFO) + StrSize (File->BaseName);
} else if (File->Path != NULL) {
Length = sizeof (EFI_FILE_INFO) + StrSize (File->Path);
} else {
return EFI_VOLUME_CORRUPTED;
}
if (*Len < Length) {
*Len = Length;
return EFI_BUFFER_TOO_SMALL;
}
ZeroMem (Data, sizeof (EFI_FILE_INFO));
Info->Size = (UINT64)Length;
Info->Attribute = EFI_FILE_READ_ONLY;
NtfsToEfiTime (&Info->CreateTime, File->RootFile.CreationTime);
NtfsToEfiTime (&Info->LastAccessTime, File->RootFile.ReadTime);
NtfsToEfiTime (&Info->ModificationTime, File->RootFile.AlteredTime);
if (File->IsDir) {
Info->Attribute |= EFI_FILE_DIRECTORY;
} else {
Info->FileSize = File->RootFile.DataAttributeSize;
Info->PhysicalSize = File->RootFile.DataAttributeSize;
}
if (File->BaseName != NULL) {
CopyMem (Info->FileName, File->BaseName, StrSize (File->BaseName));
} else {
CopyMem (Info->FileName, File->Path, StrSize (File->Path));
}
*Len = Length;
return EFI_SUCCESS;
}
if (CompareMem (Type, &gEfiFileSystemInfoGuid, sizeof (*Type)) == 0) {
//
// Get file system information
//
FSInfo = (EFI_FILE_SYSTEM_INFO *)Data;
if (*Len < MINIMUM_FS_INFO_LENGTH) {
*Len = MINIMUM_FS_INFO_LENGTH;
return EFI_BUFFER_TOO_SMALL;
}
FSInfo->ReadOnly = TRUE;
FSInfo->BlockSize = File->FileSystem->BlockIo->Media->BlockSize;
if (FSInfo->BlockSize == 0) {
DEBUG ((DEBUG_INFO, "NTFS: Corrected Media BlockSize\n"));
FSInfo->BlockSize = 512;
}
FSInfo->VolumeSize = (File->FileSystem->BlockIo->Media->LastBlock + 1U) * FSInfo->BlockSize;
//
// The device is Read Only
//
FSInfo->FreeSpace = 0;
Length = *Len - sizeof (EFI_FILE_SYSTEM_INFO);
Status = FileGetInfo (
This,
&gEfiFileSystemVolumeLabelInfoIdGuid,
&Length,
FSInfo->VolumeLabel
);
FSInfo->Size = (UINT64)Length + sizeof (EFI_FILE_SYSTEM_INFO);
*Len = (UINTN)FSInfo->Size;
return Status;
}
if (CompareMem (Type, &gEfiFileSystemVolumeLabelInfoIdGuid, sizeof (*Type)) == 0) {
//
// Get the volume label
//
VLInfo = (EFI_FILE_SYSTEM_VOLUME_LABEL *)Data;
Status = GetLabel (File->FileSystem, &Label);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not read disk label - %r\n", Status));
return EFI_DEVICE_ERROR;
}
if (Label == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Volume has no label\n"));
*Len = 0;
} else {
if (*Len < StrSize (Label)) {
*Len = StrSize (Label);
return EFI_BUFFER_TOO_SMALL;
}
*Len = StrSize (Label);
CopyMem (VLInfo->VolumeLabel, Label, *Len);
}
return EFI_SUCCESS;
}
return EFI_UNSUPPORTED;
}
EFI_STATUS
EFIAPI
FileSetInfo (
IN EFI_FILE_PROTOCOL *This,
IN EFI_GUID *InformationType,
IN UINTN BufferSize,
IN VOID *Buffer
)
{
return EFI_WRITE_PROTECTED;
}

403
Platform/OpenNtfsDxe/NTFS.c Normal file
View File

@ -0,0 +1,403 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
#define LANGUAGE_CODE_ENGLISH "eng"
CHAR16 *gNTFSDriverName = L"NTFS Driver";
EFI_COMPONENT_NAME_PROTOCOL
gNTFSDriverNames = {
.GetDriverName = NTFSCtlDriverName,
.GetControllerName = NTFSCtlGetControllerName,
.SupportedLanguages = LANGUAGE_CODE_ENGLISH
};
EFI_DRIVER_BINDING_PROTOCOL
gNTFSDriverBinding = {
.Supported = NTFSSupported,
.Start = NTFSStart,
.Stop = NTFSStop,
.Version = 0x10U,
.ImageHandle = NULL,
.DriverBindingHandle = NULL
};
EFI_STATUS
EFIAPI
NTFSEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
Status = gBS->OpenProtocol (
ImageHandle,
&gEfiLoadedImageProtocolGuid,
(VOID **)&LoadedImage,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not open loaded image protocol - %r\n", Status));
return Status;
}
gNTFSDriverBinding.ImageHandle = ImageHandle;
gNTFSDriverBinding.DriverBindingHandle = ImageHandle;
Status = gBS->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiDriverBindingProtocolGuid,
&gNTFSDriverBinding,
&gEfiComponentNameProtocolGuid,
&gNTFSDriverNames,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not bind driver - %r\n", Status));
return Status;
}
LoadedImage->Unload = UnloadNTFSDriver;
return Status;
}
EFI_STATUS
EFIAPI
UnloadNTFSDriver (
IN EFI_HANDLE ImageHandle
)
{
EFI_HANDLE *Buffer;
UINTN NumOfHandles;
UINTN Index;
EFI_STATUS Status;
Status = gBS->LocateHandleBuffer (
AllHandles,
NULL,
NULL,
&NumOfHandles,
&Buffer
);
if (EFI_ERROR (Status)) {
return Status;
}
for (Index = 0; Index < NumOfHandles; ++Index) {
gBS->DisconnectController (Buffer[Index], ImageHandle, NULL);
}
if (Buffer != NULL) {
FreePool (Buffer);
}
Status = gBS->UninstallMultipleProtocolInterfaces (
ImageHandle,
&gEfiDriverBindingProtocolGuid,
&gNTFSDriverBinding,
&gEfiComponentNameProtocolGuid,
&gNTFSDriverNames,
NULL
);
return Status;
}
EFI_STATUS
EFIAPI
NTFSSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_FS *Instance;
Instance = AllocateZeroPool (sizeof (EFI_FS));
if (Instance == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Out of memory.\n"));
return EFI_UNSUPPORTED;
}
Status = gBS->OpenProtocol (
Controller,
&gEfiBlockIoProtocolGuid,
(VOID **)&Instance->BlockIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
FreePool (Instance);
return Status;
}
Status = gBS->OpenProtocol (
Controller,
&gEfiDiskIoProtocolGuid,
(VOID **)&Instance->DiskIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
FreePool (Instance);
return Status;
}
Status = NtfsMount (Instance);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: This is not NTFS Volume.\n"));
Status = EFI_UNSUPPORTED;
} else {
FreeAttr (&Instance->RootIndex->Attr);
FreeAttr (&Instance->MftStart->Attr);
FreePool (Instance->RootIndex->FileRecord);
FreePool (Instance->MftStart->FileRecord);
FreePool (Instance->RootIndex->File);
}
gBS->CloseProtocol (
Controller,
&gEfiDiskIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
FreePool (Instance);
return Status;
}
STATIC
EFI_STATUS
EFIAPI
OpenVolume (
IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **Root
)
{
EFI_FS *Instance;
ASSERT (This != NULL);
ASSERT (Root != NULL);
Instance = (EFI_FS *)This;
*Root = (EFI_FILE_PROTOCOL *)Instance->RootIndex->File;
return EFI_SUCCESS;
}
/**
Installs Simple File System Protocol
**/
EFI_STATUS
EFIAPI
NTFSStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
EFI_STATUS Status;
EFI_FS *Instance;
Instance = AllocateZeroPool (sizeof (EFI_FS));
if (Instance == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Could not allocate a new file system instance\n"));
return EFI_OUT_OF_RESOURCES;
}
Instance->FileIoInterface.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
Instance->FileIoInterface.OpenVolume = OpenVolume;
Instance->DevicePath = DevicePathFromHandle (Controller);
if (Instance->DevicePath == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Could not get Device Path\n"));
FreePool (Instance);
return EFI_NO_MAPPING;
}
Status = gBS->OpenProtocol (
Controller,
&gEfiBlockIoProtocolGuid,
(VOID **)&Instance->BlockIo,
This->DriverBindingHandle,
Controller,
/*
* EFI_OPEN_PROTOCOL_BY_DRIVER would return Access Denied here,
* because the disk driver has that protocol already open. So use
* EFI_OPEN_PROTOCOL_GET_PROTOCOL (which doesn't require us to close it).
*/
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not access BlockIO protocol - %r\n", Status));
FreePool (Instance);
return Status;
}
Status = gBS->OpenProtocol (
Controller,
&gEfiDiskIoProtocolGuid,
(VOID **)&Instance->DiskIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not access DiskIO protocol - %r\n", Status));
FreePool (Instance);
return Status;
}
Instance->EfiFile.Revision = EFI_FILE_PROTOCOL_REVISION2;
Instance->EfiFile.Open = FileOpen;
Instance->EfiFile.Close = FileClose;
Instance->EfiFile.Delete = FileDelete;
Instance->EfiFile.Read = FileRead;
Instance->EfiFile.Write = FileWrite;
Instance->EfiFile.GetPosition = FileGetPosition;
Instance->EfiFile.SetPosition = FileSetPosition;
Instance->EfiFile.GetInfo = FileGetInfo;
Instance->EfiFile.SetInfo = FileSetInfo;
Instance->EfiFile.Flush = FileFlush;
Status = NtfsMount (Instance);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not mount file system.\n"));
FreePool (Instance);
return Status;
}
Status = gBS->InstallMultipleProtocolInterfaces (
&Controller,
&gEfiSimpleFileSystemProtocolGuid,
&Instance->FileIoInterface,
NULL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not install simple file system protocol - %r\n", Status));
gBS->CloseProtocol (
Controller,
&gEfiDiskIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
FreeAttr (&Instance->RootIndex->Attr);
FreeAttr (&Instance->MftStart->Attr);
FreePool (Instance->RootIndex->FileRecord);
FreePool (Instance->MftStart->FileRecord);
FreePool (Instance->RootIndex->File);
FreePool (Instance);
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
/**
Uninstall Simple File system Protocol
**/
EFI_STATUS
EFIAPI
NTFSStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *NTFS;
EFI_FS *Instance;
Status = gBS->OpenProtocol (
Controller,
&gEfiSimpleFileSystemProtocolGuid,
(VOID **)&NTFS,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not locate our instance - %r\n", Status));
return Status;
}
Instance = (EFI_FS *)NTFS;
FreeAttr (&Instance->RootIndex->Attr);
FreeAttr (&Instance->MftStart->Attr);
FreePool (Instance->RootIndex->FileRecord);
FreePool (Instance->MftStart->FileRecord);
FreePool (Instance->RootIndex->File);
Status = gBS->UninstallMultipleProtocolInterfaces (
Controller,
&gEfiSimpleFileSystemProtocolGuid,
&NTFS,
NULL
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Close DISK_IO Protocol
//
Status = gBS->CloseProtocol (
Controller,
&gEfiDiskIoProtocolGuid,
This->DriverBindingHandle,
Controller
);
if (EFI_ERROR (Status)) {
return Status;
}
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
NTFSCtlDriverName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN CHAR8 *Language,
OUT CHAR16 **DriverName
)
{
*DriverName = gNTFSDriverName;
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
NTFSCtlGetControllerName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_HANDLE ChildHandle OPTIONAL,
IN CHAR8 *Language,
OUT CHAR16 **ControllerName
)
{
return EFI_UNSUPPORTED;
}

160
Platform/OpenNtfsDxe/NTFS.h Normal file
View File

@ -0,0 +1,160 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#ifndef NTFS_H
#define NTFS_H
#include "Protocol/PciIo.h"
#include "Protocol/DevicePath.h"
#include "Protocol/DriverBinding.h"
#include "Protocol/BlockIo.h"
#include "Protocol/DiskIo.h"
#include "Protocol/SimpleFileSystem.h"
#include "Protocol/UnicodeCollation.h"
#include "Protocol/LoadedImage.h"
#include <Protocol/ComponentName.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Guid/FileSystemVolumeLabelInfo.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/OcStringLib.h>
EFI_STATUS
EFIAPI
NTFSSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
);
EFI_STATUS
EFIAPI
NTFSStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
);
EFI_STATUS
EFIAPI
NTFSStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer
);
EFI_STATUS
EFIAPI
UnloadNTFSDriver (
IN EFI_HANDLE ImageHandle
);
EFI_STATUS
EFIAPI
NTFSCtlDriverName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN CHAR8 *Language,
OUT CHAR16 **DriverName
);
EFI_STATUS
EFIAPI
NTFSCtlGetControllerName (
IN EFI_COMPONENT_NAME_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_HANDLE ChildHandle OPTIONAL,
IN CHAR8 *Language,
OUT CHAR16 **ControllerName
);
EFI_STATUS
EFIAPI
FileOpen (
IN EFI_FILE_PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **NewHandle,
IN CHAR16 *FileName,
IN UINT64 OpenMode,
IN UINT64 Attributes
);
EFI_STATUS
EFIAPI
FileClose (
IN EFI_FILE_PROTOCOL *This
);
EFI_STATUS
EFIAPI
FileDelete (
IN EFI_FILE_PROTOCOL *This
);
EFI_STATUS
EFIAPI
FileRead (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
);
EFI_STATUS
EFIAPI
FileWrite (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
);
EFI_STATUS
EFIAPI
FileGetPosition (
IN EFI_FILE_PROTOCOL *This,
OUT UINT64 *Position
);
EFI_STATUS
EFIAPI
FileSetPosition (
IN EFI_FILE_PROTOCOL *This,
IN UINT64 Position
);
EFI_STATUS
EFIAPI
FileGetInfo (
IN EFI_FILE_PROTOCOL *This,
IN EFI_GUID *Type,
IN OUT UINTN *Len,
OUT VOID *Data
);
EFI_STATUS
EFIAPI
FileSetInfo (
IN EFI_FILE_PROTOCOL *This,
IN EFI_GUID *InformationType,
IN UINTN BufferSize,
IN VOID *Buffer
);
EFI_STATUS
EFIAPI
FileFlush (
IN EFI_FILE_PROTOCOL *This
);
#endif // NTFS_H

378
Platform/OpenNtfsDxe/Open.c Normal file
View File

@ -0,0 +1,378 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Helper.h"
extern INT64 mIndexCounter;
EFI_STATUS
EFIAPI
FileOpen (
IN EFI_FILE_PROTOCOL *This,
OUT EFI_FILE_PROTOCOL **NewHandle,
IN CHAR16 *FileName,
IN UINT64 OpenMode,
IN UINT64 Attributes
)
{
EFI_STATUS Status;
EFI_NTFS_FILE *File;
EFI_FS *FileSystem;
EFI_NTFS_FILE *NewFile;
CHAR16 Path[MAX_PATH];
CHAR16 CleanPath[MAX_PATH];
CHAR16 *DirName;
UINTN Index;
UINTN Length;
BOOLEAN AbsolutePath;
ASSERT (This != NULL);
ASSERT (NewHandle != NULL);
ASSERT (FileName != NULL);
File = (EFI_NTFS_FILE *)This;
FileSystem = File->FileSystem;
AbsolutePath = (*FileName == L'\\');
ZeroMem (Path, sizeof (Path));
if (OpenMode != EFI_FILE_MODE_READ) {
DEBUG ((DEBUG_INFO, "NTFS: File '%s' can only be opened in read-only mode\n", FileName));
return EFI_WRITE_PROTECTED;
}
if ((*FileName == 0) || (CompareMem (FileName, L".", sizeof (L".")) == 0)) {
++File->RefCount;
*NewHandle = This;
return EFI_SUCCESS;
}
if (AbsolutePath) {
Length = 0;
} else {
Length = StrnLenS (File->Path, MAX_PATH);
if (Length == MAX_PATH) {
DEBUG ((DEBUG_INFO, "NTFS: Path is too long.\n"));
return EFI_OUT_OF_RESOURCES;
}
Status = StrCpyS (Path, MAX_PATH, File->Path);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not copy string.\n"));
return Status;
}
if ((Length == 0) || (Path[Length - 1U] != '/')) {
Path[Length++] = L'/';
}
}
Status = StrCpyS (&Path[Length], MAX_PATH - Length, FileName);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not copy FileName `%s`.\n", FileName));
return Status;
}
Index = StrnLenS (Path, MAX_PATH);
while (Index > Length) {
--Index;
if (Path[Index] == L'\\') {
Path[Index] = L'/';
}
}
ZeroMem (CleanPath, sizeof (CleanPath));
Status = RelativeToAbsolute (CleanPath, Path);
if (EFI_ERROR (Status)) {
return Status;
}
NewFile = AllocateZeroPool (sizeof (EFI_NTFS_FILE));
if (NewFile == NULL) {
return EFI_OUT_OF_RESOURCES;
}
NewFile->FileSystem = FileSystem;
CopyMem (&NewFile->EfiFile, &FileSystem->EfiFile, sizeof (EFI_FILE_PROTOCOL));
NewFile->Path = AllocateZeroPool (StrnSizeS (CleanPath, MAX_PATH));
if (NewFile->Path == NULL) {
DEBUG ((DEBUG_INFO, "NTFS: Could not instantiate path\n"));
FreePool (NewFile);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (NewFile->Path, CleanPath, StrnSizeS (CleanPath, MAX_PATH));
Index = StrnLenS (CleanPath, MAX_PATH);
if (Index == MAX_PATH) {
DEBUG ((DEBUG_INFO, "NTFS: CleanPath is too long.\n"));
return EFI_OUT_OF_RESOURCES;
}
while (Index > 0) {
--Index;
if (CleanPath[Index] == L'/') {
CleanPath[Index] = 0;
break;
}
}
DirName = (Index == 0) ? L"/" : CleanPath;
NewFile->BaseName = (Index == 0) ? L"\0" : &NewFile->Path[Index + 1U];
Status = NtfsDir (FileSystem, DirName, NewFile, INFO_HOOK);
if (EFI_ERROR (Status)) {
FreePool (NewFile->Path);
FreePool (NewFile);
return Status;
}
if (!NewFile->IsDir) {
Status = NtfsOpen (NewFile);
if (EFI_ERROR (Status)) {
FreePool (NewFile->Path);
FreePool (NewFile);
return Status;
}
}
NewFile->RefCount++;
*NewHandle = &NewFile->EfiFile;
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
FileReadDir (
IN EFI_NTFS_FILE *File,
OUT VOID *Data,
IN OUT UINTN *Size
)
{
EFI_STATUS Status;
EFI_FILE_INFO *Info;
CHAR16 Path[MAX_PATH];
EFI_NTFS_FILE *TmpFile = NULL;
UINTN Length;
ASSERT (File != NULL);
ASSERT (Size != NULL);
ASSERT ((Data != NULL) || ((Data == NULL) && (*Size == 0)));
Info = (EFI_FILE_INFO *)Data;
if (*Size < MINIMUM_INFO_LENGTH) {
*Size = MINIMUM_INFO_LENGTH;
return EFI_BUFFER_TOO_SMALL;
}
ZeroMem (Path, sizeof (Path));
ZeroMem (Data, *Size);
Info->Size = *Size;
Status = StrCpyS (Path, MAX_PATH, File->Path);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Could not copy string.\n"));
return Status;
}
Length = StrnLenS (Path, MAX_PATH);
if (Length == MAX_PATH) {
DEBUG ((DEBUG_INFO, "NTFS: Path is too long.\n"));
return EFI_OUT_OF_RESOURCES;
}
if ((Length == 0) || (Path[Length - 1U] != L'/')) {
Path[Length] = L'/';
Length++;
}
mIndexCounter = (INT64)File->DirIndex;
if (Length == 0) {
Status = NtfsDir (File->FileSystem, L"/", Data, DIR_HOOK);
} else {
Status = NtfsDir (File->FileSystem, File->Path, Data, DIR_HOOK);
}
if (mIndexCounter >= 0) {
//
// No entries left
//
*Size = 0;
return EFI_SUCCESS;
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_INFO, "NTFS: Directory listing failed.\n"));
return Status;
}
Info->FileSize = 0;
Info->PhysicalSize = 0;
if ((Info->Attribute & EFI_FILE_DIRECTORY) == 0) {
TmpFile = AllocateZeroPool (sizeof (EFI_NTFS_FILE));
if (TmpFile == NULL) {
return EFI_OUT_OF_RESOURCES;
}
TmpFile->FileSystem = File->FileSystem;
CopyMem (&TmpFile->EfiFile, &File->FileSystem->EfiFile, sizeof (EFI_FILE_PROTOCOL));
if ((MAX_PATH - Length) < StrSize (Info->FileName)) {
DEBUG ((DEBUG_INFO, "NTFS: FileName is too long.\n"));
FreePool (TmpFile);
return EFI_OUT_OF_RESOURCES;
}
CopyMem (&Path[Length], Info->FileName, StrSize (Info->FileName));
TmpFile->Path = Path;
Status = NtfsOpen (TmpFile);
if (EFI_ERROR (Status)) {
// DEBUG ((DEBUG_INFO, "NTFS: Unable to obtain the size of '%s'\n", Info->FileName));
} else {
Info->FileSize = TmpFile->RootFile.DataAttributeSize;
Info->PhysicalSize = TmpFile->RootFile.DataAttributeSize;
}
FreePool (TmpFile);
}
*Size = (UINTN)Info->Size;
++File->DirIndex;
// DEBUG ((DEBUG_INFO, "NTFS: Entry[%d]: '%s' %s\n", File->DirIndex - 1U, Info->FileName,
// (Info->Attribute & EFI_FILE_DIRECTORY) ? L"<DIR>" : L""));
return EFI_SUCCESS;
}
STATIC
EFI_STATUS
Read (
IN EFI_NTFS_FILE *File,
OUT VOID *Data,
IN UINTN *Size
)
{
EFI_STATUS Status;
UINT64 Remaining;
NTFS_FILE *BaseMftRecord;
ASSERT (File != NULL);
ASSERT (Size != NULL);
ASSERT ((Data != NULL) || ((Data == NULL) && (*Size == 0)));
Remaining = (File->RootFile.DataAttributeSize > File->Offset) ?
File->RootFile.DataAttributeSize - File->Offset : 0;
if (*Size > Remaining) {
*Size = (UINTN)Remaining;
}
BaseMftRecord = &File->RootFile;
Status = ReadAttr (&BaseMftRecord->Attr, Data, File->Offset, *Size);
if (EFI_ERROR (Status)) {
*Size = 0;
FreeAttr (&BaseMftRecord->Attr);
return Status;
}
File->Offset += *Size;
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
FileRead (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
OUT VOID *Buffer
)
{
EFI_NTFS_FILE *File;
ASSERT (This != NULL);
ASSERT (BufferSize != NULL);
ASSERT ((Buffer != NULL) || ((Buffer == NULL) && (*BufferSize == 0)));
File = (EFI_NTFS_FILE *)This;
if (File->IsDir) {
return FileReadDir (File, Buffer, BufferSize);
}
return Read (File, Buffer, BufferSize);
}
EFI_STATUS
EFIAPI
FileWrite (
IN EFI_FILE_PROTOCOL *This,
IN OUT UINTN *BufferSize,
IN VOID *Buffer
)
{
return EFI_WRITE_PROTECTED;
}
EFI_STATUS
EFIAPI
FileDelete (
IN EFI_FILE_PROTOCOL *This
)
{
FileClose (This);
return EFI_WARN_DELETE_FAILURE;
}
EFI_STATUS
EFIAPI
FileFlush (
IN EFI_FILE_PROTOCOL *This
)
{
return EFI_ACCESS_DENIED;
}
EFI_STATUS
EFIAPI
FileClose (
IN EFI_FILE_PROTOCOL *This
)
{
EFI_NTFS_FILE *File;
ASSERT (This != NULL);
File = (EFI_NTFS_FILE *)This;
if (&File->RootFile == File->FileSystem->RootIndex) {
return EFI_SUCCESS;
}
if (--File->RefCount == 0) {
FreeFile (&File->RootFile);
FreeFile (&File->MftFile);
if (File->Path != NULL) {
FreePool (File->Path);
}
FreePool (File);
}
return EFI_SUCCESS;
}

View File

@ -0,0 +1,66 @@
## @file
#
# New Technologies File System (NTFS) read-only driver.
#
# Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
#
##
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = OpenNtfsDxe
FILE_GUID = 5E18FB33-27FE-4613-86AD-10FABDA95BB0
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = NTFSEntryPoint
#
# The following information is for reference only and not required by the build tools.
#
# VALID_ARCHITECTURES = IA32 X64
#
[Sources]
NTFS.h
Driver.h
Helper.h
NTFS.c
Open.c
Info.c
Position.c
Disc.c
Data.c
Index.c
Compression.c
[Packages]
MdePkg/MdePkg.dec
OpenCorePkg/OpenCorePkg.dec
[LibraryClasses]
BaseLib
BaseMemoryLib
DebugLib
DevicePathLib
MemoryAllocationLib
UefiBootServicesTableLib
UefiDriverEntryPoint
OcStringLib
[Protocols]
gEfiDriverBindingProtocolGuid ## PRODUCES
gEfiSimpleFileSystemProtocolGuid ## PRODUCES
gEfiComponentNameProtocolGuid ## CONSUMES
gEfiDiskIoProtocolGuid ## CONSUMES
gEfiBlockIoProtocolGuid ## CONSUMES
gEfiUnicodeCollationProtocolGuid ## CONSUMES
[Guids]
gEfiFileInfoGuid ## CONSUMES
gEfiFileSystemInfoGuid ## CONSUMES
gEfiFileSystemVolumeLabelInfoIdGuid ## CONSUMES
[Depex]
TRUE

View File

@ -0,0 +1,66 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
Functional and structural descriptions follow NTFS Documentation
by Richard Russon and Yuval Fledel
**/
#include "NTFS.h"
#include "Driver.h"
EFI_STATUS
EFIAPI
FileGetPosition (
IN EFI_FILE_PROTOCOL *This,
OUT UINT64 *Position
)
{
EFI_NTFS_FILE *File;
ASSERT (This != NULL);
ASSERT (Position != NULL);
File = (EFI_NTFS_FILE *)This;
*Position = File->IsDir ? File->DirIndex : File->Offset;
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
FileSetPosition (
IN EFI_FILE_PROTOCOL *This,
IN UINT64 Position
)
{
EFI_NTFS_FILE *File;
UINT64 FileSize;
ASSERT (This != NULL);
File = (EFI_NTFS_FILE *)This;
if (File->IsDir) {
if (Position != 0) {
return EFI_INVALID_PARAMETER;
}
File->DirIndex = 0;
return EFI_SUCCESS;
}
FileSize = File->RootFile.DataAttributeSize;
if (Position == 0xFFFFFFFFFFFFFFFFULL) {
Position = FileSize;
} else if (Position > FileSize) {
DEBUG ((DEBUG_INFO, "NTFS: '%s': Cannot seek to #%Lx of %Lx\n", File->Path, Position, FileSize));
return EFI_UNSUPPORTED;
}
File->Offset = Position;
return EFI_SUCCESS;
}

View File

@ -72,6 +72,11 @@ extern EFI_GUID gEfiLegacyRegion2ProtocolGuid;
extern EFI_GUID gEfiPciRootBridgeIoProtocolGuid;
extern EFI_GUID gEfiSmbiosTableGuid;
extern EFI_GUID gEfiUnicodeCollation2ProtocolGuid;
extern EFI_GUID gEfiFileSystemInfoGuid;
extern EFI_GUID gEfiDiskIoProtocolGuid;
extern EFI_GUID gEfiBlockIoProtocolGuid;
extern EFI_GUID gEfiDriverBindingProtocolGuid;
extern EFI_GUID gEfiComponentNameProtocolGuid;
extern EFI_GUID gOcBootstrapProtocolGuid;
extern EFI_GUID gOcVendorVariableGuid;

View File

@ -160,6 +160,21 @@ EFI_GUID gEfiSmbiosTableGuid = {
EFI_GUID gEfiUnicodeCollation2ProtocolGuid = {
0xa4c751fc, 0x23ae, 0x4c3e, { 0x92, 0xe9, 0x49, 0x64, 0xcf, 0x63, 0xf3, 0x49 }
};
EFI_GUID gEfiFileSystemInfoGuid = {
0x09576E93, 0x6D3F, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }
};
EFI_GUID gEfiDiskIoProtocolGuid = {
0xCE345171, 0xBA0B, 0x11D2, { 0x8E, 0x4F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }
};
EFI_GUID gEfiBlockIoProtocolGuid = {
0x964E5B21, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }
};
EFI_GUID gEfiDriverBindingProtocolGuid = {
0x18A031AB, 0xB443, 0x4D1A, { 0xA5, 0xC0, 0x0C, 0x09, 0x26, 0x1E, 0x9F, 0x71 }
};
EFI_GUID gEfiComponentNameProtocolGuid = {
0x107A772C, 0xD5E1, 0x11D4, { 0x9A, 0x46, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }
};
EFI_GUID gOcBootstrapProtocolGuid = {
0xBA1EB455, 0xB182, 0x4F14, { 0x85, 0x21, 0xE4, 0x22, 0xC3, 0x25, 0xDE, 0xF6 }

View File

@ -0,0 +1,15 @@
## @file
# Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
##
PROJECT = TestNtfsDxe
PRODUCT = $(PROJECT)$(INFIX)$(SUFFIX)
OBJS = $(PROJECT).o
OBJS += Compression.o Data.o Disc.o Index.o Info.o NTFS.o Open.o Position.o
include ../../User/Makefile
CFLAGS += -I../../Platform/OpenNtfsDxe
VPATH += ../../Platform/OpenNtfsDxe:$

View File

@ -0,0 +1,257 @@
/** @file
Copyright (c) 2022, Mikhail Krichanov. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
**/
#include <NTFS.h>
#include <Helper.h>
#include <UserFile.h>
#include <UserGlobalVar.h>
UINTN mFuzzOffset;
UINTN mFuzzSize;
CONST UINT8 *mFuzzPointer;
EFI_STATUS
EFIAPI
FuzzReadDisk (
IN EFI_DISK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN UINT64 Offset,
IN UINTN BufferSize,
OUT VOID *Buffer
)
{
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
if ((mFuzzSize - mFuzzOffset) < BufferSize) {
return EFI_OUT_OF_RESOURCES;
}
CopyMem (Buffer, mFuzzPointer, BufferSize);
mFuzzPointer += BufferSize;
mFuzzOffset += BufferSize;
return EFI_SUCCESS;
}
VOID
FreeAll (
IN CHAR16 *FileName,
IN EFI_FS *Instance
)
{
FreePool (FileName);
if (Instance != NULL) {
if (Instance->DiskIo != NULL) {
FreePool (Instance->DiskIo);
}
if (Instance->BlockIo != NULL) {
if (Instance->BlockIo->Media != NULL) {
FreePool (Instance->BlockIo->Media);
}
FreePool (Instance->BlockIo);
}
if (Instance->RootIndex != NULL) {
FreeAttr (&Instance->RootIndex->Attr);
FreeAttr (&Instance->MftStart->Attr);
FreePool (Instance->RootIndex->FileRecord);
FreePool (Instance->MftStart->FileRecord);
FreePool (Instance->RootIndex->File);
}
FreePool (Instance);
}
}
INT32
LLVMFuzzerTestOneInput (
CONST UINT8 *FuzzData,
UINTN FuzzSize
)
{
EFI_STATUS Status;
EFI_FS *Instance;
EFI_FILE_PROTOCOL *This;
UINTN BufferSize;
VOID *Buffer;
EFI_FILE_PROTOCOL *NewHandle;
CHAR16 *FileName;
VOID *Info;
UINTN Len;
UINT64 Position;
mFuzzOffset = 0;
mFuzzPointer = FuzzData;
mFuzzSize = FuzzSize;
Instance = NULL;
BufferSize = 100;
//
// Construct File Name
//
FileName = AllocateZeroPool (BufferSize);
if (FileName == NULL) {
return 0;
}
ASAN_CHECK_MEMORY_REGION (FileName, BufferSize);
if ((mFuzzSize - mFuzzOffset) < BufferSize) {
FreeAll (FileName, Instance);
return 0;
}
CopyMem (FileName, mFuzzPointer, BufferSize - 2);
mFuzzPointer += BufferSize - 2;
mFuzzOffset += BufferSize - 2;
//
// Construct File System
//
Instance = AllocateZeroPool (sizeof (EFI_FS));
if (Instance == NULL) {
FreeAll (FileName, Instance);
return 0;
}
ASAN_CHECK_MEMORY_REGION (Instance, sizeof (EFI_FS));
Instance->DiskIo = AllocateZeroPool (sizeof (EFI_DISK_IO_PROTOCOL));
if (Instance->DiskIo == NULL) {
FreeAll (FileName, Instance);
return 0;
}
ASAN_CHECK_MEMORY_REGION (Instance->DiskIo, sizeof (EFI_DISK_IO_PROTOCOL));
Instance->DiskIo->ReadDisk = FuzzReadDisk;
Instance->BlockIo = AllocateZeroPool (sizeof (EFI_BLOCK_IO_PROTOCOL));
if (Instance->BlockIo == NULL) {
FreeAll (FileName, Instance);
return 0;
}
ASAN_CHECK_MEMORY_REGION (Instance->BlockIo, sizeof (EFI_BLOCK_IO_PROTOCOL));
Instance->BlockIo->Media = AllocateZeroPool (sizeof (EFI_BLOCK_IO_MEDIA));
if (Instance->BlockIo->Media == NULL) {
FreeAll (FileName, Instance);
return 0;
}
ASAN_CHECK_MEMORY_REGION (Instance->BlockIo->Media, sizeof (EFI_BLOCK_IO_MEDIA));
Instance->EfiFile.Revision = EFI_FILE_PROTOCOL_REVISION2;
Instance->EfiFile.Open = FileOpen;
Instance->EfiFile.Close = FileClose;
Instance->EfiFile.Delete = FileDelete;
Instance->EfiFile.Read = FileRead;
Instance->EfiFile.Write = FileWrite;
Instance->EfiFile.GetPosition = FileGetPosition;
Instance->EfiFile.SetPosition = FileSetPosition;
Instance->EfiFile.GetInfo = FileGetInfo;
Instance->EfiFile.SetInfo = FileSetInfo;
Instance->EfiFile.Flush = FileFlush;
Status = NtfsMount (Instance);
if (EFI_ERROR (Status)) {
FreeAll (FileName, Instance);
return 0;
}
This = (EFI_FILE_PROTOCOL *)Instance->RootIndex->File;
//
// Test Ntfs Driver
//
Status = FileOpen (This, &NewHandle, FileName, EFI_FILE_MODE_READ, 0);
if (Status == EFI_SUCCESS) {
Buffer = NULL;
BufferSize = 0;
Status = FileRead (NewHandle, &BufferSize, Buffer);
if (Status == EFI_BUFFER_TOO_SMALL) {
Buffer = AllocateZeroPool (BufferSize);
if (Buffer == NULL) {
FreeAll (FileName, Instance);
return 0;
}
ASAN_CHECK_MEMORY_REGION (Buffer, BufferSize);
FileRead (NewHandle, &BufferSize, Buffer);
FileWrite (NewHandle, &BufferSize, Buffer);
FileFlush (NewHandle);
FreePool (Buffer);
}
Len = 0;
Info = NULL;
Status = FileGetInfo (NewHandle, &gEfiFileInfoGuid, &Len, Info);
if (Status == EFI_BUFFER_TOO_SMALL) {
Info = AllocateZeroPool (Len);
FileGetInfo (NewHandle, &gEfiFileInfoGuid, &Len, Info);
FreePool (Info);
}
Len = 0;
Status = FileGetInfo (NewHandle, &gEfiFileSystemInfoGuid, &Len, Info);
if (Status == EFI_BUFFER_TOO_SMALL) {
Info = AllocateZeroPool (Len);
FileGetInfo (NewHandle, &gEfiFileSystemInfoGuid, &Len, Info);
FreePool (Info);
}
Len = 0;
Status = FileGetInfo (NewHandle, &gEfiFileSystemVolumeLabelInfoIdGuid, &Len, Info);
if (Status == EFI_BUFFER_TOO_SMALL) {
Info = AllocateZeroPool (Len);
FileGetInfo (NewHandle, &gEfiFileSystemVolumeLabelInfoIdGuid, &Len, Info);
FreePool (Info);
}
FileSetInfo (NewHandle, &gEfiFileSystemVolumeLabelInfoIdGuid, Len, Info);
FileGetPosition (NewHandle, &Position);
while (!EFI_ERROR (FileSetPosition (NewHandle, Position))) {
++Position;
}
FileDelete (NewHandle);
}
FreeAll (FileName, Instance);
return 0;
}
int
ENTRY_POINT (
int argc,
char **argv
)
{
uint32_t f;
uint8_t *b;
if ((b = UserReadFile ((argc > 1) ? argv[1] : "in.bin", &f)) == NULL) {
DEBUG ((DEBUG_ERROR, "Read fail\n"));
return -1;
}
LLVMFuzzerTestOneInput (b, f);
FreePool (b);
return 0;
}

View File

@ -25,6 +25,7 @@ buildutil() {
"TestKextInject"
"TestMacho"
"TestMp3"
"TestNtfsDxe"
"TestPeCoff"
"TestRsaPreprocess"
"TestSmbios"
@ -164,6 +165,7 @@ package() {
"OpenCanopy.efi"
"OpenHfsPlus.efi"
"OpenLinuxBoot.efi"
"OpenNtfsDxe.efi"
"OpenPartitionDxe.efi"
"OpenRuntime.efi"
"OpenUsbKbDxe.efi"