mirror of
https://github.com/reactos/reactos.git
synced 2024-10-30 11:35:58 +00:00
fc788cf2fd
Likely not optimal, but fixes some races conditions where the directory is uninit in the middle of the write.
1683 lines
57 KiB
C
1683 lines
57 KiB
C
/*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS kernel
|
|
* FILE: drivers/filesystems/fastfat/finfo.c
|
|
* PURPOSE: VFAT Filesystem
|
|
* PROGRAMMER: Jason Filby (jasonfilby@yahoo.com)
|
|
* Herve Poussineau (reactos@poussine.freesurf.fr)
|
|
* Pierre Schweitzer (pierre@reactos.org)
|
|
*
|
|
*/
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
#include "vfat.h"
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
#define NASSERTS_RENAME
|
|
|
|
/* GLOBALS ******************************************************************/
|
|
|
|
const char* FileInformationClassNames[] =
|
|
{
|
|
"??????",
|
|
"FileDirectoryInformation",
|
|
"FileFullDirectoryInformation",
|
|
"FileBothDirectoryInformation",
|
|
"FileBasicInformation",
|
|
"FileStandardInformation",
|
|
"FileInternalInformation",
|
|
"FileEaInformation",
|
|
"FileAccessInformation",
|
|
"FileNameInformation",
|
|
"FileRenameInformation",
|
|
"FileLinkInformation",
|
|
"FileNamesInformation",
|
|
"FileDispositionInformation",
|
|
"FilePositionInformation",
|
|
"FileFullEaInformation",
|
|
"FileModeInformation",
|
|
"FileAlignmentInformation",
|
|
"FileAllInformation",
|
|
"FileAllocationInformation",
|
|
"FileEndOfFileInformation",
|
|
"FileAlternateNameInformation",
|
|
"FileStreamInformation",
|
|
"FilePipeInformation",
|
|
"FilePipeLocalInformation",
|
|
"FilePipeRemoteInformation",
|
|
"FileMailslotQueryInformation",
|
|
"FileMailslotSetInformation",
|
|
"FileCompressionInformation",
|
|
"FileObjectIdInformation",
|
|
"FileCompletionInformation",
|
|
"FileMoveClusterInformation",
|
|
"FileQuotaInformation",
|
|
"FileReparsePointInformation",
|
|
"FileNetworkOpenInformation",
|
|
"FileAttributeTagInformation",
|
|
"FileTrackingInformation",
|
|
"FileIdBothDirectoryInformation",
|
|
"FileIdFullDirectoryInformation",
|
|
"FileValidDataLengthInformation",
|
|
"FileShortNameInformation",
|
|
"FileMaximumInformation"
|
|
};
|
|
|
|
/* FUNCTIONS ****************************************************************/
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the standard file information
|
|
*/
|
|
NTSTATUS
|
|
VfatGetStandardInformation(
|
|
PVFATFCB FCB,
|
|
PFILE_STANDARD_INFORMATION StandardInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
if (*BufferLength < sizeof(FILE_STANDARD_INFORMATION))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
/* PRECONDITION */
|
|
ASSERT(StandardInfo != NULL);
|
|
ASSERT(FCB != NULL);
|
|
|
|
if (vfatFCBIsDirectory(FCB))
|
|
{
|
|
StandardInfo->AllocationSize.QuadPart = 0;
|
|
StandardInfo->EndOfFile.QuadPart = 0;
|
|
StandardInfo->Directory = TRUE;
|
|
}
|
|
else
|
|
{
|
|
StandardInfo->AllocationSize = FCB->RFCB.AllocationSize;
|
|
StandardInfo->EndOfFile = FCB->RFCB.FileSize;
|
|
StandardInfo->Directory = FALSE;
|
|
}
|
|
StandardInfo->NumberOfLinks = 1;
|
|
StandardInfo->DeletePending = BooleanFlagOn(FCB->Flags, FCB_DELETE_PENDING);
|
|
|
|
*BufferLength -= sizeof(FILE_STANDARD_INFORMATION);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatSetPositionInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PFILE_POSITION_INFORMATION PositionInfo)
|
|
{
|
|
DPRINT("FsdSetPositionInformation()\n");
|
|
|
|
DPRINT("PositionInfo %p\n", PositionInfo);
|
|
DPRINT("Setting position %u\n", PositionInfo->CurrentByteOffset.u.LowPart);
|
|
|
|
FileObject->CurrentByteOffset.QuadPart =
|
|
PositionInfo->CurrentByteOffset.QuadPart;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatGetPositionInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_POSITION_INFORMATION PositionInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
UNREFERENCED_PARAMETER(FileObject);
|
|
UNREFERENCED_PARAMETER(FCB);
|
|
UNREFERENCED_PARAMETER(DeviceExt);
|
|
|
|
DPRINT("VfatGetPositionInformation()\n");
|
|
|
|
if (*BufferLength < sizeof(FILE_POSITION_INFORMATION))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
PositionInfo->CurrentByteOffset.QuadPart =
|
|
FileObject->CurrentByteOffset.QuadPart;
|
|
|
|
DPRINT("Getting position %I64x\n",
|
|
PositionInfo->CurrentByteOffset.QuadPart);
|
|
|
|
*BufferLength -= sizeof(FILE_POSITION_INFORMATION);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatSetBasicInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_BASIC_INFORMATION BasicInfo)
|
|
{
|
|
ULONG NotifyFilter;
|
|
|
|
DPRINT("VfatSetBasicInformation()\n");
|
|
|
|
ASSERT(NULL != FileObject);
|
|
ASSERT(NULL != FCB);
|
|
ASSERT(NULL != DeviceExt);
|
|
ASSERT(NULL != BasicInfo);
|
|
/* Check volume label bit */
|
|
ASSERT(0 == (*FCB->Attributes & _A_VOLID));
|
|
|
|
NotifyFilter = 0;
|
|
|
|
if (BasicInfo->FileAttributes != 0)
|
|
{
|
|
UCHAR Attributes;
|
|
|
|
Attributes = (BasicInfo->FileAttributes & (FILE_ATTRIBUTE_ARCHIVE |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_ATTRIBUTE_READONLY));
|
|
|
|
if (vfatFCBIsDirectory(FCB))
|
|
{
|
|
if (BooleanFlagOn(BasicInfo->FileAttributes, FILE_ATTRIBUTE_TEMPORARY))
|
|
{
|
|
DPRINT("Setting temporary attribute on a directory!\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
Attributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
if (BooleanFlagOn(BasicInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
DPRINT("Setting directory attribute on a file!\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (Attributes != *FCB->Attributes)
|
|
{
|
|
*FCB->Attributes = Attributes;
|
|
DPRINT("Setting attributes 0x%02x\n", *FCB->Attributes);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
|
}
|
|
}
|
|
|
|
if (vfatVolumeIsFatX(DeviceExt))
|
|
{
|
|
if (BasicInfo->CreationTime.QuadPart != 0 && BasicInfo->CreationTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->CreationTime,
|
|
&FCB->entry.FatX.CreationDate,
|
|
&FCB->entry.FatX.CreationTime);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_CREATION;
|
|
}
|
|
|
|
if (BasicInfo->LastAccessTime.QuadPart != 0 && BasicInfo->LastAccessTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->LastAccessTime,
|
|
&FCB->entry.FatX.AccessDate,
|
|
&FCB->entry.FatX.AccessTime);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
|
}
|
|
|
|
if (BasicInfo->LastWriteTime.QuadPart != 0 && BasicInfo->LastWriteTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->LastWriteTime,
|
|
&FCB->entry.FatX.UpdateDate,
|
|
&FCB->entry.FatX.UpdateTime);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (BasicInfo->CreationTime.QuadPart != 0 && BasicInfo->CreationTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->CreationTime,
|
|
&FCB->entry.Fat.CreationDate,
|
|
&FCB->entry.Fat.CreationTime);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_CREATION;
|
|
}
|
|
|
|
if (BasicInfo->LastAccessTime.QuadPart != 0 && BasicInfo->LastAccessTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->LastAccessTime,
|
|
&FCB->entry.Fat.AccessDate,
|
|
NULL);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
|
}
|
|
|
|
if (BasicInfo->LastWriteTime.QuadPart != 0 && BasicInfo->LastWriteTime.QuadPart != -1)
|
|
{
|
|
FsdSystemTimeToDosDateTime(DeviceExt,
|
|
&BasicInfo->LastWriteTime,
|
|
&FCB->entry.Fat.UpdateDate,
|
|
&FCB->entry.Fat.UpdateTime);
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
}
|
|
}
|
|
|
|
VfatUpdateEntry(DeviceExt, FCB);
|
|
|
|
if (NotifyFilter != 0)
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
NotifyFilter,
|
|
FILE_ACTION_MODIFIED);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
VfatGetBasicInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_BASIC_INFORMATION BasicInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
UNREFERENCED_PARAMETER(FileObject);
|
|
|
|
DPRINT("VfatGetBasicInformation()\n");
|
|
|
|
if (*BufferLength < sizeof(FILE_BASIC_INFORMATION))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
if (vfatVolumeIsFatX(DeviceExt))
|
|
{
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.FatX.CreationDate,
|
|
FCB->entry.FatX.CreationTime,
|
|
&BasicInfo->CreationTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.FatX.AccessDate,
|
|
FCB->entry.FatX.AccessTime,
|
|
&BasicInfo->LastAccessTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.FatX.UpdateDate,
|
|
FCB->entry.FatX.UpdateTime,
|
|
&BasicInfo->LastWriteTime);
|
|
BasicInfo->ChangeTime = BasicInfo->LastWriteTime;
|
|
}
|
|
else
|
|
{
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.Fat.CreationDate,
|
|
FCB->entry.Fat.CreationTime,
|
|
&BasicInfo->CreationTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.Fat.AccessDate,
|
|
0,
|
|
&BasicInfo->LastAccessTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
FCB->entry.Fat.UpdateDate,
|
|
FCB->entry.Fat.UpdateTime,
|
|
&BasicInfo->LastWriteTime);
|
|
BasicInfo->ChangeTime = BasicInfo->LastWriteTime;
|
|
}
|
|
|
|
BasicInfo->FileAttributes = *FCB->Attributes & 0x3f;
|
|
/* Synthesize FILE_ATTRIBUTE_NORMAL */
|
|
if (0 == (BasicInfo->FileAttributes & (FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_ATTRIBUTE_ARCHIVE |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_READONLY)))
|
|
{
|
|
DPRINT("Synthesizing FILE_ATTRIBUTE_NORMAL\n");
|
|
BasicInfo->FileAttributes |= FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
DPRINT("Getting attributes 0x%02x\n", BasicInfo->FileAttributes);
|
|
|
|
*BufferLength -= sizeof(FILE_BASIC_INFORMATION);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatSetDispositionInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_DISPOSITION_INFORMATION DispositionInfo)
|
|
{
|
|
DPRINT("FsdSetDispositionInformation(<%wZ>, Delete %u)\n", &FCB->PathNameU, DispositionInfo->DeleteFile);
|
|
|
|
ASSERT(DeviceExt != NULL);
|
|
ASSERT(DeviceExt->FatInfo.BytesPerCluster != 0);
|
|
ASSERT(FCB != NULL);
|
|
|
|
if (!DispositionInfo->DeleteFile)
|
|
{
|
|
/* undelete the file */
|
|
FCB->Flags &= ~FCB_DELETE_PENDING;
|
|
FileObject->DeletePending = FALSE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (BooleanFlagOn(FCB->Flags, FCB_DELETE_PENDING))
|
|
{
|
|
/* stream already marked for deletion. just update the file object */
|
|
FileObject->DeletePending = TRUE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (vfatFCBIsReadOnly(FCB))
|
|
{
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
if (vfatFCBIsRoot(FCB) ||
|
|
(FCB->LongNameU.Length == sizeof(WCHAR) && FCB->LongNameU.Buffer[0] == L'.') ||
|
|
(FCB->LongNameU.Length == 2 * sizeof(WCHAR) && FCB->LongNameU.Buffer[0] == L'.' && FCB->LongNameU.Buffer[1] == L'.'))
|
|
{
|
|
/* we cannot delete a '.', '..' or the root directory */
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (!MmFlushImageSection (FileObject->SectionObjectPointer, MmFlushForDelete))
|
|
{
|
|
/* can't delete a file if its mapped into a process */
|
|
|
|
DPRINT("MmFlushImageSection returned FALSE\n");
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
if (vfatFCBIsDirectory(FCB) && !VfatIsDirectoryEmpty(DeviceExt, FCB))
|
|
{
|
|
/* can't delete a non-empty directory */
|
|
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
}
|
|
|
|
/* all good */
|
|
FCB->Flags |= FCB_DELETE_PENDING;
|
|
FileObject->DeletePending = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS
|
|
vfatPrepareTargetForRename(
|
|
IN PDEVICE_EXTENSION DeviceExt,
|
|
IN PVFATFCB * ParentFCB,
|
|
IN PUNICODE_STRING NewName,
|
|
IN BOOLEAN ReplaceIfExists,
|
|
IN PUNICODE_STRING ParentName,
|
|
OUT PBOOLEAN Deleted)
|
|
{
|
|
NTSTATUS Status;
|
|
PVFATFCB TargetFcb;
|
|
|
|
DPRINT("vfatPrepareTargetForRename(%p, %p, %wZ, %d, %wZ, %p)\n", DeviceExt, ParentFCB, NewName, ReplaceIfExists, ParentName);
|
|
|
|
*Deleted = FALSE;
|
|
/* Try to open target */
|
|
Status = vfatGetFCBForFile(DeviceExt, ParentFCB, &TargetFcb, NewName);
|
|
/* If it exists */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
DPRINT("Target file %wZ exists. FCB Flags %08x\n", NewName, TargetFcb->Flags);
|
|
/* Check whether we are allowed to replace */
|
|
if (ReplaceIfExists)
|
|
{
|
|
/* If that's a directory or a read-only file, we're not allowed */
|
|
if (vfatFCBIsDirectory(TargetFcb) || vfatFCBIsReadOnly(TargetFcb))
|
|
{
|
|
DPRINT("And this is a readonly file!\n");
|
|
vfatReleaseFCB(DeviceExt, *ParentFCB);
|
|
*ParentFCB = NULL;
|
|
vfatReleaseFCB(DeviceExt, TargetFcb);
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
|
|
|
|
/* If we still have a file object, close it. */
|
|
if (TargetFcb->FileObject)
|
|
{
|
|
if (!MmFlushImageSection(TargetFcb->FileObject->SectionObjectPointer, MmFlushForDelete))
|
|
{
|
|
DPRINT("MmFlushImageSection failed.\n");
|
|
vfatReleaseFCB(DeviceExt, *ParentFCB);
|
|
*ParentFCB = NULL;
|
|
vfatReleaseFCB(DeviceExt, TargetFcb);
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
TargetFcb->FileObject->DeletePending = TRUE;
|
|
VfatCloseFile(DeviceExt, TargetFcb->FileObject);
|
|
}
|
|
|
|
/* If we are here, ensure the file isn't open by anyone! */
|
|
if (TargetFcb->OpenHandleCount != 0)
|
|
{
|
|
DPRINT("There are still open handles for this file.\n");
|
|
vfatReleaseFCB(DeviceExt, *ParentFCB);
|
|
*ParentFCB = NULL;
|
|
vfatReleaseFCB(DeviceExt, TargetFcb);
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
/* Effectively delete old file to allow renaming */
|
|
DPRINT("Effectively deleting the file.\n");
|
|
VfatDelEntry(DeviceExt, TargetFcb, NULL);
|
|
vfatReleaseFCB(DeviceExt, TargetFcb);
|
|
*Deleted = TRUE;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
vfatReleaseFCB(DeviceExt, *ParentFCB);
|
|
*ParentFCB = NULL;
|
|
vfatReleaseFCB(DeviceExt, TargetFcb);
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
}
|
|
else if (*ParentFCB != NULL)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Failure */
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
BOOLEAN
|
|
IsThereAChildOpened(PVFATFCB FCB)
|
|
{
|
|
PLIST_ENTRY Entry;
|
|
PVFATFCB VolFCB;
|
|
|
|
for (Entry = FCB->ParentListHead.Flink; Entry != &FCB->ParentListHead; Entry = Entry->Flink)
|
|
{
|
|
VolFCB = CONTAINING_RECORD(Entry, VFATFCB, ParentListEntry);
|
|
if (VolFCB->OpenHandleCount != 0)
|
|
{
|
|
ASSERT(VolFCB->parentFcb == FCB);
|
|
DPRINT1("At least one children file opened! %wZ (%u, %u)\n", &VolFCB->PathNameU, VolFCB->RefCount, VolFCB->OpenHandleCount);
|
|
return TRUE;
|
|
}
|
|
|
|
if (vfatFCBIsDirectory(VolFCB) && !IsListEmpty(&VolFCB->ParentListHead))
|
|
{
|
|
if (IsThereAChildOpened(VolFCB))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static
|
|
VOID
|
|
VfatRenameChildFCB(
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PVFATFCB FCB)
|
|
{
|
|
PLIST_ENTRY Entry;
|
|
PVFATFCB Child;
|
|
|
|
if (IsListEmpty(&FCB->ParentListHead))
|
|
return;
|
|
|
|
for (Entry = FCB->ParentListHead.Flink; Entry != &FCB->ParentListHead; Entry = Entry->Flink)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
Child = CONTAINING_RECORD(Entry, VFATFCB, ParentListEntry);
|
|
DPRINT("Found %wZ with still %lu references (parent: %lu)!\n", &Child->PathNameU, Child->RefCount, FCB->RefCount);
|
|
|
|
Status = vfatSetFCBNewDirName(DeviceExt, Child, FCB);
|
|
if (!NT_SUCCESS(Status))
|
|
continue;
|
|
|
|
if (vfatFCBIsDirectory(Child))
|
|
{
|
|
VfatRenameChildFCB(DeviceExt, Child);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: Set the file name information
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
VfatSetRenameInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_RENAME_INFORMATION RenameInfo,
|
|
PFILE_OBJECT TargetFileObject)
|
|
{
|
|
#ifdef NASSERTS_RENAME
|
|
#pragma push_macro("ASSERT")
|
|
#undef ASSERT
|
|
#define ASSERT(x) ((VOID) 0)
|
|
#endif
|
|
NTSTATUS Status;
|
|
UNICODE_STRING NewName;
|
|
UNICODE_STRING SourcePath;
|
|
UNICODE_STRING SourceFile;
|
|
UNICODE_STRING NewPath;
|
|
UNICODE_STRING NewFile;
|
|
PFILE_OBJECT RootFileObject;
|
|
PVFATFCB RootFCB;
|
|
UNICODE_STRING RenameInfoString;
|
|
PVFATFCB ParentFCB;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE TargetHandle;
|
|
BOOLEAN DeletedTarget;
|
|
ULONG OldReferences, NewReferences;
|
|
PVFATFCB OldParent;
|
|
|
|
DPRINT("VfatSetRenameInfo(%p, %p, %p, %p, %p)\n", FileObject, FCB, DeviceExt, RenameInfo, TargetFileObject);
|
|
|
|
/* Disallow renaming root */
|
|
if (vfatFCBIsRoot(FCB))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
OldReferences = FCB->parentFcb->RefCount;
|
|
#ifdef NASSERTS_RENAME
|
|
UNREFERENCED_PARAMETER(OldReferences);
|
|
#endif
|
|
|
|
/* If we are performing relative opening for rename, get FO for getting FCB and path name */
|
|
if (RenameInfo->RootDirectory != NULL)
|
|
{
|
|
/* We cannot tolerate relative opening with a full path */
|
|
if (RenameInfo->FileName[0] == L'\\')
|
|
{
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
Status = ObReferenceObjectByHandle(RenameInfo->RootDirectory,
|
|
FILE_READ_DATA,
|
|
*IoFileObjectType,
|
|
ExGetPreviousMode(),
|
|
(PVOID *)&RootFileObject,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
RootFCB = RootFileObject->FsContext;
|
|
}
|
|
|
|
RtlInitEmptyUnicodeString(&NewName, NULL, 0);
|
|
ParentFCB = NULL;
|
|
|
|
if (TargetFileObject == NULL)
|
|
{
|
|
/* If we don't have target file object, construct paths thanks to relative FCB, if any, and with
|
|
* information supplied by the user
|
|
*/
|
|
|
|
/* First, setup a string we'll work on */
|
|
RenameInfoString.Length = RenameInfo->FileNameLength;
|
|
RenameInfoString.MaximumLength = RenameInfo->FileNameLength;
|
|
RenameInfoString.Buffer = RenameInfo->FileName;
|
|
|
|
/* Check whether we have FQN */
|
|
if (RenameInfoString.Length > 6 * sizeof(WCHAR))
|
|
{
|
|
if (RenameInfoString.Buffer[0] == L'\\' && RenameInfoString.Buffer[1] == L'?' &&
|
|
RenameInfoString.Buffer[2] == L'?' && RenameInfoString.Buffer[3] == L'\\' &&
|
|
RenameInfoString.Buffer[5] == L':' && (RenameInfoString.Buffer[4] >= L'A' &&
|
|
RenameInfoString.Buffer[4] <= L'Z'))
|
|
{
|
|
/* If so, open its target directory */
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&RenameInfoString,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
NULL, NULL);
|
|
|
|
Status = IoCreateFile(&TargetHandle,
|
|
FILE_WRITE_DATA | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL, 0,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_OPEN,
|
|
FILE_OPEN_FOR_BACKUP_INTENT,
|
|
NULL, 0,
|
|
CreateFileTypeNone,
|
|
NULL,
|
|
IO_FORCE_ACCESS_CHECK | IO_OPEN_TARGET_DIRECTORY);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Get its FO to get the FCB */
|
|
Status = ObReferenceObjectByHandle(TargetHandle,
|
|
FILE_WRITE_DATA,
|
|
*IoFileObjectType,
|
|
KernelMode,
|
|
(PVOID *)&TargetFileObject,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ZwClose(TargetHandle);
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Are we working on the same volume? */
|
|
if (IoGetRelatedDeviceObject(TargetFileObject) != IoGetRelatedDeviceObject(FileObject))
|
|
{
|
|
ObDereferenceObject(TargetFileObject);
|
|
ZwClose(TargetHandle);
|
|
TargetFileObject = NULL;
|
|
Status = STATUS_NOT_SAME_DEVICE;
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
NewName.Length = 0;
|
|
NewName.MaximumLength = RenameInfo->FileNameLength;
|
|
if (RenameInfo->RootDirectory != NULL)
|
|
{
|
|
NewName.MaximumLength += sizeof(WCHAR) + RootFCB->PathNameU.Length;
|
|
}
|
|
else if (RenameInfo->FileName[0] != L'\\')
|
|
{
|
|
/* We don't have full path, and we don't have root directory:
|
|
* => we move inside the same directory
|
|
*/
|
|
NewName.MaximumLength += sizeof(WCHAR) + FCB->DirNameU.Length;
|
|
}
|
|
else if (TargetFileObject != NULL)
|
|
{
|
|
/* We had a FQN:
|
|
* => we need to use its correct path
|
|
*/
|
|
NewName.MaximumLength += sizeof(WCHAR) + ((PVFATFCB)TargetFileObject->FsContext)->PathNameU.Length;
|
|
}
|
|
|
|
NewName.Buffer = ExAllocatePoolWithTag(NonPagedPool, NewName.MaximumLength, TAG_VFAT);
|
|
if (NewName.Buffer == NULL)
|
|
{
|
|
if (TargetFileObject != NULL)
|
|
{
|
|
ObDereferenceObject(TargetFileObject);
|
|
ZwClose(TargetHandle);
|
|
TargetFileObject = NULL;
|
|
}
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (RenameInfo->RootDirectory != NULL)
|
|
{
|
|
/* Here, copy first absolute and then append relative */
|
|
RtlCopyUnicodeString(&NewName, &RootFCB->PathNameU);
|
|
NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
|
|
NewName.Length += sizeof(WCHAR);
|
|
RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
|
|
}
|
|
else if (RenameInfo->FileName[0] != L'\\')
|
|
{
|
|
/* Here, copy first work directory and then append filename */
|
|
RtlCopyUnicodeString(&NewName, &FCB->DirNameU);
|
|
NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
|
|
NewName.Length += sizeof(WCHAR);
|
|
RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
|
|
}
|
|
else if (TargetFileObject != NULL)
|
|
{
|
|
/* Here, copy first path name and then append filename */
|
|
RtlCopyUnicodeString(&NewName, &((PVFATFCB)TargetFileObject->FsContext)->PathNameU);
|
|
NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
|
|
NewName.Length += sizeof(WCHAR);
|
|
RtlAppendUnicodeStringToString(&NewName, &RenameInfoString);
|
|
}
|
|
else
|
|
{
|
|
/* Here we should have full path, so simply copy it */
|
|
RtlCopyUnicodeString(&NewName, &RenameInfoString);
|
|
}
|
|
|
|
/* Do we have to cleanup some stuff? */
|
|
if (TargetFileObject != NULL)
|
|
{
|
|
ObDereferenceObject(TargetFileObject);
|
|
ZwClose(TargetHandle);
|
|
TargetFileObject = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* At that point, we shouldn't care about whether we are relative opening
|
|
* Target FO FCB should already have full path
|
|
*/
|
|
|
|
/* Before constructing string, just make a sanity check (just to be sure!) */
|
|
if (IoGetRelatedDeviceObject(TargetFileObject) != IoGetRelatedDeviceObject(FileObject))
|
|
{
|
|
Status = STATUS_NOT_SAME_DEVICE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
NewName.Length = 0;
|
|
NewName.MaximumLength = TargetFileObject->FileName.Length + ((PVFATFCB)TargetFileObject->FsContext)->PathNameU.Length + sizeof(WCHAR);
|
|
NewName.Buffer = ExAllocatePoolWithTag(NonPagedPool, NewName.MaximumLength, TAG_VFAT);
|
|
if (NewName.Buffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyUnicodeString(&NewName, &((PVFATFCB)TargetFileObject->FsContext)->PathNameU);
|
|
NewName.Buffer[NewName.Length / sizeof(WCHAR)] = L'\\';
|
|
NewName.Length += sizeof(WCHAR);
|
|
RtlAppendUnicodeStringToString(&NewName, &TargetFileObject->FileName);
|
|
}
|
|
|
|
/* Explode our paths to get path & filename */
|
|
vfatSplitPathName(&FCB->PathNameU, &SourcePath, &SourceFile);
|
|
DPRINT("Old dir: %wZ, Old file: %wZ\n", &SourcePath, &SourceFile);
|
|
vfatSplitPathName(&NewName, &NewPath, &NewFile);
|
|
DPRINT("New dir: %wZ, New file: %wZ\n", &NewPath, &NewFile);
|
|
|
|
if (vfatFCBIsDirectory(FCB) && !IsListEmpty(&FCB->ParentListHead))
|
|
{
|
|
if (IsThereAChildOpened(FCB))
|
|
{
|
|
Status = STATUS_ACCESS_DENIED;
|
|
ASSERT(OldReferences == FCB->parentFcb->RefCount);
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
/* Are we working in place? */
|
|
if (FsRtlAreNamesEqual(&SourcePath, &NewPath, TRUE, NULL))
|
|
{
|
|
if (FsRtlAreNamesEqual(&SourceFile, &NewFile, FALSE, NULL))
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
ASSERT(OldReferences == FCB->parentFcb->RefCount);
|
|
goto Cleanup;
|
|
}
|
|
|
|
if (FsRtlAreNamesEqual(&SourceFile, &NewFile, TRUE, NULL))
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_RENAMED_OLD_NAME);
|
|
Status = vfatRenameEntry(DeviceExt, FCB, &NewFile, TRUE);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_RENAMED_NEW_NAME);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Try to find target */
|
|
ParentFCB = FCB->parentFcb;
|
|
vfatGrabFCB(DeviceExt, ParentFCB);
|
|
Status = vfatPrepareTargetForRename(DeviceExt,
|
|
&ParentFCB,
|
|
&NewFile,
|
|
RenameInfo->ReplaceIfExists,
|
|
&NewPath,
|
|
&DeletedTarget);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(OldReferences == FCB->parentFcb->RefCount - 1);
|
|
ASSERT(OldReferences == ParentFCB->RefCount - 1);
|
|
goto Cleanup;
|
|
}
|
|
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
(DeletedTarget ? FILE_ACTION_REMOVED : FILE_ACTION_RENAMED_OLD_NAME));
|
|
Status = vfatRenameEntry(DeviceExt, FCB, &NewFile, FALSE);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (DeletedTarget)
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
| FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA,
|
|
FILE_ACTION_MODIFIED);
|
|
}
|
|
else
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_RENAMED_NEW_NAME);
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(OldReferences == FCB->parentFcb->RefCount - 1); // extra grab
|
|
ASSERT(OldReferences == ParentFCB->RefCount - 1); // extra grab
|
|
}
|
|
else
|
|
{
|
|
|
|
/* Try to find target */
|
|
ParentFCB = NULL;
|
|
OldParent = FCB->parentFcb;
|
|
#ifdef NASSERTS_RENAME
|
|
UNREFERENCED_PARAMETER(OldParent);
|
|
#endif
|
|
Status = vfatPrepareTargetForRename(DeviceExt,
|
|
&ParentFCB,
|
|
&NewName,
|
|
RenameInfo->ReplaceIfExists,
|
|
&NewPath,
|
|
&DeletedTarget);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(OldReferences == FCB->parentFcb->RefCount);
|
|
goto Cleanup;
|
|
}
|
|
|
|
NewReferences = ParentFCB->RefCount;
|
|
#ifdef NASSERTS_RENAME
|
|
UNREFERENCED_PARAMETER(NewReferences);
|
|
#endif
|
|
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_REMOVED);
|
|
Status = VfatMoveEntry(DeviceExt, FCB, &NewFile, ParentFCB);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (DeletedTarget)
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
| FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA,
|
|
FILE_ACTION_MODIFIED);
|
|
}
|
|
else
|
|
{
|
|
vfatReportChange(DeviceExt,
|
|
FCB,
|
|
(vfatFCBIsDirectory(FCB) ?
|
|
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME),
|
|
FILE_ACTION_ADDED);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) && vfatFCBIsDirectory(FCB))
|
|
{
|
|
VfatRenameChildFCB(DeviceExt, FCB);
|
|
}
|
|
|
|
ASSERT(OldReferences == OldParent->RefCount + 1); // removed file
|
|
ASSERT(NewReferences == ParentFCB->RefCount - 1); // new file
|
|
Cleanup:
|
|
if (ParentFCB != NULL) vfatReleaseFCB(DeviceExt, ParentFCB);
|
|
if (NewName.Buffer != NULL) ExFreePoolWithTag(NewName.Buffer, TAG_VFAT);
|
|
if (RenameInfo->RootDirectory != NULL) ObDereferenceObject(RootFileObject);
|
|
|
|
return Status;
|
|
#ifdef NASSERTS_RENAME
|
|
#pragma pop_macro("ASSERT")
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the file name information
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
VfatGetNameInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB FCB,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_NAME_INFORMATION NameInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
ULONG BytesToCopy;
|
|
|
|
UNREFERENCED_PARAMETER(FileObject);
|
|
UNREFERENCED_PARAMETER(DeviceExt);
|
|
|
|
ASSERT(NameInfo != NULL);
|
|
ASSERT(FCB != NULL);
|
|
|
|
/* If buffer can't hold at least the file name length, bail out */
|
|
if (*BufferLength < (ULONG)FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
/* Save file name length, and as much file len, as buffer length allows */
|
|
NameInfo->FileNameLength = FCB->PathNameU.Length;
|
|
|
|
/* Calculate amount of bytes to copy not to overflow the buffer */
|
|
BytesToCopy = min(FCB->PathNameU.Length,
|
|
*BufferLength - FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]));
|
|
|
|
/* Fill in the bytes */
|
|
RtlCopyMemory(NameInfo->FileName, FCB->PathNameU.Buffer, BytesToCopy);
|
|
|
|
/* Check if we could write more but are not able to */
|
|
if (*BufferLength < FCB->PathNameU.Length + (ULONG)FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]))
|
|
{
|
|
/* Return number of bytes written */
|
|
*BufferLength -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]) + BytesToCopy;
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
/* We filled up as many bytes, as needed */
|
|
*BufferLength -= (FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]) + FCB->PathNameU.Length);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatGetInternalInformation(
|
|
PVFATFCB Fcb,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_INTERNAL_INFORMATION InternalInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
ASSERT(InternalInfo);
|
|
ASSERT(Fcb);
|
|
|
|
if (*BufferLength < sizeof(FILE_INTERNAL_INFORMATION))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
InternalInfo->IndexNumber.QuadPart = (LONGLONG)vfatDirEntryGetFirstCluster(DeviceExt, &Fcb->entry) * DeviceExt->FatInfo.BytesPerCluster;
|
|
|
|
*BufferLength -= sizeof(FILE_INTERNAL_INFORMATION);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the file network open information
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
VfatGetNetworkOpenInformation(
|
|
PVFATFCB Fcb,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_NETWORK_OPEN_INFORMATION NetworkInfo,
|
|
PULONG BufferLength)
|
|
{
|
|
ASSERT(NetworkInfo);
|
|
ASSERT(Fcb);
|
|
|
|
if (*BufferLength < sizeof(FILE_NETWORK_OPEN_INFORMATION))
|
|
return(STATUS_BUFFER_OVERFLOW);
|
|
|
|
if (vfatVolumeIsFatX(DeviceExt))
|
|
{
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.FatX.CreationDate,
|
|
Fcb->entry.FatX.CreationTime,
|
|
&NetworkInfo->CreationTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.FatX.AccessDate,
|
|
Fcb->entry.FatX.AccessTime,
|
|
&NetworkInfo->LastAccessTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.FatX.UpdateDate,
|
|
Fcb->entry.FatX.UpdateTime,
|
|
&NetworkInfo->LastWriteTime);
|
|
NetworkInfo->ChangeTime.QuadPart = NetworkInfo->LastWriteTime.QuadPart;
|
|
}
|
|
else
|
|
{
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.Fat.CreationDate,
|
|
Fcb->entry.Fat.CreationTime,
|
|
&NetworkInfo->CreationTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.Fat.AccessDate,
|
|
0,
|
|
&NetworkInfo->LastAccessTime);
|
|
FsdDosDateTimeToSystemTime(DeviceExt,
|
|
Fcb->entry.Fat.UpdateDate,
|
|
Fcb->entry.Fat.UpdateTime,
|
|
&NetworkInfo->LastWriteTime);
|
|
NetworkInfo->ChangeTime.QuadPart = NetworkInfo->LastWriteTime.QuadPart;
|
|
}
|
|
|
|
if (vfatFCBIsDirectory(Fcb))
|
|
{
|
|
NetworkInfo->EndOfFile.QuadPart = 0L;
|
|
NetworkInfo->AllocationSize.QuadPart = 0L;
|
|
}
|
|
else
|
|
{
|
|
NetworkInfo->AllocationSize = Fcb->RFCB.AllocationSize;
|
|
NetworkInfo->EndOfFile = Fcb->RFCB.FileSize;
|
|
}
|
|
|
|
NetworkInfo->FileAttributes = *Fcb->Attributes & 0x3f;
|
|
/* Synthesize FILE_ATTRIBUTE_NORMAL */
|
|
if (0 == (NetworkInfo->FileAttributes & (FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_ATTRIBUTE_ARCHIVE |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_READONLY)))
|
|
{
|
|
DPRINT("Synthesizing FILE_ATTRIBUTE_NORMAL\n");
|
|
NetworkInfo->FileAttributes |= FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
|
|
*BufferLength -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
static
|
|
NTSTATUS
|
|
VfatGetEaInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB Fcb,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_EA_INFORMATION Info,
|
|
PULONG BufferLength)
|
|
{
|
|
UNREFERENCED_PARAMETER(FileObject);
|
|
UNREFERENCED_PARAMETER(Fcb);
|
|
|
|
/* FIXME - use SEH to access the buffer! */
|
|
Info->EaSize = 0;
|
|
*BufferLength -= sizeof(*Info);
|
|
if (DeviceExt->FatInfo.FatType == FAT12 ||
|
|
DeviceExt->FatInfo.FatType == FAT16)
|
|
{
|
|
/* FIXME */
|
|
DPRINT1("VFAT: FileEaInformation not implemented!\n");
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the all file information
|
|
*/
|
|
static
|
|
NTSTATUS
|
|
VfatGetAllInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB Fcb,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_ALL_INFORMATION Info,
|
|
PULONG BufferLength)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
ASSERT(Info);
|
|
ASSERT(Fcb);
|
|
|
|
if (*BufferLength < FIELD_OFFSET(FILE_ALL_INFORMATION, NameInformation.FileName))
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
|
|
*BufferLength -= (sizeof(FILE_ACCESS_INFORMATION) + sizeof(FILE_MODE_INFORMATION) + sizeof(FILE_ALIGNMENT_INFORMATION));
|
|
|
|
/* Basic Information */
|
|
Status = VfatGetBasicInformation(FileObject, Fcb, DeviceExt, &Info->BasicInformation, BufferLength);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
/* Standard Information */
|
|
Status = VfatGetStandardInformation(Fcb, &Info->StandardInformation, BufferLength);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
/* Internal Information */
|
|
Status = VfatGetInternalInformation(Fcb, DeviceExt, &Info->InternalInformation, BufferLength);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
/* EA Information */
|
|
Status = VfatGetEaInformation(FileObject, Fcb, DeviceExt, &Info->EaInformation, BufferLength);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
/* Position Information */
|
|
Status = VfatGetPositionInformation(FileObject, Fcb, DeviceExt, &Info->PositionInformation, BufferLength);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
/* Name Information */
|
|
Status = VfatGetNameInformation(FileObject, Fcb, DeviceExt, &Info->NameInformation, BufferLength);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
VOID
|
|
UpdateFileSize(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB Fcb,
|
|
ULONG Size,
|
|
ULONG ClusterSize,
|
|
BOOLEAN IsFatX)
|
|
{
|
|
if (Size > 0)
|
|
{
|
|
Fcb->RFCB.AllocationSize.QuadPart = ROUND_UP_64(Size, ClusterSize);
|
|
}
|
|
else
|
|
{
|
|
Fcb->RFCB.AllocationSize.QuadPart = (LONGLONG)0;
|
|
}
|
|
if (!vfatFCBIsDirectory(Fcb))
|
|
{
|
|
if (IsFatX)
|
|
Fcb->entry.FatX.FileSize = Size;
|
|
else
|
|
Fcb->entry.Fat.FileSize = Size;
|
|
}
|
|
Fcb->RFCB.FileSize.QuadPart = Size;
|
|
Fcb->RFCB.ValidDataLength.QuadPart = Size;
|
|
|
|
CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
|
|
}
|
|
|
|
NTSTATUS
|
|
VfatSetAllocationSizeInformation(
|
|
PFILE_OBJECT FileObject,
|
|
PVFATFCB Fcb,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PLARGE_INTEGER AllocationSize)
|
|
{
|
|
ULONG OldSize;
|
|
ULONG Cluster, FirstCluster;
|
|
NTSTATUS Status;
|
|
|
|
ULONG ClusterSize = DeviceExt->FatInfo.BytesPerCluster;
|
|
ULONG NewSize = AllocationSize->u.LowPart;
|
|
ULONG NCluster;
|
|
BOOLEAN AllocSizeChanged = FALSE, IsFatX = vfatVolumeIsFatX(DeviceExt);
|
|
|
|
DPRINT("VfatSetAllocationSizeInformation(File <%wZ>, AllocationSize %d %u)\n",
|
|
&Fcb->PathNameU, AllocationSize->HighPart, AllocationSize->LowPart);
|
|
|
|
if (IsFatX)
|
|
OldSize = Fcb->entry.FatX.FileSize;
|
|
else
|
|
OldSize = Fcb->entry.Fat.FileSize;
|
|
|
|
if (AllocationSize->u.HighPart > 0)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (OldSize == NewSize)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
FirstCluster = vfatDirEntryGetFirstCluster(DeviceExt, &Fcb->entry);
|
|
|
|
if (NewSize > Fcb->RFCB.AllocationSize.u.LowPart)
|
|
{
|
|
AllocSizeChanged = TRUE;
|
|
if (FirstCluster == 0)
|
|
{
|
|
Fcb->LastCluster = Fcb->LastOffset = 0;
|
|
Status = NextCluster(DeviceExt, FirstCluster, &FirstCluster, TRUE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("NextCluster failed. Status = %x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (FirstCluster == 0xffffffff)
|
|
{
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
Status = OffsetToCluster(DeviceExt, FirstCluster,
|
|
ROUND_DOWN(NewSize - 1, ClusterSize),
|
|
&NCluster, TRUE);
|
|
if (NCluster == 0xffffffff || !NT_SUCCESS(Status))
|
|
{
|
|
/* disk is full */
|
|
NCluster = Cluster = FirstCluster;
|
|
Status = STATUS_SUCCESS;
|
|
while (NT_SUCCESS(Status) && Cluster != 0xffffffff && Cluster > 1)
|
|
{
|
|
Status = NextCluster(DeviceExt, FirstCluster, &NCluster, FALSE);
|
|
WriteCluster(DeviceExt, Cluster, 0);
|
|
Cluster = NCluster;
|
|
}
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
if (IsFatX)
|
|
{
|
|
Fcb->entry.FatX.FirstCluster = FirstCluster;
|
|
}
|
|
else
|
|
{
|
|
if (DeviceExt->FatInfo.FatType == FAT32)
|
|
{
|
|
Fcb->entry.Fat.FirstCluster = (unsigned short)(FirstCluster & 0x0000FFFF);
|
|
Fcb->entry.Fat.FirstClusterHigh = FirstCluster >> 16;
|
|
}
|
|
else
|
|
{
|
|
ASSERT((FirstCluster >> 16) == 0);
|
|
Fcb->entry.Fat.FirstCluster = (unsigned short)(FirstCluster & 0x0000FFFF);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Fcb->LastCluster > 0)
|
|
{
|
|
if (Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize == Fcb->LastOffset)
|
|
{
|
|
Cluster = Fcb->LastCluster;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
Status = OffsetToCluster(DeviceExt, Fcb->LastCluster,
|
|
Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize - Fcb->LastOffset,
|
|
&Cluster, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = OffsetToCluster(DeviceExt, FirstCluster,
|
|
Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize,
|
|
&Cluster, FALSE);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
Fcb->LastCluster = Cluster;
|
|
Fcb->LastOffset = Fcb->RFCB.AllocationSize.u.LowPart - ClusterSize;
|
|
|
|
/* FIXME: Check status */
|
|
/* Cluster points now to the last cluster within the chain */
|
|
Status = OffsetToCluster(DeviceExt, Cluster,
|
|
ROUND_DOWN(NewSize - 1, ClusterSize) - Fcb->LastOffset,
|
|
&NCluster, TRUE);
|
|
if (NCluster == 0xffffffff || !NT_SUCCESS(Status))
|
|
{
|
|
/* disk is full */
|
|
NCluster = Cluster;
|
|
Status = NextCluster(DeviceExt, FirstCluster, &NCluster, FALSE);
|
|
WriteCluster(DeviceExt, Cluster, 0xffffffff);
|
|
Cluster = NCluster;
|
|
while (NT_SUCCESS(Status) && Cluster != 0xffffffff && Cluster > 1)
|
|
{
|
|
Status = NextCluster(DeviceExt, FirstCluster, &NCluster, FALSE);
|
|
WriteCluster(DeviceExt, Cluster, 0);
|
|
Cluster = NCluster;
|
|
}
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
}
|
|
UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize, vfatVolumeIsFatX(DeviceExt));
|
|
}
|
|
else if (NewSize + ClusterSize <= Fcb->RFCB.AllocationSize.u.LowPart)
|
|
{
|
|
DPRINT("Check for the ability to set file size\n");
|
|
if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)AllocationSize))
|
|
{
|
|
DPRINT("Couldn't set file size!\n");
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
DPRINT("Can set file size\n");
|
|
|
|
AllocSizeChanged = TRUE;
|
|
/* FIXME: Use the cached cluster/offset better way. */
|
|
Fcb->LastCluster = Fcb->LastOffset = 0;
|
|
UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize, vfatVolumeIsFatX(DeviceExt));
|
|
if (NewSize > 0)
|
|
{
|
|
Status = OffsetToCluster(DeviceExt, FirstCluster,
|
|
ROUND_DOWN(NewSize - 1, ClusterSize),
|
|
&Cluster, FALSE);
|
|
|
|
NCluster = Cluster;
|
|
Status = NextCluster(DeviceExt, FirstCluster, &NCluster, FALSE);
|
|
WriteCluster(DeviceExt, Cluster, 0xffffffff);
|
|
Cluster = NCluster;
|
|
}
|
|
else
|
|
{
|
|
if (IsFatX)
|
|
{
|
|
Fcb->entry.FatX.FirstCluster = 0;
|
|
}
|
|
else
|
|
{
|
|
if (DeviceExt->FatInfo.FatType == FAT32)
|
|
{
|
|
Fcb->entry.Fat.FirstCluster = 0;
|
|
Fcb->entry.Fat.FirstClusterHigh = 0;
|
|
}
|
|
else
|
|
{
|
|
Fcb->entry.Fat.FirstCluster = 0;
|
|
}
|
|
}
|
|
|
|
NCluster = Cluster = FirstCluster;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
while (NT_SUCCESS(Status) && 0xffffffff != Cluster && Cluster > 1)
|
|
{
|
|
Status = NextCluster(DeviceExt, FirstCluster, &NCluster, FALSE);
|
|
WriteCluster(DeviceExt, Cluster, 0);
|
|
Cluster = NCluster;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateFileSize(FileObject, Fcb, NewSize, ClusterSize, vfatVolumeIsFatX(DeviceExt));
|
|
}
|
|
|
|
/* Update the on-disk directory entry */
|
|
Fcb->Flags |= FCB_IS_DIRTY;
|
|
if (AllocSizeChanged)
|
|
{
|
|
VfatUpdateEntry(DeviceExt, Fcb);
|
|
|
|
vfatReportChange(DeviceExt, Fcb, FILE_NOTIFY_CHANGE_SIZE, FILE_ACTION_MODIFIED);
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the specified file information
|
|
*/
|
|
NTSTATUS
|
|
VfatQueryInformation(
|
|
PVFAT_IRP_CONTEXT IrpContext)
|
|
{
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
PVFATFCB FCB;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PVOID SystemBuffer;
|
|
ULONG BufferLength;
|
|
|
|
/* PRECONDITION */
|
|
ASSERT(IrpContext);
|
|
|
|
/* INITIALIZATION */
|
|
FileInformationClass = IrpContext->Stack->Parameters.QueryFile.FileInformationClass;
|
|
FCB = (PVFATFCB) IrpContext->FileObject->FsContext;
|
|
|
|
DPRINT("VfatQueryInformation is called for '%s'\n",
|
|
FileInformationClass >= FileMaximumInformation - 1 ? "????" : FileInformationClassNames[FileInformationClass]);
|
|
|
|
if (FCB == NULL)
|
|
{
|
|
DPRINT1("IRP_MJ_QUERY_INFORMATION without FCB!\n");
|
|
IrpContext->Irp->IoStatus.Information = 0;
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;
|
|
BufferLength = IrpContext->Stack->Parameters.QueryFile.Length;
|
|
|
|
if (!BooleanFlagOn(FCB->Flags, FCB_IS_PAGE_FILE))
|
|
{
|
|
if (!ExAcquireResourceSharedLite(&FCB->MainResource,
|
|
BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
|
|
{
|
|
return VfatMarkIrpContextForQueue(IrpContext);
|
|
}
|
|
}
|
|
|
|
switch (FileInformationClass)
|
|
{
|
|
case FileStandardInformation:
|
|
Status = VfatGetStandardInformation(FCB,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
Status = VfatGetPositionInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileBasicInformation:
|
|
Status = VfatGetBasicInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileNameInformation:
|
|
Status = VfatGetNameInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileInternalInformation:
|
|
Status = VfatGetInternalInformation(FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileNetworkOpenInformation:
|
|
Status = VfatGetNetworkOpenInformation(FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileAllInformation:
|
|
Status = VfatGetAllInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileEaInformation:
|
|
Status = VfatGetEaInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
&BufferLength);
|
|
break;
|
|
|
|
case FileAlternateNameInformation:
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
default:
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!BooleanFlagOn(FCB->Flags, FCB_IS_PAGE_FILE))
|
|
{
|
|
ExReleaseResourceLite(&FCB->MainResource);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW)
|
|
IrpContext->Irp->IoStatus.Information =
|
|
IrpContext->Stack->Parameters.QueryFile.Length - BufferLength;
|
|
else
|
|
IrpContext->Irp->IoStatus.Information = 0;
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* FUNCTION: Retrieve the specified file information
|
|
*/
|
|
NTSTATUS
|
|
VfatSetInformation(
|
|
PVFAT_IRP_CONTEXT IrpContext)
|
|
{
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
PVFATFCB FCB;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PVOID SystemBuffer;
|
|
BOOLEAN LockDir;
|
|
|
|
/* PRECONDITION */
|
|
ASSERT(IrpContext);
|
|
|
|
DPRINT("VfatSetInformation(IrpContext %p)\n", IrpContext);
|
|
|
|
/* INITIALIZATION */
|
|
FileInformationClass =
|
|
IrpContext->Stack->Parameters.SetFile.FileInformationClass;
|
|
FCB = (PVFATFCB) IrpContext->FileObject->FsContext;
|
|
SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
DPRINT("VfatSetInformation is called for '%s'\n",
|
|
FileInformationClass >= FileMaximumInformation - 1 ? "????" : FileInformationClassNames[ FileInformationClass]);
|
|
|
|
DPRINT("FileInformationClass %d\n", FileInformationClass);
|
|
DPRINT("SystemBuffer %p\n", SystemBuffer);
|
|
|
|
if (FCB == NULL)
|
|
{
|
|
DPRINT1("IRP_MJ_SET_INFORMATION without FCB!\n");
|
|
IrpContext->Irp->IoStatus.Information = 0;
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Special: We should call MmCanFileBeTruncated here to determine if changing
|
|
the file size would be allowed. If not, we bail with the right error.
|
|
We must do this before acquiring the lock. */
|
|
if (FileInformationClass == FileEndOfFileInformation)
|
|
{
|
|
DPRINT("Check for the ability to set file size\n");
|
|
if (!MmCanFileBeTruncated(IrpContext->FileObject->SectionObjectPointer,
|
|
(PLARGE_INTEGER)SystemBuffer))
|
|
{
|
|
DPRINT("Couldn't set file size!\n");
|
|
IrpContext->Irp->IoStatus.Information = 0;
|
|
return STATUS_USER_MAPPED_FILE;
|
|
}
|
|
DPRINT("Can set file size\n");
|
|
}
|
|
|
|
LockDir = FALSE;
|
|
if (FileInformationClass == FileRenameInformation || FileInformationClass == FileAllocationInformation ||
|
|
FileInformationClass == FileEndOfFileInformation || FileInformationClass == FileBasicInformation)
|
|
{
|
|
LockDir = TRUE;
|
|
}
|
|
|
|
if (LockDir)
|
|
{
|
|
if (!ExAcquireResourceExclusiveLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource,
|
|
BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
|
|
{
|
|
return VfatMarkIrpContextForQueue(IrpContext);
|
|
}
|
|
}
|
|
|
|
if (!BooleanFlagOn(FCB->Flags, FCB_IS_PAGE_FILE))
|
|
{
|
|
if (!ExAcquireResourceExclusiveLite(&FCB->MainResource,
|
|
BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
|
|
{
|
|
if (LockDir)
|
|
{
|
|
ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
|
|
}
|
|
|
|
return VfatMarkIrpContextForQueue(IrpContext);
|
|
}
|
|
}
|
|
|
|
switch (FileInformationClass)
|
|
{
|
|
case FilePositionInformation:
|
|
Status = VfatSetPositionInformation(IrpContext->FileObject,
|
|
SystemBuffer);
|
|
break;
|
|
|
|
case FileDispositionInformation:
|
|
Status = VfatSetDispositionInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer);
|
|
break;
|
|
|
|
case FileAllocationInformation:
|
|
case FileEndOfFileInformation:
|
|
Status = VfatSetAllocationSizeInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
(PLARGE_INTEGER)SystemBuffer);
|
|
break;
|
|
|
|
case FileBasicInformation:
|
|
Status = VfatSetBasicInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer);
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
Status = VfatSetRenameInformation(IrpContext->FileObject,
|
|
FCB,
|
|
IrpContext->DeviceExt,
|
|
SystemBuffer,
|
|
IrpContext->Stack->Parameters.SetFile.FileObject);
|
|
break;
|
|
|
|
default:
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (!BooleanFlagOn(FCB->Flags, FCB_IS_PAGE_FILE))
|
|
{
|
|
ExReleaseResourceLite(&FCB->MainResource);
|
|
}
|
|
|
|
if (LockDir)
|
|
{
|
|
ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
|
|
}
|
|
|
|
IrpContext->Irp->IoStatus.Information = 0;
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|