718 lines
18 KiB
C
718 lines
18 KiB
C
// fastfind.c
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <nt.h>
|
||
#include <ntrtl.h>
|
||
#include <nturtl.h>
|
||
#include <windows.h>
|
||
|
||
#include <ntioapi.h>
|
||
#include <myntfs.h>
|
||
|
||
#define VOLUME_PATH L"\\\\.\\H:"
|
||
#define VOLUME_DRIVE_LETTER_INDEX 4
|
||
#define FULL_PATH L"\\??\\H:\\1234567890123456"
|
||
#define FULL_DRIVE_LETTER_INDEX 4
|
||
#define DEVICE_PREFIX_LEN 14
|
||
|
||
typedef struct _EXTENT {
|
||
|
||
LONGLONG Vcn;
|
||
LONGLONG Lcn;
|
||
LONGLONG Length;
|
||
|
||
} EXTENT, *PEXTENT;
|
||
|
||
#define MAX_EXTENTS 64
|
||
|
||
//
|
||
// Some globals.
|
||
//
|
||
|
||
LARGE_INTEGER MftStart;
|
||
ULONG ClusterSize;
|
||
ULONG FrsSize;
|
||
EXTENT Extents[MAX_EXTENTS];
|
||
ULONG DebugLevel;
|
||
UCHAR CacheBuffer[0x10000]; // max cluster size
|
||
LONGLONG CachedOffset = -1;
|
||
char mybuffer[32768];
|
||
|
||
LONGLONG
|
||
ComputeFileRecordLbo (
|
||
IN ULONG MftIndex
|
||
)
|
||
{
|
||
LONGLONG vcn;
|
||
LONGLONG lcn = 0;
|
||
ULONG extentIndex;
|
||
ULONG offsetWithinCluster;
|
||
|
||
vcn = (MftIndex * FrsSize) / ClusterSize;
|
||
|
||
for (extentIndex = 0; extentIndex < MAX_EXTENTS; extentIndex += 1) {
|
||
|
||
if ((vcn >= Extents[extentIndex].Vcn) &&
|
||
(vcn < Extents[extentIndex].Vcn + Extents[extentIndex].Length)) {
|
||
|
||
lcn = Extents[extentIndex].Lcn + (vcn - Extents[extentIndex].Vcn);
|
||
}
|
||
}
|
||
|
||
if (ClusterSize >= FrsSize ) {
|
||
|
||
offsetWithinCluster = (MftIndex % (ClusterSize / FrsSize)) * FrsSize;
|
||
return (lcn * ClusterSize + offsetWithinCluster);
|
||
|
||
} else {
|
||
|
||
//
|
||
// BUGBUG keithka 4/28/00 Handle old fashioned big frs and/or big
|
||
// clusters someday.
|
||
//
|
||
|
||
ASSERT( FALSE );
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
FindAttributeInFileRecord (
|
||
IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
|
||
IN ATTRIBUTE_TYPE_CODE TypeCode,
|
||
IN PATTRIBUTE_RECORD_HEADER PreviousAttribute OPTIONAL,
|
||
OUT PATTRIBUTE_RECORD_HEADER *Attribute
|
||
)
|
||
// Attribute set to NULL if not found.
|
||
{
|
||
PATTRIBUTE_RECORD_HEADER attr;
|
||
|
||
*Attribute = NULL;
|
||
|
||
if (FileRecord->Pad0[0] != 'F' ||
|
||
FileRecord->Pad0[1] != 'I' ||
|
||
FileRecord->Pad0[2] != 'L' ||
|
||
FileRecord->Pad0[3] != 'E') {
|
||
|
||
if (DebugLevel >= 1) {
|
||
|
||
printf( "\nBad MFT record %c%c%c%c",
|
||
FileRecord->Pad0[0],
|
||
FileRecord->Pad0[1],
|
||
FileRecord->Pad0[2],
|
||
FileRecord->Pad0[3] );
|
||
}
|
||
|
||
//
|
||
// This isn't a good file record, but that doesn't make this a corrupt volume.
|
||
// It's possible that this file record has never been used. Since we don't look
|
||
// at the MFT bitmap, we don't know if this was expected to be a valid filerecord.
|
||
// The output Attribute is set to NULL already, so we can exit now.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
if (0 == (FileRecord->Flags & FILE_RECORD_SEGMENT_IN_USE)) {
|
||
|
||
//
|
||
// File record not in use, skip it.
|
||
//
|
||
|
||
return;
|
||
}
|
||
|
||
if (NULL == PreviousAttribute) {
|
||
|
||
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR)FileRecord + FileRecord->FirstAttributeOffset);
|
||
|
||
} else {
|
||
|
||
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR) PreviousAttribute + PreviousAttribute->RecordLength);
|
||
|
||
if (((PUCHAR)attr - (PUCHAR)FileRecord) > (LONG) FrsSize) {
|
||
|
||
ASSERT (FALSE);
|
||
return;
|
||
}
|
||
}
|
||
|
||
while (attr->TypeCode < TypeCode &&
|
||
attr->TypeCode != $END) {
|
||
|
||
ASSERT( attr->RecordLength < FrsSize );
|
||
|
||
attr = (PATTRIBUTE_RECORD_HEADER) ((PUCHAR) attr + attr->RecordLength);
|
||
|
||
//
|
||
// BUGBUG keitha 4/20/00 need to handle attribute list case someday...
|
||
// It's relativley rare that an MFT gets so fragmented it needs an
|
||
// attribute list. Certainly rare enough to skip it for now in a
|
||
// piece of test code.
|
||
//
|
||
}
|
||
|
||
if (attr->TypeCode == TypeCode) {
|
||
|
||
*Attribute = attr;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
BOOLEAN
|
||
FindNameInFileRecord (
|
||
IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
|
||
IN PWCHAR FileName,
|
||
IN ULONG FileNameLength
|
||
)
|
||
{
|
||
PATTRIBUTE_RECORD_HEADER attr;
|
||
PFILE_NAME fileNameAttr;
|
||
ULONG cmpResult;
|
||
|
||
FindAttributeInFileRecord( FileRecord,
|
||
$FILE_NAME,
|
||
NULL,
|
||
&attr );
|
||
|
||
while (NULL != attr) {
|
||
|
||
if (((PUCHAR)attr - (PUCHAR)FileRecord) > (LONG) FrsSize) {
|
||
|
||
ASSERT( FALSE );
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Names shouldn't go nonresident.
|
||
//
|
||
|
||
if (attr->FormCode != RESIDENT_FORM) {
|
||
|
||
ASSERT( FALSE );
|
||
return FALSE;
|
||
}
|
||
|
||
fileNameAttr = (PFILE_NAME) ((PUCHAR)attr + attr->Form.Resident.ValueOffset);
|
||
|
||
if (fileNameAttr->FileNameLength == FileNameLength) {
|
||
|
||
cmpResult = wcsncmp( FileName,
|
||
(PWCHAR) fileNameAttr->FileName,
|
||
fileNameAttr->FileNameLength );
|
||
|
||
if (0 == cmpResult) {
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
} else if (DebugLevel >= 3) {
|
||
|
||
printf( "\nNot a match %S,%S", FileName, fileNameAttr->FileName );
|
||
}
|
||
|
||
//
|
||
// Find the next filename, if any.
|
||
//
|
||
|
||
FindAttributeInFileRecord( FileRecord,
|
||
$FILE_NAME,
|
||
attr,
|
||
&attr );
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
|
||
int
|
||
FsTestOpenById (
|
||
IN UCHAR *ObjectId,
|
||
IN HANDLE VolumeHandle
|
||
)
|
||
{
|
||
HANDLE File;
|
||
IO_STATUS_BLOCK IoStatusBlock;
|
||
NTSTATUS Status;
|
||
NTSTATUS GetNameStatus;
|
||
NTSTATUS CloseStatus;
|
||
OBJECT_ATTRIBUTES ObjectAttributes;
|
||
UNICODE_STRING str;
|
||
WCHAR nameBuffer[MAX_PATH];
|
||
PFILE_NAME_INFORMATION FileName;
|
||
WCHAR Full[] = FULL_PATH; // Arrays of WCHAR's aren't constants
|
||
|
||
RtlInitUnicodeString( &str, Full );
|
||
|
||
str.Length = 8;
|
||
RtlCopyMemory( &str.Buffer[0], // no device prefix for relative open.
|
||
ObjectId,
|
||
8 );
|
||
|
||
InitializeObjectAttributes( &ObjectAttributes,
|
||
&str,
|
||
OBJ_CASE_INSENSITIVE,
|
||
VolumeHandle,
|
||
NULL );
|
||
|
||
Status = NtCreateFile( &File,
|
||
GENERIC_READ,
|
||
&ObjectAttributes,
|
||
&IoStatusBlock,
|
||
NULL,
|
||
FILE_ATTRIBUTE_NORMAL,
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
FILE_OPEN,
|
||
FILE_OPEN_BY_FILE_ID,
|
||
NULL,
|
||
0 );
|
||
|
||
if (NT_SUCCESS( Status )) {
|
||
|
||
RtlZeroMemory( nameBuffer, sizeof(nameBuffer) );
|
||
FileName = (PFILE_NAME_INFORMATION) &nameBuffer[0];
|
||
FileName->FileNameLength = sizeof(nameBuffer) - sizeof(ULONG);
|
||
|
||
GetNameStatus = NtQueryInformationFile( File,
|
||
&IoStatusBlock,
|
||
FileName,
|
||
sizeof(nameBuffer),
|
||
FileNameInformation );
|
||
|
||
printf( "%S\n", FileName->FileName );
|
||
|
||
CloseStatus = NtClose( File );
|
||
|
||
if (!NT_SUCCESS( CloseStatus )) {
|
||
|
||
printf( "\nCloseStatus %x", CloseStatus );
|
||
}
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
NTSTATUS
|
||
ReadFileRecord (
|
||
IN HANDLE VolumeHandle,
|
||
IN ULONG RecordIndex,
|
||
IN OUT PVOID Buffer
|
||
)
|
||
{
|
||
NTSTATUS status;
|
||
LARGE_INTEGER byteOffset;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
ULONG offsetWithinBuffer;
|
||
|
||
byteOffset.QuadPart = ComputeFileRecordLbo( RecordIndex );
|
||
|
||
if (FrsSize >= ClusterSize) {
|
||
|
||
status = NtReadFile( VolumeHandle,
|
||
NULL, // Event
|
||
NULL, // ApcRoutine
|
||
NULL, // ApcContext
|
||
&ioStatusBlock,
|
||
Buffer,
|
||
FrsSize,
|
||
&byteOffset, // ByteOffset
|
||
NULL ); // Key
|
||
|
||
} else {
|
||
|
||
//
|
||
// Clusters bigger than filerecords, do cluster
|
||
// size reads and dice up the returns.
|
||
//
|
||
|
||
if ((-1 == CachedOffset) ||
|
||
(byteOffset.QuadPart < CachedOffset) ||
|
||
((byteOffset.QuadPart + FrsSize) > (CachedOffset + ClusterSize))) {
|
||
|
||
if (DebugLevel >= 1) {
|
||
|
||
printf( "\nCache miss at %I64x", byteOffset.QuadPart );
|
||
}
|
||
|
||
status = NtReadFile( VolumeHandle,
|
||
NULL, // Event
|
||
NULL, // ApcRoutine
|
||
NULL, // ApcContext
|
||
&ioStatusBlock,
|
||
CacheBuffer,
|
||
ClusterSize,
|
||
&byteOffset, // ByteOffset
|
||
NULL ); // Key
|
||
|
||
if (STATUS_SUCCESS != status) {
|
||
|
||
//
|
||
// The cache buffer may be junk now, reread it next time.
|
||
//
|
||
|
||
CachedOffset = -1;
|
||
return status;
|
||
}
|
||
|
||
CachedOffset = byteOffset.QuadPart;
|
||
offsetWithinBuffer = 0;
|
||
|
||
} else {
|
||
|
||
if (DebugLevel >= 1) {
|
||
|
||
printf( "\nCache hit at %I64x", byteOffset.QuadPart );
|
||
}
|
||
offsetWithinBuffer = (ULONG) (byteOffset.QuadPart % CachedOffset);
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
|
||
RtlCopyMemory( Buffer, CacheBuffer + offsetWithinBuffer, FrsSize );
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
int
|
||
FastFind (
|
||
IN PWCHAR FileName,
|
||
IN PWCHAR DriveLetter
|
||
)
|
||
{
|
||
IO_STATUS_BLOCK IoStatusBlock;
|
||
UNICODE_STRING str;
|
||
NTSTATUS Status;
|
||
NTSTATUS ReadStatus;
|
||
NTSTATUS CloseStatus;
|
||
LARGE_INTEGER byteOffset;
|
||
LONGLONG mftBytesRead;
|
||
HANDLE volumeHandle;
|
||
DWORD WStatus;
|
||
WCHAR Full[] = FULL_PATH; // Arrays of WCHAR's aren't constants
|
||
WCHAR Volume[] = VOLUME_PATH;
|
||
BIOS_PARAMETER_BLOCK bpb;
|
||
PPACKED_BOOT_SECTOR bootSector;
|
||
PFILE_RECORD_SEGMENT_HEADER fileRecord;
|
||
PATTRIBUTE_RECORD_HEADER attr;
|
||
VCN nextVcn;
|
||
VCN currentVcn;
|
||
VCN vcnDelta;
|
||
LCN currentLcn;
|
||
LCN lcnDelta;
|
||
PUCHAR bsPtr;
|
||
UCHAR v;
|
||
UCHAR l;
|
||
UCHAR i;
|
||
ULONG extentCount;
|
||
ULONG recordIndex;
|
||
ULONG mftRecords;
|
||
ULONG fileNameLength;
|
||
MFT_SEGMENT_REFERENCE segRef;
|
||
|
||
RtlInitUnicodeString( &str, Full );
|
||
|
||
RtlCopyMemory( &str.Buffer[FULL_DRIVE_LETTER_INDEX], DriveLetter, sizeof(WCHAR) );
|
||
str.Length = 0x1E;
|
||
|
||
//
|
||
// Open the volume for relative opens.
|
||
//
|
||
|
||
RtlCopyMemory( &Volume[VOLUME_DRIVE_LETTER_INDEX], DriveLetter, sizeof(WCHAR) );
|
||
printf( "\nOpening volume handle, this may take a while..." );
|
||
volumeHandle = CreateFileW( (PUSHORT) &Volume,
|
||
GENERIC_READ | GENERIC_WRITE,
|
||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||
NULL,
|
||
OPEN_EXISTING,
|
||
0,
|
||
NULL );
|
||
|
||
if (volumeHandle == INVALID_HANDLE_VALUE) {
|
||
|
||
WStatus = GetLastError();
|
||
printf( "Unable to open %ws volume\n", &Volume );
|
||
printf( "Error from CreateFile", WStatus );
|
||
return WStatus;
|
||
}
|
||
|
||
printf( "\nVolume handle opened, starting MFT scan" );
|
||
byteOffset.QuadPart = 0;
|
||
|
||
ReadStatus = NtReadFile( volumeHandle,
|
||
NULL, // Event
|
||
NULL, // ApcRoutine
|
||
NULL, // ApcContext
|
||
&IoStatusBlock,
|
||
mybuffer,
|
||
0x200,
|
||
&byteOffset, // ByteOffset
|
||
NULL ); // Key
|
||
|
||
if (STATUS_SUCCESS != ReadStatus) {
|
||
|
||
printf( "\nBoot sector read failed with status %x", ReadStatus );
|
||
goto exit;
|
||
}
|
||
|
||
bootSector = (PPACKED_BOOT_SECTOR) mybuffer;
|
||
|
||
if (bootSector->Oem[0] != 'N' ||
|
||
bootSector->Oem[1] != 'T' ||
|
||
bootSector->Oem[2] != 'F' ||
|
||
bootSector->Oem[3] != 'S') {
|
||
|
||
printf( "\nNot an NTFS volume" );
|
||
goto exit;
|
||
}
|
||
|
||
NtfsUnpackBios( &bpb, &bootSector->PackedBpb );
|
||
|
||
ClusterSize = bpb.BytesPerSector * bpb.SectorsPerCluster;
|
||
if (bootSector->ClustersPerFileRecordSegment < 0) {
|
||
|
||
FrsSize = 1 << (-1 * bootSector->ClustersPerFileRecordSegment);
|
||
|
||
} else {
|
||
|
||
FrsSize = bootSector->ClustersPerFileRecordSegment * ClusterSize;
|
||
}
|
||
|
||
MftStart.QuadPart = ClusterSize * bootSector->MftStartLcn;
|
||
|
||
mftBytesRead = 0;
|
||
|
||
ReadStatus = NtReadFile( volumeHandle,
|
||
NULL, // Event
|
||
NULL, // ApcRoutine
|
||
NULL, // ApcContext
|
||
&IoStatusBlock,
|
||
mybuffer,
|
||
FrsSize,
|
||
&MftStart, // ByteOffset
|
||
NULL ); // Key
|
||
|
||
if (STATUS_SUCCESS != ReadStatus) {
|
||
|
||
printf( "\nMFT record 0 read failed with status %x", ReadStatus );
|
||
goto exit;
|
||
}
|
||
|
||
mftBytesRead += IoStatusBlock.Information;
|
||
|
||
FindAttributeInFileRecord( (PFILE_RECORD_SEGMENT_HEADER) mybuffer,
|
||
$DATA,
|
||
NULL,
|
||
&attr );
|
||
|
||
if (NULL == attr) {
|
||
|
||
printf( "\nMFT record 0 has no $DATA attribute" );
|
||
goto exit;
|
||
}
|
||
|
||
if (attr->FormCode == RESIDENT_FORM) {
|
||
|
||
printf( "\nVolume has very few files, use dir /s" );
|
||
goto exit;
|
||
}
|
||
|
||
//
|
||
// BUGBUG keithka 4/28/00 Handle MFT with more than 4billion entries.
|
||
//
|
||
|
||
ASSERT (attr->Form.Nonresident.FileSize <= MAXULONG);
|
||
mftRecords = (ULONG) (attr->Form.Nonresident.FileSize / FrsSize);
|
||
|
||
//
|
||
// Crack mapping pairs, read those clusters in a few big trnasfers,
|
||
// seek out given filename in those buffers.
|
||
//
|
||
|
||
nextVcn = attr->Form.Nonresident.LowestVcn;
|
||
currentLcn = 0;
|
||
extentCount = 0;
|
||
RtlZeroMemory( Extents, sizeof(Extents) );
|
||
|
||
bsPtr = ((PUCHAR) attr) + attr->Form.Nonresident.MappingPairsOffset;
|
||
|
||
while (*bsPtr != 0) {
|
||
|
||
currentVcn = nextVcn;
|
||
|
||
//
|
||
// Variable names v and l used for consistency with comments in
|
||
// ATTRIBUTE_RECORD_HEADER struct explaining how to decompress
|
||
// mapping pair information.
|
||
//
|
||
|
||
v = (*bsPtr) & 0xf;
|
||
l = ((*bsPtr) & 0xf0) >> 4;
|
||
|
||
bsPtr += 1;
|
||
|
||
for (vcnDelta = 0, i = 0; i < v; i++) {
|
||
|
||
vcnDelta += *(bsPtr++) << (8 * i);
|
||
}
|
||
|
||
for (lcnDelta = 0, i = 0; i < l; i++) {
|
||
|
||
lcnDelta += *(bsPtr++) << (8 * i);
|
||
}
|
||
|
||
//
|
||
// Sign extend.
|
||
//
|
||
|
||
if (0x80 & (*(bsPtr - 1))) {
|
||
|
||
for(; i < sizeof(lcnDelta); i++) {
|
||
|
||
lcnDelta += 0xff << (8 * i);
|
||
}
|
||
}
|
||
|
||
currentLcn += lcnDelta;
|
||
// printf( "\nVcn %I64x, Lcn %I64x, Length %I64x", currentVcn, currentLcn, vcnDelta );
|
||
|
||
if (extentCount < MAX_EXTENTS) {
|
||
|
||
Extents[extentCount].Vcn = currentVcn;
|
||
Extents[extentCount].Lcn = currentLcn;
|
||
Extents[extentCount].Length = vcnDelta;
|
||
|
||
extentCount += 1;
|
||
|
||
} else {
|
||
|
||
printf( "\nExcessive MFT fragmentation, redefine MAX_EXTENTS and recompile" );
|
||
}
|
||
|
||
currentVcn += vcnDelta;
|
||
}
|
||
|
||
//
|
||
// Now we know where the MFT is, let's go read it.
|
||
//
|
||
|
||
fileNameLength = wcslen( FileName );
|
||
|
||
for (recordIndex = 0; recordIndex <= mftRecords; recordIndex++) {
|
||
|
||
ReadStatus = ReadFileRecord( volumeHandle,
|
||
recordIndex,
|
||
mybuffer );
|
||
|
||
if (STATUS_SUCCESS != ReadStatus) {
|
||
|
||
printf( "\nMFT record read failed with status %x", ReadStatus );
|
||
goto exit;
|
||
}
|
||
|
||
if (FindNameInFileRecord( (PFILE_RECORD_SEGMENT_HEADER) mybuffer,
|
||
FileName,
|
||
fileNameLength )) {
|
||
|
||
//
|
||
// Found a match, open by id and retrieve name.
|
||
//
|
||
|
||
if (DebugLevel >= 1) {
|
||
|
||
printf( "\nFound match in file %08x %08x\n",
|
||
((PFILE_RECORD_SEGMENT_HEADER) mybuffer)->SequenceNumber,
|
||
recordIndex );
|
||
|
||
} else {
|
||
|
||
printf( "\n" );
|
||
}
|
||
|
||
segRef.SegmentNumberLowPart = recordIndex;
|
||
segRef.SegmentNumberHighPart = 0;
|
||
segRef.SequenceNumber = ((PFILE_RECORD_SEGMENT_HEADER) mybuffer)->SequenceNumber;
|
||
|
||
FsTestOpenById( (PUCHAR) &segRef, volumeHandle );
|
||
}
|
||
|
||
//
|
||
// The number 0x400 is completely arbitrary. It's a reasonable interval
|
||
// of work to do before printing another period to tell the user we're
|
||
// making progress still.
|
||
//
|
||
|
||
if (0 == (recordIndex % 0x400)) {
|
||
|
||
printf( "." );
|
||
}
|
||
}
|
||
|
||
exit:
|
||
if (volumeHandle != NULL) {
|
||
|
||
CloseHandle( volumeHandle );
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
VOID
|
||
FastFindHelp (
|
||
char *ExeName
|
||
)
|
||
{
|
||
|
||
printf( "This program finds a file by scanning the MFT (ntfs only).\n\n" );
|
||
printf( "usage: %s x: filename\n", ExeName );
|
||
|
||
printf( "Where x: is the drive letter\n" );
|
||
printf( "example:\n" );
|
||
printf( "%s d: windows.h", ExeName );
|
||
}
|
||
|
||
VOID
|
||
_cdecl
|
||
main (
|
||
int argc,
|
||
char *argv[]
|
||
)
|
||
{
|
||
WCHAR drive;
|
||
ANSI_STRING fileName;
|
||
WCHAR uniBuff[MAX_PATH];
|
||
UNICODE_STRING uniFileName;
|
||
|
||
//
|
||
// Get parameters.
|
||
//
|
||
|
||
if (argc < 3) {
|
||
|
||
FastFindHelp( argv[0] );
|
||
return;
|
||
}
|
||
|
||
if (argc >= 4) {
|
||
|
||
sscanf( argv[3], "%x", &DebugLevel );
|
||
|
||
} else {
|
||
|
||
DebugLevel = 0;
|
||
}
|
||
|
||
drive = *argv[1];
|
||
|
||
RtlInitAnsiString( &fileName, argv[2] );
|
||
uniFileName.Buffer = uniBuff;
|
||
RtlAnsiStringToUnicodeString( &uniFileName, &fileName, FALSE );
|
||
|
||
FastFind( uniFileName.Buffer, &drive );
|
||
|
||
return;
|
||
}
|