mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 01:55:19 +00:00
5186 lines
140 KiB
C
5186 lines
140 KiB
C
/*++
|
|
|
|
Copyright (c) 1989-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
FileInfo.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the File Information routines for Fat called by
|
|
the dispatch driver.
|
|
|
|
|
|
--*/
|
|
|
|
#include "fatprocs.h"
|
|
|
|
//
|
|
// The Bug check file id for this module
|
|
//
|
|
|
|
#define BugCheckFileId (FAT_BUG_CHECK_FILEINFO)
|
|
|
|
//
|
|
// The local debug trace level
|
|
//
|
|
|
|
#define Dbg (DEBUG_TRACE_FILEINFO)
|
|
|
|
VOID
|
|
FatQueryBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_BASIC_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryStandardInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_STANDARD_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
VOID
|
|
FatQueryInternalInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_INTERNAL_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
VOID
|
|
FatQueryEaInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_EA_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
VOID
|
|
FatQueryPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_POSITION_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb,
|
|
IN BOOLEAN Normalized,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
VOID
|
|
FatQueryShortNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryNetworkInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetDispositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb
|
|
);
|
|
|
|
NTSTATUS
|
|
FatSetRenameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
NTSTATUS
|
|
FatSetPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetAllocationInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetEndOfFileInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PVCB Vcb,
|
|
IN PFCB Fcb
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetValidDataLengthInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
);
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatRenameEAs (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN USHORT ExtendedAttributes,
|
|
IN POEM_STRING OldOemName
|
|
);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, FatCommonQueryInformation)
|
|
#pragma alloc_text(PAGE, FatCommonSetInformation)
|
|
#pragma alloc_text(PAGE, FatFsdQueryInformation)
|
|
#pragma alloc_text(PAGE, FatFsdSetInformation)
|
|
#pragma alloc_text(PAGE, FatQueryBasicInfo)
|
|
#pragma alloc_text(PAGE, FatQueryEaInfo)
|
|
#pragma alloc_text(PAGE, FatQueryInternalInfo)
|
|
#pragma alloc_text(PAGE, FatQueryNameInfo)
|
|
#pragma alloc_text(PAGE, FatQueryNetworkInfo)
|
|
#pragma alloc_text(PAGE, FatQueryShortNameInfo)
|
|
#pragma alloc_text(PAGE, FatQueryPositionInfo)
|
|
#pragma alloc_text(PAGE, FatQueryStandardInfo)
|
|
#pragma alloc_text(PAGE, FatSetAllocationInfo)
|
|
#pragma alloc_text(PAGE, FatSetBasicInfo)
|
|
#pragma alloc_text(PAGE, FatSetDispositionInfo)
|
|
#pragma alloc_text(PAGE, FatSetEndOfFileInfo)
|
|
#pragma alloc_text(PAGE, FatSetValidDataLengthInfo)
|
|
#pragma alloc_text(PAGE, FatSetPositionInfo)
|
|
#pragma alloc_text(PAGE, FatSetRenameInfo)
|
|
#pragma alloc_text(PAGE, FatDeleteFile)
|
|
#pragma alloc_text(PAGE, FatRenameEAs)
|
|
#endif
|
|
|
|
|
|
_Function_class_(IRP_MJ_QUERY_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS
|
|
NTAPI
|
|
FatFsdQueryInformation (
|
|
_In_ PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
|
|
_Inout_ PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements the Fsd part of the NtQueryInformationFile API
|
|
call.
|
|
|
|
Arguments:
|
|
|
|
VolumeDeviceObject - Supplies the volume device object where the file
|
|
being queried exists.
|
|
|
|
Irp - Supplies the Irp being processed.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The FSD status for the Irp.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PIRP_CONTEXT IrpContext = NULL;
|
|
|
|
BOOLEAN TopLevel;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatFsdQueryInformation\n", 0);
|
|
|
|
//
|
|
// Call the common query routine, with blocking allowed if synchronous
|
|
//
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
TopLevel = FatIsIrpTopLevel( Irp );
|
|
|
|
_SEH2_TRY {
|
|
|
|
IrpContext = FatCreateIrpContext( Irp, CanFsdWait( Irp ) );
|
|
|
|
Status = FatCommonQueryInformation( IrpContext, Irp );
|
|
|
|
} _SEH2_EXCEPT(FatExceptionFilter( IrpContext, _SEH2_GetExceptionInformation() )) {
|
|
|
|
//
|
|
// We had some trouble trying to perform the requested
|
|
// operation, so we'll abort the I/O request with
|
|
// the error status that we get back from the
|
|
// execption code
|
|
//
|
|
|
|
Status = FatProcessException( IrpContext, Irp, _SEH2_GetExceptionCode() );
|
|
} _SEH2_END;
|
|
|
|
if (TopLevel) { IoSetTopLevelIrp( NULL ); }
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace(-1, Dbg, "FatFsdQueryInformation -> %08lx\n", Status);
|
|
|
|
UNREFERENCED_PARAMETER( VolumeDeviceObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
_Function_class_(IRP_MJ_SET_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS
|
|
NTAPI
|
|
FatFsdSetInformation (
|
|
_In_ PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
|
|
_Inout_ PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine implements the FSD part of the NtSetInformationFile API
|
|
call.
|
|
|
|
Arguments:
|
|
|
|
VolumeDeviceObject - Supplies the volume device object where the file
|
|
being set exists.
|
|
|
|
Irp - Supplies the Irp being processed.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The FSD status for the Irp.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PIRP_CONTEXT IrpContext = NULL;
|
|
|
|
BOOLEAN TopLevel;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatFsdSetInformation\n", 0);
|
|
|
|
//
|
|
// Call the common set routine, with blocking allowed if synchronous
|
|
//
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
TopLevel = FatIsIrpTopLevel( Irp );
|
|
|
|
_SEH2_TRY {
|
|
|
|
IrpContext = FatCreateIrpContext( Irp, CanFsdWait( Irp ) );
|
|
|
|
Status = FatCommonSetInformation( IrpContext, Irp );
|
|
|
|
} _SEH2_EXCEPT(FatExceptionFilter( IrpContext, _SEH2_GetExceptionInformation() )) {
|
|
|
|
//
|
|
// We had some trouble trying to perform the requested
|
|
// operation, so we'll abort the I/O request with
|
|
// the error status that we get back from the
|
|
// execption code
|
|
//
|
|
|
|
Status = FatProcessException( IrpContext, Irp, _SEH2_GetExceptionCode() );
|
|
} _SEH2_END;
|
|
|
|
if (TopLevel) { IoSetTopLevelIrp( NULL ); }
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
//
|
|
// And return to our caller
|
|
//
|
|
|
|
DebugTrace(-1, Dbg, "FatFsdSetInformation -> %08lx\n", Status);
|
|
|
|
UNREFERENCED_PARAMETER( VolumeDeviceObject );
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatCommonQueryInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the common routine for querying file information called by both
|
|
the fsd and fsp threads.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PFILE_OBJECT FileObject;
|
|
|
|
LONG Length;
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
PVOID Buffer;
|
|
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PCCB Ccb;
|
|
|
|
BOOLEAN FcbAcquired = FALSE;
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
|
|
PFILE_ALL_INFORMATION AllInfo;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
|
|
DebugTrace(+1, Dbg, "FatCommonQueryInformation...\n", 0);
|
|
DebugTrace( 0, Dbg, "Irp = %p\n", Irp);
|
|
DebugTrace( 0, Dbg, "->Length = %08lx\n", IrpSp->Parameters.QueryFile.Length);
|
|
DebugTrace( 0, Dbg, "->FileInformationClass = %08lx\n", IrpSp->Parameters.QueryFile.FileInformationClass);
|
|
DebugTrace( 0, Dbg, "->Buffer = %p\n", Irp->AssociatedIrp.SystemBuffer);
|
|
|
|
//
|
|
// Reference our input parameters to make things easier
|
|
//
|
|
|
|
Length = (LONG)IrpSp->Parameters.QueryFile.Length;
|
|
FileInformationClass = IrpSp->Parameters.QueryFile.FileInformationClass;
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Decode the file object
|
|
//
|
|
|
|
TypeOfOpen = FatDecodeFileObject( FileObject, &Vcb, &Fcb, &Ccb );
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Case on the type of open we're dealing with
|
|
//
|
|
|
|
switch (TypeOfOpen) {
|
|
|
|
case UserVolumeOpen:
|
|
|
|
//
|
|
// We cannot query the user volume open.
|
|
//
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
|
|
case UserFileOpen:
|
|
case UserDirectoryOpen:
|
|
case DirectoryFile:
|
|
|
|
|
|
//
|
|
// NameInfo requires synchronization with deletion in order to perform
|
|
// the full filename query. A lighter-weight way to do this would be per
|
|
// directory as the full name is built up and since the multiple Fcb
|
|
// lockorder is bottom up, this is conceivable. At this time, though,
|
|
// this change is safer.
|
|
//
|
|
|
|
if (FileInformationClass == FileNameInformation ||
|
|
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
|
FileInformationClass == FileNormalizedNameInformation ||
|
|
#endif
|
|
FileInformationClass == FileAllInformation ) {
|
|
|
|
if (!FatAcquireExclusiveVcb( IrpContext, Vcb )) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot acquire Vcb\n", 0);
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
IrpContext = NULL;
|
|
Irp = NULL;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
VcbAcquired = TRUE;
|
|
}
|
|
|
|
//
|
|
// Acquire shared access to the fcb, except for a paging file
|
|
// in order to avoid deadlocks with Mm.
|
|
//
|
|
// The "removable" check was added specifically for ReadyBoost,
|
|
// which opens its cache file on a removable device as a paging file and
|
|
// relies on the file system to validate its mapping information after a
|
|
// power transition.
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) ||
|
|
FlagOn(Fcb->Vcb->VcbState, VCB_STATE_FLAG_REMOVABLE_MEDIA)) {
|
|
|
|
if (!FatAcquireSharedFcb( IrpContext, Fcb )) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot acquire Fcb\n", 0);
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
IrpContext = NULL;
|
|
Irp = NULL;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
FcbAcquired = TRUE;
|
|
}
|
|
|
|
//
|
|
// Make sure the Fcb is in a usable condition. This
|
|
// will raise an error condition if the fcb is unusable
|
|
//
|
|
|
|
FatVerifyFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// Based on the information class we'll do different
|
|
// actions. Each of hte procedures that we're calling fills
|
|
// up the output buffer, if possible. They will raise the
|
|
// status STATUS_BUFFER_OVERFLOW for an insufficient buffer.
|
|
// This is considered a somewhat unusual case and is handled
|
|
// more cleanly with the exception mechanism rather than
|
|
// testing a return status value for each call.
|
|
//
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileAllInformation:
|
|
|
|
//
|
|
// For the all information class we'll typecast a local
|
|
// pointer to the output buffer and then call the
|
|
// individual routines to fill in the buffer.
|
|
//
|
|
|
|
AllInfo = Buffer;
|
|
Length -= (sizeof(FILE_ACCESS_INFORMATION)
|
|
+ sizeof(FILE_MODE_INFORMATION)
|
|
+ sizeof(FILE_ALIGNMENT_INFORMATION));
|
|
|
|
FatQueryBasicInfo( IrpContext, Fcb, FileObject, &AllInfo->BasicInformation, &Length );
|
|
FatQueryStandardInfo( IrpContext, Fcb, &AllInfo->StandardInformation, &Length );
|
|
FatQueryInternalInfo( IrpContext, Fcb, &AllInfo->InternalInformation, &Length );
|
|
FatQueryEaInfo( IrpContext, Fcb, &AllInfo->EaInformation, &Length );
|
|
FatQueryPositionInfo( IrpContext, FileObject, &AllInfo->PositionInformation, &Length );
|
|
FatQueryNameInfo( IrpContext, Fcb, Ccb, FALSE, &AllInfo->NameInformation, &Length );
|
|
|
|
break;
|
|
|
|
case FileBasicInformation:
|
|
|
|
FatQueryBasicInfo( IrpContext, Fcb, FileObject, Buffer, &Length );
|
|
break;
|
|
|
|
case FileStandardInformation:
|
|
|
|
FatQueryStandardInfo( IrpContext, Fcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileInternalInformation:
|
|
|
|
FatQueryInternalInfo( IrpContext, Fcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileEaInformation:
|
|
|
|
FatQueryEaInfo( IrpContext, Fcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
|
|
FatQueryPositionInfo( IrpContext, FileObject, Buffer, &Length );
|
|
break;
|
|
|
|
case FileNameInformation:
|
|
|
|
FatQueryNameInfo( IrpContext, Fcb, Ccb, FALSE, Buffer, &Length );
|
|
break;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
|
case FileNormalizedNameInformation:
|
|
|
|
FatQueryNameInfo( IrpContext, Fcb, Ccb, TRUE, Buffer, &Length );
|
|
break;
|
|
#endif
|
|
|
|
case FileAlternateNameInformation:
|
|
|
|
FatQueryShortNameInfo( IrpContext, Fcb, Buffer, &Length );
|
|
break;
|
|
|
|
case FileNetworkOpenInformation:
|
|
|
|
FatQueryNetworkInfo( IrpContext, Fcb, FileObject, Buffer, &Length );
|
|
break;
|
|
|
|
default:
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
KdPrintEx((DPFLTR_FASTFAT_ID,
|
|
DPFLTR_INFO_LEVEL,
|
|
"FATQueryFile, Illegal TypeOfOpen = %08lx\n",
|
|
TypeOfOpen));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we overflowed the buffer, set the length to 0 and change the
|
|
// status to STATUS_BUFFER_OVERFLOW.
|
|
//
|
|
|
|
if ( Length < 0 ) {
|
|
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
|
|
Length = 0;
|
|
}
|
|
|
|
//
|
|
// Set the information field to the number of bytes actually filled in
|
|
// and then complete the request
|
|
//
|
|
|
|
Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - Length;
|
|
|
|
try_exit: NOTHING;
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatCommonQueryInformation );
|
|
|
|
if (FcbAcquired) { FatReleaseFcb( IrpContext, Fcb ); }
|
|
if (VcbAcquired) { FatReleaseVcb( IrpContext, Vcb ); }
|
|
|
|
if (!_SEH2_AbnormalTermination()) {
|
|
|
|
FatCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatCommonQueryInformation -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatCommonSetInformation (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the common routine for setting file information called by both
|
|
the fsd and fsp threads.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the Irp being processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The return status for the operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PFILE_OBJECT FileObject;
|
|
FILE_INFORMATION_CLASS FileInformationClass;
|
|
|
|
TYPE_OF_OPEN TypeOfOpen;
|
|
PVCB Vcb;
|
|
PFCB Fcb;
|
|
PCCB Ccb;
|
|
|
|
BOOLEAN VcbAcquired = FALSE;
|
|
BOOLEAN FcbAcquired = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get the current stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
DebugTrace(+1, Dbg, "FatCommonSetInformation...\n", 0);
|
|
DebugTrace( 0, Dbg, "Irp = %p\n", Irp);
|
|
DebugTrace( 0, Dbg, "->Length = %08lx\n", IrpSp->Parameters.SetFile.Length);
|
|
DebugTrace( 0, Dbg, "->FileInformationClass = %08lx\n", IrpSp->Parameters.SetFile.FileInformationClass);
|
|
DebugTrace( 0, Dbg, "->FileObject = %p\n", IrpSp->Parameters.SetFile.FileObject);
|
|
DebugTrace( 0, Dbg, "->ReplaceIfExists = %08lx\n", IrpSp->Parameters.SetFile.ReplaceIfExists);
|
|
DebugTrace( 0, Dbg, "->Buffer = %p\n", Irp->AssociatedIrp.SystemBuffer);
|
|
|
|
//
|
|
// Reference our input parameters to make things easier
|
|
//
|
|
|
|
FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
|
|
FileObject = IrpSp->FileObject;
|
|
|
|
//
|
|
// Decode the file object
|
|
//
|
|
|
|
TypeOfOpen = FatDecodeFileObject( FileObject, &Vcb, &Fcb, &Ccb );
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Case on the type of open we're dealing with
|
|
//
|
|
|
|
switch (TypeOfOpen) {
|
|
|
|
case UserVolumeOpen:
|
|
|
|
//
|
|
// We cannot query the user volume open.
|
|
//
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
break;
|
|
|
|
case UserFileOpen:
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) &&
|
|
((FileInformationClass == FileEndOfFileInformation) ||
|
|
(FileInformationClass == FileAllocationInformation) ||
|
|
(FileInformationClass == FileValidDataLengthInformation))) {
|
|
|
|
//
|
|
// We check whether we can proceed
|
|
// based on the state of the file oplocks.
|
|
//
|
|
|
|
Status = FsRtlCheckOplock( FatGetFcbOplock(Fcb),
|
|
Irp,
|
|
IrpContext,
|
|
NULL,
|
|
NULL );
|
|
|
|
if (Status != STATUS_SUCCESS) {
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
//
|
|
// Set the flag indicating if Fast I/O is possible
|
|
//
|
|
|
|
Fcb->Header.IsFastIoPossible = FatIsFastIoPossible( Fcb );
|
|
}
|
|
break;
|
|
|
|
case UserDirectoryOpen:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We can only do a set on a nonroot dcb, so we do the test
|
|
// and then fall through to the user file open code.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_ROOT_DCB) {
|
|
|
|
if (FileInformationClass == FileDispositionInformation) {
|
|
|
|
try_return( Status = STATUS_CANNOT_DELETE );
|
|
}
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// In the following two cases, we cannot have creates occuring
|
|
// while we are here, so acquire the volume exclusive.
|
|
//
|
|
|
|
if ((FileInformationClass == FileDispositionInformation) ||
|
|
(FileInformationClass == FileRenameInformation)) {
|
|
|
|
if (!FatAcquireExclusiveVcb( IrpContext, Vcb )) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot acquire Vcb\n", 0);
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
VcbAcquired = TRUE;
|
|
|
|
//
|
|
// Make sure we haven't been called recursively by a filter inside an existing
|
|
// create request.
|
|
//
|
|
|
|
if (FlagOn( Vcb->VcbState, VCB_STATE_FLAG_CREATE_IN_PROGRESS)) {
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast( suppress:28159, "things are seriously wrong if we get here" )
|
|
#endif
|
|
FatBugCheck( 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Acquire exclusive access to the Fcb, We use exclusive
|
|
// because it is probable that one of the subroutines
|
|
// that we call will need to monkey with file allocation,
|
|
// create/delete extra fcbs. So we're willing to pay the
|
|
// cost of exclusive Fcb access.
|
|
//
|
|
// Note that we do not acquire the resource for paging file
|
|
// operations in order to avoid deadlock with Mm.
|
|
//
|
|
// The "removable" check was added specifically for ReadyBoost,
|
|
// which opens its cache file on a removable device as a paging file and
|
|
// relies on the file system to validate its mapping information after a
|
|
// power transition.
|
|
//
|
|
|
|
if (!FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) ||
|
|
FlagOn(Fcb->Vcb->VcbState, VCB_STATE_FLAG_REMOVABLE_MEDIA)) {
|
|
|
|
if (!FatAcquireExclusiveFcb( IrpContext, Fcb )) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot acquire Fcb\n", 0);
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
FcbAcquired = TRUE;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Make sure the Fcb is in a usable condition. This
|
|
// will raise an error condition if the fcb is unusable
|
|
//
|
|
|
|
FatVerifyFcb( IrpContext, Fcb );
|
|
|
|
//
|
|
// Now that we've acquired the file, do an oplock check if the operation
|
|
// so warrants.
|
|
//
|
|
|
|
if (FatIsFileOplockable( Fcb )&&
|
|
((FileInformationClass == FileRenameInformation) ||
|
|
((FileInformationClass == FileDispositionInformation) &&
|
|
((PFILE_DISPOSITION_INFORMATION) Irp->AssociatedIrp.SystemBuffer)->DeleteFile))) {
|
|
|
|
Status = FsRtlCheckOplock( FatGetFcbOplock(Fcb),
|
|
Irp,
|
|
IrpContext,
|
|
FatOplockComplete,
|
|
NULL );
|
|
|
|
//
|
|
// Set the flag indicating if Fast I/O is possible
|
|
//
|
|
|
|
Fcb->Header.IsFastIoPossible = FatIsFastIoPossible( Fcb );
|
|
|
|
//
|
|
// If STATUS_PENDING is returned it means the oplock
|
|
// package has the Irp. Don't complete the request here.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
}
|
|
|
|
if (!NT_SUCCESS( Status ) ||
|
|
(Status == STATUS_PENDING)) {
|
|
|
|
try_return( Status );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Based on the information class we'll do different
|
|
// actions. Each of the procedures that we're calling will either
|
|
// complete the request of send the request off to the fsp
|
|
// to do the work.
|
|
//
|
|
|
|
switch (FileInformationClass) {
|
|
|
|
case FileBasicInformation:
|
|
|
|
Status = FatSetBasicInfo( IrpContext, Irp, Fcb, Ccb );
|
|
break;
|
|
|
|
case FileDispositionInformation:
|
|
|
|
//
|
|
// If this is on deferred flush media, we have to be able to wait.
|
|
//
|
|
|
|
if ( FlagOn(Vcb->VcbState, VCB_STATE_FLAG_DEFERRED_FLUSH) &&
|
|
!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) ) {
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
|
|
} else {
|
|
|
|
Status = FatSetDispositionInfo( IrpContext, Irp, FileObject, Fcb );
|
|
}
|
|
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
|
|
//
|
|
// We proceed with this operation only if we can wait
|
|
//
|
|
|
|
if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
|
|
|
|
Status = FatFsdPostRequest( IrpContext, Irp );
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
|
|
} else {
|
|
|
|
Status = FatSetRenameInfo( IrpContext, Irp, Vcb, Fcb, Ccb );
|
|
|
|
//
|
|
// If STATUS_PENDING is returned it means the oplock
|
|
// package has the Irp. Don't complete the request here.
|
|
//
|
|
|
|
if (Status == STATUS_PENDING) {
|
|
Irp = NULL;
|
|
IrpContext = NULL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
|
|
Status = FatSetPositionInfo( IrpContext, Irp, FileObject );
|
|
break;
|
|
|
|
case FileLinkInformation:
|
|
|
|
Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
|
|
case FileAllocationInformation:
|
|
|
|
Status = FatSetAllocationInfo( IrpContext, Irp, Fcb, FileObject );
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
|
|
Status = FatSetEndOfFileInfo( IrpContext, Irp, FileObject, Vcb, Fcb );
|
|
break;
|
|
|
|
case FileValidDataLengthInformation:
|
|
|
|
Status = FatSetValidDataLengthInfo( IrpContext, Irp, FileObject, Fcb, Ccb );
|
|
break;
|
|
|
|
default:
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
if ( IrpContext != NULL ) {
|
|
|
|
FatUnpinRepinnedBcbs( IrpContext );
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatCommonSetInformation );
|
|
|
|
if (FcbAcquired) { FatReleaseFcb( IrpContext, Fcb ); }
|
|
|
|
if (VcbAcquired) { FatReleaseVcb( IrpContext, Vcb ); }
|
|
|
|
if (!_SEH2_AbnormalTermination()) {
|
|
|
|
FatCompleteRequest( IrpContext, Irp, Status );
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatCommonSetInformation -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
FatQueryBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_BASIC_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
Description:
|
|
|
|
This routine performs the query basic information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
FileObject - Supplies the flag bit that indicates the file was modified.
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER( FileObject );
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryBasicInfo...\n", 0);
|
|
|
|
//
|
|
// Zero out the output buffer, and set it to indicate that
|
|
// the query is a normal file. Later we might overwrite the
|
|
// attribute.
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_BASIC_INFORMATION) );
|
|
|
|
//
|
|
// Extract the data and fill in the non zero fields of the output
|
|
// buffer
|
|
//
|
|
|
|
if (Fcb->Header.NodeTypeCode == FAT_NTC_ROOT_DCB) {
|
|
|
|
//
|
|
// We have to munge a lie on the fly. Every time we have to
|
|
// use 1/1/80 we need to convert to GMT since the TZ may have
|
|
// changed on us.
|
|
//
|
|
|
|
ExLocalTimeToSystemTime( &FatJanOne1980,
|
|
&Buffer->LastWriteTime );
|
|
Buffer->CreationTime = Buffer->LastAccessTime = Buffer->LastWriteTime;
|
|
|
|
} else {
|
|
|
|
Buffer->LastWriteTime = Fcb->LastWriteTime;
|
|
Buffer->CreationTime = Fcb->CreationTime;
|
|
Buffer->LastAccessTime = Fcb->LastAccessTime;
|
|
}
|
|
|
|
Buffer->FileAttributes = Fcb->DirentFatFlags;
|
|
|
|
|
|
//
|
|
// If the temporary flag is set, then set it in the buffer.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_TEMPORARY )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
|
|
}
|
|
|
|
//
|
|
// If no attributes were set, set the normal bit.
|
|
//
|
|
|
|
if (Buffer->FileAttributes == 0) {
|
|
|
|
Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_BASIC_INFORMATION );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryBasicInfo -> VOID\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryStandardInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_STANDARD_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query standard information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryStandardInfo...\n", 0);
|
|
|
|
//
|
|
// Zero out the output buffer, and fill in the number of links
|
|
// and the delete pending flag.
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_STANDARD_INFORMATION) );
|
|
|
|
Buffer->NumberOfLinks = 1;
|
|
Buffer->DeletePending = BooleanFlagOn( Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE );
|
|
|
|
//
|
|
// Case on whether this is a file or a directory, and extract
|
|
// the information and fill in the fcb/dcb specific parts
|
|
// of the output buffer
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_FCB) {
|
|
|
|
if (Fcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) {
|
|
|
|
FatLookupFileAllocationSize( IrpContext, Fcb );
|
|
}
|
|
|
|
Buffer->AllocationSize = Fcb->Header.AllocationSize;
|
|
Buffer->EndOfFile = Fcb->Header.FileSize;
|
|
|
|
Buffer->Directory = FALSE;
|
|
|
|
} else {
|
|
|
|
Buffer->Directory = TRUE;
|
|
}
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_STANDARD_INFORMATION );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryStandardInfo -> VOID\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
FatQueryInternalInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_INTERNAL_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query internal information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryInternalInfo...\n", 0);
|
|
|
|
_SEH2_TRY {
|
|
|
|
Buffer->IndexNumber.QuadPart = FatGenerateFileIdFromFcb( Fcb );
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_INTERNAL_INFORMATION );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatQueryInternalInfo );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryInternalInfo -> VOID\n", 0);
|
|
} _SEH2_END;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
FatQueryEaInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_EA_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query Ea information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PBCB Bcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER( Fcb );
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryEaInfo...\n", 0);
|
|
|
|
Bcb = NULL;
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Zero out the output buffer
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_EA_INFORMATION) );
|
|
|
|
#if 0
|
|
//
|
|
// The Root dcb does not have any EAs so don't look for any. Fat32
|
|
// doesn't have any, either.
|
|
//
|
|
|
|
if ( NodeType( Fcb ) != FAT_NTC_ROOT_DCB &&
|
|
!FatIsFat32( Fcb->Vcb )) {
|
|
|
|
PDIRENT Dirent;
|
|
|
|
//
|
|
// Try to get the dirent for this file.
|
|
//
|
|
|
|
FatGetDirentFromFcbOrDcb( IrpContext,
|
|
Fcb,
|
|
&Dirent,
|
|
&Bcb );
|
|
|
|
if (Dirent != NULL) {
|
|
|
|
//
|
|
// Get a the size needed to store the full eas for the file.
|
|
//
|
|
|
|
FatGetEaLength( IrpContext,
|
|
Fcb->Vcb,
|
|
Dirent,
|
|
&Buffer->EaSize );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_EA_INFORMATION );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatQueryEaInfo );
|
|
|
|
//
|
|
// Unpin the dirent if pinned.
|
|
//
|
|
|
|
FatUnpinBcb( IrpContext, Bcb );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryEaInfo -> VOID\n", 0);
|
|
} _SEH2_END;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
FatQueryPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_POSITION_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query position information function for fat.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Supplies the File object being queried
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryPositionInfo...\n", 0);
|
|
|
|
//
|
|
// Get the current position found in the file object.
|
|
//
|
|
|
|
Buffer->CurrentByteOffset = FileObject->CurrentByteOffset;
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_POSITION_INFORMATION );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryPositionInfo -> VOID\n", 0);
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb,
|
|
IN BOOLEAN Normalized,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the query name information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
Ccb - Supplies the Ccb for the context of the user open
|
|
|
|
Normalized - if true the caller wants a normalized name (w/out short names).
|
|
This means we're servicing a FileNormalizedNameInformation query.
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG BytesToCopy;
|
|
LONG TrimLength;
|
|
BOOLEAN Overflow = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryNameInfo...\n", 0);
|
|
|
|
//
|
|
// Convert the name to UNICODE
|
|
//
|
|
|
|
*Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
//
|
|
// Use the full filename to build the path up. If we wanted to be
|
|
// slick in the future, we'd just build the path directly into the
|
|
// return buffer and avoid constructing the full filename, but since
|
|
// the full filename winds up being required so often lets not
|
|
// over optimize this case yet.
|
|
//
|
|
|
|
if (Fcb->FullFileName.Buffer == NULL) {
|
|
|
|
FatSetFullFileNameInFcb( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// Here is where it gets a smidge tricky. FinalNameLength is the length
|
|
// of the LFN element if it exists, and since the long name is always used
|
|
// to build FullFileName, we have two cases:
|
|
//
|
|
// 1) short name: use FinalNameLength to tear off the path from FullFileName
|
|
// and append the UNICODE converted short name.
|
|
// 2) long name: just use FullFileName
|
|
//
|
|
// We bias to the name the user thinks they opened by. This winds
|
|
// up fixing some oddball tunneling cases where intermediate filters
|
|
// translate operations like delete into renames - this lets them
|
|
// do the operation in the context of the name the user was using.
|
|
//
|
|
// It also matches what NTFS does, and so we have the definition of
|
|
// correct behavior.
|
|
//
|
|
|
|
//
|
|
//
|
|
// Assume there is no long name and we are just going to use
|
|
// FullFileName.
|
|
//
|
|
|
|
TrimLength = 0;
|
|
|
|
//
|
|
// If a LongName exists, the caller isn't asking for the normalized name,
|
|
// and the original open was by the short name then set TrimLength to point
|
|
// to the place where the short name goes.
|
|
//
|
|
// Note: The Ccb can be NULL. The lazy writer calls to get the name of
|
|
// a DirectoryOpen FILE_OBJECT that it wants to display in the lost
|
|
// delayed write popup. Handle this case by just using the FileFullName.
|
|
//
|
|
|
|
if (!Normalized &&
|
|
(Fcb->LongName.Unicode.Name.Unicode.Buffer != NULL)) {
|
|
|
|
if ((Ccb != NULL) &&
|
|
FlagOn(Ccb->Flags, CCB_FLAG_OPENED_BY_SHORTNAME)) {
|
|
|
|
TrimLength = Fcb->FinalNameLength;
|
|
}
|
|
}
|
|
|
|
if (*Length < Fcb->FullFileName.Length - TrimLength) {
|
|
|
|
BytesToCopy = *Length;
|
|
Overflow = TRUE;
|
|
|
|
} else {
|
|
|
|
BytesToCopy = Fcb->FullFileName.Length - TrimLength;
|
|
*Length -= BytesToCopy;
|
|
}
|
|
|
|
RtlCopyMemory( &Buffer->FileName[0],
|
|
Fcb->FullFileName.Buffer,
|
|
BytesToCopy );
|
|
|
|
//
|
|
// Note that this is just the amount of name we've copied so far. It'll
|
|
// either be all of it (long) or the path element including the \ (short).
|
|
//
|
|
|
|
Buffer->FileNameLength = Fcb->FullFileName.Length - TrimLength;
|
|
|
|
//
|
|
// If we trimmed off the name element, this is the short name case. Pick
|
|
// up the UNICODE conversion and append it.
|
|
//
|
|
|
|
if (TrimLength != 0) {
|
|
|
|
UNICODE_STRING ShortName;
|
|
WCHAR ShortNameBuffer[12];
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Convert the short name to UNICODE and figure out how much
|
|
// of it can fit. Again, we always bump the returned length
|
|
// to indicate how much is available even if we can't return it.
|
|
//
|
|
|
|
ShortName.Length = 0;
|
|
ShortName.MaximumLength = sizeof(ShortNameBuffer);
|
|
ShortName.Buffer = ShortNameBuffer;
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast( suppress:28931, "needed for debug build" )
|
|
#endif
|
|
Status = RtlOemStringToCountedUnicodeString( &ShortName,
|
|
&Fcb->ShortName.Name.Oem,
|
|
FALSE );
|
|
|
|
NT_ASSERT( Status == STATUS_SUCCESS );
|
|
|
|
if (!Overflow) {
|
|
|
|
if (*Length < ShortName.Length) {
|
|
|
|
BytesToCopy = *Length;
|
|
Overflow = TRUE;
|
|
|
|
} else {
|
|
|
|
BytesToCopy = ShortName.Length;
|
|
*Length -= BytesToCopy;
|
|
}
|
|
|
|
RtlCopyMemory( (PUCHAR)&Buffer->FileName[0] + Buffer->FileNameLength,
|
|
ShortName.Buffer,
|
|
BytesToCopy );
|
|
}
|
|
|
|
Buffer->FileNameLength += ShortName.Length;
|
|
}
|
|
|
|
if (Overflow) {
|
|
|
|
*Length = -1;
|
|
}
|
|
|
|
//
|
|
// Return to caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryNameInfo -> VOID\n", 0);
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
VOID
|
|
FatQueryShortNameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN OUT PFILE_NAME_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the short name of the file.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
ULONG BytesToCopy;
|
|
WCHAR ShortNameBuffer[12];
|
|
UNICODE_STRING ShortName;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryNameInfo...\n", 0);
|
|
|
|
//
|
|
// Convert the name to UNICODE
|
|
//
|
|
|
|
ShortName.Length = 0;
|
|
ShortName.MaximumLength = sizeof(ShortNameBuffer);
|
|
ShortName.Buffer = ShortNameBuffer;
|
|
|
|
*Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast( suppress:28931, "needed for debug build" )
|
|
#endif
|
|
Status = RtlOemStringToCountedUnicodeString( &ShortName,
|
|
&Fcb->ShortName.Name.Oem,
|
|
FALSE );
|
|
|
|
NT_ASSERT( Status == STATUS_SUCCESS );
|
|
|
|
//
|
|
// If we overflow, set *Length to -1 as a flag.
|
|
//
|
|
|
|
if (*Length < ShortName.Length) {
|
|
|
|
BytesToCopy = *Length;
|
|
*Length = -1;
|
|
|
|
} else {
|
|
|
|
BytesToCopy = ShortName.Length;
|
|
*Length -= ShortName.Length;
|
|
}
|
|
|
|
RtlCopyMemory( &Buffer->FileName[0],
|
|
&ShortName.Buffer[0],
|
|
BytesToCopy );
|
|
|
|
Buffer->FileNameLength = ShortName.Length;
|
|
|
|
//
|
|
// Return to caller
|
|
//
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryNameInfo -> VOID\n", 0);
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatQueryNetworkInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
|
|
IN OUT PLONG Length
|
|
)
|
|
|
|
/*++
|
|
Description:
|
|
|
|
This routine performs the query network open information function for fat.
|
|
|
|
Arguments:
|
|
|
|
Fcb - Supplies the Fcb being queried, it has been verified
|
|
|
|
FileObject - Supplies the flag bit that indicates the file was modified.
|
|
|
|
Buffer - Supplies a pointer to the buffer where the information is to
|
|
be returned
|
|
|
|
Length - Supplies the length of the buffer in bytes, and receives the
|
|
remaining bytes free in the buffer upon return.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER( FileObject );
|
|
|
|
DebugTrace(+1, Dbg, "FatQueryNetworkInfo...\n", 0);
|
|
|
|
//
|
|
// Zero out the output buffer, and set it to indicate that
|
|
// the query is a normal file. Later we might overwrite the
|
|
// attribute.
|
|
//
|
|
|
|
RtlZeroMemory( Buffer, sizeof(FILE_NETWORK_OPEN_INFORMATION) );
|
|
|
|
//
|
|
// Extract the data and fill in the non zero fields of the output
|
|
// buffer
|
|
//
|
|
|
|
if (Fcb->Header.NodeTypeCode == FAT_NTC_ROOT_DCB) {
|
|
|
|
//
|
|
// We have to munge a lie on the fly. Every time we have to
|
|
// use 1/1/80 we need to convert to GMT since the TZ may have
|
|
// changed on us.
|
|
//
|
|
|
|
ExLocalTimeToSystemTime( &FatJanOne1980,
|
|
&Buffer->LastWriteTime );
|
|
Buffer->CreationTime = Buffer->LastAccessTime = Buffer->LastWriteTime;
|
|
|
|
} else {
|
|
|
|
Buffer->LastWriteTime.QuadPart = Fcb->LastWriteTime.QuadPart;
|
|
Buffer->CreationTime.QuadPart = Fcb->CreationTime.QuadPart;
|
|
Buffer->LastAccessTime.QuadPart = Fcb->LastAccessTime.QuadPart;
|
|
}
|
|
|
|
Buffer->FileAttributes = Fcb->DirentFatFlags;
|
|
|
|
|
|
//
|
|
// If the temporary flag is set, then set it in the buffer.
|
|
//
|
|
|
|
if (FlagOn( Fcb->FcbState, FCB_STATE_TEMPORARY )) {
|
|
|
|
SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
|
|
}
|
|
|
|
//
|
|
// If no attributes were set, set the normal bit.
|
|
//
|
|
|
|
if (Buffer->FileAttributes == 0) {
|
|
|
|
Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
}
|
|
//
|
|
// Case on whether this is a file or a directory, and extract
|
|
// the information and fill in the fcb/dcb specific parts
|
|
// of the output buffer
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_FCB) {
|
|
|
|
if (Fcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) {
|
|
|
|
FatLookupFileAllocationSize( IrpContext, Fcb );
|
|
}
|
|
|
|
Buffer->AllocationSize.QuadPart = Fcb->Header.AllocationSize.QuadPart;
|
|
Buffer->EndOfFile.QuadPart = Fcb->Header.FileSize.QuadPart;
|
|
}
|
|
|
|
//
|
|
// Update the length and status output variables
|
|
//
|
|
|
|
*Length -= sizeof( FILE_NETWORK_OPEN_INFORMATION );
|
|
|
|
DebugTrace( 0, Dbg, "*Length = %08lx\n", *Length);
|
|
|
|
DebugTrace(-1, Dbg, "FatQueryNetworkInfo -> VOID\n", 0);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetBasicInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set basic information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
Ccb - Supplies the flag bit that control updating the last modify
|
|
time on cleanup.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PFILE_BASIC_INFORMATION Buffer;
|
|
|
|
PDIRENT Dirent;
|
|
PBCB DirentBcb;
|
|
|
|
FAT_TIME_STAMP CreationTime = {0};
|
|
UCHAR CreationMSec = 0;
|
|
FAT_TIME_STAMP LastWriteTime = {0};
|
|
FAT_TIME_STAMP LastAccessTime = {0};
|
|
FAT_DATE LastAccessDate = {0};
|
|
UCHAR Attributes;
|
|
|
|
BOOLEAN ModifyCreation = FALSE;
|
|
BOOLEAN ModifyLastWrite = FALSE;
|
|
BOOLEAN ModifyLastAccess = FALSE;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
BOOLEAN ModifiedAttributes = FALSE;
|
|
#endif
|
|
|
|
LARGE_INTEGER LargeCreationTime = {0};
|
|
LARGE_INTEGER LargeLastWriteTime = {0};
|
|
LARGE_INTEGER LargeLastAccessTime = {0};
|
|
|
|
ULONG NotifyFilter = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatSetBasicInfo...\n", 0);
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// If the user is specifying -1 for a field, that means
|
|
// we should leave that field unchanged, even if we might
|
|
// have otherwise set it ourselves. We'll set the Ccb flag
|
|
// saying that the user set the field so that we
|
|
// don't do our default updating.
|
|
//
|
|
// We set the field to 0 then so we know not to actually
|
|
// set the field to the user-specified (and in this case,
|
|
// illegal) value.
|
|
//
|
|
|
|
if (Buffer->LastWriteTime.QuadPart == -1) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_WRITE );
|
|
Buffer->LastWriteTime.QuadPart = 0;
|
|
}
|
|
|
|
if (Buffer->LastAccessTime.QuadPart == -1) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS );
|
|
Buffer->LastAccessTime.QuadPart = 0;
|
|
}
|
|
|
|
if (Buffer->CreationTime.QuadPart == -1) {
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_CREATION );
|
|
Buffer->CreationTime.QuadPart = 0;
|
|
}
|
|
|
|
DirentBcb = NULL;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
_SEH2_TRY {
|
|
|
|
LARGE_INTEGER FatLocalDecThirtyOne1979;
|
|
LARGE_INTEGER FatLocalJanOne1980;
|
|
|
|
ExLocalTimeToSystemTime( &FatDecThirtyOne1979,
|
|
&FatLocalDecThirtyOne1979 );
|
|
|
|
ExLocalTimeToSystemTime( &FatJanOne1980,
|
|
&FatLocalJanOne1980 );
|
|
|
|
//
|
|
// Get a pointer to the dirent
|
|
//
|
|
|
|
NT_ASSERT( Fcb->FcbCondition == FcbGood );
|
|
|
|
FatGetDirentFromFcbOrDcb( IrpContext,
|
|
Fcb,
|
|
FALSE,
|
|
&Dirent,
|
|
&DirentBcb );
|
|
//
|
|
// Check if the user specified a non-zero creation time
|
|
//
|
|
|
|
if (FatData.ChicagoMode && (Buffer->CreationTime.QuadPart != 0)) {
|
|
|
|
LargeCreationTime = Buffer->CreationTime;
|
|
|
|
//
|
|
// Convert the Nt time to a Fat time
|
|
//
|
|
|
|
if ( !FatNtTimeToFatTime( IrpContext,
|
|
&LargeCreationTime,
|
|
FALSE,
|
|
&CreationTime,
|
|
&CreationMSec )) {
|
|
|
|
//
|
|
// Special case the value 12/31/79 and treat this as 1/1/80.
|
|
// This '79 value can happen because of time zone issues.
|
|
//
|
|
|
|
if ((LargeCreationTime.QuadPart >= FatLocalDecThirtyOne1979.QuadPart) &&
|
|
(LargeCreationTime.QuadPart < FatLocalJanOne1980.QuadPart)) {
|
|
|
|
CreationTime = FatTimeJanOne1980;
|
|
LargeCreationTime = FatLocalJanOne1980;
|
|
|
|
} else {
|
|
|
|
DebugTrace(0, Dbg, "Invalid CreationTime\n", 0);
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Don't worry about CreationMSec
|
|
//
|
|
|
|
CreationMSec = 0;
|
|
}
|
|
|
|
ModifyCreation = TRUE;
|
|
}
|
|
|
|
//
|
|
// Check if the user specified a non-zero last access time
|
|
//
|
|
|
|
if (FatData.ChicagoMode && (Buffer->LastAccessTime.QuadPart != 0)) {
|
|
|
|
LargeLastAccessTime = Buffer->LastAccessTime;
|
|
|
|
//
|
|
// Convert the Nt time to a Fat time
|
|
//
|
|
|
|
if ( !FatNtTimeToFatTime( IrpContext,
|
|
&LargeLastAccessTime,
|
|
TRUE,
|
|
&LastAccessTime,
|
|
NULL )) {
|
|
|
|
//
|
|
// Special case the value 12/31/79 and treat this as 1/1/80.
|
|
// This '79 value can happen because of time zone issues.
|
|
//
|
|
|
|
if ((LargeLastAccessTime.QuadPart >= FatLocalDecThirtyOne1979.QuadPart) &&
|
|
(LargeLastAccessTime.QuadPart < FatLocalJanOne1980.QuadPart)) {
|
|
|
|
LastAccessTime = FatTimeJanOne1980;
|
|
LargeLastAccessTime = FatLocalJanOne1980;
|
|
|
|
} else {
|
|
|
|
DebugTrace(0, Dbg, "Invalid LastAccessTime\n", 0);
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
}
|
|
|
|
LastAccessDate = LastAccessTime.Date;
|
|
ModifyLastAccess = TRUE;
|
|
}
|
|
|
|
//
|
|
// Check if the user specified a non-zero last write time
|
|
//
|
|
|
|
if (Buffer->LastWriteTime.QuadPart != 0) {
|
|
|
|
//
|
|
// First do a quick check here if the this time is the same
|
|
// time as LastAccessTime.
|
|
//
|
|
|
|
if (ModifyLastAccess &&
|
|
(Buffer->LastWriteTime.QuadPart == Buffer->LastAccessTime.QuadPart)) {
|
|
|
|
ModifyLastWrite = TRUE;
|
|
LastWriteTime = LastAccessTime;
|
|
LargeLastWriteTime = LargeLastAccessTime;
|
|
|
|
} else {
|
|
|
|
LargeLastWriteTime = Buffer->LastWriteTime;
|
|
|
|
//
|
|
// Convert the Nt time to a Fat time
|
|
//
|
|
|
|
if ( !FatNtTimeToFatTime( IrpContext,
|
|
&LargeLastWriteTime,
|
|
TRUE,
|
|
&LastWriteTime,
|
|
NULL )) {
|
|
|
|
|
|
//
|
|
// Special case the value 12/31/79 and treat this as 1/1/80.
|
|
// This '79 value can happen because of time zone issues.
|
|
//
|
|
|
|
if ((LargeLastWriteTime.QuadPart >= FatLocalDecThirtyOne1979.QuadPart) &&
|
|
(LargeLastWriteTime.QuadPart < FatLocalJanOne1980.QuadPart)) {
|
|
|
|
LastWriteTime = FatTimeJanOne1980;
|
|
LargeLastWriteTime = FatLocalJanOne1980;
|
|
|
|
} else {
|
|
|
|
DebugTrace(0, Dbg, "Invalid LastWriteTime\n", 0);
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
}
|
|
|
|
ModifyLastWrite = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Check if the user specified a non zero file attributes byte
|
|
//
|
|
|
|
if (Buffer->FileAttributes != 0) {
|
|
|
|
//
|
|
// Only permit the attributes that FAT understands. The rest are silently
|
|
// dropped on the floor.
|
|
//
|
|
|
|
Attributes = (UCHAR)(Buffer->FileAttributes & (FILE_ATTRIBUTE_READONLY |
|
|
FILE_ATTRIBUTE_HIDDEN |
|
|
FILE_ATTRIBUTE_SYSTEM |
|
|
FILE_ATTRIBUTE_DIRECTORY |
|
|
FILE_ATTRIBUTE_ARCHIVE));
|
|
|
|
//
|
|
// Make sure that for a file the directory bit is not set
|
|
// and that for a directory the bit is set.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_FCB) {
|
|
|
|
if (FlagOn(Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
|
|
|
|
DebugTrace(0, Dbg, "Attempt to set dir attribute on file\n", 0);
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
} else {
|
|
|
|
Attributes |= FAT_DIRENT_ATTR_DIRECTORY;
|
|
}
|
|
|
|
//
|
|
// Mark the FcbState temporary flag correctly.
|
|
//
|
|
|
|
if (FlagOn(Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY)) {
|
|
|
|
//
|
|
// Don't allow the temporary bit to be set on directories.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
DebugTrace(0, Dbg, "No temporary directories\n", 0);
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_TEMPORARY );
|
|
|
|
SetFlag( IoGetCurrentIrpStackLocation(Irp)->FileObject->Flags,
|
|
FO_TEMPORARY_FILE );
|
|
|
|
} else {
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_TEMPORARY );
|
|
|
|
ClearFlag( IoGetCurrentIrpStackLocation(Irp)->FileObject->Flags,
|
|
FO_TEMPORARY_FILE );
|
|
}
|
|
|
|
//
|
|
// Now, only proceed if the requested file attributes are different
|
|
// than the existing attributes.
|
|
//
|
|
|
|
if (Dirent->Attributes != Attributes) {
|
|
|
|
//
|
|
// Set the new attributes byte, and mark the bcb dirty
|
|
//
|
|
|
|
Fcb->DirentFatFlags = Attributes;
|
|
|
|
Dirent->Attributes = Attributes;
|
|
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
ModifiedAttributes = TRUE;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if ( ModifyCreation ) {
|
|
|
|
//
|
|
// Set the new last write time in the dirent, and mark
|
|
// the bcb dirty
|
|
//
|
|
|
|
Fcb->CreationTime = LargeCreationTime;
|
|
Dirent->CreationTime = CreationTime;
|
|
Dirent->CreationMSec = CreationMSec;
|
|
|
|
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_CREATION;
|
|
//
|
|
// Now we have to round the time in the Fcb up to the
|
|
// nearest tem msec.
|
|
//
|
|
|
|
Fcb->CreationTime.QuadPart =
|
|
|
|
((Fcb->CreationTime.QuadPart + AlmostTenMSec) /
|
|
TenMSec) * TenMSec;
|
|
|
|
//
|
|
// Now because the user just set the creation time we
|
|
// better not set the creation time on close
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_CREATION );
|
|
}
|
|
|
|
if ( ModifyLastAccess ) {
|
|
|
|
//
|
|
// Set the new last write time in the dirent, and mark
|
|
// the bcb dirty
|
|
//
|
|
|
|
Fcb->LastAccessTime = LargeLastAccessTime;
|
|
Dirent->LastAccessDate = LastAccessDate;
|
|
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
|
|
|
//
|
|
// Now we have to truncate the time in the Fcb down to the
|
|
// current day. This has to be in LocalTime though, so first
|
|
// convert to local, trunacate, then set back to GMT.
|
|
//
|
|
|
|
ExSystemTimeToLocalTime( &Fcb->LastAccessTime,
|
|
&Fcb->LastAccessTime );
|
|
|
|
Fcb->LastAccessTime.QuadPart =
|
|
|
|
(Fcb->LastAccessTime.QuadPart /
|
|
FatOneDay.QuadPart) * FatOneDay.QuadPart;
|
|
|
|
ExLocalTimeToSystemTime( &Fcb->LastAccessTime,
|
|
&Fcb->LastAccessTime );
|
|
|
|
//
|
|
// Now because the user just set the last access time we
|
|
// better not set the last access time on close
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS );
|
|
}
|
|
|
|
if ( ModifyLastWrite ) {
|
|
|
|
//
|
|
// Set the new last write time in the dirent, and mark
|
|
// the bcb dirty
|
|
//
|
|
|
|
Fcb->LastWriteTime = LargeLastWriteTime;
|
|
Dirent->LastWriteTime = LastWriteTime;
|
|
|
|
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
|
|
//
|
|
// Now we have to round the time in the Fcb up to the
|
|
// nearest two seconds.
|
|
//
|
|
|
|
Fcb->LastWriteTime.QuadPart =
|
|
|
|
((Fcb->LastWriteTime.QuadPart + AlmostTwoSeconds) /
|
|
TwoSeconds) * TwoSeconds;
|
|
|
|
//
|
|
// Now because the user just set the last write time we
|
|
// better not set the last write time on close
|
|
//
|
|
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_WRITE );
|
|
}
|
|
|
|
//
|
|
// If we modified any of the values, we report this to the notify
|
|
// package.
|
|
//
|
|
// We also take this opportunity to set the current file size and
|
|
// first cluster in the Dirent in order to support a server hack.
|
|
//
|
|
|
|
if (NotifyFilter != 0) {
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_FCB) {
|
|
|
|
Dirent->FileSize = Fcb->Header.FileSize.LowPart;
|
|
|
|
|
|
Dirent->FirstClusterOfFile = (USHORT)Fcb->FirstClusterOfFile;
|
|
|
|
if (FatIsFat32(Fcb->Vcb)) {
|
|
|
|
Dirent->FirstClusterOfFileHi =
|
|
(USHORT)(Fcb->FirstClusterOfFile >> 16);
|
|
}
|
|
}
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Fcb->Vcb,
|
|
Fcb,
|
|
NotifyFilter,
|
|
FILE_ACTION_MODIFIED );
|
|
|
|
FatSetDirtyBcb( IrpContext, DirentBcb, Fcb->Vcb, TRUE );
|
|
}
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
//
|
|
// If last-access, last-write, or any attribute bits changed, break
|
|
// parent directory oplock.
|
|
//
|
|
|
|
if ((Fcb->ParentDcb != NULL) &&
|
|
(ModifyLastAccess ||
|
|
ModifyLastWrite ||
|
|
ModifiedAttributes)) {
|
|
|
|
FsRtlCheckOplockEx( FatGetFcbOplock(Fcb->ParentDcb),
|
|
IrpContext->OriginatingIrp,
|
|
OPLOCK_FLAG_PARENT_OBJECT,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
#endif
|
|
|
|
try_exit: NOTHING;
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatSetBasicInfo );
|
|
|
|
FatUnpinBcb( IrpContext, DirentBcb );
|
|
|
|
DebugTrace(-1, Dbg, "FatSetBasicInfo -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetDispositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set disposition information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_DISPOSITION_INFORMATION Buffer;
|
|
PBCB Bcb;
|
|
PDIRENT Dirent;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatSetDispositionInfo...\n", 0);
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Check if the user wants to delete the file or not delete
|
|
// the file
|
|
//
|
|
|
|
if (Buffer->DeleteFile) {
|
|
|
|
//
|
|
// Check if the file is marked read only
|
|
//
|
|
|
|
if (FlagOn(Fcb->DirentFatFlags, FAT_DIRENT_ATTR_READ_ONLY)) {
|
|
|
|
DebugTrace(-1, Dbg, "Cannot delete readonly file\n", 0);
|
|
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
//
|
|
// Make sure there is no process mapping this file as an image.
|
|
//
|
|
|
|
if (!MmFlushImageSection( &Fcb->NonPaged->SectionObjectPointers,
|
|
MmFlushForDelete )) {
|
|
|
|
DebugTrace(-1, Dbg, "Cannot delete user mapped image\n", 0);
|
|
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
//
|
|
// Check if this is a dcb and if so then only allow
|
|
// the request if the directory is empty.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_ROOT_DCB) {
|
|
|
|
DebugTrace(-1, Dbg, "Cannot delete root Directory\n", 0);
|
|
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
DebugTrace(-1, Dbg, "User wants to delete a directory\n", 0);
|
|
|
|
//
|
|
// Check if the directory is empty
|
|
//
|
|
|
|
if ( !FatIsDirectoryEmpty(IrpContext, Fcb) ) {
|
|
|
|
DebugTrace(-1, Dbg, "Directory is not empty\n", 0);
|
|
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is a floppy, touch the volume so to verify that it
|
|
// is not write protected.
|
|
//
|
|
|
|
if ( FlagOn(Fcb->Vcb->Vpb->RealDevice->Characteristics, FILE_FLOPPY_DISKETTE)) {
|
|
|
|
PVCB Vcb;
|
|
PBCB LocalBcb = NULL;
|
|
UCHAR *LocalBuffer;
|
|
UCHAR TmpChar;
|
|
ULONG BytesToMap;
|
|
|
|
IO_STATUS_BLOCK Iosb;
|
|
|
|
Vcb = Fcb->Vcb;
|
|
|
|
BytesToMap = Vcb->AllocationSupport.FatIndexBitSize == 12 ?
|
|
FatReservedBytes(&Vcb->Bpb) +
|
|
FatBytesPerFat(&Vcb->Bpb):PAGE_SIZE;
|
|
|
|
FatReadVolumeFile( IrpContext,
|
|
Vcb,
|
|
0,
|
|
BytesToMap,
|
|
&LocalBcb,
|
|
(PVOID *)&LocalBuffer );
|
|
|
|
_SEH2_TRY {
|
|
|
|
if (!CcPinMappedData( Vcb->VirtualVolumeFile,
|
|
&FatLargeZero,
|
|
BytesToMap,
|
|
BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
|
|
&LocalBcb )) {
|
|
|
|
//
|
|
// Could not pin the data without waiting (cache miss).
|
|
//
|
|
|
|
FatRaiseStatus( IrpContext, STATUS_CANT_WAIT );
|
|
}
|
|
|
|
//
|
|
// Make Mm, myself, and Cc think the byte is dirty, and then
|
|
// force a writethrough.
|
|
//
|
|
|
|
LocalBuffer += FatReservedBytes(&Vcb->Bpb);
|
|
|
|
TmpChar = LocalBuffer[0];
|
|
LocalBuffer[0] = TmpChar;
|
|
|
|
FatAddMcbEntry( Vcb, &Vcb->DirtyFatMcb,
|
|
FatReservedBytes( &Vcb->Bpb ),
|
|
FatReservedBytes( &Vcb->Bpb ),
|
|
Vcb->Bpb.BytesPerSector );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
if (_SEH2_AbnormalTermination() && (LocalBcb != NULL)) {
|
|
|
|
FatUnpinBcb( IrpContext, LocalBcb );
|
|
}
|
|
} _SEH2_END;
|
|
|
|
CcRepinBcb( LocalBcb );
|
|
CcSetDirtyPinnedData( LocalBcb, NULL );
|
|
CcUnpinData( LocalBcb );
|
|
DbgDoit( NT_ASSERT( IrpContext->PinCount ));
|
|
DbgDoit( IrpContext->PinCount -= 1 );
|
|
CcUnpinRepinnedBcb( LocalBcb, TRUE, &Iosb );
|
|
|
|
//
|
|
// If this was not successful, raise the status.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(Iosb.Status) ) {
|
|
|
|
FatNormalizeAndRaiseStatus( IrpContext, Iosb.Status );
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Just set a Bcb dirty here. The above code was only there to
|
|
// detect a write protected floppy, while the below code works
|
|
// for any write protected media and only takes a hit when the
|
|
// volume in clean.
|
|
//
|
|
|
|
FatGetDirentFromFcbOrDcb( IrpContext,
|
|
Fcb,
|
|
FALSE,
|
|
&Dirent,
|
|
&Bcb );
|
|
|
|
//
|
|
// This has to work for the usual reasons (we verified the Fcb within
|
|
// volume synch).
|
|
//
|
|
|
|
_SEH2_TRY {
|
|
|
|
FatSetDirtyBcb( IrpContext, Bcb, Fcb->Vcb, TRUE );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
FatUnpinBcb( IrpContext, Bcb );
|
|
} _SEH2_END;
|
|
}
|
|
|
|
//
|
|
// At this point either we have a file or an empty directory
|
|
// so we know the delete can proceed.
|
|
//
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE );
|
|
FileObject->DeletePending = TRUE;
|
|
|
|
//
|
|
// If this is a directory then report this delete pending to
|
|
// the dir notify package.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
FsRtlNotifyFullChangeDirectory( Fcb->Vcb->NotifySync,
|
|
&Fcb->Vcb->DirNotifyList,
|
|
FileObject->FsContext,
|
|
NULL,
|
|
FALSE,
|
|
FALSE,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// The user doesn't want to delete the file so clear
|
|
// the delete on close bit
|
|
//
|
|
|
|
DebugTrace(0, Dbg, "User want to not delete file\n", 0);
|
|
|
|
ClearFlag( Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE );
|
|
FileObject->DeletePending = FALSE;
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatSetDispositionInfo -> STATUS_SUCCESS\n", 0);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
FatSetRenameInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PVCB Vcb,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set name information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
Vcb - Supplies the Vcb being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
Ccb - Supplies the Ccb corresponding to the handle opening the source
|
|
file
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN AllLowerComponent;
|
|
BOOLEAN AllLowerExtension;
|
|
BOOLEAN CaseOnlyRename;
|
|
BOOLEAN ContinueWithRename;
|
|
BOOLEAN CreateLfn = FALSE;
|
|
BOOLEAN DeleteSourceDirent;
|
|
BOOLEAN DeleteTarget;
|
|
BOOLEAN NewDirentFromPool;
|
|
BOOLEAN RenamedAcrossDirectories;
|
|
BOOLEAN ReplaceIfExists;
|
|
|
|
CCB LocalCcb;
|
|
PCCB SourceCcb;
|
|
|
|
DIRENT Dirent;
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
OEM_STRING OldOemName;
|
|
OEM_STRING NewOemName;
|
|
UCHAR OemNameBuffer[24*2];
|
|
|
|
PBCB DotDotBcb;
|
|
PBCB NewDirentBcb;
|
|
PBCB OldDirentBcb;
|
|
PBCB SecondPageBcb;
|
|
PBCB TargetDirentBcb;
|
|
|
|
PDCB TargetDcb = NULL;
|
|
PDCB OldParentDcb;
|
|
|
|
PDIRENT DotDotDirent = NULL;
|
|
PDIRENT FirstPageDirent = NULL;
|
|
PDIRENT NewDirent = NULL;
|
|
PDIRENT OldDirent = NULL;
|
|
PDIRENT SecondPageDirent = NULL;
|
|
PDIRENT ShortDirent = NULL;
|
|
PDIRENT TargetDirent = NULL;
|
|
|
|
PFCB TempFcb;
|
|
|
|
PFILE_OBJECT TargetFileObject;
|
|
PFILE_OBJECT FileObject;
|
|
|
|
PIO_STACK_LOCATION IrpSp;
|
|
|
|
PLIST_ENTRY Links;
|
|
|
|
ULONG BytesInFirstPage = 0;
|
|
ULONG DirentsInFirstPage = 0;
|
|
ULONG DirentsRequired = 0;
|
|
ULONG NewOffset = 0;
|
|
ULONG NotifyAction = 0;
|
|
ULONG SecondPageOffset = 0;
|
|
ULONG ShortDirentOffset = 0;
|
|
ULONG TargetDirentOffset = 0;
|
|
ULONG TargetLfnOffset = 0;
|
|
|
|
// NewName comes from the IRP buffer or the TargetFileObject, so we can't
|
|
// go around modifying it. Instead we modify NewNameCopy.
|
|
UNICODE_STRING NewName;
|
|
|
|
// NB: these five UNICODE_STRINGS are allocated
|
|
// from one chopped up pool allocation called UnicodeBuffer.
|
|
UNICODE_STRING NewNameCopy;
|
|
UNICODE_STRING NewUpcasedName;
|
|
UNICODE_STRING OldName;
|
|
UNICODE_STRING OldUpcasedName;
|
|
UNICODE_STRING TargetLfn;
|
|
PWCHAR UnicodeBuffer;
|
|
|
|
UNICODE_STRING TargetOrigLfn = {0};
|
|
|
|
UNICODE_STRING UniTunneledShortName;
|
|
WCHAR UniTunneledShortNameBuffer[12];
|
|
UNICODE_STRING UniTunneledLongName;
|
|
WCHAR UniTunneledLongNameBuffer[26];
|
|
|
|
LARGE_INTEGER TunneledCreationTime;
|
|
ULONG TunneledDataSize;
|
|
BOOLEAN HaveTunneledInformation = FALSE;
|
|
BOOLEAN UsingTunneledLfn = FALSE;
|
|
|
|
BOOLEAN InvalidateFcbOnRaise = FALSE;
|
|
|
|
PFILE_OBJECT DirectoryFileObject = NULL;
|
|
ULONG Flags = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatSetRenameInfo...\n", 0);
|
|
|
|
//
|
|
// P H A S E 0: Initialize some variables.
|
|
//
|
|
|
|
CaseOnlyRename = FALSE;
|
|
ContinueWithRename = FALSE;
|
|
DeleteSourceDirent = FALSE;
|
|
DeleteTarget = FALSE;
|
|
NewDirentFromPool = FALSE;
|
|
RenamedAcrossDirectories = FALSE;
|
|
|
|
DotDotBcb = NULL;
|
|
NewDirentBcb = NULL;
|
|
OldDirentBcb = NULL;
|
|
SecondPageBcb = NULL;
|
|
TargetDirentBcb = NULL;
|
|
|
|
NewOemName.Length = 0;
|
|
NewOemName.MaximumLength = 24;
|
|
NewOemName.Buffer = (PCHAR)&OemNameBuffer[0];
|
|
|
|
OldOemName.Length = 0;
|
|
OldOemName.MaximumLength = 24;
|
|
OldOemName.Buffer = (PCHAR)&OemNameBuffer[24];
|
|
|
|
UnicodeBuffer = FsRtlAllocatePoolWithTag( PagedPool,
|
|
5 * MAX_LFN_CHARACTERS * sizeof(WCHAR),
|
|
TAG_FILENAME_BUFFER );
|
|
|
|
NewUpcasedName.Length = 0;
|
|
NewUpcasedName.MaximumLength = MAX_LFN_CHARACTERS * sizeof(WCHAR);
|
|
NewUpcasedName.Buffer = &UnicodeBuffer[0];
|
|
|
|
OldName.Length = 0;
|
|
OldName.MaximumLength = MAX_LFN_CHARACTERS * sizeof(WCHAR);
|
|
OldName.Buffer = &UnicodeBuffer[MAX_LFN_CHARACTERS];
|
|
|
|
OldUpcasedName.Length = 0;
|
|
OldUpcasedName.MaximumLength = MAX_LFN_CHARACTERS * sizeof(WCHAR);
|
|
OldUpcasedName.Buffer = &UnicodeBuffer[MAX_LFN_CHARACTERS * 2];
|
|
|
|
TargetLfn.Length = 0;
|
|
TargetLfn.MaximumLength = MAX_LFN_CHARACTERS * sizeof(WCHAR);
|
|
TargetLfn.Buffer = &UnicodeBuffer[MAX_LFN_CHARACTERS * 3];
|
|
|
|
NewNameCopy.Length = 0;
|
|
NewNameCopy.MaximumLength = MAX_LFN_CHARACTERS * sizeof(WCHAR);
|
|
NewNameCopy.Buffer = &UnicodeBuffer[MAX_LFN_CHARACTERS * 4];
|
|
|
|
UniTunneledShortName.Length = 0;
|
|
UniTunneledShortName.MaximumLength = sizeof(UniTunneledShortNameBuffer);
|
|
UniTunneledShortName.Buffer = &UniTunneledShortNameBuffer[0];
|
|
|
|
UniTunneledLongName.Length = 0;
|
|
UniTunneledLongName.MaximumLength = sizeof(UniTunneledLongNameBuffer);
|
|
UniTunneledLongName.Buffer = &UniTunneledLongNameBuffer[0];
|
|
|
|
//
|
|
// Remember the name in case we have to modify the name
|
|
// value in the ea.
|
|
//
|
|
|
|
RtlCopyMemory( OldOemName.Buffer,
|
|
Fcb->ShortName.Name.Oem.Buffer,
|
|
OldOemName.Length );
|
|
|
|
//
|
|
// Get the current stack location
|
|
//
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
|
|
//
|
|
// Extract information from the Irp to make our life easier
|
|
//
|
|
|
|
FileObject = IrpSp->FileObject;
|
|
SourceCcb = FileObject->FsContext2;
|
|
TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
|
|
ReplaceIfExists = IrpSp->Parameters.SetFile.ReplaceIfExists;
|
|
|
|
RtlZeroMemory( &LocalCcb, sizeof(CCB) );
|
|
|
|
//
|
|
// P H A S E 1:
|
|
//
|
|
// Test if rename is legal. Only small side-effects are not undone.
|
|
//
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Can't rename the root directory
|
|
//
|
|
|
|
if ( NodeType(Fcb) == FAT_NTC_ROOT_DCB ) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Check that we were not given a dcb with open handles beneath
|
|
// it. If there are only UncleanCount == 0 Fcbs beneath us, then
|
|
// remove them from the prefix table, and they will just close
|
|
// and go away naturally.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
PFCB BatchOplockFcb;
|
|
ULONG BatchOplockCount;
|
|
|
|
//
|
|
// Loop until there are no batch oplocks in the subtree below
|
|
// this directory.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
BatchOplockFcb = NULL;
|
|
BatchOplockCount = 0;
|
|
|
|
//
|
|
// First look for any UncleanCount != 0 Fcbs, and fail if we
|
|
// find any.
|
|
//
|
|
|
|
for ( TempFcb = FatGetNextFcbBottomUp(IrpContext, NULL, Fcb);
|
|
TempFcb != Fcb;
|
|
TempFcb = FatGetNextFcbBottomUp(IrpContext, TempFcb, Fcb) ) {
|
|
|
|
if ( TempFcb->UncleanCount != 0 ) {
|
|
|
|
//
|
|
// If there is a batch oplock on this file then
|
|
// increment our count and remember the Fcb if
|
|
// this is the first.
|
|
//
|
|
|
|
if (FatIsFileOplockable( TempFcb ) &&
|
|
(FsRtlCurrentBatchOplock( FatGetFcbOplock(TempFcb) )
|
|
#if (NTDDI_VERSION >= NTDDI_WIN7)
|
|
||
|
|
FsRtlCurrentOplockH( FatGetFcbOplock(TempFcb) )
|
|
#endif
|
|
)) {
|
|
|
|
BatchOplockCount += 1;
|
|
if ( BatchOplockFcb == NULL ) {
|
|
|
|
BatchOplockFcb = TempFcb;
|
|
}
|
|
|
|
} else {
|
|
|
|
try_return( Status = STATUS_ACCESS_DENIED );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If this is not the first pass for rename and the number
|
|
// of batch oplocks has not decreased then give up.
|
|
//
|
|
|
|
if ( BatchOplockFcb != NULL ) {
|
|
|
|
if ( (Irp->IoStatus.Information != 0) &&
|
|
(BatchOplockCount >= Irp->IoStatus.Information) ) {
|
|
|
|
try_return( Status = STATUS_ACCESS_DENIED );
|
|
}
|
|
|
|
//
|
|
// Try to break this batch oplock.
|
|
//
|
|
|
|
Irp->IoStatus.Information = BatchOplockCount;
|
|
Status = FsRtlCheckOplock( FatGetFcbOplock(BatchOplockFcb),
|
|
Irp,
|
|
IrpContext,
|
|
FatOplockComplete,
|
|
NULL );
|
|
|
|
//
|
|
// If the oplock was already broken then look for more
|
|
// batch oplocks.
|
|
//
|
|
|
|
if (Status == STATUS_SUCCESS) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Otherwise the oplock package will post or complete the
|
|
// request.
|
|
//
|
|
|
|
try_return( Status = STATUS_PENDING );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Now try to get as many of these file object, and thus Fcbs
|
|
// to go away as possible, flushing first, of course.
|
|
//
|
|
|
|
FatPurgeReferencedFileObjects( IrpContext, Fcb, Flush );
|
|
|
|
//
|
|
// OK, so there are no UncleanCount != 0, Fcbs. Infact, there
|
|
// shouldn't really be any Fcbs left at all, except obstinate
|
|
// ones from user mapped sections .... Remove the full file name
|
|
// and exact case lfn.
|
|
//
|
|
|
|
for ( TempFcb = FatGetNextFcbBottomUp(IrpContext, NULL, Fcb);
|
|
TempFcb != Fcb;
|
|
TempFcb = FatGetNextFcbBottomUp(IrpContext, TempFcb, Fcb) ) {
|
|
|
|
FatAcquireExclusiveFcb( IrpContext, TempFcb );
|
|
|
|
if (TempFcb->FullFileName.Buffer != NULL) {
|
|
|
|
ExFreePool( TempFcb->FullFileName.Buffer );
|
|
TempFcb->FullFileName.Buffer = NULL;
|
|
}
|
|
|
|
FatReleaseFcb( IrpContext, TempFcb );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if this is a simple rename or a fully-qualified rename
|
|
// In both cases we need to figure out what the TargetDcb, and
|
|
// NewName are.
|
|
//
|
|
|
|
if (TargetFileObject == NULL) {
|
|
|
|
//
|
|
// In the case of a simple rename the target dcb is the
|
|
// same as the source file's parent dcb, and the new file name
|
|
// is taken from the system buffer
|
|
//
|
|
|
|
PFILE_RENAME_INFORMATION Buffer;
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TargetDcb = Fcb->ParentDcb;
|
|
|
|
NewName.Length = (USHORT) Buffer->FileNameLength;
|
|
NewName.Buffer = (PWSTR) &Buffer->FileName;
|
|
|
|
//
|
|
// Make sure the name is of legal length.
|
|
//
|
|
|
|
if (NewName.Length > 255*sizeof(WCHAR)) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
RtlCopyUnicodeString(&NewNameCopy,&NewName);
|
|
|
|
} else {
|
|
|
|
//
|
|
// For a fully-qualified rename the target dcb is taken from
|
|
// the target file object, which must be on the same vcb as
|
|
// the source.
|
|
//
|
|
|
|
PVCB TargetVcb;
|
|
PCCB TargetCcb;
|
|
|
|
if ((FatDecodeFileObject( TargetFileObject,
|
|
&TargetVcb,
|
|
&TargetDcb,
|
|
&TargetCcb ) != UserDirectoryOpen) ||
|
|
(TargetVcb != Vcb)) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// This name is by definition legal.
|
|
//
|
|
|
|
NewName = *((PUNICODE_STRING)&TargetFileObject->FileName);
|
|
|
|
RtlCopyUnicodeString(&NewNameCopy,&NewName);
|
|
|
|
}
|
|
|
|
//
|
|
// We will need an upcased version of the unicode name and the
|
|
// old name as well.
|
|
//
|
|
|
|
Status = RtlUpcaseUnicodeString( &NewUpcasedName, &NewName, FALSE );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
try_return( Status );
|
|
}
|
|
|
|
FatGetUnicodeNameFromFcb( IrpContext, Fcb, &OldName );
|
|
|
|
Status = RtlUpcaseUnicodeString( &OldUpcasedName, &OldName, FALSE );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
//
|
|
// Check if the current name and new name are equal, and the
|
|
// DCBs are equal. If they are then our work is already done.
|
|
//
|
|
|
|
if (TargetDcb == Fcb->ParentDcb) {
|
|
|
|
//
|
|
// OK, now if we found something then check if it was an exact
|
|
// match or just a case match. If it was an exact match, then
|
|
// we can bail here.
|
|
//
|
|
|
|
if (FsRtlAreNamesEqual( &NewName,
|
|
&OldName,
|
|
FALSE,
|
|
NULL )) {
|
|
|
|
try_return( Status = STATUS_SUCCESS );
|
|
}
|
|
|
|
//
|
|
// Check now for a case only rename.
|
|
//
|
|
|
|
|
|
if (FsRtlAreNamesEqual( &NewUpcasedName,
|
|
&OldUpcasedName,
|
|
FALSE,
|
|
NULL )) {
|
|
|
|
CaseOnlyRename = TRUE;
|
|
}
|
|
|
|
} else {
|
|
|
|
RenamedAcrossDirectories = TRUE;
|
|
}
|
|
|
|
//
|
|
// Upcase the name and convert it to the Oem code page.
|
|
//
|
|
// If the new UNICODE name is already more than 12 characters,
|
|
// then we know the Oem name will not be valid
|
|
//
|
|
|
|
if (NewName.Length <= 12*sizeof(WCHAR)) {
|
|
|
|
FatUnicodeToUpcaseOem( IrpContext, &NewOemName, &NewName );
|
|
|
|
//
|
|
// If the name is not valid 8.3, zero the length.
|
|
//
|
|
|
|
if (FatSpaceInName( IrpContext, &NewName ) ||
|
|
!FatIsNameShortOemValid( IrpContext, NewOemName, FALSE, FALSE, FALSE)) {
|
|
|
|
NewOemName.Length = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
NewOemName.Length = 0;
|
|
}
|
|
|
|
//
|
|
// Look in the tunnel cache for names and timestamps to restore
|
|
//
|
|
|
|
TunneledDataSize = sizeof(LARGE_INTEGER);
|
|
HaveTunneledInformation = FsRtlFindInTunnelCache( &Vcb->Tunnel,
|
|
FatDirectoryKey(TargetDcb),
|
|
&NewName,
|
|
&UniTunneledShortName,
|
|
&UniTunneledLongName,
|
|
&TunneledDataSize,
|
|
&TunneledCreationTime );
|
|
NT_ASSERT(TunneledDataSize == sizeof(LARGE_INTEGER));
|
|
|
|
|
|
//
|
|
// Now we need to determine how many dirents this new name will
|
|
// require.
|
|
//
|
|
|
|
if ((NewOemName.Length == 0) ||
|
|
(FatEvaluateNameCase( IrpContext,
|
|
&NewNameCopy,
|
|
&AllLowerComponent,
|
|
&AllLowerExtension,
|
|
&CreateLfn ),
|
|
CreateLfn)) {
|
|
|
|
DirentsRequired = FAT_LFN_DIRENTS_NEEDED(&NewNameCopy) + 1;
|
|
|
|
} else {
|
|
|
|
//
|
|
// The user-given name is a short name, but we might still have
|
|
// a tunneled long name we want to use. See if we can.
|
|
//
|
|
|
|
if (UniTunneledLongName.Length &&
|
|
!FatLfnDirentExists(IrpContext, TargetDcb, &UniTunneledLongName, &TargetLfn)) {
|
|
|
|
UsingTunneledLfn = CreateLfn = TRUE;
|
|
DirentsRequired = FAT_LFN_DIRENTS_NEEDED(&UniTunneledLongName) + 1;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This really is a simple dirent. Note that the two AllLower BOOLEANs
|
|
// are correctly set now.
|
|
//
|
|
|
|
DirentsRequired = 1;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Do some extra checks here if we are not in Chicago mode.
|
|
//
|
|
|
|
if (!FatData.ChicagoMode) {
|
|
|
|
//
|
|
// If the name was not 8.3 valid, fail the rename.
|
|
//
|
|
|
|
if (NewOemName.Length == 0) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_INVALID );
|
|
}
|
|
|
|
//
|
|
// Don't use the magic bits.
|
|
//
|
|
|
|
AllLowerComponent = FALSE;
|
|
AllLowerExtension = FALSE;
|
|
CreateLfn = FALSE;
|
|
UsingTunneledLfn = FALSE;
|
|
}
|
|
|
|
if (!CaseOnlyRename) {
|
|
|
|
//
|
|
// Check if the new name already exists, wait is known to be
|
|
// true.
|
|
//
|
|
|
|
if (NewOemName.Length != 0) {
|
|
|
|
FatStringTo8dot3( IrpContext,
|
|
NewOemName,
|
|
&LocalCcb.OemQueryTemplate.Constant );
|
|
|
|
} else {
|
|
|
|
SetFlag( LocalCcb.Flags, CCB_FLAG_SKIP_SHORT_NAME_COMPARE );
|
|
}
|
|
|
|
LocalCcb.UnicodeQueryTemplate = NewUpcasedName;
|
|
LocalCcb.ContainsWildCards = FALSE;
|
|
|
|
Flags = 0;
|
|
|
|
|
|
FatLocateDirent( IrpContext,
|
|
TargetDcb,
|
|
&LocalCcb,
|
|
0,
|
|
&Flags,
|
|
&TargetDirent,
|
|
&TargetDirentBcb,
|
|
(PVBO)&TargetDirentOffset,
|
|
NULL,
|
|
&TargetLfn,
|
|
&TargetOrigLfn );
|
|
|
|
if (TargetDirent != NULL) {
|
|
|
|
|
|
//
|
|
// The name already exists, check if the user wants
|
|
// to overwrite the name, and has access to do the overwrite
|
|
// We cannot overwrite a directory.
|
|
//
|
|
|
|
if ((!ReplaceIfExists) ||
|
|
(FlagOn(TargetDirent->Attributes, FAT_DIRENT_ATTR_DIRECTORY)) ||
|
|
(FlagOn(TargetDirent->Attributes, FAT_DIRENT_ATTR_READ_ONLY))) {
|
|
|
|
try_return( Status = STATUS_OBJECT_NAME_COLLISION );
|
|
}
|
|
|
|
//
|
|
// Check that the file has no open user handles, if it does
|
|
// then we will deny access. We do the check by searching
|
|
// down the list of fcbs opened under our parent Dcb, and making
|
|
// sure none of the maching Fcbs have a non-zero unclean count or
|
|
// outstanding image sections.
|
|
//
|
|
|
|
for (Links = TargetDcb->Specific.Dcb.ParentDcbQueue.Flink;
|
|
Links != &TargetDcb->Specific.Dcb.ParentDcbQueue; ) {
|
|
|
|
TempFcb = CONTAINING_RECORD( Links, FCB, ParentDcbLinks );
|
|
|
|
//
|
|
// Advance now. The image section flush may cause the final
|
|
// close, which will recursively happen underneath of us here.
|
|
// It would be unfortunate if we looked through free memory.
|
|
//
|
|
|
|
Links = Links->Flink;
|
|
|
|
if ((TempFcb->DirentOffsetWithinDirectory == TargetDirentOffset) &&
|
|
((TempFcb->UncleanCount != 0) ||
|
|
!MmFlushImageSection( &TempFcb->NonPaged->SectionObjectPointers,
|
|
MmFlushForDelete))) {
|
|
|
|
//
|
|
// If there are batch oplocks on this file then break the
|
|
// oplocks before failing the rename.
|
|
//
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
|
|
if (FatIsFileOplockable( TempFcb ) &&
|
|
(FsRtlCurrentBatchOplock( FatGetFcbOplock(TempFcb) )
|
|
#if (NTDDI_VERSION >= NTDDI_WIN7)
|
|
||
|
|
FsRtlCurrentOplockH( FatGetFcbOplock(TempFcb) )
|
|
#endif
|
|
)) {
|
|
|
|
//
|
|
// Do all of our cleanup now since the IrpContext
|
|
// could go away when this request is posted.
|
|
//
|
|
|
|
FatUnpinBcb( IrpContext, TargetDirentBcb );
|
|
|
|
Status = FsRtlCheckOplock( FatGetFcbOplock(TempFcb),
|
|
Irp,
|
|
IrpContext,
|
|
FatOplockComplete,
|
|
NULL );
|
|
|
|
if (Status != STATUS_PENDING) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
}
|
|
}
|
|
|
|
try_return( NOTHING );
|
|
}
|
|
}
|
|
|
|
//
|
|
// OK, this target is toast. Remember the Lfn offset.
|
|
//
|
|
|
|
TargetLfnOffset = TargetDirentOffset -
|
|
FAT_LFN_DIRENTS_NEEDED(&TargetOrigLfn) *
|
|
sizeof(DIRENT);
|
|
|
|
DeleteTarget = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If we will need more dirents than we have, allocate them now.
|
|
//
|
|
|
|
if ((TargetDcb != Fcb->ParentDcb) ||
|
|
(DirentsRequired !=
|
|
(Fcb->DirentOffsetWithinDirectory -
|
|
Fcb->LfnOffsetWithinDirectory) / sizeof(DIRENT) + 1)) {
|
|
|
|
//
|
|
// Get some new allocation
|
|
//
|
|
|
|
NewOffset = FatCreateNewDirent( IrpContext,
|
|
TargetDcb,
|
|
DirentsRequired,
|
|
FALSE );
|
|
|
|
DeleteSourceDirent = TRUE;
|
|
|
|
} else {
|
|
|
|
NewOffset = Fcb->LfnOffsetWithinDirectory;
|
|
}
|
|
|
|
ContinueWithRename = TRUE;
|
|
|
|
try_exit: NOTHING;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
if (!ContinueWithRename) {
|
|
|
|
//
|
|
// Undo everything from above.
|
|
//
|
|
|
|
ExFreePool( UnicodeBuffer );
|
|
FatUnpinBcb( IrpContext, TargetDirentBcb );
|
|
|
|
}
|
|
} _SEH2_END;
|
|
|
|
//
|
|
// Now, if we are already done, return here.
|
|
//
|
|
|
|
if (!ContinueWithRename) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// P H A S E 2: Actually perform the rename.
|
|
//
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Report the fact that we are going to remove this entry.
|
|
// If we renamed within the same directory and the new name for the
|
|
// file did not previously exist, we report this as a rename old
|
|
// name. Otherwise this is a removed file.
|
|
//
|
|
|
|
if (!RenamedAcrossDirectories && !DeleteTarget) {
|
|
|
|
NotifyAction = FILE_ACTION_RENAMED_OLD_NAME;
|
|
|
|
} else {
|
|
|
|
NotifyAction = FILE_ACTION_REMOVED;
|
|
}
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
Fcb,
|
|
((NodeType( Fcb ) == FAT_NTC_FCB)
|
|
? FILE_NOTIFY_CHANGE_FILE_NAME
|
|
: FILE_NOTIFY_CHANGE_DIR_NAME ),
|
|
NotifyAction );
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Capture a copy of the source dirent.
|
|
//
|
|
|
|
FatGetDirentFromFcbOrDcb( IrpContext, Fcb, FALSE, &OldDirent, &OldDirentBcb );
|
|
|
|
Dirent = *OldDirent;
|
|
|
|
//
|
|
// Tunnel the source Fcb - the names are disappearing regardless of
|
|
// whether the dirent allocation physically changed
|
|
//
|
|
|
|
FatTunnelFcbOrDcb( Fcb, SourceCcb );
|
|
|
|
//
|
|
// From here until very nearly the end of the operation, if we raise there
|
|
// is no reasonable way to suppose we'd be able to undo the damage. Not
|
|
// being a transactional filesystem, FAT is at the mercy of a lot of things
|
|
// (as the astute reader has no doubt realized by now).
|
|
//
|
|
|
|
InvalidateFcbOnRaise = TRUE;
|
|
|
|
//
|
|
// Delete our current dirent(s) if we got a new one.
|
|
//
|
|
|
|
if (DeleteSourceDirent) {
|
|
|
|
FatDeleteDirent( IrpContext, Fcb, NULL, FALSE );
|
|
}
|
|
|
|
//
|
|
// Delete a target conflict if we were meant to.
|
|
//
|
|
|
|
if (DeleteTarget) {
|
|
|
|
FatDeleteFile( IrpContext,
|
|
TargetDcb,
|
|
TargetLfnOffset,
|
|
TargetDirentOffset,
|
|
TargetDirent,
|
|
&TargetLfn );
|
|
}
|
|
|
|
//
|
|
// We need to evaluate any short names required. If there were any
|
|
// conflicts in existing short names, they would have been deleted above.
|
|
//
|
|
// It isn't neccesary to worry about the UsingTunneledLfn case. Since we
|
|
// actually already know whether CreateLfn will be set either NewName is
|
|
// an Lfn and !UsingTunneledLfn is implied or NewName is a short name and
|
|
// we can handle that externally.
|
|
//
|
|
|
|
FatSelectNames( IrpContext,
|
|
TargetDcb,
|
|
&NewOemName,
|
|
&NewNameCopy,
|
|
&NewOemName,
|
|
(HaveTunneledInformation ? &UniTunneledShortName : NULL),
|
|
&AllLowerComponent,
|
|
&AllLowerExtension,
|
|
&CreateLfn );
|
|
|
|
if (!CreateLfn && UsingTunneledLfn) {
|
|
|
|
CreateLfn = TRUE;
|
|
RtlCopyUnicodeString( &NewNameCopy, &UniTunneledLongName );
|
|
|
|
//
|
|
// Short names are always upcase if an LFN exists
|
|
//
|
|
|
|
AllLowerComponent = FALSE;
|
|
AllLowerExtension = FALSE;
|
|
}
|
|
|
|
//
|
|
// OK, now setup the new dirent(s) for the new name.
|
|
//
|
|
|
|
FatPrepareWriteDirectoryFile( IrpContext,
|
|
TargetDcb,
|
|
NewOffset,
|
|
sizeof(DIRENT),
|
|
&NewDirentBcb,
|
|
#ifndef __REACTOS__
|
|
&NewDirent,
|
|
#else
|
|
(PVOID *)&NewDirent,
|
|
#endif
|
|
FALSE,
|
|
TRUE,
|
|
&Status );
|
|
|
|
NT_ASSERT( NT_SUCCESS( Status ) );
|
|
|
|
//
|
|
// Deal with the special case of an LFN + Dirent structure crossing
|
|
// a page boundry.
|
|
//
|
|
|
|
if ((NewOffset / PAGE_SIZE) !=
|
|
((NewOffset + (DirentsRequired - 1) * sizeof(DIRENT)) / PAGE_SIZE)) {
|
|
|
|
SecondPageOffset = (NewOffset & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
|
|
|
|
BytesInFirstPage = SecondPageOffset - NewOffset;
|
|
|
|
DirentsInFirstPage = BytesInFirstPage / sizeof(DIRENT);
|
|
|
|
FatPrepareWriteDirectoryFile( IrpContext,
|
|
TargetDcb,
|
|
SecondPageOffset,
|
|
sizeof(DIRENT),
|
|
&SecondPageBcb,
|
|
#ifndef __REACTOS__
|
|
&SecondPageDirent,
|
|
#else
|
|
(PVOID *)&SecondPageDirent,
|
|
#endif
|
|
FALSE,
|
|
TRUE,
|
|
&Status );
|
|
|
|
NT_ASSERT( NT_SUCCESS( Status ) );
|
|
|
|
FirstPageDirent = NewDirent;
|
|
|
|
NewDirent = FsRtlAllocatePoolWithTag( PagedPool,
|
|
DirentsRequired * sizeof(DIRENT),
|
|
TAG_DIRENT );
|
|
|
|
NewDirentFromPool = TRUE;
|
|
}
|
|
|
|
//
|
|
// Bump up Dirent and DirentOffset
|
|
//
|
|
|
|
ShortDirent = NewDirent + DirentsRequired - 1;
|
|
ShortDirentOffset = NewOffset + (DirentsRequired - 1) * sizeof(DIRENT);
|
|
|
|
//
|
|
// Fill in the fields of the dirent.
|
|
//
|
|
|
|
*ShortDirent = Dirent;
|
|
|
|
FatConstructDirent( IrpContext,
|
|
ShortDirent,
|
|
&NewOemName,
|
|
AllLowerComponent,
|
|
AllLowerExtension,
|
|
CreateLfn ? &NewNameCopy : NULL,
|
|
Dirent.Attributes,
|
|
FALSE,
|
|
(HaveTunneledInformation ? &TunneledCreationTime : NULL) );
|
|
|
|
if (HaveTunneledInformation) {
|
|
|
|
//
|
|
// Need to go in and fix the timestamps in the FCB. Note that we can't use
|
|
// the TunneledCreationTime since the conversions may have failed.
|
|
//
|
|
|
|
Fcb->CreationTime = FatFatTimeToNtTime(IrpContext, ShortDirent->CreationTime, ShortDirent->CreationMSec);
|
|
Fcb->LastWriteTime = FatFatTimeToNtTime(IrpContext, ShortDirent->LastWriteTime, 0);
|
|
Fcb->LastAccessTime = FatFatDateToNtTime(IrpContext, ShortDirent->LastAccessDate);
|
|
}
|
|
|
|
|
|
//
|
|
// If the dirent crossed pages, split the contents of the
|
|
// temporary pool between the two pages.
|
|
//
|
|
|
|
if (NewDirentFromPool) {
|
|
|
|
RtlCopyMemory( FirstPageDirent, NewDirent, BytesInFirstPage );
|
|
|
|
RtlCopyMemory( SecondPageDirent,
|
|
NewDirent + DirentsInFirstPage,
|
|
DirentsRequired*sizeof(DIRENT) - BytesInFirstPage );
|
|
|
|
ShortDirent = SecondPageDirent +
|
|
(DirentsRequired - DirentsInFirstPage) - 1;
|
|
}
|
|
|
|
Dirent = *ShortDirent;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
//
|
|
// Remove the entry from the splay table, and then remove the
|
|
// full file name and exact case lfn. It is important that we
|
|
// always remove the name from the prefix table regardless of
|
|
// other errors.
|
|
//
|
|
|
|
FatRemoveNames( IrpContext, Fcb );
|
|
|
|
if (Fcb->FullFileName.Buffer != NULL) {
|
|
|
|
ExFreePool( Fcb->FullFileName.Buffer );
|
|
Fcb->FullFileName.Buffer = NULL;
|
|
}
|
|
|
|
if (Fcb->ExactCaseLongName.Buffer) {
|
|
|
|
ExFreePool( Fcb->ExactCaseLongName.Buffer );
|
|
Fcb->ExactCaseLongName.Buffer = NULL;
|
|
}
|
|
|
|
FatUnpinBcb( IrpContext, OldDirentBcb );
|
|
FatUnpinBcb( IrpContext, TargetDirentBcb );
|
|
FatUnpinBcb( IrpContext, NewDirentBcb );
|
|
FatUnpinBcb( IrpContext, SecondPageBcb );
|
|
} _SEH2_END;
|
|
|
|
//
|
|
// Now we need to update the location of the file's directory
|
|
// offset and move the fcb from its current parent dcb to
|
|
// the target dcb.
|
|
//
|
|
|
|
Fcb->LfnOffsetWithinDirectory = NewOffset;
|
|
Fcb->DirentOffsetWithinDirectory = ShortDirentOffset;
|
|
|
|
RemoveEntryList( &Fcb->ParentDcbLinks );
|
|
|
|
//
|
|
// There is a deep reason we put files on the tail, others on the head,
|
|
// which is to allow us to easily enumerate all child directories before
|
|
// child files. This is important to let us maintain whole-volume lockorder
|
|
// via BottomUp enumeration.
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_FCB) {
|
|
|
|
InsertTailList( &TargetDcb->Specific.Dcb.ParentDcbQueue,
|
|
&Fcb->ParentDcbLinks );
|
|
|
|
} else {
|
|
|
|
InsertHeadList( &TargetDcb->Specific.Dcb.ParentDcbQueue,
|
|
&Fcb->ParentDcbLinks );
|
|
}
|
|
|
|
OldParentDcb = Fcb->ParentDcb;
|
|
Fcb->ParentDcb = TargetDcb;
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
//
|
|
// Break parent directory oplock on the old parent. Directory oplock
|
|
// breaks are always advisory, so we will never block/get STATUS_PENDING
|
|
// here.
|
|
//
|
|
|
|
FsRtlCheckOplockEx( FatGetFcbOplock(OldParentDcb),
|
|
IrpContext->OriginatingIrp,
|
|
OPLOCK_FLAG_PARENT_OBJECT,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
#endif
|
|
|
|
//
|
|
// If we renamed across directories, some cleanup is now in order.
|
|
//
|
|
|
|
if (RenamedAcrossDirectories) {
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
//
|
|
// Break parent directory oplock on the new parent. Directory oplock
|
|
// breaks are always advisory, so we will never block/get STATUS_PENDING
|
|
// here.
|
|
//
|
|
|
|
FsRtlCheckOplockEx( FatGetFcbOplock(TargetDcb),
|
|
IrpContext->OriginatingIrp,
|
|
OPLOCK_FLAG_PARENT_OBJECT,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
#endif
|
|
|
|
//
|
|
// See if we need to uninitialize the cachemap for the source directory.
|
|
// Do this now in case we get unlucky and raise trying to finalize the
|
|
// operation.
|
|
//
|
|
|
|
if (IsListEmpty(&OldParentDcb->Specific.Dcb.ParentDcbQueue) &&
|
|
(OldParentDcb->OpenCount == 0) &&
|
|
(OldParentDcb->Specific.Dcb.DirectoryFile != NULL)) {
|
|
|
|
NT_ASSERT( NodeType(OldParentDcb) == FAT_NTC_DCB );
|
|
|
|
DirectoryFileObject = OldParentDcb->Specific.Dcb.DirectoryFile;
|
|
|
|
OldParentDcb->Specific.Dcb.DirectoryFile = NULL;
|
|
}
|
|
|
|
//
|
|
// If we move a directory across directories, we have to change
|
|
// the cluster number in its .. entry
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
FatPrepareWriteDirectoryFile( IrpContext,
|
|
Fcb,
|
|
sizeof(DIRENT),
|
|
sizeof(DIRENT),
|
|
&DotDotBcb,
|
|
#ifndef __REACTOS__
|
|
&DotDotDirent,
|
|
#else
|
|
(PVOID *)&DotDotDirent,
|
|
#endif
|
|
FALSE,
|
|
TRUE,
|
|
&Status );
|
|
|
|
NT_ASSERT( NT_SUCCESS( Status ) );
|
|
|
|
DotDotDirent->FirstClusterOfFile = (USHORT)
|
|
( NodeType(TargetDcb) == FAT_NTC_ROOT_DCB ?
|
|
0 : TargetDcb->FirstClusterOfFile);
|
|
|
|
if (FatIsFat32( Vcb )) {
|
|
|
|
DotDotDirent->FirstClusterOfFileHi = (USHORT)
|
|
( NodeType( TargetDcb ) == FAT_NTC_ROOT_DCB ?
|
|
0 : (TargetDcb->FirstClusterOfFile >> 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we need to setup the splay table and the name within
|
|
// the fcb. Free the old short name at this point.
|
|
//
|
|
|
|
ExFreePool( Fcb->ShortName.Name.Oem.Buffer );
|
|
Fcb->ShortName.Name.Oem.Buffer = NULL;
|
|
|
|
|
|
FatConstructNamesInFcb( IrpContext,
|
|
Fcb,
|
|
&Dirent,
|
|
CreateLfn ? &NewName : NULL );
|
|
|
|
FatSetFullNameInFcb( IrpContext, Fcb, &NewName );
|
|
|
|
//
|
|
// The rest of the actions taken are not related to correctness of
|
|
// the in-memory structures, so we shouldn't toast the Fcb if we
|
|
// raise from here to the end.
|
|
//
|
|
|
|
InvalidateFcbOnRaise = FALSE;
|
|
|
|
//
|
|
// If a file, set the file as modified so that the archive bit
|
|
// is set. We prevent this from adjusting the write time by
|
|
// indicating the user flag in the ccb.
|
|
//
|
|
|
|
if (Fcb->Header.NodeTypeCode == FAT_NTC_FCB) {
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
|
|
SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_WRITE );
|
|
}
|
|
|
|
//
|
|
// We have three cases to report.
|
|
//
|
|
// 1. If we overwrote an existing file, we report this as
|
|
// a modified file.
|
|
//
|
|
// 2. If we renamed to a new directory, then we added a file.
|
|
//
|
|
// 3. If we renamed in the same directory, then we report the
|
|
// the renamednewname.
|
|
//
|
|
|
|
if (DeleteTarget) {
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
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 if (RenamedAcrossDirectories) {
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
Fcb,
|
|
((NodeType( Fcb ) == FAT_NTC_FCB)
|
|
? FILE_NOTIFY_CHANGE_FILE_NAME
|
|
: FILE_NOTIFY_CHANGE_DIR_NAME ),
|
|
FILE_ACTION_ADDED );
|
|
|
|
} else {
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
Fcb,
|
|
((NodeType( Fcb ) == FAT_NTC_FCB)
|
|
? FILE_NOTIFY_CHANGE_FILE_NAME
|
|
: FILE_NOTIFY_CHANGE_DIR_NAME ),
|
|
FILE_ACTION_RENAMED_NEW_NAME );
|
|
}
|
|
|
|
//
|
|
// We need to update the file name in the dirent. This value
|
|
// is never used elsewhere, so we don't concern ourselves
|
|
// with any error we may encounter. We let chkdsk fix the
|
|
// disk at some later time.
|
|
//
|
|
|
|
if (!FatIsFat32(Vcb) &&
|
|
Dirent.ExtendedAttributes != 0) {
|
|
|
|
FatRenameEAs( IrpContext,
|
|
Fcb,
|
|
Dirent.ExtendedAttributes,
|
|
&OldOemName );
|
|
}
|
|
|
|
FatUnpinBcb( IrpContext, DotDotBcb );
|
|
|
|
FatUnpinRepinnedBcbs( IrpContext );
|
|
|
|
//
|
|
// Set our final status
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatSetRenameInfo );
|
|
|
|
ExFreePool( UnicodeBuffer );
|
|
|
|
if (UniTunneledLongName.Buffer != UniTunneledLongNameBuffer) {
|
|
|
|
//
|
|
// Free pool if the buffer was grown on tunneling lookup
|
|
//
|
|
|
|
ExFreePool(UniTunneledLongName.Buffer);
|
|
}
|
|
|
|
if (NewDirentFromPool) {
|
|
|
|
ExFreePool( NewDirent );
|
|
}
|
|
|
|
FatUnpinBcb( IrpContext, TargetDirentBcb );
|
|
FatUnpinBcb( IrpContext, DotDotBcb );
|
|
|
|
//
|
|
// Uninitialize the cachemap for the source directory if we need to.
|
|
//
|
|
|
|
if (DirectoryFileObject) {
|
|
|
|
DebugTrace(0, Dbg, "Uninitialize our parent Stream Cache Map\n", 0);
|
|
|
|
CcUninitializeCacheMap( DirectoryFileObject, NULL, NULL );
|
|
|
|
ObDereferenceObject( DirectoryFileObject );
|
|
}
|
|
|
|
//
|
|
// If this was an abnormal termination, then we are in trouble.
|
|
// Should the operation have been in a sensitive state there is
|
|
// nothing we can do but invalidate the Fcb.
|
|
//
|
|
|
|
if (_SEH2_AbnormalTermination() && InvalidateFcbOnRaise) {
|
|
|
|
Fcb->FcbCondition = FcbBad;
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatSetRenameInfo -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
NTSTATUS
|
|
FatSetPositionInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set position information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFILE_POSITION_INFORMATION Buffer;
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatSetPositionInfo...\n", 0);
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
//
|
|
// Check if the file does not use intermediate buffering. If it
|
|
// does not use intermediate buffering then the new position we're
|
|
// supplied must be aligned properly for the device
|
|
//
|
|
|
|
if (FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING )) {
|
|
|
|
PDEVICE_OBJECT DeviceObject;
|
|
|
|
DeviceObject = IoGetCurrentIrpStackLocation( Irp )->DeviceObject;
|
|
|
|
if ((Buffer->CurrentByteOffset.LowPart & DeviceObject->AlignmentRequirement) != 0) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot set position due to aligment conflict\n", 0);
|
|
DebugTrace(-1, Dbg, "FatSetPositionInfo -> %08lx\n", STATUS_INVALID_PARAMETER);
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The input parameter is fine so set the current byte offset and
|
|
// complete the request
|
|
//
|
|
|
|
DebugTrace(0, Dbg, "Set the new position to %08lx\n", Buffer->CurrentByteOffset);
|
|
|
|
FileObject->CurrentByteOffset = Buffer->CurrentByteOffset;
|
|
|
|
DebugTrace(-1, Dbg, "FatSetPositionInfo -> %08lx\n", STATUS_SUCCESS);
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetAllocationInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFCB Fcb,
|
|
IN PFILE_OBJECT FileObject
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set Allocation information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
FileObject - Supplies the FileObject being processed, already known not to
|
|
be the root dcb
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PFILE_ALLOCATION_INFORMATION Buffer;
|
|
ULONG NewAllocationSize = 0;
|
|
ULONG HeaderSize = 0;
|
|
|
|
BOOLEAN FileSizeTruncated = FALSE;
|
|
BOOLEAN CacheMapInitialized = FALSE;
|
|
BOOLEAN ResourceAcquired = FALSE;
|
|
ULONG OriginalFileSize = 0;
|
|
ULONG OriginalValidDataLength = 0;
|
|
ULONG OriginalValidDataToDisk = 0;
|
|
|
|
PAGED_CODE();
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
NewAllocationSize = Buffer->AllocationSize.LowPart;
|
|
|
|
DebugTrace(+1, Dbg, "FatSetAllocationInfo.. to %08lx\n", NewAllocationSize);
|
|
|
|
//
|
|
// Allocation is only allowed on a file and not a directory
|
|
//
|
|
|
|
if (NodeType(Fcb) == FAT_NTC_DCB) {
|
|
|
|
DebugTrace(-1, Dbg, "Cannot change allocation of a directory\n", 0);
|
|
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
|
|
//
|
|
// Check that the new file allocation is legal
|
|
//
|
|
|
|
|
|
if (!FatIsIoRangeValid( Fcb->Vcb, Buffer->AllocationSize, 0)) {
|
|
|
|
DebugTrace(-1, Dbg, "Illegal allocation size\n", 0);
|
|
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
|
|
//
|
|
// If we haven't yet looked up the correct AllocationSize, do so.
|
|
//
|
|
|
|
if (Fcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) {
|
|
|
|
FatLookupFileAllocationSize( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// This is kinda gross, but if the file is not cached, but there is
|
|
// a data section, we have to cache the file to avoid a bunch of
|
|
// extra work.
|
|
//
|
|
|
|
if ((FileObject->SectionObjectPointer->DataSectionObject != NULL) &&
|
|
(FileObject->SectionObjectPointer->SharedCacheMap == NULL) &&
|
|
!FlagOn(Irp->Flags, IRP_PAGING_IO)) {
|
|
|
|
NT_ASSERT( !FlagOn( FileObject->Flags, FO_CLEANUP_COMPLETE ) );
|
|
|
|
//
|
|
// Now initialize the cache map.
|
|
//
|
|
|
|
FatInitializeCacheMap( FileObject,
|
|
(PCC_FILE_SIZES)&Fcb->Header.AllocationSize,
|
|
FALSE,
|
|
&FatData.CacheManagerCallbacks,
|
|
Fcb );
|
|
|
|
CacheMapInitialized = TRUE;
|
|
}
|
|
|
|
//
|
|
// Now mark the fact that the file needs to be truncated on close
|
|
//
|
|
|
|
Fcb->FcbState |= FCB_STATE_TRUNCATE_ON_CLOSE;
|
|
|
|
//
|
|
// Now mark that the time on the dirent needs to be updated on close.
|
|
//
|
|
|
|
SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Increase or decrease the allocation size.
|
|
//
|
|
|
|
if (NewAllocationSize+HeaderSize > Fcb->Header.AllocationSize.LowPart) {
|
|
|
|
FatAddFileAllocation( IrpContext, Fcb, FileObject, NewAllocationSize+HeaderSize);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Check here if we will be decreasing file size and synchonize with
|
|
// paging IO.
|
|
//
|
|
|
|
if ( Fcb->Header.FileSize.LowPart > NewAllocationSize+HeaderSize ) {
|
|
|
|
//
|
|
// The way Sections for DataScan are created and used, an AV's
|
|
// memory-mapping can come into being after we check it's safe
|
|
// to truncate a file while continuing to hold the file here.
|
|
// This leads to data corruption because Purge eventually fails
|
|
// (during call to Cc to set file sizes) and stale data continues
|
|
// to live in the cache/memory.
|
|
//
|
|
|
|
if (Fcb->PurgeFailureModeEnableCount != 0) {
|
|
|
|
try_return( Status = STATUS_PURGE_FAILED );
|
|
}
|
|
|
|
//
|
|
// Before we actually truncate, check to see if the purge
|
|
// is going to fail.
|
|
//
|
|
|
|
if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
|
|
&Buffer->AllocationSize )) {
|
|
|
|
try_return( Status = STATUS_USER_MAPPED_FILE );
|
|
}
|
|
|
|
FileSizeTruncated = TRUE;
|
|
|
|
OriginalFileSize = Fcb->Header.FileSize.LowPart;
|
|
OriginalValidDataLength = Fcb->Header.ValidDataLength.LowPart;
|
|
OriginalValidDataToDisk = Fcb->ValidDataToDisk;
|
|
|
|
(VOID)ExAcquireResourceExclusiveLite( Fcb->Header.PagingIoResource, TRUE );
|
|
ResourceAcquired = TRUE;
|
|
|
|
Fcb->Header.FileSize.LowPart = NewAllocationSize;
|
|
|
|
//
|
|
// If we reduced the file size to less than the ValidDataLength,
|
|
// adjust the VDL. Likewise ValidDataToDisk.
|
|
//
|
|
|
|
if (Fcb->Header.ValidDataLength.LowPart > Fcb->Header.FileSize.LowPart) {
|
|
|
|
Fcb->Header.ValidDataLength.LowPart = Fcb->Header.FileSize.LowPart;
|
|
}
|
|
if (Fcb->ValidDataToDisk > Fcb->Header.FileSize.LowPart) {
|
|
|
|
Fcb->ValidDataToDisk = Fcb->Header.FileSize.LowPart;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Now that File Size is down, actually do the truncate.
|
|
//
|
|
|
|
FatTruncateFileAllocation( IrpContext, Fcb, NewAllocationSize+HeaderSize );
|
|
|
|
//
|
|
// Now check if we needed to decrease the file size accordingly.
|
|
//
|
|
|
|
if ( FileSizeTruncated ) {
|
|
|
|
//
|
|
// Tell the cache manager we reduced the file size.
|
|
// The call is unconditional, because MM always wants to know.
|
|
//
|
|
|
|
#if DBG
|
|
_SEH2_TRY {
|
|
#endif
|
|
|
|
CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Fcb->Header.AllocationSize );
|
|
|
|
#if DBG
|
|
} _SEH2_EXCEPT(FatBugCheckExceptionFilter( _SEH2_GetExceptionInformation() )) {
|
|
|
|
NOTHING;
|
|
} _SEH2_END;
|
|
#endif
|
|
|
|
NT_ASSERT( FileObject->DeleteAccess || FileObject->WriteAccess );
|
|
|
|
//
|
|
// There is no going back from this. If we run into problems updating
|
|
// the dirent we will have to live with the consequences. Not sending
|
|
// the notifies is likewise pretty benign compared to failing the entire
|
|
// operation and trying to back out everything, which could fail for the
|
|
// same reasons.
|
|
//
|
|
// If you want a transacted filesystem, use NTFS ...
|
|
//
|
|
|
|
FileSizeTruncated = FALSE;
|
|
|
|
FatSetFileSizeInDirent( IrpContext, Fcb, NULL );
|
|
|
|
//
|
|
// Report that we just reduced the file size.
|
|
//
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Fcb->Vcb,
|
|
Fcb,
|
|
FILE_NOTIFY_CHANGE_SIZE,
|
|
FILE_ACTION_MODIFIED );
|
|
}
|
|
}
|
|
|
|
try_exit: NOTHING;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
if ( _SEH2_AbnormalTermination() && FileSizeTruncated ) {
|
|
|
|
Fcb->Header.FileSize.LowPart = OriginalFileSize;
|
|
Fcb->Header.ValidDataLength.LowPart = OriginalValidDataLength;
|
|
Fcb->ValidDataToDisk = OriginalValidDataToDisk;
|
|
|
|
//
|
|
// Make sure Cc knows the right filesize.
|
|
//
|
|
|
|
if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
|
|
|
|
*CcGetFileSizePointer(FileObject) = Fcb->Header.FileSize;
|
|
}
|
|
|
|
NT_ASSERT( Fcb->Header.FileSize.LowPart <= Fcb->Header.AllocationSize.LowPart );
|
|
}
|
|
|
|
if (CacheMapInitialized) {
|
|
|
|
CcUninitializeCacheMap( FileObject, NULL, NULL );
|
|
}
|
|
|
|
if (ResourceAcquired) {
|
|
|
|
ExReleaseResourceLite( Fcb->Header.PagingIoResource );
|
|
|
|
}
|
|
|
|
} _SEH2_END;
|
|
|
|
DebugTrace(-1, Dbg, "FatSetAllocationInfo -> %08lx\n", STATUS_SUCCESS);
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetEndOfFileInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PVCB Vcb,
|
|
IN PFCB Fcb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set End of File information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Vcb - Supplies the Vcb being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PFILE_END_OF_FILE_INFORMATION Buffer;
|
|
|
|
ULONG NewFileSize = 0;
|
|
ULONG InitialFileSize = 0;
|
|
ULONG InitialValidDataLength = 0;
|
|
ULONG InitialValidDataToDisk = 0;
|
|
|
|
BOOLEAN CacheMapInitialized = FALSE;
|
|
BOOLEAN UnwindFileSizes = FALSE;
|
|
BOOLEAN ResourceAcquired = FALSE;
|
|
|
|
|
|
PAGED_CODE();
|
|
|
|
DebugTrace(+1, Dbg, "FatSetEndOfFileInfo...\n", 0);
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// File Size changes are only allowed on a file and not a directory
|
|
//
|
|
|
|
if (NodeType(Fcb) != FAT_NTC_FCB) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot change size of a directory\n", 0);
|
|
|
|
try_return( Status = STATUS_INVALID_DEVICE_REQUEST );
|
|
}
|
|
|
|
|
|
//
|
|
// Check that the new file size is legal
|
|
//
|
|
|
|
|
|
if (!FatIsIoRangeValid( Fcb->Vcb, Buffer->EndOfFile, 0)) {
|
|
|
|
DebugTrace(0, Dbg, "Illegal allocation size\n", 0);
|
|
|
|
try_return( Status = STATUS_DISK_FULL );
|
|
}
|
|
|
|
NewFileSize = Buffer->EndOfFile.LowPart;
|
|
|
|
|
|
//
|
|
// If we haven't yet looked up the correct AllocationSize, do so.
|
|
//
|
|
|
|
if ((Fcb->Header.AllocationSize.QuadPart == FCB_LOOKUP_ALLOCATIONSIZE_HINT) &&
|
|
(Fcb->FcbCondition == FcbGood)) {
|
|
|
|
FatLookupFileAllocationSize( IrpContext, Fcb );
|
|
}
|
|
|
|
//
|
|
// This is kinda gross, but if the file is not cached, but there is
|
|
// a data section, we have to cache the file to avoid a bunch of
|
|
// extra work.
|
|
//
|
|
|
|
if ((FileObject->SectionObjectPointer->DataSectionObject != NULL) &&
|
|
(FileObject->SectionObjectPointer->SharedCacheMap == NULL) &&
|
|
!FlagOn(Irp->Flags, IRP_PAGING_IO)) {
|
|
|
|
if (FlagOn( FileObject->Flags, FO_CLEANUP_COMPLETE )) {
|
|
|
|
//
|
|
// This IRP has raced (and lost) with a close (=>cleanup)
|
|
// on the same fileobject. We don't want to reinitialise the
|
|
// cachemap here now because we'll leak it (unless we do so &
|
|
// then tear it down again here, which is too much of a change at
|
|
// this stage). So we'll just say the file is closed - which
|
|
// is arguably the right thing to do anyway, since a caller
|
|
// racing operations in this way is broken. The only stumbling
|
|
// block is possibly filters - do they operate on cleaned
|
|
// up fileobjects?
|
|
//
|
|
|
|
FatRaiseStatus( IrpContext, STATUS_FILE_CLOSED);
|
|
}
|
|
|
|
//
|
|
// Now initialize the cache map.
|
|
//
|
|
|
|
FatInitializeCacheMap( FileObject,
|
|
(PCC_FILE_SIZES)&Fcb->Header.AllocationSize,
|
|
FALSE,
|
|
&FatData.CacheManagerCallbacks,
|
|
Fcb );
|
|
|
|
CacheMapInitialized = TRUE;
|
|
}
|
|
|
|
//
|
|
// Do a special case here for the lazy write of file sizes.
|
|
//
|
|
|
|
if (IoGetCurrentIrpStackLocation(Irp)->Parameters.SetFile.AdvanceOnly) {
|
|
|
|
//
|
|
// Only attempt this if the file hasn't been "deleted on close" and
|
|
// this is a good FCB.
|
|
//
|
|
|
|
if (!IsFileDeleted( IrpContext, Fcb ) && (Fcb->FcbCondition == FcbGood)) {
|
|
|
|
PDIRENT Dirent = NULL;
|
|
PBCB DirentBcb;
|
|
|
|
|
|
//
|
|
// Never have the dirent filesize larger than the fcb filesize
|
|
//
|
|
|
|
if (NewFileSize >= Fcb->Header.FileSize.LowPart) {
|
|
|
|
NewFileSize = Fcb->Header.FileSize.LowPart;
|
|
}
|
|
|
|
//
|
|
// Make sure we don't set anything higher than the alloc size.
|
|
//
|
|
|
|
|
|
NT_ASSERT( NewFileSize <= Fcb->Header.AllocationSize.LowPart );
|
|
|
|
|
|
//
|
|
// Only advance the file size, never reduce it with this call
|
|
//
|
|
|
|
FatGetDirentFromFcbOrDcb( IrpContext,
|
|
Fcb,
|
|
FALSE,
|
|
&Dirent,
|
|
&DirentBcb );
|
|
_SEH2_TRY {
|
|
|
|
|
|
if ( NewFileSize > Dirent->FileSize ) {
|
|
Dirent->FileSize = NewFileSize;
|
|
|
|
FatSetDirtyBcb( IrpContext, DirentBcb, Fcb->Vcb, TRUE );
|
|
|
|
//
|
|
// Report that we just changed the file size.
|
|
//
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
Fcb,
|
|
FILE_NOTIFY_CHANGE_SIZE,
|
|
FILE_ACTION_MODIFIED );
|
|
|
|
}
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
FatUnpinBcb( IrpContext, DirentBcb );
|
|
} _SEH2_END;
|
|
|
|
} else {
|
|
|
|
DebugTrace(0, Dbg, "Cannot set size on deleted file.\n", 0);
|
|
}
|
|
|
|
try_return( Status = STATUS_SUCCESS );
|
|
}
|
|
|
|
//
|
|
// Check if the new file size is greater than the current
|
|
// allocation size. If it is then we need to increase the
|
|
// allocation size.
|
|
//
|
|
|
|
|
|
if ( (NewFileSize) > Fcb->Header.AllocationSize.LowPart ) {
|
|
|
|
//
|
|
// Change the file allocation
|
|
//
|
|
|
|
FatAddFileAllocation( IrpContext, Fcb, FileObject, NewFileSize );
|
|
}
|
|
|
|
|
|
//
|
|
// At this point we have enough allocation for the file.
|
|
// So check if we are really changing the file size
|
|
//
|
|
|
|
if (Fcb->Header.FileSize.LowPart != NewFileSize) {
|
|
|
|
if ( NewFileSize < Fcb->Header.FileSize.LowPart ) {
|
|
|
|
//
|
|
// The way Sections for DataScan are created and used, an AV's
|
|
// memory-mapping can come into being after we check it's safe
|
|
// to truncate a file while continuing to hold the file here.
|
|
// This leads to data corruption because Purge eventually fails
|
|
// (during call to Cc to set file sizes) and stale data continues
|
|
// to live in the cache/memory.
|
|
//
|
|
|
|
if (Fcb->PurgeFailureModeEnableCount != 0) {
|
|
|
|
try_return( Status = STATUS_PURGE_FAILED );
|
|
}
|
|
|
|
//
|
|
// Before we actually truncate, check to see if the purge
|
|
// is going to fail.
|
|
//
|
|
|
|
if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
|
|
&Buffer->EndOfFile )) {
|
|
|
|
try_return( Status = STATUS_USER_MAPPED_FILE );
|
|
}
|
|
|
|
//
|
|
// This call is unconditional, because MM always wants to know.
|
|
// Also serialize here with paging io since we are truncating
|
|
// the file size.
|
|
//
|
|
|
|
ResourceAcquired =
|
|
ExAcquireResourceExclusiveLite( Fcb->Header.PagingIoResource, TRUE );
|
|
}
|
|
|
|
//
|
|
// Set the new file size
|
|
//
|
|
|
|
InitialFileSize = Fcb->Header.FileSize.LowPart;
|
|
InitialValidDataLength = Fcb->Header.ValidDataLength.LowPart;
|
|
InitialValidDataToDisk = Fcb->ValidDataToDisk;
|
|
UnwindFileSizes = TRUE;
|
|
|
|
Fcb->Header.FileSize.LowPart = NewFileSize;
|
|
|
|
//
|
|
// If we reduced the file size to less than the ValidDataLength,
|
|
// adjust the VDL. Likewise ValidDataToDisk.
|
|
//
|
|
|
|
if (Fcb->Header.ValidDataLength.LowPart > NewFileSize) {
|
|
|
|
Fcb->Header.ValidDataLength.LowPart = NewFileSize;
|
|
}
|
|
|
|
if (Fcb->ValidDataToDisk > NewFileSize) {
|
|
|
|
Fcb->ValidDataToDisk = NewFileSize;
|
|
}
|
|
|
|
DebugTrace(0, Dbg, "New file size is 0x%08lx.\n", NewFileSize);
|
|
|
|
//
|
|
// We must now update the cache mapping (benign if not cached).
|
|
//
|
|
|
|
CcSetFileSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Fcb->Header.AllocationSize );
|
|
|
|
FatSetFileSizeInDirent( IrpContext, Fcb, NULL );
|
|
|
|
//
|
|
// Report that we just changed the file size.
|
|
//
|
|
|
|
FatNotifyReportChange( IrpContext,
|
|
Vcb,
|
|
Fcb,
|
|
FILE_NOTIFY_CHANGE_SIZE,
|
|
FILE_ACTION_MODIFIED );
|
|
|
|
//
|
|
// Mark the fact that the file will need to checked for
|
|
// truncation on cleanup.
|
|
//
|
|
|
|
SetFlag( Fcb->FcbState, FCB_STATE_TRUNCATE_ON_CLOSE );
|
|
}
|
|
|
|
//
|
|
// Set this handle as having modified the file
|
|
//
|
|
|
|
FileObject->Flags |= FO_FILE_MODIFIED;
|
|
|
|
//
|
|
// Set our return status to success
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
try_exit: NOTHING;
|
|
|
|
FatUnpinRepinnedBcbs( IrpContext );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatSetEndOfFileInfo );
|
|
|
|
if (_SEH2_AbnormalTermination() && UnwindFileSizes) {
|
|
|
|
Fcb->Header.FileSize.LowPart = InitialFileSize;
|
|
Fcb->Header.ValidDataLength.LowPart = InitialValidDataLength;
|
|
Fcb->ValidDataToDisk = InitialValidDataToDisk;
|
|
|
|
if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
|
|
|
|
*CcGetFileSizePointer(FileObject) = Fcb->Header.FileSize;
|
|
}
|
|
|
|
//
|
|
// WinSE bug #307418 "Occasional data corruption when
|
|
// standby/resume while copying files to removable FAT
|
|
// formatted media".
|
|
// On system suspend FatUnpinRepinnedBcbs() can fail
|
|
// because the underlying drive is already marked with DO_VERIFY
|
|
// flag. FatUnpinRepinnedBcbs() will raise in this case and
|
|
// the file size changes will be un-rolled in FCB but the change
|
|
// to Dirent file still can make it to the disk since its BCB
|
|
// will not be purged by FatUnpinRepinnedBcbs(). In this case
|
|
// we'll also try to un-roll the change to Dirent to keep
|
|
// in-memory and on-disk metadata in sync.
|
|
//
|
|
|
|
FatSetFileSizeInDirentNoRaise( IrpContext, Fcb, NULL );
|
|
|
|
}
|
|
|
|
if (CacheMapInitialized) {
|
|
|
|
CcUninitializeCacheMap( FileObject, NULL, NULL );
|
|
}
|
|
|
|
if ( ResourceAcquired ) {
|
|
|
|
ExReleaseResourceLite( Fcb->Header.PagingIoResource );
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatSetEndOfFileInfo -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
NTSTATUS
|
|
FatSetValidDataLengthInfo (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFCB Fcb,
|
|
IN PCCB Ccb
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs the set valid data length information for fat. It either
|
|
completes the request or enqueues it off to the fsp.
|
|
|
|
Arguments:
|
|
|
|
Irp - Supplies the irp being processed
|
|
|
|
FileObject - Supplies the file object being processed
|
|
|
|
Fcb - Supplies the Fcb or Dcb being processed, already known not to
|
|
be the root dcb
|
|
|
|
Ccb - Supplies the Ccb corresponding to the handle opening the source
|
|
file
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - The result of this operation if it completes without
|
|
an exception.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PFILE_VALID_DATA_LENGTH_INFORMATION Buffer;
|
|
|
|
ULONG NewValidDataLength;
|
|
BOOLEAN ResourceAcquired = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
UNREFERENCED_PARAMETER( IrpContext );
|
|
|
|
DebugTrace(+1, Dbg, "FatSetValidDataLengthInfo...\n", 0);
|
|
|
|
Buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// User must have manage volume privilege to explicitly tweak the VDL
|
|
//
|
|
|
|
if ((Ccb == NULL) || !FlagOn( Ccb->Flags, CCB_FLAG_MANAGE_VOLUME_ACCESS )) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// Valid data length changes are only allowed on a file and not a directory
|
|
//
|
|
|
|
if (NodeType(Fcb) != FAT_NTC_FCB) {
|
|
|
|
DebugTrace(0, Dbg, "Cannot change VDL of a directory\n", 0);
|
|
|
|
try_return( Status = STATUS_INVALID_DEVICE_REQUEST );
|
|
}
|
|
|
|
//
|
|
// Check that the new file size is legal
|
|
//
|
|
|
|
|
|
if (!FatIsIoRangeValid( Fcb->Vcb, Buffer->ValidDataLength, 0)) {
|
|
|
|
DebugTrace(0, Dbg, "Illegal allocation size\n", 0);
|
|
|
|
try_return( Status = STATUS_DISK_FULL );
|
|
}
|
|
|
|
|
|
NewValidDataLength = Buffer->ValidDataLength.LowPart;
|
|
|
|
//
|
|
// VDL can only move forward
|
|
//
|
|
|
|
if ((NewValidDataLength < Fcb->Header.ValidDataLength.LowPart) ||
|
|
(NewValidDataLength > Fcb->Header.FileSize.LowPart)) {
|
|
|
|
try_return( Status = STATUS_INVALID_PARAMETER );
|
|
}
|
|
|
|
//
|
|
// We can't change the VDL without being able to purge. This should stay
|
|
// constant since we own everything exclusive
|
|
//
|
|
|
|
if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
|
|
&Buffer->ValidDataLength )) {
|
|
|
|
try_return( Status = STATUS_USER_MAPPED_FILE );
|
|
}
|
|
|
|
//
|
|
// Flush old data out and purge the cache so we can see new data.
|
|
//
|
|
|
|
if (FileObject->SectionObjectPointer->DataSectionObject != NULL) {
|
|
|
|
ResourceAcquired =
|
|
ExAcquireResourceExclusiveLite( Fcb->Header.PagingIoResource, TRUE );
|
|
|
|
CcFlushCache( FileObject->SectionObjectPointer,
|
|
NULL,
|
|
0,
|
|
&Irp->IoStatus );
|
|
|
|
if (!NT_SUCCESS( Irp->IoStatus.Status )) {
|
|
|
|
try_return( Irp->IoStatus.Status );
|
|
}
|
|
|
|
CcPurgeCacheSection( FileObject->SectionObjectPointer,
|
|
NULL,
|
|
0,
|
|
FALSE );
|
|
}
|
|
|
|
//
|
|
// Set the new ValidDataLength, Likewise ValidDataToDisk.
|
|
//
|
|
|
|
Fcb->Header.ValidDataLength.LowPart = NewValidDataLength;
|
|
Fcb->ValidDataToDisk = NewValidDataLength;
|
|
|
|
DebugTrace(0, Dbg, "New VDL is 0x%08lx.\n", NewValidDataLength);
|
|
|
|
//
|
|
// We must now update the cache mapping.
|
|
//
|
|
|
|
if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
|
|
|
|
CcSetFileSizes( FileObject,
|
|
(PCC_FILE_SIZES)&Fcb->Header.AllocationSize );
|
|
}
|
|
|
|
//
|
|
// Set this handle as having modified the file
|
|
//
|
|
|
|
FileObject->Flags |= FO_FILE_MODIFIED;
|
|
|
|
//
|
|
// Set our return status to success
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
try_exit: NOTHING;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
DebugUnwind( FatSetValidDataLengthInfo );
|
|
|
|
if (ResourceAcquired) {
|
|
|
|
ExReleaseResourceLite( Fcb->Header.PagingIoResource );
|
|
}
|
|
|
|
DebugTrace(-1, Dbg, "FatSetValidDataLengthInfo -> %08lx\n", Status);
|
|
} _SEH2_END;
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Internal Support Routine
|
|
//
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatRenameEAs (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PFCB Fcb,
|
|
IN USHORT ExtendedAttributes,
|
|
IN POEM_STRING OldOemName
|
|
)
|
|
{
|
|
BOOLEAN LockedEaFcb = FALSE;
|
|
|
|
PBCB EaBcb = NULL;
|
|
PDIRENT EaDirent;
|
|
EA_RANGE EaSetRange;
|
|
PEA_SET_HEADER EaSetHeader;
|
|
|
|
PVCB Vcb;
|
|
|
|
PAGED_CODE();
|
|
|
|
RtlZeroMemory( &EaSetRange, sizeof( EA_RANGE ));
|
|
|
|
Vcb = Fcb->Vcb;
|
|
|
|
_SEH2_TRY {
|
|
|
|
//
|
|
// Use a try-except to catch any errors.
|
|
//
|
|
|
|
_SEH2_TRY {
|
|
|
|
|
|
//
|
|
// Try to get the Ea file object. Return FALSE on failure.
|
|
//
|
|
|
|
FatGetEaFile( IrpContext,
|
|
Vcb,
|
|
&EaDirent,
|
|
&EaBcb,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
LockedEaFcb = TRUE;
|
|
|
|
//
|
|
// If we didn't get the file because it doesn't exist, then the
|
|
// disk is corrupted. We do nothing here.
|
|
//
|
|
|
|
if (Vcb->VirtualEaFile != NULL) {
|
|
|
|
//
|
|
// Try to pin down the Ea set header for the index in the
|
|
// dirent. If the operation doesn't complete, return FALSE
|
|
// from this routine.
|
|
//
|
|
|
|
FatReadEaSet( IrpContext,
|
|
Vcb,
|
|
ExtendedAttributes,
|
|
OldOemName,
|
|
FALSE,
|
|
&EaSetRange );
|
|
|
|
EaSetHeader = (PEA_SET_HEADER) EaSetRange.Data;
|
|
|
|
//
|
|
// We now have the Ea set header for this file. We simply
|
|
// overwrite the owning file name.
|
|
//
|
|
|
|
RtlZeroMemory( EaSetHeader->OwnerFileName, 14 );
|
|
|
|
RtlCopyMemory( EaSetHeader->OwnerFileName,
|
|
Fcb->ShortName.Name.Oem.Buffer,
|
|
Fcb->ShortName.Name.Oem.Length );
|
|
|
|
FatMarkEaRangeDirty( IrpContext, Vcb->VirtualEaFile, &EaSetRange );
|
|
FatUnpinEaRange( IrpContext, &EaSetRange );
|
|
|
|
CcFlushCache( Vcb->VirtualEaFile->SectionObjectPointer, NULL, 0, NULL );
|
|
}
|
|
|
|
} _SEH2_EXCEPT(FatExceptionFilter( IrpContext, _SEH2_GetExceptionInformation() )) {
|
|
|
|
//
|
|
// We catch all exceptions that Fat catches, but don't do
|
|
// anything with them.
|
|
//
|
|
} _SEH2_END;
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
//
|
|
// Unpin the EaDirent and the EaSetHeader if pinned.
|
|
//
|
|
|
|
FatUnpinBcb( IrpContext, EaBcb );
|
|
FatUnpinEaRange( IrpContext, &EaSetRange );
|
|
|
|
//
|
|
// Release the Fcb for the Ea file if locked.
|
|
//
|
|
|
|
if (LockedEaFcb) {
|
|
|
|
FatReleaseFcb( IrpContext, Vcb->EaFcb );
|
|
}
|
|
} _SEH2_END;
|
|
|
|
return;
|
|
}
|
|
|
|
_Requires_lock_held_(_Global_critical_region_)
|
|
VOID
|
|
FatDeleteFile (
|
|
IN PIRP_CONTEXT IrpContext,
|
|
IN PDCB TargetDcb,
|
|
IN ULONG LfnOffset,
|
|
IN ULONG DirentOffset,
|
|
IN PDIRENT Dirent,
|
|
IN PUNICODE_STRING Lfn
|
|
)
|
|
{
|
|
PFCB Fcb;
|
|
PLIST_ENTRY Links;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// We can do the replace by removing the other Fcb(s) from
|
|
// the prefix table.
|
|
//
|
|
|
|
for (Links = TargetDcb->Specific.Dcb.ParentDcbQueue.Flink;
|
|
Links != &TargetDcb->Specific.Dcb.ParentDcbQueue;
|
|
Links = Links->Flink) {
|
|
|
|
Fcb = CONTAINING_RECORD( Links, FCB, ParentDcbLinks );
|
|
|
|
if (FlagOn(Fcb->FcbState, FCB_STATE_NAMES_IN_SPLAY_TREE) &&
|
|
(Fcb->DirentOffsetWithinDirectory == DirentOffset)) {
|
|
|
|
NT_ASSERT( NodeType(Fcb) == FAT_NTC_FCB );
|
|
NT_ASSERT( Fcb->LfnOffsetWithinDirectory == LfnOffset );
|
|
|
|
if ( Fcb->UncleanCount != 0 ) {
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma prefast( suppress:28159, "things are seriously wrong if we get here" )
|
|
#endif
|
|
FatBugCheck(0,0,0);
|
|
|
|
} else {
|
|
|
|
PERESOURCE Resource;
|
|
|
|
//
|
|
// Make this fcb "appear" deleted, synchronizing with
|
|
// paging IO.
|
|
//
|
|
|
|
FatRemoveNames( IrpContext, Fcb );
|
|
|
|
Resource = Fcb->Header.PagingIoResource;
|
|
|
|
(VOID)ExAcquireResourceExclusiveLite( Resource, TRUE );
|
|
|
|
SetFlag(Fcb->FcbState, FCB_STATE_DELETE_ON_CLOSE);
|
|
|
|
Fcb->ValidDataToDisk = 0;
|
|
Fcb->Header.FileSize.QuadPart =
|
|
Fcb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
Fcb->FirstClusterOfFile = 0;
|
|
|
|
ExReleaseResourceLite( Resource );
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// The file is not currently opened so we can delete the file
|
|
// that is being overwritten. To do the operation we dummy
|
|
// up an fcb, truncate allocation, delete the fcb, and delete
|
|
// the dirent.
|
|
//
|
|
|
|
Fcb = FatCreateFcb( IrpContext,
|
|
TargetDcb->Vcb,
|
|
TargetDcb,
|
|
LfnOffset,
|
|
DirentOffset,
|
|
Dirent,
|
|
Lfn,
|
|
NULL,
|
|
FALSE,
|
|
FALSE );
|
|
|
|
Fcb->Header.FileSize.LowPart = 0;
|
|
|
|
_SEH2_TRY {
|
|
|
|
FatTruncateFileAllocation( IrpContext, Fcb, 0 );
|
|
|
|
FatDeleteDirent( IrpContext, Fcb, NULL, TRUE );
|
|
|
|
} _SEH2_FINALLY {
|
|
|
|
FatDeleteFcb( IrpContext, &Fcb );
|
|
} _SEH2_END;
|
|
}
|
|
|
|
|