mirror of
https://github.com/reactos/reactos.git
synced 2024-06-17 10:01:58 +00:00
6cd2bc16b8
svn path=/trunk/; revision=130
1188 lines
30 KiB
C
1188 lines
30 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS kernel
|
|
* FILE: services/fs/vfat/iface.c
|
|
* PURPOSE: VFAT Filesystem
|
|
* PROGRAMMER: Jason Filby (jasonfilby@yahoo.com)
|
|
* UPDATE HISTORY:
|
|
?? Created
|
|
24-10-1998 Fixed bugs in long filename support
|
|
Fixed a bug that prevented unsuccessful file open requests being reported
|
|
Now works with long filenames that span over a sector boundary
|
|
28-10-1998 Reads entire FAT into memory
|
|
VFatReadSector modified to read in more than one sector at a time
|
|
7-11-1998 Fixed bug that assumed that directory data could be fragmented
|
|
8-12-1998 Added FAT32 support
|
|
Added initial writability functions
|
|
WARNING: DO NOT ATTEMPT TO TEST WRITABILITY FUNCTIONS!!!
|
|
12-12-1998 Added basic support for FILE_STANDARD_INFORMATION request
|
|
|
|
*/
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
#include <ddk/ntddk.h>
|
|
#include <internal/string.h>
|
|
#include <wstring.h>
|
|
#include <ddk/cctypes.h>
|
|
|
|
#define NDEBUG
|
|
#include <internal/debug.h>
|
|
|
|
#include "vfat.h"
|
|
|
|
#define FAT16 (1)
|
|
#define FAT12 (2)
|
|
#define FAT32 (3)
|
|
|
|
typedef struct
|
|
{
|
|
PDEVICE_OBJECT StorageDevice;
|
|
BootSector *Boot;
|
|
int rootDirectorySectors, FATStart, rootStart, dataStart;
|
|
int FATEntriesPerSector, FATUnit;
|
|
ULONG BytesPerCluster;
|
|
ULONG FatType;
|
|
unsigned char* FAT;
|
|
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
|
|
|
|
typedef struct
|
|
{
|
|
FATDirEntry entry;
|
|
} FCB, *PFCB;
|
|
|
|
#define ENTRIES_PER_SECTOR (BLOCKSIZE / sizeof(FATDirEntry))
|
|
|
|
/* GLOBALS *****************************************************************/
|
|
|
|
static PDRIVER_OBJECT DriverObject;
|
|
|
|
/* FUNCTIONS ****************************************************************/
|
|
|
|
ULONG Fat32GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|
{
|
|
ULONG FATsector;
|
|
ULONG FATeis;
|
|
PULONG Block;
|
|
Block = ExAllocatePool(NonPagedPool,1024);
|
|
FATsector=CurrentCluster/(512/sizeof(ULONG));
|
|
FATeis=CurrentCluster-(FATsector*(512/sizeof(ULONG)));
|
|
VFATReadSectors(DeviceExt->StorageDevice,DeviceExt->FATStart+FATsector, 1,
|
|
Block);
|
|
CurrentCluster = Block[FATeis];
|
|
if (CurrentCluster >= 0xffffff8 && CurrentCluster <= 0xfffffff)
|
|
CurrentCluster = 0;
|
|
ExFreePool(Block);
|
|
return(CurrentCluster);
|
|
}
|
|
|
|
ULONG Fat16GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|
{
|
|
ULONG FATsector;
|
|
ULONG FATeis;
|
|
PUSHORT Block;
|
|
|
|
FATsector=CurrentCluster/(512/sizeof(USHORT));
|
|
FATeis=CurrentCluster-(FATsector*256);
|
|
|
|
|
|
// VFATReadSectors(DeviceExt->StorageDevice,DeviceExt->FATStart+FATsector, 1,
|
|
// (UCHAR *)Block);
|
|
|
|
Block = (PUSHORT)(DeviceExt->FAT + (FATsector * BLOCKSIZE));
|
|
|
|
CurrentCluster = Block[FATeis];
|
|
if (CurrentCluster >= 0xfff8 && CurrentCluster <= 0xffff)
|
|
CurrentCluster = 0;
|
|
|
|
DPRINT("Returning %x\n",CurrentCluster);
|
|
return(CurrentCluster);
|
|
}
|
|
|
|
ULONG Fat12GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|
{
|
|
unsigned char* CBlock;
|
|
ULONG FATsector;
|
|
ULONG FATOffset;
|
|
ULONG Entry;
|
|
|
|
|
|
FATsector = (CurrentCluster * 12) / (512 * 8);
|
|
|
|
// VFATReadSectors(DeviceExt->StorageDevice,DeviceExt->FATStart
|
|
// +FATsector,1,CBlock);
|
|
|
|
CBlock = (unsigned char *)(DeviceExt->FAT + (FATsector * BLOCKSIZE));
|
|
|
|
FATOffset = (CurrentCluster * 12) % (512 * 8);
|
|
DPRINT("FATSector %d FATOffset %d\n",FATsector,FATOffset);
|
|
if ((CurrentCluster % 2) == 0)
|
|
{
|
|
Entry = CBlock[((FATOffset / 24)*3)];
|
|
Entry |= (CBlock[((FATOffset / 24)*3) + 1] & 0xf);
|
|
}
|
|
else
|
|
{
|
|
Entry = (CBlock[((FATOffset / 24)*3) + 1] >> 4);
|
|
Entry |= (CBlock[((FATOffset / 24)*3) + 2] << 4);
|
|
}
|
|
DPRINT("Entry %x\n",Entry);
|
|
if (Entry >= 0xff8 && Entry <= 0xfff)
|
|
Entry = 0;
|
|
CurrentCluster = Entry;
|
|
|
|
DPRINT("Returning %x\n",CurrentCluster);
|
|
return(CurrentCluster);
|
|
}
|
|
|
|
ULONG GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|
{
|
|
|
|
DPRINT("GetNextCluster(DeviceExt %x, CurrentCluster %x)\n",
|
|
DeviceExt,CurrentCluster);
|
|
if (DeviceExt->FatType == FAT16)
|
|
{
|
|
return(Fat16GetNextCluster(DeviceExt, CurrentCluster));
|
|
}
|
|
else if (DeviceExt->FatType == FAT32)
|
|
{
|
|
return(Fat32GetNextCluster(DeviceExt, CurrentCluster));
|
|
}
|
|
else
|
|
{
|
|
return(Fat12GetNextCluster(DeviceExt, CurrentCluster));
|
|
}
|
|
}
|
|
|
|
ULONG FAT16FindAvailableCluster(PDEVICE_EXTENSION DeviceExt)
|
|
{
|
|
ULONG sector;
|
|
PUSHORT Block;
|
|
int i;
|
|
|
|
sector = 0;
|
|
Block = ExAllocatePool(NonPagedPool,BLOCKSIZE);
|
|
|
|
while(sector<DeviceExt->Boot->FATSectors) {
|
|
memcpy(Block, DeviceExt->FAT+sector*BLOCKSIZE, BLOCKSIZE);
|
|
|
|
for(i=0; i<512; i++) {
|
|
if(Block[i]==0) {
|
|
ExFreePool(Block);
|
|
return i;
|
|
}
|
|
}
|
|
|
|
sector++;
|
|
}
|
|
|
|
/* Give an error message (out of disk space) if we reach here) */
|
|
|
|
ExFreePool(Block);
|
|
return 0;
|
|
}
|
|
|
|
void FAT16WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite,
|
|
ULONG NewValue)
|
|
{
|
|
ULONG FATsector;
|
|
ULONG FATeis;
|
|
PUSHORT Block;
|
|
|
|
Block = ExAllocatePool(NonPagedPool,BLOCKSIZE);
|
|
|
|
FATsector=ClusterToWrite/(512/sizeof(USHORT));
|
|
FATeis=ClusterToWrite-(FATsector*256);
|
|
|
|
/* Update the in-memory FAT */
|
|
memcpy(Block, DeviceExt->FAT+FATsector*BLOCKSIZE, BLOCKSIZE);
|
|
Block[FATeis] = NewValue;
|
|
memcpy(DeviceExt->FAT+FATsector*BLOCKSIZE, Block, BLOCKSIZE);
|
|
|
|
/* Write the changed FAT sector to disk */
|
|
VFATWriteSectors(DeviceExt->StorageDevice,
|
|
DeviceExt->FATStart+FATsector,
|
|
DeviceExt->Boot->SectorsPerCluster,
|
|
(UCHAR *)Block);
|
|
|
|
ExFreePool(Block);
|
|
}
|
|
|
|
void FAT12WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite,
|
|
ULONG NewValue)
|
|
{
|
|
unsigned char* CBlock;
|
|
ULONG FATsector;
|
|
ULONG FATOffset;
|
|
ULONG Entry;
|
|
|
|
CBlock = ExAllocatePool(NonPagedPool,1024);
|
|
FATsector = (ClusterToWrite * 12) / (512 * 8);
|
|
|
|
memcpy(CBlock,DeviceExt->FAT+FATsector*BLOCKSIZE, BLOCKSIZE);
|
|
FATOffset = (ClusterToWrite * 12) % (512 * 8);
|
|
|
|
DPRINT("FATSector %d FATOffset %d\n",FATsector,FATOffset);
|
|
|
|
/*
|
|
|
|
Write 12-bit entry
|
|
|
|
if ((CurrentCluster % 2) == 0)
|
|
{
|
|
Entry = CBlock[((FATOffset / 24)*3)];
|
|
Entry |= (CBlock[((FATOffset / 24)*3) + 1] & 0xf);
|
|
}
|
|
else
|
|
{
|
|
Entry = (CBlock[((FATOffset / 24)*3) + 1] >> 4);
|
|
Entry |= (CBlock[((FATOffset / 24)*3) + 2] << 4);
|
|
} */
|
|
|
|
ExFreePool(CBlock);
|
|
}
|
|
|
|
void WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite,
|
|
ULONG NewValue)
|
|
{
|
|
if (DeviceExt->FatType == FAT16) {
|
|
FAT16WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
|
} else {
|
|
FAT12WriteCluster(DeviceExt, ClusterToWrite, NewValue);
|
|
}
|
|
}
|
|
|
|
ULONG GetNextWriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster)
|
|
{
|
|
ULONG LastCluster, NewCluster;
|
|
BOOLEAN EOF = FALSE;
|
|
|
|
DPRINT("GetNextWriteCluster(DeviceExt %x, CurrentCluster %x)\n",
|
|
DeviceExt,CurrentCluster);
|
|
|
|
/* Find out what was happening in the last cluster's AU */
|
|
|
|
if (DeviceExt->FatType == FAT16)
|
|
{
|
|
LastCluster = Fat16GetNextCluster(DeviceExt, CurrentCluster);
|
|
if(LastCluster == 0xFFFF) {
|
|
EOF = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LastCluster = Fat12GetNextCluster(DeviceExt, CurrentCluster);
|
|
if(LastCluster == 0xFFF) {
|
|
EOF = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Check to see if we must append or overwrite */
|
|
|
|
if (EOF == TRUE) {
|
|
|
|
/* Append */
|
|
|
|
/* Firstly, find the next available open allocation unit */
|
|
NewCluster = FAT16FindAvailableCluster(DeviceExt);
|
|
|
|
/* Mark the new AU as the EOF */
|
|
if(DeviceExt->FatType == FAT16) {
|
|
FAT16WriteCluster(DeviceExt, NewCluster, 0xFFFF);
|
|
} else {
|
|
FAT12WriteCluster(DeviceExt, NewCluster, 0xFFF);
|
|
}
|
|
|
|
/* Now, write the AU of the LastCluster with the value of the newly
|
|
found AU */
|
|
WriteCluster(DeviceExt, LastCluster, NewCluster);
|
|
|
|
/* Return NewCluster as CurrentCluster */
|
|
return NewCluster;
|
|
|
|
} else {
|
|
|
|
/* Overwrite: Return LastCluster as CurrentCluster */
|
|
return LastCluster;
|
|
|
|
}
|
|
}
|
|
|
|
unsigned long ClusterToSector(PDEVICE_EXTENSION DeviceExt,
|
|
unsigned long Cluster)
|
|
{
|
|
return DeviceExt->dataStart+((Cluster-2)*DeviceExt->Boot->SectorsPerCluster);
|
|
}
|
|
|
|
void RtlAnsiToUnicode(PWSTR Dest, PCH Source, ULONG Length)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; (i<Length && Source[i] != ' '); i++)
|
|
{
|
|
Dest[i] = Source[i];
|
|
}
|
|
Dest[i]=0;
|
|
}
|
|
|
|
void RtlCatAnsiToUnicode(PWSTR Dest, PCH Source, ULONG Length)
|
|
{
|
|
ULONG i;
|
|
|
|
while((*Dest)!=0)
|
|
{
|
|
Dest++;
|
|
}
|
|
for (i=0; (i<Length && Source[i] != ' '); i++)
|
|
{
|
|
Dest[i] = Source[i];
|
|
}
|
|
Dest[i]=0;
|
|
}
|
|
|
|
void vfat_initstr(wchar_t *wstr, ULONG wsize)
|
|
{
|
|
int i;
|
|
wchar_t nc=0;
|
|
for(i=0; i<wsize; i++)
|
|
{
|
|
*wstr=nc;
|
|
wstr++;
|
|
}
|
|
wstr=wstr-wsize;
|
|
}
|
|
|
|
wchar_t * vfat_wcsncat(wchar_t * dest, const wchar_t * src,size_t wstart, size_t wcount)
|
|
{
|
|
int i;
|
|
|
|
dest+=wstart;
|
|
for(i=0; i<wcount; i++)
|
|
{
|
|
*dest=src[i];
|
|
dest++;
|
|
}
|
|
dest=dest-(wcount+wstart);
|
|
|
|
return dest;
|
|
}
|
|
|
|
wchar_t * vfat_wcsncpy(wchar_t * dest, const wchar_t *src,size_t wcount)
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<wcount;i++)
|
|
{
|
|
*dest=src[i];
|
|
dest++;
|
|
}
|
|
dest=dest-wcount;
|
|
|
|
return(dest);
|
|
}
|
|
|
|
wchar_t * vfat_movstr(wchar_t * dest, const wchar_t *src, ULONG dpos,
|
|
ULONG spos, ULONG len)
|
|
{
|
|
int i;
|
|
|
|
dest+=dpos;
|
|
for(i=spos; i<spos+len; i++)
|
|
{
|
|
*dest=src[i];
|
|
dest++;
|
|
}
|
|
dest-=(dpos+len);
|
|
|
|
return(dest);
|
|
}
|
|
|
|
BOOLEAN IsLastEntry(PVOID Block, ULONG Offset)
|
|
{
|
|
return(((FATDirEntry *)Block)[Offset].Filename[0] == 0);
|
|
}
|
|
|
|
BOOLEAN IsDeletedEntry(PVOID Block, ULONG Offset)
|
|
{
|
|
/* Checks special character (short names) or attrib=0 (long names) */
|
|
|
|
return ((((FATDirEntry *)Block)[Offset].Filename[0] == 0xe5) ||
|
|
(((FATDirEntry *)Block)[Offset].Attrib == 0x00));
|
|
}
|
|
|
|
BOOLEAN GetEntryName(PVOID Block, PULONG _Offset, PWSTR Name, PULONG _jloop,
|
|
PDEVICE_EXTENSION DeviceExt, PULONG _StartingSector)
|
|
{
|
|
FATDirEntry* test;
|
|
slot* test2;
|
|
ULONG Offset = *_Offset;
|
|
ULONG StartingSector = *_StartingSector;
|
|
ULONG jloop = *_jloop;
|
|
ULONG cpos;
|
|
WCHAR tmp[256];
|
|
|
|
test = (FATDirEntry *)Block;
|
|
test2 = (slot *)Block;
|
|
|
|
*Name = 0;
|
|
|
|
if (IsDeletedEntry(Block,Offset))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
if(test2[Offset].attr == 0x0f)
|
|
{
|
|
vfat_initstr(Name, 256);
|
|
vfat_wcsncpy(Name,test2[Offset].name0_4,5);
|
|
vfat_wcsncat(Name,test2[Offset].name5_10,5,6);
|
|
vfat_wcsncat(Name,test2[Offset].name11_12,11,2);
|
|
|
|
cpos=0;
|
|
while((test2[Offset].id!=0x41) && (test2[Offset].id!=0x01) &&
|
|
(test2[Offset].attr>0))
|
|
{
|
|
Offset++;
|
|
if(Offset==ENTRIES_PER_SECTOR) {
|
|
Offset=0;
|
|
StartingSector++;
|
|
jloop++;
|
|
VFATReadSectors(DeviceExt->StorageDevice,StartingSector,1,Block);
|
|
test2 = (slot *)Block;
|
|
}
|
|
cpos++;
|
|
|
|
vfat_initstr(tmp, 256);
|
|
vfat_movstr(tmp, Name, 13, 0, cpos*13);
|
|
vfat_wcsncpy(Name, tmp, 256);
|
|
vfat_wcsncpy(Name, test2[Offset].name0_4, 5);
|
|
vfat_wcsncat(Name,test2[Offset].name5_10,5,6);
|
|
vfat_wcsncat(Name,test2[Offset].name11_12,11,2);
|
|
|
|
}
|
|
|
|
if (IsDeletedEntry(Block,Offset+1))
|
|
{
|
|
Offset++;
|
|
*_Offset = Offset;
|
|
*_jloop = jloop;
|
|
*_StartingSector = StartingSector;
|
|
return(FALSE);
|
|
}
|
|
|
|
*_Offset = Offset;
|
|
*_jloop = jloop;
|
|
*_StartingSector = StartingSector;
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
RtlAnsiToUnicode(Name,test[Offset].Filename,8);
|
|
if (test[Offset].Ext[0]!=' ')
|
|
{
|
|
RtlCatAnsiToUnicode(Name,".",1);
|
|
}
|
|
RtlCatAnsiToUnicode(Name,test[Offset].Ext,3);
|
|
|
|
*_Offset = Offset;
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
BOOLEAN wstrcmpi(PWSTR s1, PWSTR s2)
|
|
{
|
|
DPRINT("s1 '%w' s2 '%w'\n",s1,s2);
|
|
while (wtolower(*s1)==wtolower(*s2))
|
|
{
|
|
if ((*s1)==0 && (*s2)==0)
|
|
{
|
|
return(TRUE);
|
|
}
|
|
|
|
s1++;
|
|
s2++;
|
|
}
|
|
return(FALSE);
|
|
}
|
|
|
|
NTSTATUS FindFile(PDEVICE_EXTENSION DeviceExt, PFCB Fcb,
|
|
PFCB Parent, PWSTR FileToFind)
|
|
{
|
|
ULONG i, j;
|
|
ULONG Size;
|
|
char* block;
|
|
WCHAR name[256];
|
|
ULONG StartingSector;
|
|
ULONG NextCluster;
|
|
DPRINT("FileFile(Parent %x, FileToFind %w)\n",Parent,FileToFind);
|
|
|
|
if (Parent == NULL)
|
|
{
|
|
Size = DeviceExt->rootDirectorySectors;//FIXME : in fat32, no limit
|
|
StartingSector = DeviceExt->rootStart;
|
|
}
|
|
else
|
|
{
|
|
DPRINT("Parent->entry.FileSize %x\n",Parent->entry.FileSize);
|
|
|
|
Size = ULONG_MAX;
|
|
if (DeviceExt->FatType == FAT32)
|
|
NextCluster = Parent->entry.FirstCluster+Parent->entry.FirstClusterHigh*65536;
|
|
else
|
|
NextCluster = Parent->entry.FirstCluster;
|
|
StartingSector = ClusterToSector(DeviceExt, NextCluster);
|
|
}
|
|
block = ExAllocatePool(NonPagedPool,BLOCKSIZE);
|
|
|
|
for (j=0; j<Size; j++)
|
|
{
|
|
VFATReadSectors(DeviceExt->StorageDevice,StartingSector,1,block);
|
|
|
|
for (i=0; i<ENTRIES_PER_SECTOR; i++)
|
|
{
|
|
if (IsLastEntry((PVOID)block,i))
|
|
{
|
|
ExFreePool(block);
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
if (GetEntryName((PVOID)block,&i,name,&j,DeviceExt,&StartingSector))
|
|
{
|
|
DPRINT("Scanning %w\n",name);
|
|
DPRINT("Comparing %w %w\n",name,FileToFind);
|
|
if (wstrcmpi(name,FileToFind))
|
|
{
|
|
/* In the case of a long filename, the firstcluster is stored in
|
|
the next record -- where it's short name is */
|
|
if(DeviceExt->FatType==FAT32)
|
|
if(((FATDirEntry *)block)[i].FirstCluster==0
|
|
&&((FATDirEntry *)block)[i].FirstClusterHigh==0
|
|
) i++;
|
|
else
|
|
if(((FATDirEntry *)block)[i].FirstCluster==0) i++;
|
|
DPRINT("Found it at cluster %u\n", ((FATDirEntry *)block)[i].FirstCluster);
|
|
if( i==ENTRIES_PER_SECTOR)
|
|
{
|
|
VFATReadSectors(DeviceExt->StorageDevice,StartingSector+1,1,block);
|
|
i=0;
|
|
}
|
|
|
|
memcpy(&Fcb->entry,&((FATDirEntry *)block)[i],
|
|
sizeof(FATDirEntry));
|
|
|
|
ExFreePool(block);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* It seems that directory sectors cannot be fragmented and therefore,
|
|
they only have a first cluster, but the one's after it are marked
|
|
with 0xffff. This theory is still not 100% certain, so the following
|
|
lines are commented and not removed */
|
|
|
|
StartingSector++;
|
|
/* if (Parent == NULL)
|
|
{
|
|
StartingSector++;
|
|
}
|
|
else
|
|
{
|
|
NextCluster = GetNextCluster(DeviceExt,NextCluster);
|
|
if (NextCluster == 0)
|
|
{
|
|
ExFreePool(block);
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
StartingSector = ClusterToSector(DeviceExt,NextCluster);
|
|
} */
|
|
}
|
|
ExFreePool(block);
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
|
|
NTSTATUS FsdCloseFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject)
|
|
/*
|
|
* FUNCTION: Closes a file
|
|
*/
|
|
{
|
|
/* NOP */
|
|
}
|
|
|
|
NTSTATUS FsdOpenFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,
|
|
PWSTR FileName)
|
|
/*
|
|
* FUNCTION: Opens a file
|
|
*/
|
|
{
|
|
PWSTR current;
|
|
PWSTR next;
|
|
PWSTR string = FileName;
|
|
PFCB ParentFcb = NULL;
|
|
PFCB Fcb = ExAllocatePool(NonPagedPool,sizeof(FCB));
|
|
PFCB Temp;
|
|
NTSTATUS Status;
|
|
|
|
next = &string[0];
|
|
current = next+1;
|
|
|
|
while (next!=NULL)
|
|
{
|
|
DPRINT("current %w next %x\n",current,next);
|
|
|
|
*next = '\\';
|
|
current = next+1;
|
|
next = wcschr(next+1,'\\');
|
|
if (next!=NULL)
|
|
{
|
|
*next=0;
|
|
}
|
|
|
|
Status = FindFile(DeviceExt,Fcb,ParentFcb,current);
|
|
if (Status != STATUS_SUCCESS)
|
|
{
|
|
return(Status);
|
|
}
|
|
Temp = Fcb;
|
|
if (ParentFcb == NULL)
|
|
{
|
|
Fcb = ExAllocatePool(NonPagedPool,sizeof(FCB));
|
|
ParentFcb = Temp;
|
|
}
|
|
else
|
|
{
|
|
Fcb = ParentFcb;
|
|
ParentFcb = Temp;
|
|
}
|
|
}
|
|
FileObject->FsContext = ParentFcb;
|
|
DPRINT("ParentFcb->entry.FileSize %d\n",ParentFcb->entry.FileSize);
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
BOOLEAN FsdHasFileSystem(PDEVICE_OBJECT DeviceToMount)
|
|
/*
|
|
* FUNCTION: Tests if the device contains a filesystem that can be mounted
|
|
* by this fsd
|
|
*/
|
|
{
|
|
BootSector* Boot;
|
|
|
|
Boot = ExAllocatePool(NonPagedPool,512);
|
|
|
|
VFATReadSectors(DeviceToMount, 0, 1, (UCHAR *)Boot);
|
|
|
|
if (strncmp(Boot->SysType,"FAT12",5)==0 ||
|
|
strncmp(Boot->SysType,"FAT16",5)==0 ||
|
|
strncmp(((struct _BootSector32 *)(Boot))->SysType,"FAT32",5)==0)
|
|
{
|
|
ExFreePool(Boot);
|
|
return(TRUE);
|
|
}
|
|
ExFreePool(Boot);
|
|
return(FALSE);
|
|
}
|
|
|
|
NTSTATUS FsdMountDevice(PDEVICE_EXTENSION DeviceExt,
|
|
PDEVICE_OBJECT DeviceToMount)
|
|
/*
|
|
* FUNCTION: Mounts the device
|
|
*/
|
|
{
|
|
int i;
|
|
|
|
DPRINT("Mounting VFAT device...");
|
|
DPRINT("DeviceExt %x\n",DeviceExt);
|
|
|
|
DeviceExt->Boot = ExAllocatePool(NonPagedPool,512);
|
|
VFATReadSectors(DeviceToMount, 0, 1, (UCHAR *)DeviceExt->Boot);
|
|
|
|
DPRINT("DeviceExt->Boot->BytesPerSector %x\n",
|
|
DeviceExt->Boot->BytesPerSector);
|
|
|
|
DeviceExt->FATStart=DeviceExt->Boot->ReservedSectors;
|
|
DeviceExt->rootDirectorySectors=
|
|
(DeviceExt->Boot->RootEntries*32)/DeviceExt->Boot->BytesPerSector;
|
|
DeviceExt->rootStart=
|
|
DeviceExt->FATStart+DeviceExt->Boot->FATCount*DeviceExt->Boot->FATSectors;
|
|
DeviceExt->dataStart=DeviceExt->rootStart+DeviceExt->rootDirectorySectors;
|
|
DeviceExt->FATEntriesPerSector=DeviceExt->Boot->BytesPerSector/32;
|
|
DeviceExt->BytesPerCluster = DeviceExt->Boot->SectorsPerCluster *
|
|
DeviceExt->Boot->BytesPerSector;
|
|
|
|
if (strncmp(DeviceExt->Boot->SysType,"FAT12",5)==0)
|
|
{
|
|
DeviceExt->FatType = FAT12;
|
|
}
|
|
else if (strncmp(((struct _BootSector32 *)(DeviceExt->Boot))->SysType,"FAT32",5)==0)
|
|
{
|
|
DeviceExt->FatType = FAT32;
|
|
DeviceExt->rootDirectorySectors=DeviceExt->Boot->SectorsPerCluster;
|
|
DeviceExt->rootStart=
|
|
DeviceExt->FATStart+DeviceExt->Boot->FATCount
|
|
* ((struct _BootSector32 *)( DeviceExt->Boot))->FATSectors32;
|
|
DeviceExt->dataStart=DeviceExt->rootStart;
|
|
}
|
|
else
|
|
{
|
|
DeviceExt->FatType = FAT16;
|
|
}
|
|
|
|
// with FAT32 it's not a good idea to load always fat in memory
|
|
// because on a 8GB partition with 2 KO clusters, the fat = 8 MO
|
|
if(DeviceExt->FatType!=FAT32)
|
|
{
|
|
DeviceExt->FAT = ExAllocatePool(NonPagedPool, BLOCKSIZE*DeviceExt->Boot->FATSectors);
|
|
VFATReadSectors(DeviceToMount, DeviceExt->FATStart, DeviceExt->Boot->FATSectors, (UCHAR *)DeviceExt->FAT);
|
|
}
|
|
}
|
|
|
|
void VFATLoadCluster(PDEVICE_EXTENSION DeviceExt, PVOID Buffer, ULONG Cluster)
|
|
{
|
|
ULONG Sector;
|
|
|
|
DPRINT("VFATLoadCluster(DeviceExt %x, Buffer %x, Cluster %d)\n",
|
|
DeviceExt,Buffer,Cluster);
|
|
|
|
Sector = ClusterToSector(DeviceExt, Cluster);
|
|
|
|
VFATReadSectors(DeviceExt->StorageDevice,
|
|
Sector,
|
|
DeviceExt->Boot->SectorsPerCluster,
|
|
Buffer);
|
|
}
|
|
|
|
void VFATWriteCluster(PDEVICE_EXTENSION DeviceExt, PVOID Buffer, ULONG Cluster)
|
|
{
|
|
ULONG Sector;
|
|
|
|
DPRINT("VFATWriteCluster(DeviceExt %x, Buffer %x, Cluster %d)\n",
|
|
DeviceExt,Buffer,Cluster);
|
|
|
|
Sector = ClusterToSector(DeviceExt, Cluster);
|
|
|
|
VFATWriteSectors(DeviceExt->StorageDevice,
|
|
Sector,
|
|
DeviceExt->Boot->SectorsPerCluster,
|
|
Buffer);
|
|
}
|
|
|
|
NTSTATUS FsdReadFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,
|
|
PVOID Buffer, ULONG Length, ULONG ReadOffset,
|
|
PULONG LengthRead)
|
|
/*
|
|
* FUNCTION: Reads data from a file
|
|
*/
|
|
{
|
|
ULONG CurrentCluster;
|
|
ULONG FileOffset;
|
|
ULONG FirstCluster;
|
|
PFCB Fcb;
|
|
PVOID Temp;
|
|
ULONG TempLength;
|
|
|
|
DPRINT("FsdReadFile(DeviceExt %x, FileObject %x, Buffer %x, "
|
|
"Length %d, ReadOffset %d)\n",DeviceExt,FileObject,Buffer,
|
|
Length,ReadOffset);
|
|
|
|
FirstCluster = ReadOffset / DeviceExt->BytesPerCluster;
|
|
Fcb = FileObject->FsContext;
|
|
if (DeviceExt->FatType == FAT32)
|
|
CurrentCluster = Fcb->entry.FirstCluster+Fcb->entry.FirstClusterHigh*65536;
|
|
else
|
|
CurrentCluster = Fcb->entry.FirstCluster;
|
|
|
|
if (ReadOffset >= Fcb->entry.FileSize)
|
|
{
|
|
return(STATUS_END_OF_FILE);
|
|
}
|
|
if ((ReadOffset + Length) > Fcb->entry.FileSize)
|
|
{
|
|
Length = Fcb->entry.FileSize - ReadOffset;
|
|
}
|
|
*LengthRead = 0;
|
|
|
|
DPRINT("DeviceExt->BytesPerCluster %x\n",DeviceExt->BytesPerCluster);
|
|
|
|
Temp = ExAllocatePool(NonPagedPool,DeviceExt->BytesPerCluster);
|
|
|
|
for (FileOffset=0; FileOffset < FirstCluster; FileOffset++)
|
|
{
|
|
CurrentCluster = GetNextCluster(DeviceExt,CurrentCluster);
|
|
}
|
|
CHECKPOINT;
|
|
if ((ReadOffset % DeviceExt->BytesPerCluster)!=0)
|
|
{
|
|
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
|
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
|
|
|
TempLength = min(Length,DeviceExt->BytesPerCluster -
|
|
(ReadOffset % DeviceExt->BytesPerCluster));
|
|
|
|
memcpy(Buffer, Temp + ReadOffset % DeviceExt->BytesPerCluster,
|
|
TempLength);
|
|
|
|
(*LengthRead) = (*LengthRead) + TempLength;
|
|
Length = Length - TempLength;
|
|
Buffer = Buffer + TempLength;
|
|
}
|
|
CHECKPOINT;
|
|
while (Length > DeviceExt->BytesPerCluster)
|
|
{
|
|
VFATLoadCluster(DeviceExt, Buffer, CurrentCluster);
|
|
CurrentCluster = GetNextCluster(DeviceExt, CurrentCluster);
|
|
|
|
if (CurrentCluster == 0)
|
|
{
|
|
ExFreePool(Temp);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
(*LengthRead) = (*LengthRead) + DeviceExt->BytesPerCluster;
|
|
Buffer = Buffer + DeviceExt->BytesPerCluster;
|
|
Length = Length - DeviceExt->BytesPerCluster;
|
|
}
|
|
CHECKPOINT;
|
|
if (Length > 0)
|
|
{
|
|
(*LengthRead) = (*LengthRead) + Length;
|
|
VFATLoadCluster(DeviceExt, Temp, CurrentCluster);
|
|
memcpy(Buffer, Temp, Length);
|
|
}
|
|
ExFreePool(Temp);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
NTSTATUS FsdWriteFile(PDEVICE_EXTENSION DeviceExt, PFILE_OBJECT FileObject,
|
|
PVOID Buffer, ULONG Length, ULONG WriteOffset)
|
|
/*
|
|
* FUNCTION: Writes data to file
|
|
*/
|
|
{
|
|
ULONG CurrentCluster;
|
|
ULONG FileOffset;
|
|
ULONG FirstCluster;
|
|
PFCB Fcb;
|
|
PVOID Temp;
|
|
ULONG TempLength;
|
|
|
|
/* Locate the first cluster of the file */
|
|
|
|
FirstCluster = WriteOffset / DeviceExt->BytesPerCluster;
|
|
Fcb = FileObject->FsContext;
|
|
if (DeviceExt->FatType == FAT32)
|
|
CurrentCluster = Fcb->entry.FirstCluster+Fcb->entry.FirstClusterHigh*65536;
|
|
else
|
|
CurrentCluster = Fcb->entry.FirstCluster;
|
|
|
|
/* Allocate a buffer to hold 1 cluster of data */
|
|
|
|
Temp = ExAllocatePool(NonPagedPool,DeviceExt->BytesPerCluster);
|
|
|
|
/* Find the cluster according to the offset in the file */
|
|
|
|
for (FileOffset=0; FileOffset < FirstCluster; FileOffset++)
|
|
{
|
|
CurrentCluster = GetNextCluster(DeviceExt,CurrentCluster);
|
|
}
|
|
CHECKPOINT;
|
|
|
|
/*
|
|
If the offset in the cluster doesn't fall on the cluster boundary then
|
|
we have to write only from the specified offset
|
|
*/
|
|
|
|
if ((WriteOffset % DeviceExt->BytesPerCluster)!=0)
|
|
{
|
|
TempLength = min(Length,DeviceExt->BytesPerCluster -
|
|
(WriteOffset % DeviceExt->BytesPerCluster));
|
|
|
|
/* Read in the existing cluster data */
|
|
VFATLoadCluster(DeviceExt,Temp,CurrentCluster);
|
|
|
|
/* Overwrite the last parts of the data as necessary */
|
|
memcpy(Temp + WriteOffset % DeviceExt->BytesPerCluster, Buffer,
|
|
TempLength);
|
|
|
|
/* Write the cluster back */
|
|
VFATWriteCluster(DeviceExt,Temp,CurrentCluster);
|
|
|
|
/* Next write cluster */
|
|
CurrentCluster = GetNextWriteCluster(DeviceExt, CurrentCluster);
|
|
|
|
Length = Length - TempLength;
|
|
Buffer = Buffer + TempLength;
|
|
}
|
|
CHECKPOINT;
|
|
|
|
/* Write the buffer in chunks of 1 cluster */
|
|
|
|
while (Length > DeviceExt->BytesPerCluster)
|
|
{
|
|
VFATWriteCluster(DeviceExt, Buffer, CurrentCluster);
|
|
CurrentCluster = GetNextWriteCluster(DeviceExt, CurrentCluster);
|
|
|
|
if (CurrentCluster == 0)
|
|
{
|
|
ExFreePool(Temp);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
Buffer = Buffer + DeviceExt->BytesPerCluster;
|
|
Length = Length - DeviceExt->BytesPerCluster;
|
|
}
|
|
CHECKPOINT;
|
|
|
|
/* Write the remainder */
|
|
|
|
if (Length > 0)
|
|
{
|
|
memcpy(Temp, Buffer, Length);
|
|
VFATWriteCluster(DeviceExt, Temp, CurrentCluster);
|
|
}
|
|
|
|
ExFreePool(Temp);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
NTSTATUS FsdClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = Stack->FileObject;
|
|
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
|
|
Status = FsdCloseFile(DeviceExtension,FileObject);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return(Status);
|
|
}
|
|
|
|
NTSTATUS FsdCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = Stack->FileObject;
|
|
NTSTATUS Status;
|
|
PDEVICE_EXTENSION DeviceExt;
|
|
|
|
DPRINT("VFAT FsdCreate...\n");
|
|
|
|
DeviceExt = DeviceObject->DeviceExtension;
|
|
Status = FsdOpenFile(DeviceExt,FileObject,FileObject->FileName.Buffer);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS FsdWrite(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
ULONG Length;
|
|
PVOID Buffer;
|
|
ULONG Offset;
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = Stack->FileObject;
|
|
PDEVICE_EXTENSION DeviceExt = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
|
|
DPRINT("FsdWrite(DeviceObject %x Irp %x)\n",DeviceObject,Irp);
|
|
|
|
Length = Stack->Parameters.Write.Length;
|
|
Buffer = MmGetSystemAddressForMdl(Irp->MdlAddress);
|
|
Offset = Stack->Parameters.Write.ByteOffset.LowPart;
|
|
|
|
Status = FsdWriteFile(DeviceExt,FileObject,Buffer,Length,Offset);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = Length;
|
|
IoCompleteRequest(Irp,IO_NO_INCREMENT);
|
|
|
|
return(Status);
|
|
}
|
|
|
|
NTSTATUS FsdRead(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
ULONG Length;
|
|
PVOID Buffer;
|
|
ULONG Offset;
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = Stack->FileObject;
|
|
PDEVICE_EXTENSION DeviceExt = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
ULONG LengthRead;
|
|
|
|
DPRINT("FsdRead(DeviceObject %x, Irp %x)\n",DeviceObject,Irp);
|
|
|
|
Length = Stack->Parameters.Read.Length;
|
|
Buffer = MmGetSystemAddressForMdl(Irp->MdlAddress);
|
|
Offset = Stack->Parameters.Read.ByteOffset.LowPart;
|
|
|
|
Status = FsdReadFile(DeviceExt,FileObject,Buffer,Length,Offset,
|
|
&LengthRead);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = LengthRead;
|
|
|
|
IoCompleteRequest(Irp,IO_NO_INCREMENT);
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS FsdMount(PDEVICE_OBJECT DeviceToMount)
|
|
{
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PDEVICE_EXTENSION DeviceExt;
|
|
|
|
IoCreateDevice(DriverObject,
|
|
sizeof(DEVICE_EXTENSION),
|
|
NULL,
|
|
FILE_DEVICE_FILE_SYSTEM,
|
|
0,
|
|
FALSE,
|
|
&DeviceObject);
|
|
DeviceObject->Flags = DeviceObject->Flags | DO_DIRECT_IO;
|
|
DeviceExt = (PVOID)DeviceObject->DeviceExtension;
|
|
|
|
FsdMountDevice(DeviceExt,DeviceToMount);
|
|
|
|
DeviceExt->StorageDevice = IoAttachDeviceToDeviceStack(DeviceObject,
|
|
DeviceToMount);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
NTSTATUS FsdFileSystemControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
// PVPB vpb = Stack->Parameters.Mount.Vpb;
|
|
PDEVICE_OBJECT DeviceToMount = Stack->Parameters.Mount.DeviceObject;
|
|
NTSTATUS Status;
|
|
|
|
DPRINT("VFAT FSC\n");
|
|
|
|
if (FsdHasFileSystem(DeviceToMount))
|
|
{
|
|
Status = FsdMount(DeviceToMount);
|
|
}
|
|
else
|
|
{
|
|
DPRINT("VFAT: Unrecognized Volume\n");
|
|
Status = STATUS_UNRECOGNIZED_VOLUME;
|
|
}
|
|
DPRINT("VFAT File system successfully mounted\n");
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
return(Status);
|
|
}
|
|
|
|
NTSTATUS FsdGetStandardInformation(PFCB FCB, PDEVICE_OBJECT DeviceObject,
|
|
PFILE_STANDARD_INFORMATION StandardInfo)
|
|
{
|
|
PDEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
|
|
unsigned long AllocSize;
|
|
|
|
RtlZeroMemory(StandardInfo, sizeof(FILE_STANDARD_INFORMATION));
|
|
|
|
/* Make allocsize a rounded up multiple of BytesPerCluster */
|
|
AllocSize = 0;
|
|
while(AllocSize<FCB->entry.FileSize) {
|
|
AllocSize+=DeviceExtension->BytesPerCluster;
|
|
}
|
|
|
|
StandardInfo->AllocationSize = RtlConvertUlongToLargeInteger(AllocSize);
|
|
StandardInfo->EndOfFile = RtlConvertUlongToLargeInteger(FCB->entry.FileSize);
|
|
StandardInfo->NumberOfLinks = 0;
|
|
StandardInfo->DeletePending = FALSE;
|
|
if((FCB->entry.Attrib & 0x10)>0) {
|
|
StandardInfo->Directory = TRUE;
|
|
} else {
|
|
StandardInfo->Directory = FALSE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS FsdQueryInformation(PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
|
|
FILE_INFORMATION_CLASS FileInformationClass =
|
|
Stack->Parameters.QueryFile.FileInformationClass;
|
|
PFILE_OBJECT FileObject = NULL;
|
|
PFCB FCB = NULL;
|
|
PCCB CCB = NULL;
|
|
|
|
NTSTATUS RC = STATUS_SUCCESS;
|
|
void *SystemBuffer;
|
|
|
|
FileObject = Stack->FileObject;
|
|
CCB = (PCCB)(FileObject->FsContext2);
|
|
FCB = CCB->Buffer; // Should be CCB->FCB???
|
|
|
|
SystemBuffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
switch(FileInformationClass) {
|
|
case FileStandardInformation:
|
|
RC = FsdGetStandardInformation(FCB, DeviceObject, SystemBuffer);
|
|
break;
|
|
}
|
|
|
|
return RC;
|
|
}
|
|
|
|
NTSTATUS DriverEntry(PDRIVER_OBJECT _DriverObject,
|
|
PUNICODE_STRING RegistryPath)
|
|
/*
|
|
* FUNCTION: Called by the system to initalize the driver
|
|
* ARGUMENTS:
|
|
* DriverObject = object describing this driver
|
|
* RegistryPath = path to our configuration entries
|
|
* RETURNS: Success or failure
|
|
*/
|
|
{
|
|
PDEVICE_OBJECT DeviceObject;
|
|
NTSTATUS ret;
|
|
UNICODE_STRING ustr;
|
|
ANSI_STRING astr;
|
|
|
|
DbgPrint("VFAT 0.0.4\n");
|
|
|
|
DriverObject = _DriverObject;
|
|
|
|
RtlInitAnsiString(&astr,"\\Device\\VFAT");
|
|
RtlAnsiStringToUnicodeString(&ustr,&astr,TRUE);
|
|
ret = IoCreateDevice(DriverObject,0,&ustr,
|
|
FILE_DEVICE_FILE_SYSTEM,0,FALSE,&DeviceObject);
|
|
if (ret!=STATUS_SUCCESS)
|
|
{
|
|
return(ret);
|
|
}
|
|
|
|
DeviceObject->Flags=0;
|
|
DriverObject->MajorFunction[IRP_MJ_CLOSE] = FsdClose;
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = FsdCreate;
|
|
DriverObject->MajorFunction[IRP_MJ_READ] = FsdRead;
|
|
DriverObject->MajorFunction[IRP_MJ_WRITE] = FsdWrite;
|
|
DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] =
|
|
FsdFileSystemControl;
|
|
DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] =
|
|
FsdQueryInformation;
|
|
DriverObject->DriverUnload = NULL;
|
|
|
|
IoRegisterFileSystem(DeviceObject);
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|