/*++ Copyright (c) 1989-2000 Microsoft Corporation Module Name: DirSup.c Abstract: This module implements the dirent support routines for Fat. // @@BEGIN_DDKSPLIT Author: DavidGoebel [DavidGoe] 08-Nov-90 // @@END_DDKSPLIT --*/ #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 ); VOID FatRescanDirectory ( PIRP_CONTEXT IrpContext, PDCB Dcb ); 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, FatTunnelFcbOrDcb) #pragma alloc_text(PAGE, FatUpdateDirentFromFcb) #endif ULONG FatCreateNewDirent ( IN PIRP_CONTEXT IrpContext, IN PDCB ParentDirectory, IN ULONG DirentsNeeded ) /*++ 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; NTSTATUS Status = STATUS_SUCCESS; PAGED_CODE(); DebugTrace(+1, Dbg, "FatCreateNewDirent\n", 0); DebugTrace( 0, Dbg, " ParentDirectory = %08lx\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 virgin 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) { 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 ( 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; try { ULONG ClusterSize; PVOID Buffer; ClusterSize = 1 << ParentDirectory->Vcb->AllocationSupport.LogOfBytesPerCluster; FatPrepareWriteDirectoryFile( IrpContext, ParentDirectory, UnusedVbo, 1, &Bcb, &Buffer, FALSE, TRUE, &Status ); } finally { FatUnpinBcb( IrpContext, Bcb ); } } } // // 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)))) { 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 ); ASSERT( RtlAreBitsSet( &ParentDirectory->Specific.Dcb.FreeDirentBitmap, (ByteOffset - sizeof(DIRENT))/ sizeof(DIRENT), DirentsNeeded ) ); RtlClearBits( &ParentDirectory->Specific.Dcb.FreeDirentBitmap, (ByteOffset - sizeof(DIRENT))/ sizeof(DIRENT), DirentsNeeded ); } } finally { FatUnpinBcb( IrpContext, Bcb ); } } // // Assert that the dirents are in fact unused // 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 ); } } } finally { FatUnpinBcb( IrpContext, Bcb ); } // // Set the Bits in the bitmap and move the Unused Dirent Vbo. // 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; } 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; PAGED_CODE(); DebugTrace(+1, Dbg, "FatInitializeDirectoryDirent\n", 0); DebugTrace( 0, Dbg, " Dcb = %08lx\n", Dcb); // // Assert that we are not attempting this on the root directory. // ASSERT( NodeType(Dcb) != FAT_NTC_ROOT_DCB ); // // Assert that this is only attempted on newly created directories. // 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 ); ASSERT( NT_SUCCESS( DontCare )); // // Add the . and .. entries // try { FatConstructDot( IrpContext, Dcb, ParentDirent, (PDIRENT)Buffer + 0); FatConstructDotDot( IrpContext, Dcb, ParentDirent, (PDIRENT)Buffer + 1); // // Unpin the buffer and return to the caller. // } finally { FatUnpinBcb( IrpContext, Bcb ); } 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; 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; Status = RtlOemStringToCountedUnicodeString( &ShortNameWithCase, &FcbOrDcb->ShortName.Name.Oem, FALSE); ASSERT(ShortNameWithCase.Length != 0); 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; } 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; NTSTATUS DontCare; ULONG Offset; ULONG DirentsToDelete; PAGED_CODE(); DebugTrace(+1, Dbg, "FatDeleteDirent\n", 0); DebugTrace( 0, Dbg, " FcbOrDcb = %08lx\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 darn unfortunate if this raced with the // rename path. // ASSERT( ExIsResourceAcquiredExclusiveLite( &FcbOrDcb->Vcb->Resource )); // // Assert that we are not attempting this on the root directory. // 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); 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. // 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 ); } 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)) { try { FatDeleteEa( IrpContext, FcbOrDcb->Vcb, Dirent->ExtendedAttributes, &FcbOrDcb->ShortName.Name.Oem ); } except(FatExceptionFilter( IrpContext, GetExceptionInformation() )) { // // We catch all exceptions that Fat catches, but don't do // anything with them. // } } // // Now clear the bits in the free dirent mask. // DirentsToDelete = (FcbOrDcb->DirentOffsetWithinDirectory - FcbOrDcb->LfnOffsetWithinDirectory) / sizeof(DIRENT) + 1; 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; } } finally { FatUnpinBcb( IrpContext, Bcb ); // // Release our parent. // ExReleaseResourceLite( FcbOrDcb->ParentDcb->Header.Resource ); } DebugTrace(-1, Dbg, "FatDeleteDirent -> (VOID)\n", 0); return; } 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; 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; try { FatLocateDirent( IrpContext, Dcb, &Ccb, 0, &Dirent, &DirentBcb, &DirentByteOffset, NULL, LfnTmp); } finally { if (DirentBcb) { Result = TRUE; } FatUnpinBcb(IrpContext, DirentBcb); } return Result; } VOID FatLocateDirent ( IN PIRP_CONTEXT IrpContext, IN PDCB ParentDirectory, IN PCCB Ccb, IN VBO OffsetToStartSearchFrom, OUT PDIRENT *Dirent, OUT PBCB *Bcb, OUT PVBO ByteOffset, OUT PBOOLEAN FileNameDos OPTIONAL, IN OUT PUNICODE_STRING LongFileName 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]; UNICODE_STRING UpcasedLfn; WCHAR LocalLfnBuffer[32]; BOOLEAN LfnInProgress = FALSE; UCHAR LfnChecksum; ULONG LfnSize; ULONG LfnIndex; UCHAR Ordinal; VBO LfnByteOffset; TimerStart(Dbg); PAGED_CODE(); DebugTrace(+1, Dbg, "FatLocateDirent\n", 0); DebugTrace( 0, Dbg, " ParentDirectory = %08lx\n", ParentDirectory); DebugTrace( 0, Dbg, " OffsetToStartSearchFrom = %08lx\n", OffsetToStartSearchFrom); DebugTrace( 0, Dbg, " Dirent = %08lx\n", Dirent); DebugTrace( 0, Dbg, " Bcb = %08lx\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. // 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. // // // Set up the strings that receives file names from our search // Name.MaximumLength = 12; Name.Buffer = 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; } // // 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); try { while ( TRUE ) { BOOLEAN FoundValidLfn; // // 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 { 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)) { ASSERT( Ordinal == 1); FoundValidLfn = TRUE; LongFileName->Length = (USHORT)(LfnSize * sizeof(WCHAR)); } 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; } 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; } 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; goto GetNextDirent; } // // We need to upcase the name we found. // 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 ); } // // 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; } GetNextDirent: // // Move on to the next dirent. // *ByteOffset += sizeof(DIRENT); *Dirent += 1; } } finally { FatFreeStringBuffer( &UpcasedLfn); } DebugTrace(-1, Dbg, "FatLocateDirent -> (VOID)\n", 0); TimerStop(Dbg,"FatLocateDirent"); return; } 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, Dirent, Bcb, ByteOffset, NULL, NULL); return; } 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; PAGED_CODE(); DebugTrace(+1, Dbg, "FatLocateVolumeLabel\n", 0); DebugTrace( 0, Dbg, " Vcb = %08lx\n", Vcb); DebugTrace( 0, Dbg, " Dirent = %08lx\n", Dirent); DebugTrace( 0, Dbg, " Bcb = %08lx\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; } VOID FatGetDirentFromFcbOrDcb ( IN PIRP_CONTEXT IrpContext, IN PFCB FcbOrDcb, 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; PAGED_CODE(); DebugTrace(+1, Dbg, "FatGetDirentFromFcbOrDcb\n", 0); DebugTrace( 0, Dbg, " FcbOrDcb = %08lx\n", FcbOrDcb); DebugTrace( 0, Dbg, " Dirent = %08lx\n", Dirent); DebugTrace( 0, Dbg, " Bcb = %08lx\n", Bcb); // // Assert that we are not attempting this on the root directory. // 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. // 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. // ASSERT( NT_SUCCESS( DontCare ) || FcbOrDcb->FcbCondition == FcbNeedsToBeVerified ); DebugTrace(-1, Dbg, "FatGetDirentFromFcbOrDcb -> (VOID)\n", 0); return; } 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; BOOLEAN IsDirectoryEmpty = FALSE; NTSTATUS Status = STATUS_SUCCESS; PAGED_CODE(); DebugTrace(+1, Dbg, "FatIsDirectoryEmpty\n", 0); DebugTrace( 0, Dbg, " Dcb = %08lx\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; 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)) { DebugTrace( 0, Dbg, "Not Empty. First entry at VBO = %08lx\n", ByteOffset); IsDirectoryEmpty = FALSE; break; } // // Move on to the next dirent. // ByteOffset += sizeof(DIRENT); Dirent += 1; } } finally { FatUnpinBcb( IrpContext, Bcb ); } 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 UCHAR 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 = %08lx\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 = 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; ASSERT( FatData.ChicagoMode ); DirentChecksum = FatComputeLfnChecksum( Dirent ); LfnOrdinal = DirentsInLfn = FAT_LFN_DIRENTS_NEEDED(Lfn); LfnBuffer = &Lfn->Buffer[(DirentsInLfn - 1) * 13]; 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 = %08lx\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; } 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(); ASSERT( Fcb->FcbCondition == FcbGood ); FatGetDirentFromFcbOrDcb( IrpContext, Fcb, &Dirent, &DirentBcb ); ASSERT( Dirent && DirentBcb ); try { Dirent->FileSize = ARGUMENT_PRESENT( AlternativeFileSize ) ? *AlternativeFileSize : Fcb->Header.FileSize.LowPart; FatSetDirtyBcb( IrpContext, DirentBcb, Fcb->Vcb, TRUE ); } finally { FatUnpinBcb( IrpContext, DirentBcb ); } } 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; PDIRENT Dirent; PBCB DirentBcb = NULL; ULONG NotifyFilter = 0; FAT_TIME_STAMP CurrentFatTime; LARGE_INTEGER CurrentTime; LARGE_INTEGER CurrentDay; 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); try { try { // // Get the dirent // FatGetDirentFromFcbOrDcb( IrpContext, FcbOrDcb, &Dirent, &DirentBcb ); ASSERT( 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; } 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; } 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; } 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. // 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; } } FatNotifyReportChange( IrpContext, FcbOrDcb->Vcb, FcbOrDcb, NotifyFilter, FILE_ACTION_MODIFIED ); // // 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 ); } except( FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) { FatResetExceptionState( IrpContext ); } } finally { FatUnpinBcb( IrpContext, DirentBcb ); } } } // // 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 // 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; NTSTATUS Status; 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; 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); } } finally { FatUnpinBcb( IrpContext, Bcb ); } // // 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 // 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; 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; PUCHAR UnusedDirents; PUCHAR UnusedDirentBuffer = NULL; PUCHAR UsedDirents; PUCHAR UsedDirentBuffer = NULL; PBCB *Bcbs = NULL; ULONG Page; ULONG PagesPinned; ULONG DcbSize; ULONG TotalBytesAllocated = 0; PAGED_CODE(); // // We assume we own the Vcb. // 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 ); } 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, &Dirent, &Bcb, &FoundOffset, NULL, &Lfn); 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. // Result = FsRtlAddLargeMcbEntry( &Mcb, LfnByteOffset, LfnByteOffset, BytesUsed ); 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], &Dirent, FALSE, TRUE, &DontCare ); if (Page == 0) { Directory = (PUCHAR)Dirent; } } TotalRuns = FsRtlNumberOfRunsInLargeMcb( &Mcb ); for (Run = 0; Run < TotalRuns; Run++) { LBO Vbo; LBO Lbo; Result = FsRtlGetNextLargeMcbEntry( &Mcb, Run, &Vbo, &Lbo, &BytesUsed ); 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 // 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. // try { FatUnpinRepinnedBcbs( IrpContext ); } except(FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { InvalidateFcbs = TRUE; } // // 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; 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) { try { FatLocateSimpleOemDirent( IrpContext, Dcb, &Fcb->ShortName.Name.Oem, &TmpDirent, &TmpBcb, &TmpOffset ); } except(FsRtlIsNtstatusExpected(GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { InvalidateFcbs = TRUE; } } 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; } 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; } // // Now return the offset of the first free dirent to the caller. // return ReturnValue; }