reactos/drivers/filesystems/fastfat/dirsup.c

3796 lines
104 KiB
C

/*++
Copyright (c) 1989-2000 Microsoft Corporation
Module Name:
DirSup.c
Abstract:
This module implements the dirent support routines for Fat.
--*/
#include "fatprocs.h"
//
// The Bug check file id for this module
//
#define BugCheckFileId (FAT_BUG_CHECK_DIRSUP)
//
// Local debug trace level
//
#define Dbg (DEBUG_TRACE_DIRSUP)
//
// The following three macro all assume the input dirent has been zeroed.
//
//
// VOID
// FatConstructDot (
// IN PIRP_CONTEXT IrpContext,
// IN PDCB Directory,
// IN PDIRENT ParentDirent,
// IN OUT PDIRENT Dirent
// );
//
// The following macro is called to initalize the "." dirent.
//
// Always setting FirstClusterOfFileHi is OK because it will be zero
// unless we're working on a FAT 32 disk.
//
#define FatConstructDot(IRPCONTEXT,DCB,PARENT,DIRENT) { \
\
RtlCopyMemory( (PUCHAR)(DIRENT), ". ", 11 ); \
(DIRENT)->Attributes = FAT_DIRENT_ATTR_DIRECTORY; \
(DIRENT)->LastWriteTime = (PARENT)->LastWriteTime; \
if (FatData.ChicagoMode) { \
(DIRENT)->CreationTime = (PARENT)->CreationTime; \
(DIRENT)->CreationMSec = (PARENT)->CreationMSec; \
(DIRENT)->LastAccessDate = (PARENT)->LastAccessDate; \
} \
(DIRENT)->FirstClusterOfFile = \
(USHORT)(DCB)->FirstClusterOfFile; \
(DIRENT)->FirstClusterOfFileHi = \
(USHORT)((DCB)->FirstClusterOfFile/0x10000); \
}
//
// VOID
// FatConstructDotDot (
// IN PIRP_CONTEXT IrpContext,
// IN PDCB Directory,
// IN PDIRENT ParentDirent,
// IN OUT PDIRENT Dirent
// );
//
// The following macro is called to initalize the ".." dirent.
//
// Always setting FirstClusterOfFileHi is OK because it will be zero
// unless we're working on a FAT 32 disk.
//
#define FatConstructDotDot(IRPCONTEXT,DCB,PARENT,DIRENT) { \
\
RtlCopyMemory( (PUCHAR)(DIRENT), ".. ", 11 ); \
(DIRENT)->Attributes = FAT_DIRENT_ATTR_DIRECTORY; \
(DIRENT)->LastWriteTime = (PARENT)->LastWriteTime; \
if (FatData.ChicagoMode) { \
(DIRENT)->CreationTime = (PARENT)->CreationTime; \
(DIRENT)->CreationMSec = (PARENT)->CreationMSec; \
(DIRENT)->LastAccessDate = (PARENT)->LastAccessDate; \
} \
if (NodeType((DCB)->ParentDcb) == FAT_NTC_ROOT_DCB) { \
(DIRENT)->FirstClusterOfFile = 0; \
(DIRENT)->FirstClusterOfFileHi = 0; \
} else { \
(DIRENT)->FirstClusterOfFile = (USHORT) \
((DCB)->ParentDcb->FirstClusterOfFile); \
(DIRENT)->FirstClusterOfFileHi = (USHORT) \
((DCB)->ParentDcb->FirstClusterOfFile/0x10000); \
} \
}
//
// VOID
// FatConstructEndDirent (
// IN PIRP_CONTEXT IrpContext,
// IN OUT PDIRENT Dirent
// );
//
// The following macro created the end dirent. Note that since the
// dirent was zeroed, the first byte of the name already contains 0x0,
// so there is nothing to do.
//
#define FatConstructEndDirent(IRPCONTEXT,DIRENT) NOTHING
//
// VOID
// FatReadDirent (
// IN PIRP_CONTEXT IrpContext,
// IN PDCB Dcb,
// IN VBO Vbo,
// OUT PBCB *Bcb,
// OUT PVOID *Dirent,
// OUT PNTSTATUS Status
// );
//
//
// This macro reads in a page of dirents when we step onto a new page,
// or this is the first iteration of a loop and Bcb is NULL.
//
#define FatReadDirent(IRPCONTEXT,DCB,VBO,BCB,DIRENT,STATUS) \
if ((VBO) >= (DCB)->Header.AllocationSize.LowPart) { \
*(STATUS) = STATUS_END_OF_FILE; \
FatUnpinBcb( (IRPCONTEXT), *(BCB) ); \
} else if ( ((VBO) % PAGE_SIZE == 0) || (*(BCB) == NULL) ) { \
FatUnpinBcb( (IRPCONTEXT), *(BCB) ); \
FatReadDirectoryFile( (IRPCONTEXT), \
(DCB), \
(VBO) & ~(PAGE_SIZE - 1), \
PAGE_SIZE, \
FALSE, \
(BCB), \
(PVOID *)(DIRENT), \
(STATUS) ); \
*(DIRENT) = (PVOID)((PUCHAR)*(DIRENT) + ((VBO) % PAGE_SIZE)); \
}
//
// Internal support routines
//
UCHAR
FatComputeLfnChecksum (
PDIRENT Dirent
);
_Requires_lock_held_(_Global_critical_region_)
VOID
FatRescanDirectory (
PIRP_CONTEXT IrpContext,
PDCB Dcb
);
_Requires_lock_held_(_Global_critical_region_)
ULONG
FatDefragDirectory (
IN PIRP_CONTEXT IrpContext,
IN PDCB Dcb,
IN ULONG DirentsNeeded
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FatComputeLfnChecksum)
#pragma alloc_text(PAGE, FatConstructDirent)
#pragma alloc_text(PAGE, FatConstructLabelDirent)
#pragma alloc_text(PAGE, FatCreateNewDirent)
#pragma alloc_text(PAGE, FatDefragDirectory)
#pragma alloc_text(PAGE, FatDeleteDirent)
#pragma alloc_text(PAGE, FatGetDirentFromFcbOrDcb)
#pragma alloc_text(PAGE, FatInitializeDirectoryDirent)
#pragma alloc_text(PAGE, FatIsDirectoryEmpty)
#pragma alloc_text(PAGE, FatLfnDirentExists)
#pragma alloc_text(PAGE, FatLocateDirent)
#pragma alloc_text(PAGE, FatLocateSimpleOemDirent)
#pragma alloc_text(PAGE, FatLocateVolumeLabel)
#pragma alloc_text(PAGE, FatRescanDirectory)
#pragma alloc_text(PAGE, FatSetFileSizeInDirent)
#pragma alloc_text(PAGE, FatSetFileSizeInDirentNoRaise)
#pragma alloc_text(PAGE, FatTunnelFcbOrDcb)
#pragma alloc_text(PAGE, FatUpdateDirentFromFcb)
#endif
_Requires_lock_held_(_Global_critical_region_)
ULONG
FatCreateNewDirent (
IN PIRP_CONTEXT IrpContext,
IN PDCB ParentDirectory,
IN ULONG DirentsNeeded,
IN BOOLEAN RescanDir
)
/*++
Routine Description:
This routine allocates on the disk a new dirent inside of the
parent directory. If a new dirent cannot be allocated (i.e.,
because the disk is full or the root directory is full) then
it raises the appropriate status. The dirent itself is
neither initialized nor pinned by this procedure.
Arguments:
ParentDirectory - Supplies the DCB for the directory in which
to create the new dirent
DirentsNeeded - This is the number of continginous dirents required
Return Value:
ByteOffset - Returns the VBO within the Parent directory where
the dirent has been allocated
--*/
{
VBO UnusedVbo;
VBO DeletedHint;
ULONG ByteOffset;
PBCB Bcb = NULL;
PDIRENT Dirent = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatCreateNewDirent\n", 0);
DebugTrace( 0, Dbg, " ParentDirectory = %p\n", ParentDirectory);
//
// If UnusedDirentVbo is within our current file allocation then we
// don't have to search through the directory at all; we know just
// where to put it.
//
// If UnusedDirentVbo is beyond the current file allocation then
// there are no more unused dirents in the current allocation, though
// upon adding another cluster of allocation UnusedDirentVbo
// will point to an unused dirent. Haveing found no unused dirents
// we use the DeletedDirentHint to try and find a deleted dirent in
// the current allocation. In this also runs off the end of the file,
// we finally have to break down and allocate another sector. Note
// that simply writing beyond the current allocation will automatically
// do just this.
//
// We also must deal with the special case where UnusedDirentVbo and
// DeletedDirentHint have yet to be initialized. In this case we must
// first walk through the directory looking for the first deleted entry
// first unused dirent. After this point we continue as before.
// This initial state is denoted by the special value of 0xffffffff.
//
UnusedVbo = ParentDirectory->Specific.Dcb.UnusedDirentVbo;
DeletedHint = ParentDirectory->Specific.Dcb.DeletedDirentHint;
//
// Check for our first call to this routine with this Dcb. If so
// we have to correctly set the two hints in the Dcb.
//
if (UnusedVbo == 0xffffffff || RescanDir) {
FatRescanDirectory( IrpContext, ParentDirectory );
UnusedVbo = ParentDirectory->Specific.Dcb.UnusedDirentVbo;
DeletedHint = ParentDirectory->Specific.Dcb.DeletedDirentHint;
}
//
// Now we know that UnusedDirentVbo and DeletedDirentHint are correctly
// set so we check if there is already an unused dirent in the the
// current allocation. This is the easy case.
//
DebugTrace( 0, Dbg, " UnusedVbo = %08lx\n", UnusedVbo);
DebugTrace( 0, Dbg, " DeletedHint = %08lx\n", DeletedHint);
if (!RescanDir && ( UnusedVbo + (DirentsNeeded * sizeof(DIRENT)) <=
ParentDirectory->Header.AllocationSize.LowPart )) {
//
// Get this unused dirent for the caller. We have a
// sporting chance that we won't have to wait.
//
DebugTrace( 0, Dbg, "There is a never used entry.\n", 0);
ByteOffset = UnusedVbo;
UnusedVbo += DirentsNeeded * sizeof(DIRENT);
} else {
//
// Life is tough. We have to march from the DeletedDirentHint
// looking for a deleted dirent. If we get to EOF without finding
// one, we will have to allocate a new cluster.
//
ByteOffset =
RtlFindClearBits( &ParentDirectory->Specific.Dcb.FreeDirentBitmap,
DirentsNeeded,
DeletedHint / sizeof(DIRENT) );
//
// Do a quick check for a root directory allocation that failed
// simply because of fragmentation. Also, only attempt to defrag
// if the length is less that 0x40000. This is to avoid
// complications arising from crossing a MM view boundary (256kb).
// By default on DOS the root directory is only 0x2000 long.
//
// Don't try to defrag fat32 root dirs.
//
if (!FatIsFat32(ParentDirectory->Vcb) &&
(ByteOffset == -1) &&
(NodeType(ParentDirectory) == FAT_NTC_ROOT_DCB) &&
(ParentDirectory->Header.AllocationSize.LowPart <= 0x40000)) {
ByteOffset = FatDefragDirectory( IrpContext, ParentDirectory, DirentsNeeded );
}
if (ByteOffset != -1) {
//
// If we consuemed deleted dirents at Deleted Hint, update.
// We also may have consumed some un-used dirents as well,
// so be sure to check for that as well.
//
ByteOffset *= sizeof(DIRENT);
if (ByteOffset == DeletedHint) {
DeletedHint += DirentsNeeded * sizeof(DIRENT);
}
if (ByteOffset + DirentsNeeded * sizeof(DIRENT) > UnusedVbo) {
UnusedVbo = ByteOffset + DirentsNeeded * sizeof(DIRENT);
}
} else {
//
// We are going to have to allocate another cluster. Do
// so, update both the UnusedVbo and the DeletedHint and bail.
//
DebugTrace( 0, Dbg, "We have to allocate another cluster.\n", 0);
//
// A reason why we might fail, unrelated to physical reasons,
// is that we constrain to 64k directory entries to match the
// restriction on Win95. There are fundamental reasons to do
// this since searching a FAT directory is a linear operation
// and to allow FAT32 to toss us over the cliff is not permissable.
//
if (ParentDirectory->Header.AllocationSize.LowPart >= (64 * 1024 * sizeof(DIRENT)) ||
//
// Make sure we are not trying to expand the root directory on non
// FAT32. FAT16 and FAT12 have fixed size allocations.
//
(!FatIsFat32(ParentDirectory->Vcb) &&
NodeType(ParentDirectory) == FAT_NTC_ROOT_DCB)) {
DebugTrace(0, Dbg, "Full root directory or too big on FAT32. Raise Status.\n", 0);
FatRaiseStatus( IrpContext, STATUS_CANNOT_MAKE );
}
//
// Take the last dirent(s) in this cluster. We will allocate
// more clusters below.
//
ByteOffset = UnusedVbo;
UnusedVbo += DirentsNeeded * sizeof(DIRENT);
//
// Touch the directory file to cause space for the new dirents
// to be allocated.
//
Bcb = NULL;
_SEH2_TRY {
PVOID Buffer;
FatPrepareWriteDirectoryFile( IrpContext,
ParentDirectory,
UnusedVbo,
1,
&Bcb,
&Buffer,
FALSE,
TRUE,
&Status );
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
}
}
//
// If we are only requesting a single dirent, and we did not get the
// first dirent in a directory, then check that the preceding dirent
// is not an orphaned LFN. If it is, then mark it deleted. Thus
// reducing the possibility of an accidental pairing.
//
// Only do this when we are in Chicago Mode.
//
Bcb = NULL;
if (FatData.ChicagoMode &&
(DirentsNeeded == 1) &&
(ByteOffset > (NodeType(ParentDirectory) == FAT_NTC_ROOT_DCB ?
0 : 2 * sizeof(DIRENT)))) {
_SEH2_TRY {
FatReadDirent( IrpContext,
ParentDirectory,
ByteOffset - sizeof(DIRENT),
&Bcb,
&Dirent,
&Status );
if ((Status != STATUS_SUCCESS) ||
(Dirent->FileName[0] == FAT_DIRENT_NEVER_USED)) {
FatPopUpFileCorrupt( IrpContext, ParentDirectory );
FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR );
}
if ((Dirent->Attributes == FAT_DIRENT_ATTR_LFN) &&
(Dirent->FileName[0] != FAT_DIRENT_DELETED)) {
//
// Pin it, mark it, and set it dirty.
//
FatPinMappedData( IrpContext,
ParentDirectory,
ByteOffset - sizeof(DIRENT),
sizeof(DIRENT),
&Bcb );
Dirent->FileName[0] = FAT_DIRENT_DELETED;
FatSetDirtyBcb( IrpContext, Bcb, ParentDirectory->Vcb, TRUE );
NT_ASSERT( RtlAreBitsSet( &ParentDirectory->Specific.Dcb.FreeDirentBitmap,
(ByteOffset - sizeof(DIRENT))/ sizeof(DIRENT),
DirentsNeeded ) );
RtlClearBits( &ParentDirectory->Specific.Dcb.FreeDirentBitmap,
(ByteOffset - sizeof(DIRENT))/ sizeof(DIRENT),
DirentsNeeded );
}
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
}
//
// Assert that the dirents are in fact unused
//
_SEH2_TRY {
ULONG i;
Bcb = NULL;
for (i = 0; i < DirentsNeeded; i++) {
FatReadDirent( IrpContext,
ParentDirectory,
ByteOffset + i*sizeof(DIRENT),
&Bcb,
&Dirent,
&Status );
if ((Status != STATUS_SUCCESS) ||
((Dirent->FileName[0] != FAT_DIRENT_NEVER_USED) &&
(Dirent->FileName[0] != FAT_DIRENT_DELETED))) {
FatPopUpFileCorrupt( IrpContext, ParentDirectory );
FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR );
}
}
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
//
// Set the Bits in the bitmap and move the Unused Dirent Vbo.
//
NT_ASSERT( RtlAreBitsClear( &ParentDirectory->Specific.Dcb.FreeDirentBitmap,
ByteOffset / sizeof(DIRENT),
DirentsNeeded ) );
RtlSetBits( &ParentDirectory->Specific.Dcb.FreeDirentBitmap,
ByteOffset / sizeof(DIRENT),
DirentsNeeded );
//
// Save the newly computed values in the Parent Directory Fcb
//
ParentDirectory->Specific.Dcb.UnusedDirentVbo = UnusedVbo;
ParentDirectory->Specific.Dcb.DeletedDirentHint = DeletedHint;
DebugTrace(-1, Dbg, "FatCreateNewDirent -> (VOID)\n", 0);
return ByteOffset;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatInitializeDirectoryDirent (
IN PIRP_CONTEXT IrpContext,
IN PDCB Dcb,
IN PDIRENT ParentDirent
)
/*++
Routine Description:
This routine converts a dirent into a directory on the disk. It does this
setting the directory flag in the dirent, and by allocating the necessary
space for the "." and ".." dirents and initializing them.
If a new dirent cannot be allocated (i.e., because the disk is full) then
it raises the appropriate status.
Arguments:
Dcb - Supplies the Dcb denoting the file that is to be made into a
directory. This must be input a completely empty file with
an allocation size of zero.
ParentDirent - Provides the parent Dirent for a time-stamp model.
Return Value:
None.
--*/
{
PBCB Bcb;
PVOID Buffer;
NTSTATUS DontCare = STATUS_SUCCESS;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatInitializeDirectoryDirent\n", 0);
DebugTrace( 0, Dbg, " Dcb = %p\n", Dcb);
//
// Assert that we are not attempting this on the root directory.
//
NT_ASSERT( NodeType(Dcb) != FAT_NTC_ROOT_DCB );
//
// Assert that this is only attempted on newly created directories.
//
NT_ASSERT( Dcb->Header.AllocationSize.LowPart == 0 );
//
// Prepare the directory file for writing. Note that we can use a single
// Bcb for these two entries because we know they are the first two in
// the directory, and thus together do not span a page boundry. Also
// note that we prepare write 2 entries: one for "." and one for "..".
// The end of directory marker is automatically set since the whole
// directory is initially zero (DIRENT_NEVER_USED).
//
FatPrepareWriteDirectoryFile( IrpContext,
Dcb,
0,
2 * sizeof(DIRENT),
&Bcb,
&Buffer,
FALSE,
TRUE,
&DontCare );
NT_ASSERT( NT_SUCCESS( DontCare ));
//
// Add the . and .. entries
//
_SEH2_TRY {
FatConstructDot( IrpContext, Dcb, ParentDirent, (PDIRENT)Buffer + 0);
FatConstructDotDot( IrpContext, Dcb, ParentDirent, (PDIRENT)Buffer + 1);
//
// Unpin the buffer and return to the caller.
//
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
DebugTrace(-1, Dbg, "FatInitializeDirectoryDirent -> (VOID)\n", 0);
return;
}
VOID
FatTunnelFcbOrDcb (
IN PFCB FcbOrDcb,
IN PCCB Ccb OPTIONAL
)
/*++
Routine Description:
This routine handles tunneling of an Fcb or Dcb associated with
an object whose name is disappearing from a directory.
Arguments:
FcbOrDcb - Supplies the Fcb/Dcb whose name will be going away
Ccb - Supplies the Ccb for the Fcb (not reqired for a Dcb) so
that we know which name the Fcb was opened by
Return Value:
None.
--*/
{
UNICODE_STRING ShortNameWithCase = {0};
UNICODE_STRING DownCaseSeg;
WCHAR ShortNameBuffer[8+1+3];
NTSTATUS Status;
USHORT i;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatTunnelFcbOrDcb\n", 0);
if (NodeType(FcbOrDcb) == FAT_NTC_DCB) {
//
// Directory deletion. Flush all entries from this directory in
// the cache for this volume
//
FsRtlDeleteKeyFromTunnelCache( &FcbOrDcb->Vcb->Tunnel,
FatDirectoryKey(FcbOrDcb) );
} else {
//
// Was a file, so throw it into the tunnel cache
//
//
// Get the short name into UNICODE
//
ShortNameWithCase.Length = 0;
ShortNameWithCase.MaximumLength = sizeof(ShortNameBuffer);
ShortNameWithCase.Buffer = ShortNameBuffer;
#ifdef _MSC_VER
#pragma prefast( suppress:28931, "needed for debug build" )
#endif
Status = RtlOemStringToCountedUnicodeString( &ShortNameWithCase,
&FcbOrDcb->ShortName.Name.Oem,
FALSE);
NT_ASSERT(ShortNameWithCase.Length != 0);
NT_ASSERT(NT_SUCCESS(Status));
if (FlagOn(FcbOrDcb->FcbState, FCB_STATE_8_LOWER_CASE | FCB_STATE_3_LOWER_CASE)) {
//
// Have to repair the case of the short name
//
for (i = 0; i < (ShortNameWithCase.Length/sizeof(WCHAR)) &&
ShortNameWithCase.Buffer[i] != L'.'; i++);
//
// Now pointing at the '.', or otherwise the end of name component
//
if (FlagOn(FcbOrDcb->FcbState, FCB_STATE_8_LOWER_CASE)) {
DownCaseSeg.Buffer = ShortNameWithCase.Buffer;
DownCaseSeg.MaximumLength = DownCaseSeg.Length = i*sizeof(WCHAR);
RtlDowncaseUnicodeString(&DownCaseSeg, &DownCaseSeg, FALSE);
}
i++;
//
// Now pointing at first wchar of the extension.
//
if (FlagOn(FcbOrDcb->FcbState, FCB_STATE_3_LOWER_CASE)) {
//
// It is not neccesarily the case that we can rely on the flag
// indicating that we really have an extension.
//
if ((i*sizeof(WCHAR)) < ShortNameWithCase.Length) {
DownCaseSeg.Buffer = &ShortNameWithCase.Buffer[i];
DownCaseSeg.MaximumLength = DownCaseSeg.Length = ShortNameWithCase.Length - i*sizeof(WCHAR);
RtlDowncaseUnicodeString(&DownCaseSeg, &DownCaseSeg, FALSE);
}
}
}
//
// ... and add it in
//
FsRtlAddToTunnelCache( &FcbOrDcb->Vcb->Tunnel,
FatDirectoryKey(FcbOrDcb->ParentDcb),
&ShortNameWithCase,
&FcbOrDcb->ExactCaseLongName,
BooleanFlagOn(Ccb->Flags, CCB_FLAG_OPENED_BY_SHORTNAME),
sizeof(LARGE_INTEGER),
&FcbOrDcb->CreationTime );
}
DebugTrace(-1, Dbg, "FatTunnelFcbOrDcb -> (VOID)\n", 0);
return;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatDeleteDirent (
IN PIRP_CONTEXT IrpContext,
IN PFCB FcbOrDcb,
IN PDELETE_CONTEXT DeleteContext OPTIONAL,
IN BOOLEAN DeleteEa
)
/*++
Routine Description:
This routine Deletes on the disk the indicated dirent. It does
this by marking the dirent as deleted.
Arguments:
FcbOrDcb - Supplies the FCB/DCB for the file/directory being
deleted. For a file the file size and allocation must be zero.
(Zero allocation is implied by a zero cluster index).
For a directory the allocation must be zero.
DeleteContext - This variable, if speicified, may be used to preserve
the file size and first cluster of file information in the dirent
fot the benefit of unerase utilities.
DeleteEa - Tells us whether to delete the EA and whether to check
for no allocation/ Mainly TRUE. FALSE passed in from rename.
Return Value:
None.
--*/
{
PBCB Bcb = NULL;
PDIRENT Dirent = NULL;
NTSTATUS DontCare;
ULONG Offset;
ULONG DirentsToDelete;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatDeleteDirent\n", 0);
DebugTrace( 0, Dbg, " FcbOrDcb = %p\n", FcbOrDcb);
//
// We must be holding the vcb exclusive here to deal with the locate dirent
// cases where it cannot be holding the parent simply. This is actually
// a true statement from olden daze, lets just wire in our assertion.
//
// Among other reasons, it'd be unfortunate if this raced with the
// rename path.
//
NT_ASSERT( ExIsResourceAcquiredExclusiveLite( &FcbOrDcb->Vcb->Resource ));
//
// Assert that we are not attempting this on the root directory.
//
NT_ASSERT( NodeType(FcbOrDcb) != FAT_NTC_ROOT_DCB );
//
// Make sure all requests have zero allocation/file size
//
if (DeleteEa &&
((FcbOrDcb->Header.AllocationSize.LowPart != 0) ||
((NodeType(FcbOrDcb) == FAT_NTC_FCB) &&
(FcbOrDcb->Header.FileSize.LowPart != 0)))) {
DebugTrace( 0, Dbg, "Called with non zero allocation/file size.\n", 0);
#ifdef _MSC_VER
#pragma prefast( suppress:28159, "things are seriously wrong if we get here" )
#endif
FatBugCheck( 0, 0, 0 );
}
//
// Now, mark the dirents deleted, unpin the Bcb, and return to the caller.
// Assert that there isn't any allocation associated with this dirent.
//
// Note that this loop will end with Dirent pointing to the short name.
//
_SEH2_TRY {
//
// We must acquire our parent exclusive to synchronize with enumerators
// who do not hold the vcb (ex: dirctrl).
//
// This relies on our bottom up lockorder.
//
ExAcquireResourceExclusiveLite( FcbOrDcb->ParentDcb->Header.Resource, TRUE );
for ( Offset = FcbOrDcb->LfnOffsetWithinDirectory;
Offset <= FcbOrDcb->DirentOffsetWithinDirectory;
Offset += sizeof(DIRENT), Dirent += 1 ) {
//
// If we stepped onto a new page, or this is the first iteration,
// unpin the old page, and pin the new one.
//
if ((Offset == FcbOrDcb->LfnOffsetWithinDirectory) ||
((Offset & (PAGE_SIZE - 1)) == 0)) {
FatUnpinBcb( IrpContext, Bcb );
FatPrepareWriteDirectoryFile( IrpContext,
FcbOrDcb->ParentDcb,
Offset,
sizeof(DIRENT),
&Bcb,
(PVOID *)&Dirent,
FALSE,
TRUE,
&DontCare );
}
NT_ASSERT( (Dirent->FirstClusterOfFile == 0) || !DeleteEa );
Dirent->FileName[0] = FAT_DIRENT_DELETED;
}
//
// Back Dirent off by one to point back to the short dirent.
//
Dirent -= 1;
//
// If there are extended attributes for this dirent, we will attempt
// to remove them. We ignore any errors in removing Eas.
//
if (!FatIsFat32(FcbOrDcb->Vcb) &&
DeleteEa && (Dirent->ExtendedAttributes != 0)) {
_SEH2_TRY {
FatDeleteEa( IrpContext,
FcbOrDcb->Vcb,
Dirent->ExtendedAttributes,
&FcbOrDcb->ShortName.Name.Oem );
} _SEH2_EXCEPT(FatExceptionFilter( IrpContext, _SEH2_GetExceptionInformation() )) {
//
// We catch all exceptions that Fat catches, but don't do
// anything with them.
//
} _SEH2_END;
}
//
// Now clear the bits in the free dirent mask.
//
DirentsToDelete = (FcbOrDcb->DirentOffsetWithinDirectory -
FcbOrDcb->LfnOffsetWithinDirectory) / sizeof(DIRENT) + 1;
NT_ASSERT( (FcbOrDcb->ParentDcb->Specific.Dcb.UnusedDirentVbo == 0xffffffff) ||
RtlAreBitsSet( &FcbOrDcb->ParentDcb->Specific.Dcb.FreeDirentBitmap,
FcbOrDcb->LfnOffsetWithinDirectory / sizeof(DIRENT),
DirentsToDelete ) );
RtlClearBits( &FcbOrDcb->ParentDcb->Specific.Dcb.FreeDirentBitmap,
FcbOrDcb->LfnOffsetWithinDirectory / sizeof(DIRENT),
DirentsToDelete );
//
// Now, if the caller specified a DeleteContext, use it.
//
if ( ARGUMENT_PRESENT( DeleteContext ) ) {
Dirent->FileSize = DeleteContext->FileSize;
Dirent->FirstClusterOfFile = (USHORT)DeleteContext->FirstClusterOfFile;
}
//
// If this newly deleted dirent is before the DeletedDirentHint, change
// the DeletedDirentHint to point here.
//
if (FcbOrDcb->DirentOffsetWithinDirectory <
FcbOrDcb->ParentDcb->Specific.Dcb.DeletedDirentHint) {
FcbOrDcb->ParentDcb->Specific.Dcb.DeletedDirentHint =
FcbOrDcb->LfnOffsetWithinDirectory;
}
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
//
// Release our parent.
//
ExReleaseResourceLite( FcbOrDcb->ParentDcb->Header.Resource );
} _SEH2_END;
DebugTrace(-1, Dbg, "FatDeleteDirent -> (VOID)\n", 0);
return;
}
_Requires_lock_held_(_Global_critical_region_)
BOOLEAN
FatLfnDirentExists (
IN PIRP_CONTEXT IrpContext,
IN PDCB Dcb,
IN PUNICODE_STRING Lfn,
IN PUNICODE_STRING LfnTmp
)
/*++
Routine Description:
This routine looks for a given Lfn in a directory
Arguments:
Dcb - The directory to search
Lfn - The Lfn to look for
Lfn - Temporary buffer to use to search for Lfn with (if < MAX_LFN then this
function may cause it to be allocated from pool if not large enough.
Retrn Value:
BOOLEAN TRUE if it exists, FALSE if not
--*/
{
CCB Ccb;
PDIRENT Dirent;
PBCB DirentBcb = NULL;
VBO DirentByteOffset;
BOOLEAN Result = FALSE;
ULONG Flags = 0;
PAGED_CODE();
//
// Pay performance penalty by forcing the compares to be case insensitive as
// opposed to grabbing more pool for a monocased copy of the Lfn. This is slight.
//
Ccb.UnicodeQueryTemplate = *Lfn;
Ccb.ContainsWildCards = FALSE;
Ccb.Flags = CCB_FLAG_SKIP_SHORT_NAME_COMPARE | CCB_FLAG_QUERY_TEMPLATE_MIXED;
_SEH2_TRY {
FatLocateDirent( IrpContext,
Dcb,
&Ccb,
0,
&Flags,
&Dirent,
&DirentBcb,
&DirentByteOffset,
NULL,
LfnTmp,
NULL );
} _SEH2_FINALLY {
if (DirentBcb) {
Result = TRUE;
}
FatUnpinBcb(IrpContext, DirentBcb);
} _SEH2_END;
return Result;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatLocateDirent (
IN PIRP_CONTEXT IrpContext,
IN PDCB ParentDirectory,
IN PCCB Ccb,
IN VBO OffsetToStartSearchFrom,
IN OUT PULONG Flags,
OUT PDIRENT *Dirent,
OUT PBCB *Bcb,
OUT PVBO ByteOffset,
OUT PBOOLEAN FileNameDos OPTIONAL,
IN OUT PUNICODE_STRING LongFileName OPTIONAL,
IN OUT PUNICODE_STRING OrigLongFileName OPTIONAL
)
/*++
Routine Description:
This routine locates on the disk an undeleted dirent matching a given name.
Arguments:
ParentDirectory - Supplies the DCB for the directory to search
Ccb - Contains a context control block with all matching information.
OffsetToStartSearchFrom - Supplies the VBO within the parent directory
from which to start looking for another real dirent.
Dirent - Receives a pointer to the located dirent if one was found
or NULL otherwise.
Bcb - Receives the Bcb for the located dirent if one was found or
NULL otherwise.
ByteOffset - Receives the VBO within the Parent directory for
the located dirent if one was found, or 0 otherwise.
FileNameDos - Receives TRUE if the element of the dirent we hit on
was the short (non LFN) side
LongFileName - If specified, this parameter returns the long file name
associated with the returned dirent. Note that it is the caller's
responsibility to provide the buffer (and set MaximumLength
accordingly) for this unicode string. The Length field is reset
to 0 by this routine on invocation. If the supplied buffer is not
large enough, a new one will be allocated from pool.
Return Value:
None.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
OEM_STRING Name;
UCHAR NameBuffer[12];
BOOLEAN UpcasedLfnValid = FALSE;
UNICODE_STRING UpcasedLfn = {0};
WCHAR LocalLfnBuffer[32];
BOOLEAN LfnInProgress = FALSE;
UCHAR LfnChecksum = 0;
ULONG LfnSize = 0;
ULONG LfnIndex = 0;
UCHAR Ordinal = 0;
VBO LfnByteOffset = 0;
TimerStart(Dbg);
PAGED_CODE();
DebugTrace(+1, Dbg, "FatLocateDirent\n", 0);
DebugTrace( 0, Dbg, " ParentDirectory = %p\n", ParentDirectory);
DebugTrace( 0, Dbg, " OffsetToStartSearchFrom = %08lx\n", OffsetToStartSearchFrom);
DebugTrace( 0, Dbg, " Dirent = %p\n", Dirent);
DebugTrace( 0, Dbg, " Bcb = %p\n", Bcb);
DebugTrace( 0, Dbg, " ByteOffset = %08lx\n", *ByteOffset);
//
// We must have acquired the parent or the vcb to synchronize with deletion. This
// is important since we can't survive racing a thread marking a series of lfn
// dirents deleted - we'd get a bogus ordinal, and otherwise get really messed up.
//
// This routine cannot do the acquire since it would be out-of-order with respect
// to the Bcb resources on iterative calls. Our order has Bcbs as the inferior resource.
//
// Deletion always grabs the parent (safely - this used to not be possible until the
// multiple fcb lockorder was fixed to be bottom up!). Deletion always occurs with
// the vcb held exclusive as well, and this will cover the cases where we can't easily
// hold the parent here, see above.
//
NT_ASSERT( ExIsResourceAcquiredSharedLite( ParentDirectory->Header.Resource ) ||
ExIsResourceAcquiredExclusiveLite( ParentDirectory->Header.Resource ) ||
ExIsResourceAcquiredSharedLite( &ParentDirectory->Vcb->Resource ) ||
ExIsResourceAcquiredExclusiveLite( &ParentDirectory->Vcb->Resource ));
//
// The algorithm here is pretty simple. We just walk through the
// parent directory until we:
//
// A) Find a matching entry.
// B) Can't Wait
// C) Hit the End of Directory
// D) Hit Eof
//
// In the first case we found it, in the latter three cases we did not.
//
UNREFERENCED_PARAMETER( Flags ); // future use
Name.MaximumLength = 12;
Name.Buffer = (PCHAR)NameBuffer;
UpcasedLfn.Length = 0;
UpcasedLfn.MaximumLength = sizeof( LocalLfnBuffer);
UpcasedLfn.Buffer = LocalLfnBuffer;
//
// If we were given a non-NULL Bcb, compute the new Dirent address
// from the prior one, or unpin the Bcb if the new Dirent is not pinned.
//
if (*Bcb != NULL) {
if ((OffsetToStartSearchFrom / PAGE_SIZE) == (*ByteOffset / PAGE_SIZE)) {
*Dirent += (OffsetToStartSearchFrom - *ByteOffset) / sizeof(DIRENT);
} else {
FatUnpinBcb( IrpContext, *Bcb );
}
}
//
// Init the Lfn if we were given one.
//
if (ARGUMENT_PRESENT(LongFileName)) {
LongFileName->Length = 0;
}
if (ARGUMENT_PRESENT(OrigLongFileName)) {
OrigLongFileName->Length = 0;
}
//
// Init the FileNameDos flag
//
if (FileNameDos) {
*FileNameDos = FALSE;
}
//
// Round up OffsetToStartSearchFrom to the nearest Dirent, and store
// in ByteOffset. Note that this wipes out the prior value.
//
*ByteOffset = (OffsetToStartSearchFrom + (sizeof(DIRENT) - 1))
& ~(sizeof(DIRENT) - 1);
_SEH2_TRY {
while ( TRUE ) {
BOOLEAN FoundValidLfn;
UpcasedLfnValid = FALSE;
//
// Try to read in the dirent
//
FatReadDirent( IrpContext,
ParentDirectory,
*ByteOffset,
Bcb,
Dirent,
&Status );
//
// If End Directory dirent or EOF, set all out parameters to
// indicate entry not found and, like, bail.
//
// Note that the order of evaluation here is important since we
// cannot check the first character of the dirent until after we
// know we are not beyond EOF
//
if ((Status == STATUS_END_OF_FILE) ||
((*Dirent)->FileName[0] == FAT_DIRENT_NEVER_USED)) {
DebugTrace( 0, Dbg, "End of directory: entry not found.\n", 0);
//
// If there is a Bcb, unpin it and set it to null
//
FatUnpinBcb( IrpContext, *Bcb );
*Dirent = NULL;
*ByteOffset = 0;
break;
}
//
// If the entry is marked deleted, skip. If there was an Lfn in
// progress we throw it out at this point.
//
if ((*Dirent)->FileName[0] == FAT_DIRENT_DELETED) {
LfnInProgress = FALSE;
goto GetNextDirent;
}
//
// If we have wandered onto an LFN entry, try to interpret it.
//
if (FatData.ChicagoMode &&
ARGUMENT_PRESENT(LongFileName) &&
((*Dirent)->Attributes == FAT_DIRENT_ATTR_LFN)) {
PLFN_DIRENT Lfn;
Lfn = (PLFN_DIRENT)*Dirent;
if (LfnInProgress) {
//
// Check for a proper continuation of the Lfn in progress.
//
if ((Lfn->Ordinal & FAT_LAST_LONG_ENTRY) ||
(Lfn->Ordinal == 0) ||
(Lfn->Ordinal != Ordinal - 1) ||
(Lfn->Checksum != LfnChecksum) ||
(Lfn->MustBeZero != 0)) {
//
// The Lfn is not proper, stop constructing it.
//
LfnInProgress = FALSE;
} else {
NT_ASSERT( ((LfnIndex % 13) == 0) && LfnIndex );
LfnIndex -= 13;
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+0],
&Lfn->Name1[0],
5*sizeof(WCHAR) );
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+5],
&Lfn->Name2[0],
6 * sizeof(WCHAR) );
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+11],
&Lfn->Name3[0],
2 * sizeof(WCHAR) );
Ordinal = Lfn->Ordinal;
LfnByteOffset = *ByteOffset;
}
}
//
// Now check (maybe again) if we should analyze this entry
// for a possible last entry.
//
if ((!LfnInProgress) &&
(Lfn->Ordinal & FAT_LAST_LONG_ENTRY) &&
((Lfn->Ordinal & ~FAT_LAST_LONG_ENTRY) <= MAX_LFN_DIRENTS) &&
(Lfn->MustBeZero == 0)) {
BOOLEAN CheckTail = FALSE;
Ordinal = Lfn->Ordinal & ~FAT_LAST_LONG_ENTRY;
//
// We're usually permissive (following the lead of Win9x) when we find
// malformation of the LFN dirent pile. I'm not sure this is a good idea,
// so I'm going to trigger corruption on this particularly ugly one. Perhaps
// we should come back and redo the original code here with this in mind in the
// future.
//
if (Ordinal == 0) {
//
// First LFN in the pile was zero marked as the last. This is never
// possible since oridinals are 1-based.
//
FatPopUpFileCorrupt( IrpContext, ParentDirectory );
FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR );
}
LfnIndex = (Ordinal - 1) * 13;
FatEnsureStringBufferEnough( LongFileName,
(USHORT)((LfnIndex + 13) << 1));
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+0],
&Lfn->Name1[0],
5*sizeof(WCHAR));
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+5],
&Lfn->Name2[0],
6 * sizeof(WCHAR) );
RtlCopyMemory( &LongFileName->Buffer[LfnIndex+11],
&Lfn->Name3[0],
2 * sizeof(WCHAR) );
//
// Now compute the Lfn size and make sure that the tail
// bytes are correct.
//
while (LfnIndex != (ULONG)Ordinal * 13) {
if (!CheckTail) {
if (LongFileName->Buffer[LfnIndex] == 0x0000) {
LfnSize = LfnIndex;
CheckTail = TRUE;
}
} else {
if (LongFileName->Buffer[LfnIndex] != 0xffff) {
break;
}
}
LfnIndex += 1;
}
//
// If we exited this loop prematurely, the LFN is not valid.
//
if (LfnIndex == (ULONG)Ordinal * 13) {
//
// If we didn't find the NULL terminator, then the size
// is LfnIndex.
//
if (!CheckTail) {
LfnSize = LfnIndex;
}
LfnIndex -= 13;
LfnInProgress = TRUE;
LfnChecksum = Lfn->Checksum;
LfnByteOffset = *ByteOffset;
}
}
//
// Move on to the next dirent.
//
goto GetNextDirent;
}
//
// If this is the volume label, skip. Note that we never arrive here
// while building the LFN. If we did, we weren't asked to find LFNs
// and that is another good reason to skip this LFN fragment.
//
if (FlagOn((*Dirent)->Attributes, FAT_DIRENT_ATTR_VOLUME_ID)) {
//
// If we actually were asked to hand back volume labels,
// do it.
//
if (FlagOn(Ccb->Flags, CCB_FLAG_MATCH_VOLUME_ID)) {
break;
}
goto GetNextDirent;
}
//
// We may have just stepped off a valid Lfn run. Check to see if
// it is indeed valid for the following dirent.
//
if (LfnInProgress &&
(*ByteOffset == LfnByteOffset + sizeof(DIRENT)) &&
(LfnIndex == 0) &&
(FatComputeLfnChecksum(*Dirent) == LfnChecksum)) {
NT_ASSERT( Ordinal == 1);
FoundValidLfn = TRUE;
LongFileName->Length = (USHORT)(LfnSize * sizeof(WCHAR));
if (ARGUMENT_PRESENT(OrigLongFileName)) {
*OrigLongFileName = *LongFileName;
}
} else {
FoundValidLfn = FALSE;
}
//
// If we are supposed to match all entries, then match this entry.
//
if (FlagOn(Ccb->Flags, CCB_FLAG_MATCH_ALL)) {
break;
}
//
// Check against the short name given if one was.
//
if (!FlagOn( Ccb->Flags, CCB_FLAG_SKIP_SHORT_NAME_COMPARE )) {
if (Ccb->ContainsWildCards) {
//
// If we get one, note that all out parameters are already set.
//
(VOID)Fat8dot3ToString( IrpContext, (*Dirent), FALSE, &Name );
//
// For fat we special case the ".." dirent because we want it to
// match ????????.??? and to do that we change ".." to "." before
// calling the Fsrtl routine. But only do this if the expression
// is greater than one character long.
//
if ((Name.Length == 2) &&
(Name.Buffer[0] == '.') &&
(Name.Buffer[1] == '.') &&
(Ccb->OemQueryTemplate.Wild.Length > 1)) {
Name.Length = 1;
}
if (FatIsNameInExpression( IrpContext,
Ccb->OemQueryTemplate.Wild,
Name)) {
DebugTrace( 0, Dbg, "Entry found: Name = \"%Z\"\n", &Name);
DebugTrace( 0, Dbg, " VBO = %08lx\n", *ByteOffset);
if (FileNameDos) {
*FileNameDos = TRUE;
}
SetFlag( Ccb->Flags, CCB_FLAG_OPENED_BY_SHORTNAME );
break;
}
} else {
//
// Do the quickest 8.3 equivalency check possible
//
if (!FlagOn((*Dirent)->Attributes, FAT_DIRENT_ATTR_VOLUME_ID) &&
(*(PULONG)&(Ccb->OemQueryTemplate.Constant[0]) == *(PULONG)&((*Dirent)->FileName[0])) &&
(*(PULONG)&(Ccb->OemQueryTemplate.Constant[4]) == *(PULONG)&((*Dirent)->FileName[4])) &&
(*(PUSHORT)&(Ccb->OemQueryTemplate.Constant[8]) == *(PUSHORT)&((*Dirent)->FileName[8])) &&
(*(PUCHAR)&(Ccb->OemQueryTemplate.Constant[10]) == *(PUCHAR)&((*Dirent)->FileName[10]))) {
DebugTrace( 0, Dbg, "Entry found.\n", 0);
if (FileNameDos) {
*FileNameDos = TRUE;
}
SetFlag( Ccb->Flags, CCB_FLAG_OPENED_BY_SHORTNAME );
break;
}
}
}
//
// No matches were found with the short name. If an LFN exists,
// use it for the search.
//
if (FoundValidLfn) {
//
// First do a quick check here for different sized constant
// name and expression before upcasing.
//
if (!Ccb->ContainsWildCards &&
(Ccb->UnicodeQueryTemplate.Length != (USHORT)(LfnSize * sizeof(WCHAR)))) {
//
// Move on to the next dirent.
//
FoundValidLfn = FALSE;
LongFileName->Length = 0;
if (OrigLongFileName) {
OrigLongFileName->Length = 0;
}
goto GetNextDirent;
}
if (!UpcasedLfnValid) {
//
// We need to upcase the name we found on disk.
// We need a buffer. Try to avoid doing an allocation.
//
FatEnsureStringBufferEnough( &UpcasedLfn,
LongFileName->Length);
Status = RtlUpcaseUnicodeString( &UpcasedLfn,
LongFileName,
FALSE );
if (!NT_SUCCESS(Status)) {
FatNormalizeAndRaiseStatus( IrpContext, Status );
}
UpcasedLfnValid = TRUE;
}
//
// Do the compare
//
if (Ccb->ContainsWildCards) {
if (FsRtlIsNameInExpression( &Ccb->UnicodeQueryTemplate,
&UpcasedLfn,
TRUE,
NULL )) {
break;
}
} else {
if (FsRtlAreNamesEqual( &Ccb->UnicodeQueryTemplate,
&UpcasedLfn,
BooleanFlagOn( Ccb->Flags, CCB_FLAG_QUERY_TEMPLATE_MIXED ),
NULL )) {
break;
}
}
}
//
// This long name was not a match. Zero out the Length field.
//
if (FoundValidLfn) {
FoundValidLfn = FALSE;
LongFileName->Length = 0;
if (OrigLongFileName) {
OrigLongFileName->Length = 0;
}
}
GetNextDirent:
//
// Move on to the next dirent.
//
*ByteOffset += sizeof(DIRENT);
*Dirent += 1;
}
} _SEH2_FINALLY {
FatFreeStringBuffer( &UpcasedLfn );
} _SEH2_END;
DebugTrace(-1, Dbg, "FatLocateDirent -> (VOID)\n", 0);
TimerStop(Dbg,"FatLocateDirent");
return;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatLocateSimpleOemDirent (
IN PIRP_CONTEXT IrpContext,
IN PDCB ParentDirectory,
IN POEM_STRING FileName,
OUT PDIRENT *Dirent,
OUT PBCB *Bcb,
OUT PVBO ByteOffset
)
/*++
Routine Description:
This routine locates on the disk an undelted simple Oem dirent. By simple
I mean that FileName cannot contain any extended characters, and we do
not search LFNs or return them.
Arguments:
ParentDirectory - Supplies the DCB for the directory in which
to search
FileName - Supplies the filename to search for. The name may contain
wild cards
OffsetToStartSearchFrom - Supplies the VBO within the parent directory
from which to start looking for another real dirent.
Dirent - Receives a pointer to the located dirent if one was found
or NULL otherwise.
Bcb - Receives the Bcb for the located dirent if one was found or
NULL otherwise.
ByteOffset - Receives the VBO within the Parent directory for
the located dirent if one was found, or 0 otherwise.
Return Value:
None.
--*/
{
CCB LocalCcb;
PAGED_CODE();
//
// Note, this routine is called rarely, so performance is not critical.
// Just fill in a Ccb structure on my stack with the values that are
// required.
//
FatStringTo8dot3( IrpContext,
*FileName,
&LocalCcb.OemQueryTemplate.Constant );
LocalCcb.ContainsWildCards = FALSE;
LocalCcb.Flags = 0;
FatLocateDirent( IrpContext,
ParentDirectory,
&LocalCcb,
0,
NULL,
Dirent,
Bcb,
ByteOffset,
NULL,
NULL,
NULL );
return;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatLocateVolumeLabel (
IN PIRP_CONTEXT IrpContext,
IN PVCB Vcb,
OUT PDIRENT *Dirent,
OUT PBCB *Bcb,
OUT PVBO ByteOffset
)
/*++
Routine Description:
This routine locates on the disk a dirent representing the volume
label. It does this by searching the root directory for a special
volume label dirent.
Arguments:
Vcb - Supplies the VCB for the volume to search
Dirent - Receives a pointer to the located dirent if one was found
or NULL otherwise.
Bcb - Receives the Bcb for the located dirent if one was found or
NULL otherwise.
ByteOffset - Receives the VBO within the Parent directory for
the located dirent if one was found, or 0 otherwise.
Return Value:
None.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatLocateVolumeLabel\n", 0);
DebugTrace( 0, Dbg, " Vcb = %p\n", Vcb);
DebugTrace( 0, Dbg, " Dirent = %p\n", Dirent);
DebugTrace( 0, Dbg, " Bcb = %p\n", Bcb);
DebugTrace( 0, Dbg, " ByteOffset = %08lx\n", *ByteOffset);
//
// The algorithm here is really simple. We just walk through the
// root directory until we:
//
// A) Find the non-deleted volume label
// B) Can't Wait
// C) Hit the End of Directory
// D) Hit Eof
//
// In the first case we found it, in the latter three cases we did not.
//
*Bcb = NULL;
*ByteOffset = 0;
while ( TRUE ) {
//
// Try to read in the dirent
//
FatReadDirent( IrpContext,
Vcb->RootDcb,
*ByteOffset,
Bcb,
Dirent,
&Status );
//
// If End Directory dirent or EOF, set all out parameters to
// indicate volume label not found and, like, bail.
//
// Note that the order of evaluation here is important since we cannot
// check the first character of the dirent until after we know we
// are not beyond EOF
//
if ((Status == STATUS_END_OF_FILE) ||
((*Dirent)->FileName[0] == FAT_DIRENT_NEVER_USED)) {
DebugTrace( 0, Dbg, "Volume label not found.\n", 0);
//
// If there is a Bcb, unpin it and set it to null
//
FatUnpinBcb( IrpContext, *Bcb );
*Dirent = NULL;
*ByteOffset = 0;
break;
}
//
// If the entry is the non-deleted volume label break from the loop.
//
// Note that all out parameters are already correctly set.
//
if ((((*Dirent)->Attributes & ~FAT_DIRENT_ATTR_ARCHIVE) == FAT_DIRENT_ATTR_VOLUME_ID) &&
((*Dirent)->FileName[0] != FAT_DIRENT_DELETED)) {
DebugTrace( 0, Dbg, "Volume label found at VBO = %08lx\n", *ByteOffset);
//
// We may set this dirty, so pin it.
//
FatPinMappedData( IrpContext,
Vcb->RootDcb,
*ByteOffset,
sizeof(DIRENT),
Bcb );
break;
}
//
// Move on to the next dirent.
//
*ByteOffset += sizeof(DIRENT);
*Dirent += 1;
}
DebugTrace(-1, Dbg, "FatLocateVolumeLabel -> (VOID)\n", 0);
return;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatGetDirentFromFcbOrDcb (
IN PIRP_CONTEXT IrpContext,
IN PFCB FcbOrDcb,
IN BOOLEAN ReturnOnFailure,
OUT PDIRENT *Dirent,
OUT PBCB *Bcb
)
/*++
Routine Description:
This routine reads locates on the disk the dirent denoted by the
specified Fcb/Dcb.
Arguments:
FcbOrDcb - Supplies the FCB/DCB for the file/directory whose dirent
we are trying to read in. This must not be the root dcb.
Dirent - Receives a pointer to the dirent
Bcb - Receives the Bcb for the dirent
Return Value:
None.
--*/
{
NTSTATUS DontCare = STATUS_SUCCESS;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatGetDirentFromFcbOrDcb\n", 0);
DebugTrace( 0, Dbg, " FcbOrDcb = %p\n", FcbOrDcb);
DebugTrace( 0, Dbg, " Dirent = %p\n", Dirent);
DebugTrace( 0, Dbg, " Bcb = %p\n", Bcb);
//
// Assert that we are not attempting this on the root directory.
//
NT_ASSERT( NodeType(FcbOrDcb) != FAT_NTC_ROOT_DCB );
//
// We know the offset of the dirent within the directory file,
// so we just read it (with pinning).
//
FatReadDirectoryFile( IrpContext,
FcbOrDcb->ParentDcb,
FcbOrDcb->DirentOffsetWithinDirectory,
sizeof(DIRENT),
TRUE,
Bcb,
(PVOID *)Dirent,
&DontCare );
//
// Previous call can fail. We used to assert success, but we use this
// as part of volume verification (DetermineAndMarkFcbCondition) after
// media has been removed. Clearly the directory could shrink and we
// would try to read beyond filesize.
//
// The caller will note this via NULL pointers for Bcb/Buffer. Note that
// both asserts below are OK since this should never happen fixed media.
//
// This was a Prefix catch.
//
NT_ASSERT( FlagOn( FcbOrDcb->Vcb->VcbState, VCB_STATE_FLAG_REMOVABLE_MEDIA) ||
NT_SUCCESS( DontCare ));
//
// Note also that the only way this could fail is if the Fcb was being
// verified. This can't happen if the Fcb is in good condition.
//
// Also a Prefix catch.
//
NT_ASSERT( NT_SUCCESS( DontCare ) || FcbOrDcb->FcbCondition == FcbNeedsToBeVerified );
//
// This should never happen except in very specific cases (during volume
// verify) but we'll handle and raise here to save all callers checking the
// pointers.
//
if ((NULL == *Dirent) && !ReturnOnFailure) {
NT_ASSERT( FALSE);
FatRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR);
}
DebugTrace(-1, Dbg, "FatGetDirentFromFcbOrDcb -> (VOID)\n", 0);
}
_Requires_lock_held_(_Global_critical_region_)
BOOLEAN
FatIsDirectoryEmpty (
IN PIRP_CONTEXT IrpContext,
IN PDCB Dcb
)
/*++
Routine Description:
This routine indicates to the caller if the specified directory
is empty. (i.e., it is not the root dcb and it only contains
the "." and ".." entries, or deleted files).
Arguments:
Dcb - Supplies the DCB for the directory being queried.
Return Value:
BOOLEAN - Returns TRUE if the directory is empty and
FALSE if the directory and is not empty.
--*/
{
PBCB Bcb;
ULONG ByteOffset;
PDIRENT Dirent = NULL;
BOOLEAN IsDirectoryEmpty = FALSE;
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
DebugTrace(+1, Dbg, "FatIsDirectoryEmpty\n", 0);
DebugTrace( 0, Dbg, " Dcb = %p\n", Dcb);
DebugTrace( 0, Dbg, " IsDirectoryEmpty = %08lx\n", IsDirectoryEmpty);
//
// Check to see if the first entry is an and of directory marker.
// For the root directory we check at Vbo = 0, for normal directories
// we check after the "." and ".." entries.
//
ByteOffset = (NodeType(Dcb) == FAT_NTC_ROOT_DCB) ? 0 : 2*sizeof(DIRENT);
//
// We just march through the directory looking for anything other
// than deleted files, LFNs, an EOF, or end of directory marker.
//
Bcb = NULL;
_SEH2_TRY {
while ( TRUE ) {
//
// Try to read in the dirent
//
FatReadDirent( IrpContext,
Dcb,
ByteOffset,
&Bcb,
&Dirent,
&Status );
//
// If End Directory dirent or EOF, set IsDirectoryEmpty to TRUE and,
// like, bail.
//
// Note that the order of evaluation here is important since we cannot
// check the first character of the dirent until after we know we
// are not beyond EOF
//
if ((Status == STATUS_END_OF_FILE) ||
(Dirent->FileName[0] == FAT_DIRENT_NEVER_USED)) {
DebugTrace( 0, Dbg, "Empty. Last exempt entry at VBO = %08lx\n", ByteOffset);
IsDirectoryEmpty = TRUE;
break;
}
//
// If this dirent is NOT deleted or an LFN set IsDirectoryEmpty to
// FALSE and, like, bail.
//
if ((Dirent->FileName[0] != FAT_DIRENT_DELETED) &&
(Dirent->Attributes != FAT_DIRENT_ATTR_LFN)) {
break;
}
//
// Move on to the next dirent.
//
ByteOffset += sizeof(DIRENT);
Dirent += 1;
}
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
DebugTrace(-1, Dbg, "FatIsDirectoryEmpty -> %ld\n", IsDirectoryEmpty);
return IsDirectoryEmpty;
}
VOID
FatConstructDirent (
IN PIRP_CONTEXT IrpContext,
IN OUT PDIRENT Dirent,
IN POEM_STRING FileName,
IN BOOLEAN ComponentReallyLowercase,
IN BOOLEAN ExtensionReallyLowercase,
IN PUNICODE_STRING Lfn OPTIONAL,
IN USHORT Attributes,
IN BOOLEAN ZeroAndSetTimeFields,
IN PLARGE_INTEGER SetCreationTime OPTIONAL
)
/*++
Routine Description:
This routine modifies the fields of a dirent.
Arguments:
Dirent - Supplies the dirent being modified.
FileName - Supplies the name to store in the Dirent. This
name must not contain wildcards.
ComponentReallyLowercase - This boolean indicates that the User Specified
compoent name was really all a-z and < 0x80 characters. We set the
magic bit in this case.
ExtensionReallyLowercase - Same as above, but for the extension.
Lfn - May supply a long file name.
Attributes - Supplies the attributes to store in the dirent
ZeroAndSetTimeFields - Tells whether or not to initially zero the dirent
and update the time fields.
SetCreationTime - If specified, contains a timestamp to use as the creation
time of this dirent
Return Value:
None.
--*/
{
PAGED_CODE();
DebugTrace(+1, Dbg, "FatConstructDirent\n", 0);
DebugTrace( 0, Dbg, " Dirent = %p\n", Dirent);
DebugTrace( 0, Dbg, " FileName = %Z\n", FileName);
DebugTrace( 0, Dbg, " Attributes = %08lx\n", Attributes);
if (ZeroAndSetTimeFields) {
RtlZeroMemory( Dirent, sizeof(DIRENT) );
}
//
// We just merrily go and fill up the dirent with the fields given.
//
FatStringTo8dot3( IrpContext, *FileName, (PFAT8DOT3)&Dirent->FileName[0] );
if (ZeroAndSetTimeFields || SetCreationTime) {
LARGE_INTEGER Time, SaveTime;
KeQuerySystemTime( &Time );
if (FatData.ChicagoMode) {
if (!SetCreationTime || !FatNtTimeToFatTime( IrpContext,
SetCreationTime,
FALSE,
&Dirent->CreationTime,
&Dirent->CreationMSec )) {
//
// No tunneled time or the tunneled time was bogus. Since we aren't
// responsible for initializing the to-be-created Fcb with creation
// time, we can't do the usual thing and let NtTimeToFatTime perform
// rounding on the timestamp - this would mess up converting to the
// LastWriteTime below.
//
SaveTime = Time;
if (!FatNtTimeToFatTime( IrpContext,
&SaveTime,
FALSE,
&Dirent->CreationTime,
&Dirent->CreationMSec )) {
//
// Failed again. Wow.
//
RtlZeroMemory( &Dirent->CreationTime, sizeof(FAT_TIME_STAMP));
Dirent->CreationMSec = 0;
}
}
}
if (ZeroAndSetTimeFields) {
//
// We only touch the other timestamps if we are initializing the dirent
//
if (!FatNtTimeToFatTime( IrpContext,
&Time,
TRUE,
&Dirent->LastWriteTime,
NULL )) {
DebugTrace( 0, Dbg, "Current time invalid.\n", 0);
RtlZeroMemory( &Dirent->LastWriteTime, sizeof(FAT_TIME_STAMP) );
}
if (FatData.ChicagoMode) {
Dirent->LastAccessDate = Dirent->LastWriteTime.Date;
}
}
}
//
// Copy the attributes
//
Dirent->Attributes = (UCHAR)Attributes;
//
// Set the magic bit here, to tell dirctrl.c that this name is really
// lowercase.
//
Dirent->NtByte = 0;
if (ComponentReallyLowercase) {
SetFlag( Dirent->NtByte, FAT_DIRENT_NT_BYTE_8_LOWER_CASE );
}
if (ExtensionReallyLowercase) {
SetFlag( Dirent->NtByte, FAT_DIRENT_NT_BYTE_3_LOWER_CASE );
}
//
// See if we have to create an Lfn entry
//
if (ARGUMENT_PRESENT(Lfn)) {
UCHAR DirentChecksum;
UCHAR DirentsInLfn;
UCHAR LfnOrdinal;
PWCHAR LfnBuffer;
PLFN_DIRENT LfnDirent;
NT_ASSERT( FatData.ChicagoMode );
DirentChecksum = FatComputeLfnChecksum( Dirent );
LfnOrdinal =
DirentsInLfn = (UCHAR)FAT_LFN_DIRENTS_NEEDED(Lfn);
LfnBuffer = &Lfn->Buffer[(DirentsInLfn - 1) * 13];
NT_ASSERT( DirentsInLfn <= MAX_LFN_DIRENTS );
for (LfnDirent = (PLFN_DIRENT)Dirent - DirentsInLfn;
LfnDirent < (PLFN_DIRENT)Dirent;
LfnDirent += 1, LfnOrdinal -= 1, LfnBuffer -= 13) {
WCHAR FinalLfnBuffer[13];
PWCHAR Buffer;
//
// We need to special case the "final" dirent.
//
if (LfnOrdinal == DirentsInLfn) {
ULONG i;
ULONG RemainderChars;
RemainderChars = (Lfn->Length / sizeof(WCHAR)) % 13;
LfnDirent->Ordinal = LfnOrdinal | FAT_LAST_LONG_ENTRY;
if (RemainderChars != 0) {
RtlCopyMemory( FinalLfnBuffer,
LfnBuffer,
RemainderChars * sizeof(WCHAR) );
for (i = RemainderChars; i < 13; i++) {
//
// Figure out which character to use.
//
if (i == RemainderChars) {
FinalLfnBuffer[i] = 0x0000;
} else {
FinalLfnBuffer[i] = 0xffff;
}
}
Buffer = FinalLfnBuffer;
} else {
Buffer = LfnBuffer;
}
} else {
LfnDirent->Ordinal = LfnOrdinal;
Buffer = LfnBuffer;
}
//
// Now fill in the name.
//
RtlCopyMemory( &LfnDirent->Name1[0],
&Buffer[0],
5 * sizeof(WCHAR) );
RtlCopyMemory( &LfnDirent->Name2[0],
&Buffer[5],
6 * sizeof(WCHAR) );
RtlCopyMemory( &LfnDirent->Name3[0],
&Buffer[11],
2 * sizeof(WCHAR) );
//
// And the other fields
//
LfnDirent->Attributes = FAT_DIRENT_ATTR_LFN;
LfnDirent->Type = 0;
LfnDirent->Checksum = DirentChecksum;
LfnDirent->MustBeZero = 0;
}
}
DebugTrace(-1, Dbg, "FatConstructDirent -> (VOID)\n", 0);
return;
}
VOID
FatConstructLabelDirent (
IN PIRP_CONTEXT IrpContext,
IN OUT PDIRENT Dirent,
IN POEM_STRING Label
)
/*++
Routine Description:
This routine modifies the fields of a dirent to be used for a label.
Arguments:
Dirent - Supplies the dirent being modified.
Label - Supplies the name to store in the Dirent. This
name must not contain wildcards.
Return Value:
None.
--*/
{
PAGED_CODE();
DebugTrace(+1, Dbg, "FatConstructLabelDirent\n", 0);
DebugTrace( 0, Dbg, " Dirent = %p\n", Dirent);
DebugTrace( 0, Dbg, " Label = %Z\n", Label);
RtlZeroMemory( Dirent, sizeof(DIRENT) );
//
// We just merrily go and fill up the dirent with the fields given.
//
RtlCopyMemory( Dirent->FileName, Label->Buffer, Label->Length );
//
// Pad the label with spaces, not nulls.
//
RtlFillMemory( &Dirent->FileName[Label->Length], 11 - Label->Length, ' ');
Dirent->LastWriteTime = FatGetCurrentFatTime( IrpContext );
Dirent->Attributes = FAT_DIRENT_ATTR_VOLUME_ID;
Dirent->ExtendedAttributes = 0;
Dirent->FileSize = 0;
DebugTrace(-1, Dbg, "FatConstructLabelDirent -> (VOID)\n", 0);
return;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatSetFileSizeInDirent (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PULONG AlternativeFileSize OPTIONAL
)
/*++
Routine Description:
This routine saves the file size in an fcb into its dirent.
Arguments:
Fcb - Supplies the Fcb being referenced
AlternativeFileSize - If non-null we use the ULONG it points to as
the new file size. Otherwise we use the one in the Fcb.
Return Value:
None.
--*/
{
PDIRENT Dirent;
PBCB DirentBcb;
PAGED_CODE();
NT_ASSERT( Fcb->FcbCondition == FcbGood );
FatGetDirentFromFcbOrDcb( IrpContext,
Fcb,
FALSE,
&Dirent,
&DirentBcb );
_SEH2_TRY {
Dirent->FileSize = ARGUMENT_PRESENT( AlternativeFileSize ) ?
*AlternativeFileSize : Fcb->Header.FileSize.LowPart;
FatSetDirtyBcb( IrpContext, DirentBcb, Fcb->Vcb, TRUE );
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, DirentBcb );
} _SEH2_END;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatSetFileSizeInDirentNoRaise (
IN PIRP_CONTEXT IrpContext,
IN PFCB Fcb,
IN PULONG AlternativeFileSize OPTIONAL
)
/*++
Routine Description:
This routine saves the file size in an fcb into its dirent.
All exceptions thrown from FatSetFileSizeInDirent are
silently swallowed.
Arguments:
Fcb - Supplies the Fcb being referenced
AlternativeFileSize - If non-null we use the ULONG it points to as
the new file size. Otherwise we use the one in the Fcb.
Return Value:
None.
--*/
{
_SEH2_TRY {
FatSetFileSizeInDirent( IrpContext, Fcb, AlternativeFileSize );
} _SEH2_EXCEPT(FatExceptionFilter( IrpContext, _SEH2_GetExceptionInformation() )) {
NOTHING;
} _SEH2_END;
}
_Requires_lock_held_(_Global_critical_region_)
VOID
FatUpdateDirentFromFcb (
IN PIRP_CONTEXT IrpContext,
IN PFILE_OBJECT FileObject,
IN PFCB FcbOrDcb,
IN PCCB Ccb
)
/*++
Routine Description:
This routine modifies an objects directory entry based on the hints
that have been built up over previous operations on a handle. Notify
change filters are built and fired as a result of these updates.
Arguments:
FileObject - Fileobject representing the handle involved
FcbOrDcb - File/Dir involved
Ccb - User context involved
Return Value:
None.
--*/
{
BOOLEAN SetArchiveBit;
BOOLEAN UpdateFileSize;
BOOLEAN UpdateLastWriteTime;
BOOLEAN UpdateLastAccessTime;
BOOLEAN UpdateDirent = FALSE;
PDIRENT Dirent;
PBCB DirentBcb = NULL;
ULONG NotifyFilter = 0;
FAT_TIME_STAMP CurrentFatTime = {0};
LARGE_INTEGER CurrentTime;
LARGE_INTEGER CurrentDay = {0};
LARGE_INTEGER LastAccessDay;
PAGED_CODE();
//
// Nothing to do if the fcb is bad, volume is readonly or we got the
// root dir.
//
if (FcbOrDcb->FcbCondition != FcbGood ||
NodeType(FcbOrDcb) == FAT_NTC_ROOT_DCB ||
FlagOn(FcbOrDcb->Vcb->VcbState, VCB_STATE_FLAG_WRITE_PROTECTED)) {
return;
}
//
// Check if we should be changing the time or file size and set
// the archive bit on the file.
//
KeQuerySystemTime( &CurrentTime );
//
// Note that we HAVE to use BooleanFlagOn() here because
// FO_FILE_SIZE_CHANGED > 0x80 (i.e., not in the first byte).
//
SetArchiveBit = BooleanFlagOn(FileObject->Flags, FO_FILE_MODIFIED);
UpdateLastWriteTime = FlagOn(FileObject->Flags, FO_FILE_MODIFIED) &&
!FlagOn(Ccb->Flags, CCB_FLAG_USER_SET_LAST_WRITE);
UpdateFileSize = NodeType(FcbOrDcb) == FAT_NTC_FCB &&
BooleanFlagOn(FileObject->Flags, FO_FILE_SIZE_CHANGED);
//
// Do one further check here of access time. Only update it if
// the current version is at least one day old. We know that
// the current FcbOrDcb->LastAccessTime corresponds to 12 midnight local
// time, so just see if the current time is on the same day.
//
if (FatData.ChicagoMode &&
(UpdateLastWriteTime ||
FlagOn(FileObject->Flags, FO_FILE_FAST_IO_READ)) &&
!FlagOn(Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS)) {
ExSystemTimeToLocalTime( &FcbOrDcb->LastAccessTime, &LastAccessDay );
ExSystemTimeToLocalTime( &CurrentTime, &CurrentDay );
LastAccessDay.QuadPart /= FatOneDay.QuadPart;
CurrentDay.QuadPart /= FatOneDay.QuadPart;
if (LastAccessDay.LowPart != CurrentDay.LowPart) {
UpdateLastAccessTime = TRUE;
} else {
UpdateLastAccessTime = FALSE;
}
} else {
UpdateLastAccessTime = FALSE;
}
if (SetArchiveBit ||
UpdateFileSize ||
UpdateLastWriteTime ||
UpdateLastAccessTime
) {
DebugTrace(0, Dbg, "Update Time and/or file size on File/Dir\n", 0);
_SEH2_TRY {
_SEH2_TRY {
#if (NTDDI_VERSION >= NTDDI_WIN8)
//
// Break parent directory oplock. Directory oplock breaks are
// always advisory, so we will never block/get STATUS_PENDING here.
//
if (FcbOrDcb->ParentDcb != NULL) {
FsRtlCheckOplockEx( FatGetFcbOplock(FcbOrDcb->ParentDcb),
IrpContext->OriginatingIrp,
OPLOCK_FLAG_PARENT_OBJECT,
NULL,
NULL,
NULL );
}
#endif
//
// Get the dirent
//
FatGetDirentFromFcbOrDcb( IrpContext,
FcbOrDcb,
FALSE,
&Dirent,
&DirentBcb );
if (UpdateLastWriteTime || UpdateLastAccessTime) {
(VOID)FatNtTimeToFatTime( IrpContext,
&CurrentTime,
TRUE,
&CurrentFatTime,
NULL );
}
if (SetArchiveBit) {
Dirent->Attributes |= FILE_ATTRIBUTE_ARCHIVE;
FcbOrDcb->DirentFatFlags |= FILE_ATTRIBUTE_ARCHIVE;
NotifyFilter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
UpdateDirent = TRUE;
}
if (UpdateLastWriteTime) {
//
// Update its time of last write
//
FcbOrDcb->LastWriteTime = CurrentTime;
Dirent->LastWriteTime = CurrentFatTime;
//
// We call the notify package to report that the
// last modification time has changed.
//
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
UpdateDirent = TRUE;
}
if (UpdateLastAccessTime) {
//
// Now we have to truncate the local time down
// to the current day, then convert back to UTC.
//
FcbOrDcb->LastAccessTime.QuadPart =
CurrentDay.QuadPart * FatOneDay.QuadPart;
ExLocalTimeToSystemTime( &FcbOrDcb->LastAccessTime,
&FcbOrDcb->LastAccessTime );
Dirent->LastAccessDate = CurrentFatTime.Date;
//
// We call the notify package to report that the
// last access time has changed.
//
NotifyFilter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
UpdateDirent = TRUE;
}
if (UpdateFileSize) {
//
// Perhaps we were called to make certain that the
// filesize on disc was updated - don't bother updating
// and firing the filter if nothing changed.
//
NT_ASSERT( NodeType(FcbOrDcb) == FAT_NTC_FCB );
if (Dirent->FileSize != FcbOrDcb->Header.FileSize.LowPart) {
//
// Update the dirent file size
//
Dirent->FileSize = FcbOrDcb->Header.FileSize.LowPart;
//
// We call the notify package to report that the
// size has changed.
//
NotifyFilter |= FILE_NOTIFY_CHANGE_SIZE;
UpdateDirent = TRUE;
}
}
FatNotifyReportChange( IrpContext,
FcbOrDcb->Vcb,
FcbOrDcb,
NotifyFilter,
FILE_ACTION_MODIFIED );
if (UpdateDirent) {
//
// If all we did was update last access time,
// don't mark the volume dirty.
//
FatSetDirtyBcb( IrpContext,
DirentBcb,
NotifyFilter == FILE_NOTIFY_CHANGE_LAST_ACCESS ?
NULL : FcbOrDcb->Vcb,
TRUE );
}
} _SEH2_EXCEPT( FsRtlIsNtstatusExpected(_SEH2_GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) {
FatResetExceptionState( IrpContext );
} _SEH2_END;
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, DirentBcb );
} _SEH2_END;
}
}
//
// Internal support routine
//
UCHAR
FatComputeLfnChecksum (
PDIRENT Dirent
)
/*++
Routine Description:
This routine computes the Chicago long file name checksum.
Arguments:
Dirent - Specifies the dirent that we are to compute a checksum for.
Return Value:
The checksum.
--*/
{
ULONG i;
UCHAR Checksum;
PAGED_CODE();
Checksum = Dirent->FileName[0];
for (i=1; i < 11; i++) {
Checksum = ((Checksum & 1) ? 0x80 : 0) +
(Checksum >> 1) +
Dirent->FileName[i];
}
return Checksum;
}
#if 0 // It turns out Win95 is still creating short names without a ~
//
// Internal support routine
//
BOOLEAN
FatIsLfnPairValid (
PWCHAR Lfn,
ULONG LfnSize,
PDIRENT Dirent
)
/*++
Routine Description:
This routine does a few more checks to make sure that a LFN/short
name pairing is legitimate. Basically this is the test:
Pairing is valid if:
DIRENT has a ~ character ||
(LFN is 8.3 compliant &&
(LFN has extended character(s) ? TRUE :
LFN upcases to DIRENT))
When checking for the presence of a tilda character in the short
name, note that we purposely do a single byte search instead of
converting the name to UNICODE and looking there for the tilda.
This protects us from accidently missing the tilda if the
preceding byte is a lead byte in the current Oem code page,
but wasn't in the Oem code page that created the file.
Also note that if the LFN is longer than 12 characters, then the
second clause of the OR must be false.
Arguments:
Lfn - Points to a buffer of UNICODE chars.
LfnSize - This is the size of the LFN in characters.
Dirent - Specifies the dirent we are to consider.
Return Value:
TRUE if the Lfn/DIRENT form a legitimate pair, FALSE otherwise.
--*/
{
ULONG i;
BOOLEAN ExtendedChars;
ULONG DirentBuffer[3];
PUCHAR DirentName;
ULONG DirentIndex;
BOOLEAN DotEncountered;
//
// First, look for a tilda
//
for (i=0; i<11; i++) {
if (Dirent->FileName[i] == '~') {
return TRUE;
}
}
//
// No tilda. If the LFN is longer than 12 characters, then it can
// neither upcase to the DIRENT nor be 8.3 complient.
//
if (LfnSize > 12) {
return FALSE;
}
//
// Now see if the name is 8.3, and build an upcased DIRENT as well.
//
DirentBuffer[0] = 0x20202020;
DirentBuffer[1] = 0x20202020;
DirentBuffer[2] = 0x20202020;
DirentName = (PUCHAR)DirentBuffer;
ExtendedChars = FALSE;
DirentIndex = 0;
DotEncountered = FALSE;
for (i=0; i < LfnSize; i++) {
//
// Do dot transition work
//
if (Lfn[i] == L'.') {
if (DotEncountered ||
(i > 8) ||
((LfnSize - i) > 4) ||
(i && Lfn[i-1] == L' ')) {
return FALSE;
}
DotEncountered = TRUE;
DirentIndex = 8;
continue;
}
//
// The character must be legal in order to be 8.3
//
if ((Lfn[i] < 0x80) &&
!FsRtlIsAnsiCharacterLegalFat((UCHAR)Lfn[i], FALSE)) {
return FALSE;
}
//
// If the name contains no extended chars, continue building DIRENT
//
if (!ExtendedChars) {
if (Lfn[i] > 0x7f) {
ExtendedChars = TRUE;
} else {
DirentName[DirentIndex++] = (UCHAR) (
Lfn[i] < 'a' ? Lfn[i] : Lfn[i] <= 'z' ? Lfn[i] - ('a' - 'A') : Lfn[i]);
}
}
}
//
// If the LFN ended in a space, or there was no dot and the name
// has more than 8 characters, then it is not 8.3 compliant.
//
if ((Lfn[LfnSize - 1] == L' ') ||
(!DotEncountered && (LfnSize > 8))) {
return FALSE;
}
//
// OK, now if we got this far then the LFN is 8dot3. If there are
// no extended characters, then we can also check to make sure that
// the LFN is only a case varient of the DIRENT.
//
if (!ExtendedChars &&
!RtlEqualMemory(Dirent->FileName, DirentName, 11)) {
return FALSE;
}
//
// We have now verified this pairing the very best we can without
// knowledge of the code page that the file was created under.
//
return TRUE;
}
#endif //0
//
// Internal support routine
//
_Requires_lock_held_(_Global_critical_region_)
VOID
FatRescanDirectory (
PIRP_CONTEXT IrpContext,
PDCB Dcb
)
/*++
Routine Description:
This routine rescans the given directory, finding the first unused
dirent, first deleted dirent, and setting the free dirent bitmap
appropriately.
Arguments:
Dcb - Supplies the directory to rescan.
Return Value:
None.
--*/
{
PBCB Bcb = NULL;
PDIRENT Dirent = NULL;
NTSTATUS Status = STATUS_SUCCESS;
ULONG UnusedVbo;
ULONG DeletedHint;
ULONG DirentIndex;
ULONG DirentsThisRun;
ULONG StartIndexOfThisRun;
enum RunType {
InitialRun,
FreeDirents,
AllocatedDirents,
} CurrentRun;
PAGED_CODE();
DebugTrace( 0, Dbg, "We must scan the whole directory.\n", 0);
UnusedVbo = 0;
DeletedHint = 0xffffffff;
//
// To start with, we have to find out if the first dirent is free.
//
CurrentRun = InitialRun;
DirentIndex =
StartIndexOfThisRun = 0;
_SEH2_TRY {
while ( TRUE ) {
BOOLEAN DirentDeleted;
//
// Read a dirent
//
FatReadDirent( IrpContext,
Dcb,
UnusedVbo,
&Bcb,
&Dirent,
&Status );
//
// If EOF, or we found a NEVER_USED entry, we exit the loop
//
if ( (Status == STATUS_END_OF_FILE ) ||
(Dirent->FileName[0] == FAT_DIRENT_NEVER_USED)) {
break;
}
//
// If the dirent is DELETED, and it is the first one we found, set
// it in the deleted hint.
//
if (Dirent->FileName[0] == FAT_DIRENT_DELETED) {
DirentDeleted = TRUE;
if (DeletedHint == 0xffffffff) {
DeletedHint = UnusedVbo;
}
} else {
DirentDeleted = FALSE;
}
//
// Check for the first time through the loop, and determine
// the current run type.
//
if (CurrentRun == InitialRun) {
CurrentRun = DirentDeleted ?
FreeDirents : AllocatedDirents;
} else {
//
// Are we switching from a free run to an allocated run?
//
if ((CurrentRun == FreeDirents) && !DirentDeleted) {
DirentsThisRun = DirentIndex - StartIndexOfThisRun;
RtlClearBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
StartIndexOfThisRun,
DirentsThisRun );
CurrentRun = AllocatedDirents;
StartIndexOfThisRun = DirentIndex;
}
//
// Are we switching from an allocated run to a free run?
//
if ((CurrentRun == AllocatedDirents) && DirentDeleted) {
DirentsThisRun = DirentIndex - StartIndexOfThisRun;
RtlSetBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
StartIndexOfThisRun,
DirentsThisRun );
CurrentRun = FreeDirents;
StartIndexOfThisRun = DirentIndex;
}
}
//
// Move on to the next dirent.
//
UnusedVbo += sizeof(DIRENT);
Dirent += 1;
DirentIndex += 1;
}
//
// Now we have to record the final run we encoutered
//
DirentsThisRun = DirentIndex - StartIndexOfThisRun;
if ((CurrentRun == FreeDirents) || (CurrentRun == InitialRun)) {
RtlClearBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
StartIndexOfThisRun,
DirentsThisRun );
} else {
RtlSetBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
StartIndexOfThisRun,
DirentsThisRun );
}
//
// Now if there we bailed prematurely out of the loop because
// we hit an unused entry, set all the rest as free.
//
if (UnusedVbo < Dcb->Header.AllocationSize.LowPart) {
StartIndexOfThisRun = UnusedVbo / sizeof(DIRENT);
DirentsThisRun = (Dcb->Header.AllocationSize.LowPart -
UnusedVbo) / sizeof(DIRENT);
RtlClearBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
StartIndexOfThisRun,
DirentsThisRun);
}
} _SEH2_FINALLY {
FatUnpinBcb( IrpContext, Bcb );
} _SEH2_END;
//
// If there weren't any DELETED entries, set the index to our current
// position.
//
if (DeletedHint == 0xffffffff) { DeletedHint = UnusedVbo; }
Dcb->Specific.Dcb.UnusedDirentVbo = UnusedVbo;
Dcb->Specific.Dcb.DeletedDirentHint = DeletedHint;
return;
}
//
// Internal support routine
//
_Requires_lock_held_(_Global_critical_region_)
ULONG
FatDefragDirectory (
IN PIRP_CONTEXT IrpContext,
IN PDCB Dcb,
IN ULONG DirentsNeeded
)
/*++
Routine Description:
This routine determines if the requested number of dirents can be found
in the directory, looking for deleted dirents and orphaned LFNs. If the
request can be satisifed, orphaned LFNs are marked as deleted, and deleted
dirents are all grouped together at the end of the directory.
Note that this routine is currently used only on the root directory, but
it is completely general and could be used on any directory.
Arguments:
Dcb - Supplies the directory to defrag.
Return Value:
The Index of the first dirent available for use, or -1 if the
request cannot be satisfied.
--*/
{
ULONG SavedIrpContextFlag;
PLIST_ENTRY Links;
ULONG ReturnValue = 0;
PFCB Fcb;
PBCB Bcb = NULL;
PDIRENT Dirent = NULL;
UNICODE_STRING Lfn = {0,0,NULL};
LARGE_MCB Mcb;
BOOLEAN McbInitialized = FALSE;
BOOLEAN InvalidateFcbs = FALSE;
PUCHAR Directory = NULL;
PUCHAR UnusedDirents;
PUCHAR UnusedDirentBuffer = NULL;
PUCHAR UsedDirents;
PUCHAR UsedDirentBuffer = NULL;
PBCB *Bcbs = NULL;
ULONG Page;
ULONG PagesPinned = 0;
ULONG DcbSize;
ULONG TotalBytesAllocated = 0;
PAGED_CODE();
//
// We assume we own the Vcb.
//
NT_ASSERT( FatVcbAcquiredExclusive(IrpContext, Dcb->Vcb) );
//
// We will only attempt this on directories less than 0x40000 bytes
// long (by default on DOS the root directory is only 0x2000 long).
// This is to avoid a cache manager complication.
//
DcbSize = Dcb->Header.AllocationSize.LowPart;
if (DcbSize > 0x40000) {
return (ULONG)-1;
}
//
// Force wait to TRUE
//
SavedIrpContextFlag = IrpContext->Flags;
SetFlag( IrpContext->Flags,
IRP_CONTEXT_FLAG_WAIT | IRP_CONTEXT_FLAG_WRITE_THROUGH );
//
// Now acquire all open Fcbs in the Dcb exclusive.
//
for (Links = Dcb->Specific.Dcb.ParentDcbQueue.Flink;
Links != &Dcb->Specific.Dcb.ParentDcbQueue;
Links = Links->Flink) {
Fcb = CONTAINING_RECORD( Links, FCB, ParentDcbLinks );
(VOID)ExAcquireResourceExclusiveLite( Fcb->Header.Resource, TRUE );
}
_SEH2_TRY {
CCB Ccb;
ULONG QueryOffset = 0;
ULONG FoundOffset = 0;
ULONGLONG BytesUsed = 0;
NTSTATUS DontCare;
ULONG Run;
ULONG TotalRuns;
BOOLEAN Result;
PUCHAR Char;
//
// We are going to build a new bitmap that will show all orphaned
// LFNs as well as deleted dirents as available.
//
// Initialize our local CCB that will match all files and even
// a label if it is here.
//
RtlZeroMemory( &Ccb, sizeof(CCB) );
Ccb.Flags = CCB_FLAG_MATCH_ALL | CCB_FLAG_MATCH_VOLUME_ID;
//
// Init the Long File Name string.
//
Lfn.MaximumLength = 260 * sizeof(WCHAR);
Lfn.Buffer = FsRtlAllocatePoolWithTag( PagedPool,
260*sizeof(WCHAR),
TAG_FILENAME_BUFFER );
//
// Initalize the Mcb. We use this structure to keep track of runs
// of free and allocated dirents. Runs are identity allocations, and
// holes are free dirents.
//
FsRtlInitializeLargeMcb( &Mcb, PagedPool );
McbInitialized = TRUE;
do {
FatLocateDirent( IrpContext,
Dcb,
&Ccb,
QueryOffset,
NULL,
&Dirent,
&Bcb,
(PVBO)&FoundOffset,
NULL,
&Lfn,
NULL );
if (Dirent != NULL) {
ULONG LfnByteOffset;
//
// Compute the LfnByteOffset.
//
LfnByteOffset = FoundOffset -
FAT_LFN_DIRENTS_NEEDED(&Lfn) * sizeof(LFN_DIRENT);
BytesUsed = FoundOffset - LfnByteOffset + sizeof(DIRENT);
//
// Set a run to represent all the dirents used for this
// file in the Dcb dir.
//
#ifdef _MSC_VER
#pragma prefast( suppress:28931, "needed for debug build" )
#endif
Result = FsRtlAddLargeMcbEntry( &Mcb,
LfnByteOffset,
LfnByteOffset,
BytesUsed );
NT_ASSERT( Result );
//
// Move on to the next dirent.
//
TotalBytesAllocated += (ULONG) BytesUsed;
QueryOffset = FoundOffset + sizeof(DIRENT);
}
} while ((Dirent != NULL) && (QueryOffset < DcbSize));
if (Bcb != NULL) {
FatUnpinBcb( IrpContext, Bcb );
}
//
// If we need more dirents than are available, bail.
//
if (DirentsNeeded > (DcbSize - TotalBytesAllocated)/sizeof(DIRENT)) {
try_return(ReturnValue = (ULONG)-1);
}
//
// Now we are going to copy all the used and un-used parts of the
// directory to separate pool.
//
// Allocate these buffers and pin the entire directory.
//
UnusedDirents =
UnusedDirentBuffer = FsRtlAllocatePoolWithTag( PagedPool,
DcbSize - TotalBytesAllocated,
TAG_DIRENT );
UsedDirents =
UsedDirentBuffer = FsRtlAllocatePoolWithTag( PagedPool,
TotalBytesAllocated,
TAG_DIRENT );
PagesPinned = (DcbSize + (PAGE_SIZE - 1 )) / PAGE_SIZE;
Bcbs = FsRtlAllocatePoolWithTag( PagedPool,
PagesPinned * sizeof(PBCB),
TAG_BCB );
RtlZeroMemory( Bcbs, PagesPinned * sizeof(PBCB) );
for (Page = 0; Page < PagesPinned; Page += 1) {
ULONG PinSize;
//
// Don't try to pin beyond the Dcb size.
//
if ((Page + 1) * PAGE_SIZE > DcbSize) {
PinSize = DcbSize - (Page * PAGE_SIZE);
} else {
PinSize = PAGE_SIZE;
}
FatPrepareWriteDirectoryFile( IrpContext,
Dcb,
Page * PAGE_SIZE,
PinSize,
&Bcbs[Page],
#ifndef __REACTOS__
&Dirent,
#else
(PVOID *)&Dirent,
#endif
FALSE,
TRUE,
&DontCare );
if (Page == 0) {
Directory = (PUCHAR)Dirent;
}
}
TotalRuns = FsRtlNumberOfRunsInLargeMcb( &Mcb );
for (Run = 0; Run < TotalRuns; Run++) {
LBO Vbo;
LBO Lbo;
#ifdef _MSC_VER
#pragma prefast( suppress:28931, "needed for debug build" )
#endif
Result = FsRtlGetNextLargeMcbEntry( &Mcb,
Run,
&Vbo,
&Lbo,
(PLONGLONG)&BytesUsed );
NT_ASSERT(Result);
//
// Copy each run to their specific pool.
//
if (Lbo != -1) {
RtlCopyMemory( UsedDirents,
Directory + Vbo,
(ULONG) BytesUsed );
UsedDirents += BytesUsed;
} else {
RtlCopyMemory( UnusedDirents,
Directory + Vbo,
(ULONG) BytesUsed );
UnusedDirents += BytesUsed;
}
}
//
// Marking all the un-used dirents as "deleted". This will reclaim
// storage used by orphaned LFNs.
//
for (Char = UnusedDirentBuffer; Char < UnusedDirents; Char += sizeof(DIRENT)) {
*Char = FAT_DIRENT_DELETED;
}
//
// Now, for the permanent step. Copy the two pool buffer back to the
// real Dcb directory, and flush the Dcb directory
//
NT_ASSERT( TotalBytesAllocated == (ULONG)(UsedDirents - UsedDirentBuffer) );
RtlCopyMemory( Directory, UsedDirentBuffer, TotalBytesAllocated );
RtlCopyMemory( Directory + TotalBytesAllocated,
UnusedDirentBuffer,
UnusedDirents - UnusedDirentBuffer );
//
// We need to unpin here so that the UnpinRepinned won't deadlock.
//
if (Bcbs) {
for (Page = 0; Page < PagesPinned; Page += 1) {
FatUnpinBcb( IrpContext, Bcbs[Page] );
}
ExFreePool(Bcbs);
Bcbs = NULL;
}
//
// Now make the free dirent bitmap reflect the new state of the Dcb
// directory.
//
RtlSetBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
0,
TotalBytesAllocated / sizeof(DIRENT) );
RtlClearBits( &Dcb->Specific.Dcb.FreeDirentBitmap,
TotalBytesAllocated / sizeof(DIRENT),
(DcbSize - TotalBytesAllocated) / sizeof(DIRENT) );
ReturnValue = TotalBytesAllocated / sizeof(DIRENT);
//
// Flush the directory to disk. If we raise, we will need to invalidate
// all of the children. Sorry, guys, but I can't figure out where you are
// now - if this failed I probably can't read the media either. And we
// probably purged the cache to boot.
//
_SEH2_TRY {
FatUnpinRepinnedBcbs( IrpContext );
} _SEH2_EXCEPT(FsRtlIsNtstatusExpected(_SEH2_GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
InvalidateFcbs = TRUE;
} _SEH2_END;
//
// OK, now nothing can go wrong. We have two more things to do.
// First, we have to fix up all the dirent offsets in any open Fcbs.
// If we cannot now find the Fcb, the file is marked invalid. Also,
// we skip deleted files.
//
for (Links = Dcb->Specific.Dcb.ParentDcbQueue.Flink;
Links != &Dcb->Specific.Dcb.ParentDcbQueue;
Links = Links->Flink) {
PBCB TmpBcb = NULL;
ULONG TmpOffset = 0;
PDIRENT TmpDirent = NULL;
ULONG PreviousLfnSpread;
Fcb = CONTAINING_RECORD( Links, FCB, ParentDcbLinks );
if (IsFileDeleted( IrpContext, Fcb )) {
continue;
}
//
// If we aren't already giving up, safely try to pick up the dirent
// to update the Fcb. If this raises, we have to give up and blow
// evenyone else away too.
//
if (!InvalidateFcbs) {
_SEH2_TRY {
FatLocateSimpleOemDirent( IrpContext,
Dcb,
&Fcb->ShortName.Name.Oem,
&TmpDirent,
&TmpBcb,
(PVBO)&TmpOffset );
} _SEH2_EXCEPT(FsRtlIsNtstatusExpected(_SEH2_GetExceptionCode()) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
InvalidateFcbs = TRUE;
} _SEH2_END;
}
if (TmpBcb == NULL || InvalidateFcbs) {
FatUnpinBcb( IrpContext, TmpBcb );
FatMarkFcbCondition( IrpContext, Fcb, FcbBad, TRUE );
} else {
FatUnpinBcb( IrpContext, TmpBcb );
PreviousLfnSpread = Fcb->DirentOffsetWithinDirectory -
Fcb->LfnOffsetWithinDirectory;
Fcb->DirentOffsetWithinDirectory = TmpOffset;
Fcb->LfnOffsetWithinDirectory = TmpOffset - PreviousLfnSpread;
}
}
try_exit: NOTHING;
} _SEH2_FINALLY {
//
// Free all our resources and stuff.
//
if (McbInitialized) {
FsRtlUninitializeLargeMcb( &Mcb );
}
if (Lfn.Buffer) {
ExFreePool( Lfn.Buffer );
}
if (UnusedDirentBuffer) {
ExFreePool( UnusedDirentBuffer );
}
if (UsedDirentBuffer) {
ExFreePool( UsedDirentBuffer );
}
if (Bcbs) {
for (Page = 0; Page < PagesPinned; Page += 1) {
FatUnpinBcb( IrpContext, Bcbs[Page] );
}
ExFreePool(Bcbs);
}
FatUnpinBcb( IrpContext, Bcb );
for (Links = Dcb->Specific.Dcb.ParentDcbQueue.Flink;
Links != &Dcb->Specific.Dcb.ParentDcbQueue;
Links = Links->Flink) {
Fcb = CONTAINING_RECORD( Links, FCB, ParentDcbLinks );
ExReleaseResourceLite( Fcb->Header.Resource );
}
IrpContext->Flags = SavedIrpContextFlag;
} _SEH2_END;
//
// Now return the offset of the first free dirent to the caller.
//
return ReturnValue;
}