mirror of
https://github.com/reactos/reactos.git
synced 2024-08-20 02:13:29 +00:00
3797 lines
104 KiB
C
3797 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;
|
|
}
|
|
|
|
|
|
|