You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4471 lines
119 KiB
4471 lines
119 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
NtfsBoot.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the Ntfs boot file system used by the operating system
|
|
loader.
|
|
|
|
Author:
|
|
|
|
Gary Kimura [GaryKi] 10-April-1992
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
//
|
|
// Stuff to get around the fact that we include both Fat, Hpfs, and Ntfs include
|
|
// environments
|
|
//
|
|
|
|
#define _FAT_
|
|
#define _HPFS_
|
|
#define _CVF_
|
|
|
|
#define VBO ULONG
|
|
#define LBO ULONG
|
|
#define BIOS_PARAMETER_BLOCK ULONG
|
|
#define CVF_LAYOUT ULONG
|
|
#define CVF_HEADER ULONG
|
|
#define COMPONENT_LOCATION ULONG
|
|
#define PCVF_FAT_EXTENSIONS PCHAR
|
|
|
|
typedef struct DIRENT {
|
|
char Garbage[32];
|
|
} DIRENT; // sizeof = 32
|
|
|
|
|
|
#include "bootlib.h"
|
|
#include "stdio.h"
|
|
#include "blcache.h"
|
|
|
|
#include "bootstatus.h"
|
|
|
|
BOOTFS_INFO NtfsBootFsInfo={L"ntfs"};
|
|
|
|
#undef VBO
|
|
#undef LBO
|
|
#undef BIOS_PARAMETER_BLOCK
|
|
#undef DIRENT
|
|
|
|
#include "ntfs.h"
|
|
|
|
int Line = 0;
|
|
|
|
VOID NtfsPrint( IN PCHAR FormatString, ... )
|
|
{ va_list arglist; CHAR text[78+1]; ULONG Count,Length;
|
|
|
|
va_start(arglist,FormatString);
|
|
Length = _vsnprintf(text,sizeof(text),FormatString,arglist);
|
|
text[78] = 0;
|
|
ArcWrite(ARC_CONSOLE_OUTPUT,text,Length,&Count);
|
|
va_end(arglist);
|
|
}
|
|
|
|
VOID NtfsGetChar(VOID) { UCHAR c; ULONG count; ArcRead(ARC_CONSOLE_INPUT,&c,1,&count); }
|
|
|
|
#define ReadConsole(c) { \
|
|
UCHAR Key=0; ULONG Count; \
|
|
while (Key != c) { \
|
|
if (ArcGetReadStatus(BlConsoleInDeviceId) == ESUCCESS) { \
|
|
ArcRead(BlConsoleInDeviceId, &Key, sizeof(Key), &Count); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#define Pause ReadConsole( ' ' )
|
|
|
|
#if FALSE
|
|
#define PausedPrint(x) { \
|
|
NtfsPrint x; \
|
|
Line++; \
|
|
if (Line >= 20) { \
|
|
NtfsPrint( ">" ); \
|
|
Pause; \
|
|
Line = 0; \
|
|
} \
|
|
}
|
|
#else
|
|
#define PausedPrint(x)
|
|
#endif
|
|
|
|
#define ToUpper(C) ((((C) >= 'a') && ((C) <= 'z')) ? (C) - 'a' + 'A' : (C))
|
|
|
|
#define DereferenceFileRecord(idx) { \
|
|
NtfsFileRecordBufferPinned[idx] -= 1; \
|
|
if (NtfsFileRecordBufferPinned[idx] & 0xFF00) { \
|
|
PausedPrint(( "NtfsFileRecordBufferPinned[%x]=%x\r\n", \
|
|
idx, NtfsFileRecordBufferPinned[idx])); \
|
|
} \
|
|
}
|
|
|
|
|
|
//
|
|
// Low level disk read routines
|
|
//
|
|
|
|
//
|
|
// VOID
|
|
// ReadDisk (
|
|
// IN ULONG DeviceId,
|
|
// IN LONGLONG Lbo,
|
|
// IN ULONG ByteCount,
|
|
// IN OUT PVOID Buffer,
|
|
// IN BOOLEAN CacheNewData
|
|
// );
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsReadDisk (
|
|
IN ULONG DeviceId,
|
|
IN LONGLONG Lbo,
|
|
IN ULONG ByteCount,
|
|
IN OUT PVOID Buffer,
|
|
IN BOOLEAN CacheNewData
|
|
);
|
|
|
|
#define ReadDisk(A,B,C,D,E) { ARC_STATUS _s; \
|
|
if ((_s = NtfsReadDisk(A,B,C,D,E)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
//
|
|
// Low level disk write routines
|
|
//
|
|
//
|
|
// VOID
|
|
// WriteDisk (
|
|
// IN ULONG DeviceId,
|
|
// IN LONGLONG Lbo,
|
|
// IN ULONG ByteCount,
|
|
// IN OUT PVOID Buffer
|
|
// );
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsWriteDisk (
|
|
IN ULONG DeviceId,
|
|
IN LONGLONG Lbo,
|
|
IN ULONG ByteCount,
|
|
IN OUT PVOID Buffer
|
|
);
|
|
|
|
#define WriteDisk(A,B,C,D) { ARC_STATUS _s; \
|
|
if ((_s = NtfsWriteDisk(A,B,C,D)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
//
|
|
// Attribute lookup and read routines
|
|
//
|
|
//
|
|
// VOID
|
|
// LookupAttribute (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN LONGLONG FileRecord,
|
|
// IN ATTRIBUTE_TYPE_CODE TypeCode,
|
|
// OUT PBOOLEAN FoundAttribute,
|
|
// OUT PNTFS_ATTRIBUTE_CONTEXT AttributeContext
|
|
// );
|
|
//
|
|
// VOID
|
|
// ReadAttribute (
|
|
// IN PNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
// IN VBO Vbo,
|
|
// IN ULONG Length,
|
|
// IN PVOID Buffer
|
|
// );
|
|
//
|
|
// VOID
|
|
// ReadAndDecodeFileRecord (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN LONGLONG FileRecord,
|
|
// OUT PULONG Index
|
|
// );
|
|
//
|
|
// VOID
|
|
// DecodeUsa (
|
|
// IN PVOID UsaBuffer,
|
|
// IN ULONG Length
|
|
// );
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsLookupAttribute(
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN LONGLONG FileRecord,
|
|
IN ATTRIBUTE_TYPE_CODE TypeCode,
|
|
OUT PBOOLEAN FoundAttribute,
|
|
OUT PNTFS_ATTRIBUTE_CONTEXT AttributeContext
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsReadResidentAttribute (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsReadNonresidentAttribute (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsWriteNonresidentAttribute (
|
|
IN PNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsReadAndDecodeFileRecord (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN LONGLONG FileRecord,
|
|
OUT PULONG Index
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsDecodeUsa (
|
|
IN PVOID UsaBuffer,
|
|
IN ULONG Length
|
|
);
|
|
|
|
#define LookupAttribute(A,B,C,D,E) { ARC_STATUS _s; \
|
|
if ((_s = NtfsLookupAttribute(A,B,C,D,E)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
#define ReadAttribute(A,B,C,D,E) { ARC_STATUS _s; \
|
|
if ((B)->IsAttributeResident) { \
|
|
if ((_s = NtfsReadResidentAttribute(A,B,C,D,E)) != ESUCCESS) {return _s;} \
|
|
} else { \
|
|
if ((_s = NtfsReadNonresidentAttribute(A,B,C,D,E)) != ESUCCESS) {return _s;} \
|
|
} \
|
|
}
|
|
|
|
#define ReadAndDecodeFileRecord(A,B,C) { ARC_STATUS _s; \
|
|
if ((_s = NtfsReadAndDecodeFileRecord(A,B,C)) != ESUCCESS) { return _s; } \
|
|
}
|
|
|
|
#define DecodeUsa(A,B) { ARC_STATUS _s; \
|
|
if ((_s = NtfsDecodeUsa(A,B)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
|
|
//
|
|
// Directory and index lookup routines
|
|
//
|
|
//
|
|
// VOID
|
|
// SearchForFileName (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN CSTRING FileName,
|
|
// IN OUT PLONGLONG FileRecord,
|
|
// OUT PBOOLEAN FoundFileName,
|
|
// OUT PBOOLEAN IsDirectory
|
|
// );
|
|
//
|
|
// VOID
|
|
// IsRecordAllocated (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PCNTFS_ATTRIBUTE_CONTEXT AllocationBitmap,
|
|
// IN ULONG BitOffset,
|
|
// OUT PBOOLEAN IsAllocated
|
|
// );
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsSearchForFileName (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN FoundFileName,
|
|
OUT PBOOLEAN IsDirectory
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsIsRecordAllocated (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AllocationBitmap,
|
|
IN ULONG BitOffset,
|
|
OUT PBOOLEAN IsAllocated
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsLinearDirectoryScan(
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsInexactSortedDirectoryScan(
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
);
|
|
|
|
#define SearchForFileName(A,B,C,D,E) \
|
|
{ \
|
|
ARC_STATUS _s; \
|
|
if ((_s = NtfsSearchForFileName(A,B,C,D,E)) != ESUCCESS) { \
|
|
return _s; \
|
|
} \
|
|
}
|
|
|
|
#define IsRecordAllocated(A,B,C,D) \
|
|
{ \
|
|
ARC_STATUS _s; \
|
|
if ((_s = NtfsIsRecordAllocated(A,B,C,D)) != ESUCCESS) { \
|
|
return _s; \
|
|
} \
|
|
}
|
|
|
|
#define LinearDirectoryScan(A,B,C,D,E) \
|
|
{ \
|
|
ARC_STATUS _s; \
|
|
if ((_s = NtfsLinearDirectoryScan(A,B,C,D,E)) != ESUCCESS) {\
|
|
return _s; \
|
|
} \
|
|
}
|
|
|
|
#define InexactSortedDirectoryScan(A,B,C,D,E) \
|
|
{ \
|
|
ARC_STATUS _s; \
|
|
if ((_s = NtfsInexactSortedDirectoryScan(A,B,C,D,E)) != ESUCCESS) {\
|
|
return _s; \
|
|
} \
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Mcb support routines
|
|
//
|
|
//
|
|
// VOID
|
|
// LoadMcb (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
// IN VBO Vbo,
|
|
// IN PNTFS_MCB Mcb
|
|
// );
|
|
//
|
|
// VOID
|
|
// VboToLbo (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
// IN VBO Vbo,
|
|
// OUT PLBO Lbo,
|
|
// OUT PULONG ByteCount
|
|
// );
|
|
//
|
|
// VOID
|
|
// DecodeRetrievalInformation (
|
|
// IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PNTFS_MCB Mcb,
|
|
// IN VCN Vcn,
|
|
// IN PATTRIBUTE_RECORD_HEADER AttributeHeader
|
|
// );
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsLoadMcb (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN PNTFS_MCB Mcb
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsVboToLbo (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
OUT PLBO Lbo,
|
|
OUT PULONG ByteCount
|
|
);
|
|
|
|
ARC_STATUS
|
|
NtfsDecodeRetrievalInformation (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PNTFS_MCB Mcb,
|
|
IN VCN Vcn,
|
|
IN PATTRIBUTE_RECORD_HEADER AttributeHeader
|
|
);
|
|
|
|
#define LoadMcb(A,B,C,D) { ARC_STATUS _s; \
|
|
if ((_s = NtfsLoadMcb(A,B,C,D)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
#define VboToLbo(A,B,C,D,E) { ARC_STATUS _s; \
|
|
if ((_s = NtfsVboToLbo(A,B,C,D,E)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
#define DecodeRetrievalInformation(A,B,C,D) { ARC_STATUS _s; \
|
|
if ((_s = NtfsDecodeRetrievalInformation(A,B,C,D)) != ESUCCESS) {return _s;} \
|
|
}
|
|
|
|
|
|
//
|
|
// Miscellaneous routines
|
|
//
|
|
|
|
|
|
VOID
|
|
NtfsFirstComponent (
|
|
IN OUT PCSTRING String,
|
|
OUT PCSTRING FirstComponent
|
|
);
|
|
|
|
int
|
|
NtfsCompareName (
|
|
IN CSTRING AnsiString,
|
|
IN UNICODE_STRING UnicodeString
|
|
);
|
|
|
|
VOID
|
|
NtfsInvalidateCacheEntries(
|
|
IN ULONG DeviceId
|
|
);
|
|
//
|
|
// VOID
|
|
// FileReferenceToLargeInteger (
|
|
// IN PFILE_REFERENCE FileReference,
|
|
// OUT PLONGLONG LargeInteger
|
|
// );
|
|
//
|
|
// VOID
|
|
// InitializeAttributeContext (
|
|
// IN PNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
// IN PVOID FileRecordBuffer,
|
|
// IN PVOID AttributeHeader,
|
|
// IN LONGLONG FileRecord,
|
|
// OUT PNTFS_ATTRIBUTE_CONTEXT AttributeContext
|
|
// );
|
|
//
|
|
|
|
#define FileReferenceToLargeInteger(FR,LI) { \
|
|
*(LI) = *(PLONGLONG)&(FR); \
|
|
((PFILE_REFERENCE)(LI))->SequenceNumber = 0; \
|
|
}
|
|
|
|
//
|
|
//**** note that the code to get the compression engine will need to change
|
|
//**** once the NTFS format changes
|
|
//
|
|
|
|
#define InitializeAttributeContext(SC,FRB,AH,FR,AC) { \
|
|
(AC)->TypeCode = (AH)->TypeCode; \
|
|
(AC)->FileRecord = (FR); \
|
|
(AC)->FileRecordOffset = (USHORT)PtrOffset((FRB),(AH)); \
|
|
if (((AC)->IsAttributeResident = ((AH)->FormCode == RESIDENT_FORM)) != 0) { \
|
|
(AC)->DataSize = /*xxFromUlong*/((AH)->Form.Resident.ValueLength); \
|
|
} else { \
|
|
(AC)->DataSize = (AH)->Form.Nonresident.FileSize; \
|
|
} \
|
|
(AC)->CompressionFormat = COMPRESSION_FORMAT_NONE; \
|
|
if ((AH)->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK) { \
|
|
ULONG _i; \
|
|
(AC)->CompressionFormat = COMPRESSION_FORMAT_LZNT1; \
|
|
(AC)->CompressionUnit = (SC)->BytesPerCluster; \
|
|
for (_i = 0; _i < (AH)->Form.Nonresident.CompressionUnit; _i += 1) { \
|
|
(AC)->CompressionUnit *= 2; \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#define FlagOn(Flags,SingleFlag) ((BOOLEAN)(((Flags) & (SingleFlag)) != 0))
|
|
#define SetFlag(Flags,SingleFlag) { (Flags) |= (SingleFlag); }
|
|
#define ClearFlag(Flags,SingleFlag) { (Flags) &= ~(SingleFlag); }
|
|
|
|
#define Add2Ptr(POINTER,INCREMENT) ((PVOID)((PUCHAR)(POINTER) + (INCREMENT)))
|
|
#define PtrOffset(BASE,OFFSET) ((ULONG)((ULONG_PTR)(OFFSET) - (ULONG_PTR)(BASE)))
|
|
|
|
#define Minimum(X,Y) ((X) < (Y) ? (X) : (Y))
|
|
|
|
#define IsCharZero(C) (((C) & 0x000000ff) == 0x00000000)
|
|
#define IsCharLtrZero(C) (((C) & 0x00000080) == 0x00000080)
|
|
|
|
//
|
|
// The following types and macros are used to help unpack the packed and misaligned
|
|
// fields found in the Bios parameter block
|
|
//
|
|
|
|
typedef union _UCHAR1 { UCHAR Uchar[1]; UCHAR ForceAlignment; } UCHAR1, *PUCHAR1;
|
|
typedef union _UCHAR2 { UCHAR Uchar[2]; USHORT ForceAlignment; } UCHAR2, *PUCHAR2;
|
|
typedef union _UCHAR4 { UCHAR Uchar[4]; ULONG ForceAlignment; } UCHAR4, *PUCHAR4;
|
|
|
|
//
|
|
// This macro copies an unaligned src byte to an aligned dst byte
|
|
//
|
|
|
|
#define CopyUchar1(Dst,Src) { \
|
|
*((UCHAR1 *)(Dst)) = *((UNALIGNED UCHAR1 *)(Src)); \
|
|
}
|
|
|
|
//
|
|
// This macro copies an unaligned src word to an aligned dst word
|
|
//
|
|
|
|
#define CopyUchar2(Dst,Src) { \
|
|
*((UCHAR2 *)(Dst)) = *((UNALIGNED UCHAR2 *)(Src)); \
|
|
}
|
|
|
|
//
|
|
// This macro copies an unaligned src longword to an aligned dsr longword
|
|
//
|
|
|
|
#define CopyUchar4(Dst,Src) { \
|
|
*((UCHAR4 *)(Dst)) = *((UNALIGNED UCHAR4 *)(Src)); \
|
|
}
|
|
|
|
|
|
//
|
|
// Define global data.
|
|
//
|
|
|
|
ULONG LastMcb = 0;
|
|
BOOLEAN FirstTime = TRUE;
|
|
|
|
//
|
|
// File entry table - This is a structure that provides entry to the NTFS
|
|
// file system procedures. It is exported when a NTFS file structure
|
|
// is recognized.
|
|
//
|
|
|
|
BL_DEVICE_ENTRY_TABLE NtfsDeviceEntryTable;
|
|
|
|
//
|
|
// These are the static buffers that we use when read file records and index
|
|
// allocation buffers. To save ourselves some extra reads we will identify the
|
|
// current file record by its Vbo within the mft.
|
|
//
|
|
|
|
#define BUFFER_COUNT (64)
|
|
|
|
USHORT NtfsFileRecordBufferPinned[BUFFER_COUNT];
|
|
VBO NtfsFileRecordBufferVbo[BUFFER_COUNT];
|
|
PFILE_RECORD_SEGMENT_HEADER NtfsFileRecordBuffer[BUFFER_COUNT];
|
|
|
|
PINDEX_ALLOCATION_BUFFER NtfsIndexAllocationBuffer;
|
|
|
|
//
|
|
// The following field are used to identify and store the cached
|
|
// compressed buffer and its uncompressed equivalent. The first
|
|
// two fields identifies the attribute stream, and the third field
|
|
// identifies the Vbo within the attribute stream that we have
|
|
// cached. The compressed and uncompressed buffer contains
|
|
// the data.
|
|
//
|
|
|
|
LONGLONG NtfsCompressedFileRecord;
|
|
USHORT NtfsCompressedOffset;
|
|
ULONG NtfsCompressedVbo;
|
|
|
|
PUCHAR NtfsCompressedBuffer;
|
|
PUCHAR NtfsUncompressedBuffer;
|
|
|
|
UCHAR NtfsBuffer0[MAXIMUM_FILE_RECORD_SIZE+256];
|
|
UCHAR NtfsBuffer1[MAXIMUM_FILE_RECORD_SIZE+256];
|
|
UCHAR NtfsBuffer2[MAXIMUM_INDEX_ALLOCATION_SIZE+256];
|
|
UCHAR NtfsBuffer3[MAXIMUM_COMPRESSION_UNIT_SIZE+256];
|
|
UCHAR NtfsBuffer4[MAXIMUM_COMPRESSION_UNIT_SIZE+256];
|
|
|
|
//
|
|
// The following is a simple prefix cache to speed up directory traversal
|
|
//
|
|
|
|
typedef struct {
|
|
|
|
//
|
|
// DeviceId used to for I/O. Serves as unique volume identifier
|
|
//
|
|
|
|
ULONG DeviceId;
|
|
|
|
//
|
|
// Parent file record of entry
|
|
//
|
|
|
|
LONGLONG ParentFileRecord;
|
|
|
|
//
|
|
// Name length and text of entry. This is already uppercased!
|
|
//
|
|
|
|
ULONG NameLength;
|
|
UCHAR RelativeName[32];
|
|
|
|
//
|
|
// File record of name relative to parent
|
|
//
|
|
|
|
LONGLONG ChildFileRecord;
|
|
} NTFS_CACHE_ENTRY;
|
|
|
|
#define MAX_CACHE_ENTRIES 8
|
|
NTFS_CACHE_ENTRY NtfsLinkCache[MAX_CACHE_ENTRIES];
|
|
ULONG NtfsLinkCacheCount = 0;
|
|
|
|
|
|
PBL_DEVICE_ENTRY_TABLE
|
|
IsNtfsFileStructure (
|
|
IN ULONG DeviceId,
|
|
IN PVOID OpaqueStructureContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines if the partition on the specified channel contains an
|
|
Ntfs file system volume.
|
|
|
|
Arguments:
|
|
|
|
DeviceId - Supplies the file table index for the device on which read operations
|
|
are to be performed.
|
|
|
|
StructureContext - Supplies a pointer to a Ntfs file structure context.
|
|
|
|
Return Value:
|
|
|
|
A pointer to the Ntfs entry table is returned if the partition is recognized as
|
|
containing a Ntfs volume. Otherwise, NULL is returned.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_STRUCTURE_CONTEXT StructureContext = (PNTFS_STRUCTURE_CONTEXT)OpaqueStructureContext;
|
|
|
|
PPACKED_BOOT_SECTOR BootSector;
|
|
BIOS_PARAMETER_BLOCK Bpb;
|
|
|
|
ULONG ClusterSize;
|
|
ULONG FileRecordSize;
|
|
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
ULONG i;
|
|
|
|
//
|
|
// Clear the file system context block for the specified channel and initialize
|
|
// the global buffer pointers that we use for buffering I/O
|
|
//
|
|
|
|
RtlZeroMemory(StructureContext, sizeof(NTFS_STRUCTURE_CONTEXT));
|
|
|
|
//
|
|
// Zero out the pinned buffer array because we start with nothing pinned
|
|
// Also negate the vbo array to not let us get spooked with stale data
|
|
//
|
|
|
|
RtlZeroMemory( NtfsFileRecordBufferPinned, sizeof(NtfsFileRecordBufferPinned));
|
|
for (i = 0; i < BUFFER_COUNT; i += 1) { NtfsFileRecordBufferVbo[i] = -1; }
|
|
|
|
NtfsCompressedFileRecord = 0;
|
|
NtfsCompressedOffset = 0;
|
|
NtfsCompressedVbo = 0;
|
|
|
|
//
|
|
// Set up a local pointer that we will use to read in the boot sector and check
|
|
// for an Ntfs partition. We will temporarily use the global file record buffer
|
|
//
|
|
|
|
BootSector = (PPACKED_BOOT_SECTOR)NtfsFileRecordBuffer[0];
|
|
|
|
//
|
|
// Now read in the boot sector and return null if we can't do the read
|
|
//
|
|
|
|
if (NtfsReadDisk(DeviceId, 0, sizeof(PACKED_BOOT_SECTOR), BootSector, CACHE_NEW_DATA) != ESUCCESS) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Unpack the Bios parameter block
|
|
//
|
|
|
|
NtfsUnpackBios( &Bpb, &BootSector->PackedBpb );
|
|
|
|
//
|
|
// Check if it is NTFS, by first checking the signature, then must be zero
|
|
// fields, then the media type, and then sanity check the non zero fields.
|
|
//
|
|
|
|
if (RtlCompareMemory( &BootSector->Oem[0], "NTFS ", 8) != 8) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((Bpb.ReservedSectors != 0) ||
|
|
(Bpb.Fats != 0) ||
|
|
(Bpb.RootEntries != 0) ||
|
|
(Bpb.Sectors != 0) ||
|
|
(Bpb.SectorsPerFat != 0) ||
|
|
(Bpb.LargeSectors != 0)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((Bpb.Media != 0xf0) &&
|
|
(Bpb.Media != 0xf8) &&
|
|
(Bpb.Media != 0xf9) &&
|
|
(Bpb.Media != 0xfc) &&
|
|
(Bpb.Media != 0xfd) &&
|
|
(Bpb.Media != 0xfe) &&
|
|
(Bpb.Media != 0xff)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((Bpb.BytesPerSector != 128) &&
|
|
(Bpb.BytesPerSector != 256) &&
|
|
(Bpb.BytesPerSector != 512) &&
|
|
(Bpb.BytesPerSector != 1024) &&
|
|
(Bpb.BytesPerSector != 2048)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((Bpb.SectorsPerCluster != 1) &&
|
|
(Bpb.SectorsPerCluster != 2) &&
|
|
(Bpb.SectorsPerCluster != 4) &&
|
|
(Bpb.SectorsPerCluster != 8) &&
|
|
(Bpb.SectorsPerCluster != 16) &&
|
|
(Bpb.SectorsPerCluster != 32) &&
|
|
(Bpb.SectorsPerCluster != 64) &&
|
|
(Bpb.SectorsPerCluster != 128)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((BootSector->NumberSectors == 0) ||
|
|
(BootSector->MftStartLcn == 0) ||
|
|
(BootSector->Mft2StartLcn == 0) ||
|
|
(BootSector->ClustersPerFileRecordSegment == 0) ||
|
|
(BootSector->DefaultClustersPerIndexAllocationBuffer == 0)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if ((BootSector->ClustersPerFileRecordSegment < 0) &&
|
|
((BootSector->ClustersPerFileRecordSegment > -9) ||
|
|
(BootSector->ClustersPerFileRecordSegment < -31))) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// So far the boot sector has checked out to be an NTFS partition so now compute
|
|
// some of the volume constants.
|
|
//
|
|
|
|
StructureContext->DeviceId = DeviceId;
|
|
|
|
StructureContext->BytesPerCluster =
|
|
ClusterSize = Bpb.SectorsPerCluster * Bpb.BytesPerSector;
|
|
|
|
//
|
|
// If the number of clusters per file record is less than zero then the file record
|
|
// size computed by using the negative of this number as a shift value.
|
|
//
|
|
|
|
if (BootSector->ClustersPerFileRecordSegment > 0) {
|
|
|
|
StructureContext->BytesPerFileRecord =
|
|
FileRecordSize = BootSector->ClustersPerFileRecordSegment * ClusterSize;
|
|
|
|
} else {
|
|
|
|
StructureContext->BytesPerFileRecord =
|
|
FileRecordSize = 1 << (-1 * BootSector->ClustersPerFileRecordSegment);
|
|
}
|
|
|
|
//
|
|
// Read in the base file record for the mft
|
|
//
|
|
|
|
if (NtfsReadDisk( DeviceId,
|
|
/*xxXMul*/(BootSector->MftStartLcn * ClusterSize),
|
|
FileRecordSize,
|
|
NtfsFileRecordBuffer[0],
|
|
CACHE_NEW_DATA) != ESUCCESS) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Decode Usa for the file record
|
|
//
|
|
|
|
if (NtfsDecodeUsa(NtfsFileRecordBuffer[0], FileRecordSize) != ESUCCESS) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Make sure the file record is in use
|
|
//
|
|
|
|
if (!FlagOn(NtfsFileRecordBuffer[0]->Flags, FILE_RECORD_SEGMENT_IN_USE)) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Search for the unnamed $data attribute header, if we reach $end then it is
|
|
// an error
|
|
//
|
|
|
|
for (AttributeHeader = NtfsFirstAttribute( NtfsFileRecordBuffer[0] );
|
|
(AttributeHeader->TypeCode != $DATA) || (AttributeHeader->NameLength != 0);
|
|
AttributeHeader = NtfsGetNextRecord( AttributeHeader )) {
|
|
|
|
if (AttributeHeader->TypeCode == $END) {
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the $data attribute for the mft is non resident
|
|
//
|
|
|
|
if (AttributeHeader->FormCode != NONRESIDENT_FORM) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Now set the mft structure context up for later use
|
|
//
|
|
|
|
InitializeAttributeContext( StructureContext,
|
|
NtfsFileRecordBuffer[0],
|
|
AttributeHeader,
|
|
0,
|
|
&StructureContext->MftAttributeContext );
|
|
|
|
//
|
|
// Now decipher the part of the Mcb that is stored in the file record
|
|
//
|
|
|
|
if (NtfsDecodeRetrievalInformation( StructureContext,
|
|
&StructureContext->MftBaseMcb,
|
|
0,
|
|
AttributeHeader ) != ESUCCESS) {
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// We have finished initializing the structure context so now Initialize the
|
|
// file entry table and return the address of the table.
|
|
//
|
|
|
|
NtfsDeviceEntryTable.Open = NtfsOpen;
|
|
NtfsDeviceEntryTable.Close = NtfsClose;
|
|
NtfsDeviceEntryTable.Read = NtfsRead;
|
|
NtfsDeviceEntryTable.Seek = NtfsSeek;
|
|
NtfsDeviceEntryTable.Write = NtfsWrite;
|
|
NtfsDeviceEntryTable.GetFileInformation = NtfsGetFileInformation;
|
|
NtfsDeviceEntryTable.SetFileInformation = NtfsSetFileInformation;
|
|
NtfsDeviceEntryTable.BootFsInfo = &NtfsBootFsInfo;
|
|
|
|
return &NtfsDeviceEntryTable;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsClose (
|
|
IN ULONG FileId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine closes the file specified by the file id.
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the file table index.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS if returned as the function value.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Indicate that the file isn't open any longer
|
|
//
|
|
BlFileTable[FileId].Flags.Open = 0;
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsGetFileInformation (
|
|
IN ULONG FileId,
|
|
OUT FILE_INFORMATION * FIRMWARE_PTR Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This procedure returns to the user a buffer filled with file information
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the File id for the operation
|
|
|
|
Buffer - Supplies the buffer to receive the file information. Note that
|
|
it must be large enough to hold the full file name
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the open operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBL_FILE_TABLE FileTableEntry;
|
|
PNTFS_STRUCTURE_CONTEXT StructureContext;
|
|
PNTFS_FILE_CONTEXT FileContext;
|
|
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext;
|
|
BOOLEAN Found;
|
|
|
|
STANDARD_INFORMATION StandardInformation;
|
|
|
|
ULONG i;
|
|
|
|
//
|
|
// Setup some local references
|
|
//
|
|
|
|
FileTableEntry = &BlFileTable[FileId];
|
|
StructureContext = (PNTFS_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
|
|
FileContext = &FileTableEntry->u.NtfsFileContext;
|
|
|
|
//
|
|
// Zero out the output buffer and fill in its non-zero values
|
|
//
|
|
|
|
RtlZeroMemory(Buffer, sizeof(FILE_INFORMATION));
|
|
|
|
Buffer->EndingAddress.QuadPart = FileContext->DataSize;
|
|
Buffer->CurrentPosition = FileTableEntry->Position;
|
|
|
|
//
|
|
// Locate and read in the standard information for the file. This will get us
|
|
// the attributes for the file.
|
|
//
|
|
|
|
LookupAttribute( StructureContext,
|
|
FileContext->FileRecord,
|
|
$STANDARD_INFORMATION,
|
|
&Found,
|
|
&AttributeContext );
|
|
|
|
if (!Found) { return EBADF; }
|
|
|
|
ReadAttribute( StructureContext,
|
|
&AttributeContext,
|
|
0,
|
|
sizeof(STANDARD_INFORMATION),
|
|
&StandardInformation );
|
|
|
|
//
|
|
// Now check for set bits in the standard information structure and set the
|
|
// appropriate bits in the output buffer
|
|
//
|
|
|
|
if (FlagOn(StandardInformation.FileAttributes, FAT_DIRENT_ATTR_READ_ONLY)) {
|
|
|
|
SetFlag(Buffer->Attributes, ArcReadOnlyFile);
|
|
}
|
|
|
|
if (FlagOn(StandardInformation.FileAttributes, FAT_DIRENT_ATTR_HIDDEN)) {
|
|
|
|
SetFlag(Buffer->Attributes, ArcHiddenFile);
|
|
}
|
|
|
|
if (FlagOn(StandardInformation.FileAttributes, FAT_DIRENT_ATTR_SYSTEM)) {
|
|
|
|
SetFlag(Buffer->Attributes, ArcSystemFile);
|
|
}
|
|
|
|
if (FlagOn(StandardInformation.FileAttributes, FAT_DIRENT_ATTR_ARCHIVE)) {
|
|
|
|
SetFlag(Buffer->Attributes, ArcArchiveFile);
|
|
}
|
|
|
|
if (FlagOn(StandardInformation.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT)) {
|
|
|
|
SetFlag(Buffer->Attributes, ArcDirectoryFile);
|
|
}
|
|
|
|
//
|
|
// Get the file name from the file table entry
|
|
//
|
|
|
|
Buffer->FileNameLength = FileTableEntry->FileNameLength;
|
|
|
|
for (i = 0; i < FileTableEntry->FileNameLength; i += 1) {
|
|
|
|
Buffer->FileName[i] = FileTableEntry->FileName[i];
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsOpen (
|
|
IN CHAR * FIRMWARE_PTR RWFileName,
|
|
IN OPEN_MODE OpenMode,
|
|
IN ULONG * FIRMWARE_PTR FileId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches the root directory for a file matching FileName.
|
|
If a match is found the dirent for the file is saved and the file is
|
|
opened.
|
|
|
|
Arguments:
|
|
|
|
FileName - Supplies a pointer to a zero terminated file name.
|
|
|
|
OpenMode - Supplies the mode of the open.
|
|
|
|
FileId - Supplies a pointer to a variable that specifies the file
|
|
table entry that is to be filled in if the open is successful.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the open operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
const CHAR * FIRMWARE_PTR FileName = (const CHAR * FIRMWARE_PTR)RWFileName;
|
|
PBL_FILE_TABLE FileTableEntry;
|
|
PNTFS_STRUCTURE_CONTEXT StructureContext;
|
|
PNTFS_FILE_CONTEXT FileContext;
|
|
|
|
CSTRING PathName;
|
|
CSTRING Name;
|
|
|
|
LONGLONG FileRecord;
|
|
BOOLEAN IsDirectory;
|
|
BOOLEAN Found;
|
|
|
|
PausedPrint(( "NtfsOpen(\"%s\")\r\n", FileName ));
|
|
|
|
//
|
|
// Load our local variables
|
|
//
|
|
|
|
FileTableEntry = &BlFileTable[*FileId];
|
|
StructureContext = (PNTFS_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
|
|
FileContext = &FileTableEntry->u.NtfsFileContext;
|
|
|
|
//
|
|
// Zero out the file context and position information in the file table entry
|
|
//
|
|
|
|
FileTableEntry->Position.QuadPart = 0;
|
|
|
|
RtlZeroMemory(FileContext, sizeof(NTFS_FILE_CONTEXT));
|
|
|
|
//
|
|
// Construct a file name descriptor from the input file name
|
|
//
|
|
|
|
RtlInitString( (PSTRING)&PathName, FileName );
|
|
|
|
//
|
|
// Open the root directory as our starting point, The root directory file
|
|
// reference number is 5.
|
|
//
|
|
|
|
FileRecord = 5;
|
|
IsDirectory = TRUE;
|
|
|
|
//
|
|
// While the path name has some characters left in it and current attribute
|
|
// context is a directory we will continue our search
|
|
//
|
|
|
|
while ((PathName.Length > 0) && IsDirectory) {
|
|
|
|
//
|
|
// Extract the first component and search the directory for a match, but
|
|
// first copy the first part to the file name buffer in the file table entry
|
|
//
|
|
|
|
if (PathName.Buffer[0] == '\\') {
|
|
|
|
PathName.Buffer +=1;
|
|
PathName.Length -=1;
|
|
}
|
|
|
|
for (FileTableEntry->FileNameLength = 0;
|
|
(((USHORT)FileTableEntry->FileNameLength < PathName.Length) &&
|
|
(PathName.Buffer[FileTableEntry->FileNameLength] != '\\'));
|
|
FileTableEntry->FileNameLength += 1) {
|
|
|
|
FileTableEntry->FileName[FileTableEntry->FileNameLength] =
|
|
PathName.Buffer[FileTableEntry->FileNameLength];
|
|
}
|
|
|
|
NtfsFirstComponent( &PathName, &Name );
|
|
|
|
//
|
|
// Search for the name in the current directory
|
|
//
|
|
|
|
SearchForFileName( StructureContext,
|
|
Name,
|
|
&FileRecord,
|
|
&Found,
|
|
&IsDirectory );
|
|
|
|
//
|
|
// If we didn't find it then we should get out right now
|
|
//
|
|
|
|
if (!Found) { return ENOENT; }
|
|
}
|
|
|
|
//
|
|
// At this point we have exhausted our pathname or we did not get a directory
|
|
// Check if we didn't get a directory and we still have a name to crack
|
|
//
|
|
|
|
if (PathName.Length > 0) {
|
|
|
|
return ENOTDIR;
|
|
}
|
|
|
|
//
|
|
// Now FileRecord is the one we wanted to open. Check the various open modes
|
|
// against what we have located
|
|
//
|
|
|
|
if (IsDirectory) {
|
|
|
|
switch (OpenMode) {
|
|
|
|
case ArcOpenDirectory:
|
|
|
|
//
|
|
// To open the directory we will lookup the index root as our file
|
|
// context and then increment the appropriate counters.
|
|
//
|
|
|
|
LookupAttribute( StructureContext,
|
|
FileRecord,
|
|
$INDEX_ROOT,
|
|
&Found,
|
|
FileContext );
|
|
|
|
if (!Found) { return EBADF; }
|
|
|
|
FileTableEntry->Flags.Open = 1;
|
|
FileTableEntry->Flags.Read = 1;
|
|
|
|
return ESUCCESS;
|
|
|
|
case ArcCreateDirectory:
|
|
|
|
return EROFS;
|
|
|
|
default:
|
|
|
|
return EISDIR;
|
|
}
|
|
|
|
}
|
|
|
|
switch (OpenMode) {
|
|
|
|
case ArcOpenReadWrite:
|
|
//
|
|
// The only file allowed to be opened with write access is the hiberfil
|
|
//
|
|
if (!strstr(FileName, "\\hiberfil.sys") &&
|
|
!strstr(FileName, BSD_FILE_NAME)) {
|
|
return EROFS;
|
|
}
|
|
|
|
//
|
|
// To open the file we will lookup the $data as our file context and then
|
|
// increment the appropriate counters.
|
|
//
|
|
|
|
LookupAttribute( StructureContext,
|
|
FileRecord,
|
|
$DATA,
|
|
&Found,
|
|
FileContext );
|
|
|
|
if (!Found) { return EBADF; }
|
|
|
|
FileTableEntry->Flags.Open = 1;
|
|
FileTableEntry->Flags.Read = 1;
|
|
FileTableEntry->Flags.Write = 1;
|
|
return ESUCCESS;
|
|
|
|
case ArcOpenReadOnly:
|
|
|
|
//
|
|
// To open the file we will lookup the $data as our file context and then
|
|
// increment the appropriate counters.
|
|
//
|
|
|
|
LookupAttribute( StructureContext,
|
|
FileRecord,
|
|
$DATA,
|
|
&Found,
|
|
FileContext );
|
|
|
|
if (!Found) { return EBADF; }
|
|
|
|
FileTableEntry->Flags.Open = 1;
|
|
FileTableEntry->Flags.Read = 1;
|
|
|
|
return ESUCCESS;
|
|
|
|
case ArcOpenDirectory:
|
|
|
|
return ENOTDIR;
|
|
|
|
default:
|
|
|
|
return EROFS;
|
|
}
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsRead (
|
|
IN ULONG FileId,
|
|
OUT VOID * FIRMWARE_PTR Buffer,
|
|
IN ULONG Length,
|
|
OUT ULONG * FIRMWARE_PTR Transfer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads data from the specified file.
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the file table index.
|
|
|
|
Buffer - Supplies a pointer to the buffer that receives the data
|
|
read.
|
|
|
|
Length - Supplies the number of bytes that are to be read.
|
|
|
|
Transfer - Supplies a pointer to a variable that receives the number
|
|
of bytes actually transfered.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the read operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBL_FILE_TABLE FileTableEntry;
|
|
PNTFS_STRUCTURE_CONTEXT StructureContext;
|
|
PNTFS_FILE_CONTEXT FileContext;
|
|
|
|
LONGLONG AmountLeft;
|
|
|
|
//
|
|
// Setup some local references
|
|
//
|
|
|
|
FileTableEntry = &BlFileTable[FileId];
|
|
StructureContext = (PNTFS_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
|
|
FileContext = &FileTableEntry->u.NtfsFileContext;
|
|
|
|
//
|
|
// Compute the amount left in the file and then from that we compute the amount
|
|
// for the transfer
|
|
//
|
|
|
|
AmountLeft = /*xxSub*/( FileContext->DataSize - FileTableEntry->Position.QuadPart);
|
|
|
|
if (/*xxLeq*/(/*xxFromUlong*/(Length) <= AmountLeft)) {
|
|
|
|
*Transfer = Length;
|
|
|
|
} else {
|
|
|
|
*Transfer = ((ULONG)AmountLeft);
|
|
}
|
|
|
|
//
|
|
// Now issue the read attribute
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
FileContext,
|
|
FileTableEntry->Position.QuadPart,
|
|
*Transfer,
|
|
Buffer );
|
|
|
|
//
|
|
// Update the current position, and return to our caller
|
|
//
|
|
|
|
FileTableEntry->Position.QuadPart = /*xxAdd*/(FileTableEntry->Position.QuadPart + /*xxFromUlong*/(*Transfer));
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsSeek (
|
|
IN ULONG FileId,
|
|
IN LARGE_INTEGER * FIRMWARE_PTR Offset,
|
|
IN SEEK_MODE SeekMode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine seeks to the specified position for the file specified
|
|
by the file id.
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the file table index.
|
|
|
|
Offset - Supplies the offset in the file to position to.
|
|
|
|
SeekMode - Supplies the mode of the seek operation.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS if returned as the function value.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBL_FILE_TABLE FileTableEntry;
|
|
LONGLONG NewPosition;
|
|
|
|
//
|
|
// Load our local variables
|
|
//
|
|
|
|
FileTableEntry = &BlFileTable[FileId];
|
|
|
|
//
|
|
// Compute the new position
|
|
//
|
|
|
|
if (SeekMode == SeekAbsolute) {
|
|
|
|
NewPosition = Offset->QuadPart;
|
|
|
|
} else {
|
|
|
|
NewPosition = /*xxAdd*/(FileTableEntry->Position.QuadPart + Offset->QuadPart);
|
|
}
|
|
|
|
//
|
|
// If the new position is greater than the file size then return an error
|
|
//
|
|
|
|
if (/*xxGtr*/(NewPosition > FileTableEntry->u.NtfsFileContext.DataSize)) {
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
//
|
|
// Otherwise set the new position and return to our caller
|
|
//
|
|
|
|
FileTableEntry->Position.QuadPart = NewPosition;
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsSetFileInformation (
|
|
IN ULONG FileId,
|
|
IN ULONG AttributeFlags,
|
|
IN ULONG AttributeMask
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine sets the file attributes of the indicated file
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the File Id for the operation
|
|
|
|
AttributeFlags - Supplies the value (on or off) for each attribute being modified
|
|
|
|
AttributeMask - Supplies a mask of the attributes being altered. All other
|
|
file attributes are left alone.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the read operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
UNREFERENCED_PARAMETER( FileId );
|
|
UNREFERENCED_PARAMETER( AttributeFlags );
|
|
UNREFERENCED_PARAMETER( AttributeMask );
|
|
|
|
return EROFS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsWrite (
|
|
IN ULONG FileId,
|
|
IN VOID * FIRMWARE_PTR Buffer,
|
|
IN ULONG Length,
|
|
OUT ULONG * FIRMWARE_PTR Transfer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine writes data to the specified file.
|
|
|
|
Arguments:
|
|
|
|
FileId - Supplies the file table index.
|
|
|
|
Buffer - Supplies a pointer to the buffer that contains the data
|
|
written.
|
|
|
|
Length - Supplies the number of bytes that are to be written.
|
|
|
|
Transfer - Supplies a pointer to a variable that receives the number
|
|
of bytes actually transfered.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the write operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PBL_FILE_TABLE FileTableEntry;
|
|
PNTFS_STRUCTURE_CONTEXT StructureContext;
|
|
PNTFS_FILE_CONTEXT FileContext;
|
|
LONGLONG AmountLeft;
|
|
ULONG Status;
|
|
|
|
//
|
|
// Setup some local references
|
|
//
|
|
|
|
FileTableEntry = &BlFileTable[FileId];
|
|
StructureContext = (PNTFS_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
|
|
FileContext = &FileTableEntry->u.NtfsFileContext;
|
|
|
|
//
|
|
// Compute the amount left in the file and then from that we compute the amount
|
|
// for the transfer
|
|
//
|
|
|
|
AmountLeft = /*xxSub*/( FileContext->DataSize - FileTableEntry->Position.QuadPart);
|
|
|
|
if (Length <= AmountLeft) {
|
|
|
|
*Transfer = Length;
|
|
|
|
} else {
|
|
|
|
*Transfer = ((ULONG)AmountLeft);
|
|
}
|
|
|
|
//
|
|
// Now issue the write attribute
|
|
//
|
|
|
|
if (FileContext->IsAttributeResident) {
|
|
return EROFS;
|
|
}
|
|
|
|
Status = NtfsWriteNonresidentAttribute(
|
|
StructureContext,
|
|
FileContext,
|
|
FileTableEntry->Position.QuadPart,
|
|
*Transfer,
|
|
Buffer
|
|
);
|
|
|
|
if (Status != ESUCCESS) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Update the current position, and return to our caller
|
|
//
|
|
|
|
FileTableEntry->Position.QuadPart += *Transfer;
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsInitialize (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the ntfs boot filesystem.
|
|
Currently this is a no-op.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// The first time we will zero out the file record buffer and allocate
|
|
// a few buffers for read in data.
|
|
//
|
|
ARC_STATUS Status = ESUCCESS;
|
|
ULONG Index = 0;
|
|
|
|
RtlZeroMemory(NtfsLinkCache, sizeof(NtfsLinkCache));
|
|
|
|
for (Index=0; Index < MAX_CACHE_ENTRIES; Index++) {
|
|
NtfsLinkCache[Index].DeviceId = UNINITIALIZED_DEVICE_ID;
|
|
}
|
|
|
|
RtlZeroMemory( NtfsFileRecordBuffer, sizeof(NtfsFileRecordBuffer));
|
|
|
|
NtfsFileRecordBuffer[0] = ALIGN_BUFFER(NtfsBuffer0);
|
|
NtfsFileRecordBuffer[1] = ALIGN_BUFFER(NtfsBuffer1);
|
|
NtfsIndexAllocationBuffer = ALIGN_BUFFER(NtfsBuffer2);
|
|
NtfsCompressedBuffer = ALIGN_BUFFER(NtfsBuffer3);
|
|
NtfsUncompressedBuffer = ALIGN_BUFFER(NtfsBuffer4);
|
|
|
|
#ifdef CACHE_DEVINFO
|
|
|
|
Status = ArcRegisterForDeviceClose(NtfsInvalidateCacheEntries);
|
|
|
|
#endif // for CACHE_DEV_INFO
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsReadDisk (
|
|
IN ULONG DeviceId,
|
|
IN LONGLONG Lbo,
|
|
IN ULONG ByteCount,
|
|
IN OUT PVOID Buffer,
|
|
IN BOOLEAN CacheNewData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads in zero or more bytes from the specified device.
|
|
|
|
Arguments:
|
|
|
|
DeviceId - Supplies the device id to use in the arc calls.
|
|
|
|
Lbo - Supplies the LBO to start reading from.
|
|
|
|
ByteCount - Supplies the number of bytes to read.
|
|
|
|
Buffer - Supplies a pointer to the buffer to read the bytes into.
|
|
|
|
CacheNewData - Whether to cache new data read from the disk.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the read operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ARC_STATUS Status;
|
|
ULONG i;
|
|
|
|
//
|
|
// Special case the zero byte read request
|
|
//
|
|
|
|
if (ByteCount == 0) {
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// Issue the read through the cache.
|
|
//
|
|
|
|
Status = BlDiskCacheRead(DeviceId,
|
|
(PLARGE_INTEGER)&Lbo,
|
|
Buffer,
|
|
ByteCount,
|
|
&i,
|
|
CacheNewData);
|
|
|
|
if (Status != ESUCCESS) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Make sure we got back the amount requested
|
|
//
|
|
|
|
if (ByteCount != i) {
|
|
|
|
return EIO;
|
|
}
|
|
|
|
//
|
|
// Everything is fine so return success to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsWriteDisk (
|
|
IN ULONG DeviceId,
|
|
IN LONGLONG Lbo,
|
|
IN ULONG ByteCount,
|
|
IN OUT PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine Writes in zero or more bytes from the specified device.
|
|
|
|
Arguments:
|
|
|
|
DeviceId - Supplies the device id to use in the arc calls.
|
|
|
|
Lbo - Supplies the LBO to start Writeing from.
|
|
|
|
ByteCount - Supplies the number of bytes to Write.
|
|
|
|
Buffer - Supplies a pointer to the buffer to Write the bytes into.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the Write operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ARC_STATUS Status;
|
|
ULONG i;
|
|
|
|
//
|
|
// Special case the zero byte Write request
|
|
//
|
|
|
|
if (ByteCount == 0) {
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Issue the write through the cache.
|
|
//
|
|
|
|
Status = BlDiskCacheWrite (DeviceId,
|
|
(PLARGE_INTEGER) &Lbo,
|
|
Buffer,
|
|
ByteCount,
|
|
&i);
|
|
|
|
if (Status != ESUCCESS) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Make sure we got back the amount requested
|
|
//
|
|
|
|
if (ByteCount != i) {
|
|
|
|
return EIO;
|
|
}
|
|
|
|
//
|
|
// Everything is fine so return success to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsLookupAttribute (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN LONGLONG FileRecord,
|
|
IN ATTRIBUTE_TYPE_CODE TypeCode,
|
|
OUT PBOOLEAN FoundAttribute,
|
|
OUT PNTFS_ATTRIBUTE_CONTEXT AttributeContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine search the input file record for the indicated
|
|
attribute record. It will search through multiple related
|
|
file records to find the attribute. If the type code is for $data
|
|
then the attribute we look for must be unnamed otherwise we will
|
|
ignore the names of the attributes and return the first attriubute
|
|
of the indicated type.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileRecord - Supplies the file record to start searching from. This need
|
|
not be the base file record.
|
|
|
|
TypeCode - Supplies the attribute type that we are looking for
|
|
|
|
FoundAttribute - Receives an indicating if the attribute was located
|
|
|
|
AttributeContext - Receives the attribute context for the found attribute
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext1;
|
|
PNTFS_ATTRIBUTE_CONTEXT AttributeList;
|
|
|
|
LONGLONG li;
|
|
ATTRIBUTE_LIST_ENTRY AttributeListEntry;
|
|
|
|
ULONG BufferIndex;
|
|
|
|
//
|
|
// Unless other noted we will assume we haven't found the attribute
|
|
//
|
|
|
|
*FoundAttribute = FALSE;
|
|
|
|
//
|
|
// Read in the file record and if necessary move ourselves up to the base file
|
|
// record
|
|
//
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
FileRecord,
|
|
&BufferIndex );
|
|
|
|
if (/*!xxEqlZero*/(*((PLONGLONG)&(NtfsFileRecordBuffer[BufferIndex]->BaseFileRecordSegment)) != 0)) {
|
|
|
|
//
|
|
// This isn't the base file record so now extract the base file record
|
|
// number and read it in
|
|
//
|
|
|
|
FileReferenceToLargeInteger( NtfsFileRecordBuffer[BufferIndex]->BaseFileRecordSegment,
|
|
&FileRecord );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
FileRecord,
|
|
&BufferIndex );
|
|
}
|
|
|
|
//
|
|
// Now we have read in the base file record so search for the target attribute
|
|
// type code and also remember if we find the attribute list attribute
|
|
//
|
|
|
|
AttributeList = NULL;
|
|
|
|
for (AttributeHeader = NtfsFirstAttribute( NtfsFileRecordBuffer[BufferIndex] );
|
|
AttributeHeader->TypeCode != $END;
|
|
AttributeHeader = NtfsGetNextRecord( AttributeHeader )) {
|
|
|
|
//
|
|
// We have located the attribute in question if the type code match and if
|
|
// it is either not the data attribute or if it is the data attribute then
|
|
// it is also unnamed
|
|
//
|
|
|
|
if ((AttributeHeader->TypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeHeader->NameLength == 0)))
|
|
|
|
&&
|
|
|
|
((AttributeHeader->FormCode != NONRESIDENT_FORM) ||
|
|
(AttributeHeader->Form.Nonresident.LowestVcn == 0))) {
|
|
|
|
//
|
|
// Indicate that we have found the attribute and setup the output
|
|
// attribute context and then return to our caller
|
|
//
|
|
|
|
*FoundAttribute = TRUE;
|
|
|
|
InitializeAttributeContext( StructureContext,
|
|
NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeHeader,
|
|
FileRecord,
|
|
AttributeContext );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// Check if this is the attribute list attribute and if so then setup a
|
|
// local attribute context to use just in case we don't find the attribute
|
|
// we're after in the base file record
|
|
//
|
|
|
|
if (AttributeHeader->TypeCode == $ATTRIBUTE_LIST) {
|
|
|
|
InitializeAttributeContext( StructureContext,
|
|
NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeHeader,
|
|
FileRecord,
|
|
AttributeList = &AttributeContext1 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we reach this point then the attribute has not been found in the base file
|
|
// record so check if we have located an attribute list. If not then the search
|
|
// has not been successful
|
|
//
|
|
|
|
if (AttributeList == NULL) {
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// Now that we've located the attribute list we need to continue our search. So
|
|
// what this outer loop does is search down the attribute list looking for a
|
|
// match.
|
|
//
|
|
|
|
for (li = 0;
|
|
/*xxLtr*/(li < AttributeList->DataSize);
|
|
li = /*xxAdd*/(li + /*xxFromUlong*/(AttributeListEntry.RecordLength))) {
|
|
|
|
//
|
|
// Read in the attribute list entry. We don't need to read in the name,
|
|
// just the first part of the list entry.
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
AttributeList,
|
|
li,
|
|
sizeof(ATTRIBUTE_LIST_ENTRY),
|
|
&AttributeListEntry );
|
|
|
|
//
|
|
// Now check if the attribute matches, and it is the first of multiple
|
|
// segments, and either it is not $data or if it is $data then it is unnamed
|
|
//
|
|
|
|
if ((AttributeListEntry.AttributeTypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
/*xxEqlZero*/(AttributeListEntry.LowestVcn == 0)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeListEntry.AttributeNameLength == 0)))) {
|
|
|
|
//
|
|
// We found a match so now compute the file record containing the
|
|
// attribute we're after and read in the file record
|
|
//
|
|
|
|
FileReferenceToLargeInteger( AttributeListEntry.SegmentReference,
|
|
&FileRecord );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
FileRecord,
|
|
&BufferIndex );
|
|
|
|
//
|
|
// Now search down the file record for our matching attribute, and it
|
|
// better be there otherwise the attribute list is wrong.
|
|
//
|
|
|
|
for (AttributeHeader = NtfsFirstAttribute( NtfsFileRecordBuffer[BufferIndex] );
|
|
AttributeHeader->TypeCode != $END;
|
|
AttributeHeader = NtfsGetNextRecord( AttributeHeader )) {
|
|
|
|
//
|
|
// We have located the attribute in question if the type code match
|
|
// and if it is either not the data attribute or if it is the data
|
|
// attribute then it is also unnamed
|
|
//
|
|
|
|
if ((AttributeHeader->TypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeHeader->NameLength == 0)))) {
|
|
|
|
//
|
|
// Indicate that we have found the attribute and setup the
|
|
// output attribute context and return to our caller
|
|
//
|
|
|
|
*FoundAttribute = TRUE;
|
|
|
|
InitializeAttributeContext( StructureContext,
|
|
NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeHeader,
|
|
FileRecord,
|
|
AttributeContext );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
}
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return EBADF;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we reach this point we've exhausted the attribute list without finding the
|
|
// attribute
|
|
//
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsReadResidentAttribute (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads in the value of a resident attribute. The attribute
|
|
must be resident.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AttributeContext - Supplies the attribute being read.
|
|
|
|
Vbo - Supplies the offset within the value to return
|
|
|
|
Length - Supplies the number of bytes to return
|
|
|
|
Buffer - Supplies a pointer to the output buffer for storing the data
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the read operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
ULONG BufferIndex;
|
|
|
|
//
|
|
// Read in the file record containing the resident attribute
|
|
//
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
AttributeContext->FileRecord,
|
|
&BufferIndex );
|
|
|
|
//
|
|
// Get a pointer to the attribute header
|
|
//
|
|
|
|
AttributeHeader = Add2Ptr( NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeContext->FileRecordOffset );
|
|
|
|
//
|
|
// Copy the amount of data the user asked for starting with the proper offset
|
|
//
|
|
|
|
RtlMoveMemory( Buffer,
|
|
Add2Ptr(NtfsGetValue(AttributeHeader), ((ULONG)Vbo)),
|
|
Length );
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsReadNonresidentAttribute (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads in the value of a Nonresident attribute. The attribute
|
|
must be Nonresident.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AttributeContext - Supplies the attribute being read.
|
|
|
|
Vbo - Supplies the offset within the value to return
|
|
|
|
Length - Supplies the number of bytes to return
|
|
|
|
Buffer - Supplies a pointer to the output buffer for storing the data
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the read operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN bCacheNewData;
|
|
|
|
//
|
|
// We want to cache new data read from the disk to satisfy this
|
|
// request only if we are reading the MFT, or $INDEX_ROOT,
|
|
// $BITMAP or $INDEX_ALLOCATION attributes for directory look
|
|
// up. $INDEX_ROOT is supposed to be resident in the file record
|
|
// but we want cache a read we make for it otherwise.
|
|
//
|
|
|
|
if ((AttributeContext == &StructureContext->MftAttributeContext) ||
|
|
(AttributeContext->TypeCode == $INDEX_ROOT) ||
|
|
(AttributeContext->TypeCode == $INDEX_ALLOCATION) ||
|
|
(AttributeContext->TypeCode == $BITMAP)) {
|
|
|
|
bCacheNewData = CACHE_NEW_DATA;
|
|
|
|
} else {
|
|
|
|
bCacheNewData = DONT_CACHE_NEW_DATA;
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Check if we are reading a compressed attribute
|
|
//
|
|
|
|
if (AttributeContext->CompressionFormat != 0) {
|
|
|
|
//
|
|
// While there is still some more to copy into the
|
|
// caller's buffer, we will load the cached compressed buffers
|
|
// and then copy out the data
|
|
//
|
|
|
|
while (Length > 0) {
|
|
|
|
ULONG ByteCount;
|
|
|
|
//
|
|
// Load up the cached compressed buffers with the
|
|
// the proper data. First check if the buffer is
|
|
// already (i.e., the file record and offset match and
|
|
// the vbo we're after is within the buffers range)
|
|
//
|
|
|
|
if (/*xxNeq*/(NtfsCompressedFileRecord != AttributeContext->FileRecord) ||
|
|
(NtfsCompressedOffset != AttributeContext->FileRecordOffset) ||
|
|
(((ULONG)Vbo) < NtfsCompressedVbo) ||
|
|
(((ULONG)Vbo) >= (NtfsCompressedVbo + AttributeContext->CompressionUnit))) {
|
|
|
|
ULONG i;
|
|
LBO Lbo;
|
|
|
|
//
|
|
// Load up the cached identification information
|
|
//
|
|
|
|
NtfsCompressedFileRecord = AttributeContext->FileRecord;
|
|
NtfsCompressedOffset = AttributeContext->FileRecordOffset;
|
|
|
|
NtfsCompressedVbo = ((ULONG)Vbo) & ~(AttributeContext->CompressionUnit - 1);
|
|
|
|
//
|
|
// Now load up the compressed buffer with data. We keep on
|
|
// loading until we're done loading or the Lbo we get back is
|
|
// zero.
|
|
//
|
|
|
|
for (i = 0; i < AttributeContext->CompressionUnit; i += ByteCount) {
|
|
|
|
VboToLbo( StructureContext,
|
|
AttributeContext,
|
|
/*xxFromUlong*/(NtfsCompressedVbo + i),
|
|
&Lbo,
|
|
&ByteCount );
|
|
|
|
if (/*xxEqlZero*/(Lbo == 0)) { break; }
|
|
|
|
//
|
|
// Trim the byte count down to a compression unit and we'll catch the
|
|
// excess the next time through the loop
|
|
//
|
|
|
|
if ((i + ByteCount) > AttributeContext->CompressionUnit) {
|
|
|
|
ByteCount = AttributeContext->CompressionUnit - i;
|
|
}
|
|
|
|
ReadDisk( StructureContext->DeviceId, Lbo, ByteCount, &NtfsCompressedBuffer[i], bCacheNewData );
|
|
}
|
|
|
|
//
|
|
// If the index for the preceding loop is zero then we know
|
|
// that there isn't any data on disk for the compression unit
|
|
// and in-fact the compression unit is all zeros
|
|
//
|
|
|
|
if (i == 0) {
|
|
|
|
RtlZeroMemory( NtfsUncompressedBuffer, AttributeContext->CompressionUnit );
|
|
|
|
//
|
|
// Otherwise the unit we just read in cannot be compressed
|
|
// because it completely fills up the compression unit
|
|
//
|
|
|
|
} else if (i >= AttributeContext->CompressionUnit) {
|
|
|
|
RtlMoveMemory( NtfsUncompressedBuffer,
|
|
NtfsCompressedBuffer,
|
|
AttributeContext->CompressionUnit );
|
|
|
|
//
|
|
// If the index for the preceding loop is less then the
|
|
// compression unit size then we know that the data we
|
|
// read in is less than the compression unit and we hit
|
|
// a zero lbo. So the unit must be compressed.
|
|
//
|
|
|
|
} else {
|
|
|
|
NTSTATUS Status;
|
|
|
|
Status = RtlDecompressBuffer( AttributeContext->CompressionFormat,
|
|
NtfsUncompressedBuffer,
|
|
AttributeContext->CompressionUnit,
|
|
NtfsCompressedBuffer,
|
|
i,
|
|
&ByteCount );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
//
|
|
// Check if the decompressed buffer doesn't fill up the
|
|
// compression unit and if so then zero out the remainder
|
|
// of the uncompressed buffer
|
|
//
|
|
|
|
if (ByteCount < AttributeContext->CompressionUnit) {
|
|
|
|
RtlZeroMemory( &NtfsUncompressedBuffer[ByteCount],
|
|
AttributeContext->CompressionUnit - ByteCount );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now copy off the data from the compressed buffer to the
|
|
// user buffer and continue the loop until the length is zero.
|
|
// The amount of data we need to copy is the smaller of the
|
|
// length the user wants back or the number of bytes left in
|
|
// the uncompressed buffer from the requested vbo to the end
|
|
// of the buffer.
|
|
//
|
|
|
|
ByteCount = Minimum( Length,
|
|
NtfsCompressedVbo + AttributeContext->CompressionUnit - ((ULONG)Vbo) );
|
|
|
|
RtlMoveMemory( Buffer,
|
|
&NtfsUncompressedBuffer[ ((ULONG)Vbo) - NtfsCompressedVbo ],
|
|
ByteCount );
|
|
|
|
//
|
|
// Update the length to be what the user now needs read in,
|
|
// also update the Vbo and Buffer to be the next locations
|
|
// to be read in.
|
|
//
|
|
|
|
Length -= ByteCount;
|
|
Vbo = /*xxAdd*/( Vbo + /*xxFromUlong*/(ByteCount));
|
|
Buffer = (PCHAR)Buffer + ByteCount;
|
|
}
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// Read in runs of data until the byte count goes to zero
|
|
//
|
|
|
|
while (Length > 0) {
|
|
|
|
LBO Lbo;
|
|
ULONG CurrentRunByteCount;
|
|
|
|
//
|
|
// Lookup the corresponding Lbo and run length for the current position
|
|
// (i.e., vbo)
|
|
//
|
|
|
|
VboToLbo( StructureContext,
|
|
AttributeContext,
|
|
Vbo,
|
|
&Lbo,
|
|
&CurrentRunByteCount );
|
|
|
|
//
|
|
// While there are bytes to be read in from the current run length and we
|
|
// haven't exhausted the request we loop reading in bytes. The biggest
|
|
// request we'll handle is only 32KB contiguous bytes per physical read.
|
|
// So we might need to loop through the run
|
|
//
|
|
|
|
while ((Length > 0) && (CurrentRunByteCount > 0)) {
|
|
|
|
LONG SingleReadSize;
|
|
|
|
//
|
|
// Compute the size of the next physical read
|
|
//
|
|
|
|
SingleReadSize = Minimum(Length, 32*1024);
|
|
SingleReadSize = Minimum((ULONG)SingleReadSize, CurrentRunByteCount);
|
|
|
|
//
|
|
// Don't read beyond the data size
|
|
//
|
|
|
|
if (/*xxGtr*/(/*xxAdd*/(Vbo + /*xxFromUlong*/(SingleReadSize)) > AttributeContext->DataSize )) {
|
|
|
|
SingleReadSize = ((ULONG)(/*xxSub*/(AttributeContext->DataSize - Vbo)));
|
|
|
|
//
|
|
// If the readjusted read length is now zero then we're done
|
|
//
|
|
|
|
if (SingleReadSize <= 0) {
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// By also setting length we'll make sure that this is our last read
|
|
//
|
|
|
|
Length = SingleReadSize;
|
|
}
|
|
|
|
//
|
|
// Issue the read
|
|
//
|
|
|
|
ReadDisk( StructureContext->DeviceId, Lbo, SingleReadSize, Buffer, bCacheNewData );
|
|
|
|
//
|
|
// Update the remaining length, current run byte count, and new lbo
|
|
// offset
|
|
//
|
|
|
|
Length -= SingleReadSize;
|
|
CurrentRunByteCount -= SingleReadSize;
|
|
Lbo = /*xxAdd*/(Lbo + /*xxFromUlong*/(SingleReadSize));
|
|
Vbo = /*xxAdd*/(Vbo + /*xxFromUlong*/(SingleReadSize));
|
|
|
|
//
|
|
// Update the buffer to point to the next byte location to fill in
|
|
//
|
|
|
|
Buffer = (PCHAR)Buffer + SingleReadSize;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we get here then the remaining byte count is zero so we can return success
|
|
// to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsWriteNonresidentAttribute (
|
|
IN PNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN ULONG Length,
|
|
IN PVOID Buffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine write in the value of a Nonresident attribute.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AttributeContext - Supplies the attribute being written
|
|
|
|
Vbo - Supplies the offset within the value to return
|
|
|
|
Length - Supplies the number of bytes to return
|
|
|
|
Buffer - Supplies a pointer to the output buffer for storing the data
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the write operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Check if we are writing a compressed attribute
|
|
//
|
|
|
|
if (AttributeContext->CompressionFormat != 0) {
|
|
|
|
return EROFS;
|
|
|
|
}
|
|
|
|
//
|
|
// Write in runs of data until the byte count goes to zero
|
|
//
|
|
|
|
while (Length > 0) {
|
|
|
|
LBO Lbo;
|
|
ULONG CurrentRunByteCount;
|
|
|
|
//
|
|
// Lookup the corresponding Lbo and run length for the current position
|
|
// (i.e., vbo)
|
|
//
|
|
|
|
VboToLbo( StructureContext,
|
|
AttributeContext,
|
|
Vbo,
|
|
&Lbo,
|
|
&CurrentRunByteCount );
|
|
|
|
//
|
|
// While there are bytes to be written in from the current run length and we
|
|
// haven't exhausted the request we loop writing in bytes. The biggest
|
|
// request we'll handle is only 32KB contiguous bytes per physical write.
|
|
// So we might need to loop through the run
|
|
//
|
|
|
|
while ((Length > 0) && (CurrentRunByteCount > 0)) {
|
|
|
|
LONG SingleWriteSize;
|
|
|
|
//
|
|
// Compute the size of the next physical written
|
|
//
|
|
|
|
SingleWriteSize = Minimum(Length, 32*1024);
|
|
SingleWriteSize = Minimum((ULONG)SingleWriteSize, CurrentRunByteCount);
|
|
|
|
//
|
|
// Don't write beyond the data size
|
|
//
|
|
|
|
if (/*xxGtr*/(/*xxAdd*/(Vbo + /*xxFromUlong*/(SingleWriteSize)) > AttributeContext->DataSize )) {
|
|
|
|
SingleWriteSize = ((ULONG)(/*xxSub*/(AttributeContext->DataSize - Vbo)));
|
|
|
|
//
|
|
// If the adjusted write length is now zero then we're done
|
|
//
|
|
|
|
if (SingleWriteSize <= 0) {
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// By also setting length we'll make sure that this is our last write
|
|
//
|
|
|
|
Length = SingleWriteSize;
|
|
}
|
|
|
|
//
|
|
// Issue the write
|
|
//
|
|
|
|
WriteDisk( StructureContext->DeviceId, Lbo, SingleWriteSize, Buffer );
|
|
|
|
//
|
|
// Update the remaining length, current run byte count, and new lbo
|
|
// offset
|
|
//
|
|
|
|
Length -= SingleWriteSize;
|
|
CurrentRunByteCount -= SingleWriteSize;
|
|
Lbo = /*xxAdd*/(Lbo + /*xxFromUlong*/(SingleWriteSize));
|
|
Vbo = /*xxAdd*/(Vbo + /*xxFromUlong*/(SingleWriteSize));
|
|
|
|
//
|
|
// Update the buffer to point to the next byte location to fill in
|
|
//
|
|
|
|
Buffer = (PCHAR)Buffer + SingleWriteSize;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we get here then the remaining byte count is zero so we can return success
|
|
// to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsReadAndDecodeFileRecord (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN LONGLONG FileRecord,
|
|
OUT PULONG Index
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads in the specified file record into the indicated
|
|
ntfs file record buffer index provided that the buffer is not pinned.
|
|
It will also look at the current buffers and see if any will already
|
|
satisfy the request or assign an unused buffer if necessary and
|
|
fix Index to point to the right buffer
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileRecord - Supplies the file record number being read
|
|
|
|
Index - Receives the index of where we put the buffer. After this
|
|
call the buffer is pinned and will need to be unpinned if it is
|
|
to be reused.
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ARC_STATUS Status;
|
|
|
|
//
|
|
// For each buffer that is not null check if we have a hit on the
|
|
// file record and if so then increment the pin count and return
|
|
// that index
|
|
//
|
|
|
|
for (*Index = 0; (*Index < BUFFER_COUNT) && (NtfsFileRecordBuffer[*Index] != NULL); *Index += 1) {
|
|
|
|
if (NtfsFileRecordBufferVbo[*Index] == FileRecord) {
|
|
|
|
NtfsFileRecordBufferPinned[*Index] += 1;
|
|
return ESUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check for the first unpinned buffer and make sure we haven't exhausted the
|
|
// array
|
|
//
|
|
|
|
for (*Index = 0; (*Index < BUFFER_COUNT) && (NtfsFileRecordBufferPinned[*Index] != 0); *Index += 1) {
|
|
|
|
NOTHING;
|
|
}
|
|
|
|
if (*Index == BUFFER_COUNT) { return E2BIG; }
|
|
|
|
//
|
|
// We have an unpinned buffer that we want to use, check if we need to
|
|
// allocate a buffer to actually hold the data
|
|
//
|
|
|
|
PausedPrint(( "Reusing index %x for %I64x\r\n", *Index, FileRecord ));
|
|
|
|
if (NtfsFileRecordBuffer[*Index] == NULL) {
|
|
|
|
NtfsFileRecordBuffer[*Index] = BlAllocateHeapAligned(MAXIMUM_FILE_RECORD_SIZE);
|
|
}
|
|
|
|
//
|
|
// Pin the buffer and then read in the data
|
|
//
|
|
|
|
NtfsFileRecordBufferPinned[*Index] += 1;
|
|
|
|
if ((Status = NtfsReadNonresidentAttribute( StructureContext,
|
|
&StructureContext->MftAttributeContext,
|
|
FileRecord * StructureContext->BytesPerFileRecord,
|
|
StructureContext->BytesPerFileRecord,
|
|
NtfsFileRecordBuffer[*Index] )) != ESUCCESS) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Decode the usa
|
|
//
|
|
|
|
if ((Status = NtfsDecodeUsa( NtfsFileRecordBuffer[*Index],
|
|
StructureContext->BytesPerFileRecord )) != ESUCCESS) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// And set the file record so that we know where it came from
|
|
//
|
|
|
|
NtfsFileRecordBufferVbo[*Index] = FileRecord;
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsDecodeUsa (
|
|
IN PVOID UsaBuffer,
|
|
IN ULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine takes as input file record or index buffer and applies the
|
|
usa transformation to get it back into a state that we can use it.
|
|
|
|
Arguments:
|
|
|
|
UsaBuffer - Supplies the buffer used in this operation
|
|
|
|
Length - Supplies the length of the buffer in bytes
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PMULTI_SECTOR_HEADER MultiSectorHeader;
|
|
|
|
PUSHORT UsaOffset;
|
|
ULONG UsaSize;
|
|
|
|
ULONG i;
|
|
PUSHORT ProtectedUshort;
|
|
|
|
UNREFERENCED_PARAMETER( Length );
|
|
|
|
//
|
|
// Setup our local variables
|
|
//
|
|
|
|
MultiSectorHeader = (PMULTI_SECTOR_HEADER)UsaBuffer;
|
|
|
|
UsaOffset = Add2Ptr(UsaBuffer, MultiSectorHeader->UpdateSequenceArrayOffset);
|
|
UsaSize = MultiSectorHeader->UpdateSequenceArraySize;
|
|
|
|
//
|
|
// For every entry in the usa we need to compute the address of the protected
|
|
// ushort and then check that the protected ushort is equal to the current
|
|
// sequence number (i.e., the number at UsaOffset[0]) and then replace the
|
|
// protected ushort number with the saved ushort in the usa.
|
|
//
|
|
|
|
for (i = 1; i < UsaSize; i += 1) {
|
|
|
|
ProtectedUshort = Add2Ptr( UsaBuffer,
|
|
(SEQUENCE_NUMBER_STRIDE * i) - sizeof(USHORT));
|
|
|
|
if (*ProtectedUshort != UsaOffset[0]) {
|
|
|
|
// NtfsPrint( "USA Failure\r\n" );
|
|
|
|
return EBADF;
|
|
}
|
|
|
|
*ProtectedUshort = UsaOffset[i];
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
BOOLEAN
|
|
NtfsIsNameCached (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine consults the cache for the given link.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileName - name of entry to look up
|
|
|
|
FileRecord - IN file record of parent directory, OUT file record of child
|
|
|
|
Found - whether we found this in the cache or not
|
|
|
|
Return Value:
|
|
|
|
TRUE if the name was found in the cache.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG i, j;
|
|
|
|
*Found = FALSE;
|
|
|
|
#ifdef CACHE_DEVINFO
|
|
|
|
// NtfsPrint( "Cache probe on %04x %I64x '%.*s'\r\n",
|
|
// StructureContext->DeviceId,
|
|
// *FileRecord,
|
|
// FileName.Length,
|
|
// FileName.Buffer );
|
|
|
|
for (i = 0; i < MAX_CACHE_ENTRIES; i++) {
|
|
// NtfsPrint( "Cache comparing to %04x %I64x '%.*s'\r\n",
|
|
// NtfsLinkCache[i].DeviceId,
|
|
// NtfsLinkCache[i].ParentFileRecord,
|
|
// NtfsLinkCache[i].NameLength,
|
|
// NtfsLinkCache[i].RelativeName );
|
|
|
|
if (NtfsLinkCache[i].DeviceId == StructureContext->DeviceId &&
|
|
NtfsLinkCache[i].ParentFileRecord == *FileRecord &&
|
|
NtfsLinkCache[i].NameLength == FileName.Length) {
|
|
|
|
// NtfsPrint( "Comparing names\r\n" );
|
|
|
|
for (j = 0; j < FileName.Length; j++ ) {
|
|
if (NtfsLinkCache[i].RelativeName[j] != ToUpper( (USHORT) FileName.Buffer[j] )) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == FileName.Length) {
|
|
|
|
//
|
|
// Match
|
|
//
|
|
|
|
// NtfsPrint( "Cache hit\r\n" );
|
|
|
|
*Found = TRUE;
|
|
*FileRecord = NtfsLinkCache[i].ChildFileRecord;
|
|
*IsDirectory = TRUE;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // CACHE_DEVINFO
|
|
|
|
return *Found;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
#ifdef CACHE_DEVINFO
|
|
|
|
VOID
|
|
NtfsInvalidateCacheEntries(
|
|
IN ULONG DeviceId
|
|
)
|
|
{
|
|
ULONG i, Count = 0;
|
|
|
|
|
|
#if 0
|
|
BlPrint("NtfsInvalidateCacheEntries() called for %d(%d)\r\n",
|
|
DeviceId,
|
|
NtfsLinkCacheCount);
|
|
|
|
while (!BlGetKey());
|
|
#endif
|
|
|
|
for (i = 0; i < MAX_CACHE_ENTRIES; i++) {
|
|
if (NtfsLinkCache[i].DeviceId == DeviceId) {
|
|
NtfsLinkCache[i].DeviceId = UNINITIALIZED_DEVICE_ID;
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
if (NtfsLinkCacheCount >= Count) {
|
|
NtfsLinkCacheCount -= Count;
|
|
} else {
|
|
NtfsLinkCacheCount = 0;
|
|
}
|
|
|
|
|
|
#if 0
|
|
BlPrint("NtfsInvalidateCacheEntries() called for %d(%d)\r\n",
|
|
DeviceId,
|
|
NtfsLinkCacheCount);
|
|
|
|
while (!BlGetKey());
|
|
#endif
|
|
}
|
|
|
|
#endif // CACHE_DEV_INFO
|
|
|
|
VOID
|
|
NtfsAddNameToCache (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN LONGLONG ParentFileRecord,
|
|
IN LONGLONG FileRecord
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine adds a name and link to the name cache
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileName - Supplies the file name being cached (in ansi).
|
|
|
|
ParentFileRecord - the file record of the parent
|
|
|
|
FileRecord - file record associated with the name
|
|
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
#ifdef CACHE_DEVINFO
|
|
|
|
if (NtfsLinkCacheCount < MAX_CACHE_ENTRIES) {
|
|
ULONG i;
|
|
ULONG Index;
|
|
|
|
for (Index = 0; Index < MAX_CACHE_ENTRIES; Index++) {
|
|
if (NtfsLinkCache[Index].DeviceId == UNINITIALIZED_DEVICE_ID) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Index < MAX_CACHE_ENTRIES) {
|
|
NtfsLinkCache[Index].DeviceId = StructureContext->DeviceId;
|
|
NtfsLinkCache[Index].ParentFileRecord = ParentFileRecord;
|
|
NtfsLinkCache[Index].NameLength = FileName.Length;
|
|
|
|
for (i = 0; i < FileName.Length; i++) {
|
|
NtfsLinkCache[Index].RelativeName[i] = ToUpper( FileName.Buffer[i] );
|
|
}
|
|
|
|
NtfsLinkCache[Index].ChildFileRecord = FileRecord;
|
|
NtfsLinkCacheCount++;
|
|
|
|
PausedPrint( ("Caching %04x %I64x %.*s %I64X\r\n",
|
|
StructureContext->DeviceId,
|
|
ParentFileRecord,
|
|
FileName.Length,
|
|
FileName.Buffer,
|
|
FileRecord ));
|
|
}
|
|
} else {
|
|
// NtfsPrint( "Cache is full at %I64x %.*s %I64X\r\n",
|
|
// ParentFileRecord,
|
|
// FileName.Length,
|
|
// FileName.Buffer,
|
|
// FileRecord );
|
|
// Pause;
|
|
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsSearchForFileName (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches a given index root and allocation for the specified
|
|
file name.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileName - Supplies the file name being searched for (in ansi).
|
|
|
|
FileRecord - Receives the file record for the entry if one was located.
|
|
|
|
Found - Receives a value to indicate if we found the specified
|
|
file name in the directory
|
|
|
|
IsDirectory - Receives a value to indicate if the found index is itself
|
|
a directory
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONGLONG ParentFileRecord;
|
|
|
|
//
|
|
// Test to see if the file name is cached
|
|
//
|
|
|
|
if (NtfsIsNameCached( StructureContext, FileName, FileRecord, Found, IsDirectory )) {
|
|
return ESUCCESS;
|
|
}
|
|
|
|
ParentFileRecord = *FileRecord;
|
|
|
|
InexactSortedDirectoryScan( StructureContext, FileName, FileRecord, Found, IsDirectory );
|
|
|
|
if (!*Found) {
|
|
LinearDirectoryScan( StructureContext, FileName, FileRecord, Found, IsDirectory );
|
|
}
|
|
|
|
//
|
|
// If we have a directory entry, then add it to the cache
|
|
//
|
|
|
|
if (*Found && *IsDirectory) {
|
|
NtfsAddNameToCache( StructureContext, FileName, ParentFileRecord, *FileRecord );
|
|
}
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsInexactSortedDirectoryScan (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches a given index root and allocation for the specified
|
|
file name by performing simple uppercasing and using that to wander through
|
|
the directory tree.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileName - Supplies the file name being searched for (in ansi).
|
|
|
|
FileRecord - Receives the file record for the entry if one was located.
|
|
|
|
Found - Receives a value to indicate if we found the specified
|
|
file name in the directory
|
|
|
|
IsDirectory - Receives a value to indicate if the found index is itself
|
|
a directory
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PATTRIBUTE_RECORD_HEADER IndexAttributeHeader;
|
|
PINDEX_ROOT IndexRootValue;
|
|
PINDEX_HEADER IndexHeader;
|
|
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext1;
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext2;
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext3;
|
|
|
|
PNTFS_ATTRIBUTE_CONTEXT IndexRoot;
|
|
PNTFS_ATTRIBUTE_CONTEXT IndexAllocation;
|
|
PNTFS_ATTRIBUTE_CONTEXT AllocationBitmap;
|
|
|
|
ULONG NextIndexBuffer;
|
|
ULONG BytesPerIndexBuffer;
|
|
|
|
ULONG BufferIndex;
|
|
|
|
//
|
|
// The current file record must be a directory so now lookup the index root,
|
|
// allocation and bitmap for the directory and then we can do our search.
|
|
//
|
|
|
|
// NtfsPrint( "InexactSortedDirectoryScan %04x %I64x for '%.*s'\r\n",
|
|
// StructureContext->DeviceId,
|
|
// *FileRecord, FileName.Length, FileName.Buffer );
|
|
// Pause;
|
|
|
|
IndexRoot = &AttributeContext1;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$INDEX_ROOT,
|
|
Found,
|
|
IndexRoot);
|
|
|
|
if (!*Found) { return EBADF; }
|
|
|
|
IndexAllocation = &AttributeContext2;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$INDEX_ALLOCATION,
|
|
Found,
|
|
IndexAllocation);
|
|
|
|
if (!*Found) { IndexAllocation = NULL; }
|
|
|
|
AllocationBitmap = &AttributeContext3;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$BITMAP,
|
|
Found,
|
|
AllocationBitmap);
|
|
|
|
if (!*Found) { AllocationBitmap = NULL; }
|
|
|
|
//
|
|
// unless otherwise set we will assume that our search has failed
|
|
//
|
|
|
|
*Found = FALSE;
|
|
|
|
//
|
|
// First read in and search the index root for the file name. We know the index
|
|
// root is resident so we'll save some buffering and just read in file record
|
|
// with the index root directly
|
|
//
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
IndexRoot->FileRecord,
|
|
&BufferIndex );
|
|
|
|
IndexAttributeHeader = Add2Ptr( NtfsFileRecordBuffer[BufferIndex],
|
|
IndexRoot->FileRecordOffset );
|
|
|
|
IndexRootValue = NtfsGetValue( IndexAttributeHeader );
|
|
|
|
IndexHeader = &IndexRootValue->IndexHeader;
|
|
|
|
//
|
|
// We also setup ourselves so that if the current index does not contain a match
|
|
// we will read in the next index and continue our search
|
|
//
|
|
|
|
BytesPerIndexBuffer = IndexRootValue->BytesPerIndexBuffer;
|
|
|
|
//
|
|
// Now we'll just continue looping intil we either find a match or exhaust all
|
|
// of the index buffer
|
|
//
|
|
|
|
NextIndexBuffer = UNINITIALIZED_DEVICE_ID;
|
|
while (TRUE) {
|
|
|
|
PINDEX_ENTRY IndexEntry;
|
|
VBO Vbo;
|
|
|
|
// NtfsPrint( "Searching IndexBuffer %x\r\n", NextIndexBuffer );
|
|
|
|
//
|
|
// Search the current index buffer (from index header looking for a match
|
|
//
|
|
|
|
for (IndexEntry = Add2Ptr(IndexHeader, IndexHeader->FirstIndexEntry);
|
|
!FlagOn(IndexEntry->Flags, INDEX_ENTRY_END);
|
|
IndexEntry = Add2Ptr(IndexEntry, IndexEntry->Length)) {
|
|
|
|
PFILE_NAME FileNameEntry;
|
|
UNICODE_STRING UnicodeFileName;
|
|
int Result;
|
|
|
|
//
|
|
// Get the FileName for this index entry
|
|
//
|
|
|
|
FileNameEntry = Add2Ptr(IndexEntry, sizeof(INDEX_ENTRY));
|
|
|
|
UnicodeFileName.Length = FileNameEntry->FileNameLength * 2;
|
|
UnicodeFileName.Buffer = &FileNameEntry->FileName[0];
|
|
|
|
//
|
|
// Check if this the name we're after if it is then say we found it and
|
|
// setup the output variables
|
|
//
|
|
|
|
Result = NtfsCompareName( FileName, UnicodeFileName );
|
|
if (Result == 0) {
|
|
|
|
FileReferenceToLargeInteger( IndexEntry->FileReference,
|
|
FileRecord );
|
|
|
|
*Found = TRUE;
|
|
*IsDirectory = FlagOn( FileNameEntry->Info.FileAttributes,
|
|
DUP_FILE_NAME_INDEX_PRESENT);
|
|
|
|
// NtfsPrint( "Found Entry %I64x\r\n", *FileRecord );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
} else if (Result < 0) {
|
|
// NtfsPrint( "Found > entry '%.*ws'\r\n", UnicodeFileName.Length, UnicodeFileName.Buffer );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point, we've either hit the end of the index or we have
|
|
// found the first entry larger than the name we're looking for. In either case
|
|
// we may have a downpointer to examine. If not, then there is no entry here.
|
|
//
|
|
|
|
//
|
|
// If no down pointer then release the file record buffer and quit
|
|
//
|
|
|
|
if (!FlagOn( IndexEntry->Flags, INDEX_ENTRY_NODE )) {
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
// NtfsPrint( "No down pointer\r\n" );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// At this point we've searched one index header and need to read in another
|
|
// one to check. But first make sure there are additional index buffers
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(IndexAllocation) ||
|
|
!ARGUMENT_PRESENT(AllocationBitmap)) {
|
|
|
|
// NtfsPrint( "No index allocation\r\n" );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
NextIndexBuffer = (ULONG)NtfsIndexEntryBlock( IndexEntry ) ;
|
|
Vbo = NextIndexBuffer * StructureContext->BytesPerCluster;
|
|
|
|
//
|
|
// Make sure the buffer offset is within the stream
|
|
//
|
|
|
|
if (Vbo >= IndexAllocation->DataSize) {
|
|
|
|
// NtfsPrint( "Beyond end of stream %I64x %x\r\n", IndexAllocation->DataSize, NextIndexBuffer );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
|
|
}
|
|
|
|
//
|
|
// At this point we've computed the next index allocation buffer to read in
|
|
// so read it in, decode it, and go back to the top of our loop
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
IndexAllocation,
|
|
Vbo,
|
|
BytesPerIndexBuffer,
|
|
NtfsIndexAllocationBuffer );
|
|
|
|
DecodeUsa( NtfsIndexAllocationBuffer, BytesPerIndexBuffer );
|
|
|
|
IndexHeader = &NtfsIndexAllocationBuffer->IndexHeader;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsLinearDirectoryScan (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN CSTRING FileName,
|
|
IN OUT PLONGLONG FileRecord,
|
|
OUT PBOOLEAN Found,
|
|
OUT PBOOLEAN IsDirectory
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches a given index root and allocation for the specified
|
|
file name by looking linearly through every entry.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
FileName - Supplies the file name being searched for (in ansi).
|
|
|
|
FileRecord - Receives the file record for the entry if one was located.
|
|
|
|
Found - Receives a value to indicate if we found the specified
|
|
file name in the directory
|
|
|
|
IsDirectory - Receives a value to indicate if the found index is itself
|
|
a directory
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PATTRIBUTE_RECORD_HEADER IndexAttributeHeader;
|
|
PINDEX_ROOT IndexRootValue;
|
|
PINDEX_HEADER IndexHeader;
|
|
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext1;
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext2;
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext3;
|
|
|
|
PNTFS_ATTRIBUTE_CONTEXT IndexRoot;
|
|
PNTFS_ATTRIBUTE_CONTEXT IndexAllocation;
|
|
PNTFS_ATTRIBUTE_CONTEXT AllocationBitmap;
|
|
|
|
ULONG NextIndexBuffer;
|
|
ULONG BytesPerIndexBuffer;
|
|
|
|
ULONG BufferIndex;
|
|
|
|
//
|
|
// The current file record must be a directory so now lookup the index root,
|
|
// allocation and bitmap for the directory and then we can do our search.
|
|
//
|
|
|
|
// NtfsPrint( "LinearSearching %04x %I64x for %.*s\r\n",
|
|
// StructureContext->DeviceId,
|
|
// *FileRecord, FileName.Length, FileName.Buffer );
|
|
// Pause;
|
|
|
|
IndexRoot = &AttributeContext1;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$INDEX_ROOT,
|
|
Found,
|
|
IndexRoot);
|
|
|
|
if (!*Found) { return EBADF; }
|
|
|
|
IndexAllocation = &AttributeContext2;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$INDEX_ALLOCATION,
|
|
Found,
|
|
IndexAllocation);
|
|
|
|
if (!*Found) { IndexAllocation = NULL; }
|
|
|
|
AllocationBitmap = &AttributeContext3;
|
|
|
|
LookupAttribute( StructureContext,
|
|
*FileRecord,
|
|
$BITMAP,
|
|
Found,
|
|
AllocationBitmap);
|
|
|
|
if (!*Found) { AllocationBitmap = NULL; }
|
|
|
|
//
|
|
// unless otherwise set we will assume that our search has failed
|
|
//
|
|
|
|
*Found = FALSE;
|
|
|
|
//
|
|
// First read in and search the index root for the file name. We know the index
|
|
// root is resident so we'll save some buffering and just read in file record
|
|
// with the index root directly
|
|
//
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
IndexRoot->FileRecord,
|
|
&BufferIndex );
|
|
|
|
IndexAttributeHeader = Add2Ptr( NtfsFileRecordBuffer[BufferIndex],
|
|
IndexRoot->FileRecordOffset );
|
|
|
|
IndexRootValue = NtfsGetValue( IndexAttributeHeader );
|
|
|
|
IndexHeader = &IndexRootValue->IndexHeader;
|
|
|
|
//
|
|
// We also setup ourselves so that if the current index does not contain a match
|
|
// we will read in the next index and continue our search
|
|
//
|
|
|
|
NextIndexBuffer = 0;
|
|
|
|
BytesPerIndexBuffer = IndexRootValue->BytesPerIndexBuffer;
|
|
|
|
//
|
|
// Now we'll just continue looping intil we either find a match or exhaust all
|
|
// of the index buffer
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
PINDEX_ENTRY IndexEntry;
|
|
BOOLEAN IsAllocated;
|
|
VBO Vbo = 0;
|
|
|
|
//
|
|
// Search the current index buffer (from index header looking for a match
|
|
//
|
|
|
|
for (IndexEntry = Add2Ptr(IndexHeader, IndexHeader->FirstIndexEntry);
|
|
!FlagOn(IndexEntry->Flags, INDEX_ENTRY_END);
|
|
IndexEntry = Add2Ptr(IndexEntry, IndexEntry->Length)) {
|
|
|
|
PFILE_NAME FileNameEntry;
|
|
UNICODE_STRING UnicodeFileName;
|
|
|
|
//
|
|
// Get the FileName for this index entry
|
|
//
|
|
|
|
FileNameEntry = Add2Ptr(IndexEntry, sizeof(INDEX_ENTRY));
|
|
|
|
UnicodeFileName.Length = FileNameEntry->FileNameLength * 2;
|
|
UnicodeFileName.Buffer = &FileNameEntry->FileName[0];
|
|
|
|
//
|
|
// Check if this the name we're after if it is then say we found it and
|
|
// setup the output variables
|
|
//
|
|
|
|
if (NtfsCompareName( FileName, UnicodeFileName ) == 0) {
|
|
|
|
FileReferenceToLargeInteger( IndexEntry->FileReference,
|
|
FileRecord );
|
|
|
|
*Found = TRUE;
|
|
*IsDirectory = FlagOn( FileNameEntry->Info.FileAttributes,
|
|
DUP_FILE_NAME_INDEX_PRESENT);
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point we've searched one index header and need to read in another
|
|
// one to check. But first make sure there are additional index buffers
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(IndexAllocation) ||
|
|
!ARGUMENT_PRESENT(AllocationBitmap)) {
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// Now the following loop reads in the valid index buffer. The variable
|
|
// next index buffer denotes the buffer we want to read in. The idea is to
|
|
// first check that the buffer is part of the index allocation otherwise
|
|
// we've exhausted the list without finding a match. Once we know the
|
|
// allocation exists then we check if the record is really allocated if it
|
|
// is not allocated we try the next buffer and so on.
|
|
//
|
|
|
|
IsAllocated = FALSE;
|
|
|
|
while (!IsAllocated) {
|
|
|
|
//
|
|
// Compute the starting vbo of the next index buffer and check if it is
|
|
// still within the data size.
|
|
//
|
|
|
|
Vbo = (BytesPerIndexBuffer * NextIndexBuffer);
|
|
|
|
if (Vbo >= IndexAllocation->DataSize) {
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
|
|
}
|
|
|
|
//
|
|
// Now check if the index buffer is in use
|
|
//
|
|
|
|
IsRecordAllocated( StructureContext,
|
|
AllocationBitmap,
|
|
NextIndexBuffer,
|
|
&IsAllocated );
|
|
|
|
NextIndexBuffer += 1;
|
|
}
|
|
|
|
//
|
|
// At this point we've computed the next index allocation buffer to read in
|
|
// so read it in, decode it, and go back to the top of our loop
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
IndexAllocation,
|
|
Vbo,
|
|
BytesPerIndexBuffer,
|
|
NtfsIndexAllocationBuffer );
|
|
|
|
DecodeUsa( NtfsIndexAllocationBuffer, BytesPerIndexBuffer );
|
|
|
|
IndexHeader = &NtfsIndexAllocationBuffer->IndexHeader;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsIsRecordAllocated (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AllocationBitmap,
|
|
IN ULONG BitOffset,
|
|
OUT PBOOLEAN IsAllocated
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine indicates to the caller if the specified index allocation record
|
|
is in use (i.e., its bit is 1).
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AllocationBitmap - Supplies the attribute context for the index allocation bitmap
|
|
|
|
BitOffset - Supplies the offset (zero based) being checked
|
|
|
|
IsAllocated - Recieves an value indicating if the record is allocated or not
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG ByteIndex;
|
|
ULONG BitIndex;
|
|
UCHAR LocalByte;
|
|
|
|
//
|
|
// This routine is rather dumb in that it only reads in the byte that contains
|
|
// the bit we're interested in and doesn't keep any state information between
|
|
// calls. We first break down the bit offset into the byte and bit within
|
|
// the byte that we need to check
|
|
//
|
|
|
|
ByteIndex = BitOffset / 8;
|
|
BitIndex = BitOffset % 8;
|
|
|
|
//
|
|
// Read in a single byte containing the bit we need to check
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
AllocationBitmap,
|
|
/*xxFromUlong*/(ByteIndex),
|
|
1,
|
|
&LocalByte );
|
|
|
|
//
|
|
// Shift over the local byte so that the bit we want is in the low order bit and
|
|
// then mask it out to see if the bit is set
|
|
//
|
|
|
|
if (FlagOn(LocalByte >> BitIndex, 0x01)) {
|
|
|
|
*IsAllocated = TRUE;
|
|
|
|
} else {
|
|
|
|
*IsAllocated = FALSE;
|
|
}
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsLoadMcb (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
IN PNTFS_MCB Mcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine loads into one of the cached mcbs the retrival information for the
|
|
starting vbo.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AttributeContext - Supplies the Nonresident attribute being queried
|
|
|
|
Vbo - Supplies the starting Vbo to use when loading the mcb
|
|
|
|
Mcb - Supplies the mcb that we should be loading
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PATTRIBUTE_RECORD_HEADER AttributeHeader;
|
|
|
|
ULONG BytesPerCluster;
|
|
|
|
VBO LowestVbo;
|
|
VBO HighestVbo;
|
|
|
|
LONGLONG FileRecord;
|
|
|
|
NTFS_ATTRIBUTE_CONTEXT AttributeContext1;
|
|
PNTFS_ATTRIBUTE_CONTEXT AttributeList;
|
|
|
|
LONGLONG li;
|
|
LONGLONG Previousli;
|
|
ATTRIBUTE_LIST_ENTRY AttributeListEntry;
|
|
|
|
ATTRIBUTE_TYPE_CODE TypeCode;
|
|
|
|
ULONG BufferIndex;
|
|
ULONG SavedBufferIndex;
|
|
|
|
//
|
|
// Load our local variables
|
|
//
|
|
|
|
BytesPerCluster = StructureContext->BytesPerCluster;
|
|
|
|
//
|
|
// Setup a pointer to the cached mcb, indicate the attribute context that is will
|
|
// now own the cached mcb, and zero out the mcb
|
|
//
|
|
|
|
Mcb->InUse = 0;
|
|
|
|
//
|
|
// Read in the file record that contains the non-resident attribute and get a
|
|
// pointer to the attribute header
|
|
//
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
AttributeContext->FileRecord,
|
|
&BufferIndex );
|
|
|
|
AttributeHeader = Add2Ptr( NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeContext->FileRecordOffset );
|
|
|
|
//
|
|
// Compute the lowest and highest vbo that is described by this attribute header
|
|
//
|
|
|
|
LowestVbo = AttributeHeader->Form.Nonresident.LowestVcn * BytesPerCluster;
|
|
|
|
HighestVbo = ((AttributeHeader->Form.Nonresident.HighestVcn + 1) * BytesPerCluster) - 1;
|
|
|
|
//
|
|
// Now check if the vbo we are after is within the range of this attribute header
|
|
// and if so then decode the retrieval information and return to our caller
|
|
//
|
|
|
|
if ((LowestVbo <= Vbo) && (Vbo <= HighestVbo)) {
|
|
|
|
DecodeRetrievalInformation( StructureContext, Mcb, Vbo, AttributeHeader );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
//
|
|
// At this point the attribute header does not contain the range we need so read
|
|
// in the base file record and we'll search the attribute list for a attribute
|
|
// header that we need. We need to make sure that we don't already have the base FRS.
|
|
// If we do, then we just continue using it.
|
|
//
|
|
|
|
if (/*!xxEqlZero*/(*((PLONGLONG)&(NtfsFileRecordBuffer[BufferIndex]->BaseFileRecordSegment)) != 0)) {
|
|
|
|
FileReferenceToLargeInteger( NtfsFileRecordBuffer[BufferIndex]->BaseFileRecordSegment,
|
|
&FileRecord );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
FileRecord,
|
|
&BufferIndex );
|
|
|
|
} else {
|
|
|
|
FileRecord = NtfsFileRecordBufferVbo[BufferIndex];
|
|
}
|
|
|
|
//
|
|
// Now we have read in the base file record so search for the attribute list
|
|
// attribute
|
|
//
|
|
|
|
AttributeList = NULL;
|
|
|
|
for (AttributeHeader = NtfsFirstAttribute( NtfsFileRecordBuffer[BufferIndex] );
|
|
AttributeHeader->TypeCode != $END;
|
|
AttributeHeader = NtfsGetNextRecord( AttributeHeader )) {
|
|
|
|
//
|
|
// Check if this is the attribute list attribute and if so then setup a local
|
|
// attribute context
|
|
//
|
|
|
|
if (AttributeHeader->TypeCode == $ATTRIBUTE_LIST) {
|
|
|
|
InitializeAttributeContext( StructureContext,
|
|
NtfsFileRecordBuffer[BufferIndex],
|
|
AttributeHeader,
|
|
FileRecord,
|
|
AttributeList = &AttributeContext1 );
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have better located an attribute list otherwise we're in trouble
|
|
//
|
|
|
|
if (AttributeList == NULL) {
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
//
|
|
// Setup a local for the type code
|
|
//
|
|
|
|
TypeCode = AttributeContext->TypeCode;
|
|
|
|
//
|
|
// Now that we've located the attribute list we need to continue our search. So
|
|
// what this outer loop does is search down the attribute list looking for a
|
|
// match.
|
|
//
|
|
|
|
NtfsFileRecordBufferPinned[SavedBufferIndex = BufferIndex] += 1;
|
|
|
|
for (Previousli = li = 0;
|
|
/*xxLtr*/(li < AttributeList->DataSize);
|
|
li = /*xxAdd*/(li + /*xxFromUlong*/(AttributeListEntry.RecordLength))) {
|
|
|
|
//
|
|
// Read in the attribute list entry. We don't need to read in the name,
|
|
// just the first part of the list entry.
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
AttributeList,
|
|
li,
|
|
sizeof(ATTRIBUTE_LIST_ENTRY),
|
|
&AttributeListEntry );
|
|
|
|
//
|
|
// Now check if the attribute matches, and either it is not $data or if it
|
|
// is $data then it is unnamed
|
|
//
|
|
|
|
if ((AttributeListEntry.AttributeTypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeListEntry.AttributeNameLength == 0)))) {
|
|
|
|
//
|
|
// If the lowest vcn is is greater than the vbo we've after then
|
|
// we are done and can use previous li otherwise set previous li accordingly.
|
|
|
|
if (Vbo < AttributeListEntry.LowestVcn * BytesPerCluster) {
|
|
|
|
break;
|
|
}
|
|
|
|
Previousli = li;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we should have found the offset for the attribute list entry
|
|
// so read it in and verify that it is correct
|
|
//
|
|
|
|
ReadAttribute( StructureContext,
|
|
AttributeList,
|
|
Previousli,
|
|
sizeof(ATTRIBUTE_LIST_ENTRY),
|
|
&AttributeListEntry );
|
|
|
|
if ((AttributeListEntry.AttributeTypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeListEntry.AttributeNameLength == 0)))) {
|
|
|
|
//
|
|
// We found a match so now compute the file record containing this
|
|
// attribute and read in the file record
|
|
//
|
|
|
|
FileReferenceToLargeInteger( AttributeListEntry.SegmentReference, &FileRecord );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
|
|
ReadAndDecodeFileRecord( StructureContext,
|
|
FileRecord,
|
|
&BufferIndex );
|
|
|
|
//
|
|
// Now search down the file record for our matching attribute, and it
|
|
// better be there otherwise the attribute list is wrong.
|
|
//
|
|
|
|
for (AttributeHeader = NtfsFirstAttribute( NtfsFileRecordBuffer[BufferIndex] );
|
|
AttributeHeader->TypeCode != $END;
|
|
AttributeHeader = NtfsGetNextRecord( AttributeHeader )) {
|
|
|
|
//
|
|
// As a quick check make sure that this attribute is non resident
|
|
//
|
|
|
|
if (AttributeHeader->FormCode == NONRESIDENT_FORM) {
|
|
|
|
//
|
|
// Compute the range of this attribute header
|
|
//
|
|
|
|
LowestVbo = AttributeHeader->Form.Nonresident.LowestVcn * BytesPerCluster;
|
|
|
|
HighestVbo = ((AttributeHeader->Form.Nonresident.HighestVcn + 1) * BytesPerCluster) - 1;
|
|
|
|
//
|
|
// We have located the attribute in question if the type code
|
|
// match, it is within the proper range, and if it is either not
|
|
// the data attribute or if it is the data attribute then it is
|
|
// also unnamed
|
|
//
|
|
|
|
if ((AttributeHeader->TypeCode == TypeCode)
|
|
|
|
&&
|
|
|
|
(LowestVbo <= Vbo) && (Vbo <= HighestVbo)
|
|
|
|
&&
|
|
|
|
((TypeCode != $DATA) ||
|
|
((TypeCode == $DATA) && (AttributeHeader->NameLength == 0)))) {
|
|
|
|
//
|
|
// We've located the attribute so now it is time to decode
|
|
// the retrieval information and return to our caller
|
|
//
|
|
|
|
DecodeRetrievalInformation( StructureContext,
|
|
Mcb,
|
|
Vbo,
|
|
AttributeHeader );
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
DereferenceFileRecord( SavedBufferIndex );
|
|
|
|
return ESUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DereferenceFileRecord( BufferIndex );
|
|
DereferenceFileRecord( SavedBufferIndex );
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
|
|
ARC_STATUS
|
|
NtfsVboToLbo (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PCNTFS_ATTRIBUTE_CONTEXT AttributeContext,
|
|
IN VBO Vbo,
|
|
OUT PLBO Lbo,
|
|
OUT PULONG ByteCount
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine computes the run denoted by the input vbo to into its
|
|
corresponding lbo and also returns the number of bytes remaining in
|
|
the run.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
AttributeContext - Supplies the Nonresident attribute being queried
|
|
|
|
Vbo - Supplies the Vbo to match
|
|
|
|
Lbo - Recieves the corresponding Lbo
|
|
|
|
ByteCount - Receives the number of bytes remaining in the run
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTFS_MCB Mcb;
|
|
ULONG i;
|
|
|
|
//
|
|
// Check if we are doing the mft or some other attribute
|
|
//
|
|
|
|
Mcb = NULL;
|
|
|
|
if (AttributeContext == &StructureContext->MftAttributeContext) {
|
|
|
|
//
|
|
// For the mft we start with the base mcb but if the vbo is not in the mcb
|
|
// then we immediately switch over to the cached mcb
|
|
//
|
|
|
|
Mcb = (PNTFS_MCB)&StructureContext->MftBaseMcb;
|
|
|
|
if (/*xxLtr*/(Vbo < Mcb->Vbo[0]) || /*xxGeq*/(Vbo >= Mcb->Vbo[Mcb->InUse])) {
|
|
|
|
Mcb = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the Mcb is still null then we are to use the cached mcb, first find
|
|
// if one of the cached ones contains the range we're after
|
|
//
|
|
|
|
if (Mcb == NULL) {
|
|
|
|
for (i = 0; i < 16; i += 1) {
|
|
|
|
//
|
|
// check if we have a hit, on the same attribute and range
|
|
//
|
|
|
|
Mcb = (PNTFS_MCB)&StructureContext->CachedMcb[i];
|
|
|
|
if ((/*xxEql*/(AttributeContext->FileRecord == StructureContext->CachedMcbFileRecord[i]) &&
|
|
(AttributeContext->FileRecordOffset == StructureContext->CachedMcbFileRecordOffset[i]) &&
|
|
/*xxLeq*/(Mcb->Vbo[0] <= Vbo) && /*xxLtr*/(Vbo < Mcb->Vbo[Mcb->InUse]))) {
|
|
|
|
break;
|
|
}
|
|
|
|
Mcb = NULL;
|
|
}
|
|
|
|
//
|
|
// If we didn't get a hit then we need to load a new mcb we'll
|
|
// alternate through our two cached mcbs
|
|
//
|
|
|
|
if (Mcb == NULL) {
|
|
|
|
|
|
Mcb = (PNTFS_MCB)&StructureContext->CachedMcb[LastMcb % 16];
|
|
((PNTFS_STRUCTURE_CONTEXT)StructureContext)->CachedMcbFileRecord[LastMcb % 16]
|
|
= AttributeContext->FileRecord;
|
|
((PNTFS_STRUCTURE_CONTEXT)StructureContext)->CachedMcbFileRecordOffset[LastMcb % 16]
|
|
= AttributeContext->FileRecordOffset;
|
|
|
|
LastMcb += 1;
|
|
|
|
LoadMcb( StructureContext, AttributeContext, Vbo, Mcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point the mcb contains the vbo asked for. So now search for the vbo.
|
|
// Note that we could also do binary search here but because the run count is
|
|
// probably small the extra overhead of a binary search doesn't buy us anything
|
|
//
|
|
|
|
for (i = 0; i < Mcb->InUse; i += 1) {
|
|
|
|
|
|
//
|
|
// We found our slot if the vbo we're after is less than the next mcb's vbo
|
|
//
|
|
|
|
if (/*xxLtr*/(Vbo < Mcb->Vbo[i+1])) {
|
|
|
|
//
|
|
// Compute the corresponding lbo which is the stored lbo plus the
|
|
// difference between the stored vbo and the vbo we're looking up.
|
|
// Also compute the byte count which is the difference between the
|
|
// current vbo we're looking up and the vbo for the next run
|
|
//
|
|
|
|
if (/*xxNeqZero*/(Mcb->Lbo[i] != 0)) {
|
|
|
|
*Lbo = /*xxAdd*/(Mcb->Lbo[i] + /*xxSub*/(Vbo - Mcb->Vbo[i]));
|
|
|
|
} else {
|
|
|
|
*Lbo = 0;
|
|
}
|
|
|
|
*ByteCount = ((ULONG)/*xxSub*/(Mcb->Vbo[i+1] - Vbo));
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
return ESUCCESS;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we really reach here we have an error. Most likely the file is not large
|
|
// enough for the requested vbo
|
|
//
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
ARC_STATUS
|
|
NtfsDecodeRetrievalInformation (
|
|
IN PCNTFS_STRUCTURE_CONTEXT StructureContext,
|
|
IN PNTFS_MCB Mcb,
|
|
IN VBO Vbo,
|
|
IN PATTRIBUTE_RECORD_HEADER AttributeHeader
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine does the decode of the retrival information stored in a Nonresident
|
|
attribute header into the specified output mcb starting with the specified
|
|
Lbo.
|
|
|
|
Arguments:
|
|
|
|
StructureContext - Supplies the volume structure for this operation
|
|
|
|
Mcb - Supplies the Mcb used in this operation
|
|
|
|
Vbo - Supplies the starting vbo that must be stored in the mcb
|
|
|
|
AttributeHeader - Supplies the non resident attribute header that
|
|
we are to use in this operation
|
|
|
|
Return Value:
|
|
|
|
ESUCCESS is returned if the operation is successful. Otherwise,
|
|
an unsuccessful status is returned that describes the reason for failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG BytesPerCluster;
|
|
|
|
VBO NextVbo;
|
|
LBO CurrentLbo;
|
|
VBO CurrentVbo;
|
|
|
|
LONGLONG Change;
|
|
PCHAR ch;
|
|
ULONG VboBytes;
|
|
ULONG LboBytes;
|
|
|
|
//
|
|
// Initialize our locals
|
|
//
|
|
|
|
BytesPerCluster = StructureContext->BytesPerCluster;
|
|
|
|
//
|
|
// Setup the next vbo and current lbo and ch for the following loop that decodes
|
|
// the retrieval information
|
|
//
|
|
|
|
NextVbo = /*xxXMul*/(AttributeHeader->Form.Nonresident.LowestVcn * BytesPerCluster);
|
|
|
|
CurrentLbo = 0;
|
|
|
|
ch = Add2Ptr( AttributeHeader,
|
|
AttributeHeader->Form.Nonresident.MappingPairsOffset );
|
|
|
|
Mcb->InUse = 0;
|
|
|
|
//
|
|
// Loop to process mapping pairs
|
|
//
|
|
|
|
while (!IsCharZero(*ch)) {
|
|
|
|
//
|
|
// Set current Vbo from initial value or last pass through loop
|
|
//
|
|
|
|
CurrentVbo = NextVbo;
|
|
|
|
//
|
|
// Extract the counts from the two nibbles of this byte
|
|
//
|
|
|
|
VboBytes = *ch & 0x0f;
|
|
LboBytes = *ch++ >> 4;
|
|
|
|
//
|
|
// Extract the Vbo change and update next vbo
|
|
//
|
|
|
|
Change = 0;
|
|
|
|
if (IsCharLtrZero(*(ch + VboBytes - 1))) {
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
RtlMoveMemory( &Change, ch, VboBytes );
|
|
|
|
ch += VboBytes;
|
|
|
|
NextVbo = /*xxAdd*/(NextVbo + /*xXMul*/(Change * BytesPerCluster));
|
|
|
|
//
|
|
// If we have reached the maximum for this mcb then it is time
|
|
// to return and not decipher any more retrieval information
|
|
//
|
|
|
|
if (Mcb->InUse >= MAXIMUM_NUMBER_OF_MCB_ENTRIES - 1) {
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now check if there is an lbo change. If there isn't
|
|
// then we only need to update the vbo, because this
|
|
// is sparse/compressed file.
|
|
//
|
|
|
|
if (LboBytes != 0) {
|
|
|
|
//
|
|
// Extract the Lbo change and update current lbo
|
|
//
|
|
|
|
Change = 0;
|
|
|
|
if (IsCharLtrZero(*(ch + LboBytes - 1))) {
|
|
|
|
Change = /*xxSub*/( Change - 1 );
|
|
}
|
|
|
|
RtlMoveMemory( &Change, ch, LboBytes );
|
|
|
|
ch += LboBytes;
|
|
|
|
CurrentLbo = /*xxAdd*/( CurrentLbo + /*xxXMul*/(Change * BytesPerCluster));
|
|
}
|
|
|
|
//
|
|
// Now check if the Next Vbo is greater than the Vbo we after
|
|
//
|
|
|
|
if (/*xxGeq*/(NextVbo >= Vbo)) {
|
|
|
|
//
|
|
// Load this entry into the mcb and advance our in use counter
|
|
//
|
|
|
|
Mcb->Vbo[Mcb->InUse] = CurrentVbo;
|
|
Mcb->Lbo[Mcb->InUse] = (LboBytes != 0 ? CurrentLbo : 0);
|
|
Mcb->Vbo[Mcb->InUse + 1] = NextVbo;
|
|
|
|
Mcb->InUse += 1;
|
|
}
|
|
}
|
|
|
|
return ESUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
VOID
|
|
NtfsFirstComponent (
|
|
IN OUT PCSTRING String,
|
|
OUT PCSTRING FirstComponent
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine takes an input path name and separates it into its first
|
|
file name component and the remaining part.
|
|
|
|
Arguments:
|
|
|
|
String - Supplies the original string being dissected (in ansi). On return
|
|
this string will now point to the remaining part.
|
|
|
|
FirstComponent - Recieves the string representing the first file name in
|
|
the input string.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Index;
|
|
|
|
//
|
|
// Copy over the string variable into the first component variable
|
|
//
|
|
|
|
*FirstComponent = *String;
|
|
|
|
//
|
|
// Now if the first character in the name is a backslash then
|
|
// simply skip over the backslash.
|
|
//
|
|
|
|
if (FirstComponent->Buffer[0] == '\\') {
|
|
|
|
FirstComponent->Buffer += 1;
|
|
FirstComponent->Length -= 1;
|
|
}
|
|
|
|
//
|
|
// Now search the name for a backslash
|
|
//
|
|
|
|
for (Index = 0; Index < FirstComponent->Length; Index += 1) {
|
|
|
|
if (FirstComponent->Buffer[Index] == '\\') {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// At this point Index denotes a backslash or is equal to the length of the
|
|
// string. So update string to be the remaining part. Decrement the length of
|
|
// the first component by the approprate amount
|
|
//
|
|
|
|
String->Buffer = &FirstComponent->Buffer[Index];
|
|
String->Length = (SHORT)(FirstComponent->Length - Index);
|
|
|
|
FirstComponent->Length = (SHORT)Index;
|
|
|
|
//
|
|
// And return to our caller.
|
|
//
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Local support routine
|
|
//
|
|
|
|
int
|
|
NtfsCompareName (
|
|
IN CSTRING AnsiString,
|
|
IN UNICODE_STRING UnicodeString
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine compares two names (one ansi and one unicode) for equality.
|
|
|
|
Arguments:
|
|
|
|
AnsiString - Supplies the ansi string to compare
|
|
|
|
UnicodeString - Supplies the unicode string to compare
|
|
|
|
Return Value:
|
|
|
|
< 0 if AnsiString is approximately < than UnicodeString
|
|
= 0 if AnsiString is approximately == UnicodeString
|
|
> 0 otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG i;
|
|
ULONG Length;
|
|
|
|
//
|
|
// Determine length for compare
|
|
//
|
|
|
|
if (AnsiString.Length * sizeof( WCHAR ) < UnicodeString.Length) {
|
|
Length = AnsiString.Length;
|
|
} else {
|
|
Length = UnicodeString.Length / sizeof( WCHAR );
|
|
}
|
|
|
|
i = 0;
|
|
while (i < Length) {
|
|
|
|
//
|
|
// If the current char is a mismatch, return the difference
|
|
//
|
|
|
|
if (ToUpper( (USHORT)AnsiString.Buffer[i] ) != ToUpper( UnicodeString.Buffer[i] )) {
|
|
return ToUpper( (USHORT)AnsiString.Buffer[i] ) - ToUpper( UnicodeString.Buffer[i] );
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
//
|
|
// We've compared equal up to the length of the shortest string. Return
|
|
// based on length comparison now.
|
|
//
|
|
|
|
return AnsiString.Length - UnicodeString.Length / sizeof( WCHAR );
|
|
}
|