summaryrefslogtreecommitdiffstats
path: root/private/ntos/cntfs
diff options
context:
space:
mode:
authorAdam <you@example.com>2020-05-17 05:51:50 +0200
committerAdam <you@example.com>2020-05-17 05:51:50 +0200
commite611b132f9b8abe35b362e5870b74bce94a1e58e (patch)
treea5781d2ec0e085eeca33cf350cf878f2efea6fe5 /private/ntos/cntfs
downloadNT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.gz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.bz2
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.lz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.xz
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.tar.zst
NT4.0-e611b132f9b8abe35b362e5870b74bce94a1e58e.zip
Diffstat (limited to 'private/ntos/cntfs')
-rw-r--r--private/ntos/cntfs/allocsup.c3384
-rw-r--r--private/ntos/cntfs/attrdata.c165
-rw-r--r--private/ntos/cntfs/attrsup.c13605
-rw-r--r--private/ntos/cntfs/bitmpsup.c7977
-rw-r--r--private/ntos/cntfs/cachesup.c1481
-rw-r--r--private/ntos/cntfs/checksup.c862
-rw-r--r--private/ntos/cntfs/cleanup.c2129
-rw-r--r--private/ntos/cntfs/close.c1187
-rw-r--r--private/ntos/cntfs/colatsup.c678
-rw-r--r--private/ntos/cntfs/create.c11241
-rw-r--r--private/ntos/cntfs/devctrl.c271
-rw-r--r--private/ntos/cntfs/deviosup.c7934
-rw-r--r--private/ntos/cntfs/dirctrl.c1532
-rw-r--r--private/ntos/cntfs/dirs26
-rw-r--r--private/ntos/cntfs/ea.c2748
-rw-r--r--private/ntos/cntfs/fieldoff.c630
-rw-r--r--private/ntos/cntfs/fileinfo.c8307
-rw-r--r--private/ntos/cntfs/filobsup.c332
-rw-r--r--private/ntos/cntfs/flush.c1871
-rw-r--r--private/ntos/cntfs/fsctrl.c8366
-rw-r--r--private/ntos/cntfs/fspdisp.c790
-rw-r--r--private/ntos/cntfs/fstiosup.c1604
-rw-r--r--private/ntos/cntfs/index.h159
-rw-r--r--private/ntos/cntfs/indexsup.c6765
-rw-r--r--private/ntos/cntfs/lockctrl.c899
-rw-r--r--private/ntos/cntfs/logsup.c3434
-rw-r--r--private/ntos/cntfs/mcbsup.c2610
-rw-r--r--private/ntos/cntfs/mftsup.c2195
-rw-r--r--private/ntos/cntfs/mp/makefile6
-rw-r--r--private/ntos/cntfs/mp/ntfs.prf407
-rw-r--r--private/ntos/cntfs/mp/sources30
-rw-r--r--private/ntos/cntfs/namesup.c1114
-rw-r--r--private/ntos/cntfs/nodetype.h122
-rw-r--r--private/ntos/cntfs/ntfs.def16
-rw-r--r--private/ntos/cntfs/ntfs.h2334
-rw-r--r--private/ntos/cntfs/ntfs.rc11
-rw-r--r--private/ntos/cntfs/ntfsdata.c2607
-rw-r--r--private/ntos/cntfs/ntfsdata.h604
-rw-r--r--private/ntos/cntfs/ntfsexp.c256
-rw-r--r--private/ntos/cntfs/ntfsinit.c946
-rw-r--r--private/ntos/cntfs/ntfslog.h711
-rw-r--r--private/ntos/cntfs/ntfsproc.h5685
-rw-r--r--private/ntos/cntfs/ntfsstru.h3503
-rw-r--r--private/ntos/cntfs/prefxsup.c805
-rw-r--r--private/ntos/cntfs/quotasup.c5064
-rw-r--r--private/ntos/cntfs/read.c1956
-rw-r--r--private/ntos/cntfs/resrcsup.c2114
-rw-r--r--private/ntos/cntfs/restrsup.c4744
-rw-r--r--private/ntos/cntfs/rwcmpsup.c1821
-rw-r--r--private/ntos/cntfs/secursup.c3847
-rw-r--r--private/ntos/cntfs/seinfo.c578
-rw-r--r--private/ntos/cntfs/shutdown.c340
-rw-r--r--private/ntos/cntfs/sources.inc89
-rw-r--r--private/ntos/cntfs/spec/acls.docbin0 -> 62464 bytes
-rw-r--r--private/ntos/cntfs/spec/devplan.docbin0 -> 33280 bytes
-rw-r--r--private/ntos/cntfs/spec/journal.docbin0 -> 31744 bytes
-rw-r--r--private/ntos/cntfs/spec/ntfs.pptbin0 -> 29696 bytes
-rw-r--r--private/ntos/cntfs/spec/ntofsods.docbin0 -> 54784 bytes
-rw-r--r--private/ntos/cntfs/spec/props.docbin0 -> 233472 bytes
-rw-r--r--private/ntos/cntfs/spec/quota.docbin0 -> 19105 bytes
-rw-r--r--private/ntos/cntfs/spec/rwcmp.docbin0 -> 68608 bytes
-rw-r--r--private/ntos/cntfs/spec/usn.docbin0 -> 88576 bytes
-rw-r--r--private/ntos/cntfs/specnot2.txt243
-rw-r--r--private/ntos/cntfs/specnote.txt47
-rw-r--r--private/ntos/cntfs/strucsup.c7383
-rw-r--r--private/ntos/cntfs/tests/makefile9
-rw-r--r--private/ntos/cntfs/tests/proptest.cxx1891
-rw-r--r--private/ntos/cntfs/tests/quota.c781
-rw-r--r--private/ntos/cntfs/tests/sources45
-rw-r--r--private/ntos/cntfs/up/makefile6
-rw-r--r--private/ntos/cntfs/up/ntfs.prf407
-rw-r--r--private/ntos/cntfs/up/sources30
-rw-r--r--private/ntos/cntfs/vattrsup.c627
-rw-r--r--private/ntos/cntfs/verfysup.c1779
-rw-r--r--private/ntos/cntfs/views/check.c282
-rw-r--r--private/ntos/cntfs/views/driver.c287
-rw-r--r--private/ntos/cntfs/views/heap.c506
-rw-r--r--private/ntos/cntfs/views/initprop.c122
-rw-r--r--private/ntos/cntfs/views/makefile13
-rw-r--r--private/ntos/cntfs/views/readprop.c802
-rw-r--r--private/ntos/cntfs/views/sources56
-rw-r--r--private/ntos/cntfs/views/table.c543
-rw-r--r--private/ntos/cntfs/views/viewprop.h600
-rw-r--r--private/ntos/cntfs/views/views.reg7
-rw-r--r--private/ntos/cntfs/views/writprop.c700
-rw-r--r--private/ntos/cntfs/viewsup.c2293
-rw-r--r--private/ntos/cntfs/volinfo.c1353
-rw-r--r--private/ntos/cntfs/workque.c433
-rw-r--r--private/ntos/cntfs/write.c2769
89 files changed, 156906 insertions, 0 deletions
diff --git a/private/ntos/cntfs/allocsup.c b/private/ntos/cntfs/allocsup.c
new file mode 100644
index 000000000..6ee4526ab
--- /dev/null
+++ b/private/ntos/cntfs/allocsup.c
@@ -0,0 +1,3384 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ AllocSup.c
+
+Abstract:
+
+ This module implements the general file stream allocation & truncation
+ routines for Ntfs
+
+Author:
+
+ Tom Miller [TomM] 15-Jul-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_ALLOCSUP)
+
+//
+// Internal support routines
+//
+
+VOID
+NtfsDeleteAllocationInternal (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ IN BOOLEAN LogIt
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsPreloadAllocation)
+#pragma alloc_text(PAGE, NtfsAddAllocation)
+#pragma alloc_text(PAGE, NtfsAllocateAttribute)
+#pragma alloc_text(PAGE, NtfsBuildMappingPairs)
+#pragma alloc_text(PAGE, NtfsDeleteAllocation)
+#pragma alloc_text(PAGE, NtfsDeleteAllocationInternal)
+#pragma alloc_text(PAGE, NtfsGetHighestVcn)
+#pragma alloc_text(PAGE, NtfsGetSizeForMappingPairs)
+#endif
+
+
+ULONG
+NtfsPreloadAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine assures that all ranges of the Mcb are loaded in the specified
+ Vcn range
+
+Arguments:
+
+ Scb - Specifies which Scb is to be preloaded
+
+ StartingVcn - Specifies the first Vcn to be loaded
+
+ EndingVcn - Specifies the last Vcn to be loaded
+
+Return Value:
+
+ Number of ranges spanned by the load request.
+
+--*/
+
+{
+ VCN CurrentVcn, LastCurrentVcn;
+ LCN Lcn;
+ LONGLONG Count;
+ PVOID RangePtr;
+ ULONG RunIndex;
+ ULONG RangesLoaded = 0;
+
+ PAGED_CODE();
+
+ //
+ // Start with starting Vcn
+ //
+
+ CurrentVcn = StartingVcn;
+
+ //
+ // Always load the nonpaged guys from the front, so we don't
+ // produce an Mcb with a "known hole".
+ //
+
+ if (FlagOn(Scb->Fcb->FcbState, FCB_STATE_NONPAGED)) {
+ CurrentVcn = 0;
+ }
+
+ //
+ // Loop until it's all loaded.
+ //
+
+ while (CurrentVcn <= EndingVcn) {
+
+ //
+ // Remember this CurrentVcn as a way to know when we have hit the end
+ // (stopped making progress).
+ //
+
+ LastCurrentVcn = CurrentVcn;
+
+ //
+ // Load range with CurrentVcn, and if it is not there, get out.
+ //
+
+ (VOID)NtfsLookupAllocation(IrpContext, Scb, CurrentVcn, &Lcn, &Count, &RangePtr, &RunIndex);
+
+ //
+ // Find out how many runs there are in this range
+ //
+
+ if (!NtfsNumberOfRunsInRange(&Scb->Mcb, RangePtr, &RunIndex) || (RunIndex == 0)) {
+ break;
+ }
+
+ //
+ // Get the highest run in this range and calculate the next Vcn beyond this range.
+ //
+
+ NtfsGetNextNtfsMcbEntry(&Scb->Mcb, &RangePtr, RunIndex - 1, &CurrentVcn, &Lcn, &Count);
+
+ CurrentVcn += Count;
+
+ //
+ // If we are making no progress, we must have hit the end of the allocation,
+ // and we are done.
+ //
+
+ if (CurrentVcn == LastCurrentVcn) {
+ break;
+ }
+
+ RangesLoaded += 1;
+ }
+
+ return RangesLoaded;
+}
+
+
+BOOLEAN
+NtfsLookupAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN VCN Vcn,
+ OUT PLCN Lcn,
+ OUT PLONGLONG ClusterCount,
+ OUT PVOID *RangePtr OPTIONAL,
+ OUT PULONG RunIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine looks up the given Vcn for an Scb, and returns whether it
+ is allocated and how many contiguously allocated (or deallocated) Lcns
+ exist at that point.
+
+Arguments:
+
+ Scb - Specifies which attribute the lookup is to occur on.
+
+ Vcn - Specifies the Vcn to be looked up.
+
+ Lcn - If returning TRUE, returns the Lcn that the specified Vcn is mapped
+ to. If returning FALSE, the return value is undefined.
+
+ ClusterCount - If returning TRUE, returns the number of contiguously allocated
+ Lcns exist beginning at the Lcn returned. If returning FALSE,
+ specifies the number of unallocated Vcns exist beginning with
+ the specified Vcn.
+
+ RangePtr - If specified, we return the range index for the start of the mapping.
+
+ RunIndex - If specified, we return the run index within the range for the start of the mapping.
+
+Return Value:
+
+ BOOLEAN - TRUE if the input Vcn has a corresponding Lcn and
+ FALSE otherwise.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ VCN HighestCandidate;
+
+ BOOLEAN Found;
+ BOOLEAN EntryAdded;
+
+ VCN CapturedLowestVcn;
+ VCN CapturedHighestVcn;
+
+ PVCB Vcb = Scb->Vcb;
+ BOOLEAN McbMutexAcquired = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ DebugTrace( +1, Dbg, ("NtfsLookupAllocation\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Vcn = %I64x\n", Vcn) );
+
+ //
+ // First try to look up the allocation in the mcb, and return the run
+ // from there if we can. Also, if we are doing restart, just return
+ // the answer straight from the Mcb, because we cannot read the disk.
+ // We also do this for the Mft if the volume has been mounted as the
+ // Mcb for the Mft should always represent the entire file.
+ //
+
+ HighestCandidate = MAXLONGLONG;
+ if ((Found = NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, ClusterCount, NULL, NULL, RangePtr, RunIndex ))
+
+ ||
+
+ (Scb == Scb->Vcb->MftScb
+
+ &&
+
+ FlagOn( Scb->Vcb->Vpb->Flags, VPB_MOUNTED ))
+
+ ||
+
+ FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
+
+ //
+ // If not found (beyond the end of the Mcb), we will return the
+ // count to the largest representable Lcn.
+ //
+
+ if ( !Found ) {
+ *ClusterCount = MAXLONGLONG - Vcn;
+
+ //
+ // Test if we found a hole in the allocation. In this case
+ // Found will be TRUE and the Lcn will be the UNUSED_LCN.
+ // We only expect this case at restart.
+ //
+
+ } else if (*Lcn == UNUSED_LCN) {
+
+ //
+ // If the Mcb package returned UNUSED_LCN, because of a hole, then
+ // we turn this into FALSE.
+ //
+
+ Found = FALSE;
+ }
+
+ ASSERT( !Found ||
+ (*Lcn != 0) ||
+ (NtfsEqualMftRef( &Scb->Fcb->FileReference, &BootFileReference )) ||
+ (NtfsEqualMftRef( &Scb->Fcb->FileReference, &VolumeFileReference )));
+
+ DebugTrace( -1, Dbg, ("NtfsLookupAllocation -> %02lx\n", Found) );
+
+ return Found;
+ }
+
+ PAGED_CODE();
+
+ //
+ // Prepare for looking up attribute records to get the retrieval
+ // information.
+ //
+
+ CapturedLowestVcn = MAXLONGLONG;
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Make sure we have the main resource acquired shared so that the
+ // attributes in the file record are not moving around. We blindly
+ // use Wait = TRUE. Most of the time when we go to the disk for I/O
+ // (and thus need mapping) we are synchronous, and otherwise, the Mcb
+ // is virtually always loaded anyway and we do not get here.
+ //
+
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+
+ try {
+
+ //
+ // Lookup the attribute record for this Scb.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, &Vcn, &Context );
+
+ //
+ // The desired Vcn is not currently in the Mcb. We will loop to lookup all
+ // the allocation, and we need to make sure we cleanup on the way out.
+ //
+ // It is important to note that if we ever optimize this lookup to do random
+ // access to the mapping pairs, rather than sequentially loading up the Mcb
+ // until we get the Vcn he asked for, then NtfsDeleteAllocation will have to
+ // be changed.
+ //
+
+ //
+ // Acquire exclusive access to the mcb to keep others from looking at
+ // it while it is not fully loaded. Otherwise they might see a hole
+ // while we're still filling up the mcb
+ //
+
+ if (!FlagOn(Scb->Fcb->FcbState, FCB_STATE_NONPAGED)) {
+ NtfsAcquireNtfsMcbMutex( &Scb->Mcb );
+ McbMutexAcquired = TRUE;
+ }
+
+ //
+ // Store run information in the Mcb until we hit the last Vcn we are
+ // interested in, or until we cannot find any more attribute records.
+ //
+
+ do {
+
+ VCN CurrentVcn;
+ LCN CurrentLcn;
+ LONGLONG Change;
+ PCHAR ch;
+ ULONG VcnBytes;
+ ULONG LcnBytes;
+
+ Attribute = NtfsFoundAttribute( &Context );
+
+ ASSERT( !NtfsIsAttributeResident(Attribute) );
+
+ //
+ // Define the new range.
+ //
+
+ NtfsDefineNtfsMcbRange( &Scb->Mcb,
+ CapturedLowestVcn = Attribute->Form.Nonresident.LowestVcn,
+ CapturedHighestVcn = Attribute->Form.Nonresident.HighestVcn,
+ McbMutexAcquired );
+
+ //
+ // Implement the decompression algorithm, as defined in ntfs.h.
+ //
+
+ HighestCandidate = Attribute->Form.Nonresident.LowestVcn;
+ CurrentLcn = 0;
+ ch = (PCHAR)Attribute + Attribute->Form.Nonresident.MappingPairsOffset;
+
+ //
+ // Loop to process mapping pairs.
+ //
+
+ EntryAdded = FALSE;
+ while (!IsCharZero(*ch)) {
+
+ //
+ // Set Current Vcn from initial value or last pass through loop.
+ //
+
+ CurrentVcn = HighestCandidate;
+
+ //
+ // Extract the counts from the two nibbles of this byte.
+ //
+
+ VcnBytes = *ch & 0xF;
+ LcnBytes = *ch++ >> 4;
+
+ //
+ // Extract the Vcn change (use of RtlCopyMemory works for little-Endian)
+ // and update HighestCandidate.
+ //
+
+ Change = 0;
+
+ //
+ // The file is corrupt if there are 0 or more than 8 Vcn change bytes,
+ // more than 8 Lcn change bytes, or if we would walk off the end of
+ // the record, or a Vcn change is negative.
+ //
+
+ if (((ULONG)(VcnBytes - 1) > 7) || (LcnBytes > 8) ||
+ ((ch + VcnBytes + LcnBytes + 1) > (PCHAR)Add2Ptr(Attribute, Attribute->RecordLength)) ||
+ IsCharLtrZero(*(ch + VcnBytes - 1))) {
+
+ ASSERT( FALSE );
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ RtlCopyMemory( &Change, ch, VcnBytes );
+ ch += VcnBytes;
+ HighestCandidate = HighestCandidate + Change;
+
+ //
+ // Extract the Lcn change and update CurrentLcn.
+ //
+
+ if (LcnBytes != 0) {
+
+ Change = 0;
+ if (IsCharLtrZero(*(ch + LcnBytes - 1))) {
+ Change = Change - 1;
+ }
+ RtlCopyMemory( &Change, ch, LcnBytes );
+ ch += LcnBytes;
+ CurrentLcn = CurrentLcn + Change;
+
+ //
+ // Now add it in to the mcb.
+ //
+
+ if ((CurrentLcn >= 0) && (LcnBytes != 0)) {
+
+ LONGLONG ClustersToAdd;
+ ClustersToAdd = HighestCandidate - CurrentVcn;
+
+ //
+ // If we are adding a cluster which extends into the upper
+ // 32 bits then the disk is corrupt.
+ //
+
+ ASSERT( ((PLARGE_INTEGER)&HighestCandidate)->HighPart == 0 );
+
+ if (((PLARGE_INTEGER)&HighestCandidate)->HighPart != 0) {
+
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ Scb->Fcb );
+ }
+
+ //
+ // Now try to add the current run. We never expect this
+ // call to return false.
+ //
+
+ ASSERT( ((ULONG)CurrentLcn) != 0xffffffff );
+
+#ifdef NTFS_CHECK_BITMAP
+ //
+ // Make sure these bits are allocated in our copy of the bitmap.
+ //
+
+ if ((Vcb->BitmapCopy != NULL) &&
+ !NtfsCheckBitmap( Vcb,
+ (ULONG) CurrentLcn,
+ (ULONG) ClustersToAdd,
+ TRUE )) {
+
+ NtfsBadBitmapCopy( IrpContext, (ULONG) CurrentLcn, (ULONG) ClustersToAdd );
+ }
+#endif
+ if (!NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ CurrentVcn,
+ CurrentLcn,
+ ClustersToAdd,
+ McbMutexAcquired )) {
+
+ ASSERTMSG( "Unable to add entry to Mcb\n", FALSE );
+
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ Scb->Fcb );
+ }
+
+ EntryAdded = TRUE;
+ }
+ }
+ }
+
+ //
+ // Make sure that at least the Mcb gets loaded.
+ //
+
+ if (!EntryAdded) {
+ NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ CapturedLowestVcn,
+ UNUSED_LCN,
+ 1,
+ McbMutexAcquired );
+ }
+
+ } while (( Vcn >= HighestCandidate )
+
+ &&
+
+ NtfsLookupNextAttributeForScb( IrpContext,
+ Scb,
+ &Context ));
+
+ //
+ // Now free the mutex and lookup in the Mcb while we still own
+ // the resource.
+ //
+
+ if (McbMutexAcquired) {
+ NtfsReleaseNtfsMcbMutex( &Scb->Mcb );
+ McbMutexAcquired = FALSE;
+ }
+
+ if (NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, ClusterCount, NULL, NULL, RangePtr, RunIndex )) {
+
+ Found = (BOOLEAN)(*Lcn != UNUSED_LCN);
+
+ if (Found) { ASSERT_LCN_RANGE_CHECKING( Scb->Vcb, (*Lcn + *ClusterCount) ); }
+
+ } else {
+
+ Found = FALSE;
+
+ //
+ // At the end of file, we pretend there is one large hole!
+ //
+
+ if (HighestCandidate >=
+ LlClustersFromBytes(Vcb, Scb->Header.AllocationSize.QuadPart)) {
+ HighestCandidate = MAXLONGLONG;
+ }
+
+ *ClusterCount = HighestCandidate - Vcn;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsLookupAllocation );
+
+ //
+ // If this is an error case then we better unload what we've just
+ // loaded
+ //
+
+ if (AbnormalTermination() &&
+ (CapturedLowestVcn != MAXLONGLONG) ) {
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb,
+ CapturedLowestVcn,
+ CapturedHighestVcn,
+ FALSE,
+ McbMutexAcquired );
+ }
+
+ //
+ // In all cases we free up the mcb that we locked before entering
+ // the try statement
+ //
+
+ if (McbMutexAcquired) {
+ NtfsReleaseNtfsMcbMutex( &Scb->Mcb );
+ }
+
+ ExReleaseResource( Scb->Header.Resource );
+
+ //
+ // Cleanup the attribute context on the way out.
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ ASSERT( !Found ||
+ (*Lcn != 0) ||
+ (NtfsEqualMftRef( &Scb->Fcb->FileReference, &BootFileReference )) ||
+ (NtfsEqualMftRef( &Scb->Fcb->FileReference, &VolumeFileReference )));
+
+ DebugTrace( 0, Dbg, ("Lcn < %0I64x\n", *Lcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount < %0I64x\n", *ClusterCount) );
+ DebugTrace( -1, Dbg, ("NtfsLookupAllocation -> %02lx\n", Found) );
+
+ return Found;
+}
+
+
+BOOLEAN
+NtfsAllocateAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN AllocateAll,
+ IN BOOLEAN LogIt,
+ IN LONGLONG Size,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT NewLocation OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates a new attribute and allocates space for it, either in a
+ file record, or as a nonresident attribute.
+
+Arguments:
+
+ Scb - Scb for the attribute.
+
+ AttributeTypeCode - Attribute type code to be created.
+
+ AttributeName - Optional name for the attribute.
+
+ AttributeFlags - Flags to be stored in the attribute record for this attribute.
+
+ AllocateAll - Specified as TRUE if all allocation should be allocated,
+ even if we have to break up the transaction.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are creating a new file record, and
+ will be logging the entire new file record.
+
+ Size - Size in bytes to allocate for the attribute.
+
+ NewLocation - If specified, this is the location to store the attribute.
+
+Return Value:
+
+ FALSE - if the attribute was created, but not all of the space was allocated
+ (this can only happen if Scb was not specified)
+ TRUE - if the space was allocated.
+
+--*/
+
+{
+ BOOLEAN UninitializeOnClose = FALSE;
+ BOOLEAN NewLocationSpecified;
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ LONGLONG ClusterCount, SavedClusterCount;
+ BOOLEAN FullAllocation;
+ PFCB Fcb = Scb->Fcb;
+
+ PAGED_CODE();
+
+ //
+ // Either there is no compression taking place or the attribute
+ // type code allows compression to be specified in the header.
+ // $INDEX_ROOT is a special hack to store the inherited-compression
+ // flag.
+ //
+
+ ASSERT( AttributeFlags == 0
+ || AttributeTypeCode == $INDEX_ROOT
+ || NtfsIsTypeCodeCompressible( AttributeTypeCode ));
+
+ //
+ // If the file is being created compressed, then we need to round its
+ // size to a compression unit boundary.
+ //
+
+ if ((Scb->CompressionUnit != 0) &&
+ (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA)) {
+
+ ((ULONG)Size) |= Scb->CompressionUnit - 1;
+ }
+
+ //
+ // Prepare for looking up attribute records to get the retrieval
+ // information.
+ //
+
+ if (ARGUMENT_PRESENT( NewLocation )) {
+
+ NewLocationSpecified = TRUE;
+
+ } else {
+
+ NtfsInitializeAttributeContext( &Context );
+ NewLocationSpecified = FALSE;
+ NewLocation = &Context;
+ }
+
+ try {
+
+ //
+ // If the FILE_SIZE_LOADED flag is not set, then this Scb is for
+ // an attribute that does not yet exist on disk. We will put zero
+ // into all of the sizes fields and set the flags indicating that
+ // Scb is valid. NOTE - This routine expects both FILE_SIZE_LOADED
+ // and HEADER_INITIALIZED to be both set or both clear.
+ //
+
+ ASSERT( BooleanFlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )
+ == BooleanFlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ));
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ Scb->ValidDataToDisk =
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
+ SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_UNINITIALIZE_ON_RESTORE );
+
+ UninitializeOnClose = TRUE;
+ }
+
+ //
+ // Now snapshot this Scb. We use a try-finally so we can uninitialize
+ // the scb if neccessary.
+ //
+
+ NtfsSnapshotScb( IrpContext, Scb );
+
+ if (UninitializeOnClose &&
+ NtfsPerformQuotaOperation( Fcb ) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ) &&
+ FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode ));
+
+ //
+ // This is a new stream with zero size indicate
+ // the quota is based on allocation size.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+ }
+
+ UninitializeOnClose = FALSE;
+
+ //
+ // First allocate the space he wants.
+ //
+
+ SavedClusterCount =
+ ClusterCount = LlClustersFromBytes(Fcb->Vcb, Size);
+
+ Scb->TotalAllocated = 0;
+
+ if (Size != 0) {
+
+ ASSERT( NtfsIsExclusiveScb( Scb ));
+
+ Scb->ScbSnapshot->LowestModifiedVcn = 0;
+ Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
+
+ NtfsAllocateClusters( IrpContext,
+ Fcb->Vcb,
+ Scb,
+ (LONGLONG)0,
+ (BOOLEAN)!NtfsIsTypeCodeUserData( AttributeTypeCode ),
+ ClusterCount,
+ &ClusterCount );
+
+#ifdef _CAIRO_
+
+ //
+ // Make sure the owner is allowed to have these
+ // clusters.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ LONGLONG Delta = LlBytesFromClusters(Fcb->Vcb, ClusterCount);
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ ASSERT( !NtfsPerformQuotaOperation( Fcb ) ||
+ FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED) ||
+ FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ));
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ Fcb,
+ &Delta,
+ LogIt,
+ TRUE );
+ }
+
+#endif // _CAIRO_
+
+ }
+
+ //
+ // Now create the attribute. Remember if this routine
+ // cut the allocation because of logging problems.
+ //
+
+ FullAllocation = NtfsCreateAttributeWithAllocation( IrpContext,
+ Scb,
+ AttributeTypeCode,
+ AttributeName,
+ AttributeFlags,
+ LogIt,
+ NewLocationSpecified,
+ NewLocation );
+
+ if (AllocateAll &&
+ (!FullAllocation ||
+ (ClusterCount < SavedClusterCount))) {
+
+ //
+ // If we are creating the attribute, then we only need to pass a
+ // file object below if we already cached it ourselves, such as
+ // in the case of ConvertToNonresident.
+ //
+
+ NtfsAddAllocation( IrpContext,
+ Scb->FileObject,
+ Scb,
+ ClusterCount,
+ (SavedClusterCount - ClusterCount),
+ FALSE );
+
+ //
+ // Show that we allocated all of the space.
+ //
+
+ ClusterCount = SavedClusterCount;
+ FullAllocation = TRUE;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsAllocateAttribute );
+
+ //
+ // Cleanup the attribute context on the way out.
+ //
+
+ if (!NewLocationSpecified) {
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ //
+ // Clear out the Scb if it was uninitialized to begin with.
+ //
+
+ if (UninitializeOnClose) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
+ SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_UNINITIALIZE_ON_RESTORE );
+ }
+ }
+
+ return (FullAllocation && (SavedClusterCount <= ClusterCount));
+}
+
+
+VOID
+NtfsAddAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN LONGLONG ClusterCount,
+ IN BOOLEAN AskForMore
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds allocation to an existing nonresident attribute. None of
+ the allocation is allowed to already exist, as this would make error recovery
+ too difficult. The caller must insure that he only asks for space not already
+ allocated.
+
+Arguments:
+
+ FileObject - FileObject for the Scb
+
+ Scb - Scb for the attribute needing allocation
+
+ StartingVcn - First Vcn to be allocated.
+
+ ClusterCount - Number of clusters to allocate.
+
+ AskForMore - Indicates if we want to ask for extra allocation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG DesiredClusterCount;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ BOOLEAN Extending;
+
+
+ PVCB Vcb = IrpContext->Vcb;
+
+ LONGLONG LlTemp1;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_EXCLUSIVE_SCB( Scb );
+
+ DebugTrace( +1, Dbg, ("NtfsAddAllocation\n") );
+
+ //
+ // We cannot add space in this high level routine during restart.
+ // Everything we can use is in the Mcb.
+ //
+
+ if (FlagOn(Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS)) {
+
+ DebugTrace( -1, Dbg, ("NtfsAddAllocation (Nooped for Restart) -> VOID\n") );
+
+ return;
+ }
+
+ //
+ // If the user's request extends beyond 32 bits for the cluster number
+ // raise a disk full error.
+ //
+
+ LlTemp1 = ClusterCount + StartingVcn;
+
+ if ((((PLARGE_INTEGER)&ClusterCount)->HighPart != 0)
+ || (((PLARGE_INTEGER)&StartingVcn)->HighPart != 0)
+ || (((PLARGE_INTEGER)&LlTemp1)->HighPart != 0)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ //
+ // First make sure the Mcb is loaded.
+ //
+
+ NtfsPreloadAllocation( IrpContext, Scb, StartingVcn, StartingVcn + ClusterCount - 1 );
+
+ //
+ // Now make the call to add the new allocation, and get out if we do
+ // not actually have to allocate anything. Before we do the allocation
+ // call check if we need to compute a new desired cluster count for
+ // extending a data attribute. We never allocate more than the requested
+ // clusters for the Mft.
+ //
+
+ Extending = (BOOLEAN)((LONGLONG)LlBytesFromClusters(Vcb, (StartingVcn + ClusterCount)) >
+ Scb->Header.AllocationSize.QuadPart);
+
+ //
+ // Check if we need to modified the base Vcn value stored in the snapshot for
+ // the abort case.
+ //
+
+ ASSERT( NtfsIsExclusiveScb( Scb ));
+
+ if (Scb->ScbSnapshot == NULL) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+
+ if (Scb->ScbSnapshot != NULL) {
+
+ if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
+
+ Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
+ }
+
+ LlTemp1 -= 1;
+ if (LlTemp1 > Scb->ScbSnapshot->HighestModifiedVcn) {
+
+ if (Extending) {
+ Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
+ } else {
+ Scb->ScbSnapshot->HighestModifiedVcn = LlTemp1;
+ }
+ }
+ }
+
+ ASSERT( (Scb->ScbSnapshot != NULL) ||
+ !NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
+
+ if (AskForMore) {
+
+ ULONG TailClusters;
+
+ //
+ // Use a simpler, more aggressive allocation strategy.
+ //
+ //
+ // ULONG RunsInMcb;
+ // LARGE-INTEGER AllocatedClusterCount;
+ // LARGE-INTEGER Temp;
+ //
+ // //
+ // // For the desired run cluster allocation count we compute the following
+ // // formula
+ // //
+ // // DesiredClusterCount = Max(ClusterCount, Min(AllocatedClusterCount, 2^RunsInMcb))
+ // //
+ // // where we will not let the RunsInMcb go beyond 10
+ // //
+ //
+ // //
+ // // First compute 2^RunsInMcb
+ // //
+ //
+ // RunsInMcb = FsRtlNumberOfRunsInLargeMcb( &Scb->Mcb );
+ // Temp = XxFromUlong(1 << (RunsInMcb < 10 ? RunsInMcb : 10));
+ //
+ // //
+ // // Next compute Min(AllocatedClusterCount, 2^RunsInMcb)
+ // //
+ //
+ // AllocatedClusterCount = XxClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize );
+ // Temp = (XxLtr(AllocatedClusterCount, Temp) ? AllocatedClusterCount : Temp);
+ //
+ // //
+ // // Now compute the Max function
+ // //
+ //
+ // DesiredClusterCount = (XxGtr(ClusterCount, Temp) ? ClusterCount : Temp);
+ //
+
+ DesiredClusterCount = ClusterCount << 5;
+
+#ifdef _CAIRO_
+
+ if (NtfsPerformQuotaOperation(Scb->Fcb)) {
+
+ NtfsGetRemainingQuota( IrpContext,
+ Scb->Fcb->OwnerId,
+ &LlTemp1,
+ &Scb->Fcb->QuotaControl->QuickIndexHint );
+
+ LlTemp1 = LlClustersFromBytesTruncate( Vcb, LlTemp1 );
+
+ if (DesiredClusterCount > LlTemp1) {
+
+ //
+ // The owner is near their quota limit. Do not grow the
+ // file past the requested amount. Note we do not bother
+ // calculating a desired amount based on the remaining quota.
+ // This keeps us from using up a bunch of quota that we may
+ // not need when the user is near the limit.
+ //
+
+ DesiredClusterCount = ClusterCount;
+ }
+ }
+
+#endif _CAIRO_
+
+ //
+ // Make sure we don't extend this request into more than 32 bits.
+ //
+
+ LlTemp1 = DesiredClusterCount + StartingVcn;
+
+ if ((((PLARGE_INTEGER)&DesiredClusterCount)->HighPart != 0)
+ || (((PLARGE_INTEGER)&LlTemp1)->HighPart != 0)) {
+
+ DesiredClusterCount = MAXULONG - StartingVcn;
+ }
+
+ //
+ // Round up the cluster count so we fall on a page boundary.
+ //
+
+ TailClusters = (((ULONG)StartingVcn) + (ULONG)ClusterCount)
+ & (Vcb->ClustersPerPage - 1);
+
+ if (TailClusters != 0) {
+
+ ClusterCount = ClusterCount + (Vcb->ClustersPerPage - TailClusters);
+ }
+
+ } else {
+
+ DesiredClusterCount = ClusterCount;
+ }
+
+ //
+ // If the file is compressed, make sure we round the allocation
+ // size to a compression unit boundary, so we correctly interpret
+ // the compression state of the data at the point we are
+ // truncating to. I.e., the danger is that we throw away one
+ // or more clusters at the end of compressed data! Note that this
+ // adjustment could cause us to noop the call.
+ //
+
+ if ((Scb->CompressionUnit != 0) &&
+ (StartingVcn < LlClustersFromBytes(Vcb, (Scb->ValidDataToDisk + Scb->CompressionUnit - 1) &
+ ~(Scb->CompressionUnit - 1)))) {
+
+ ULONG CompressionUnitDeficit;
+
+ CompressionUnitDeficit = ClustersFromBytes( Scb->Vcb, Scb->CompressionUnit );
+
+ if (((ULONG)StartingVcn) & (CompressionUnitDeficit - 1)) {
+
+ //
+ // BUGBUG: It appears this code is never called.
+ //
+
+ ASSERT(FALSE);
+
+ CompressionUnitDeficit -= ((ULONG)StartingVcn) & (CompressionUnitDeficit - 1);
+ if (ClusterCount <= CompressionUnitDeficit) {
+ if (DesiredClusterCount <= CompressionUnitDeficit) {
+ return;
+ }
+ ClusterCount = 0;
+ } else {
+ ClusterCount = ClusterCount - CompressionUnitDeficit;
+ }
+ StartingVcn = StartingVcn + CompressionUnitDeficit;
+ DesiredClusterCount = DesiredClusterCount - CompressionUnitDeficit;
+ }
+ }
+
+ //
+ // Prepare for looking up attribute records to get the retrieval
+ // information.
+ //
+
+ NtfsInitializeAttributeContext( &Context );
+
+#ifdef _CAIRO_
+ if (Extending &&
+ FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
+ NtfsPerformQuotaOperation( Scb->Fcb )) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ //
+ // The quota index must be acquired before the mft scb is acquired.
+ //
+
+ ASSERT(!NtfsIsExclusiveScb( Vcb->MftScb ) || ExIsResourceAcquiredSharedLite( Vcb->QuotaTableScb->Fcb->Resource ));
+
+ NtfsAcquireQuotaControl( IrpContext, Scb->Fcb->QuotaControl );
+
+ }
+#endif // _CAIRO_
+
+ try {
+
+ while (TRUE) {
+
+ // Toplevel action is currently incompatible with our error recovery.
+ // It also costs in performance.
+ //
+ // //
+ // // Start the top-level action by remembering the current UndoNextLsn.
+ // //
+ //
+ // if (IrpContext->TransactionId != 0) {
+ //
+ // PTRANSACTION_ENTRY TransactionEntry;
+ //
+ // NtfsAcquireSharedRestartTable( &Vcb->TransactionTable, TRUE );
+ //
+ // TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ // &Vcb->TransactionTable, IrpContext->TransactionId );
+ //
+ // StartLsn = TransactionEntry->UndoNextLsn;
+ // SavedUndoRecords = TransactionEntry->UndoRecords;
+ // SavedUndoBytes = TransactionEntry->UndoBytes;
+ // NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ //
+ // } else {
+ //
+ // StartLsn = *(PLSN)&Li0;
+ // SavedUndoRecords = 0;
+ // SavedUndoBytes = 0;
+ // }
+ //
+
+ //
+ // Remember that the clusters are only in the Scb now.
+ //
+
+ if (NtfsAllocateClusters( IrpContext,
+ Scb->Vcb,
+ Scb,
+ StartingVcn,
+ (BOOLEAN)!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ),
+ ClusterCount,
+ &DesiredClusterCount )) {
+
+
+ //
+ // We defer looking up the attribute to make the "already-allocated"
+ // case faster.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
+
+ //
+ // Now add the space to the file record, if any was allocated.
+ //
+
+ if (Extending) {
+
+ LlTemp1 = Scb->Header.AllocationSize.QuadPart;
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &Context,
+ NULL,
+ NULL );
+
+#ifdef _CAIRO_
+
+ //
+ // Make sure the owner is allowed to have these
+ // clusters.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ //
+ // Note the allocated clusters cannot be used
+ // here because StartingVcn may be greater
+ // then allocation size.
+ //
+
+ LlTemp1 = Scb->Header.AllocationSize.QuadPart - LlTemp1;
+
+ ASSERT( !NtfsPerformQuotaOperation( Scb->Fcb ) || FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED));
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ Scb->Fcb,
+ &LlTemp1,
+ TRUE,
+ TRUE );
+ }
+#endif
+ } else {
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &Context,
+ &StartingVcn,
+ &ClusterCount );
+ }
+
+ //
+ // If he did not allocate anything, make sure we get out below.
+ //
+
+ } else {
+ DesiredClusterCount = ClusterCount;
+ }
+
+ // Toplevel action is currently incompatible with our error recovery.
+ //
+ // //
+ // // Now we will end this routine as a top-level action so that
+ // // anyone can use this extended space.
+ // //
+ // // ****If we find that we are always keeping the Scb exclusive anyway,
+ // // we could eliminate this log call.
+ // //
+ //
+ // (VOID)NtfsWriteLog( IrpContext,
+ // Vcb->MftScb,
+ // NULL,
+ // EndTopLevelAction,
+ // NULL,
+ // 0,
+ // CompensationLogRecord,
+ // (PVOID)&StartLsn,
+ // sizeof(LSN),
+ // Li0,
+ // 0,
+ // 0,
+ // 0 );
+ //
+ // //
+ // // Now reset the undo information for the top-level action.
+ // //
+ //
+ // {
+ // PTRANSACTION_ENTRY TransactionEntry;
+ //
+ // NtfsAcquireSharedRestartTable( &Vcb->TransactionTable, TRUE );
+ //
+ // TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ // &Vcb->TransactionTable, IrpContext->TransactionId );
+ //
+ // ASSERT(TransactionEntry->UndoBytes >= SavedUndoBytes);
+ //
+ // LfsResetUndoTotal( Vcb->LogHandle,
+ // TransactionEntry->UndoRecords - SavedUndoRecords,
+ // -(TransactionEntry->UndoBytes - SavedUndoBytes) );
+ //
+ // TransactionEntry->UndoRecords = SavedUndoRecords;
+ // TransactionEntry->UndoBytes = SavedUndoBytes;
+ //
+ //
+ // NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ // }
+ //
+
+ //
+ // Call the Cache Manager to extend the section, now that we have
+ // succeeded.
+ //
+
+ if (ARGUMENT_PRESENT( FileObject) && Extending && CcIsFileCached(FileObject)) {
+
+ CcSetFileSizes( FileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+
+ //
+ // Set up to truncate on close.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ //
+ // See if we need to loop back.
+ //
+
+ if (DesiredClusterCount < ClusterCount) {
+
+ NtfsCleanupAttributeContext( &Context );
+
+ //
+ // Commit the current transaction if we have one.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Adjust our parameters and reinitialize the context
+ // for the loop back.
+ //
+
+ StartingVcn = StartingVcn + DesiredClusterCount;
+ ClusterCount = ClusterCount - DesiredClusterCount;
+ DesiredClusterCount = ClusterCount;
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Else we are done.
+ //
+
+ } else {
+
+ break;
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsAddAllocation );
+
+ //
+ // Cleanup the attribute context on the way out.
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddAllocation -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsDeleteAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN BreakupAllowed
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes allocation from an existing nonresident attribute. If all
+ or part of the allocation does not exist, the effect is benign, and only the
+ remaining allocation is deleted.
+
+Arguments:
+
+ FileObject - FileObject for the Scb. This should always be specified if
+ possible, and must be specified if it is possible that MM has a
+ section created.
+
+ Scb - Scb for the attribute needing allocation
+
+ StartingVcn - First Vcn to be deallocated.
+
+ EndingVcn - Last Vcn to be deallocated, or xxMax to truncate at StartingVcn.
+ If EndingVcn is *not* xxMax, a sparse deallocation is performed,
+ and none of the stream sizes are changed.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are deleting the file record, and
+ will be logging this delete.
+
+ BreakupAllowed - TRUE if the caller can tolerate breaking up the deletion of
+ allocation into multiple transactions, if there are a large
+ number of runs.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ VCN MyStartingVcn, MyEndingVcn;
+ VCN BlockStartingVcn = 0;
+ PVOID FirstRangePtr;
+ ULONG FirstRunIndex;
+ PVOID LastRangePtr;
+ ULONG LastRunIndex;
+ BOOLEAN BreakingUp = FALSE;
+
+ LCN TempLcn;
+ LONGLONG TempCount;
+ ULONG CompressionUnitInClusters = 1;
+
+ PAGED_CODE();
+
+ if (Scb->CompressionUnit != 0) {
+ CompressionUnitInClusters = ClustersFromBytes( Scb->Vcb, Scb->CompressionUnit );
+ }
+
+ //
+ // If the file is compressed, make sure we round the allocation
+ // size to a compression unit boundary, so we correctly interpret
+ // the compression state of the data at the point we are
+ // truncating to. I.e., the danger is that we throw away one
+ // or more clusters at the end of compressed data! Note that this
+ // adjustment could cause us to noop the call.
+ //
+
+ if (Scb->CompressionUnit != 0) {
+
+ //
+ // Now check if we are truncating at the end of the file.
+ //
+
+ if (EndingVcn == MAXLONGLONG) {
+
+ StartingVcn = StartingVcn + (CompressionUnitInClusters - 1);
+ ((ULONG)StartingVcn) &= ~(CompressionUnitInClusters - 1);
+ }
+ }
+
+ //
+ // Make sure we have a snapshot and update it with the range of this deallocation.
+ //
+
+ ASSERT( NtfsIsExclusiveScb( Scb ));
+
+ if (Scb->ScbSnapshot == NULL) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+
+ //
+ // Make sure update the VCN range in the snapshot. We need to
+ // do it each pass through the loop
+ //
+
+ if (Scb->ScbSnapshot != NULL) {
+
+ if (StartingVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
+
+ Scb->ScbSnapshot->LowestModifiedVcn = StartingVcn;
+ }
+
+ if (EndingVcn > Scb->ScbSnapshot->HighestModifiedVcn) {
+
+ Scb->ScbSnapshot->HighestModifiedVcn = EndingVcn;
+ }
+ }
+
+ ASSERT( (Scb->ScbSnapshot != NULL) ||
+ !NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
+
+ //
+ // We may not be able to preload the entire allocation for an
+ // extremely large fragmented file. The number of Mcb's may exhaust
+ // available pool. We will break the range to deallocate into smaller
+ // ranges when preloading the allocation.
+ //
+
+ do {
+
+ LONGLONG ClustersPer4Gig;
+
+ //
+ // If this is a large file and breakup is allowed then see if we
+ // want to break up the range of the deallocation.
+ //
+
+ if ((Scb->Header.AllocationSize.HighPart != 0) && BreakupAllowed) {
+
+ //
+ // If this is the first pass through then determine the starting point
+ // for this range.
+ //
+
+ if (BlockStartingVcn == 0) {
+
+ ClustersPer4Gig = LlClustersFromBytesTruncate( Scb->Vcb,
+ 0x0000000100000000 );
+ MyEndingVcn = EndingVcn;
+
+ if (EndingVcn == MAXLONGLONG) {
+
+ MyEndingVcn = LlClustersFromBytesTruncate( Scb->Vcb,
+ Scb->Header.AllocationSize.QuadPart ) - 1;
+ }
+
+ BlockStartingVcn = MyEndingVcn - ClustersPer4Gig;
+
+ //
+ // Remember we are breaking up now, and that as a result
+ // we have to log everything.
+ //
+
+ BreakingUp = TRUE;
+ LogIt = TRUE;
+
+ } else {
+
+ //
+ // If we are truncating from the end of the file then raise CANT_WAIT. This will
+ // cause us to release our resources periodically when deleting a large file.
+ //
+
+ if (BreakingUp && (EndingVcn == MAXLONGLONG)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ BlockStartingVcn -= ClustersPer4Gig;
+ }
+
+ if (BlockStartingVcn < StartingVcn) {
+
+ BlockStartingVcn = StartingVcn;
+
+ } else if (Scb->CompressionUnit != 0) {
+
+ //
+ // Now check if we are truncating at the end of the file.
+ // Always truncate to a compression unit boundary.
+ //
+
+ if (EndingVcn == MAXLONGLONG) {
+
+ BlockStartingVcn += (CompressionUnitInClusters - 1);
+ ((ULONG)BlockStartingVcn) &= ~(CompressionUnitInClusters - 1);
+ }
+ }
+
+ } else {
+
+ BlockStartingVcn = StartingVcn;
+ }
+
+ //
+ // First make sure the Mcb is loaded. Note it is possible that
+ // we could need the previous range loaded if the delete starts
+ // at the beginning of a file record boundary, thus the -1.
+ //
+
+ NtfsPreloadAllocation( IrpContext, Scb, ((BlockStartingVcn != 0) ? (BlockStartingVcn - 1) : 0), EndingVcn );
+
+ //
+ // Loop to do one or more deallocate calls.
+ //
+
+ MyEndingVcn = EndingVcn;
+ do {
+
+ //
+ // Now lookup and get the indices for the first Vcn being deleted.
+ // If we are off the end, get out. We do this in the loop, because
+ // conceivably deleting space could change the range pointer and
+ // index of the first entry.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ BlockStartingVcn,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &FirstRangePtr,
+ &FirstRunIndex )) {
+
+ break;
+ }
+
+ //
+ // Now see if we can deallocate everything at once.
+ //
+
+ MyStartingVcn = BlockStartingVcn;
+ LastRunIndex = MAXULONG;
+
+ if (BreakupAllowed) {
+
+ //
+ // Now lookup and get the indices for the last Vcn being deleted.
+ // If we are off the end, get the last index.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ MyEndingVcn,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &LastRangePtr,
+ &LastRunIndex )) {
+
+ NtfsNumberOfRunsInRange(&Scb->Mcb, LastRangePtr, &LastRunIndex);
+ }
+
+ //
+ // If the Vcns to delete span multiple ranges, or there
+ // are too many in the last range to delete, then we
+ // will calculate the index of a run to start with for
+ // this pass through the loop.
+ //
+
+ if ((FirstRangePtr != LastRangePtr) ||
+ ((LastRunIndex - FirstRunIndex) > MAXIMUM_RUNS_AT_ONCE)) {
+
+ //
+ // Figure out where we can afford to truncate to.
+ //
+
+ if (LastRunIndex >= MAXIMUM_RUNS_AT_ONCE) {
+ LastRunIndex -= MAXIMUM_RUNS_AT_ONCE;
+ } else {
+ LastRunIndex = 0;
+ }
+
+ //
+ // Now lookup the first Vcn in this run.
+ //
+
+ NtfsGetNextNtfsMcbEntry( &Scb->Mcb,
+ &LastRangePtr,
+ LastRunIndex,
+ &MyStartingVcn,
+ &TempLcn,
+ &TempCount );
+
+ ASSERT(MyStartingVcn > BlockStartingVcn);
+
+ //
+ // If compressed, round down to a compression unit boundary.
+ //
+
+ ((ULONG)MyStartingVcn) &= ~(CompressionUnitInClusters - 1);
+
+ //
+ // Remember we are breaking up now, and that as a result
+ // we have to log everything.
+ //
+
+ BreakingUp = TRUE;
+ LogIt = TRUE;
+ }
+ }
+
+#ifdef _CAIRO_
+ //
+ // CAIROBUG Consider optimizing this code when the cairo ifdef's
+ // are removed.
+ //
+
+ //
+ // If this is a user data stream and we are truncating to end the
+ // return the quota to the owner.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
+ EndingVcn == MAXLONGLONG) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ ASSERT( !NtfsPerformQuotaOperation( Scb->Fcb ) ||
+ FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED) ||
+ FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ));
+
+ //
+ // Calculate the amount that allocation size is being reduced.
+ //
+
+ TempCount = LlBytesFromClusters( Scb->Vcb, MyStartingVcn ) -
+ Scb->Header.AllocationSize.QuadPart;
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ Scb->Fcb,
+ &TempCount,
+ TRUE,
+ FALSE );
+
+ }
+#endif // _CAIRO_
+
+ //
+ // Now deallocate a range of clusters
+ //
+
+ NtfsDeleteAllocationInternal( IrpContext,
+ FileObject,
+ Scb,
+ MyStartingVcn,
+ EndingVcn,
+ LogIt );
+
+ //
+ // Now, if we are breaking up this deallocation, then do some
+ // transaction cleanup.
+ //
+
+ if (BreakingUp) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Move the ending Vcn backwards in the file. This will
+ // let us move down to the next earlier file record if
+ // this case spans multiple file records.
+ //
+
+ MyEndingVcn = MyStartingVcn - 1;
+ }
+
+ } while (MyStartingVcn != BlockStartingVcn);
+
+ } while (BlockStartingVcn != StartingVcn);
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+NtfsDeleteAllocationInternal (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ IN BOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes allocation from an existing nonresident attribute. If all
+ or part of the allocation does not exist, the effect is benign, and only the
+ remaining allocation is deleted.
+
+Arguments:
+
+ FileObject - FileObject for the Scb. This should always be specified if
+ possible, and must be specified if it is possible that MM has a
+ section created.
+
+ Scb - Scb for the attribute needing allocation
+
+ StartingVcn - First Vcn to be deallocated.
+
+ EndingVcn - Last Vcn to be deallocated, or xxMax to truncate at StartingVcn.
+ If EndingVcn is *not* xxMax, a sparse deallocation is performed,
+ and none of the stream sizes are changed.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are deleting the file record, and
+ will be logging this delete.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context, TempContext;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ LONGLONG SizeInBytes, SizeInClusters;
+ VCN Vcn1;
+ PVCB Vcb = Scb->Vcb;
+ BOOLEAN AddSpaceBack = FALSE;
+ BOOLEAN SplitMcb = FALSE;
+ BOOLEAN UpdatedAllocationSize = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_EXCLUSIVE_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteAllocation\n") );
+
+ //
+ // Calculate new allocation size, assuming truncate.
+ //
+
+ SizeInBytes = LlBytesFromClusters( Vcb, StartingVcn );
+
+ ASSERT( (Scb->ScbSnapshot == NULL) ||
+ (Scb->ScbSnapshot->LowestModifiedVcn <= StartingVcn) );
+
+ //
+ // If this is a sparse deallocation, then we will have to call
+ // NtfsAddAttributeAllocation at the end to complete the fixup.
+ //
+
+ if (EndingVcn != MAXLONGLONG) {
+
+ AddSpaceBack = TRUE;
+
+ //
+ // If we have not written anything beyond the last Vcn to be
+ // deleted, then we can actually call FsRtlSplitLargeMcb to
+ // slide the allocated space up and keep the file contiguous!
+ //
+ // Ignore this if this is the Mft and we are creating a hole or
+ // if we are in the process of changing the compression state.
+ //
+ // If we were called from either SetEOF or SetAllocation for a
+ // compressed file then we can be doing a flush for the last
+ // page of the file as a result of a call to CcSetFileSizes.
+ // In this case we don't want to split the Mcb because we could
+ // reenter CcSetFileSizes and throw away the last page.
+ //
+
+ if (Scb != Vcb->MftScb &&
+ !FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE ) &&
+ (Scb->CompressionUnit != 0) &&
+ (EndingVcn >= LlClustersFromBytes(Vcb, (Scb->ValidDataToDisk + Scb->CompressionUnit - 1) &
+ ~(Scb->CompressionUnit - 1))) &&
+ ((IrpContext == IrpContext->TopLevelIrpContext) ||
+ (IrpContext->TopLevelIrpContext->MajorFunction != IRP_MJ_SET_INFORMATION))) {
+
+ ASSERT( FlagOn( Scb->ScbState, SCB_STATE_COMPRESSED ));
+
+ //
+ // If we are going to split the Mcb, then make sure it is fully loaded.
+ // Do not bother to split if there are multiple ranges involved, so we
+ // do not end up rewriting lots of file records.
+ //
+
+ if (NtfsPreloadAllocation(IrpContext, Scb, StartingVcn, MAXLONGLONG) <= 1) {
+
+ SizeInClusters = (EndingVcn - StartingVcn) + 1;
+
+ ASSERT( NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ));
+
+ SplitMcb = NtfsSplitNtfsMcb( &Scb->Mcb, StartingVcn, SizeInClusters );
+
+ //
+ // If the delete is off the end, we can get out.
+ //
+
+ if (!SplitMcb) {
+ return;
+ }
+
+ //
+ // We must protect the call below with a try-finally in
+ // order to unload the Split Mcb. If there is no transaction
+ // underway then a release of the Scb would cause the
+ // snapshot to go away.
+ //
+
+ try {
+
+ //
+ // We are not properly synchronized to change AllocationSize,
+ // so we will delete any clusters that may have slid off the
+ // end. Since we are going to smash EndingVcn soon anyway,
+ // use it as a scratch to hold AllocationSize in Vcns...
+ //
+
+ EndingVcn = LlClustersFromBytes(Vcb, Scb->Header.AllocationSize.QuadPart);
+
+ NtfsDeallocateClusters( IrpContext,
+ Vcb,
+ &Scb->Mcb,
+ EndingVcn,
+ MAXLONGLONG,
+ &Scb->TotalAllocated );
+
+ } finally {
+
+ if (AbnormalTermination()) {
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb,
+ StartingVcn,
+ MAXLONGLONG,
+ FALSE,
+ FALSE );
+ }
+ }
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb,
+ EndingVcn,
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+
+ //
+ // Since we did a split, jam highest modified all the way up.
+ //
+
+ Scb->ScbSnapshot->HighestModifiedVcn = MAXLONGLONG;
+
+ //
+ // We will have to redo all of the allocation to the end now.
+ //
+
+ EndingVcn = MAXLONGLONG;
+ }
+ }
+ }
+
+ //
+ // Now make the call to delete the allocation (if we did not just split
+ // the Mcb), and get out if we didn't have to do anything, because a
+ // hole is being created where there is already a hole.
+ //
+
+ if (!SplitMcb &&
+ !NtfsDeallocateClusters( IrpContext,
+ Vcb,
+ &Scb->Mcb,
+ StartingVcn,
+ EndingVcn,
+ &Scb->TotalAllocated ) &&
+ EndingVcn != MAXLONGLONG) {
+
+ return;
+ }
+
+ //
+ // On successful truncates, we nuke the entire range here.
+ //
+
+ if (!SplitMcb && (EndingVcn == MAXLONGLONG)) {
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb, StartingVcn, MAXLONGLONG, TRUE, FALSE );
+ }
+
+ //
+ // Prepare for looking up attribute records to get the retrieval
+ // information.
+ //
+
+ NtfsInitializeAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &TempContext );
+
+ try {
+
+ //
+ // Lookup the attribute record so we can ultimately delete space to it.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, &StartingVcn, &Context );
+
+ //
+ // Now loop to delete the space to the file record. Do not do this if LogIt
+ // is FALSE, as this is someone trying to delete the entire file
+ // record, so we do not have to clean up the attribute record.
+ //
+
+ if (LogIt) {
+
+ do {
+
+ Attribute = NtfsFoundAttribute(&Context);
+
+ //
+ // If there is no overlap, then continue.
+ //
+
+ if ((Attribute->Form.Nonresident.HighestVcn < StartingVcn) ||
+ (Attribute->Form.Nonresident.LowestVcn > EndingVcn)) {
+
+ continue;
+
+ //
+ // If all of the allocation is going away, then delete the entire
+ // record. We have to show that the allocation is already deleted
+ // to avoid being called back via NtfsDeleteAttributeRecord! We
+ // avoid this for the first instance of this attribute.
+ //
+
+ } else if ((Attribute->Form.Nonresident.LowestVcn >= StartingVcn) &&
+ (EndingVcn == MAXLONGLONG) &&
+ (Attribute->Form.Nonresident.LowestVcn != 0)) {
+
+ Context.FoundAttribute.AttributeAllocationDeleted = TRUE;
+ NtfsDeleteAttributeRecord( IrpContext, Scb->Fcb, LogIt, FALSE, &Context );
+ Context.FoundAttribute.AttributeAllocationDeleted = FALSE;
+
+ //
+ // If just part of the allocation is going away, then make the
+ // call here to reconstruct the mapping pairs array.
+ //
+
+ } else {
+
+ //
+ // If this is the end of a sparse deallocation, then break out
+ // because we will rewrite this file record below anyway.
+ //
+
+ if (EndingVcn <= Attribute->Form.Nonresident.HighestVcn) {
+ break;
+
+ //
+ // If we split the Mcb, then make sure we only regenerate the
+ // mapping pairs once at the split point (but continue to
+ // scan for any entire records to delete).
+ //
+
+ } else if (SplitMcb) {
+ continue;
+ }
+
+ //
+ // If this is a sparse deallocation, then we have to call to
+ // add the allocation, since it is possible that the file record
+ // must split.
+ //
+
+ if (EndingVcn != MAXLONGLONG) {
+
+ //
+ // Compute the last Vcn in the file, Then remember if it is smaller,
+ // because that is the last one we will delete to, in that case.
+ //
+
+ Vcn1 = Attribute->Form.Nonresident.HighestVcn;
+
+ SizeInClusters = (Vcn1 - Attribute->Form.Nonresident.LowestVcn) + 1;
+ Vcn1 = Attribute->Form.Nonresident.LowestVcn;
+
+ NtfsCleanupAttributeContext( &TempContext );
+ NtfsInitializeAttributeContext( &TempContext );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &TempContext );
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &TempContext,
+ &Vcn1,
+ &SizeInClusters );
+
+ //
+ // Since we used a temporary context we will need to
+ // restart the scan from the first file record. We update
+ // the range to deallocate by the last operation. In most
+ // cases we will only need to modify one file record and
+ // we can exit this loop.
+ //
+
+ StartingVcn = Vcn1 + SizeInClusters;
+
+ if (StartingVcn > EndingVcn) {
+
+ break;
+ }
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
+ continue;
+
+ //
+ // Otherwise, we can simply delete the allocation, because
+ // we know the file record cannot grow.
+ //
+
+ } else {
+
+ Vcn1 = StartingVcn - 1;
+
+ NtfsDeleteAttributeAllocation( IrpContext,
+ Scb,
+ LogIt,
+ &Vcn1,
+ &Context,
+ TRUE );
+
+ //
+ // The call above will update the allocation size and
+ // set the new file sizes on disk.
+ //
+
+ UpdatedAllocationSize = TRUE;
+ }
+ }
+
+ } while (NtfsLookupNextAttributeForScb(IrpContext, Scb, &Context));
+
+ //
+ // If this deletion makes the file sparse, then we have to call
+ // NtfsAddAttributeAllocation to regenerate the mapping pairs.
+ // Note that potentially they may no longer fit, and we could actually
+ // have to add a file record.
+ //
+
+ if (AddSpaceBack) {
+
+ //
+ // If we did not just split the Mcb, we have to calculate the
+ // SizeInClusters parameter for NtfsAddAttributeAllocation.
+ //
+
+ if (!SplitMcb) {
+
+ //
+ // Compute the last Vcn in the file, Then remember if it is smaller,
+ // because that is the last one we will delete to, in that case.
+ //
+
+ Vcn1 = Attribute->Form.Nonresident.HighestVcn;
+
+ //
+ // Get out if there is nothing to delete.
+ //
+
+ if (Vcn1 < StartingVcn) {
+ try_return(NOTHING);
+ }
+
+ SizeInClusters = (Vcn1 - Attribute->Form.Nonresident.LowestVcn) + 1;
+ Vcn1 = Attribute->Form.Nonresident.LowestVcn;
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &Context,
+ &Vcn1,
+ &SizeInClusters );
+
+ } else {
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &Context,
+ NULL,
+ NULL );
+ }
+
+ //
+ // If we truncated the file by removing a file record but didn't update
+ // the new allocation size then do so now. We don't have to worry about
+ // this for the sparse deallocation path.
+ //
+
+ } else if (!UpdatedAllocationSize) {
+
+ Scb->Header.AllocationSize.QuadPart = SizeInBytes;
+
+ if (Scb->Header.ValidDataLength.QuadPart > SizeInBytes) {
+ Scb->Header.ValidDataLength.QuadPart = SizeInBytes;
+ }
+
+ if (Scb->Header.FileSize.QuadPart > SizeInBytes) {
+ Scb->Header.FileSize.QuadPart = SizeInBytes;
+ }
+
+ //
+ // Possibly update ValidDataToDisk
+ //
+
+ if (SizeInBytes < Scb->ValidDataToDisk) {
+ Scb->ValidDataToDisk = SizeInBytes;
+ }
+ }
+ }
+
+ //
+ // If this was a sparse deallocation, it is time to get out once we
+ // have fixed up the allocation information.
+ //
+
+ if (SplitMcb || (EndingVcn != MAXLONGLONG)) {
+ try_return(NOTHING);
+ }
+
+ //
+ // We update the allocation size in the attribute, only for normal
+ // truncates (AddAttributeAllocation does this for SplitMcb case).
+ //
+
+ if (LogIt) {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+ }
+
+ //
+ // Call the Cache Manager to change allocation size for either
+ // truncate or SplitMcb case (where EndingVcn was set to xxMax!).
+ //
+
+ if (ARGUMENT_PRESENT(FileObject) && CcIsFileCached( FileObject )) {
+
+ CcSetFileSizes( FileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+
+ //
+ // Free any reserved clusters in the space freed.
+ //
+
+ if ((EndingVcn == MAXLONGLONG) &&
+ FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) &&
+ (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA)) {
+
+ NtfsFreeReservedClusters( Scb,
+ LlBytesFromClusters(Vcb, StartingVcn),
+ 0 );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsDeleteAllocation );
+
+ //
+ // Cleanup the attribute context on the way out.
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsCleanupAttributeContext( &TempContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteAllocation -> VOID\n") );
+
+ return;
+}
+
+
+ULONG
+NtfsGetSizeForMappingPairs (
+ IN PNTFS_MCB Mcb,
+ IN ULONG BytesAvailable,
+ IN VCN LowestVcn,
+ IN PVCN StopOnVcn OPTIONAL,
+ OUT PVCN StoppedOnVcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine calculates the size required to describe the given Mcb in
+ a mapping pairs array. The caller may specify how many bytes are available
+ for mapping pairs storage, for the event that the entire Mcb cannot be
+ be represented. In any case, StoppedOnVcn returns the Vcn to supply to
+ NtfsBuildMappingPairs in order to generate the specified number of bytes.
+ In the event that the entire Mcb could not be described in the bytes available,
+ StoppedOnVcn is also the correct value to specify to resume the building
+ of mapping pairs in a subsequent record.
+
+Arguments:
+
+ Mcb - The Mcb describing new allocation.
+
+ BytesAvailable - Bytes available for storing mapping pairs. This routine
+ is guaranteed to stop before returning a count greater
+ than this.
+
+ LowestVcn - Lowest Vcn field applying to the mapping pairs array
+
+ StopOnVcn - If specified, calculating size at the first run starting with a Vcn
+ beyond the specified Vcn
+
+ StoppedOnVcn - Returns the Vcn on which a stop was necessary, or xxMax if
+ the entire Mcb could be stored. This Vcn should be
+ subsequently supplied to NtfsBuildMappingPairs to generate
+ the calculated number of bytes.
+
+Return Value:
+
+ Size required required for entire new array in bytes.
+
+--*/
+
+{
+ VCN NextVcn, CurrentVcn;
+ LCN CurrentLcn;
+ VCN RunVcn;
+ LCN RunLcn;
+ BOOLEAN Found;
+ LONGLONG RunCount;
+ VCN HighestVcn;
+ PVOID RangePtr;
+ ULONG RunIndex;
+ ULONG MSize = 0;
+ ULONG LastSize = 0;
+
+ PAGED_CODE();
+
+ HighestVcn = MAXLONGLONG;
+
+ //
+ // Initialize CurrentLcn as it will be initialized for decode.
+ //
+
+ CurrentLcn = 0;
+ NextVcn = RunVcn = LowestVcn;
+
+ Found = NtfsLookupNtfsMcbEntry( Mcb, RunVcn, &RunLcn, &RunCount, NULL, NULL, &RangePtr, &RunIndex );
+
+ //
+ // Loop through the Mcb to calculate the size of the mapping array.
+ //
+
+ while (TRUE) {
+
+ LONGLONG Change;
+ PCHAR cp;
+
+ //
+ // See if there is another entry in the Mcb.
+ //
+
+ if (!Found) {
+
+ //
+ // If the caller did not specify StopOnVcn, then break out.
+ //
+
+ if (!ARGUMENT_PRESENT(StopOnVcn)) {
+ break;
+ }
+
+ //
+ // Otherwise, describe the "hole" up to and including the
+ // Vcn we are stopping on.
+ //
+
+ RunVcn = NextVcn;
+ RunLcn = UNUSED_LCN;
+ RunCount = (*StopOnVcn - RunVcn) + 1;
+ RunIndex = MAXULONG - 1;
+ }
+
+ //
+ // If we were asked to stop after a certain Vcn, do it here.
+ //
+
+ if (ARGUMENT_PRESENT(StopOnVcn)) {
+
+ //
+ // If the next Vcn is beyond the one we are to stop on, then
+ // set HighestVcn, if not already set below, and get out.
+ //
+
+ if (RunVcn > *StopOnVcn) {
+ if (*StopOnVcn == MAXLONGLONG) {
+ HighestVcn = RunVcn;
+ }
+ break;
+ }
+
+ //
+ // If this run extends beyond the current end of this attribute
+ // record, then we still need to stop where we are supposed to
+ // after outputting this run.
+ //
+
+ if ((RunVcn + RunCount) > *StopOnVcn) {
+ HighestVcn = *StopOnVcn + 1;
+ }
+ }
+
+ //
+ // Advance the RunIndex for the next call.
+ //
+
+ RunIndex += 1;
+
+ //
+ // Add in one for the count byte.
+ //
+
+ MSize += 1;
+
+ //
+ // NextVcn becomes current Vcn and we calculate the new NextVcn.
+ //
+
+ CurrentVcn = RunVcn;
+ NextVcn = RunVcn + RunCount;
+
+ //
+ // Calculate the Vcn change to store.
+ //
+
+ Change = NextVcn - CurrentVcn;
+
+ //
+ // Now calculate the first byte to actually output
+ //
+
+ if (Change < 0) {
+
+ GetNegativeByte( (PLARGE_INTEGER)&Change, &cp );
+
+ } else {
+
+ GetPositiveByte( (PLARGE_INTEGER)&Change, &cp );
+ }
+
+ //
+ // Now add in the number of Vcn change bytes.
+ //
+
+ MSize += cp - (PCHAR)&Change + 1;
+
+ //
+ // Do not output any Lcn bytes if it is the unused Lcn.
+ //
+
+ if (RunLcn != UNUSED_LCN) {
+
+ //
+ // Calculate the Lcn change to store.
+ //
+
+ Change = RunLcn - CurrentLcn;
+
+ //
+ // Now calculate the first byte to actually output
+ //
+
+ if (Change < 0) {
+
+ GetNegativeByte( (PLARGE_INTEGER)&Change, &cp );
+
+ } else {
+
+ GetPositiveByte( (PLARGE_INTEGER)&Change, &cp );
+ }
+
+ //
+ // Now add in the number of Lcn change bytes.
+ //
+
+ MSize += cp - (PCHAR)&Change + 1;
+
+ CurrentLcn = RunLcn;
+ }
+
+ //
+ // Now see if we can still store the required number of bytes,
+ // and get out if not.
+ //
+
+ if ((MSize + 1) > BytesAvailable) {
+
+ HighestVcn = RunVcn;
+ MSize = LastSize;
+ break;
+ }
+
+ //
+ // Now advance some locals before looping back.
+ //
+
+ LastSize = MSize;
+
+ Found = NtfsGetSequentialMcbEntry( Mcb, &RangePtr, RunIndex, &RunVcn, &RunLcn, &RunCount );
+ }
+
+ //
+ // The caller had sufficient bytes available to store at least on
+ // run, or that we were able to process the entire (empty) Mcb.
+ //
+
+ ASSERT( (MSize != 0) || (HighestVcn == MAXLONGLONG) );
+
+ //
+ // Return the Vcn we stopped on (or xxMax) and the size caculated,
+ // adding one for the terminating 0.
+ //
+
+ *StoppedOnVcn = HighestVcn;
+
+ return MSize + 1;
+}
+
+
+VOID
+NtfsBuildMappingPairs (
+ IN PNTFS_MCB Mcb,
+ IN VCN LowestVcn,
+ IN OUT PVCN HighestVcn,
+ OUT PCHAR MappingPairs
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds a new mapping pairs array or adds to an old one.
+
+ At this time, this routine only supports adding to the end of the
+ Mapping Pairs Array.
+
+Arguments:
+
+ Mcb - The Mcb describing new allocation.
+
+ LowestVcn - Lowest Vcn field applying to the mapping pairs array
+
+ HighestVcn - On input supplies the highest Vcn, after which we are to stop.
+ On output, returns the actual Highest Vcn represented in the
+ MappingPairs array, or LlNeg1 if the array is empty.
+
+ MappingPairs - Points to the current mapping pairs array to be extended.
+ To build a new array, the byte pointed to must contain 0.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ VCN NextVcn, CurrentVcn;
+ LCN CurrentLcn;
+ VCN RunVcn;
+ LCN RunLcn;
+ BOOLEAN Found;
+ LONGLONG RunCount;
+ PVOID RangePtr;
+ ULONG RunIndex;
+
+ PAGED_CODE();
+
+ //
+ // Initialize NextVcn and CurrentLcn as they will be initialized for decode.
+ //
+
+ CurrentLcn = 0;
+ NextVcn = RunVcn = LowestVcn;
+
+ Found = NtfsLookupNtfsMcbEntry( Mcb, RunVcn, &RunLcn, &RunCount, NULL, NULL, &RangePtr, &RunIndex );
+
+ //
+ // Loop through the Mcb to calculate the size of the mapping array.
+ //
+
+ while (TRUE) {
+
+ LONGLONG ChangeV, ChangeL;
+ PCHAR cp;
+ ULONG SizeV;
+ ULONG SizeL;
+
+ //
+ // See if there is another entry in the Mcb.
+ //
+
+ if (!Found) {
+
+ //
+ // Break out in the normal case
+ //
+
+ if (*HighestVcn == MAXLONGLONG) {
+ break;
+ }
+
+ //
+ // Otherwise, describe the "hole" up to and including the
+ // Vcn we are stopping on.
+ //
+
+ RunVcn = NextVcn;
+ RunLcn = UNUSED_LCN;
+ RunCount = *HighestVcn - NextVcn;
+ RunIndex = MAXULONG - 1;
+ }
+
+ //
+ // Advance the RunIndex for the next call.
+ //
+
+ RunIndex += 1;
+
+ //
+ // Exit loop if we hit the HighestVcn we are looking for.
+ //
+
+ if (RunVcn >= *HighestVcn) {
+ break;
+ }
+
+ //
+ // This run may go beyond the highest we are looking for, if so
+ // we need to shrink the count.
+ //
+
+ if ((RunVcn + RunCount) > *HighestVcn) {
+ RunCount = *HighestVcn - RunVcn;
+ }
+
+ //
+ // NextVcn becomes current Vcn and we calculate the new NextVcn.
+ //
+
+ CurrentVcn = RunVcn;
+ NextVcn = RunVcn + RunCount;
+
+ //
+ // Calculate the Vcn change to store.
+ //
+
+ ChangeV = NextVcn - CurrentVcn;
+
+ //
+ // Now calculate the first byte to actually output
+ //
+
+ if (ChangeV < 0) {
+
+ GetNegativeByte( (PLARGE_INTEGER)&ChangeV, &cp );
+
+ } else {
+
+ GetPositiveByte( (PLARGE_INTEGER)&ChangeV, &cp );
+ }
+
+ //
+ // Now add in the number of Vcn change bytes.
+ //
+
+ SizeV = cp - (PCHAR)&ChangeV + 1;
+
+ //
+ // Do not output any Lcn bytes if it is the unused Lcn.
+ //
+
+ SizeL = 0;
+ if (RunLcn != UNUSED_LCN) {
+
+ //
+ // Calculate the Lcn change to store.
+ //
+
+ ChangeL = RunLcn - CurrentLcn;
+
+ //
+ // Now calculate the first byte to actually output
+ //
+
+ if (ChangeL < 0) {
+
+ GetNegativeByte( (PLARGE_INTEGER)&ChangeL, &cp );
+
+ } else {
+
+ GetPositiveByte( (PLARGE_INTEGER)&ChangeL, &cp );
+ }
+
+ //
+ // Now add in the number of Lcn change bytes.
+ //
+
+ SizeL = (cp - (PCHAR)&ChangeL) + 1;
+
+ //
+ // Now advance CurrentLcn before looping back.
+ //
+
+ CurrentLcn = RunLcn;
+ }
+
+ //
+ // Now we can produce our outputs to the MappingPairs array.
+ //
+
+ *MappingPairs++ = (CHAR)(SizeV + (SizeL * 16));
+
+ while (SizeV != 0) {
+ *MappingPairs++ = (CHAR)(((ULONG)ChangeV) & 0xFF);
+ ChangeV = ChangeV >> 8;
+ SizeV -= 1;
+ }
+
+ while (SizeL != 0) {
+ *MappingPairs++ = (CHAR)(((ULONG)ChangeL) & 0xFF);
+ ChangeL = ChangeL >> 8;
+ SizeL -= 1;
+ }
+
+ Found = NtfsGetSequentialMcbEntry( Mcb, &RangePtr, RunIndex, &RunVcn, &RunLcn, &RunCount );
+ }
+
+ //
+ // Terminate the size with a 0 byte.
+ //
+
+ *MappingPairs = 0;
+
+ //
+ // Also return the actual highest Vcn.
+ //
+
+ *HighestVcn = NextVcn - 1;
+
+ return;
+}
+
+VCN
+NtfsGetHighestVcn (
+ IN PIRP_CONTEXT IrpContext,
+ IN VCN LowestVcn,
+ IN PCHAR MappingPairs
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the highest Vcn from a mapping pairs array. This
+ routine is intended for restart, in order to update the HighestVcn field
+ and possibly AllocatedLength in an attribute record after updating the
+ MappingPairs array.
+
+Arguments:
+
+ LowestVcn - Lowest Vcn field applying to the mapping pairs array
+
+ MappingPairs - Points to the mapping pairs array from which the highest
+ Vcn is to be extracted.
+
+Return Value:
+
+ The Highest Vcn represented by the MappingPairs array.
+
+--*/
+
+{
+ VCN CurrentVcn, NextVcn;
+ ULONG VcnBytes, LcnBytes;
+ LONGLONG Change;
+ PCHAR ch = MappingPairs;
+
+ PAGED_CODE();
+
+ //
+ // Implement the decompression algorithm, as defined in ntfs.h.
+ //
+
+ NextVcn = LowestVcn;
+ ch = MappingPairs;
+
+ //
+ // Loop to process mapping pairs.
+ //
+
+ while (!IsCharZero(*ch)) {
+
+ //
+ // Set Current Vcn from initial value or last pass through loop.
+ //
+
+ CurrentVcn = NextVcn;
+
+ //
+ // Extract the counts from the two nibbles of this byte.
+ //
+
+ VcnBytes = *ch & 0xF;
+ LcnBytes = *ch++ >> 4;
+
+ //
+ // Extract the Vcn change (use of RtlCopyMemory works for little-Endian)
+ // and update NextVcn.
+ //
+
+ Change = 0;
+
+ if (IsCharLtrZero(*(ch + VcnBytes - 1))) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL );
+ }
+ RtlCopyMemory( &Change, ch, VcnBytes );
+ NextVcn = NextVcn + Change;
+
+ //
+ // Just skip over Lcn.
+ //
+
+ ch += VcnBytes + LcnBytes;
+ }
+
+ Change = NextVcn - 1;
+ return *(PVCN)&Change;
+}
+
+
+BOOLEAN
+NtfsReserveClusters (
+ IN PIRP_CONTEXT IrpContext OPTIONAL,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reserves all clusters that would be required to write
+ the full range of compression units covered by the described range
+ of Vcns. All clusters in the range are reserved, without regard to
+ how many clusters are already reserved in that range. Not paying
+ attention to how many clusters are already allocated in that range
+ is not only a simplification, but it is also necessary, since we
+ sometimes deallocate all existing clusters anyway, and make them
+ ineligible for reallocation in the same transaction. Thus in the
+ worst case you do always need an additional 16 clusters when a
+ compression unit is first modified. Note that although we could
+ specifically reserve (double-reserve, in fact) the entire allocation
+ size of the stream, when reserving from the volume, we never reserve
+ more than AllocationSize + MM_MAXIMUM_DISK_IO_SIZE - size actually
+ allocated, since the worst we could ever need to doubly allocate is
+ limited by the maximum flush size.
+
+ For user-mapped streams, we have no way of keeping track of dirty
+ pages, so we effectivel always reserve AllocationSize +
+ MM_MAXIMUM_DISK_IO_SIZE.
+
+ This routine is called from FastIo, and therefore has no IrpContext.
+
+Arguments:
+
+ IrpContext - If IrpContext is not specified, then not all data is
+ available to determine if the clusters can be reserved,
+ and FALSE may be returned unnecessarily. This case
+ is intended for the fast I/O path, which will just
+ force us to take the long path to write.
+
+ Scb - Address of a compressed stream for which we are reserving space
+
+ FileOffset - Starting byte being modified by caller
+
+ ByteCount - Number of bytes being modified by caller
+
+Return Value:
+
+ FALSE if not all clusters could be reserved
+ TRUE if all clusters were reserved
+
+--*/
+
+{
+ ULONG FirstBit, LastBit;
+ PRTL_BITMAP NewBitMap;
+ LONGLONG SizeOfNewBitMap;
+ ULONG CompressionShift;
+ PVCB Vcb = Scb->Vcb;
+ ULONG SizeTemp = 0;
+ LONGLONG TempL;
+
+ ASSERT(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA);
+
+ //
+ // Nothing to do if byte count is zero.
+ //
+
+ if (ByteCount == 0) { return TRUE; }
+
+ //
+ // Calculate first and last bits to reserve.
+ //
+
+ CompressionShift = Vcb->ClusterShift + (ULONG)Scb->CompressionUnitShift;
+ FirstBit = (ULONG)Int64ShraMod32(FileOffset, (CompressionShift));
+ LastBit = (ULONG)Int64ShraMod32((FileOffset + (LONGLONG)ByteCount - 1), (CompressionShift));
+
+ //
+ // Make sure we started with numbers in range.
+ //
+
+ ASSERT( ((LONGLONG)(FirstBit + 1) << CompressionShift) > FileOffset );
+ ASSERT( LastBit >= FirstBit );
+
+ ExAcquireResourceExclusive( Vcb->BitmapScb->Header.Resource, TRUE );
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ //
+ // See if we have to allocate a new or bigger bitmap.
+ //
+
+ if ((Scb->ScbType.Data.ReservedBitMap == NULL) ||
+ ((SizeTemp = Scb->ScbType.Data.ReservedBitMap->SizeOfBitMap) <= LastBit)) {
+
+ //
+ // Round the size we need to the nearest quad word since we will
+ // use that much anyway, and want to reduce the number of times
+ // we grow the bitmap. Convert old size to bytes.
+ //
+
+ SizeOfNewBitMap = FileOffset + (LONGLONG)ByteCount;
+ if (SizeOfNewBitMap < Scb->Header.AllocationSize.QuadPart) {
+ SizeOfNewBitMap = Scb->Header.AllocationSize.QuadPart;
+ }
+ SizeOfNewBitMap = (ULONG)((Int64ShraMod32(SizeOfNewBitMap, CompressionShift) + 64) & ~63) / 8;
+ SizeTemp /= 8;
+
+ //
+ // Allocate and initialize the new bitmap.
+ //
+
+ NewBitMap = ExAllocatePool( PagedPool, (ULONG)SizeOfNewBitMap + sizeof(RTL_BITMAP) );
+
+ //
+ // Check for alloacation error
+ //
+
+ if (NewBitMap == NULL) {
+
+ NtfsReleaseReservedClusters( Vcb );
+ ExReleaseResource( Vcb->BitmapScb->Header.Resource );
+
+ //
+ // If we have an Irp Context then we can raise insufficient resources. Otherwise
+ // return FALSE.
+ //
+
+ if (ARGUMENT_PRESENT( IrpContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+
+ } else {
+
+ return FALSE;
+ }
+ }
+
+ RtlInitializeBitMap( NewBitMap, Add2Ptr(NewBitMap, sizeof(RTL_BITMAP)), (ULONG)SizeOfNewBitMap * 8 );
+
+ //
+ // Copy the old bitmap over and delete it. Zero the new part.
+ //
+
+ if (SizeTemp != 0) {
+
+ RtlCopyMemory( Add2Ptr(NewBitMap, sizeof(RTL_BITMAP)),
+ Add2Ptr(Scb->ScbType.Data.ReservedBitMap, sizeof(RTL_BITMAP)),
+ SizeTemp );
+ NtfsFreePool( Scb->ScbType.Data.ReservedBitMap );
+ }
+
+ RtlZeroMemory( Add2Ptr(NewBitMap, sizeof(RTL_BITMAP) + SizeTemp),
+ (ULONG)SizeOfNewBitMap - SizeTemp );
+ Scb->ScbType.Data.ReservedBitMap = NewBitMap;
+ }
+
+ NewBitMap = Scb->ScbType.Data.ReservedBitMap;
+
+ //
+ // One problem with the reservation strategy, is that we cannot precisely reserve
+ // for metadata. If we reserve too much, we will return premature disk full, if
+ // we reserve too little, the Lazy Writer can get an error. As we add compression
+ // units to a file, large files will eventually require additional File Records.
+ // If each compression unit required 0x20 bytes of run information (fairly pessimistic)
+ // then a 0x400 size file record would fill up with less than 0x20 runs requiring
+ // (worst case) two additional clusters for another file record. So each 0x20
+ // compression units require 0x200 reserved clusters, and a separate 2 cluster
+ // file record. 0x200/2 = 0x100. So the calculations below tack a 1/0x100 (about
+ // .4% "surcharge" on the amount reserved both in the Scb and the Vcb, to solve
+ // the Lazy Writer popups like the ones Alan Morris gets in the print lab.
+ //
+
+ //
+ // Figure out the worst case reservation required for this Scb, in bytes.
+ //
+
+ TempL = Scb->Header.AllocationSize.QuadPart +
+ MM_MAXIMUM_DISK_IO_SIZE + Scb->CompressionUnit -
+ (FlagOn( Scb->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) ?
+ Scb->Header.AllocationSize.QuadPart :
+ Scb->TotalAllocated) +
+ (Scb->ScbType.Data.TotalReserved / 0x100);
+
+ //
+ // Now loop to reserve the space, a compression unit at a time.
+ // We use the security fast mutex as a convenient end resource.
+ //
+
+ while (FirstBit <= LastBit) {
+
+ //
+ // If this compression unit is not already reserved, do it now.
+ //
+
+ if (!RtlCheckBit( NewBitMap, FirstBit )) {
+
+ //
+ // If there is not sufficient space on the volume, then
+ // we must see if this Scb is totally reserved anyway.
+ //
+
+ if (((Vcb->TotalReserved + (Vcb->TotalReserved / 0x100) +
+ (1 << Scb->CompressionUnitShift)) >= Vcb->FreeClusters) &&
+ (Scb->ScbType.Data.TotalReserved < TempL) &&
+ (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN))) {
+
+ NtfsReleaseReservedClusters( Vcb );
+ ExReleaseResource( Vcb->BitmapScb->Header.Resource );
+ return FALSE;
+ }
+
+ //
+ // Reserve this compression unit.
+ //
+
+ SetFlag( NewBitMap->Buffer[FirstBit / 32], 1 << (FirstBit % 32) );
+
+ //
+ // Increased TotalReserved bytes in the Scb.
+ //
+
+ Scb->ScbType.Data.TotalReserved += Scb->CompressionUnit;
+ ASSERT( Scb->CompressionUnit != 0 );
+ ASSERT( Scb->CompressionUnitShift != 0 );
+
+ //
+ // Increase total reserved clusters in the Vcb, if the user has
+ // write access. (Otherwise this must be a call from a read
+ // to a usermapped section.)
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
+ Vcb->TotalReserved += 1 << Scb->CompressionUnitShift;
+ }
+ }
+ FirstBit += 1;
+ }
+
+ NtfsReleaseReservedClusters( Vcb );
+ ExReleaseResource( Vcb->BitmapScb->Header.Resource );
+
+ return TRUE;
+}
+
+
+
+VOID
+NtfsFreeReservedClusters (
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees any previously reserved clusters in the specified range.
+
+Arguments:
+
+ Scb - Address of a compressed stream for which we are freeing reserved space
+
+ FileOffset - Starting byte being freed
+
+ ByteCount - Number of bytes being freed by caller, or 0 if to end of file
+
+Return Value:
+
+ None (all errors simply raise)
+
+--*/
+
+{
+ ULONG FirstBit, LastBit;
+ PRTL_BITMAP BitMap;
+ ULONG CompressionShift;
+ PVCB Vcb = Scb->Vcb;
+
+ ASSERT(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA);
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ //
+ // If there is no bitmap, we can get out.
+ //
+
+ CompressionShift = Vcb->ClusterShift + (ULONG)Scb->CompressionUnitShift;
+ BitMap = Scb->ScbType.Data.ReservedBitMap;
+ if (BitMap == NULL) {
+ NtfsReleaseReservedClusters( Vcb );
+ return;
+ }
+
+ //
+ // Calculate first bit to free, and initialize LastBit
+ //
+
+ FirstBit = (ULONG)Int64ShraMod32(FileOffset, (CompressionShift));
+ LastBit = MAXULONG;
+
+ //
+ // If ByteCount was specified, then calculate LastBit.
+ //
+
+ if (ByteCount != 0) {
+ LastBit = (ULONG)Int64ShraMod32((FileOffset + (LONGLONG)ByteCount - 1), (CompressionShift));
+ }
+
+ //
+ // Make sure we started with numbers in range.
+ //
+
+ ASSERT( ((LONGLONG)(FirstBit + 1) << CompressionShift) > FileOffset );
+ ASSERT( LastBit >= FirstBit );
+
+ //
+ // Under no circumstances should we go off the end!
+ //
+
+ if (LastBit >= Scb->ScbType.Data.ReservedBitMap->SizeOfBitMap) {
+ LastBit = Scb->ScbType.Data.ReservedBitMap->SizeOfBitMap - 1;
+ }
+
+ //
+ // Now loop to free the space, a compression unit at a time.
+ // We use the security fast mutex as a convenient end resource.
+ //
+
+ while (FirstBit <= LastBit) {
+
+ //
+ // If this compression unit is reserved, then free it.
+ //
+
+ if (RtlCheckBit( BitMap, FirstBit )) {
+
+ //
+ // Free this compression unit.
+ //
+
+ ClearFlag( BitMap->Buffer[FirstBit / 32], 1 << (FirstBit % 32) );
+
+ //
+ // Decrease TotalReserved bytes in the Scb.
+ //
+
+ ASSERT(Scb->ScbType.Data.TotalReserved >= Scb->CompressionUnit);
+ Scb->ScbType.Data.TotalReserved -= Scb->CompressionUnit;
+ ASSERT( Scb->CompressionUnit != 0 );
+ ASSERT( Scb->CompressionUnitShift != 0 );
+
+ //
+ // Decrease total reserved clusters in the Vcb, if we are counting
+ // against the Vcb.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
+ ASSERT(Vcb->TotalReserved >= (1 << Scb->CompressionUnitShift));
+ Vcb->TotalReserved -= 1 << Scb->CompressionUnitShift;
+ }
+ }
+ FirstBit += 1;
+ }
+
+ NtfsReleaseReservedClusters( Vcb );
+}
+
+
+VOID
+NtfsFreeFinalReservedClusters (
+ IN PVCB Vcb,
+ IN LONGLONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees any previously reserved clusters in the specified range.
+
+Arguments:
+
+ Vcb - Volume to which clusters are to be freed
+
+ ClusterCount - Number of clusters being freed by caller
+
+Return Value:
+
+ None (all errors simply raise)
+
+--*/
+
+{
+ //
+ // Use the security fast mutex as a convenient end resource.
+ //
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ ASSERT(Vcb->TotalReserved >= ClusterCount);
+ Vcb->TotalReserved -= ClusterCount;
+
+ NtfsReleaseReservedClusters( Vcb );
+}
+
+
+#ifdef SYSCACHE
+
+BOOLEAN
+FsRtlIsSyscacheFile (
+ IN PFILE_OBJECT FileObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns to the caller whether or not the specified
+ file object is a file for the Syscache stress test. It is considered
+ a syscache file, if the last component of the file name in the FileObject
+ matches "cac*.tmp", case insensitive.
+
+Arguments:
+
+ FileObject - supplies the FileObject to be tested (it must not be
+ cleaned up yet).
+
+Return Value:
+
+ FALSE - if the file is not a Syscache file.
+ TRUE - if the file is a Syscache file.
+
+--*/
+
+{
+ if ((FileObject != NULL) && (FileObject->FileName.Length >= 8*2)) {
+
+ ULONG iM = 0;
+ ULONG iF;
+ PWSTR MakName = L"cac*.tmp";
+
+ iF = FileObject->FileName.Length / 2;
+ while ((iF != 0) && (FileObject->FileName.Buffer[iF - 1] != '\\')) {
+ iF--;
+ }
+
+ while (TRUE) {
+
+ if ((iM == 8) && ((LONG)iF == FileObject->FileName.Length / 2)) {
+
+ return TRUE;
+
+ } else if (MakName[iM] == '*') {
+ if (FileObject->FileName.Buffer[iF] == '.') {
+ iM++; iM++; iF++;
+ } else {
+ iF++;
+ if ((LONG)iF == FileObject->FileName.Length / 2) {
+ break;
+ }
+ }
+ } else if (MakName[iM] == (WCHAR)(FileObject->FileName.Buffer[iF] | ('a' - 'A'))) {
+ iM++; iF++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+
+VOID
+FsRtlVerifySyscacheData (
+ IN PFILE_OBJECT FileObject,
+ IN PVOID Buffer,
+ IN ULONG Length,
+ IN ULONG Offset
+ )
+
+/*
+
+Routine Description:
+
+ This routine scans a buffer to see if it is valid data for a syscache
+ file, and stops if it sees bad data.
+
+ HINT TO CALLERS: Make sure (Offset + Length) <= FileSize!
+
+Arguments:
+
+ Buffer - Pointer to the buffer to be checked
+
+ Length - Length of the buffer to be checked in bytes
+
+ Offset - File offset at which this data starts (syscache files are currently
+ limited to 24 bits of file offset).
+
+Return Value:
+
+ None (stops on error)
+
+--*/
+
+{
+ PULONG BufferEnd;
+
+ BufferEnd = (PULONG)((PCHAR)Buffer + (Length & ~3));
+
+ while ((PULONG)Buffer < BufferEnd) {
+
+ if ((*(PULONG)Buffer != 0) && (((*(PULONG)Buffer & 0xFFFFFF) ^ Offset) != 0xFFFFFF) &&
+ ((Offset & 0x1FF) != 0)) {
+
+ DbgPrint("Bad Data, FileObject = %08lx, Offset = %08lx, Buffer = %08lx\n",
+ FileObject, Offset, (PULONG)Buffer );
+ DbgBreakPoint();
+ }
+ Offset += 4;
+ Buffer = (PVOID)((PULONG)Buffer + 1);
+ }
+}
+
+
+#endif
diff --git a/private/ntos/cntfs/attrdata.c b/private/ntos/cntfs/attrdata.c
new file mode 100644
index 000000000..a81b44926
--- /dev/null
+++ b/private/ntos/cntfs/attrdata.c
@@ -0,0 +1,165 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ AttrData.c
+
+Abstract:
+
+ This module contains an initial image of the Attribute Definition File.
+
+Author:
+
+ Tom Miller [TomM] 7-Jun-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Define an array to hold the initial attribute definitions. This is
+// essentially the initial contents of the Attribute Definition File.
+// NTFS may find it convenient to use this module for attribute
+// definitions prior to getting an NTFS volume mounted, however it is valid
+// for NTFS to assume knowledge of the system-defined attributes without
+// consulting this table.
+//
+
+ATTRIBUTE_DEFINITION_COLUMNS NtfsAttributeDefinitions[ ] =
+
+{
+ {{'$','S','T','A','N','D','A','R','D','_','I','N','F','O','R','M','A','T','I','O','N'},
+ $STANDARD_INFORMATION, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ SIZEOF_OLD_STANDARD_INFORMATION, // Minimum length
+ sizeof(STANDARD_INFORMATION)}, // Maximum length
+
+ {{'$','A','T','T','R','I','B','U','T','E','_','L','I','S','T'},
+ $ATTRIBUTE_LIST, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_LOG_NONRESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','F','I','L','E','_','N','A','M','E'},
+ $FILE_NAME, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT | ATTRIBUTE_DEF_INDEXABLE, // Flags
+ sizeof(FILE_NAME), // Minimum length
+ sizeof(FILE_NAME) + (255 * sizeof(WCHAR))}, // Maximum length
+
+ {{'$','O','B','J','E','C','T','_','I','D'},
+ $OBJECT_ID, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ 0, // Minimum length
+ 256}, // Maximum length
+
+ {{'$','S','E','C','U','R','I','T','Y','_','D','E','S','C','R','I','P','T','O','R'},
+ $SECURITY_DESCRIPTOR, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_LOG_NONRESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','V','O','L','U','M','E','_','N','A','M','E'},
+ $VOLUME_NAME, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ 2, // Minimum length
+ 256}, // Maximum length
+
+ {{'$','V','O','L','U','M','E','_','I','N','F','O','R','M','A','T','I','O','N'},
+ $VOLUME_INFORMATION, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ sizeof(VOLUME_INFORMATION), // Minimum length
+ sizeof(VOLUME_INFORMATION)}, // Maximum length
+
+ {{'$','D','A','T','A'},
+ $DATA, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ 0, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','I','N','D','E','X','_','R','O','O','T'},
+ $INDEX_ROOT, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','I','N','D','E','X','_','A','L','L','O','C','A','T','I','O','N'},
+ $INDEX_ALLOCATION, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_LOG_NONRESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','B','I','T','M','A','P'},
+ $BITMAP, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_LOG_NONRESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','S','Y','M','B','O','L','I','C','_','L','I','N','K'},
+ $SYMBOLIC_LINK, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_LOG_NONRESIDENT, // Flags
+ 0, // Minimum length
+ -1}, // Maximum length
+
+ {{'$','E','A','_','I','N','F','O','R','M','A','T','I','O','N'},
+ $EA_INFORMATION, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT, // Flags
+ sizeof(EA_INFORMATION), // Minimum length
+ sizeof(EA_INFORMATION)}, // Maximum length
+
+ {{'$','E','A',},
+ $EA, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ 0, // Flags
+ 0, // Minimum length
+ 0x10000}, // Maximum length
+
+#ifdef _CAIRO_
+ {{'$','P','R','O','P','E','R','T','Y','_','S','E','T'},
+ $PROPERTY_SET, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ 0, // Flags
+ 0, // Minimum length
+ 256*1024}, // Maximum length
+#endif // _CAIRO_
+
+ {{0, 0, 0, 0},
+ $UNUSED, // Attribute code
+ 0, // Display rule
+ 0, // Collation rule
+ 0, // Flags
+ 0, // Minimum length
+ 0}, // Maximum length
+};
+
diff --git a/private/ntos/cntfs/attrsup.c b/private/ntos/cntfs/attrsup.c
new file mode 100644
index 000000000..0fe1be6d6
--- /dev/null
+++ b/private/ntos/cntfs/attrsup.c
@@ -0,0 +1,13605 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ AttrSup.c
+
+Abstract:
+
+ This module implements the attribute management routines for Ntfs
+
+Author:
+
+ David Goebel [DavidGoe] 25-June-1991
+ Tom Miller [TomM] 9-November-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_ATTRSUP)
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_ATTRSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('AFtN')
+
+//
+//
+// Internal support routines
+//
+
+BOOLEAN
+NtfsFindInFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ OUT PATTRIBUTE_RECORD_HEADER *ReturnAttribute,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength
+ );
+
+//
+// Internal support routines for managing file record space
+//
+
+VOID
+NtfsCreateNonresidentWithValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN WriteClusters,
+ IN PSCB ThisScb OPTIONAL,
+ IN BOOLEAN LogIt,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+BOOLEAN
+NtfsGetSpaceForAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG Length,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+MakeRoomForAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG SizeNeeded,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+FindLargestAttributes (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG Number,
+ OUT PATTRIBUTE_RECORD_HEADER *AttributeArray
+ );
+
+LONGLONG
+MoveAttributeToOwnRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ OUT PBCB *NewBcb OPTIONAL,
+ OUT PFILE_RECORD_SEGMENT_HEADER *NewFileRecord OPTIONAL
+ );
+
+VOID
+SplitFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG SizeNeeded,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+PFILE_RECORD_SEGMENT_HEADER
+NtfsCloneFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN MftData,
+ OUT PBCB *Bcb,
+ OUT PMFT_SEGMENT_REFERENCE FileReference
+ );
+
+ULONG
+GetSizeForAttributeList (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord
+ );
+
+VOID
+CreateAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord1,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord2 OPTIONAL,
+ IN MFT_SEGMENT_REFERENCE SegmentReference2,
+ IN PATTRIBUTE_RECORD_HEADER OldPosition OPTIONAL,
+ IN ULONG SizeOfList,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext
+ );
+
+VOID
+UpdateAttributeListEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PMFT_SEGMENT_REFERENCE OldFileReference,
+ IN USHORT OldInstance,
+ IN PMFT_SEGMENT_REFERENCE NewFileReference,
+ IN USHORT NewInstance,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext
+ );
+
+VOID
+NtfsAddNameToParent (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN BOOLEAN IgnoreCase,
+ IN PBOOLEAN LogIt,
+ IN PFILE_NAME FileNameAttr,
+ OUT PUCHAR FileNameFlags,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ IN PNAME_PAIR NamePair OPTIONAL
+ );
+
+VOID
+NtfsAddDosOnlyName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN UNICODE_STRING FileName,
+ IN BOOLEAN LogIt,
+ IN PUNICODE_STRING SuggestedDosName OPTIONAL
+ );
+
+BOOLEAN
+NtfsAddTunneledNtfsOnlyName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN PUNICODE_STRING FileName,
+ IN PBOOLEAN LogIt
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, CreateAttributeList)
+#pragma alloc_text(PAGE, FindLargestAttributes)
+#pragma alloc_text(PAGE, GetSizeForAttributeList)
+#pragma alloc_text(PAGE, MakeRoomForAttribute)
+#pragma alloc_text(PAGE, MoveAttributeToOwnRecord)
+#pragma alloc_text(PAGE, NtfsAddAttributeAllocation)
+#pragma alloc_text(PAGE, NtfsAddDosOnlyName)
+#pragma alloc_text(PAGE, NtfsAddLink)
+#pragma alloc_text(PAGE, NtfsAddNameToParent)
+#pragma alloc_text(PAGE, NtfsAddToAttributeList)
+#pragma alloc_text(PAGE, NtfsAddTunneledNtfsOnlyName)
+#pragma alloc_text(PAGE, NtfsChangeAttributeSize)
+#pragma alloc_text(PAGE, NtfsChangeAttributeValue)
+#pragma alloc_text(PAGE, NtfsCleanupAttributeContext)
+#pragma alloc_text(PAGE, NtfsCloneFileRecord)
+#pragma alloc_text(PAGE, NtfsConvertToNonresident)
+#pragma alloc_text(PAGE, NtfsCreateAttributeWithAllocation)
+#pragma alloc_text(PAGE, NtfsCreateAttributeWithValue)
+#pragma alloc_text(PAGE, NtfsCreateNonresidentWithValue)
+#pragma alloc_text(PAGE, NtfsDeleteAllocationFromRecord)
+#pragma alloc_text(PAGE, NtfsDeleteAttributeAllocation)
+#pragma alloc_text(PAGE, NtfsDeleteAttributeRecord)
+#pragma alloc_text(PAGE, NtfsDeleteFile)
+#pragma alloc_text(PAGE, NtfsDeleteFromAttributeList)
+#pragma alloc_text(PAGE, NtfsGetAttributeTypeCode)
+#pragma alloc_text(PAGE, NtfsGetSpaceForAttribute)
+#pragma alloc_text(PAGE, NtfsGrowStandardInformation)
+#pragma alloc_text(PAGE, NtfsIsFileDeleteable)
+#pragma alloc_text(PAGE, NtfsLookupEntry)
+#pragma alloc_text(PAGE, NtfsLookupExternalAttribute)
+#pragma alloc_text(PAGE, NtfsLookupInFileRecord)
+#pragma alloc_text(PAGE, NtfsMapAttributeValue)
+#pragma alloc_text(PAGE, NtfsPrepareForUpdateDuplicate)
+#pragma alloc_text(PAGE, NtfsRemoveLink)
+#pragma alloc_text(PAGE, NtfsRemoveLinkViaFlags)
+#pragma alloc_text(PAGE, NtfsRestartChangeAttributeSize)
+#pragma alloc_text(PAGE, NtfsRestartChangeMapping)
+#pragma alloc_text(PAGE, NtfsRestartChangeValue)
+#pragma alloc_text(PAGE, NtfsRestartInsertAttribute)
+#pragma alloc_text(PAGE, NtfsRestartRemoveAttribute)
+#pragma alloc_text(PAGE, NtfsRestartWriteEndOfFileRecord)
+#pragma alloc_text(PAGE, NtfsRewriteMftMapping)
+#pragma alloc_text(PAGE, NtfsSetTotalAllocatedField)
+#pragma alloc_text(PAGE, NtfsUpdateDuplicateInfo)
+#pragma alloc_text(PAGE, NtfsUpdateFcb)
+#pragma alloc_text(PAGE, NtfsUpdateFcbInfoFromDisk)
+#pragma alloc_text(PAGE, NtfsUpdateLcbDuplicateInfo)
+#pragma alloc_text(PAGE, NtfsUpdateScbFromAttribute)
+#pragma alloc_text(PAGE, NtfsUpdateStandardInformation)
+#pragma alloc_text(PAGE, NtfsWriteFileSizes)
+#pragma alloc_text(PAGE, SplitFileRecord)
+#pragma alloc_text(PAGE, UpdateAttributeListEntry)
+#endif
+
+
+ATTRIBUTE_TYPE_CODE
+NtfsGetAttributeTypeCode (
+ IN PVCB Vcb,
+ IN UNICODE_STRING AttributeTypeName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the attribute type code for a given attribute name.
+
+Arguments:
+
+ Vcb - Pointer to the Vcb from which to consult the attribute definitions.
+
+ AttributeTypeName - A string containing the attribute type name to be
+ looked up.
+
+Return Value:
+
+ The attribute type code corresponding to the specified name, or 0 if the
+ attribute type name does not exist.
+
+--*/
+
+{
+ PATTRIBUTE_DEFINITION_COLUMNS AttributeDef = Vcb->AttributeDefinitions;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode = $UNUSED;
+
+ UNICODE_STRING AttributeCodeName;
+
+ PAGED_CODE();
+
+ //
+ // Loop through all of the definitions looking for a name match.
+ //
+
+ while (AttributeDef->AttributeName[0] != 0) {
+
+ RtlInitUnicodeString( &AttributeCodeName, AttributeDef->AttributeName );
+
+ //
+ // The name lengths must match and the characters match exactly.
+ //
+
+ if ((AttributeCodeName.Length == AttributeTypeName.Length)
+ && (RtlEqualMemory( AttributeTypeName.Buffer,
+ AttributeDef->AttributeName,
+ AttributeTypeName.Length ))) {
+
+ AttributeTypeCode = AttributeDef->AttributeTypeCode;
+ break;
+ }
+
+ //
+ // Lets go to the next attribute column.
+ //
+
+ AttributeDef += 1;
+ }
+
+ return AttributeTypeCode;
+}
+
+
+VOID
+NtfsUpdateScbFromAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN PATTRIBUTE_RECORD_HEADER AttrHeader OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine fills in the header of an Scb with the
+ information from the attribute for this Scb.
+
+Arguments:
+
+ Scb - Supplies the SCB to update
+
+ AttrHeader - Optionally provides the attribute to update from
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ BOOLEAN CleanupAttrContext = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateScbFromAttribute: Entered\n") );
+
+ //
+ // If the attribute has been deleted, we can return immediately
+ // claiming that the Scb has been initialized.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+ DebugTrace( -1, Dbg, ("NtfsUpdateScbFromAttribute: Exit\n") );
+
+ return;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If we weren't given the attribute header, we look it up now.
+ //
+
+ if (!ARGUMENT_PRESENT( AttrHeader )) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ CleanupAttrContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ AttrHeader = NtfsFoundAttribute( &AttrContext );
+ }
+
+ //
+ // Check whether this is resident or nonresident
+ //
+
+ if (NtfsIsAttributeResident( AttrHeader )) {
+
+ Scb->Header.AllocationSize.QuadPart = AttrHeader->Form.Resident.ValueLength;
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
+
+ Scb->Header.ValidDataLength =
+ Scb->Header.FileSize = Scb->Header.AllocationSize;
+ SetFlag(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED);
+ }
+
+ Scb->Header.AllocationSize.LowPart =
+ QuadAlign( Scb->Header.AllocationSize.LowPart );
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+
+ //
+ // Set the resident flag in the Scb.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
+
+ } else {
+
+ VCN FileClusters;
+ VCN AllocationClusters;
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
+
+ Scb->Header.ValidDataLength.QuadPart = AttrHeader->Form.Nonresident.ValidDataLength;
+ Scb->Header.FileSize.QuadPart = AttrHeader->Form.Nonresident.FileSize;
+ SetFlag(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED);
+ Scb->ValidDataToDisk = AttrHeader->Form.Nonresident.ValidDataLength;
+ }
+
+ Scb->Header.AllocationSize.QuadPart = AttrHeader->Form.Nonresident.AllocatedLength;
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+
+ if (FlagOn(AttrHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+
+ Scb->TotalAllocated = AttrHeader->Form.Nonresident.TotalAllocated;
+
+ if (Scb->TotalAllocated < 0) {
+
+ Scb->TotalAllocated = 0;
+
+ } else if (Scb->TotalAllocated > Scb->Header.AllocationSize.QuadPart) {
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+ }
+ }
+
+ ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
+
+ //
+ // Get the size of the compression unit.
+ //
+
+ ASSERT((AttrHeader->Form.Nonresident.CompressionUnit == 0) ||
+ (AttrHeader->Form.Nonresident.CompressionUnit == NTFS_CLUSTERS_PER_COMPRESSION));
+
+ Scb->CompressionUnit = 0;
+ Scb->CompressionUnitShift = 0;
+
+ if ((AttrHeader->Form.Nonresident.CompressionUnit != 0) &&
+ (AttrHeader->Form.Nonresident.CompressionUnit < 31)) {
+
+ Scb->CompressionUnit = BytesFromClusters( Scb->Vcb,
+ 1 << AttrHeader->Form.Nonresident.CompressionUnit );
+ Scb->CompressionUnitShift = AttrHeader->Form.Nonresident.CompressionUnit;
+
+ ASSERT( NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
+ }
+
+ //
+ // Compute the clusters for the file and its allocation.
+ //
+
+ AllocationClusters = LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart );
+
+ if (Scb->CompressionUnit == 0) {
+
+ FileClusters = LlClustersFromBytes(Scb->Vcb, Scb->Header.FileSize.QuadPart);
+
+ } else {
+
+ FileClusters = Scb->Header.FileSize.QuadPart + Scb->CompressionUnit - 1;
+ FileClusters &= ~(Scb->CompressionUnit - 1);
+ }
+
+ //
+ // If allocated clusters are greater than file clusters, mark
+ // the Scb to truncate on close.
+ //
+
+ if (AllocationClusters > FileClusters) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+ }
+ }
+
+ //
+ // Update compression information if this is not an index
+ //
+
+ if (Scb->AttributeTypeCode != $INDEX_ALLOCATION) {
+
+ Scb->AttributeFlags = AttrHeader->Flags;
+
+ if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL );
+ }
+
+ if (FlagOn(AttrHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+
+ //
+ // Do not try to infer whether we are writing compressed or not
+ // if we are actively changing the compression state.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE)) {
+ SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+
+ //
+ // If the attribute is resident, then we will use our current
+ // default.
+ //
+
+ if (Scb->CompressionUnit == 0) {
+
+ Scb->CompressionUnit = BytesFromClusters( Scb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION );
+ Scb->CompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
+
+ ASSERT( NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
+ }
+ } else {
+
+ //
+ // Do not try to infer whether we are writing compressed or not
+ // if we are actively changing the compression state.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE)) {
+ ClearFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+
+ //
+ // Make sure compression unit is 0
+ //
+
+ Scb->CompressionUnit = 0;
+ Scb->CompressionUnitShift = 0;
+ }
+ }
+
+ //
+ // If the compression unit is non-zero or this is a resident file
+ // then set the flag in the common header for the Modified page writer.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ if (NodeType( Scb ) == NTFS_NTC_SCB_DATA) {
+
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+
+ } else {
+
+ Scb->Header.IsFastIoPossible = FastIoIsNotPossible;
+ }
+
+ NtfsReleaseFsrtlHeader( Scb );
+
+ //
+ // Set the flag indicating this is the data attribute.
+ //
+
+ if (Scb->AttributeTypeCode == $DATA
+ && Scb->AttributeName.Length == 0) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA );
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_UNNAMED_DATA );
+ }
+
+ SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+
+ if (NtfsIsExclusiveScb(Scb)) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsUpdateScbFromAttribute );
+
+ //
+ // Cleanup the attribute context.
+ //
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateScbFromAttribute: Exit\n") );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateFcbInfoFromDisk (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN LoadSecurity,
+ IN OUT PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+ OUT POLD_SCB_SNAPSHOT UnnamedDataSizes OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update an Fcb from the on-disk attributes
+ for a file. We read the standard information and ea information.
+ The first one must be present, we raise if not. The other does not
+ have to exist. If this is not a directory, then we also need the
+ size of the unnamed data attribute.
+
+Arguments:
+
+ LoadSecurity - Indicates if we should load the security for this file
+ if not already present.
+
+ Fcb - This is the Fcb to update.
+
+ ParentFcb - Optional pointer to parent of this Fcb.
+
+ UnnamedDataSizes - If specified, then we store the details of the unnamed
+ data attribute as we encounter it.
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PATTRIBUTE_RECORD_HEADER AttributeHeader;
+ BOOLEAN FoundEntry;
+ BOOLEAN CorruptDisk = FALSE;
+
+ PBCB Bcb = NULL;
+
+ PDUPLICATED_INFORMATION Info;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateFcbInfoFromDisk: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Look for standard information. This routine assumes it must be
+ // the first attribute.
+ //
+
+ if (FoundEntry = NtfsLookupAttribute( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ &AttrContext )) {
+
+ //
+ // Verify that we found the standard information attribute.
+ //
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (AttributeHeader->TypeCode != $STANDARD_INFORMATION) {
+
+ try_return( CorruptDisk = TRUE );
+ }
+
+ } else {
+
+ try_return( CorruptDisk = TRUE );
+ }
+
+ Info = &Fcb->Info;
+
+ //
+ // Copy out the standard information values.
+ //
+
+ {
+ PSTANDARD_INFORMATION StandardInformation;
+ StandardInformation = (PSTANDARD_INFORMATION) NtfsAttributeValue( AttributeHeader );
+
+ Info->CreationTime = StandardInformation->CreationTime;
+ Info->LastModificationTime = StandardInformation->LastModificationTime;
+ Info->LastChangeTime = StandardInformation->LastChangeTime;
+ Info->LastAccessTime = StandardInformation->LastAccessTime;
+ Info->FileAttributes = StandardInformation->FileAttributes;
+
+#ifdef _CAIRO_
+ if (AttributeHeader->Form.Resident.ValueLength >=
+ sizeof(STANDARD_INFORMATION)) {
+ Fcb->ClassId = StandardInformation->ClassId;
+ Fcb->OwnerId = StandardInformation->OwnerId;
+ Fcb->SecurityId = StandardInformation->SecurityId;
+ Fcb->Usn = StandardInformation->Usn;
+
+ SetFlag(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO);
+ }
+
+#else _CAIRO_
+
+ if (AttributeHeader->Form.Resident.ValueLength > sizeof( STANDARD_INFORMATION )) {
+
+ SetFlag(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO);
+ }
+#endif _CAIRO_
+ }
+
+ Fcb->CurrentLastAccess = Info->LastAccessTime;
+
+ //
+ // We get the FILE_NAME_INDEX_PRESENT bit by reading the
+ // file record.
+ //
+
+ if (FlagOn( NtfsContainingFileRecord( &AttrContext )->Flags,
+ FILE_FILE_NAME_INDEX_PRESENT )) {
+
+ SetFlag( Info->FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+
+ } else {
+
+ ClearFlag( Info->FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+ }
+
+ //
+ // We now walk through all of the filename attributes, counting the
+ // number of non-8dot3-only links.
+ //
+
+ Fcb->TotalLinks =
+ Fcb->LinkCount = 0;
+
+ FoundEntry = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &AttrContext );
+
+ while (FoundEntry) {
+
+ PFILE_NAME FileName;
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (AttributeHeader->TypeCode != $FILE_NAME) {
+
+ break;
+ }
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( AttributeHeader );
+
+ //
+ // We increment the count as long as this is not a 8.3 link
+ // only.
+ //
+
+ if (FileName->Flags != FILE_NAME_DOS) {
+
+ Fcb->LinkCount += 1;
+ Fcb->TotalLinks += 1;
+ }
+
+ //
+ // Now look for the next link.
+ //
+
+ FoundEntry = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &AttrContext );
+ }
+
+ //
+ // There better be at least one.
+ //
+
+ if (Fcb->LinkCount == 0) {
+
+ try_return( CorruptDisk = TRUE );
+ }
+
+ //
+ // If we are to load the security and it is not already present we
+ // find the security attribute.
+ //
+
+ if (LoadSecurity && Fcb->SharedSecurity == NULL) {
+
+#ifdef _CAIRO_
+ //
+ // We have two sources of security descriptors. First, we have
+ // the SecurityId that is present in a large $STANDARD_INFORMATION.
+ // The other case is where we don't have such a security Id and must
+ // retrieve it from the $SECURITY_DESCRIPTOR attribute
+ //
+ // In the case where we have the Id, we load it from the volume
+ // cache or index.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO ) &&
+ Fcb->SecurityId != SECURITY_ID_INVALID) {
+
+ NtfsLoadSecurityDescriptorById( IrpContext, Fcb, ParentFcb );
+
+ } else {
+
+#endif // _CAIRO_
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+
+ //
+ // We may have to walk forward to the security descriptor.
+ //
+
+ while (FoundEntry) {
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (AttributeHeader->TypeCode == $SECURITY_DESCRIPTOR) {
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *)&SecurityDescriptor,
+ &SecurityDescriptorLength,
+ &Bcb,
+ &AttrContext );
+
+ NtfsUpdateFcbSecurity( IrpContext,
+ Fcb,
+ ParentFcb,
+#ifdef _CAIRO_
+ SECURITY_ID_INVALID,
+#endif // _CAIRO_
+ SecurityDescriptor,
+ SecurityDescriptorLength );
+
+ //
+ // If the security descriptor was resident then the Bcb field
+ // in the attribute context was stored in the returned Bcb and
+ // the Bcb in the attribute context was cleared. In that case
+ // the resumption of the attribute search will fail because
+ // this module using the Bcb field to determine if this
+ // is the initial enumeration.
+ //
+
+ if (NtfsIsAttributeResident( AttributeHeader )) {
+
+ NtfsFoundBcb( &AttrContext ) = Bcb;
+ Bcb = NULL;
+ }
+
+ } else if (AttributeHeader->TypeCode > $SECURITY_DESCRIPTOR) {
+
+ break;
+ }
+
+ FoundEntry = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &AttrContext );
+ }
+#ifdef _CAIRO_
+ }
+#endif
+ }
+
+ //
+ // If this is not a directory, we need the file size.
+ //
+
+ if (!IsDirectory( Info )) {
+
+ BOOLEAN FoundData = FALSE;
+
+ //
+ // Look for the unnamed data attribute.
+ //
+
+ while (FoundEntry) {
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (AttributeHeader->TypeCode > $DATA) {
+
+ break;
+ }
+
+ if ((AttributeHeader->TypeCode == $DATA) &&
+ (AttributeHeader->NameLength == 0)) {
+
+ //
+ // This can vary depending whether the attribute is resident
+ // or nonresident.
+ //
+
+ if (NtfsIsAttributeResident( AttributeHeader )) {
+
+ Info->AllocatedLength = AttributeHeader->Form.Resident.ValueLength;
+ Info->FileSize = Info->AllocatedLength;
+
+ ((ULONG)Info->AllocatedLength) = QuadAlign( (ULONG)(Info->AllocatedLength) );
+
+ //
+ // If the user passed in a ScbSnapshot, then copy the attribute
+ // sizes to that. We use the trick of setting the low bit of the
+ // attribute size to indicate a resident attribute.
+ //
+
+ if (ARGUMENT_PRESENT( UnnamedDataSizes )) {
+
+ UnnamedDataSizes->TotalAllocated =
+ UnnamedDataSizes->AllocationSize = Info->AllocatedLength;
+ UnnamedDataSizes->FileSize = Info->FileSize;
+ UnnamedDataSizes->ValidDataLength = Info->FileSize;
+
+ UnnamedDataSizes->Resident = TRUE;
+ UnnamedDataSizes->CompressionUnit = 0;
+
+ UnnamedDataSizes->AttributeFlags = AttributeHeader->Flags;
+ }
+
+ FoundData = TRUE;
+
+ } else if (AttributeHeader->Form.Nonresident.LowestVcn == 0) {
+
+ Info->AllocatedLength = AttributeHeader->Form.Nonresident.AllocatedLength;
+ Info->FileSize = AttributeHeader->Form.Nonresident.FileSize;
+
+ if (ARGUMENT_PRESENT( UnnamedDataSizes )) {
+
+ UnnamedDataSizes->TotalAllocated =
+ UnnamedDataSizes->AllocationSize = Info->AllocatedLength;
+ UnnamedDataSizes->FileSize = Info->FileSize;
+ UnnamedDataSizes->ValidDataLength = AttributeHeader->Form.Nonresident.ValidDataLength;
+
+ UnnamedDataSizes->Resident = FALSE;
+ UnnamedDataSizes->CompressionUnit = AttributeHeader->Form.Nonresident.CompressionUnit;
+
+ //
+ // Remember if it is compressed.
+ //
+
+ UnnamedDataSizes->AttributeFlags = AttributeHeader->Flags;
+ }
+
+ if (FlagOn( AttributeHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ Info->AllocatedLength = AttributeHeader->Form.Nonresident.TotalAllocated;
+
+ if (ARGUMENT_PRESENT( UnnamedDataSizes )) {
+
+ UnnamedDataSizes->TotalAllocated = Info->AllocatedLength;
+
+ if (UnnamedDataSizes->TotalAllocated < 0) {
+
+ UnnamedDataSizes->TotalAllocated = 0;
+
+ } else if (UnnamedDataSizes->TotalAllocated > Info->AllocatedLength) {
+
+ UnnamedDataSizes->TotalAllocated = Info->AllocatedLength;
+ }
+ }
+ }
+
+ FoundData = TRUE;
+ }
+
+ break;
+ }
+
+ FoundEntry = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &AttrContext );
+ }
+
+ if (!FoundData) {
+
+ try_return( CorruptDisk = TRUE );
+ }
+
+ } else {
+
+ Info->AllocatedLength = 0;
+ Info->FileSize = 0;
+ }
+
+ //
+ // Now we look for an Ea information attribute. This one doesn't have to
+ // be there.
+ //
+
+ Info->PackedEaSize = 0;
+
+ while (FoundEntry) {
+
+ PEA_INFORMATION EaInformation;
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (AttributeHeader->TypeCode > $EA_INFORMATION) {
+
+ break;
+
+ } else if (AttributeHeader->TypeCode == $EA_INFORMATION) {
+
+ EaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ Info->PackedEaSize = EaInformation->PackedEaSize;
+
+ break;
+ }
+
+ FoundEntry = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $EA_INFORMATION,
+ &AttrContext );
+ }
+
+ //
+ // Set the flag in the Fcb to indicate that we set these fields.
+ //
+
+ SetFlag( Fcb->FcbState, FCB_STATE_DUP_INITIALIZED );
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsUpdateFcbInfoFromDisk );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsUnpinBcb( &Bcb );
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateFcbInfoFromDisk: Exit\n") );
+ }
+
+ //
+ // If we encountered a corrupt disk, we generate a popup and raise the file
+ // corrupt error.
+ //
+
+ if (CorruptDisk) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsCleanupAttributeContext (
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to free any resources claimed within an enumeration
+ context and to unpin mapped or pinned data.
+
+Arguments:
+
+ AttributeContext - Pointer to the enumeration context to perform cleanup
+ on.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCleanupAttributeContext\n") );
+
+ //
+ // TEMPCODE We need a call to cleanup any Scb's created.
+ //
+
+ //
+ // Unpin any Bcb's pinned here.
+ //
+
+ NtfsUnpinBcb( &AttributeContext->FoundAttribute.Bcb );
+ NtfsUnpinBcb( &AttributeContext->AttributeList.Bcb );
+ NtfsUnpinBcb( &AttributeContext->AttributeList.NonresidentListBcb );
+
+ //
+ // Originally, we zeroed the entire context at this point. This is
+ // wildly inefficient since the context is either deallocated soon thereafter
+ // or is initialized again.
+ //
+ // RtlZeroMemory( AttributeContext, sizeof(ATTRIBUTE_ENUMERATION_CONTEXT) );
+ //
+
+ // BUGBUG - set entire contents to -1 (and reset Bcb's to NULL) to verify
+ // that no one reuses this data structure
+
+#if DBG
+ RtlFillMemory( AttributeContext, sizeof( *AttributeContext ), -1 );
+ AttributeContext->FoundAttribute.Bcb = NULL;
+ AttributeContext->AttributeList.Bcb = NULL;
+ AttributeContext->AttributeList.NonresidentListBcb = NULL;
+#endif
+
+ DebugTrace( -1, Dbg, ("NtfsCleanupAttributeContext -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsWriteFileSizes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLONGLONG ValidDataLength,
+ IN BOOLEAN AdvanceOnly,
+ IN BOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to modify the filesize and valid data size
+ on the disk from the Scb.
+
+Arguments:
+
+ Scb - Scb whose attribute is being modified.
+
+ ValidDataLength - Supplies pointer to the new desired ValidDataLength
+
+ AdvanceOnly - TRUE if the valid data length should be set only if
+ greater than the current value on disk. FALSE if
+ the valid data length should be set only if
+ less than the current value on disk.
+
+ LogIt - Indicates whether we should log this change.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PATTRIBUTE_RECORD_HEADER AttributeHeader;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ NEW_ATTRIBUTE_SIZES OldAttributeSizes;
+ NEW_ATTRIBUTE_SIZES NewAttributeSizes;
+
+ ULONG LogRecordSize = SIZEOF_PARTIAL_ATTRIBUTE_SIZES;
+ BOOLEAN CompressedStream = FALSE;
+
+ BOOLEAN UpdateMft = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Return immediately if the volume is locked.
+ //
+
+ if (FlagOn( Scb->Vcb->VcbState, VCB_STATE_LOCKED )) {
+
+ return;
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsWriteFileSizes: Entered\n") );
+
+ //
+ // Use a try_finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Find the attribute on the disk.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ //
+ // Pull the pointers out of the attribute context.
+ //
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ //
+ // Check if this is a resident attribute, and if it is then we only
+ // want to assert that the file sizes match and then return to
+ // our caller
+ //
+
+ if (NtfsIsAttributeResident( AttributeHeader )) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Remember the existing values.
+ //
+
+ OldAttributeSizes.TotalAllocated =
+ OldAttributeSizes.AllocationSize = AttributeHeader->Form.Nonresident.AllocatedLength;
+ OldAttributeSizes.ValidDataLength = AttributeHeader->Form.Nonresident.ValidDataLength;
+ OldAttributeSizes.FileSize = AttributeHeader->Form.Nonresident.FileSize;
+
+ if (FlagOn( AttributeHeader->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ CompressedStream = TRUE;
+ OldAttributeSizes.TotalAllocated = AttributeHeader->Form.Nonresident.TotalAllocated;
+ }
+
+ //
+ // Copy these values.
+ //
+
+ NewAttributeSizes = OldAttributeSizes;
+
+ //
+ // Check if we will be modifying the valid data length on
+ // disk. Don't acquire this for the paging file in case the
+ // current code block needs to be paged in.
+ //
+
+ if (!FlagOn( Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ NtfsAcquireFsrtlHeader(Scb);
+ }
+
+ if ((AdvanceOnly
+ && (*ValidDataLength > OldAttributeSizes.ValidDataLength))
+
+ || (!AdvanceOnly
+ && (*ValidDataLength < OldAttributeSizes.ValidDataLength))) {
+
+ //
+ // Copy the valid data length into the new size structure.
+ //
+
+ NewAttributeSizes.ValidDataLength = *ValidDataLength;
+
+ UpdateMft = TRUE;
+
+ }
+
+ //
+ // Now check if we're modifying the filesize.
+ //
+
+ if (Scb->Header.FileSize.QuadPart != OldAttributeSizes.FileSize) {
+
+ NewAttributeSizes.FileSize = Scb->Header.FileSize.QuadPart;
+
+ UpdateMft = TRUE;
+ }
+
+ if (!FlagOn( Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ NtfsReleaseFsrtlHeader(Scb);
+ }
+
+ //
+ // If this is compressed then check if totally allocated has changed.
+ //
+
+ if (CompressedStream) {
+
+ LogRecordSize = SIZEOF_FULL_ATTRIBUTE_SIZES;
+
+ if (Scb->TotalAllocated != OldAttributeSizes.TotalAllocated) {
+
+ NewAttributeSizes.TotalAllocated = Scb->TotalAllocated;
+ UpdateMft = TRUE;
+ }
+ }
+
+ //
+ // Finally, update the allocated length from the Scb if it is different.
+ //
+
+ if (Scb->Header.AllocationSize.QuadPart != AttributeHeader->Form.Nonresident.AllocatedLength) {
+
+ NewAttributeSizes.AllocationSize = Scb->Header.AllocationSize.QuadPart;
+
+ UpdateMft = TRUE;
+ }
+
+ //
+ // Continue on if we need to update the Mft.
+ //
+
+ if (UpdateMft) {
+
+ //
+ // Pin the attribute.
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Scb->Vcb,
+ &AttrContext );
+
+ AttributeHeader = NtfsFoundAttribute( &AttrContext );
+
+ if (NewAttributeSizes.ValidDataLength > NewAttributeSizes.FileSize) {
+
+ // ASSERT(XxLeq(NewAttributeSizes.ValidDataLength,NewAttributeSizes.FileSize));
+
+ NewAttributeSizes.ValidDataLength = NewAttributeSizes.FileSize;
+ }
+
+ ASSERT(NewAttributeSizes.FileSize <= NewAttributeSizes.AllocationSize);
+ ASSERT(NewAttributeSizes.ValidDataLength <= NewAttributeSizes.AllocationSize);
+
+ //
+ // Log this change to the attribute header.
+ //
+
+ if (LogIt) {
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ Scb->Vcb->MftScb,
+ NtfsFoundBcb( &AttrContext ),
+ SetNewAttributeSizes,
+ &NewAttributeSizes,
+ LogRecordSize,
+ SetNewAttributeSizes,
+ &OldAttributeSizes,
+ LogRecordSize,
+ NtfsMftOffset( &AttrContext ),
+ PtrOffset( FileRecord, AttributeHeader ),
+ 0,
+ Scb->Vcb->BytesPerFileRecordSegment );
+
+ } else {
+
+ CcSetDirtyPinnedData( NtfsFoundBcb( &AttrContext ), NULL );
+ }
+
+ AttributeHeader->Form.Nonresident.AllocatedLength = NewAttributeSizes.AllocationSize;
+ AttributeHeader->Form.Nonresident.FileSize = NewAttributeSizes.FileSize;
+ AttributeHeader->Form.Nonresident.ValidDataLength = NewAttributeSizes.ValidDataLength;
+
+ //
+ // Don't modify the total allocated field unless there is an actual field for it.
+ //
+
+ if (CompressedStream &&
+ ((AttributeHeader->NameOffset >= SIZEOF_FULL_NONRES_ATTR_HEADER) ||
+ ((AttributeHeader->NameOffset == 0) &&
+ (AttributeHeader->Form.Nonresident.MappingPairsOffset >= SIZEOF_FULL_NONRES_ATTR_HEADER)))) {
+
+ AttributeHeader->Form.Nonresident.TotalAllocated = NewAttributeSizes.TotalAllocated;
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsWriteFileSizes );
+
+ //
+ // Cleanup the attribute context.
+ //
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsWriteFileSizes: Exit\n") );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateStandardInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the standard information attribute
+ for a file from the information in the Fcb. The fields being modified
+ are the time fields and the file attributes.
+
+Arguments:
+
+ Fcb - Fcb for the file to modify.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ STANDARD_INFORMATION StandardInformation;
+ ULONG Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateStandardInformation: Entered\n") );
+
+ //
+ // Use a try-finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // Initialize the context structure.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+#ifdef _CAIRO_
+ Length = NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength;
+ //
+ // Copy the existing standard information to our buffer.
+ //
+
+ RtlCopyMemory( &StandardInformation,
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )),
+ Length);
+
+
+#else
+ //
+ // Copy the existing standard information to our buffer.
+ //
+
+ RtlCopyMemory( &StandardInformation,
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )),
+ sizeof( STANDARD_INFORMATION ));
+
+#endif
+ //
+ // Since we are updating standard information, make sure the last
+ // access time is up-to-date.
+ //
+
+ if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) {
+
+ Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+ }
+
+ //
+ // Change the relevant time fields.
+ //
+
+ StandardInformation.CreationTime = Fcb->Info.CreationTime;
+ StandardInformation.LastModificationTime = Fcb->Info.LastModificationTime;
+ StandardInformation.LastChangeTime = Fcb->Info.LastChangeTime;
+ StandardInformation.LastAccessTime = Fcb->Info.LastAccessTime;
+ StandardInformation.FileAttributes = Fcb->Info.FileAttributes;
+
+ //
+ // We clear the directory bit.
+ //
+
+ ClearFlag( StandardInformation.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+
+#ifdef _CAIRO_
+
+ // Fill in the new fields if necessary.
+
+ if (FlagOn(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO)) {
+
+ StandardInformation.ClassId = Fcb->ClassId;
+ StandardInformation.OwnerId = Fcb->OwnerId;
+ StandardInformation.SecurityId = Fcb->SecurityId;
+ StandardInformation.Usn = Fcb->Usn;
+ }
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0,
+ &StandardInformation,
+ Length,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+
+#else
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0,
+ &StandardInformation,
+ sizeof( STANDARD_INFORMATION ),
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+
+#endif
+
+ } finally {
+
+ DebugUnwind( NtfsUpdateStandadInformation );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateStandardInformation: Exit\n") );
+ }
+
+ return;
+}
+
+
+#ifdef _CAIRO_
+VOID
+NtfsGrowStandardInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to grow and update the standard information
+ attribute for a file from the information in the Fcb.
+
+Arguments:
+
+ Fcb - Fcb for the file to modify.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ STANDARD_INFORMATION StandardInformation;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGrowStandardInformation: Entered\n") );
+
+ //
+ // Use a try-finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // Initialize the context structure.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ if (NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength ==
+ SIZEOF_OLD_STANDARD_INFORMATION) {
+
+ //
+ // Copy the existing standard information to our buffer.
+ //
+
+ RtlCopyMemory( &StandardInformation,
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext )),
+ SIZEOF_OLD_STANDARD_INFORMATION);
+
+ RtlZeroMemory((PCHAR) &StandardInformation +
+ SIZEOF_OLD_STANDARD_INFORMATION,
+ sizeof( STANDARD_INFORMATION) -
+ SIZEOF_OLD_STANDARD_INFORMATION);
+ }
+
+ //
+ // Since we are updating standard information, make sure the last
+ // access time is up-to-date.
+ //
+
+ if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) {
+
+ Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+ }
+
+ //
+ // Change the relevant time fields.
+ //
+
+ StandardInformation.CreationTime = Fcb->Info.CreationTime;
+ StandardInformation.LastModificationTime = Fcb->Info.LastModificationTime;
+ StandardInformation.LastChangeTime = Fcb->Info.LastChangeTime;
+ StandardInformation.LastAccessTime = Fcb->Info.LastAccessTime;
+ StandardInformation.FileAttributes = Fcb->Info.FileAttributes;
+
+ //
+ // We clear the directory bit.
+ //
+
+ ClearFlag( StandardInformation.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+
+
+ //
+ // Fill in the new fields.
+ //
+
+ StandardInformation.ClassId = Fcb->ClassId;
+ StandardInformation.OwnerId = Fcb->OwnerId;
+ StandardInformation.SecurityId = Fcb->SecurityId;
+ StandardInformation.Usn = Fcb->Usn;
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0,
+ &StandardInformation,
+ sizeof( STANDARD_INFORMATION),
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO );
+
+ } finally {
+
+ DebugUnwind( NtfsGrowStandadInformation );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsGrowStandardInformation: Exit\n") );
+ }
+
+ return;
+}
+#endif
+
+
+BOOLEAN
+NtfsLookupEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PUNICODE_STRING Name,
+ IN OUT PFILE_NAME *FileNameAttr,
+ IN OUT PUSHORT FileNameAttrLength,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ OUT PINDEX_ENTRY *IndexEntry,
+ OUT PBCB *IndexEntryBcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to look up a particular file name in a directory.
+ It takes a single component name and a parent Scb to search in.
+ To do the search, we need to construct a FILE_NAME attribute.
+ We use a reusable buffer to do this, to avoid constantly allocating
+ and deallocating pool. We try to keep this larger than we will ever need.
+
+ When we find a match on disk, we copy over the name we were called with so
+ we have a record of the actual case on the disk. In this way we can
+ be case perserving.
+
+Arguments:
+
+ ParentScb - This is the Scb for the parent directory.
+
+ IgnoreCase - Indicates if we should ignore case while searching through
+ the index.
+
+ Name - This is the path component to search for. We will overwrite this
+ in place if a match is found.
+
+ FileNameAttr - Address of the buffer we will use to create the file name
+ attribute. We will free this buffer and allocate a new buffer
+ if needed.
+
+ FileNameAttrLength - This is the length of the FileNameAttr buffer above.
+
+ QuickIndex - If specified, supplies a pointer to a quik lookup structure
+ to be updated by this routine.
+
+ IndexEntry - Address to store the cache address of the matching entry.
+
+ IndexEntryBcb - Address to store the Bcb for the IndexEntry above.
+
+Return Value:
+
+ BOOLEAN - TRUE if a match was found, FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN FoundEntry;
+ USHORT Size;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsLookupEntry: Entered\n") );
+
+ //
+ // We compute the size of the buffer needed to build the filename
+ // attribute. If the current buffer is too small we deallocate it
+ // and allocate a new one. We always allocate twice the size we
+ // need in order to minimize the number of allocations.
+ //
+
+ Size = (USHORT)(sizeof( FILE_NAME ) + Name->Length - sizeof(WCHAR));
+
+ if (Size > *FileNameAttrLength) {
+
+ if (*FileNameAttr != NULL) {
+
+ DebugTrace( 0, Dbg, ("Deallocating previous file name attribute buffer\n") );
+ NtfsFreePool( *FileNameAttr );
+
+ *FileNameAttr = NULL;
+ }
+
+ *FileNameAttr = NtfsAllocatePool(PagedPool, Size << 1 );
+ *FileNameAttrLength = Size << 1;
+ }
+
+ //
+ // We build the filename attribute. If this operation is ignore case,
+ // we upcase the expression in the filename attribute.
+ //
+
+ NtfsBuildFileNameAttribute( IrpContext,
+ &ParentScb->Fcb->FileReference,
+ *Name,
+ 0,
+ *FileNameAttr );
+
+ //
+ // Now we call the index routine to perform the search.
+ //
+
+ FoundEntry = NtfsFindIndexEntry( IrpContext,
+ ParentScb,
+ *FileNameAttr,
+ IgnoreCase,
+ QuickIndex,
+ IndexEntryBcb,
+ IndexEntry );
+
+ //
+ // We always restore the name in the filename attribute to the original
+ // name in case we upcased it in the lookup.
+ //
+
+ if (IgnoreCase) {
+
+ RtlCopyMemory( (*FileNameAttr)->FileName,
+ Name->Buffer,
+ Name->Length );
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsLookupEntry: Exit -> %04x\n", FoundEntry) );
+
+ return FoundEntry;
+}
+
+
+VOID
+NtfsCreateAttributeWithValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN USHORT AttributeFlags,
+ IN PFILE_REFERENCE WhereIndexed OPTIONAL,
+ IN BOOLEAN LogIt,
+ OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates the specified attribute with the specified value,
+ and returns a description of it via the attribute context. If no
+ value is specified, then the attribute is created with the specified
+ number of zero bytes.
+
+ On successful return, it is up to the caller to clean up the attribute
+ context.
+
+Arguments:
+
+ Fcb - Current file.
+
+ AttributeTypeCode - Type code of the attribute to create.
+
+ AttributeName - Optional name for attribute.
+
+ Value - Pointer to the buffer containing the desired attribute value,
+ or a NULL if zeros are desired.
+
+ ValueLength - Length of value in bytes.
+
+ AttributeFlags - Desired flags for the created attribute.
+
+ WhereIndexed - Optionally supplies the file reference to the file where
+ this attribute is indexed.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are creating a new file record, and
+ will be logging the entire new file record.
+
+ Context - A handle to the created attribute. This must be cleaned up upon
+ return. Callers who may have made an attribute nonresident may
+ not count on accessing the created attribute via this context upon
+ return.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UCHAR AttributeBuffer[SIZEOF_FULL_NONRES_ATTR_HEADER];
+ ULONG RecordOffset;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ULONG SizeNeeded;
+ ULONG AttrSizeNeeded;
+ PVCB Vcb;
+ ULONG Passes = 0;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ ASSERT( (AttributeFlags == 0) ||
+ (AttributeTypeCode == $INDEX_ROOT) ||
+ NtfsIsTypeCodeCompressible( AttributeTypeCode ));
+
+ Vcb = Fcb->Vcb;
+
+ DebugTrace( +1, Dbg, ("NtfsCreateAttributeWithValue\n") );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("ValueLength = %08lx\n", ValueLength) );
+
+ //
+ // Clear out the invalid attribute flags for this volume.
+ //
+
+ AttributeFlags &= Vcb->AttributeFlagsMask;
+
+ //
+ // Calculate the size needed for this attribute
+ //
+
+ SizeNeeded = SIZEOF_RESIDENT_ATTRIBUTE_HEADER + QuadAlign( ValueLength ) +
+ (ARGUMENT_PRESENT( AttributeName ) ?
+ QuadAlign( AttributeName->Length ) : 0);
+
+ //
+ // Loop until we find all the space we need.
+ //
+
+ do {
+
+ //
+ // Reinitialize context if this is not the first pass.
+ //
+
+ if (Passes != 0) {
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+ }
+
+ Passes += 1;
+
+ ASSERT( Passes < 5 );
+
+ //
+ // If the attribute is not indexed, then we will position to the
+ // insertion point by type code and name.
+ //
+
+ if (!ARGUMENT_PRESENT( WhereIndexed )) {
+
+ if (NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ AttributeName,
+ NULL,
+ FALSE,
+ Context )) {
+
+ DebugTrace( 0, 0,
+ ("Nonindexed attribute already exists, TypeCode = %08lx\n",
+ AttributeTypeCode ));
+
+ ASSERTMSG("Nonindexed attribute already exists, About to bugcheck ", FALSE);
+ NtfsBugCheck( AttributeTypeCode, 0, 0 );
+ }
+
+ //
+ // Check here if the attribute needs to be nonresident and if so just
+ // pass this off.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+
+ if ((SizeNeeded > (FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) &&
+ (SizeNeeded >= Vcb->BigEnoughToMove) &&
+ !FlagOn(NtfsGetAttributeDefinition(Vcb,
+ AttributeTypeCode)->Flags,
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT)) {
+
+ NtfsCreateNonresidentWithValue( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ AttributeName,
+ Value,
+ ValueLength,
+ AttributeFlags,
+ FALSE,
+ NULL,
+ LogIt,
+ Context );
+
+ return;
+ }
+
+ //
+ // Otherwise, if the attribute is indexed, then we position by the
+ // attribute value.
+ //
+
+ } else {
+
+ ASSERT(ARGUMENT_PRESENT(Value));
+
+ if (NtfsLookupAttributeByValue( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ Value,
+ ValueLength,
+ Context )) {
+
+ DebugTrace( 0, 0,
+ ("Indexed attribute already exists, TypeCode = %08lx\n",
+ AttributeTypeCode ));
+
+ ASSERTMSG("Indexed attribute already exists, About to bugcheck ", FALSE);
+ NtfsBugCheck( AttributeTypeCode, 0, 0 );
+ }
+ }
+
+ //
+ // If this attribute is being positioned in the base file record and
+ // there is an attribute list then we need to ask for enough space
+ // for the attribute list entry now.
+ //
+
+ FileRecord = NtfsContainingFileRecord( Context );
+ Attribute = NtfsFoundAttribute( Context );
+
+ AttrSizeNeeded = SizeNeeded;
+ if (Context->AttributeList.Bcb != NULL
+ && (ULONG) FileRecord <= (ULONG) Context->AttributeList.AttributeList
+ && (ULONG) Attribute >= (ULONG) Context->AttributeList.AttributeList) {
+
+ //
+ // If the attribute list is non-resident then add a fudge factor of
+ // 16 bytes for any new retrieval information.
+ //
+
+ if (NtfsIsAttributeResident( Context->AttributeList.AttributeList )) {
+
+ AttrSizeNeeded += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName )
+ + (ARGUMENT_PRESENT( AttributeName ) ?
+ (ULONG) AttributeName->Length :
+ sizeof( WCHAR )));
+
+ } else {
+
+ AttrSizeNeeded += 0x10;
+ }
+ }
+
+ //
+ // Ask for the space we need.
+ //
+
+ } while (!NtfsGetSpaceForAttribute( IrpContext, Fcb, AttrSizeNeeded, Context ));
+
+ //
+ // Now point to the file record and calculate the record offset where
+ // our attribute will go. And point to our local buffer.
+ //
+
+ RecordOffset = (PCHAR)NtfsFoundAttribute(Context) - (PCHAR)FileRecord;
+ Attribute = (PATTRIBUTE_RECORD_HEADER)AttributeBuffer;
+
+ RtlZeroMemory( Attribute, SIZEOF_RESIDENT_ATTRIBUTE_HEADER );
+
+ Attribute->TypeCode = AttributeTypeCode;
+ Attribute->RecordLength = SizeNeeded;
+ Attribute->FormCode = RESIDENT_FORM;
+
+ if (ARGUMENT_PRESENT(AttributeName)) {
+
+ ASSERT( AttributeName->Length <= 0x1FF );
+
+ Attribute->NameLength = (UCHAR)(AttributeName->Length / sizeof(WCHAR));
+ Attribute->NameOffset = (USHORT)SIZEOF_RESIDENT_ATTRIBUTE_HEADER;
+ }
+
+ Attribute->Flags = AttributeFlags;
+ Attribute->Instance = FileRecord->NextAttributeInstance;
+ Attribute->Form.Resident.ValueLength = ValueLength;
+ Attribute->Form.Resident.ValueOffset =
+ (USHORT)(SIZEOF_RESIDENT_ATTRIBUTE_HEADER +
+ QuadAlign( Attribute->NameLength << 1) );
+
+ //
+ // If this attribute is indexed, then we have to set the right flag
+ // and update the file record reference count.
+ //
+
+ if (ARGUMENT_PRESENT(WhereIndexed)) {
+ Attribute->Form.Resident.ResidentFlags = RESIDENT_FORM_INDEXED;
+ }
+
+ //
+ // Now we will actually create the attribute in place, so that we
+ // save copying everything twice, and can point to the final image
+ // for the log write below.
+ //
+
+ NtfsRestartInsertAttribute( IrpContext,
+ FileRecord,
+ RecordOffset,
+ Attribute,
+ AttributeName,
+ Value,
+ ValueLength );
+
+ //
+ // Finally, log the creation of this attribute
+ //
+
+ if (LogIt) {
+
+ //
+ // We have actually created the attribute above, but the write
+ // log below could fail. The reason we did the create already
+ // was to avoid having to allocate pool and copy everything
+ // twice (header, name and value). Our normal error recovery
+ // just recovers from the log file. But if we fail to write
+ // the log, we have to remove this attribute by hand, and
+ // raise the condition again.
+ //
+
+ try {
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ CreateAttribute,
+ Add2Ptr(FileRecord, RecordOffset),
+ Attribute->RecordLength,
+ DeleteAttribute,
+ NULL,
+ 0,
+ NtfsMftOffset( Context ),
+ RecordOffset,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NtfsRestartRemoveAttribute( IrpContext, FileRecord, RecordOffset );
+
+ NtfsRaiseStatus( IrpContext, GetExceptionCode(), NULL, NULL );
+ }
+ }
+
+ //
+ // Now add it to the attribute list if necessary
+ //
+
+ if (Context->AttributeList.Bcb != NULL) {
+
+ MFT_SEGMENT_REFERENCE SegmentReference;
+
+ *(PLONGLONG)&SegmentReference = LlFileRecordsFromBytes( Vcb, NtfsMftOffset( Context ));
+ SegmentReference.SequenceNumber = FileRecord->SequenceNumber;
+
+ NtfsAddToAttributeList( IrpContext, Fcb, SegmentReference, Context );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateAttributeWithValue -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsCreateNonresidentWithValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN WriteClusters,
+ IN PSCB ThisScb OPTIONAL,
+ IN BOOLEAN LogIt,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates the specified nonresident attribute with the specified
+ value, and returns a description of it via the attribute context. If no
+ value is specified, then the attribute is created with the specified
+ number of zero bytes.
+
+ On successful return, it is up to the caller to clean up the attribute
+ context.
+
+Arguments:
+
+ Fcb - Current file.
+
+ AttributeTypeCode - Type code of the attribute to create.
+
+ AttributeName - Optional name for attribute.
+
+ Value - Pointer to the buffer containing the desired attribute value,
+ or a NULL if zeros are desired.
+
+ ValueLength - Length of value in bytes.
+
+ AttributeFlags - Desired flags for the created attribute.
+
+ WriteClusters - if supplied as TRUE, then we cannot write the data into the
+ cache but must write the clusters directly to the disk. The value buffer
+ in this case must be quad-aligned and a multiple of cluster size in size.
+ If TRUE it also means we are being called during the NtfsConvertToNonresident
+ path. We need to set a flag in the Scb in that case.
+
+ ThisScb - If present, this is the Scb to use for the create. It also indicates
+ that this call is from convert to non-resident.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are creating a new file record, and
+ will be logging the entire new file record.
+
+ Context - This is the location to create the new attribute.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSCB Scb;
+ BOOLEAN ReturnedExistingScb;
+ UNICODE_STRING LocalName;
+ PVCB Vcb = Fcb->Vcb;
+ BOOLEAN LogNonresidentToo;
+ BOOLEAN AdvanceOnly;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateNonresidentWithValue\n") );
+
+ AdvanceOnly =
+ LogNonresidentToo = BooleanFlagOn(NtfsGetAttributeDefinition(Vcb, AttributeTypeCode)->Flags,
+ ATTRIBUTE_DEF_LOG_NONRESIDENT);
+
+ ASSERT( (AttributeFlags == 0) || NtfsIsTypeCodeCompressible( AttributeTypeCode ));
+
+ //
+ // Clear out the invalid attribute flags for this volume.
+ //
+
+ AttributeFlags &= Vcb->AttributeFlagsMask;
+
+ if (ARGUMENT_PRESENT(AttributeName)) {
+
+ LocalName = *AttributeName;
+
+ } else {
+
+ LocalName.Length = LocalName.MaximumLength = 0;
+ LocalName.Buffer = NULL;
+ }
+
+ if (ARGUMENT_PRESENT( ThisScb )) {
+
+ Scb = ThisScb;
+ ReturnedExistingScb = TRUE;
+
+ } else {
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &LocalName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ //
+ // An attribute has gone away but the Scb hasn't left yet.
+ // Also mark the header as unitialized.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_ATTRIBUTE_RESIDENT |
+ SCB_STATE_FILE_SIZE_LOADED );
+
+ //
+ // Set a flag in the Scb to indicate that we are converting to non-resident.
+ //
+
+ if (WriteClusters) { SetFlag( Scb->ScbState, SCB_STATE_CONVERT_UNDERWAY ); }
+ }
+
+ //
+ // Allocate the record for the size we need.
+ //
+
+ NtfsAllocateAttribute( IrpContext,
+ Scb,
+ AttributeTypeCode,
+ AttributeName,
+ AttributeFlags,
+ TRUE,
+ LogIt,
+ (LONGLONG)ValueLength,
+ Context );
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ //
+ // We need to be careful here, if this call is due to MM creating a
+ // section, we don't want to call into the cache manager or we
+ // will deadlock on the create section call.
+ //
+
+ if (!WriteClusters
+ && !ARGUMENT_PRESENT( ThisScb )) {
+
+ //
+ // This call will initialize a stream for use below.
+ //
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+ }
+
+ //
+ // Now, write in the data.
+ //
+
+ Scb->Header.FileSize.QuadPart = ValueLength;
+ if ((ARGUMENT_PRESENT( Value )) && (ValueLength != 0)) {
+
+ if (LogNonresidentToo || !WriteClusters) {
+
+ ULONG BytesThisPage;
+ PVOID Buffer;
+ PBCB Bcb = NULL;
+
+ LONGLONG CurrentFileOffset = 0;
+ ULONG RemainingBytes = ValueLength;
+
+ PVOID CurrentValue = Value;
+
+ //
+ // While there is more to write, pin the next page and
+ // write a log record.
+ //
+
+ try {
+
+ CC_FILE_SIZES FileSizes;
+
+ //
+ // Call the Cache Manager to truncate and reestablish the FileSize,
+ // so that we are guaranteed to get a valid data length call when
+ // the data goes out. Otherwise he will likely think he does not
+ // have to call us.
+ //
+
+ RtlCopyMemory( &FileSizes, &Scb->Header.AllocationSize, sizeof( CC_FILE_SIZES ));
+
+ FileSizes.FileSize.QuadPart = 0;
+
+ CcSetFileSizes( Scb->FileObject, &FileSizes );
+
+ CcSetFileSizes( Scb->FileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ while (RemainingBytes) {
+
+ BytesThisPage = (RemainingBytes < PAGE_SIZE ? RemainingBytes : PAGE_SIZE);
+
+ NtfsUnpinBcb( &Bcb );
+ NtfsPinStream( IrpContext,
+ Scb,
+ CurrentFileOffset,
+ BytesThisPage,
+ &Bcb,
+ &Buffer );
+
+ if (ARGUMENT_PRESENT(ThisScb)) {
+
+ //
+ // Set the address range modified so that the data will get
+ // written to its new "home".
+ //
+
+ MmSetAddressRangeModified( Buffer, BytesThisPage );
+
+ } else {
+
+ RtlCopyMemory( Buffer, CurrentValue, BytesThisPage );
+ }
+
+ if (LogNonresidentToo) {
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Bcb,
+ UpdateNonresidentValue,
+ Buffer,
+ BytesThisPage,
+ Noop,
+ NULL,
+ 0,
+ CurrentFileOffset,
+ 0,
+ 0,
+ BytesThisPage );
+
+
+ } else {
+
+ CcSetDirtyPinnedData( Bcb, NULL );
+ }
+
+ RemainingBytes -= BytesThisPage;
+ CurrentValue = (PVOID) Add2Ptr( CurrentValue, BytesThisPage );
+
+ (ULONG)CurrentFileOffset += BytesThisPage;
+ }
+
+ } finally {
+
+ NtfsUnpinBcb( &Bcb );
+ }
+
+ } else {
+
+ //
+ // We are going to write the old data directly to disk.
+ //
+
+ NtfsWriteClusters( IrpContext,
+ Vcb,
+ Scb,
+ (LONGLONG)0,
+ Value,
+ ClustersFromBytes( Vcb, ValueLength ));
+
+ //
+ // Be sure to note that the data is actually on disk.
+ //
+
+ AdvanceOnly = TRUE;
+ }
+ }
+
+ //
+ // We need to maintain the file size and valid data length in the
+ // Scb and attribute record. For this attribute, the valid data
+ // size and the file size are now the value length.
+ //
+
+ Scb->Header.ValidDataLength = Scb->Header.FileSize;
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ AdvanceOnly,
+ LogIt );
+
+ if (!WriteClusters) {
+
+ //
+ // Let the cache manager know the new size for this attribute.
+ //
+
+ CcSetFileSizes( Scb->FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+
+ //
+ // If this is the unnamed data attribute, we need to mark this
+ // change in the Fcb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+
+ SetFlag( Fcb->InfoFlags,
+ (FCB_INFO_CHANGED_ALLOC_SIZE | FCB_INFO_CHANGED_FILE_SIZE) );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNonresidentWithValue -> VOID\n") );
+}
+
+
+VOID
+NtfsMapAttributeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PVOID *Buffer,
+ OUT PULONG Length,
+ OUT PBCB *Bcb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to map an entire attribute value. It works
+ whether the attribute is resident or nonresident. It is intended for
+ general handling of system-defined attributes which are small to medium
+ in size, i.e. 0-64KB. This routine will not work for attributes larger
+ than the Cache Manager's virtual address granularity (currently 256KB),
+ and this will be detected by the Cache Manager who will raise an error.
+
+ Note that this routine only maps the data for read-only access. To modify
+ the data, the caller must call NtfsChangeAttributeValue AFTER UNPINNING
+ THE BCB (IF THE SIZE IS CHANGING) returned from this routine.
+
+Arguments:
+
+ Fcb - Current file.
+
+ Buffer - returns a pointer to the mapped attribute value.
+
+ Length - returns the attribute value length in bytes.
+
+ Bcb - Returns a Bcb which must be unpinned when done with the data, and
+ before modifying the attribute value with a size change.
+
+ Context - Attribute Context positioned at the attribute to change.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PSCB Scb;
+ UNICODE_STRING AttributeName;
+ BOOLEAN ReturnedExistingScb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMapAttributeValue\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ Attribute = NtfsFoundAttribute(Context);
+
+ //
+ // For the resident case, everything we need is in the
+ // attribute enumeration context.
+ //
+
+ if (NtfsIsAttributeResident(Attribute)) {
+
+ *Buffer = NtfsAttributeValue(Attribute);
+ *Length = Attribute->Form.Resident.ValueLength;
+ *Bcb = NtfsFoundBcb(Context);
+ NtfsFoundBcb(Context) = NULL;
+
+ DebugTrace( 0, Dbg, ("Buffer < %08lx\n", *Buffer) );
+ DebugTrace( 0, Dbg, ("Length < %08lx\n", *Length) );
+ DebugTrace( 0, Dbg, ("Bcb < %08lx\n", *Bcb) );
+ DebugTrace( -1, Dbg, ("NtfsMapAttributeValue -> VOID\n") );
+
+ return;
+ }
+
+ //
+ // Otherwise, this is a nonresident attribute. First create
+ // the Scb and stream. Note we do not use any try-finally
+ // around this because we currently expect cleanup to get
+ // rid of these streams.
+ //
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ Attribute->TypeCode,
+ &AttributeName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, Attribute );
+ }
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ //
+ // Now just try to map the whole thing. Count on the Cache Manager
+ // to complain if the attribute is too big to map all at once.
+ //
+
+ NtfsMapStream( IrpContext,
+ Scb,
+ (LONGLONG)0,
+ ((ULONG)Attribute->Form.Nonresident.FileSize),
+ Bcb,
+ Buffer );
+
+ *Length = ((ULONG)Attribute->Form.Nonresident.FileSize);
+
+ DebugTrace( 0, Dbg, ("Buffer < %08lx\n", *Buffer) );
+ DebugTrace( 0, Dbg, ("Length < %08lx\n", *Length) );
+ DebugTrace( 0, Dbg, ("Bcb < %08lx\n", *Bcb) );
+ DebugTrace( -1, Dbg, ("NtfsMapAttributeValue -> VOID\n") );
+}
+
+
+VOID
+NtfsChangeAttributeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG ValueOffset,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN BOOLEAN SetNewLength,
+ IN BOOLEAN LogNonresidentToo,
+ IN BOOLEAN CreateSectionUnderway,
+ IN BOOLEAN PreserveContext,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the value of the specified attribute, optionally
+ changing its size.
+
+ The caller specifies the attribute to be changed via the attribute context,
+ and must be prepared to clean up this context no matter how this routine
+ returns.
+
+ There are three byte ranges of interest for this routine. The first is
+ existing bytes to be perserved at the beginning of the attribute. It
+ begins a byte 0 and extends to the point where the attribute is being
+ changed or the current end of the attribute, which ever is smaller.
+ The second is the range of bytes which needs to be zeroed if the modified
+ bytes begin past the current end of the file. This range will be
+ of length 0 if the modified range begins within the current range
+ of bytes for the attribute. The final range is the modified byte range.
+ This is zeroed if no value pointer was specified.
+
+ Ranges of zero bytes at the end of the attribute can be represented in
+ non-resident attributes by a valid data length set to the beginning
+ of what would be zero bytes.
+
+ The following pictures illustrates these ranges when we writing data
+ beyond the current end of the file.
+
+ Current attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Value
+ VVVVVVVVVVVVVVVVVVVVVVVV
+
+ Byte range to save
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Byte range to zero
+ 0000
+
+ Resulting attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ0000VVVVVVVVVVVVVVVVVVVVVVVV
+
+ The following picture illustrates these ranges when we writing data
+ which begins at or before the current end of the file.
+
+ Current attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Value
+ VVVVVVVVVVVVVVVVVVVVVVVV
+
+ Byte range to save
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Byte range to zero (None)
+
+
+ Resulting attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZVVVVVVVVVVVVVVVVVVVVVVVV
+
+ The following picture illustrates these ranges when we writing data
+ totally within the current range of the file without setting
+ a new size.
+
+ Current attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Value
+ VVVVVVVVVVVVVVVVVVVVVVVV
+
+ Byte range to save (Save the whole range and then write over it)
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Byte range to zero (None)
+
+ Resulting attribute
+ ZZZZVVVVVVVVVVVVVVVVVVVVVVVVZZZZ
+
+ The following picture illustrates these ranges when we writing data
+ totally within the current range of the file while setting
+ a new size.
+
+ Current attribute
+ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
+ Value
+ VVVVVVVVVVVVVVVVVVVVVVVV
+
+ Byte range to save (Only save the beginning)
+ ZZZZ
+
+ Byte range to zero (None)
+
+ Resulting attribute
+ ZZZZVVVVVVVVVVVVVVVVVVVVVVVV
+
+ Any of the 'V' values above will be replaced by zeroes if the 'Value'
+ parameter is not passed in.
+
+Arguments:
+
+ Fcb - Current file.
+
+ ValueOffset - Byte offset within the attribute at which the value change is
+ to begin.
+
+ Value - Pointer to the buffer containing the new value, if present. Otherwise
+ zeroes are desired.
+
+ ValueLength - Length of the value in the above buffer.
+
+ SetNewLength - FALSE if the size of the value is not changing, or TRUE if
+ the value length should be changed to ValueOffset + ValueLength.
+
+ LogNonresidentToo - supplies TRUE if the update should be logged even if
+ the attribute is nonresident (such as for the
+ SECURITY_DESCRIPTOR).
+
+ CreateSectionUnderway - if supplied as TRUE, then to the best of the caller's
+ knowledge, an MM Create Section could be underway,
+ which means that we cannot initiate caching on
+ this attribute, as that could cause deadlock. The
+ value buffer in this case must be quad-aligned and
+ a multiple of cluster size in size.
+
+ PreserveContext - Indicates if we need to lookup the attribute in case it
+ might move.
+
+ Context - Attribute Context positioned at the attribute to change.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ UNICODE_STRING AttributeName;
+ ULONG NewSize;
+ PVCB Vcb;
+ BOOLEAN ReturnedExistingScb;
+ BOOLEAN GoToNonResident = FALSE;
+ PVOID Buffer;
+ ULONG CurrentLength;
+ LONG SizeChange, QuadSizeChange;
+ ULONG RecordOffset;
+ ULONG ZeroLength = 0;
+ ULONG UnchangedSize = 0;
+ PBCB Bcb = NULL;
+ PSCB Scb = NULL;
+ PVOID SaveBuffer = NULL;
+ PVOID CopyInputBuffer = NULL;
+
+ WCHAR NameBuffer[8];
+ UNICODE_STRING SavedName;
+ ATTRIBUTE_TYPE_CODE TypeCode;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsChangeAttributeValue\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("ValueOffset = %08lx\n", ValueOffset) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("ValueLength = %08lx\n", ValueLength) );
+ DebugTrace( 0, Dbg, ("SetNewLength = %02lx\n", SetNewLength) );
+ DebugTrace( 0, Dbg, ("LogNonresidentToo = %02lx\n", LogNonresidentToo) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ //
+ // Get the file record and attribute pointers.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ Attribute = NtfsFoundAttribute(Context);
+ TypeCode = Attribute->TypeCode;
+
+ //
+ // Set up a pointer to the name buffer in case we have to use it.
+ //
+
+ SavedName.Buffer = NameBuffer;
+
+ //
+ // Get the current attribute value length.
+ //
+
+ if (NtfsIsAttributeResident(Attribute)) {
+
+ CurrentLength = Attribute->Form.Resident.ValueLength;
+
+ } else {
+
+ if (((PLARGE_INTEGER)&Attribute->Form.Nonresident.AllocatedLength)->HighPart != 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ CurrentLength = ((ULONG)Attribute->Form.Nonresident.AllocatedLength);
+ }
+
+ ASSERT( SetNewLength || ((ValueOffset + ValueLength) <= CurrentLength) );
+
+ //
+ // Calculate how much the file record is changing by, and its new
+ // size. We also compute the size of the range of zero bytes.
+ //
+
+ if (SetNewLength) {
+
+ NewSize = ValueOffset + ValueLength;
+ SizeChange = NewSize - CurrentLength;
+ QuadSizeChange = QuadAlign( NewSize ) - QuadAlign( CurrentLength );
+
+ //
+ // If the new size is large enough, the size change may appear to be negative.
+ // In this case we go directly to the non-resident path.
+ //
+
+ if (NewSize > Vcb->BytesPerFileRecordSegment) {
+
+ GoToNonResident = TRUE;
+ }
+
+ } else {
+
+ NewSize = CurrentLength;
+ SizeChange = 0;
+ QuadSizeChange = 0;
+ }
+
+ //
+ // If we are zeroing a range in the file and it extends to the
+ // end of the file or beyond then make this a single zeroed run.
+ //
+
+ if (!ARGUMENT_PRESENT( Value )
+ && ValueOffset >= CurrentLength) {
+
+ ZeroLength = ValueOffset + ValueLength - CurrentLength;
+
+ ValueOffset = ValueOffset + ValueLength;
+ ValueLength = 0;
+
+ //
+ // If we are writing data starting beyond the end of the
+ // file then we have a range of bytes to zero.
+ //
+
+ } else if (ValueOffset > CurrentLength) {
+
+ ZeroLength = ValueOffset - CurrentLength;
+ }
+
+ //
+ // At this point we know the following ranges:
+ //
+ // Range to save: Not needed unless going resident to non-resident
+ //
+ // Zero range: From Zero offset for length ZeroLength
+ //
+ // Modified range: From ValueOffset to NewSize, this length may
+ // be zero.
+ //
+
+ //
+ // If the attribute is resident, and it will stay resident, then we will
+ // handle that case first, and return.
+ //
+
+ if (NtfsIsAttributeResident( Attribute )
+
+ &&
+
+ !GoToNonResident
+
+ &&
+
+ ((QuadSizeChange <= (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte))
+ || ((Attribute->RecordLength + SizeChange) < Vcb->BigEnoughToMove))) {
+
+ PVOID UndoBuffer;
+ ULONG UndoLength;
+ ULONG AttributeOffset;
+
+ //
+ // If the attribute record is growing, then we have to get the new space
+ // now.
+ //
+
+ if (QuadSizeChange > 0) {
+
+ BOOLEAN FirstPass = TRUE;
+
+ ASSERT( !FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED) );
+
+ //
+ // Save a description of the attribute in case we have to look it up
+ // again.
+ //
+
+ SavedName.Length =
+ SavedName.MaximumLength = (USHORT)(Attribute->NameLength * sizeof(WCHAR));
+
+ if (SavedName.Length > sizeof(NameBuffer)) {
+
+ SavedName.Buffer = NtfsAllocatePool( NonPagedPool, SavedName.Length );
+ }
+
+ //
+ // Copy the name into the buffer.
+ //
+
+ if (SavedName.Length != 0) {
+
+ RtlCopyMemory( SavedName.Buffer,
+ Add2Ptr( Attribute, Attribute->NameOffset ),
+ SavedName.Length );
+ }
+
+ //
+ // Make sure we deallocate the name buffer.
+ //
+
+ try {
+
+ do {
+
+ //
+ // If not the first pass, we have to lookup the attribute
+ // again.
+ //
+
+ if (!FirstPass) {
+
+ BOOLEAN Found;
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ TypeCode,
+ &SavedName,
+ NULL,
+ FALSE,
+ Context );
+
+ ASSERT(Found);
+
+ //
+ // Now we have to reload our attribute pointer
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ }
+
+ FirstPass = FALSE;
+
+ //
+ // If FALSE is returned, then the space was not allocated and
+ // we have too loop back and try again. Second time must work.
+ //
+
+ } while (!NtfsChangeAttributeSize( IrpContext,
+ Fcb,
+ QuadAlign( Attribute->Form.Resident.ValueOffset + NewSize),
+ Context ));
+ } finally {
+
+ if (SavedName.Buffer != NameBuffer) {
+
+ NtfsFreePool(SavedName.Buffer);
+ }
+ }
+
+ //
+ // Now we have to reload our attribute pointer
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ Attribute = NtfsFoundAttribute(Context);
+
+ } else {
+
+ //
+ // Make sure the buffer is pinned if we are not changing size, because
+ // we begin to modify it below.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // We can eliminate some/all of the value if it has not changed.
+ //
+
+ if (ARGUMENT_PRESENT(Value)) {
+
+ UnchangedSize = RtlCompareMemory( Add2Ptr(Attribute,
+ Attribute->Form.Resident.ValueOffset +
+ ValueOffset),
+ Value,
+ ValueLength );
+
+ Value = Add2Ptr(Value, UnchangedSize);
+ ValueOffset += UnchangedSize;
+ ValueLength -= UnchangedSize;
+ }
+ }
+
+ RecordOffset = PtrOffset(FileRecord, Attribute);
+
+ //
+ // If there is a zero range of bytes, deal with it now.
+ // If we are zeroing data then we must be growing the
+ // file.
+ //
+
+ if (ZeroLength != 0) {
+
+ //
+ // We always start zeroing at the zeroing offset.
+ //
+
+ AttributeOffset = Attribute->Form.Resident.ValueOffset +
+ CurrentLength;
+
+ //
+ // If we are starting at the end of the file the undo
+ // buffer is NULL and the length is zero.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ UpdateResidentValue,
+ NULL,
+ ZeroLength,
+ UpdateResidentValue,
+ NULL,
+ 0,
+ NtfsMftOffset( Context ),
+ RecordOffset,
+ AttributeOffset,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Now zero this data by calling the same routine as restart.
+ //
+
+ NtfsRestartChangeValue( IrpContext,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ NULL,
+ ZeroLength,
+ TRUE );
+ }
+
+ //
+ // Now log the new data for the file. This range will always begin
+ // within the current range of bytes for the file. Because of this
+ // there is an undo action.
+ //
+ // Even if there is not a nonzero ValueLength, we still have to
+ // execute this code if the attribute is being truncated.
+ // The only exception is if we logged some zero data and have
+ // nothing left to log.
+ //
+
+ if ((ValueLength != 0)
+ || (ZeroLength == 0
+ && SizeChange != 0)) {
+
+ //
+ // The attribute offset is always at the value offset.
+ //
+
+ AttributeOffset = Attribute->Form.Resident.ValueOffset + ValueOffset;
+
+ //
+ // There are 3 possible cases for the undo action to
+ // log.
+ //
+
+ //
+ // If we are growing the file starting beyond the end of
+ // the file then undo buffer is NULL and the length is
+ // zero. This will still allow us to shrink the file
+ // on abort.
+ //
+
+ if (ValueOffset >= CurrentLength) {
+
+ UndoBuffer = NULL;
+ UndoLength = 0;
+
+ //
+ // For the other cases the undo buffer begins at the
+ // point of the change.
+ //
+
+ } else {
+
+ UndoBuffer = Add2Ptr( Attribute,
+ Attribute->Form.Resident.ValueOffset + ValueOffset );
+
+ //
+ // If the size isn't changing then the undo length is the same as
+ // the redo length.
+ //
+
+ if (SizeChange == 0) {
+
+ UndoLength = ValueLength;
+
+ //
+ // Otherwise the length is the range between the end of the
+ // file and the start of the new data.
+ //
+
+ } else {
+
+ UndoLength = CurrentLength - ValueOffset;
+ }
+ }
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ UpdateResidentValue,
+ Value,
+ ValueLength,
+ UpdateResidentValue,
+ UndoBuffer,
+ UndoLength,
+ NtfsMftOffset( Context ),
+ RecordOffset,
+ AttributeOffset,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Now update this data by calling the same routine as restart.
+ //
+
+ NtfsRestartChangeValue( IrpContext,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ Value,
+ ValueLength,
+ (BOOLEAN)(SizeChange != 0) );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsChangeAttributeValue -> VOID\n") );
+
+ return;
+ }
+
+ //
+ // Nonresident case. Create the Scb and attributestream.
+ //
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+ AttributeTypeCode = Attribute->TypeCode;
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &AttributeName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ //
+ // Use try-finally for cleanup.
+ //
+
+ try {
+
+ BOOLEAN AllocateBufferCopy = FALSE;
+ BOOLEAN DeleteAllocation = FALSE;
+ BOOLEAN LookupAttribute = FALSE;
+
+ BOOLEAN AdvanceValidData = FALSE;
+ LONGLONG NewValidDataLength;
+ LONGLONG LargeValueOffset;
+
+ LONGLONG LargeNewSize;
+
+ if (SetNewLength
+ && NewSize > Scb->Header.FileSize.LowPart
+ && TypeCode == $ATTRIBUTE_LIST) {
+
+ AllocateBufferCopy = TRUE;
+ }
+
+ LargeNewSize = NewSize;
+
+ LargeValueOffset = ValueOffset;
+
+ //
+ // Well, the attribute is either changing to nonresident, or it is already
+ // nonresident. First we will handle the conversion to nonresident case.
+ // We can detect this case by whether or not the attribute is currently
+ // resident.
+ //
+
+ if (NtfsIsAttributeResident(Attribute)) {
+
+ NtfsConvertToNonresident( IrpContext,
+ Fcb,
+ Attribute,
+ CreateSectionUnderway,
+ Context );
+
+ //
+ // Reload the attribute pointer from the context.
+ //
+
+ Attribute = NtfsFoundAttribute( Context );
+
+ //
+ // The process of creating a non resident attribute will also create
+ // and initialize a stream file for the Scb. If the file is already
+ // non-resident we also need a stream file.
+ //
+
+ } else {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+
+ }
+
+ //
+ // If the attribute is already nonresident, make sure the allocation
+ // is the right size. We grow it before we log the data to be sure
+ // we have the space for the new data. We shrink it after we log the
+ // new data so we have the old data available for the undo.
+ //
+
+ if (((PLARGE_INTEGER)&Attribute->Form.Nonresident.AllocatedLength)->HighPart != 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ if (NewSize > ((ULONG)Attribute->Form.Nonresident.AllocatedLength)) {
+
+ LONGLONG NewAllocation;
+
+ if (PreserveContext) {
+
+ //
+ // Save a description of the attribute in case we have to look it up
+ // again.
+ //
+
+ SavedName.Length =
+ SavedName.MaximumLength = (USHORT)(Attribute->NameLength * sizeof(WCHAR));
+
+ if (SavedName.Length > sizeof(NameBuffer)) {
+
+ SavedName.Buffer = NtfsAllocatePool( NonPagedPool, SavedName.Length );
+ }
+
+ //
+ // Copy the name into the buffer.
+ //
+
+ if (SavedName.Length != 0) {
+
+ RtlCopyMemory( SavedName.Buffer,
+ Add2Ptr( Attribute, Attribute->NameOffset ),
+ SavedName.Length );
+ }
+
+ LookupAttribute = TRUE;
+ }
+
+ //
+ // If this is the attribute list then check if we want to allocate a larger block.
+ // This way the attribute list doesn't get too fragmented.
+ //
+
+ NewAllocation = NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength);
+
+ if (Scb->AttributeTypeCode == $ATTRIBUTE_LIST) {
+
+ if ((ULONG) Attribute->Form.Nonresident.AllocatedLength > (4 * PAGE_SIZE)) {
+
+ NewAllocation = (2 * PAGE_SIZE) + NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength);
+
+ } else if ((ULONG) Attribute->Form.Nonresident.AllocatedLength > PAGE_SIZE) {
+
+ NewAllocation = PAGE_SIZE + NewSize - ((ULONG)Attribute->Form.Nonresident.AllocatedLength);
+ }
+ }
+
+ NtfsAddAllocation( IrpContext,
+ Scb->FileObject,
+ Scb,
+ LlClustersFromBytes( Vcb, Attribute->Form.Nonresident.AllocatedLength ),
+ LlClustersFromBytes( Vcb, NewAllocation ),
+ FALSE);
+
+ //
+ // AddAllocation will adjust the sizes in the Scb and report
+ // the new size to the cache manager. We need to remember if
+ // we changed the sizes for the unnamed data attribute.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ } else if (Vcb->BytesPerCluster <=
+ ((ULONG)Attribute->Form.Nonresident.AllocatedLength) - NewSize) {
+
+ if ((Scb->AttributeTypeCode != $ATTRIBUTE_LIST) ||
+ (NewSize * 2 < ((ULONG) Attribute->Form.Nonresident.AllocatedLength))) {
+
+ DeleteAllocation = TRUE;
+ }
+ }
+
+ //
+ // Now, write in the data.
+ //
+
+ if ((ValueLength != 0
+ && ARGUMENT_PRESENT( Value ))
+
+ || (LogNonresidentToo
+ && SetNewLength)) {
+
+ BOOLEAN BytesToUndo;
+
+ //
+ // We have to compute the amount of data to zero in a different
+ // way than we did for the resident case. For the non-resident
+ // case we need to zero the data between the old valid data
+ // length and the offset in the file for the new data.
+ //
+
+ if (LargeValueOffset >= Scb->Header.ValidDataLength.QuadPart) {
+
+ ZeroLength = (ULONG)(LargeValueOffset - Scb->Header.ValidDataLength.QuadPart);
+ BytesToUndo = FALSE;
+
+ } else {
+
+ ZeroLength = 0;
+ BytesToUndo = TRUE;
+ }
+
+ //
+ // Update existing nonresident attribute. (We may have just created it
+ // above.)
+ //
+ // If we are supposed to log it, then pin, log and do the update here.
+ //
+
+ if (LogNonresidentToo) {
+
+ //
+ // At this point the attribute is non-resident and contains
+ // its previous value. If the new data lies beyond the
+ // previous valid data, we need to zero this data. This
+ // action won't require any undo. Otherwise the new data
+ // lies within the existing data. In this case we need to
+ // log the previous data for possible undo. Finally, the
+ // tail of the new data may extend beyond the end of the
+ // previous data. There is no undo requirement for these
+ // bytes.
+ //
+ // We do the logging operation in three steps:
+ //
+ // 1 - We find the all the pages in the attribute that
+ // we need to zero any bytes for. There is no
+ // undo for these bytes.
+ //
+ // 2 - Find all pages where we have to perform undo and
+ // log the changes to those pages. Note only
+ // step 1 or step 2 will be performed as they
+ // are mutually exclusive.
+ //
+ // 3 - Finally, we may have pages where the new data
+ // extends beyond the current final page in the
+ // attribute. We log the new data but there is
+ // no undo.
+ //
+ // 4 - We may have pages where the old data extends
+ // beyond the new data. We will log this old
+ // data in the event that we grow and shrink
+ // this attribute several times in the same
+ // transaction (changes to the attribute list).
+ // In this case there is redo but no undo.
+ //
+
+ LONGLONG CurrentPage;
+ ULONG PageOffset;
+
+ ULONG ByteCountToUndo;
+ ULONG NewBytesRemaining;
+
+ //
+ // Find the starting page for this operation. It is the
+ // ValidDataLength rounded down to a page boundary.
+ //
+
+ CurrentPage = Scb->Header.ValidDataLength.QuadPart;
+ PageOffset = (ULONG)CurrentPage & (PAGE_SIZE - 1);
+
+ (ULONG)CurrentPage = ((ULONG)CurrentPage & ~(PAGE_SIZE - 1));
+
+ //
+ // Loop until there are no more bytes to zero.
+ //
+
+ while (ZeroLength != 0) {
+
+ ULONG ZeroBytesThisPage;
+
+ ZeroBytesThisPage = PAGE_SIZE - PageOffset;
+
+ if (ZeroBytesThisPage > ZeroLength) {
+
+ ZeroBytesThisPage = ZeroLength;
+ }
+
+ //
+ // Pin the desired page and compute a buffer into the
+ // page. Also compute how many bytes we we zero on
+ // this page.
+ //
+
+ NtfsUnpinBcb( &Bcb );
+
+ NtfsPinStream( IrpContext,
+ Scb,
+ CurrentPage,
+ ZeroBytesThisPage + PageOffset,
+ &Bcb,
+ &Buffer );
+
+ Buffer = Add2Ptr( Buffer, PageOffset );
+
+ //
+ // Now write the zeros into the log.
+ //
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Bcb,
+ UpdateNonresidentValue,
+ NULL,
+ ZeroBytesThisPage,
+ Noop,
+ NULL,
+ 0,
+ CurrentPage,
+ PageOffset,
+ 0,
+ ZeroBytesThisPage + PageOffset );
+
+ //
+ // Zero any data necessary.
+ //
+
+ RtlZeroMemory( Buffer, ZeroBytesThisPage );
+
+ //
+ // Now move through the file.
+ //
+
+ ZeroLength -= ZeroBytesThisPage;
+
+ CurrentPage = CurrentPage + PAGE_SIZE;
+ PageOffset = 0;
+ }
+
+ //
+ // Find the starting page for this operation. It is the
+ // ValueOffset rounded down to a page boundary.
+ //
+
+ CurrentPage = LargeValueOffset;
+ (ULONG)CurrentPage = ((ULONG)CurrentPage & ~(PAGE_SIZE - 1));
+
+ PageOffset = (ULONG)LargeValueOffset & (PAGE_SIZE - 1);
+
+ //
+ // Now loop until there are no more pages with undo
+ // bytes to log.
+ //
+
+ NewBytesRemaining = ValueLength;
+
+ if (BytesToUndo) {
+
+ ByteCountToUndo = (ULONG)(Scb->Header.ValidDataLength.QuadPart - LargeValueOffset);
+
+ //
+ // If we are spanning pages, growing the file and the
+ // input buffer points into the cache, we could lose
+ // data as we cross a page boundary. In that case
+ // we need to allocate a separate buffer.
+ //
+
+ if (AllocateBufferCopy
+ && NewBytesRemaining + PageOffset > PAGE_SIZE) {
+
+ CopyInputBuffer = NtfsAllocatePool(PagedPool, NewBytesRemaining );
+ RtlCopyMemory( CopyInputBuffer,
+ Value,
+ NewBytesRemaining );
+
+ Value = CopyInputBuffer;
+
+ AllocateBufferCopy = FALSE;
+ }
+
+ //
+ // If we aren't setting a new length then limit the
+ // undo bytes to those being overwritten.
+ //
+
+ if (!SetNewLength
+ && ByteCountToUndo > NewBytesRemaining) {
+
+ ByteCountToUndo = NewBytesRemaining;
+ }
+
+ while (ByteCountToUndo != 0) {
+
+ ULONG UndoBytesThisPage;
+ ULONG RedoBytesThisPage;
+ ULONG BytesThisPage;
+
+ NTFS_LOG_OPERATION RedoOperation;
+ PVOID RedoBuffer;
+
+ //
+ // Also compute the number of bytes of undo and
+ // redo on this page.
+ //
+
+ RedoBytesThisPage = UndoBytesThisPage = PAGE_SIZE - PageOffset;
+
+ if (RedoBytesThisPage > NewBytesRemaining) {
+
+ RedoBytesThisPage = NewBytesRemaining;
+ }
+
+ if (UndoBytesThisPage >= ByteCountToUndo) {
+
+ UndoBytesThisPage = ByteCountToUndo;
+ }
+
+ //
+ // We pin enough bytes on this page to cover both the
+ // redo and undo bytes.
+ //
+
+ if (UndoBytesThisPage > RedoBytesThisPage) {
+
+ BytesThisPage = PageOffset + UndoBytesThisPage;
+
+ } else {
+
+ BytesThisPage = PageOffset + RedoBytesThisPage;
+ }
+
+ //
+ // If there is no redo (we are shrinking the data),
+ // then make the redo a noop.
+ //
+
+ if (RedoBytesThisPage == 0) {
+
+ RedoOperation = Noop;
+ RedoBuffer = NULL;
+
+ } else {
+
+ RedoOperation = UpdateNonresidentValue;
+ RedoBuffer = Value;
+ }
+
+ //
+ // Now we pin the page and calculate the beginning
+ // buffer in the page.
+ //
+
+ NtfsUnpinBcb( &Bcb );
+
+ NtfsPinStream( IrpContext,
+ Scb,
+ CurrentPage,
+ BytesThisPage,
+ &Bcb,
+ &Buffer );
+
+ Buffer = Add2Ptr( Buffer, PageOffset );
+
+ //
+ // Now log the changes to this page.
+ //
+
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Bcb,
+ RedoOperation,
+ RedoBuffer,
+ RedoBytesThisPage,
+ UpdateNonresidentValue,
+ Buffer,
+ UndoBytesThisPage,
+ CurrentPage,
+ PageOffset,
+ 0,
+ BytesThisPage );
+
+ //
+ // Move the data into place if we have new data.
+ //
+
+ if (RedoBytesThisPage != 0) {
+
+ RtlMoveMemory( Buffer, Value, RedoBytesThisPage );
+ }
+
+ //
+ // Now decrement the counts and move through the
+ // caller's buffer.
+ //
+
+ ByteCountToUndo -= UndoBytesThisPage;
+ NewBytesRemaining -= RedoBytesThisPage;
+
+ CurrentPage = PAGE_SIZE + CurrentPage;
+ PageOffset = 0;
+
+ Value = Add2Ptr( Value, RedoBytesThisPage );
+ }
+ }
+
+ //
+ // Now loop until there are no more pages with new data
+ // to log.
+ //
+
+ while (NewBytesRemaining != 0) {
+
+ ULONG RedoBytesThisPage;
+
+ //
+ // Also compute the number of bytes of redo on this page.
+ //
+
+ RedoBytesThisPage = PAGE_SIZE - PageOffset;
+
+ if (RedoBytesThisPage > NewBytesRemaining) {
+
+ RedoBytesThisPage = NewBytesRemaining;
+ }
+
+ //
+ // Now we pin the page and calculate the beginning
+ // buffer in the page.
+ //
+
+ NtfsUnpinBcb( &Bcb );
+
+ NtfsPinStream( IrpContext,
+ Scb,
+ CurrentPage,
+ RedoBytesThisPage,
+ &Bcb,
+ &Buffer );
+
+ Buffer = Add2Ptr( Buffer, PageOffset );
+
+ //
+ // Now log the changes to this page.
+ //
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Bcb,
+ UpdateNonresidentValue,
+ Value,
+ RedoBytesThisPage,
+ Noop,
+ NULL,
+ 0,
+ CurrentPage,
+ PageOffset,
+ 0,
+ PageOffset + RedoBytesThisPage );
+
+ //
+ // Move the data into place.
+ //
+
+ RtlMoveMemory( Buffer, Value, RedoBytesThisPage );
+
+ //
+ // Now decrement the counts and move through the
+ // caller's buffer.
+ //
+
+ NewBytesRemaining -= RedoBytesThisPage;
+
+ CurrentPage = PAGE_SIZE + CurrentPage;
+ PageOffset = 0;
+
+ Value = Add2Ptr( Value, RedoBytesThisPage );
+ }
+
+ //
+ // If we have values to write, we write them to the cache now.
+ //
+
+ } else {
+
+ //
+ // If we have data to zero, we do no now.
+ //
+
+ if (ZeroLength != 0) {
+
+ if (!NtfsZeroData( IrpContext,
+ Scb,
+ Scb->FileObject,
+ Scb->Header.ValidDataLength.QuadPart,
+ (LONGLONG)ZeroLength )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+
+ }
+ }
+
+ if (!CcCopyWrite( Scb->FileObject,
+ (PLARGE_INTEGER)&LargeValueOffset,
+ ValueLength,
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
+ Value )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+
+ //
+ // We need to remember the new valid data length in the
+ // Scb if it is greater than the existing.
+ //
+
+ NewValidDataLength = LargeValueOffset + ValueLength;
+
+ if (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) {
+
+ Scb->Header.ValidDataLength.QuadPart = NewValidDataLength;
+
+ //
+ // If we took the log non-resident path, then we
+ // want to advance this on the disk as well.
+ //
+
+ if (LogNonresidentToo) {
+
+ AdvanceValidData = TRUE;
+ }
+
+ SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ //
+ // We need to maintain the file size in the Scb. If we grow the
+ // file, we extend the cache file size. We always set the
+ // valid data length in the Scb to the new file size. The
+ // 'AdvanceValidData' boolean and the current size on the
+ // disk will determine if it changes on disk.
+ //
+
+ if (SetNewLength) {
+
+ Scb->Header.ValidDataLength.QuadPart = NewValidDataLength;
+ }
+ }
+
+ if (SetNewLength) {
+
+ Scb->Header.FileSize.QuadPart = LargeNewSize;
+
+ if (LogNonresidentToo) {
+ Scb->Header.ValidDataLength.QuadPart = LargeNewSize;
+ }
+ }
+
+ if (Scb->Header.ValidDataLength.QuadPart < Scb->ValidDataToDisk) {
+
+ Scb->ValidDataToDisk = Scb->Header.ValidDataLength.QuadPart;
+ }
+
+ //
+ // If there is allocation to delete, we do so now.
+ //
+
+ if (DeleteAllocation ) {
+
+ NtfsDeleteAllocation( IrpContext,
+ Scb->FileObject,
+ Scb,
+ LlClustersFromBytes( Vcb, LargeNewSize ),
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+
+ //
+ // DeleteAllocation will adjust the sizes in the Scb and report
+ // the new size to the cache manager. We need to remember if
+ // we changed the sizes for the unnamed data attribute.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+
+ SetFlag( Fcb->InfoFlags,
+ (FCB_INFO_CHANGED_ALLOC_SIZE | FCB_INFO_CHANGED_FILE_SIZE) );
+ }
+
+ if (AdvanceValidData) {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+ }
+
+ } else if (SetNewLength) {
+
+ PFILE_OBJECT CacheFileObject = NULL;
+
+ //
+ // If there is no file object, we will create a stream file
+ // now,
+ //
+
+ if (Scb->FileObject != NULL) {
+
+ CacheFileObject = Scb->FileObject;
+
+ } else if (!CreateSectionUnderway) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ CacheFileObject = Scb->FileObject;
+
+ } else {
+
+ PIO_STACK_LOCATION IrpSp;
+
+ IrpSp = IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp );
+
+ if (IrpSp->FileObject->SectionObjectPointer == &Scb->NonpagedScb->SegmentObject) {
+
+ CacheFileObject = IrpSp->FileObject;
+ }
+ }
+
+ ASSERT( CacheFileObject != NULL );
+
+ CcSetFileSizes( CacheFileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ //
+ // If this is the unnamed data attribute, we need to mark this
+ // change in the Fcb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
+ }
+
+ //
+ // Now update the sizes on the disk.
+ // The new sizes will already be in the Scb.
+ //
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ AdvanceValidData,
+ TRUE );
+
+ } else if (AdvanceValidData) {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // Look up the attribute again in case it moved.
+ //
+
+ if (LookupAttribute) {
+
+ BOOLEAN Found;
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ TypeCode,
+ &SavedName,
+ NULL,
+ FALSE,
+ Context );
+
+ ASSERT(Found);
+
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsChangeAttributeValue );
+
+ if (CopyInputBuffer != NULL) {
+
+ NtfsFreePool( CopyInputBuffer );
+ }
+
+ if (SaveBuffer != NULL) {
+
+ NtfsFreePool( SaveBuffer );
+ }
+
+ NtfsUnpinBcb( &Bcb );
+
+ DebugTrace( -1, Dbg, ("NtfsChangeAttributeValue -> VOID\n") );
+ }
+}
+
+
+VOID
+NtfsConvertToNonresident (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PATTRIBUTE_RECORD_HEADER Attribute,
+ IN BOOLEAN CreateSectionUnderway,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine converts a resident attribute to nonresident. It does so
+ by allocating a buffer and copying the data and attribute name away,
+ deleting the attribute, allocating a new attribute of the right size,
+ and then copying the data back out again.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ Attribute - Supplies a pointer to the attribute to convert.
+
+ CreateSectionUnderway - if supplied as TRUE, then to the best of the caller's
+ knowledge, an MM Create Section could be underway,
+ which means that we cannot initiate caching on
+ this attribute, as that could cause deadlock. The
+ value buffer in this case must be quad-aligned and
+ a multiple of cluster size in size.
+
+ Context - An attribute context to look up another attribute in the same
+ file record. If supplied, we insure that the context is valid
+ for converted attribute.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVOID Buffer;
+ PVOID AllocatedBuffer = NULL;
+ ULONG AllocatedLength;
+ ULONG AttributeNameOffset;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ BOOLEAN CleanupLocalContext = FALSE;
+
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode = Attribute->TypeCode;
+ USHORT AttributeFlags = Attribute->Flags;
+ PVOID AttributeValue = NULL;
+ ULONG ValueLength;
+
+ UNICODE_STRING AttributeName;
+ WCHAR AttributeNameBuffer[16];
+
+ BOOLEAN WriteClusters = CreateSectionUnderway;
+
+ PBCB ResidentBcb = NULL;
+ PSCB Scb = NULL;
+
+#ifdef _CAIRO_
+ ULONG IrpContextFlags = FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+#endif // _CAIRO_
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Build a temporary copy of the name out of the attribute.
+ //
+
+ AttributeName.MaximumLength =
+ AttributeName.Length = Attribute->NameLength * sizeof( WCHAR );
+ AttributeName.Buffer = Add2Ptr( Attribute, Attribute->NameOffset );
+
+ //
+ // If we don't have an attribute context for this attribute then look it
+ // up now.
+ //
+
+ if (!ARGUMENT_PRESENT( Context )) {
+
+ Context = &LocalContext;
+ NtfsInitializeAttributeContext( Context );
+ CleanupLocalContext = TRUE;
+
+ //
+ // Lookup the first occurence of this attribute.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ &AttributeName,
+ NULL,
+ FALSE,
+ Context )) {
+
+ DebugTrace( 0, 0, ("Could not find attribute being converted\n") );
+
+ ASSERTMSG("Could not find attribute being converted, About to bugcheck ", FALSE);
+ NtfsBugCheck( AttributeTypeCode, 0, 0 );
+ }
+ }
+
+ //
+ // We need to figure out how much pool to allocate. If there is a mapped
+ // view of this section or a section is being created we will allocate a buffer
+ // and copy the data into the buffer. Otherwise we will pin the data in
+ // the cache, mark it dirty and use that buffer to perform the conversion.
+ //
+
+ AllocatedLength = AttributeName.Length;
+
+ if (CreateSectionUnderway) {
+
+#ifdef _CAIRO_
+
+ //
+ // CAIROBUG: The following should be combined with the
+ // code below when the cairo ifdefs are removed.
+ //
+
+ BOOLEAN ReturnedExistingScb;
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &AttributeName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ //
+ // If the quota has been expanded already then the rigth
+ // things will happen below. If the quota has not been
+ // expanded then no quota changes are required since
+ // file size is not changing.
+ //
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)) {
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+ }
+
+ Scb = NULL;
+
+#endif // _CAIRO_
+
+ AttributeNameOffset = ClusterAlign( Fcb->Vcb,
+ Attribute->Form.Resident.ValueLength );
+ AllocatedLength += AttributeNameOffset;
+ ValueLength = Attribute->Form.Resident.ValueLength;
+
+ } else {
+
+ BOOLEAN ReturnedExistingScb;
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &AttributeName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ //
+ // Make sure the Scb is up-to-date.
+ //
+
+ NtfsUpdateScbFromAttribute( IrpContext,
+ Scb,
+ Attribute );
+
+#ifdef _CAIRO_
+
+ //
+ // If the quota has been expanded already then the rigth
+ // things will happen below. If the quota has not been
+ // expanded then no quota changes are required since
+ // file size is not changing.
+ //
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)) {
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+ }
+
+#endif // _CAIRO_
+
+ //
+ // Set the flag in the Scb to indicate that we are converting this to
+ // non resident.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_CONVERT_UNDERWAY );
+
+ //
+ // Now check if the file is mapped by a user.
+ //
+
+ if (!MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, NULL )) {
+
+ AttributeNameOffset = ClusterAlign( Fcb->Vcb,
+ Attribute->Form.Resident.ValueLength );
+ AllocatedLength += AttributeNameOffset;
+ ValueLength = Attribute->Form.Resident.ValueLength;
+ Scb = NULL;
+ WriteClusters = TRUE;
+
+ } else {
+
+ volatile UCHAR VolatileUchar;
+
+ AttributeNameOffset = 0;
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+
+ //
+ // Make sure the cache is up-to-date.
+ //
+
+ CcSetFileSizes( Scb->FileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ ValueLength = Scb->Header.ValidDataLength.LowPart;
+
+ if (ValueLength != 0) {
+
+ NtfsPinStream( IrpContext,
+ Scb,
+ (LONGLONG)0,
+ ValueLength,
+ &ResidentBcb,
+ &AttributeValue );
+
+ //
+ // Close the window where this page can leave memory before we
+ // have the new attribute initialized. The result will be that
+ // we may fault in this page again and read uninitialized data
+ // out of the newly allocated sectors.
+ //
+ // Make the page dirty so that the cache manager will write it out
+ // and update the valid data length.
+ //
+
+ VolatileUchar = *((PUCHAR) AttributeValue);
+
+ *((PUCHAR) AttributeValue) = VolatileUchar;
+ }
+ }
+ }
+
+ if (AllocatedLength > 8) {
+
+ Buffer = AllocatedBuffer = NtfsAllocatePool(PagedPool, AllocatedLength );
+
+ } else {
+
+ Buffer = &AttributeNameBuffer;
+ }
+
+ //
+ // Now update the attribute name in the buffer.
+ //
+
+ AttributeName.Buffer = Add2Ptr( Buffer, AttributeNameOffset );
+
+ RtlCopyMemory( AttributeName.Buffer,
+ Add2Ptr( Attribute, Attribute->NameOffset ),
+ AttributeName.Length );
+
+ //
+ // If we are going to write the clusters directly to the disk then copy
+ // the bytes into the buffer.
+ //
+
+ if (WriteClusters) {
+
+ AttributeValue = Buffer;
+
+ RtlCopyMemory( AttributeValue, NtfsAttributeValue( Attribute ), ValueLength );
+ }
+
+ //
+ // Now just delete the current record and create it nonresident.
+ // Create nonresident with attribute does the right thing if we
+ // are being called by MM.
+ //
+
+ NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, TRUE, Context );
+
+ NtfsCreateNonresidentWithValue( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &AttributeName,
+ AttributeValue,
+ ValueLength,
+ AttributeFlags,
+ WriteClusters,
+ Scb,
+ TRUE,
+ Context );
+
+ //
+ // If we were passed an attribute context, then we want to
+ // reload the context with the new location of the file.
+ //
+
+ if (!CleanupLocalContext) {
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ &AttributeName,
+ NULL,
+ FALSE,
+ Context )) {
+
+ DebugTrace( 0, 0, ("Could not find attribute being converted\n") );
+
+ ASSERTMSG("Could not find attribute being converted, About to raise corrupt ", FALSE);
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsConvertToNonresident );
+
+ if (AllocatedBuffer != NULL) {
+
+ NtfsFreePool( AllocatedBuffer );
+ }
+
+ if (CleanupLocalContext) {
+
+ NtfsCleanupAttributeContext( Context );
+ }
+
+ NtfsUnpinBcb( &ResidentBcb );
+
+#ifdef _CAIRO_
+
+ //
+ // Restore the value of the quota disable flag.
+ //
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+ SetFlag( IrpContext->Flags, IrpContextFlags );
+
+#endif // _CAIRO_
+
+ }
+}
+
+
+VOID
+NtfsDeleteAttributeRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN PreserveFileRecord,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes an existing attribute removing it from the file record.
+
+ The caller specifies the attribute to be deleted via the attribute context,
+ and must be prepared to clean up this context no matter how this routine
+ returns.
+
+ Note that currently this routine does not deallocate any clusters allocated
+ to a nonresident attribute; it expects the caller to already have done so.
+
+Arguments:
+
+ Fcb - Current file.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are deleting an entire file record, and
+ will be logging that.
+
+ PreserveFileRecord - Indicates that we should save the file record because we
+ will be re-inserting an attribute. Will only be TRUE for the convert to
+ non-resident case.
+
+ Context - Attribute Context positioned at the attribute to delete.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PVCB Vcb;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteAttribute\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("Context =%08lx\n", Context) );
+
+ //
+ // Get the pointers we need.
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ AttributeTypeCode = Attribute->TypeCode;
+ FileRecord = NtfsContainingFileRecord(Context);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+
+ if ((Attribute->FormCode == NONRESIDENT_FORM) &&
+ !Context->FoundAttribute.AttributeAllocationDeleted) {
+
+ NtfsDeleteAllocationFromRecord( IrpContext, Fcb, Context, TRUE );
+
+ //
+ // Reload our local pointers.
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ FileRecord = NtfsContainingFileRecord(Context);
+ }
+
+#ifdef _CAIRO_
+
+ //
+ // If this is a resident stream then release the quota. Quota for
+ // non-resident streams is handled by NtfsDeleteAllocaiton.
+ //
+
+ if ((Attribute->FormCode == RESIDENT_FORM) &&
+ NtfsIsTypeCodeSubjectToQuota( Attribute->TypeCode )) {
+
+ LONGLONG Delta = -NtfsResidentStreamQuota( Vcb );
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ Fcb,
+ &Delta,
+ LogIt,
+ FALSE );
+ }
+
+#endif //_CAIRO
+
+ //
+ // Be sure the attribute is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Log the change.
+ //
+
+ if (LogIt) {
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ DeleteAttribute,
+ NULL,
+ 0,
+ CreateAttribute,
+ Attribute,
+ Attribute->RecordLength,
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+ }
+
+ NtfsRestartRemoveAttribute( IrpContext,
+ FileRecord,
+ (PCHAR)Attribute - (PCHAR)FileRecord );
+
+ Context->FoundAttribute.AttributeDeleted = TRUE;
+
+ if (LogIt && (Context->AttributeList.Bcb != NULL)) {
+
+ //
+ // Now delete the attribute list entry, if there is one. Do it
+ // after freeing space above, because we assume the list has not moved.
+ // Note we only do this if LogIt is TRUE, assuming that otherwise
+ // the entire file is going away anyway, so there is no need to
+ // fix up the list.
+ //
+
+ NtfsDeleteFromAttributeList( IrpContext, Fcb, Context );
+ }
+
+ //
+ // Delete the file record if it happened to go empty. (Note that
+ // delete file does not call this routine and deletes its own file
+ // records.)
+ //
+
+ if (!PreserveFileRecord &&
+ FileRecord->FirstFreeByte == ((ULONG)FileRecord->FirstAttributeOffset +
+ QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )))) {
+
+ ASSERT( NtfsFullSegmentNumber( &Fcb->FileReference ) ==
+ NtfsUnsafeSegmentNumber( &Fcb->FileReference ) );
+
+ NtfsDeallocateMftRecord( IrpContext,
+ Vcb,
+ (ULONG)Context->FoundAttribute.MftFileOffset >> Vcb->MftShift );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteAttributeRecord -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsDeleteAllocationFromRecord (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN BOOLEAN BreakupAllowed
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete the allocation of an attribute
+ from its attribute record. It does nothing to the attribute record
+ itself - the caller must deal with that.
+
+Arguments:
+
+ Fcb - Current file.
+
+ Context - Attribute enumeration context positioned to the attribute
+ whose allocation is to be deleted.
+
+ BreakupAllowed - TRUE if the caller can tolerate breaking up the deletion of
+ allocation into multiple transactions, if there are a large
+ number of runs.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PSCB Scb;
+ UNICODE_STRING AttributeName;
+ PFILE_OBJECT TempFileObject;
+ BOOLEAN ScbExisted;
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN ReinitializeContext = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Point to the current attribute.
+ //
+
+ Attribute = NtfsFoundAttribute( Context );
+
+ //
+ // If the attribute is nonresident, then delete its allocation.
+ //
+
+ ASSERT(Attribute->FormCode == NONRESIDENT_FORM);
+
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+
+ //
+ // Decode the file object
+ //
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ Attribute->TypeCode,
+ &AttributeName,
+ FALSE,
+ &ScbExisted );
+
+ try {
+
+ //
+ // Acquire the Scb Exclusive
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, Attribute );
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )) {
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+
+ }
+ }
+
+ //
+ // If we created the Scb, then this is the only case where
+ // it is legal for us to omit the File Object in the delete
+ // allocation call, because there cannot possibly be a section.
+ //
+
+ if (!ScbExisted) {
+
+ TempFileObject = NULL;
+
+ //
+ // Else, if there is already a stream file object, we can just
+ // use it.
+ //
+
+ } else if (Scb->FileObject != NULL) {
+
+ TempFileObject = Scb->FileObject;
+
+ //
+ // Else the Scb existed and we did not already have a stream,
+ // so we have to create one and delete it on the way out.
+ //
+
+ } else {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+ TempFileObject = Scb->FileObject;
+ }
+
+ //
+ // Before we make this call, we need to check if we will have to
+ // reread the current attribute. This could be necessary if
+ // we remove any records for this attribute in the delete case.
+ //
+ // We only do this under the following conditions.
+ //
+ // 1 - There is an attribute list present.
+ // 2 - There is an entry following the current entry in
+ // the attribute list.
+ // 3 - The lowest Vcn for that following entry is non-zero.
+ //
+
+ if (Context->AttributeList.Bcb != NULL) {
+
+ PATTRIBUTE_LIST_ENTRY NextEntry;
+
+ NextEntry = (PATTRIBUTE_LIST_ENTRY) NtfsGetNextRecord( Context->AttributeList.Entry );
+
+ if (NextEntry < Context->AttributeList.BeyondFinalEntry) {
+
+ if ( NextEntry->LowestVcn != 0) {
+
+ ReinitializeContext = TRUE;
+ }
+ }
+ }
+
+ NtfsDeleteAllocation( IrpContext,
+ TempFileObject,
+ Scb,
+ *(PVCN)&Li0,
+ MAXLONGLONG,
+ FALSE,
+ BreakupAllowed );
+
+ //
+ // Reread the attribute if we need to.
+ //
+
+ if (ReinitializeContext) {
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, Context );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsDeleteAllocationFromRecord );
+
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+ }
+
+ return;
+}
+
+
+//
+// This routine is intended for use by allocsup.c. Other callers should use
+// the routines in allocsup.
+//
+
+BOOLEAN
+NtfsCreateAttributeWithAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN UseContext,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates the specified attribute with allocation, and returns a
+ description of it via the attribute context. If the amount of space being
+ created is small enough, we do all of the work here. Otherwise we create the
+ initial attribute and call NtfsAddAttributeAllocation to add the rest (in order
+ to keep the more complex logic in one place).
+
+ On successful return, it is up to the caller to clean up the attribute
+ context.
+
+Arguments:
+
+ Scb - Current stream.
+
+ AttributeTypeCode - Type code of the attribute to create.
+
+ AttributeName - Optional name for attribute.
+
+ AttributeFlags - Desired flags for the created attribute.
+
+ WhereIndexed - Optionally supplies the file reference to the file where
+ this attribute is indexed.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are creating a new file record, and
+ will be logging the entire new file record.
+
+ UseContext - Indicates if the context is pointing at the location for the attribute.
+
+ Context - A handle to the created attribute. This context is in a indeterminate
+ state on return.
+
+Return Value:
+
+ BOOLEAN - TRUE if we created the attribute with all the allocation. FALSE
+ otherwise. We should only return FALSE if we are creating a file
+ and don't want to log any of the changes to the file record.
+
+--*/
+
+{
+ UCHAR AttributeBuffer[SIZEOF_FULL_NONRES_ATTR_HEADER];
+ UCHAR MappingPairsBuffer[64];
+ ULONG RecordOffset;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ULONG SizeNeeded;
+ ULONG AttrSizeNeeded;
+ PCHAR MappingPairs;
+ ULONG MappingPairsLength;
+ LCN Lcn;
+ VCN LastVcn;
+ VCN HighestVcn;
+ PVCB Vcb;
+ ULONG Passes = 0;
+ PFCB Fcb = Scb->Fcb;
+ PNTFS_MCB Mcb = &Scb->Mcb;
+ ULONG AttributeHeaderSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+ BOOLEAN AllocateAll = TRUE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ ASSERT( (AttributeFlags == 0) ||
+ NtfsIsTypeCodeCompressible( AttributeTypeCode ));
+
+ Vcb = Fcb->Vcb;
+
+ //
+ // Clear out the invalid attribute flags for this volume.
+ //
+
+ AttributeFlags &= Vcb->AttributeFlagsMask;
+
+ DebugTrace( +1, Dbg, ("NtfsCreateAttributeWithAllocation\n") );
+ DebugTrace( 0, Dbg, ("Mcb = %08lx\n", Mcb) );
+
+ //
+ // Calculate the size needed for this attribute. (We say we have
+ // Vcb->BigEnoughToMove bytes available as a short cut, since we
+ // will extend later as required anyway. It should be extremely
+ // unusual that we would really have to extend.)
+ //
+
+ MappingPairsLength = QuadAlign( NtfsGetSizeForMappingPairs( Mcb,
+ Vcb->BigEnoughToMove,
+ (LONGLONG)0,
+ NULL,
+ &LastVcn ));
+
+ //
+ // Remember the size of the attribute header needed for this file.
+ //
+
+ if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ AttributeHeaderSize = SIZEOF_FULL_NONRES_ATTR_HEADER;
+ }
+
+ SizeNeeded = AttributeHeaderSize +
+ MappingPairsLength +
+ (ARGUMENT_PRESENT(AttributeName) ?
+ QuadAlign( AttributeName->Length ) : 0);
+
+ AttrSizeNeeded = SizeNeeded;
+
+ //
+ // Loop until we find all the space we need.
+ //
+
+ do {
+
+ //
+ // Reinitialize context if this is not the first pass.
+ //
+
+ if (Passes != 0) {
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+ }
+
+ Passes += 1;
+
+ ASSERT( Passes < 5 );
+
+ //
+ // If the attribute is not indexed, then we will position to the
+ // insertion point by type code and name.
+ //
+
+ if (!UseContext &&
+ NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ AttributeName,
+ NULL,
+ FALSE,
+ Context )) {
+
+ DebugTrace( 0, 0,
+ ("Nonresident attribute already exists, TypeCode = %08lx\n",
+ AttributeTypeCode) );
+
+ ASSERTMSG("Nonresident attribute already exists, About to bugcheck ", FALSE);
+ NtfsBugCheck( AttributeTypeCode, 0, 0 );
+ }
+
+ //
+ // If this attribute is being positioned in the base file record and
+ // there is an attribute list then we need to ask for enough space
+ // for the attribute list entry now.
+ //
+
+ FileRecord = NtfsContainingFileRecord( Context );
+ Attribute = NtfsFoundAttribute( Context );
+
+ AttrSizeNeeded = SizeNeeded;
+
+ if (Context->AttributeList.Bcb != NULL
+ && (ULONG) FileRecord <= (ULONG) Context->AttributeList.AttributeList
+ && (ULONG) Attribute >= (ULONG) Context->AttributeList.AttributeList) {
+
+ //
+ // If the attribute list is non-resident then add a fudge factor of
+ // 16 bytes for any new retrieval information.
+ //
+
+ if (NtfsIsAttributeResident( Context->AttributeList.AttributeList )) {
+
+ AttrSizeNeeded += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName )
+ + (ARGUMENT_PRESENT( AttributeName ) ?
+ (ULONG) AttributeName->Length :
+ sizeof( WCHAR )));
+
+ } else {
+
+ AttrSizeNeeded += 0x10;
+ }
+ }
+
+ UseContext = FALSE;
+
+ //
+ // Ask for the space we need.
+ //
+
+ } while (!NtfsGetSpaceForAttribute( IrpContext, Fcb, AttrSizeNeeded, Context ));
+
+ //
+ // Now get the attribute pointer and fill it in.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ RecordOffset = (PCHAR)NtfsFoundAttribute(Context) - (PCHAR)FileRecord;
+ Attribute = (PATTRIBUTE_RECORD_HEADER)AttributeBuffer;
+
+ RtlZeroMemory( Attribute, SIZEOF_FULL_NONRES_ATTR_HEADER );
+
+ Attribute->TypeCode = AttributeTypeCode;
+ Attribute->RecordLength = SizeNeeded;
+ Attribute->FormCode = NONRESIDENT_FORM;
+
+ //
+ // Assume no attribute name, and calculate where the Mapping Pairs
+ // will go. (Update below if we are wrong.)
+ //
+
+ MappingPairs = Add2Ptr( Attribute, AttributeHeaderSize );
+
+ //
+ // If the attribute has a name, take care of that now.
+ //
+
+ if (ARGUMENT_PRESENT(AttributeName)
+ && AttributeName->Length != 0) {
+
+ ASSERT( AttributeName->Length <= 0x1FF );
+
+ Attribute->NameLength = (UCHAR)(AttributeName->Length / sizeof(WCHAR));
+ Attribute->NameOffset = (USHORT)AttributeHeaderSize;
+ MappingPairs += QuadAlign( AttributeName->Length );
+ }
+
+ Attribute->Flags = AttributeFlags;
+ Attribute->Instance = FileRecord->NextAttributeInstance;
+
+ //
+ // We always need the mapping pairs offset.
+ //
+
+ Attribute->Form.Nonresident.MappingPairsOffset = (USHORT)(MappingPairs -
+ (PCHAR)Attribute);
+
+ //
+ // Set up the compression unit size.
+ //
+
+ if (FlagOn(AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+ Attribute->Form.Nonresident.CompressionUnit = NTFS_CLUSTERS_PER_COMPRESSION;
+ }
+
+ //
+ // Now we need to point to the real place to build the mapping pairs buffer.
+ // If they will not be too big we can use our internal buffer.
+ //
+
+ MappingPairs = MappingPairsBuffer;
+
+ if (MappingPairsLength > 64) {
+
+ MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsLength );
+ }
+ *MappingPairs = 0;
+
+ //
+ // Find how much space is allocated by finding the last Mcb entry and
+ // looking it up. If there are no entries, all of the subsequent
+ // fields are already zeroed.
+ //
+
+ Attribute->Form.Nonresident.HighestVcn =
+ HighestVcn = -1;
+ if (NtfsLookupLastNtfsMcbEntry( Mcb, &HighestVcn, &Lcn )) {
+
+ ASSERT_LCN_RANGE_CHECKING( Vcb, Lcn );
+
+ //
+ // Now build the mapping pairs in place.
+ //
+
+ NtfsBuildMappingPairs( Mcb,
+ 0,
+ &LastVcn,
+ MappingPairs );
+ Attribute->Form.Nonresident.HighestVcn = LastVcn;
+
+ //
+ // Fill in the nonresident-specific fields. We set the allocation
+ // size to only include the Vcn's we included in the mapping pairs.
+ //
+
+ Attribute->Form.Nonresident.AllocatedLength =
+ Int64ShllMod32((LastVcn + 1 ), Vcb->ClusterShift);
+
+ //
+ // The totally allocated field in the Scb will contain the current allocated
+ // value for this stream.
+ //
+
+ if (FlagOn( AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ ASSERT( Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA );
+ Attribute->Form.Nonresident.TotalAllocated = Scb->TotalAllocated;
+ }
+
+ //
+ // We are creating a attribute with zero allocation. Make the Vcn sizes match
+ // so we don't make the call below to AddAttributeAllocation.
+ //
+
+ } else {
+
+ LastVcn = HighestVcn;
+ }
+
+ //
+ // Now we will actually create the attribute in place, so that we
+ // save copying everything twice, and can point to the final image
+ // for the log write below.
+ //
+
+ NtfsRestartInsertAttribute( IrpContext,
+ FileRecord,
+ RecordOffset,
+ Attribute,
+ AttributeName,
+ MappingPairs,
+ MappingPairsLength );
+
+ //
+ // Finally, log the creation of this attribute
+ //
+
+ if (LogIt) {
+
+ //
+ // We have actually created the attribute above, but the write
+ // log below could fail. The reason we did the create already
+ // was to avoid having to allocate pool and copy everything
+ // twice (header, name and value). Our normal error recovery
+ // just recovers from the log file. But if we fail to write
+ // the log, we have to remove this attribute by hand, and
+ // raise the condition again.
+ //
+
+ try {
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ CreateAttribute,
+ Add2Ptr(FileRecord, RecordOffset),
+ Attribute->RecordLength,
+ DeleteAttribute,
+ NULL,
+ 0,
+ NtfsMftOffset( Context ),
+ RecordOffset,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NtfsRestartRemoveAttribute( IrpContext, FileRecord, RecordOffset );
+
+ if (MappingPairs != MappingPairsBuffer) {
+
+ NtfsFreePool( MappingPairs );
+ }
+
+ NtfsRaiseStatus( IrpContext, GetExceptionCode(), NULL, NULL );
+ }
+ }
+
+ //
+ // Free the mapping pairs buffer if we allocated one.
+ //
+
+ if (MappingPairs != MappingPairsBuffer) {
+
+ NtfsFreePool( MappingPairs );
+ }
+
+ //
+ // Now add it to the attribute list if necessary
+ //
+
+ if (Context->AttributeList.Bcb != NULL) {
+
+ MFT_SEGMENT_REFERENCE SegmentReference;
+
+ *(PLONGLONG)&SegmentReference = LlFileRecordsFromBytes( Vcb, NtfsMftOffset( Context ));
+ SegmentReference.SequenceNumber = FileRecord->SequenceNumber;
+
+ NtfsAddToAttributeList( IrpContext, Fcb, SegmentReference, Context );
+ }
+
+ //
+ // We couldn't create all of the mapping for the allocation above. If
+ // this is a create then we want to truncate the allocation to what we
+ // have already allocated. Otherwise we want to call
+ // NtfsAddAttributeAllocation to map the remaining allocation.
+ //
+
+ if (LastVcn != HighestVcn) {
+
+ if (LogIt ||
+ !NtfsIsTypeCodeUserData( AttributeTypeCode ) ||
+ IrpContext->MajorFunction != IRP_MJ_CREATE) {
+
+ NtfsAddAttributeAllocation( IrpContext, Scb, Context, NULL, NULL );
+
+ } else {
+
+ //
+ // Truncate away the clusters beyond the last Vcn and set the
+ // flag in the IrpContext indicating there is more allocation
+ // to do.
+ //
+
+ NtfsDeallocateClusters( IrpContext,
+ Fcb->Vcb,
+ &Scb->Mcb,
+ LastVcn + 1,
+ MAXLONGLONG,
+ NULL );
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb,
+ LastVcn + 1,
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+
+#ifdef _CAIRO_
+ if (FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ LONGLONG Delta = LlBytesFromClusters(Fcb->Vcb, LastVcn - HighestVcn);
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode ));
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ //
+ // Return any quota charged.
+ //
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ Fcb,
+ &Delta,
+ LogIt,
+ TRUE );
+ }
+#endif // _CAIRO_
+
+ AllocateAll = FALSE;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateAttributeWithAllocation -> VOID\n") );
+
+ return AllocateAll;
+}
+
+
+//
+// This routine is intended for use by allocsup.c. Other callers should use
+// the routines in allocsup.
+//
+
+VOID
+NtfsAddAttributeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN PVCN StartingVcn OPTIONAL,
+ IN PVCN ClusterCount OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds space to an existing nonresident attribute.
+
+ The caller specifies the attribute to be changed via the attribute context,
+ and must be prepared to clean up this context no matter how this routine
+ returns.
+
+ This routine procedes in the following steps, whose numbers correspond
+ to the numbers in comments below:
+
+ 1. Save a description of the current attribute.
+
+ 2. Figure out how big the attribute would have to be to store all
+ of the new run information.
+
+ 3. Find the last occurrence of the attribute, to which the new
+ allocation is to be appended.
+
+ 4. If the attribute is getting very large and will not fit, then
+ move it to its own file record. In any case grow the attribute
+ enough to fit either all of the new allocation, or as much as
+ possible.
+
+ 5. Construct the new mapping pairs in place, and log the change.
+
+ 6. If there is still more allocation to describe, then loop to
+ create new file records and initialize them to describe additional
+ allocation until all of the allocation is described.
+
+Arguments:
+
+ Scb - Current stream.
+
+ Context - Attribute Context positioned at the attribute to change. Note
+ that unlike other routines, this parameter is left in an
+ indeterminate state upon return. The caller should plan on
+ doing nothing other than cleaning it up.
+
+ StartingVcn - Supplies Vcn to start on, if not the new highest vcn
+
+ ClusterCount - Supplies count of clusters being added, if not the new highest vcn
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ULONG NewSize, MappingPairsSize;
+ LONG SizeChange;
+ PCHAR MappingPairs;
+ ULONG SizeAvailable;
+ USHORT AttributeFlags;
+ PVCB Vcb;
+ WCHAR NameBuffer[8];
+ UNICODE_STRING AttributeName;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ VCN LowestVcnRemapped;
+ VCN LocalClusterCount;
+ VCN OldHighestVcn;
+ VCN NewHighestVcn;
+ VCN LastVcn;
+ BOOLEAN IsHotFixScb;
+ PBCB NewBcb = NULL;
+ LONGLONG MftReferenceNumber;
+ PFCB Fcb = Scb->Fcb;
+ PNTFS_MCB Mcb = &Scb->Mcb;
+ ULONG AttributeHeaderSize;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ Vcb = Fcb->Vcb;
+
+ DebugTrace( +1, Dbg, ("NtfsAddAttributeAllocation\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("Mcb = %08lx\n", Mcb) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ //
+ // Make a local copy of cluster count, if given. We will use this local
+ // copy to determine the shrinking range if we move to a previous file
+ // record on a second pass through this loop.
+ //
+
+ if (ARGUMENT_PRESENT( ClusterCount )) {
+
+ LocalClusterCount = *ClusterCount;
+ }
+
+ while (TRUE) {
+
+ //
+ // Make sure the buffer is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Make sure we cleanup on the way out
+ //
+
+ try {
+
+ //
+ // Step 1.
+ //
+ // Save a description of the attribute to help us look it up
+ // again, and to make clones if necessary.
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ AttributeTypeCode = Attribute->TypeCode;
+ AttributeFlags = Attribute->Flags;
+ AttributeName.Length =
+ AttributeName.MaximumLength = (USHORT)Attribute->NameLength << 1;
+ AttributeName.Buffer = NameBuffer;
+
+ if (AttributeName.Length > sizeof(NameBuffer)) {
+
+ AttributeName.Buffer = NtfsAllocatePool( NonPagedPool, AttributeName.Length );
+ }
+
+ RtlCopyMemory( AttributeName.Buffer,
+ Add2Ptr( Attribute, Attribute->NameOffset ),
+ AttributeName.Length );
+
+ ASSERT(Attribute->Form.Nonresident.LowestVcn == 0);
+
+ OldHighestVcn = LlClustersFromBytes(Vcb, Attribute->Form.Nonresident.AllocatedLength) - 1;
+
+ //
+ // Get the file record pointer.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+
+ //
+ // Step 2.
+ //
+ // Come up with the Vcn we will stop on. If a StartingVcn and ClusterCount
+ // were specified, then use them to calculate where we will stop. Otherwise
+ // lookup the largest Vcn in this Mcb, so that we will know when we are done.
+ // We will also write the new allocation size here.
+ //
+
+ {
+ LCN TempLcn;
+ BOOLEAN UpdateFileSizes = FALSE;
+
+ NewHighestVcn = -1;
+
+ //
+ // If a StartingVcn and ClusterCount were specified, then use them.
+ //
+
+ if (ARGUMENT_PRESENT(StartingVcn)) {
+
+ ASSERT(ARGUMENT_PRESENT(ClusterCount));
+
+ NewHighestVcn = (*StartingVcn + LocalClusterCount) - 1;
+
+ //
+ // If there are no entries in the file record then we have no new
+ // sizes to report.
+ //
+
+ } else if (NtfsLookupLastNtfsMcbEntry(Mcb, &NewHighestVcn, &TempLcn)) {
+
+ //
+ // For compressed files, make sure we are not shrinking allocation
+ // size (OldHighestVcn) due to a compression unit that was all zeros
+ // and has no allocation. Note, truncates are done in
+ // NtfsDeleteAttributeAllocation, so we should not be shrinking the
+ // file here.
+ //
+ // If this is an attribute being written compressed, then always
+ // insure that we keep the allocation size on a compression unit
+ // boundary, by pushing NewHighestVcn to a boundary - 1.
+ //
+
+ if (Scb->CompressionUnit != 0) {
+
+ //
+ // Don't shrink the file on this path.
+ //
+
+ if (OldHighestVcn > NewHighestVcn) {
+ NewHighestVcn = OldHighestVcn;
+ }
+
+ ((PLARGE_INTEGER) &NewHighestVcn)->LowPart |= ClustersFromBytes(Vcb, Scb->CompressionUnit) - 1;
+
+ //
+ // Make sure we didn't push a hole into the next compression
+ // unit. If so then truncate to the current NewHighestVcn. We
+ // know this will be on a compression unit boundary.
+ //
+
+ if (NewHighestVcn < Scb->Mcb.NtfsMcbArray[Scb->Mcb.NtfsMcbArraySizeInUse - 1].EndingVcn) {
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb,
+ NewHighestVcn + 1,
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+ }
+ }
+ }
+
+ //
+ // Copy the new allocation size into our size structure and
+ // update the attribute.
+ //
+
+ if (NewHighestVcn > OldHighestVcn) {
+
+ Scb->Header.AllocationSize.QuadPart = LlBytesFromClusters(Fcb->Vcb, NewHighestVcn + 1);
+ UpdateFileSizes = TRUE;
+ }
+
+ //
+ // If we moved the allocation size up or the totally allocated does
+ // not match the value on the disk (only for compressed files,
+ // then update the file sizes.
+ //
+
+ if (UpdateFileSizes ||
+ (FlagOn( Attribute->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) &&
+ (Attribute->Form.Nonresident.TotalAllocated != Scb->TotalAllocated))) {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+ }
+ }
+
+ //
+ // Step 3.
+ //
+ // Lookup the attribute record at which the change begins, if it is not
+ // the first file record that we are looking at.
+ //
+
+ if ((Attribute->Form.Nonresident.HighestVcn != OldHighestVcn) &&
+ (NewHighestVcn > Attribute->Form.Nonresident.HighestVcn)) {
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, &NewHighestVcn, Context );
+
+ Attribute = NtfsFoundAttribute(Context);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ FileRecord = NtfsContainingFileRecord(Context);
+ }
+
+ //
+ // Make sure we nuke this range if we get an error, by expanding
+ // the error recovery range.
+ //
+
+ if (Scb->Mcb.PoolType == PagedPool) {
+
+ if (Scb->ScbSnapshot != NULL) {
+
+ if (Attribute->Form.Nonresident.LowestVcn < Scb->ScbSnapshot->LowestModifiedVcn) {
+ Scb->ScbSnapshot->LowestModifiedVcn = Attribute->Form.Nonresident.LowestVcn;
+ }
+
+ if (NewHighestVcn > Scb->ScbSnapshot->HighestModifiedVcn) {
+ Scb->ScbSnapshot->HighestModifiedVcn = NewHighestVcn;
+ }
+
+ if (Attribute->Form.Nonresident.HighestVcn > Scb->ScbSnapshot->HighestModifiedVcn) {
+ Scb->ScbSnapshot->HighestModifiedVcn = Attribute->Form.Nonresident.HighestVcn;
+ }
+ }
+ }
+
+ //
+ // Remember the last Vcn we will need to create mapping pairs
+ // for. We use either NewHighestVcn or the highest Vcn in this
+ // file record in the case that we are just inserting a run into
+ // an existing record.
+ //
+
+ if (ARGUMENT_PRESENT(StartingVcn)) {
+
+ if (Attribute->Form.Nonresident.HighestVcn > NewHighestVcn) {
+
+ NewHighestVcn = Attribute->Form.Nonresident.HighestVcn;
+ }
+
+ //
+ // Otherwise we know we have looked up the last file record to regenerate
+ // that. Let's make sure that the range lines up so we do not end up
+ // generating a hole of one or a few clusters in the next file record.
+ //
+
+ } else {
+
+ if (Attribute->Form.Nonresident.HighestVcn < NewHighestVcn) {
+
+ NtfsDefineNtfsMcbRange( &Scb->Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ NewHighestVcn,
+ FALSE );
+ }
+ }
+
+ //
+ // Remember the lowest Vcn for this attribute. We will use this to
+ // decide whether to loop back and look for an earlier file record.
+ //
+
+ LowestVcnRemapped = Attribute->Form.Nonresident.LowestVcn;
+
+ //
+ // Remember the header size for this attribute. This will be the
+ // mapping pairs offset except for attributes with names.
+ //
+
+ AttributeHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset;
+
+ if (Attribute->NameOffset != 0) {
+
+ AttributeHeaderSize = Attribute->NameOffset;
+ }
+
+ //
+ // If we are making space for a totally allocated field then we
+ // want to add space to the non-resident header for these entries.
+ // To detect this we know that a starting Vcn was specified and
+ // we specified exactly the entire file record. Also the major
+ // and minor Irp codes are exactly that for a compression operation.
+ //
+
+ if ((IrpContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) &&
+ (IrpContext->MinorFunction == IRP_MN_USER_FS_REQUEST) &&
+ (IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp)->Parameters.FileSystemControl.FsControlCode == FSCTL_SET_COMPRESSION) &&
+ ARGUMENT_PRESENT( StartingVcn ) &&
+ (*StartingVcn == 0) &&
+ (LocalClusterCount == Attribute->Form.Nonresident.HighestVcn + 1)) {
+
+ AttributeHeaderSize += sizeof( LONGLONG );
+ }
+
+ //
+ // Now we must make sure that we never ask for more than can fit in
+ // one file record with our attribute and a $END record.
+ //
+
+ SizeAvailable = NtfsMaximumAttributeSize(Vcb->BytesPerFileRecordSegment) -
+ AttributeHeaderSize -
+ QuadAlign( AttributeName.Length );
+
+ //
+ // For the Mft, we will leave a "fudge factor" of 1/8th a file record
+ // free to make sure that possible hot fixes do not cause us to
+ // break the bootstrap process to finding the mapping for the Mft.
+ // Only take this action if we already have an attribute list for
+ // the Mft, otherwise we may not detect when we need to move to own
+ // record.
+ //
+
+ IsHotFixScb = NtfsIsTopLevelHotFixScb( Scb );
+
+ if ((Scb == Vcb->MftScb) &&
+ (Context->AttributeList.Bcb != NULL) &&
+ !IsHotFixScb &&
+ !ARGUMENT_PRESENT( StartingVcn )) {
+
+ SizeAvailable -= Vcb->MftCushion;
+ }
+
+ //
+ // Calculate how much space is actually needed, independent of whether it will
+ // fit.
+ //
+
+ MappingPairsSize = QuadAlign( NtfsGetSizeForMappingPairs( Mcb,
+ SizeAvailable,
+ Attribute->Form.Nonresident.LowestVcn,
+ &NewHighestVcn,
+ &LastVcn ));
+
+ NewSize = AttributeHeaderSize + QuadAlign( AttributeName.Length ) + MappingPairsSize;
+
+ SizeChange = (LONG)NewSize - (LONG)Attribute->RecordLength;
+
+ //
+ // Step 4.
+ //
+ // Here we decide if we need to move the attribute to its own record,
+ // or whether there is enough room to grow it in place.
+ //
+
+ {
+ VCN LowestVcn;
+ ULONG Pass = 0;
+
+ //
+ // It is important to note that at this point, if we will need an
+ // attribute list attribute, then we will already have it. This is
+ // because we calculated the size needed for the attribute, and moved
+ // to a our own record if we were not going to fit and we were not
+ // already in a separate record. Later on we assume that the attribute
+ // list exists, and just add to it as required. If we didn't move to
+ // own record because this is the Mft and this is not file record 0,
+ // then we already have an attribute list from a previous split.
+ //
+
+ do {
+
+ //
+ // If not the first pass, we have to lookup the attribute
+ // again. (It looks terrible to have to refind an attribute
+ // record other than the first one, but this should never
+ // happen, since subsequent attributes should always be in
+ // their own record.)
+ //
+
+ if (Pass != 0) {
+
+ BOOLEAN Found;
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ &AttributeName,
+ &LowestVcn,
+ FALSE,
+ Context );
+
+ ASSERT(Found);
+ }
+
+ Pass += 1;
+
+ //
+ // Now we have to reload our pointers
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ FileRecord = NtfsContainingFileRecord(Context);
+
+ //
+ // If the attribute doesn't fit, and it is not alone in this file
+ // record, and the attribute is big enough to move, then we will
+ // have to take some special action. Note that if we do not already
+ // have an attribute list, then we will only do the move if we are
+ // currently big enough to move, otherwise there may not be enough
+ // space in MoveAttributeToOwnRecord to create the attribute list,
+ // and that could cause us to recursively try to create the attribute
+ // list in Create Attribute With Value.
+ //
+ // We won't make this move if we are dealing with the Mft and it
+ // is not file record 0.
+ //
+ // Also we never move an attribute list to its own record.
+ //
+
+ if ((Attribute->TypeCode != $ATTRIBUTE_LIST)
+
+ &&
+
+ (SizeChange > (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte))
+
+ &&
+
+ ((NtfsFirstAttribute(FileRecord) != Attribute) ||
+ (((PATTRIBUTE_RECORD_HEADER)NtfsGetNextRecord(Attribute))->TypeCode != $END))
+
+ &&
+
+ (((NewSize >= Vcb->BigEnoughToMove) && (Context->AttributeList.Bcb != NULL)) ||
+ (Attribute->RecordLength >= Vcb->BigEnoughToMove))
+
+ &&
+
+ ((Scb != Vcb->MftScb)
+
+ ||
+
+ (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0))) {
+
+ //
+ // If we are moving the Mft $DATA out of the base file record, the
+ // attribute context will point to the split portion on return.
+ // The attribute will only contain previously existing mapping, none
+ // of the additional clusters which exist in the Mcb.
+ //
+
+ MftReferenceNumber = MoveAttributeToOwnRecord( IrpContext,
+ Fcb,
+ Attribute,
+ Context,
+ &NewBcb,
+ &FileRecord );
+
+ Attribute = NtfsFirstAttribute(FileRecord);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ FileRecord = NtfsContainingFileRecord(Context);
+ }
+
+ //
+ // Remember the lowest Vcn so that we can find this record again
+ // if we have to. We capture the value now, after the move attribute
+ // in case this is the Mft doing a split and the entire attribute
+ // didn't move. We depend on MoveAttributeToOwnRecord to return
+ // the new file record for the Mft split.
+ //
+
+ LowestVcn = Attribute->Form.Nonresident.LowestVcn;
+
+ //
+ // If FALSE is returned, then the space was not allocated and
+ // we have to loop back and try again. Second time must work.
+ //
+
+ } while (!NtfsChangeAttributeSize( IrpContext,
+ Fcb,
+ NewSize,
+ Context ));
+
+ //
+ // Now we have to reload our pointers
+ //
+
+ Attribute = NtfsFoundAttribute(Context);
+ FileRecord = NtfsContainingFileRecord(Context);
+ }
+
+ //
+ // Step 5.
+ //
+ // Get pointer to mapping pairs
+ //
+
+ {
+ ULONG AttributeOffset;
+ ULONG MappingPairsOffset;
+ CHAR MappingPairsBuffer[64];
+ ULONG RecordOffset = PtrOffset(FileRecord, Attribute);
+
+ //
+ // See if it is the case that all mapping pairs will not fit into
+ // the current file record, as we may wish to split in the middle
+ // rather than at the end as we are currently set up to do.
+ //
+
+ if (LastVcn < NewHighestVcn) {
+
+ if (ARGUMENT_PRESENT(StartingVcn) &&
+ (Scb != Vcb->MftScb)) {
+
+ //
+ // In this case we have run out of room for mapping pairs via
+ // an overwrite somewhere in the middle of the file. To avoid
+ // shoving a couple mapping pairs off the end over and over, we
+ // will arbitrarily split this attribute in the middle. We do
+ // so by looking up the lowest and highest Vcns that we are working
+ // with and get their indices, then split in the middle.
+ //
+
+ LCN TempLcn;
+ LONGLONG TempCount;
+ PVOID RangeLow, RangeHigh;
+ ULONG IndexLow, IndexHigh;
+ BOOLEAN Found;
+
+ //
+ // Get the low and high Mcb indices for these runs.
+ //
+
+ Found = NtfsLookupNtfsMcbEntry( Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &RangeLow,
+ &IndexLow );
+ ASSERT(Found);
+
+ //
+ // Point to the last Vcn we know is actually in the Mcb...
+ //
+
+ LastVcn = LastVcn - 1;
+
+ Found = NtfsLookupNtfsMcbEntry( Mcb,
+ LastVcn,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &RangeHigh,
+ &IndexHigh );
+ ASSERT(Found);
+ ASSERT(RangeLow == RangeHigh);
+
+ //
+ // Calculate the index in the middle.
+ //
+
+ IndexLow += (IndexHigh - IndexLow) /2;
+
+ //
+ // If we are inserting past he ValidDataToDisk (SplitMcb case),
+ // then the allocation behind us may be relatively static, so
+ // let's just move with our preallocated space to the new buffer.
+ //
+
+ if (*StartingVcn >= LlClustersFromBytes(Vcb, Scb->ValidDataToDisk)) {
+
+ //
+ // Calculate the index at about 7/8 the way. Hopefully this will
+ // move over all of the unallocated piece, while still leaving
+ // some small amount of expansion space behind for overwrites.
+ //
+
+ IndexLow += (IndexHigh - IndexLow) /2;
+ IndexLow += (IndexHigh - IndexLow) /2;
+ }
+
+ //
+ // Lookup the middle run and use the Last Vcn in that run.
+ //
+
+ Found = NtfsGetNextNtfsMcbEntry( Mcb,
+ &RangeLow,
+ IndexLow,
+ &LastVcn,
+ &TempLcn,
+ &TempCount );
+ ASSERT(Found);
+
+ LastVcn = (LastVcn + TempCount) - 1;
+
+ //
+ // Calculate how much space is now needed given our new LastVcn.
+ //
+
+ MappingPairsSize = QuadAlign( NtfsGetSizeForMappingPairs( Mcb,
+ SizeAvailable,
+ Attribute->Form.Nonresident.LowestVcn,
+ &LastVcn,
+ &LastVcn ));
+ }
+ }
+
+ //
+ // If we are growing this range, then we need to make sure we fix
+ // its definition.
+ //
+
+ if ((LastVcn - 1) > Attribute->Form.Nonresident.HighestVcn) {
+
+ NtfsDefineNtfsMcbRange( &Scb->Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ LastVcn - 1,
+ FALSE );
+ }
+
+ //
+ // Point to our local mapping pairs buffer, or allocate one if it is not
+ // big enough.
+ //
+
+ MappingPairs = MappingPairsBuffer;
+
+ if (MappingPairsSize > 64) {
+
+ MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsSize + 8 );
+ }
+
+ //
+ // Use try-finally to insure we free any pool on the way out.
+ //
+
+ try {
+
+ DebugDoit(
+
+ VCN TempVcn;
+
+ TempVcn = LastVcn - 1;
+
+ ASSERT(MappingPairsSize ==
+ QuadAlign( NtfsGetSizeForMappingPairs( Mcb, SizeAvailable,
+ Attribute->Form.Nonresident.LowestVcn,
+ &TempVcn, &LastVcn )));
+ );
+
+ //
+ // Now add the space in the file record.
+ //
+
+ *MappingPairs = 0;
+ NtfsBuildMappingPairs( Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ &LastVcn,
+ MappingPairs );
+
+ //
+ // Now find the first different byte. (Most of the time the
+ // cost to do this is probably more than paid for by less
+ // logging.)
+ //
+
+ AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset;
+ MappingPairsOffset =
+ RtlCompareMemory( MappingPairs,
+ Add2Ptr(Attribute, AttributeOffset),
+ ((Attribute->RecordLength - AttributeOffset) > MappingPairsSize ?
+ MappingPairsSize :
+ (Attribute->RecordLength - AttributeOffset)));
+
+ AttributeOffset += MappingPairsOffset;
+
+ //
+ // Log the change.
+ //
+
+ {
+ LONGLONG LogOffset;
+
+ if (NewBcb != NULL) {
+
+ //
+ // We know the file record number of the new file
+ // record. Convert it to a file offset.
+ //
+
+ LogOffset = LlBytesFromFileRecords( Vcb, MftReferenceNumber );
+
+ } else {
+
+ LogOffset = NtfsMftOffset( Context );
+ }
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NewBcb != NULL ? NewBcb : NtfsFoundBcb(Context),
+ UpdateMappingPairs,
+ Add2Ptr(MappingPairs, MappingPairsOffset),
+ MappingPairsSize - MappingPairsOffset,
+ UpdateMappingPairs,
+ Add2Ptr(Attribute, AttributeOffset),
+ Attribute->RecordLength - AttributeOffset,
+ LogOffset,
+ RecordOffset,
+ AttributeOffset,
+ Vcb->BytesPerFileRecordSegment );
+ }
+
+ //
+ // Now do the mapping pairs update by calling the same
+ // routine called at restart.
+ //
+
+ NtfsRestartChangeMapping( IrpContext,
+ Vcb,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ Add2Ptr(MappingPairs, MappingPairsOffset),
+ MappingPairsSize - MappingPairsOffset );
+
+
+ } finally {
+
+ if (MappingPairs != MappingPairsBuffer) {
+
+ NtfsFreePool(MappingPairs);
+ }
+ }
+ }
+
+ ASSERT( Attribute->Form.Nonresident.HighestVcn == LastVcn );
+
+ //
+ // Check if have spilled into the reserved area of an Mft file record.
+ //
+
+ if ((Scb == Vcb->MftScb) &&
+ (Context->AttributeList.Bcb != NULL)) {
+
+ if (FileRecord->BytesAvailable - FileRecord->FirstFreeByte < Vcb->MftReserved
+ && (*(PLONGLONG)&FileRecord->BaseFileRecordSegment != 0)) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ SetFlag( Vcb->MftDefragState,
+ VCB_MFT_DEFRAG_EXCESS_MAP | VCB_MFT_DEFRAG_ENABLED );
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+ }
+
+ //
+ // Step 6.
+ //
+ // Now loop to create new file records if we have more allocation to
+ // describe. We use the highest Vcn of the file record we began with
+ // as our stopping point or the last Vcn we are adding.
+ //
+
+ while (LastVcn < NewHighestVcn) {
+
+ MFT_SEGMENT_REFERENCE Reference;
+ LONGLONG FileRecordNumber;
+ PATTRIBUTE_TYPE_CODE NewEnd;
+
+ //
+ // If we get here as the result of a hot fix in the Mft, bail
+ // out. We could cause a disconnect in the Mft.
+ //
+
+ if (IsHotFixScb && (Scb == Vcb->MftScb)) {
+ ExRaiseStatus( STATUS_INTERNAL_ERROR );
+ }
+
+ //
+ // Clone our current file record, and point to our new attribute.
+ //
+
+ NtfsUnpinBcb( &NewBcb );
+
+ FileRecord = NtfsCloneFileRecord( IrpContext,
+ Fcb,
+ (BOOLEAN)(Scb == Vcb->MftScb),
+ &NewBcb,
+ &Reference );
+
+ Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset );
+
+ //
+ // Next LowestVcn is the LastVcn + 1
+ //
+
+ LastVcn = LastVcn + 1;
+ Attribute->Form.Nonresident.LowestVcn = LastVcn;
+
+ //
+ // Calculate the size of the attribute record we will need.
+ //
+
+ NewSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER
+ + QuadAlign( AttributeName.Length )
+ + QuadAlign( NtfsGetSizeForMappingPairs( Mcb,
+ SizeAvailable,
+ LastVcn,
+ &NewHighestVcn,
+ &LastVcn ));
+
+ //
+ // Define the new range.
+ //
+
+ NtfsDefineNtfsMcbRange( &Scb->Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ LastVcn - 1,
+ FALSE );
+
+ //
+ // Initialize the new attribute from the old one.
+ //
+
+ Attribute->TypeCode = AttributeTypeCode;
+ Attribute->RecordLength = NewSize;
+ Attribute->FormCode = NONRESIDENT_FORM;
+
+ //
+ // Assume no attribute name, and calculate where the Mapping Pairs
+ // will go. (Update below if we are wrong.)
+ //
+
+ MappingPairs = (PCHAR)Attribute + SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+
+ //
+ // If the attribute has a name, take care of that now.
+ //
+
+ if (AttributeName.Length != 0) {
+
+ Attribute->NameLength = (UCHAR)(AttributeName.Length / sizeof(WCHAR));
+ Attribute->NameOffset = (USHORT)PtrOffset(Attribute, MappingPairs);
+ RtlCopyMemory( MappingPairs,
+ AttributeName.Buffer,
+ AttributeName.Length );
+ MappingPairs += QuadAlign( AttributeName.Length );
+ }
+
+ Attribute->Flags = AttributeFlags;
+ Attribute->Instance = FileRecord->NextAttributeInstance++;
+
+ //
+ // We always need the mapping pairs offset.
+ //
+
+ Attribute->Form.Nonresident.MappingPairsOffset = (USHORT)(MappingPairs -
+ (PCHAR)Attribute);
+ NewEnd = Add2Ptr( Attribute, Attribute->RecordLength );
+ *NewEnd = $END;
+ FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd )
+ + QuadAlign( sizeof(ATTRIBUTE_TYPE_CODE ));
+
+ //
+ // Now add the space in the file record.
+ //
+
+ *MappingPairs = 0;
+
+ NtfsBuildMappingPairs( Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ &LastVcn,
+ MappingPairs );
+
+ Attribute->Form.Nonresident.HighestVcn = LastVcn;
+
+ //
+ // Now log these changes and fix up the first file record.
+ //
+
+ FileRecordNumber = NtfsFullSegmentNumber(&Reference);
+
+ //
+ // Now log these changes and fix up the first file record.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NewBcb,
+ InitializeFileRecordSegment,
+ FileRecord,
+ FileRecord->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromFileRecords( Vcb, FileRecordNumber ),
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Finally, we have to add the entry to the attribute list.
+ // The routine we have to do this gets most of its inputs
+ // out of an attribute context. Our context at this point
+ // does not have quite the right information, so we have to
+ // update it here before calling AddToAttributeList. (OK
+ // this interface ain't pretty, but any normal person would
+ // have fallen asleep before getting to this comment!)
+ //
+
+ Context->FoundAttribute.FileRecord = FileRecord;
+ Context->FoundAttribute.Attribute = Attribute;
+ Context->AttributeList.Entry =
+ NtfsGetNextRecord(Context->AttributeList.Entry);
+
+ NtfsAddToAttributeList( IrpContext, Fcb, Reference, Context );
+ }
+
+ } finally {
+
+ NtfsUnpinBcb( &NewBcb );
+
+ if (AttributeName.Buffer != NameBuffer) {
+
+ NtfsFreePool(AttributeName.Buffer);
+ }
+ }
+
+ if (!ARGUMENT_PRESENT( StartingVcn) ||
+ (LowestVcnRemapped <= *StartingVcn)) {
+
+ break;
+ }
+
+ //
+ // Move the range to be remapped down.
+ //
+
+ LocalClusterCount = LowestVcnRemapped - *StartingVcn;
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, Context );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddAttributeAllocation -> VOID\n") );
+}
+
+
+//
+// This routine is intended for use by allocsup.c. Other callers should use
+// the routines in allocsup.
+//
+
+VOID
+NtfsDeleteAttributeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN LogIt,
+ IN PVCN StopOnVcn,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN BOOLEAN TruncateToVcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes an existing nonresident attribute, removing the
+ deleted clusters only from the allocation description in the file
+ record.
+
+ The caller specifies the attribute to be changed via the attribute context,
+ and must be prepared to clean up this context no matter how this routine
+ returns. The Scb must already have deleted the clusters in question.
+
+Arguments:
+
+ Scb - Current attribute, with the clusters in question already deleted from
+ the Mcb.
+
+ LogIt - Most callers should specify TRUE, to have the change logged. However,
+ we can specify FALSE if we are deleting an entire file record, and
+ will be logging that.
+
+ StopOnVcn - Vcn to stop on for regerating mapping
+
+ Context - Attribute Context positioned at the attribute to change.
+
+ TruncateToVcn - Truncate file sizes as appropriate to the Vcn
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG AttributeOffset;
+ ULONG MappingPairsOffset, MappingPairsSize;
+ CHAR MappingPairsBuffer[64];
+ ULONG RecordOffset;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PCHAR MappingPairs;
+ VCN LastVcn;
+ ULONG NewSize;
+ PVCB Vcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ Vcb = Scb->Vcb;
+
+ //
+ // For now we only support truncation.
+ //
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteAttributeAllocation\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ //
+ // Make sure the buffer is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ Attribute = NtfsFoundAttribute(Context);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+
+ //
+ // Get the file record pointer.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ RecordOffset = PtrOffset(FileRecord, Attribute);
+
+ //
+ // Calculate how much space is actually needed.
+ //
+
+ MappingPairsSize = QuadAlign(NtfsGetSizeForMappingPairs( &Scb->Mcb,
+ MAXULONG,
+ Attribute->Form.Nonresident.LowestVcn,
+ StopOnVcn,
+ &LastVcn ));
+
+ //
+ // Don't assume we understand everything about the size of the current header.
+ // Find the offset of the name or the mapping pairs to use as the size
+ // of the header.
+ //
+
+
+ NewSize = Attribute->Form.Nonresident.MappingPairsOffset;
+
+ if (Attribute->NameLength != 0) {
+
+ NewSize = Attribute->NameOffset + QuadAlign( Attribute->NameLength << 1 );
+ }
+
+ NewSize += MappingPairsSize;
+
+ //
+ // If the record could somehow grow by deleting allocation, then
+ // NtfsChangeAttributeSize could fail and we would have to copy the
+ // loop from NtfsAddAttributeAllocation.
+ //
+
+ ASSERT( NewSize <= Attribute->RecordLength );
+
+ MappingPairs = MappingPairsBuffer;
+
+ if (MappingPairsSize > 64) {
+
+ MappingPairs = NtfsAllocatePool( NonPagedPool, MappingPairsSize + 8 );
+ }
+
+ //
+ // Use try-finally to insure we free any pool on the way out.
+ //
+
+ try {
+
+ //
+ // Now build up the mapping pairs in the buffer.
+ //
+
+ *MappingPairs = 0;
+ NtfsBuildMappingPairs( &Scb->Mcb,
+ Attribute->Form.Nonresident.LowestVcn,
+ &LastVcn,
+ MappingPairs );
+
+ //
+ // Now find the first different byte. (Most of the time the
+ // cost to do this is probably more than paid for by less
+ // logging.)
+ //
+
+ AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset;
+ MappingPairsOffset =
+ RtlCompareMemory( MappingPairs,
+ Add2Ptr(Attribute, AttributeOffset),
+ MappingPairsSize );
+
+ AttributeOffset += MappingPairsOffset;
+
+ //
+ // Log the change.
+ //
+
+ if (LogIt) {
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ UpdateMappingPairs,
+ Add2Ptr(MappingPairs, MappingPairsOffset),
+ MappingPairsSize - MappingPairsOffset,
+ UpdateMappingPairs,
+ Add2Ptr(Attribute, AttributeOffset),
+ Attribute->RecordLength - AttributeOffset,
+ NtfsMftOffset( Context ),
+ RecordOffset,
+ AttributeOffset,
+ Vcb->BytesPerFileRecordSegment );
+ }
+
+ //
+ // Now do the mapping pairs update by calling the same
+ // routine called at restart.
+ //
+
+ NtfsRestartChangeMapping( IrpContext,
+ Vcb,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ Add2Ptr(MappingPairs, MappingPairsOffset),
+ MappingPairsSize - MappingPairsOffset );
+
+ //
+ // If we were asked to stop on a Vcn, then the caller does not wish
+ // us to modify the Scb. (Currently this is only done one time when
+ // the Mft Data attribute no longer fits in the first file record.)
+ //
+
+ if (TruncateToVcn) {
+
+ LONGLONG Size;
+
+ //
+ // We add one cluster to calculate the allocation size.
+ //
+
+ LastVcn = LastVcn + 1;
+ Size = LlBytesFromClusters( Vcb, LastVcn );
+ Scb->Header.AllocationSize.QuadPart = Size;
+
+ if (Scb->Header.ValidDataLength.QuadPart > Size) {
+ Scb->Header.ValidDataLength.QuadPart = Size;
+ }
+
+ if (Scb->Header.FileSize.QuadPart > Size) {
+ Scb->Header.FileSize.QuadPart = Size;
+ }
+
+ //
+ // Possibly update ValidDataToDisk
+ //
+
+ if (Size < Scb->ValidDataToDisk) {
+ Scb->ValidDataToDisk = Size;
+ }
+ }
+
+ } finally {
+
+ if (MappingPairs != MappingPairsBuffer) {
+
+ NtfsFreePool(MappingPairs);
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteAttributeAllocation -> VOID\n") );
+}
+
+
+BOOLEAN
+NtfsIsFileDeleteable (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PBOOLEAN NonEmptyIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This look checks if a file may be deleted by examing all of the index
+ attributes to check that they have no children.
+
+ Note that once a file is marked for delete, we must insure
+ that none of the conditions checked by this routine are allowed to
+ change. For example, once the file is marked for delete, no links
+ may be added, and no files may be created in any indices of this
+ file.
+
+Arguments:
+
+ Fcb - Fcb for the file.
+
+ NonEmptyIndex - Address to store TRUE if the file is not deleteable because
+ it contains an non-empty indexed attribute.
+
+Return Value:
+
+ FALSE - If it is not ok to delete the specified file.
+ TRUE - If it is ok to delete the specified file.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ BOOLEAN MoreToGo;
+
+ PAGED_CODE();
+
+ NtfsInitializeAttributeContext( &Context );
+
+ try {
+
+ //
+ // Enumerate all of the attributes to check whether they may be deleted.
+ //
+
+ MoreToGo = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $INDEX_ROOT,
+ &Context );
+
+ while (MoreToGo) {
+
+ //
+ // Point to the current attribute.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+
+ //
+ // If the attribute is an index, then it must be empty.
+ //
+
+ if (!NtfsIsIndexEmpty( IrpContext, Attribute )) {
+
+ *NonEmptyIndex = TRUE;
+ break;
+ }
+
+ //
+ // Go to the next attribute.
+ //
+
+ MoreToGo = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $INDEX_ROOT,
+ &Context );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsIsFileDeleteable );
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ //
+ // The File is deleteable if scanned the entire file record
+ // and found no reasons we could not delete the file.
+ //
+
+ return (BOOLEAN)(!MoreToGo);
+}
+
+
+VOID
+NtfsDeleteFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB ParentScb OPTIONAL,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to see if it is the specified file may
+ be deleted from the specified parent (i.e., if the specified parent
+ were to be acquired exclusive). This routine should be called from
+ fileinfo, to see whether it is ok to mark an open file for delete.
+
+ NamePair will capture the names of the file being deleted if supplied.
+
+ Note that once a file is marked for delete, none of we must insure
+ that none of the conditions checked by this routine are allowed to
+ change. For example, once the file is marked for delete, no links
+ may be added, and no files may be created in any indices of this
+ file.
+
+ NOTE: The caller must have the Fcb and ParentScb exclusive to call
+ this routine,
+
+Arguments:
+
+ Fcb - Fcb for the file.
+
+ ParentScb - Parent Scb via which the file was opened, and which would
+ be acquired exclusive to perform the delete. There may be
+ no parent Fcb for a file whose links have already been
+ removed.
+
+ NamePair - optionally provided to capture the name pair of a file
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PVCB Vcb;
+ BOOLEAN MoreToGo;
+ ULONG RecordNumber;
+ NUKEM LocalNuke;
+ ULONG Pass;
+ ULONG i;
+ PNUKEM TempNukem;
+ PNUKEM Nukem = &LocalNuke;
+ ULONG NukemIndex = 0;
+ BOOLEAN NonresidentAttributeList = FALSE;
+
+ PAGED_CODE();
+
+ ASSERT_EXCLUSIVE_FCB( Fcb );
+
+ if (ARGUMENT_PRESENT( ParentScb )) {
+
+ ASSERT_EXCLUSIVE_SCB( ParentScb );
+ }
+
+ RtlZeroMemory( &LocalNuke, sizeof(NUKEM) );
+
+ Vcb = Fcb->Vcb;
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ try {
+
+ //
+ // We perform the delete in two passes. On the first pass we delete
+ // all of the allocation for non-resident attributes except for
+ // a security descriptor and for a non-resident attribute list (very rare).
+ //
+ // The second pass will delete remove the file names from their parent
+ // indexes and add the file records to a Neukem array.
+ //
+
+ for (Pass = 0; Pass < 2; Pass += 1) {
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Enumerate all of the attributes to check whether they may be deleted.
+ //
+
+ MoreToGo = NtfsLookupAttribute( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ &Context );
+
+ while (MoreToGo) {
+
+ //
+ // Point to the current attribute.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+
+ //
+ // All indices must be empty.
+ //
+
+ ASSERT( (Attribute->TypeCode != $INDEX_ROOT) ||
+ NtfsIsIndexEmpty( IrpContext, Attribute ));
+
+ //
+ // If the attribute is nonresident, then delete its allocation.
+ // We only need to make the NtfsDeleteAllocation from record for
+ // the attribute with lowest Vcn of zero. This will deallocate
+ // all of the clusters for the file.
+ //
+
+ if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ if (Pass == 0) {
+
+ if (Attribute->TypeCode != $SECURITY_DESCRIPTOR &&
+ Attribute->Form.Nonresident.LowestVcn == 0) {
+
+ NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, TRUE );
+
+ //
+ // Reload the attribute pointer in the event it
+ // was remapped.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+ }
+
+ } else {
+
+ if (Attribute->TypeCode == $SECURITY_DESCRIPTOR &&
+ Attribute->Form.Nonresident.LowestVcn == 0) {
+
+ NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, FALSE );
+
+ //
+ // Reload the attribute pointer in the event it
+ // was remapped.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+ }
+ }
+ } else {
+
+#ifdef _CAIRO_
+ if (Pass == 0 &&
+ Attribute->TypeCode == $STANDARD_INFORMATION &&
+ Attribute->Form.Resident.ValueLength ==
+ sizeof( STANDARD_INFORMATION )) {
+
+ ASSERT( !FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED ) ||
+ NtfsPerformQuotaOperation( Fcb ) ||
+ ((PSTANDARD_INFORMATION) NtfsAttributeValue( Attribute ))->QuotaCharged == 0);
+
+ //
+ // Return all of the user's quota for this file.
+ //
+
+ if (NtfsPerformQuotaOperation( Fcb )) {
+
+ LONGLONG Delta = -(LONGLONG) ((PSTANDARD_INFORMATION)
+ NtfsAttributeValue( Attribute ))->
+ QuotaCharged;
+
+ NtfsUpdateFileQuota( IrpContext,
+ Fcb,
+ &Delta,
+ TRUE,
+ FALSE );
+ }
+
+ }
+
+#endif // _CAIRO_
+
+ }
+
+ if (Pass == 1) {
+
+ //
+ // If the attribute is a file name, then it must be from our
+ // caller's parent directory, or else we cannot delete.
+ //
+
+ if (Attribute->TypeCode == $FILE_NAME) {
+
+ PFILE_NAME FileName;
+
+ FileName = (PFILE_NAME)NtfsAttributeValue( Attribute );
+
+ ASSERT( ARGUMENT_PRESENT( ParentScb ));
+
+ ASSERT(NtfsEqualMftRef(&FileName->ParentDirectory,
+ &ParentScb->Fcb->FileReference));
+
+ if (ARGUMENT_PRESENT(NamePair)) {
+
+ //
+ // Squirrel away names
+ //
+
+ NtfsCopyNameToNamePair( NamePair,
+ FileName->FileName,
+ FileName->FileNameLength,
+ FileName->Flags );
+ }
+
+ NtfsDeleteIndexEntry( IrpContext,
+ ParentScb,
+ (PVOID)FileName,
+ &Fcb->FileReference );
+ }
+
+ //
+ // If this file record is not already deleted, then do it now.
+ // Note, we are counting on its contents not to change.
+ //
+
+ FileRecord = NtfsContainingFileRecord( &Context );
+
+ //
+ // See if this is the same as the last one we remembered, else remember it.
+ //
+
+ if (Context.AttributeList.Bcb != NULL) {
+ RecordNumber = NtfsUnsafeSegmentNumber( &Context.AttributeList.Entry->SegmentReference );
+ } else {
+ RecordNumber = NtfsUnsafeSegmentNumber( &Fcb->FileReference );
+ }
+
+ //
+ // Now loop to see if we already remembered this record.
+ // This reduces our pool allocation and also prevents us
+ // from deleting file records twice.
+ //
+
+ TempNukem = Nukem;
+ while (TempNukem != NULL) {
+
+ for (i = 0; i < 4; i++) {
+
+ if (TempNukem->RecordNumbers[i] == RecordNumber) {
+
+ RecordNumber = 0;
+ break;
+ }
+ }
+
+ TempNukem = TempNukem->Next;
+ }
+
+ if (RecordNumber != 0) {
+
+ //
+ // Is the list full? If so allocate and initialize a new one.
+ //
+
+ if (NukemIndex > 3) {
+
+ TempNukem = (PNUKEM)ExAllocateFromPagedLookasideList( &NtfsNukemLookasideList );
+ RtlZeroMemory( TempNukem, sizeof(NUKEM) );
+ TempNukem->Next = Nukem;
+ Nukem = TempNukem;
+ NukemIndex = 0;
+ }
+
+ //
+ // Remember to delete this guy. (Note we can possibly list someone
+ // more than once, but NtfsDeleteFileRecord handles that.)
+ //
+
+ Nukem->RecordNumbers[NukemIndex] = RecordNumber;
+ NukemIndex += 1;
+ }
+
+ //
+ // When we have the first attribute, check for the existance of
+ // a non-resident attribute list.
+ //
+
+ } else if ((Attribute->TypeCode == $STANDARD_INFORMATION) &&
+ (Context.AttributeList.Bcb != NULL) &&
+ (!NtfsIsAttributeResident( Context.AttributeList.AttributeList ))) {
+
+ NonresidentAttributeList = TRUE;
+ }
+
+
+ //
+ // Go to the next attribute.
+ //
+
+ MoreToGo = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &Context );
+ }
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ //
+ // Handle the unusual nonresident attribute list case
+ //
+
+ if (NonresidentAttributeList) {
+
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $ATTRIBUTE_LIST,
+ &Context );
+
+ NtfsDeleteAllocationFromRecord( IrpContext, Fcb, &Context, FALSE );
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ //
+ // Now loop to delete the file records.
+ //
+
+ while (Nukem != NULL) {
+
+ for (i = 0; i < 4; i++) {
+
+ if (Nukem->RecordNumbers[i] != 0) {
+
+
+ NtfsDeallocateMftRecord( IrpContext,
+ Vcb,
+ Nukem->RecordNumbers[i] );
+ }
+ }
+
+ TempNukem = Nukem->Next;
+ if (Nukem != &LocalNuke) {
+ ExFreeToPagedLookasideList( &NtfsNukemLookasideList, Nukem );
+ }
+ Nukem = TempNukem;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsDeleteFile );
+
+ NtfsCleanupAttributeContext( &Context );
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ }
+
+ return;
+}
+
+
+VOID
+NtfsPrepareForUpdateDuplicate (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PLCB *Lcb,
+ IN OUT PSCB *ParentScb,
+ IN BOOLEAN AcquireShared
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to prepare for updating the duplicate information.
+ At the conclusion of this routine we will have the Lcb and Scb for the
+ update along with the Scb acquired. This routine will look at
+ the existing values for the input parameters in deciding what actions
+ need to be done.
+
+Arguments:
+
+ Fcb - Fcb for the file. The file must already be acquired exclusively.
+
+ Lcb - This is the address to store the link to update. This may already
+ have a value.
+
+ ParentScb - This is the address to store the parent Scb for the update.
+ This may already point to a valid Scb.
+
+ AcquireShared - Indicates how to acquire the parent Scb.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PLIST_ENTRY Links;
+ PLCB ThisLcb;
+
+ PAGED_CODE();
+
+ //
+ // Start by trying to guarantee we have an Lcb for the update.
+ //
+
+ if (*Lcb == NULL) {
+
+ Links = Fcb->LcbQueue.Flink;
+
+ while (Links != &Fcb->LcbQueue) {
+
+ ThisLcb = CONTAINING_RECORD( Links,
+ LCB,
+ FcbLinks );
+
+ //
+ // We can use this link if it is still present on the
+ // disk and if we were passed a parent Scb, it matches
+ // the one for this Lcb.
+ //
+
+ if (!FlagOn( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE ) &&
+ ((*ParentScb == NULL) ||
+ (*ParentScb == ThisLcb->Scb) ||
+ ((ThisLcb == Fcb->Vcb->RootLcb) &&
+ (*ParentScb == Fcb->Vcb->RootIndexScb)))) {
+
+ *Lcb = ThisLcb;
+ break;
+ }
+
+ Links = Links->Flink;
+ }
+ }
+
+ //
+ // If we have an Lcb, try to find the correct Scb.
+ //
+
+ if ((*Lcb != NULL) && (*ParentScb == NULL)) {
+
+ if (*Lcb == Fcb->Vcb->RootLcb) {
+
+ *ParentScb = Fcb->Vcb->RootIndexScb;
+
+ } else {
+
+ *ParentScb = (*Lcb)->Scb;
+ }
+ }
+
+ //
+ // Acquire the parent Scb and put it in the transaction queue in the
+ // IrpContext.
+ //
+
+ if (*ParentScb != NULL) {
+
+ if (AcquireShared) {
+
+ NtfsAcquireSharedScbForTransaction( IrpContext, *ParentScb );
+
+ } else {
+
+ NtfsAcquireExclusiveScb( IrpContext, *ParentScb );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateDuplicateInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PLCB Lcb OPTIONAL,
+ IN PSCB ParentScb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the duplicate information for a file
+ in the duplicated information of its parent. If the Lcb is specified
+ then this parent is the parent to update. If the link is either an
+ NTFS or DOS only link then we must update the complementary link as
+ well. If no Lcb is specified then this open was by file id or the
+ original link has been deleted. In that case we will try to find a different
+ link to update.
+
+Arguments:
+
+ Fcb - Fcb for the file.
+
+ Lcb - This is the link to update. Specified only if this is not
+ an open by Id operation.
+
+ ParentScb - This is the parent directory for the Lcb link if specified.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PQUICK_INDEX QuickIndex = NULL;
+
+ UCHAR Buffer[sizeof( FILE_NAME ) + 11 * sizeof( WCHAR )];
+ PFILE_NAME FileNameAttr;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+
+ BOOLEAN ReturnedExistingFcb = TRUE;
+ BOOLEAN Found;
+ UCHAR FileNameFlags;
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PVCB Vcb = Fcb->Vcb;
+
+ PFCB ParentFcb = NULL;
+
+ PAGED_CODE();
+
+ ASSERT_EXCLUSIVE_FCB( Fcb );
+
+ //
+ // Return immediately if the volume is locked.
+ //
+
+ if (FlagOn( Fcb->Vcb->VcbState, VCB_STATE_LOCKED )) {
+
+ return;
+ }
+
+ NtfsInitializeAttributeContext( &Context );
+
+ try {
+
+ //
+ // If we are updating the entry for the root then we know the
+ // file name attribute to build.
+ //
+
+ if (Fcb == Fcb->Vcb->RootIndexScb->Fcb) {
+
+ Lcb = Fcb->Vcb->RootLcb;
+ ParentScb = Fcb->Vcb->RootIndexScb;
+
+ QuickIndex = &Fcb->Vcb->RootLcb->QuickIndex;
+
+ FileNameAttr = (PFILE_NAME) &Buffer;
+
+ RtlZeroMemory( FileNameAttr,
+ sizeof( FILE_NAME ));
+
+ NtfsBuildFileNameAttribute( IrpContext,
+ &Fcb->FileReference,
+ NtfsRootIndexString,
+ FILE_NAME_DOS | FILE_NAME_NTFS,
+ FileNameAttr );
+
+ //
+ // If we have and Lcb then it is either present or we noop this update.
+ //
+
+ } else if (ARGUMENT_PRESENT( Lcb )) {
+
+ if (!FlagOn( Lcb->LcbState, LCB_STATE_LINK_IS_GONE )) {
+
+ QuickIndex = &Lcb->QuickIndex;
+ FileNameAttr = Lcb->FileNameAttr;
+
+ } else {
+
+ leave;
+ }
+
+ //
+ // If there is no Lcb then lookup the first filename attribute
+ // and update its index entry. If there is a parent Scb then we
+ // must find a file name attribute for the same parent or we could
+ // get into a deadlock situation.
+ //
+
+ } else {
+
+ //
+ // We now have a name link to update. We will now need
+ // an Scb for the parent index. Remember that we may
+ // have to teardown the Scb. If we already have a ParentScb
+ // then we must find a link to the same parent or to the root.
+ // Otherwise we could hit a deadlock.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &Context );
+
+ if (!Found) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Loop until we find a suitable link or there are no more on the file.
+ //
+
+ do {
+
+ FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context ));
+
+ //
+ // If there is a parent and this attribute has the same parent we are
+ // done. Our caller will always have acquired the ParentScb.
+ //
+
+ if (ARGUMENT_PRESENT( ParentScb )) {
+
+ if (NtfsEqualMftRef( &FileNameAttr->ParentDirectory,
+ &ParentScb->Fcb->FileReference )) {
+
+ ASSERT_SHARED_SCB( ParentScb );
+ break;
+ }
+
+ //
+ // If this is the parent of this link is the root then
+ // acquire the root directory.
+ //
+
+ } else if (NtfsEqualMftRef( &FileNameAttr->ParentDirectory,
+ &Vcb->RootIndexScb->Fcb->FileReference )) {
+
+ ParentScb = Vcb->RootIndexScb;
+ NtfsAcquireSharedScbForTransaction( IrpContext, ParentScb );
+ break;
+
+ //
+ // We have a link for this file. If we weren't given a parent
+ // Scb then create one here.
+ //
+
+ } else if (!ARGUMENT_PRESENT( ParentScb )) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ ParentFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ FileNameAttr->ParentDirectory,
+ FALSE,
+ TRUE,
+ &ReturnedExistingFcb );
+
+ ParentFcb->ReferenceCount += 1;
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, TRUE, TRUE )) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsAcquireExclusiveFcb( IrpContext, ParentFcb, NULL, TRUE, FALSE );
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ }
+
+ ParentFcb->ReferenceCount -= 1;
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ ParentScb = NtfsCreateScb( IrpContext,
+ ParentFcb,
+ $INDEX_ALLOCATION,
+ &NtfsFileNameIndex,
+ FALSE,
+ NULL );
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ break;
+ }
+
+ } while (Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &Context ));
+
+ //
+ // If we didn't find anything then return.
+ //
+
+ if (!Found) { leave; }
+ }
+
+ //
+ // Now update the filename in the parent index.
+ //
+
+ NtfsUpdateFileNameInIndex( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ &Fcb->Info,
+ QuickIndex );
+
+ //
+ // If this filename is either NTFS-ONLY or DOS-ONLY then
+ // we need to find the other link.
+ //
+
+ if ((FileNameAttr->Flags == FILE_NAME_NTFS) ||
+ (FileNameAttr->Flags == FILE_NAME_DOS)) {
+
+ //
+ // Find out which flag we should be looking for.
+ //
+
+ if (FlagOn( FileNameAttr->Flags, FILE_NAME_NTFS )) {
+
+ FileNameFlags = FILE_NAME_DOS;
+
+ } else {
+
+ FileNameFlags = FILE_NAME_NTFS;
+ }
+
+ if (!ARGUMENT_PRESENT( Lcb )) {
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+ }
+
+ //
+ // Now scan for the filename attribute we need.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &Context );
+
+ while (Found) {
+
+ FileNameAttr = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context ));
+
+ if (FileNameAttr->Flags == FileNameFlags) {
+
+ break;
+ }
+
+ Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &Context );
+ }
+
+ //
+ // We should have found the entry.
+ //
+
+ if (!Found) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ NtfsUpdateFileNameInIndex( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ &Fcb->Info,
+ NULL );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsUpdateDuplicateInfo );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ //
+ // Cleanup the attribute context for this attribute search.
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+
+ //
+ // If we created the ParentFcb here then release it and
+ // call teardown on it.
+ //
+
+ if (!ReturnedExistingFcb) {
+
+ NtfsTeardownStructures( IrpContext,
+ ParentFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateLcbDuplicateInfo (
+ IN PFCB Fcb,
+ IN PLCB Lcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called after updating duplicate information via an Lcb.
+ We want to clear the info flags for this Lcb and any complementary Lcb
+ it may be part of. We also want to OR in the Info flags in the Fcb with
+ any other Lcb's attached to the Fcb so we will update those in a timely
+ fashion as well.
+
+Arguments:
+
+ Fcb - Fcb for the file.
+
+ Lcb - Lcb used to update duplicate information. It may not be present but
+ that would be a rare case and we will perform that test here.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ UCHAR FileNameFlags;
+ PLCB NextLcb;
+ PLIST_ENTRY Links;
+
+ PAGED_CODE();
+
+ //
+ // No work to do unless we were passed an Lcb.
+ //
+
+ if (Lcb != NULL) {
+
+ //
+ // Check if this is an NTFS only or DOS only link.
+ //
+
+ if (Lcb->FileNameAttr->Flags == FILE_NAME_NTFS) {
+
+ FileNameFlags = FILE_NAME_DOS;
+
+ } else if (Lcb->FileNameAttr->Flags == FILE_NAME_DOS) {
+
+ FileNameFlags = FILE_NAME_NTFS;
+
+ } else {
+
+ FileNameFlags = (UCHAR) -1;
+ }
+
+ Lcb->InfoFlags = 0;
+
+ Links = Fcb->LcbQueue.Flink;
+
+ do {
+
+ NextLcb = CONTAINING_RECORD( Links,
+ LCB,
+ FcbLinks );
+
+ if (NextLcb != Lcb) {
+
+ if (NextLcb->FileNameAttr->Flags == FileNameFlags) {
+
+ NextLcb->InfoFlags = 0;
+
+ } else {
+
+ SetFlag( NextLcb->InfoFlags, Fcb->InfoFlags );
+ }
+ }
+
+ Links = Links->Flink;
+
+ } while (Links != &Fcb->LcbQueue);
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateFcb (
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when a timestamp may be updated on an Fcb which
+ may have no open handles. We always update the last change time to the
+ current time as well as last access and last write.
+
+Arguments:
+
+ Fcb - Fcb for the file.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ //
+ // We need to update the parent directory's time stamps
+ // to reflect this change.
+ //
+
+ KeQuerySystemTime( (PLARGE_INTEGER)&Fcb->Info.LastChangeTime );
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ SetFlag( Fcb->InfoFlags,
+ FCB_INFO_CHANGED_LAST_CHANGE | FCB_INFO_CHANGED_LAST_MOD );
+
+ Fcb->CurrentLastAccess =
+ Fcb->Info.LastModificationTime = Fcb->Info.LastChangeTime;
+
+ return;
+}
+
+
+VOID
+NtfsAddLink (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN CreatePrimaryLink,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN PFILE_NAME FileNameAttr,
+ IN PBOOLEAN LogIt OPTIONAL,
+ OUT PUCHAR FileNameFlags,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ IN PNAME_PAIR NamePair OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds a link to a file by adding the filename attribute
+ for the filename to the file and inserting the name in the parent Scb
+ index. If we are creating the primary link for the file and need
+ to generate an auxilary name, we will do that here. Use the optional
+ NamePair to suggest auxilary names if provided.
+
+Arguments:
+
+ CreatePrimaryLink - Indicates if we are creating the main Ntfs name
+ for the file.
+
+ ParentScb - This is the Scb to add the index entry for this link to.
+
+ Fcb - This is the file to add the hard link to.
+
+ FileNameAttr - File name attribute which is guaranteed only to have the
+ name in it.
+
+ LogIt - Indicates whether we should log the creation of this name. If not
+ specified then we always log the name creation. On exit we will
+ update this to TRUE if we logged the name creation because it
+ might cause a split.
+
+ FileNameFlags - We return the file name flags we use to create the link.
+
+ QuickIndex - If specified, supplies a pointer to a quik lookup structure
+ to be updated by this routine.
+
+ NamePair - If specified, supplies names that will be checked first as
+ possible auxilary names
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ BOOLEAN LocalLogIt = TRUE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddLink: Entered\n") );
+
+ if (!ARGUMENT_PRESENT( LogIt )) {
+
+ LogIt = &LocalLogIt;
+ }
+
+ *FileNameFlags = 0;
+
+ //
+ // Next add this entry to parent. It is possible that this is a link,
+ // an Ntfs name, a DOS name or Ntfs/Dos name. We use the filename
+ // attribute structure from earlier, but need to add more information.
+ //
+
+ FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference;
+
+ RtlCopyMemory( &FileNameAttr->Info,
+ &Fcb->Info,
+ sizeof( DUPLICATED_INFORMATION ));
+
+ FileNameAttr->Flags = 0;
+
+ //
+ // We will override the CreatePrimaryLink with the value in the
+ // registry.
+ //
+
+ NtfsAddNameToParent( IrpContext,
+ ParentScb,
+ Fcb,
+ (BOOLEAN)((FlagOn( NtfsData.Flags,
+ NTFS_FLAGS_CREATE_8DOT3_NAMES ) &&
+ CreatePrimaryLink) ? TRUE : FALSE),
+ LogIt,
+ FileNameAttr,
+ FileNameFlags,
+ QuickIndex,
+ NamePair );
+
+ //
+ // If the name is Ntfs only, we need to generate the DOS name.
+ //
+
+ if (*FileNameFlags == FILE_NAME_NTFS) {
+
+ UNICODE_STRING NtfsName;
+
+ NtfsName.Length = (USHORT)(FileNameAttr->FileNameLength * sizeof(WCHAR));
+ NtfsName.Buffer = FileNameAttr->FileName;
+
+ NtfsAddDosOnlyName( IrpContext,
+ ParentScb,
+ Fcb,
+ NtfsName,
+ *LogIt,
+ (NamePair ? &NamePair->Short : NULL) );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddLink: Exit\n") );
+
+ return;
+}
+
+
+VOID
+NtfsRemoveLink (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB ParentScb,
+ IN UNICODE_STRING LinkName,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes a hard link to a file by removing the filename attribute
+ for the filename from the file and removing the name from the parent Scb
+ index. It will also remove the other half of a primary link pair.
+
+ A name pair may be used to capture the names.
+
+Arguments:
+
+ Fcb - This is the file to remove the hard link from
+
+ ParentScb - This is the Scb to remove the index entry for this link from
+
+ LinkName - This is the file name to remove. It will be exact case.
+
+ NamePair - optional name pair for capture
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PFILE_NAME FoundFileName;
+ UCHAR FileNameFlags;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRemoveLink: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Now loop through the filenames and find a match.
+ // We better find at least one.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Now keep looking until we find a match.
+ //
+
+ while (TRUE) {
+
+ FoundFileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ //
+ // Do an exact memory comparison.
+ //
+
+ if ((*(PLONGLONG)&FoundFileName->ParentDirectory ==
+ *(PLONGLONG)&ParentScb->Fcb->FileReference ) &&
+
+ ((FoundFileName->FileNameLength * sizeof( WCHAR )) == (ULONG)LinkName.Length) &&
+
+ (RtlEqualMemory( LinkName.Buffer,
+ FoundFileName->FileName,
+ LinkName.Length ))) {
+
+ break;
+ }
+
+ //
+ // Get the next filename attribute.
+ //
+
+ if (!NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+ }
+
+ //
+ // Capture the name into caller's area
+ //
+
+ if (ARGUMENT_PRESENT(NamePair)) {
+
+ NtfsCopyNameToNamePair( NamePair,
+ FoundFileName->FileName,
+ FoundFileName->FileNameLength,
+ FoundFileName->Flags );
+ }
+
+ //
+ // Now delete the name from the parent Scb.
+ //
+
+ NtfsDeleteIndexEntry( IrpContext,
+ ParentScb,
+ FoundFileName,
+ &Fcb->FileReference );
+
+ //
+ // Remember the filename flags for this entry.
+ //
+
+ FileNameFlags = FoundFileName->Flags;
+
+ //
+ // Now delete the entry.
+ //
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &AttrContext );
+
+ //
+ // If the link is a partial link, we need to remove the second
+ // half of the link.
+ //
+
+ if (FlagOn( FileNameFlags, (FILE_NAME_NTFS | FILE_NAME_DOS) )
+ && (FileNameFlags != (FILE_NAME_NTFS | FILE_NAME_DOS))) {
+
+ NtfsRemoveLinkViaFlags( IrpContext,
+ Fcb,
+ ParentScb,
+ (UCHAR)(FlagOn( FileNameFlags, FILE_NAME_NTFS )
+ ? FILE_NAME_DOS
+ : FILE_NAME_NTFS),
+ NamePair );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsRemoveLink );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsRemoveLink: Exit\n") );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsRemoveLinkViaFlags (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb,
+ IN UCHAR FileNameFlags,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to remove only a Dos name or only an Ntfs name. We
+ already must know that these will be described by separate filename attributes.
+
+ A name pair may be used to capture the name.
+
+Arguments:
+
+ Fcb - This is the file to remove the hard link from
+
+ ParentScb - This is the Scb to remove the index entry for this link from
+
+ FileNameFlags - This is the single name flag that we must match exactly.
+
+ NamePair - optional name pair for capture
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PFILE_NAME FileNameAttr;
+
+ PFILE_NAME FoundFileName;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRemoveLinkViaFlags: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ FileNameAttr = NULL;
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Now loop through the filenames and find a match.
+ // We better find at least one.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb @ %08lx\n", Fcb) );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Now keep looking until we find a match.
+ //
+
+ while (TRUE) {
+
+ FoundFileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ //
+ // Check for an exact flag match.
+ //
+
+ if ((*(PLONGLONG)&FoundFileName->ParentDirectory ==
+ *(PLONGLONG)&Scb->Fcb->FileReference) &&
+
+ (FoundFileName->Flags == FileNameFlags)) {
+
+
+ break;
+ }
+
+ //
+ // Get the next filename attribute.
+ //
+
+ if (!NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find filename attribute Fcb@ %08lx\n", Fcb) );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+ }
+
+ //
+ // Capture the name into caller's area
+ //
+
+ if (ARGUMENT_PRESENT(NamePair)) {
+
+ NtfsCopyNameToNamePair( NamePair,
+ FoundFileName->FileName,
+ FoundFileName->FileNameLength,
+ FoundFileName->Flags );
+
+ }
+
+ FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME )
+ + (FoundFileName->FileNameLength << 1));
+
+ //
+ // We build the file name attribute for the search.
+ //
+
+ RtlCopyMemory( FileNameAttr,
+ FoundFileName,
+ NtfsFileNameSize( FoundFileName ));
+
+ //
+ // Now delete the entry.
+ //
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &AttrContext );
+
+ //
+ // Now delete the name from the parent Scb.
+ //
+
+ NtfsDeleteIndexEntry( IrpContext,
+ Scb,
+ FileNameAttr,
+ &Fcb->FileReference );
+
+ } finally {
+
+ DebugUnwind( NtfsRemoveLinkViaFlags );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ NtfsFreePool( FileNameAttr );
+
+ DebugTrace( -1, Dbg, ("NtfsRemoveLinkViaFlags: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// This routine is intended only for RESTART.
+//
+
+VOID
+NtfsRestartInsertAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID ValueOrMappingPairs OPTIONAL,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs a simple insert of an attribute record into a
+ file record, without worrying about Bcbs or logging.
+
+Arguments:
+
+ FileRecord - File record into which the attribute is to be inserted.
+
+ RecordOffset - ByteOffset within the file record at which insert is to occur.
+
+ Attribute - The attribute record to be inserted.
+
+ AttributeName - May pass an optional attribute name in the running system
+ only.
+
+ ValueOrMappingPairs - May pass a value or mapping pairs pointer in the
+ running system only.
+
+ Length - Length of the value or mapping pairs array in bytes - nonzero in
+ the running system only. If nonzero and the above pointer is NULL,
+ then a value is to be zeroed.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVOID From, To;
+ ULONG MoveLength;
+ ULONG AttributeHeaderSize;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartInsertAttribute\n") );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
+ DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) );
+
+ //
+ // First make room for the attribute
+ //
+
+ From = (PCHAR)FileRecord + RecordOffset;
+ To = (PCHAR)From + Attribute->RecordLength;
+ MoveLength = FileRecord->FirstFreeByte - RecordOffset;
+
+ RtlMoveMemory( To, From, MoveLength );
+
+ //
+ // If there is either an attribute name or Length is nonzero, then
+ // we are in the running system, and we are to assemble the attribute
+ // in place.
+ //
+
+ if ((Length != 0) || ARGUMENT_PRESENT(AttributeName)) {
+
+ //
+ // First move the attribute header in.
+ //
+
+ if (Attribute->FormCode == RESIDENT_FORM) {
+
+ AttributeHeaderSize = SIZEOF_RESIDENT_ATTRIBUTE_HEADER;
+
+ } else if (Attribute->NameOffset != 0) {
+
+ AttributeHeaderSize = Attribute->NameOffset;
+
+ } else {
+
+ AttributeHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset;
+ }
+
+ RtlCopyMemory( From,
+ Attribute,
+ AttributeHeaderSize );
+
+ if (ARGUMENT_PRESENT(AttributeName)) {
+
+ RtlCopyMemory( (PCHAR)From + Attribute->NameOffset,
+ AttributeName->Buffer,
+ AttributeName->Length );
+ }
+
+ //
+ // If a value was specified, move it in. Else the caller just wants us
+ // to clear for that much.
+ //
+
+ if (ARGUMENT_PRESENT(ValueOrMappingPairs)) {
+
+ RtlCopyMemory( (PCHAR)From +
+ ((Attribute->FormCode == RESIDENT_FORM) ?
+ Attribute->Form.Resident.ValueOffset :
+ Attribute->Form.Nonresident.MappingPairsOffset),
+ ValueOrMappingPairs,
+ Length );
+
+ //
+ // Only the resident form will pass a NULL pointer.
+ //
+
+ } else {
+
+ RtlZeroMemory( (PCHAR)From + Attribute->Form.Resident.ValueOffset,
+ Length );
+ }
+
+ //
+ // For the restart case, we really only have to insert the attribute.
+ // (Note we can also hit this case in the running system when a resident
+ // attribute is being created with no name and a null value.)
+ //
+
+ } else {
+
+ //
+ // Now move the attribute in.
+ //
+
+ RtlCopyMemory( From, Attribute, Attribute->RecordLength );
+ }
+
+
+ //
+ // Update the file record.
+ //
+
+ FileRecord->FirstFreeByte += Attribute->RecordLength;
+
+ //
+ // We only need to do this if we would be incrementing the instance
+ // number. In the abort or restart case, we don't need to do this.
+ //
+
+ if (FileRecord->NextAttributeInstance <= Attribute->Instance) {
+
+ FileRecord->NextAttributeInstance = Attribute->Instance + 1;
+ }
+
+ //
+ // Remember to increment the reference count if this attribute is indexed.
+ //
+
+ if (FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED)) {
+ FileRecord->ReferenceCount += 1;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsRestartInsertAttribute -> VOID\n") );
+
+ return;
+}
+
+
+//
+// This routine is intended only for RESTART.
+//
+
+VOID
+NtfsRestartRemoveAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs a simple remove of an attribute record from a
+ file record, without worrying about Bcbs or logging.
+
+Arguments:
+
+ FileRecord - File record from which the attribute is to be removed.
+
+ RecordOffset - ByteOffset within the file record at which remove is to occur.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartRemoveAttribute\n") );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
+
+ //
+ // Calculate the address of the attribute we are removing.
+ //
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+
+ //
+ // Reduce first free byte by the amount we removed.
+ //
+
+ FileRecord->FirstFreeByte -= Attribute->RecordLength;
+
+ //
+ // Remember to decrement the reference count if this attribute is indexed.
+ //
+
+ if (FlagOn(Attribute->Form.Resident.ResidentFlags, RESIDENT_FORM_INDEXED)) {
+ FileRecord->ReferenceCount -= 1;
+ }
+
+ //
+ // Remove the attribute by moving the rest of the record down.
+ //
+
+ RtlMoveMemory( Attribute,
+ (PCHAR)Attribute + Attribute->RecordLength,
+ FileRecord->FirstFreeByte - RecordOffset );
+
+ DebugTrace( -1, Dbg, ("NtfsRestartRemoveAttribute -> VOID\n") );
+
+ return;
+}
+
+
+//
+// This routine is intended only for RESTART.
+//
+
+VOID
+NtfsRestartChangeAttributeSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN ULONG NewRecordLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the size of an attribute, and makes the related
+ changes in the attribute record.
+
+Arguments:
+
+ FileRecord - Pointer to the file record in which the attribute resides.
+
+ Attribute - Pointer to the attribute whose size is changing.
+
+ NewRecordLength - New attribute record length.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONG SizeChange = NewRecordLength - Attribute->RecordLength;
+ PVOID AttributeEnd = Add2Ptr(Attribute, Attribute->RecordLength);
+
+ UNREFERENCED_PARAMETER( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartChangeAttributeSize\n") );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) );
+ DebugTrace( 0, Dbg, ("NewRecordLength = %08lx\n", NewRecordLength) );
+
+ //
+ // First move the end of the file record after the attribute we are changing.
+ //
+
+ RtlMoveMemory( Add2Ptr(Attribute, NewRecordLength),
+ AttributeEnd,
+ FileRecord->FirstFreeByte - PtrOffset(FileRecord, AttributeEnd) );
+
+ //
+ // Now update the file and attribute records.
+ //
+
+ FileRecord->FirstFreeByte += SizeChange;
+ Attribute->RecordLength = NewRecordLength;
+
+ DebugTrace( -1, Dbg, ("NtfsRestartChangeAttributeSize -> VOID\n") );
+}
+
+
+//
+// This routine is intended only for RESTART.
+//
+
+VOID
+NtfsRestartChangeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN PVOID Data,
+ IN ULONG Length,
+ IN BOOLEAN SetNewLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs a simple change of an attribute value in a
+ file record, without worrying about Bcbs or logging.
+
+Arguments:
+
+ FileRecord - File record in which the attribute is to be changed.
+
+ RecordOffset - ByteOffset within the file record at which the attribute starts.
+
+ AttributeOffset - Offset within the attribute record at which data is to
+ be changed.
+
+ Data - Pointer to the new data.
+
+ Length - Length of the new data.
+
+ SetNewLength - TRUE if the attribute length should be changed.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ BOOLEAN AlreadyMoved = FALSE;
+ BOOLEAN DataInFileRecord = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartChangeValue\n") );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
+ DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) );
+ DebugTrace( 0, Dbg, ("Data = %08lx\n", Data) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+ DebugTrace( 0, Dbg, ("SetNewLength = %02lx\n", SetNewLength) );
+
+ //
+ // Calculate the address of the attribute being changed.
+ //
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ ASSERT( RecordOffset == QuadAlign( RecordOffset ));
+
+ //
+ // First, if we are setting a new length, then move the data after the
+ // attribute record and change FirstFreeByte accordingly.
+ //
+
+ if (SetNewLength) {
+
+ ULONG NewLength = QuadAlign( AttributeOffset + Length );
+
+ //
+ // If we are shrinking the attribute, we need to move the data
+ // first to support caller's who are shifting data down in the
+ // attribute value, like DeleteFromAttributeList. If we were
+ // to shrink the record first in this case, we would clobber some
+ // of the data to be moved down.
+ //
+
+ if (NewLength < Attribute->RecordLength) {
+
+ //
+ // Now move the new data in and remember we moved it.
+ //
+
+ AlreadyMoved = TRUE;
+
+ //
+ // If there is data to modify do so now.
+ //
+
+ if (Length != 0) {
+
+ if (ARGUMENT_PRESENT(Data)) {
+
+ RtlMoveMemory( (PCHAR)Attribute + AttributeOffset, Data, Length );
+
+ } else {
+
+ RtlZeroMemory( (PCHAR)Attribute + AttributeOffset, Length );
+ }
+ }
+ }
+
+ //
+ // First move the tail of the file record to make/eliminate room.
+ //
+
+ RtlMoveMemory( Add2Ptr( Attribute, NewLength ),
+ Add2Ptr( Attribute, Attribute->RecordLength ),
+ FileRecord->FirstFreeByte - RecordOffset - Attribute->RecordLength );
+
+ //
+ // Now update fields to reflect the change.
+ //
+
+ FileRecord->FirstFreeByte += (NewLength - Attribute->RecordLength);
+
+ Attribute->RecordLength = NewLength;
+ Attribute->Form.Resident.ValueLength =
+ (USHORT)(AttributeOffset + Length -
+ (ULONG)Attribute->Form.Resident.ValueOffset);
+ }
+
+ //
+ // Now move the new data in.
+ //
+
+ if (!AlreadyMoved) {
+
+ if (ARGUMENT_PRESENT(Data)) {
+
+ RtlMoveMemory( Add2Ptr( Attribute, AttributeOffset ),
+ Data,
+ Length );
+
+ } else {
+
+ RtlZeroMemory( Add2Ptr( Attribute, AttributeOffset ),
+ Length );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsRestartChangeValue -> VOID\n") );
+
+ return;
+}
+
+
+//
+// This routine is intended only for RESTART.
+//
+
+VOID
+NtfsRestartChangeMapping (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN PVOID Data,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs a simple change of an attribute's mapping pairs in a
+ file record, without worrying about Bcbs or logging.
+
+Arguments:
+
+ Vcb - Vcb for volume
+
+ FileRecord - File record in which the attribute is to be changed.
+
+ RecordOffset - ByteOffset within the file record at which the attribute starts.
+
+ AttributeOffset - Offset within the attribute record at which mapping is to
+ be changed.
+
+ Data - Pointer to the new mapping.
+
+ Length - Length of the new mapping.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ VCN HighestVcn;
+ PCHAR MappingPairs;
+ ULONG NewLength = QuadAlign( AttributeOffset + Length );
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ UNREFERENCED_PARAMETER( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartChangeMapping\n") );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
+ DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) );
+ DebugTrace( 0, Dbg, ("Data = %08lx\n", Data) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // Calculate the address of the attribute being changed.
+ //
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord + RecordOffset);
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ ASSERT( RecordOffset == QuadAlign( RecordOffset ));
+
+ //
+ // First, if we are setting a new length, then move the data after the
+ // attribute record and change FirstFreeByte accordingly.
+ //
+
+ //
+ // First move the tail of the file record to make/eliminate room.
+ //
+
+ RtlMoveMemory( (PCHAR)Attribute + NewLength,
+ (PCHAR)Attribute + Attribute->RecordLength,
+ FileRecord->FirstFreeByte - RecordOffset -
+ Attribute->RecordLength );
+
+ //
+ // Now update fields to reflect the change.
+ //
+
+ FileRecord->FirstFreeByte += NewLength -
+ Attribute->RecordLength;
+
+ Attribute->RecordLength = NewLength;
+
+ //
+ // Now move the new data in.
+ //
+
+ RtlCopyMemory( (PCHAR)Attribute + AttributeOffset, Data, Length );
+
+
+ //
+ // Finally update HighestVcn and (optionally) AllocatedLength fields.
+ //
+
+ MappingPairs = (PCHAR)Attribute + (ULONG)Attribute->Form.Nonresident.MappingPairsOffset;
+ HighestVcn = NtfsGetHighestVcn( IrpContext,
+ Attribute->Form.Nonresident.LowestVcn,
+ MappingPairs );
+
+ Attribute->Form.Nonresident.HighestVcn = HighestVcn;
+
+ DebugTrace( -1, Dbg, ("NtfsRestartChangeMapping -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsAddToAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN MFT_SEGMENT_REFERENCE SegmentReference,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adds an attribute list entry for a newly inserted attribute.
+ It is assumed that the context variable is pointing to the attribute
+ record in the file record where it has been inserted, and also to the place
+ in the attribute list where the new attribute list entry is to be inserted.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ SegmentReference - Segment reference of the file record the new attribute
+ is in.
+
+ Context - Describes the current attribute.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ //
+ // Allocate an attribute list entry which hopefully has enough space
+ // for the name.
+ //
+
+ struct {
+
+ ATTRIBUTE_LIST_ENTRY EntryBuffer;
+
+ WCHAR Name[10];
+
+ } NewEntry;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT ListContext;
+ BOOLEAN FoundListContext;
+
+ ULONG EntrySize;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_LIST_ENTRY ListEntry = &NewEntry.EntryBuffer;
+ BOOLEAN SetNewLength = TRUE;
+
+ ULONG EntryOffset;
+ ULONG BeyondEntryOffset;
+
+ PAGED_CODE();
+
+ //
+ // First construct the attribute list entry.
+ //
+
+ FileRecord = NtfsContainingFileRecord( Context );
+ Attribute = NtfsFoundAttribute( Context );
+ EntrySize = QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName )
+ + ((ULONG) Attribute->NameLength << 1));
+
+ //
+ // Allocate the list entry if the one we have is not big enough.
+ //
+
+ if (EntrySize > sizeof(NewEntry)) {
+
+ ListEntry = (PATTRIBUTE_LIST_ENTRY)NtfsAllocatePool( NonPagedPool,
+ EntrySize );
+ }
+
+ RtlZeroMemory( ListEntry, EntrySize );
+
+ NtfsInitializeAttributeContext( &ListContext );
+
+ //
+ // Use try-finally to insure cleanup.
+ //
+
+ try {
+
+ ULONG OldQuadAttrListSize;
+ PATTRIBUTE_RECORD_HEADER ListAttribute;
+ PFILE_RECORD_SEGMENT_HEADER ListFileRecord;
+
+ //
+ // Now fill in the list entry.
+ //
+
+ ListEntry->AttributeTypeCode = Attribute->TypeCode;
+ ListEntry->RecordLength = (USHORT)EntrySize;
+ ListEntry->AttributeNameLength = Attribute->NameLength;
+ ListEntry->Instance = Attribute->Instance;
+ ListEntry->AttributeNameOffset =
+ (UCHAR)PtrOffset( ListEntry, &ListEntry->AttributeName[0] );
+
+ if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ ListEntry->LowestVcn = Attribute->Form.Nonresident.LowestVcn;
+ }
+
+ ListEntry->SegmentReference = SegmentReference;
+
+ if (Attribute->NameLength != 0) {
+
+ RtlCopyMemory( &ListEntry->AttributeName[0],
+ Add2Ptr(Attribute, Attribute->NameOffset),
+ Attribute->NameLength << 1 );
+ }
+
+ //
+ // Lookup the list context so that we can modify the attribute list.
+ //
+
+ FoundListContext =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $ATTRIBUTE_LIST,
+ &ListContext );
+
+ ASSERT( FoundListContext );
+
+ ListAttribute = NtfsFoundAttribute( &ListContext );
+ ListFileRecord = NtfsContainingFileRecord( &ListContext );
+
+ OldQuadAttrListSize = ListAttribute->RecordLength;
+
+ //
+ // Remember the relative offsets of list entries.
+ //
+
+ EntryOffset = (ULONG) PtrOffset( Context->AttributeList.FirstEntry,
+ Context->AttributeList.Entry );
+
+ BeyondEntryOffset = (ULONG) PtrOffset( Context->AttributeList.FirstEntry,
+ Context->AttributeList.BeyondFinalEntry );
+
+ //
+ // If this operation is possibly going to make the attribute list go
+ // non-resident, or else move other attributes around, then we will
+ // reserve the space first in the attribute list and then map the
+ // value. Note that some of the entries we need to shift up may
+ // be modified as a side effect of making space!
+ //
+
+ if (NtfsIsAttributeResident( ListAttribute )
+ && (ListFileRecord->BytesAvailable - ListFileRecord->FirstFreeByte) < EntrySize) {
+
+ ULONG Length;
+
+ //
+ // Add enough zeros to the end of the attribute to accommodate
+ // the new attribute list entry.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ BeyondEntryOffset,
+ NULL,
+ EntrySize,
+ TRUE,
+ TRUE,
+ FALSE,
+ TRUE,
+ &ListContext );
+
+ //
+ // We now don't have to set the new length.
+ //
+
+ SetNewLength = FALSE;
+
+ //
+ // In case the attribute list went non-resident on this call, then we
+ // need to update both list entry pointers in the found attribute.
+ // (We do this "just in case" all the time to avoid a rare code path.)
+ //
+
+ //
+ // Map the non-resident attribute list.
+ //
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *) &Context->AttributeList.FirstEntry,
+ &Length,
+ &Context->AttributeList.NonresidentListBcb,
+ &ListContext );
+
+ //
+ // If the list is still resident then unpin the current Bcb in
+ // the original context to keep our pin counts in sync.
+ //
+
+ if (Context->AttributeList.Bcb == Context->AttributeList.NonresidentListBcb) {
+
+ NtfsUnpinBcb( &Context->AttributeList.NonresidentListBcb );
+ }
+
+ Context->AttributeList.Entry = Add2Ptr( Context->AttributeList.FirstEntry,
+ EntryOffset );
+
+ Context->AttributeList.BeyondFinalEntry = Add2Ptr( Context->AttributeList.FirstEntry,
+ BeyondEntryOffset );
+ }
+
+ //
+ // Check for adding duplicate entries...
+ //
+
+ ASSERT( ((EntryOffset == 0) ||
+ (!RtlEqualMemory((PVOID)((PCHAR)Context->AttributeList.Entry - EntrySize),
+ ListEntry,
+ EntrySize)))
+
+ &&
+
+ ((BeyondEntryOffset == EntryOffset) ||
+ (!RtlEqualMemory(Context->AttributeList.Entry,
+ ListEntry,
+ EntrySize))) );
+
+ //
+ // Now shift the old contents up to make room for our new entry. We don't let
+ // the attribute list grow larger than a cache view however.
+ //
+
+ if (EntrySize + BeyondEntryOffset > VACB_MAPPING_GRANULARITY) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ EntryOffset + EntrySize,
+ Context->AttributeList.Entry,
+ BeyondEntryOffset - EntryOffset,
+ SetNewLength,
+ TRUE,
+ FALSE,
+ TRUE,
+ &ListContext );
+
+ //
+ // Now write in the new entry.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ EntryOffset,
+ (PVOID)ListEntry,
+ EntrySize,
+ FALSE,
+ TRUE,
+ FALSE,
+ FALSE,
+ &ListContext );
+
+ //
+ // Reload the attribute list values from the list context.
+ //
+
+ ListAttribute = NtfsFoundAttribute( &ListContext );
+
+ //
+ // Now fix up the context for return
+ //
+
+ if (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0) {
+
+ //
+ // We need to update the attribute pointer for the target attribute
+ // by the amount of the change in the attribute list attribute.
+ //
+
+ Context->FoundAttribute.Attribute =
+ Add2Ptr( Context->FoundAttribute.Attribute,
+ ListAttribute->RecordLength - OldQuadAttrListSize );
+ }
+
+ Context->AttributeList.BeyondFinalEntry =
+ Add2Ptr( Context->AttributeList.BeyondFinalEntry, EntrySize );
+
+#if DBG
+{
+ PATTRIBUTE_LIST_ENTRY LastEntry, Entry;
+
+ for (LastEntry = Context->AttributeList.FirstEntry, Entry = NtfsGetNextRecord(LastEntry);
+ Entry < Context->AttributeList.BeyondFinalEntry;
+ LastEntry = Entry, Entry = NtfsGetNextRecord(LastEntry)) {
+
+ ASSERT( (LastEntry->RecordLength != Entry->RecordLength) ||
+ (!RtlEqualMemory(LastEntry, Entry, Entry->RecordLength)) );
+ }
+}
+#endif
+
+ } finally {
+
+ //
+ // If we had to allocate a list entry buffer, deallocate it.
+ //
+
+ if (ListEntry != &NewEntry.EntryBuffer) {
+
+ NtfsFreePool(ListEntry);
+ }
+
+ //
+ // Cleanup the enumeration context for the list entry.
+ //
+
+ NtfsCleanupAttributeContext( &ListContext);
+ }
+}
+
+
+VOID
+NtfsDeleteFromAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes an attribute list entry for a recently deleted attribute.
+ It is assumed that the context variable is pointing to the place in
+ the attribute list where the attribute list entry is to be deleted.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ Context - Describes the current attribute.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT ListContext;
+ BOOLEAN FoundListContext;
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_LIST_ENTRY ListEntry, NextListEntry;
+ ULONG EntrySize;
+
+ ULONG SavedListSize;
+
+ PAGED_CODE();
+
+ FileRecord = NtfsContainingFileRecord( Context );
+
+ //
+ // Lookup the list context so that we can modify the attribute list.
+ //
+
+ NtfsInitializeAttributeContext( &ListContext );
+ FoundListContext =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $ATTRIBUTE_LIST,
+ &ListContext );
+
+ ASSERT(FoundListContext);
+
+ //
+ // Use try-finally to insure cleanup.
+ //
+
+ try {
+
+ SavedListSize = NtfsFoundAttribute(&ListContext)->RecordLength;
+
+ //
+ // Now shift the old contents down to make room for our new entry.
+ //
+
+ ListEntry = Context->AttributeList.Entry;
+ EntrySize = ListEntry->RecordLength;
+ NextListEntry = Add2Ptr(ListEntry, EntrySize);
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ PtrOffset( Context->AttributeList.FirstEntry,
+ Context->AttributeList.Entry ),
+ NextListEntry,
+ PtrOffset( NextListEntry,
+ Context->AttributeList.BeyondFinalEntry ),
+ TRUE,
+ TRUE,
+ FALSE,
+ TRUE,
+ &ListContext );
+
+ //
+ // Now fix up the context for return
+ //
+
+ if (*(PLONGLONG)&FileRecord->BaseFileRecordSegment == 0) {
+
+ SavedListSize -= NtfsFoundAttribute(&ListContext)->RecordLength;
+ Context->FoundAttribute.Attribute =
+ Add2Ptr( Context->FoundAttribute.Attribute, -(LONG)SavedListSize );
+ }
+
+ Context->AttributeList.BeyondFinalEntry =
+ Add2Ptr( Context->AttributeList.BeyondFinalEntry, -(LONG)EntrySize );
+
+ } finally {
+
+ //
+ // Cleanup the enumeration context for the list entry.
+ //
+
+ NtfsCleanupAttributeContext(&ListContext);
+ }
+}
+
+
+BOOLEAN
+NtfsRewriteMftMapping (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to rewrite the mapping for the Mft file. This is done
+ in the case where either hot-fixing or Mft defragging has caused us to spill
+ into the reserved area of a file record. This routine will rewrite the
+ mapping from the beginning, using the reserved record if necessary. On return
+ it will indicate whether any work was done and if there is more work to do.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume to defrag.
+
+ ExcessMapping - Address to store whether there is still excess mapping in
+ the file.
+
+Return Value:
+
+ BOOLEAN - TRUE if we made any changes to the file. FALSE if we found no
+ work to do.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PUCHAR MappingPairs = NULL;
+ PBCB FileRecordBcb = NULL;
+
+ BOOLEAN MadeChanges = FALSE;
+ BOOLEAN ExcessMapping = FALSE;
+ BOOLEAN LastFileRecord = FALSE;
+ BOOLEAN SkipLookup = FALSE;
+
+ PAGED_CODE();
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ VCN CurrentVcn; // Starting Vcn for the next file record
+ VCN MinimumVcn; // This Vcn must be in the current mapping
+ VCN LastVcn; // Last Vcn in the current mapping
+ VCN LastMftVcn; // Last Vcn in the file
+ VCN NextVcn; // First Vcn past the end of the mapping
+
+ ULONG ReservedIndex; // Reserved index in Mft
+ ULONG NextIndex; // Next file record available for Mft mapping
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ MFT_SEGMENT_REFERENCE FileRecordReference;
+ ULONG RecordOffset;
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ULONG AttributeOffset;
+
+ ULONG MappingSizeAvailable;
+ ULONG MappingPairsSize;
+
+ //
+ // Find the initial file record for the Mft.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Vcb->MftScb, NULL, &AttrContext );
+
+ //
+ // Compute some initial values. If this is the only file record
+ // for the file then we are done.
+ //
+
+ ReservedIndex = Vcb->MftScb->ScbType.Mft.ReservedIndex;
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+
+ LastMftVcn = Int64ShraMod32(Vcb->MftScb->Header.AllocationSize.QuadPart, Vcb->ClusterShift) - 1;
+
+ CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1;
+
+ if (CurrentVcn >= LastMftVcn) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Loop while there are more file records. We will insert any
+ // additional file records needed within the loop so that this
+ // call should succeed until the remapping is done.
+ //
+
+ while (SkipLookup ||
+ NtfsLookupNextAttributeForScb( IrpContext,
+ Vcb->MftScb,
+ &AttrContext )) {
+
+ BOOLEAN ReplaceFileRecord;
+ BOOLEAN ReplaceAttributeListEntry;
+
+ ReplaceAttributeListEntry = FALSE;
+
+ //
+ // If we just looked up this entry then pin the current
+ // attribute.
+ //
+
+ if (!SkipLookup) {
+
+ //
+ // Always pin the current attribute.
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Vcb,
+ &AttrContext );
+ }
+
+ //
+ // Extract some pointers from the current file record.
+ // Remember if this was the last record.
+ //
+
+ ReplaceFileRecord = FALSE;
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+ FileRecordReference = AttrContext.AttributeList.Entry->SegmentReference;
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+ AttributeOffset = Attribute->Form.Nonresident.MappingPairsOffset;
+
+ RecordOffset = PtrOffset( FileRecord, Attribute );
+
+ //
+ // Remember if we are at the last attribute.
+ //
+
+ if (Attribute->Form.Nonresident.HighestVcn == LastMftVcn) {
+
+ LastFileRecord = TRUE;
+ }
+
+ //
+ // If we have already remapped this entire file record then
+ // remove the attribute and it list entry.
+ //
+
+ if (!SkipLookup &&
+ (CurrentVcn > LastMftVcn)) {
+
+ PATTRIBUTE_LIST_ENTRY ListEntry;
+ ULONG Count;
+
+ Count = 0;
+
+ //
+ // We want to remove this entry and all subsequent entries.
+ //
+
+ ListEntry = AttrContext.AttributeList.Entry;
+
+ while ((ListEntry != AttrContext.AttributeList.BeyondFinalEntry) &&
+ (ListEntry->AttributeTypeCode == $DATA) &&
+ (ListEntry->AttributeNameLength == 0)) {
+
+ Count += 1;
+
+ NtfsDeallocateMftRecord( IrpContext,
+ Vcb,
+ NtfsUnsafeSegmentNumber( &ListEntry->SegmentReference ) );
+
+ NtfsDeleteFromAttributeList( IrpContext,
+ Vcb->MftScb->Fcb,
+ &AttrContext );
+
+ ListEntry = AttrContext.AttributeList.Entry;
+ }
+
+ //
+ // Clear out the reserved index in case one of these
+ // will do.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED );
+ ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ Vcb->MftScb->ScbType.Mft.ReservedIndex = 0;
+ try_return( NOTHING );
+ }
+
+ //
+ // Check if we are going to replace this file record with
+ // the reserved record.
+ //
+
+ if (ReservedIndex < NtfsSegmentNumber( &FileRecordReference )) {
+
+ PATTRIBUTE_RECORD_HEADER NewAttribute;
+ PATTRIBUTE_TYPE_CODE NewEnd;
+
+ //
+ // Remember this index for our computation for the Minimum mapped
+ // Vcn.
+ //
+
+ NextIndex = NtfsUnsafeSegmentNumber( &FileRecordReference );
+
+ FileRecord = NtfsCloneFileRecord( IrpContext,
+ Vcb->MftScb->Fcb,
+ TRUE,
+ &FileRecordBcb,
+ &FileRecordReference );
+
+ ReservedIndex = MAXULONG;
+
+ //
+ // Now lets create an attribute in the new file record.
+ //
+
+ NewAttribute = Add2Ptr( FileRecord,
+ FileRecord->FirstFreeByte
+ - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )));
+
+ NewAttribute->TypeCode = Attribute->TypeCode;
+ NewAttribute->RecordLength = SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+ NewAttribute->FormCode = NONRESIDENT_FORM;
+ NewAttribute->Flags = Attribute->Flags;
+ NewAttribute->Instance = FileRecord->NextAttributeInstance++;
+
+ NewAttribute->Form.Nonresident.LowestVcn = CurrentVcn;
+ NewAttribute->Form.Nonresident.HighestVcn = 0;
+ NewAttribute->Form.Nonresident.MappingPairsOffset = (USHORT) NewAttribute->RecordLength;
+
+ NewEnd = Add2Ptr( NewAttribute, NewAttribute->RecordLength );
+ *NewEnd = $END;
+
+ //
+ // Now fix up the file record with this new data.
+ //
+
+ FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd )
+ + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ FileRecord->SequenceNumber += 1;
+
+ if (FileRecord->SequenceNumber == 0) {
+
+ FileRecord->SequenceNumber = 1;
+ }
+
+ FileRecordReference.SequenceNumber = FileRecord->SequenceNumber;
+
+ //
+ // Now switch this new file record into the attribute context.
+ //
+
+ NtfsUnpinBcb( &NtfsFoundBcb( &AttrContext ));
+
+ NtfsFoundBcb( &AttrContext ) = FileRecordBcb;
+ AttrContext.FoundAttribute.MftFileOffset = LlBytesFromFileRecords( Vcb, NextIndex );
+ AttrContext.FoundAttribute.Attribute = NewAttribute;
+ AttrContext.FoundAttribute.FileRecord = FileRecord;
+
+ FileRecordBcb = NULL;
+
+ //
+ // Now add an attribute list entry for this entry.
+ //
+
+ NtfsAddToAttributeList( IrpContext,
+ Vcb->MftScb->Fcb,
+ FileRecordReference,
+ &AttrContext );
+
+ //
+ // Reload our pointers for this file record.
+ //
+
+ Attribute = NewAttribute;
+ AttributeOffset = SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+
+ RecordOffset = PtrOffset( FileRecord, Attribute );
+
+ //
+ // We must include either the last Vcn of the file or
+ // the Vcn for the next file record to use for the Mft.
+ // At this point MinimumVcn is the first Vcn that doesn't
+ // have to be in the current mapping.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ MinimumVcn = (NextIndex + 1) << Vcb->MftToClusterShift;
+
+ } else {
+
+ MinimumVcn = (NextIndex + Vcb->FileRecordsPerCluster - 1) << Vcb->MftToClusterShift;
+ }
+
+ ReplaceFileRecord = TRUE;
+
+ //
+ // We will be using the current attribute.
+ //
+
+ } else {
+
+ //
+ // The mapping we write into this page must go
+ // to the current end of the page or to the reserved
+ // or spare file record, whichever is earlier.
+ // If we are adding the reserved record to the end then
+ // we know the final Vcn already.
+ //
+
+ if (SkipLookup) {
+
+ NextVcn = LastMftVcn;
+
+ } else {
+
+ NextVcn = Attribute->Form.Nonresident.HighestVcn;
+ }
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ NextIndex = (ULONG)Int64ShraMod32((NextVcn + 1), Vcb->MftToClusterShift);
+
+ } else {
+
+ NextIndex = (ULONG)Int64ShllMod32((NextVcn + 1), Vcb->MftToClusterShift);
+ }
+
+ if (ReservedIndex < NextIndex) {
+
+ NextIndex = ReservedIndex + 1;
+ ReplaceFileRecord = TRUE;
+ }
+
+ //
+ // If we can use this file record unchanged then continue on.
+ // Start by checking that it starts on the same Vcn boundary.
+ //
+
+ if (!SkipLookup) {
+
+ //
+ // If it starts on the same boundary then we check if we
+ // can do any work with this.
+ //
+
+ if (CurrentVcn == Attribute->Form.Nonresident.LowestVcn) {
+
+ ULONG RemainingFileRecordBytes;
+
+ RemainingFileRecordBytes = FileRecord->BytesAvailable - FileRecord->FirstFreeByte;
+
+ //
+ // Check if we have less than the desired cushion
+ // left.
+ //
+
+ if (RemainingFileRecordBytes < Vcb->MftCushion) {
+
+ //
+ // If we have no more file records there is no
+ // remapping we can do.
+ //
+
+ if (!ReplaceFileRecord) {
+
+ //
+ // Remember if we used part of the reserved
+ // portion of the file record.
+ //
+
+ if (RemainingFileRecordBytes < Vcb->MftReserved) {
+
+ ExcessMapping = TRUE;
+ }
+
+ CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1;
+ continue;
+ }
+ //
+ // We have more than our cushion left. If this
+ // is the last file record we will skip this.
+ //
+
+ } else if (Attribute->Form.Nonresident.HighestVcn == LastMftVcn) {
+
+ CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1;
+ continue;
+ }
+
+ //
+ // If it doesn't start on the same boundary then we have to
+ // delete and reinsert the attribute list entry.
+ //
+
+ } else {
+
+ ReplaceAttributeListEntry = TRUE;
+ }
+ }
+
+ ReplaceFileRecord = FALSE;
+
+ //
+ // Log the beginning state of this file record.
+ //
+
+ NtfsLogMftFileRecord( IrpContext,
+ Vcb,
+ FileRecord,
+ LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) ),
+ NtfsFoundBcb( &AttrContext ),
+ FALSE );
+
+ //
+ // Compute the Vcn for the file record past the one we will use
+ // next. At this point this is the first Vcn that doesn't have
+ // to be in the current mapping.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ MinimumVcn = NextIndex << Vcb->MftToClusterShift;
+
+ } else {
+
+ MinimumVcn = (NextIndex + Vcb->FileRecordsPerCluster - 1) << Vcb->MftToClusterShift;
+ }
+ }
+
+ //
+ // Move back one vcn to adhere to the mapping pairs interface.
+ // This is now the last Vcn which MUST appear in the current
+ // mapping.
+ //
+
+ MinimumVcn = MinimumVcn - 1;
+
+ //
+ // Get the available size for the mapping pairs. We won't
+ // include the cushion here.
+ //
+
+ MappingSizeAvailable = FileRecord->BytesAvailable + Attribute->RecordLength - FileRecord->FirstFreeByte - SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+
+ //
+ // We know the range of Vcn's the mapping must cover.
+ // Compute the mapping pair size. If they won't fit and
+ // leave our desired cushion then use whatever space is
+ // needed. The NextVcn value is the first Vcn (or xxMax)
+ // for the run after the last run in the current mapping.
+ //
+
+ MappingPairsSize = NtfsGetSizeForMappingPairs( &Vcb->MftScb->Mcb,
+ MappingSizeAvailable - Vcb->MftCushion,
+ CurrentVcn,
+ NULL,
+ &NextVcn );
+
+ //
+ // If this mapping doesn't include the file record we will
+ // be using next then extend the mapping to include it.
+ //
+
+ if (NextVcn <= MinimumVcn) {
+
+ //
+ // Compute the mapping pairs again. This must fit
+ // since it already fits.
+ //
+
+ MappingPairsSize = NtfsGetSizeForMappingPairs( &Vcb->MftScb->Mcb,
+ MappingSizeAvailable,
+ CurrentVcn,
+ &MinimumVcn,
+ &NextVcn );
+
+ //
+ // Remember if we still have excess mapping.
+ //
+
+ if (MappingSizeAvailable - MappingPairsSize < Vcb->MftReserved) {
+
+ ExcessMapping = TRUE;
+ }
+ }
+
+ //
+ // Remember the last Vcn for the current run. If the NextVcn
+ // is xxMax then we are at the end of the file.
+ //
+
+ if (NextVcn == MAXLONGLONG) {
+
+ LastVcn = LastMftVcn;
+
+ //
+ // Otherwise it is one less than the next vcn value.
+ //
+
+ } else {
+
+ LastVcn = NextVcn - 1;
+ }
+
+ //
+ // Check if we have to rewrite this attribute. We will write the
+ // new mapping if any of the following are true.
+ //
+ // We are replacing a file record
+ // The attribute's LowestVcn doesn't match
+ // The attributes's HighestVcn doesn't match.
+ //
+
+ if (ReplaceFileRecord ||
+ (CurrentVcn != Attribute->Form.Nonresident.LowestVcn) ||
+ (LastVcn != Attribute->Form.Nonresident.HighestVcn )) {
+
+ Attribute->Form.Nonresident.LowestVcn = CurrentVcn;
+
+ //
+ // Replace the attribute list entry at this point if needed.
+ //
+
+ if (ReplaceAttributeListEntry) {
+
+ NtfsDeleteFromAttributeList( IrpContext,
+ Vcb->MftScb->Fcb,
+ &AttrContext );
+
+ NtfsAddToAttributeList( IrpContext,
+ Vcb->MftScb->Fcb,
+ FileRecordReference,
+ &AttrContext );
+ }
+
+ //
+ // Allocate a buffer for the mapping pairs if we haven't
+ // done so.
+ //
+
+ if (MappingPairs == NULL) {
+
+ MappingPairs = NtfsAllocatePool(PagedPool, NtfsMaximumAttributeSize( Vcb->BytesPerFileRecordSegment ));
+ }
+
+ NtfsBuildMappingPairs( &Vcb->MftScb->Mcb,
+ CurrentVcn,
+ &NextVcn,
+ MappingPairs );
+
+ Attribute->Form.Nonresident.HighestVcn = NextVcn;
+
+ NtfsRestartChangeMapping( IrpContext,
+ Vcb,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ MappingPairs,
+ MappingPairsSize );
+
+ //
+ // Log the changes to this page.
+ //
+
+ NtfsLogMftFileRecord( IrpContext,
+ Vcb,
+ FileRecord,
+ LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) ),
+ NtfsFoundBcb( &AttrContext ),
+ TRUE );
+
+ MadeChanges = TRUE;
+ }
+
+ //
+ // Move to the first Vcn of the following record.
+ //
+
+ CurrentVcn = Attribute->Form.Nonresident.HighestVcn + 1;
+
+ //
+ // If we reached the last file record and have more mapping to do
+ // then use the reserved record. It must be available or we would
+ // have written out the entire mapping.
+ //
+
+ if (LastFileRecord && (CurrentVcn < LastMftVcn)) {
+
+ PATTRIBUTE_RECORD_HEADER NewAttribute;
+ PATTRIBUTE_TYPE_CODE NewEnd;
+
+ //
+ // Start by moving to the next file record. It better not be
+ // there or the file is corrupt. This will position us to
+ // insert the new record.
+ //
+
+ if (NtfsLookupNextAttributeForScb( IrpContext,
+ Vcb->MftScb,
+ &AttrContext )) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
+ }
+
+ FileRecord = NtfsCloneFileRecord( IrpContext,
+ Vcb->MftScb->Fcb,
+ TRUE,
+ &FileRecordBcb,
+ &FileRecordReference );
+
+ ReservedIndex = MAXULONG;
+
+ //
+ // Now lets create an attribute in the new file record.
+ //
+
+ NewAttribute = Add2Ptr( FileRecord,
+ FileRecord->FirstFreeByte
+ - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE )));
+
+ NewAttribute->TypeCode = Attribute->TypeCode;
+ NewAttribute->RecordLength = SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+ NewAttribute->FormCode = NONRESIDENT_FORM;
+ NewAttribute->Flags = Attribute->Flags;
+ NewAttribute->Instance = FileRecord->NextAttributeInstance++;
+
+ NewAttribute->Form.Nonresident.LowestVcn = CurrentVcn;
+ NewAttribute->Form.Nonresident.HighestVcn = 0;
+ NewAttribute->Form.Nonresident.MappingPairsOffset = (USHORT) NewAttribute->RecordLength;
+
+ NewEnd = Add2Ptr( NewAttribute, NewAttribute->RecordLength );
+ *NewEnd = $END;
+
+ //
+ // Now fix up the file record with this new data.
+ //
+
+ FileRecord->FirstFreeByte = PtrOffset( FileRecord, NewEnd )
+ + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ FileRecord->SequenceNumber += 1;
+
+ if (FileRecord->SequenceNumber == 0) {
+
+ FileRecord->SequenceNumber = 1;
+ }
+
+ FileRecordReference.SequenceNumber = FileRecord->SequenceNumber;
+
+ //
+ // Now switch this new file record into the attribute context.
+ //
+
+ NtfsUnpinBcb( &NtfsFoundBcb( &AttrContext ));
+
+ NtfsFoundBcb( &AttrContext ) = FileRecordBcb;
+ AttrContext.FoundAttribute.MftFileOffset =
+ LlBytesFromFileRecords( Vcb, NtfsSegmentNumber( &FileRecordReference ) );
+ AttrContext.FoundAttribute.Attribute = NewAttribute;
+ AttrContext.FoundAttribute.FileRecord = FileRecord;
+
+ FileRecordBcb = NULL;
+
+ //
+ // Now add an attribute list entry for this entry.
+ //
+
+ NtfsAddToAttributeList( IrpContext,
+ Vcb->MftScb->Fcb,
+ FileRecordReference,
+ &AttrContext );
+
+ SkipLookup = TRUE;
+ LastFileRecord = FALSE;
+
+ } else {
+
+ SkipLookup = FALSE;
+ }
+
+ } // End while more file records
+
+ //
+ // If we didn't rewrite all of the mapping then there is some error.
+ //
+
+ if (CurrentVcn <= LastMftVcn) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
+ }
+
+ try_exit: NOTHING;
+
+ //
+ // Clear the excess mapping flag if no changes were made.
+ //
+
+ if (!ExcessMapping) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsRewriteMftMapping );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ NtfsUnpinBcb( &FileRecordBcb );
+
+ if (MappingPairs != NULL) {
+
+ NtfsFreePool( MappingPairs );
+ }
+ }
+
+ return MadeChanges;
+}
+
+
+//
+// Local Support Routine
+//
+
+VOID
+NtfsSetTotalAllocatedField (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN USHORT CompressionState
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to insure that first attribute of a stream has
+ the correct size attribute header based on the compression state of the
+ file. Compressed streams will have a field for the total allocated space
+ in the file in the nonresident header.
+
+ This routine will see if the header is in a valid state and make space
+ if necessary. Then it will rewrite any of the attribute data after
+ the header.
+
+Arguments:
+
+ Scb - Scb for affected stream
+
+ CompressionState - 0 for no compression or nonzero for Rtl compression code - 1
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_RECORD_HEADER NewAttribute = NULL;
+ PUNICODE_STRING NewAttributeName = NULL;
+
+ ULONG OldHeaderSize;
+ ULONG NewHeaderSize;
+
+ LONG SizeChange;
+
+ PAGED_CODE();
+
+ //
+ // This must be a non-resident user data file.
+ //
+
+ if (!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ) ||
+ FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ return;
+ }
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ while (TRUE) {
+
+ //
+ // Find the current and the new size for the attribute.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+ Attribute = NtfsFoundAttribute( &AttrContext );
+
+ OldHeaderSize = Attribute->Form.Nonresident.MappingPairsOffset;
+
+ if (Attribute->NameOffset != 0) {
+
+ OldHeaderSize = Attribute->NameOffset;
+ }
+
+ if (CompressionState == 0) {
+
+ NewHeaderSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+
+ } else {
+
+ NewHeaderSize = SIZEOF_FULL_NONRES_ATTR_HEADER;
+ }
+
+ SizeChange = NewHeaderSize - OldHeaderSize;
+
+ //
+ // Make space if we need to do so. Lookup the attribute again
+ // if necessary.
+ //
+
+ if (SizeChange > 0) {
+
+ VCN StartingVcn;
+ VCN ClusterCount;
+
+ //
+ // If the attribute is alone in the file record and there isn't
+ // enough space available then the call to ChangeAttributeSize
+ // can't make any space available. In that case we call
+ // NtfsChangeAttributeAllocation and let that routine rewrite
+ // the mapping to make space available.
+ //
+
+ if ((FileRecord->BytesAvailable - FileRecord->FirstFreeByte < (ULONG) SizeChange) &&
+ (NtfsFirstAttribute( FileRecord ) == Attribute) &&
+ (((PATTRIBUTE_RECORD_HEADER) NtfsGetNextRecord( Attribute ))->TypeCode == $END)) {
+
+ NtfsLookupAllocation( IrpContext,
+ Scb,
+ Attribute->Form.Nonresident.HighestVcn,
+ &StartingVcn,
+ &ClusterCount,
+ NULL,
+ NULL );
+
+ StartingVcn = 0;
+ ClusterCount = Attribute->Form.Nonresident.HighestVcn + 1;
+
+ NtfsAddAttributeAllocation( IrpContext,
+ Scb,
+ &AttrContext,
+ &StartingVcn,
+ &ClusterCount );
+
+ } else if (NtfsChangeAttributeSize( IrpContext,
+ Scb->Fcb,
+ Attribute->RecordLength + SizeChange,
+ &AttrContext)) {
+
+ break;
+ }
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsInitializeAttributeContext( &AttrContext );
+ continue;
+ }
+
+ break;
+ }
+
+ NtfsPinMappedAttribute( IrpContext, Scb->Vcb, &AttrContext );
+
+ //
+ // Make a copy of the existing attribute and modify the total allocated field
+ // if necessary.
+ //
+
+ NewAttribute = NtfsAllocatePool(PagedPool, Attribute->RecordLength + SizeChange );
+
+ RtlCopyMemory( NewAttribute,
+ Attribute,
+ SIZEOF_PARTIAL_NONRES_ATTR_HEADER );
+
+ if (Attribute->NameOffset != 0) {
+
+ NewAttribute->NameOffset += (USHORT) SizeChange;
+ NewAttributeName = &Scb->AttributeName;
+ }
+
+ NewAttribute->Form.Nonresident.MappingPairsOffset += (USHORT) SizeChange;
+ NewAttribute->RecordLength += SizeChange;
+
+ RtlCopyMemory( Add2Ptr( NewAttribute, NewAttribute->Form.Nonresident.MappingPairsOffset ),
+ Add2Ptr( Attribute, Attribute->Form.Nonresident.MappingPairsOffset ),
+ Attribute->RecordLength - Attribute->Form.Nonresident.MappingPairsOffset );
+
+ if (CompressionState != 0) {
+
+ NewAttribute->Form.Nonresident.TotalAllocated = Scb->TotalAllocated;
+ }
+
+ //
+ // We now have the before and after image to log.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb->Vcb->MftScb,
+ NtfsFoundBcb( &AttrContext ),
+ DeleteAttribute,
+ NULL,
+ 0,
+ CreateAttribute,
+ Attribute,
+ Attribute->RecordLength,
+ NtfsMftOffset( &AttrContext ),
+ PtrOffset( FileRecord, Attribute ),
+ 0,
+ Scb->Vcb->BytesPerFileRecordSegment );
+
+ NtfsRestartRemoveAttribute( IrpContext, FileRecord, PtrOffset( FileRecord, Attribute ));
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb->Vcb->MftScb,
+ NtfsFoundBcb( &AttrContext ),
+ CreateAttribute,
+ NewAttribute,
+ NewAttribute->RecordLength,
+ DeleteAttribute,
+ NULL,
+ 0,
+ NtfsMftOffset( &AttrContext ),
+ PtrOffset( FileRecord, Attribute ),
+ 0,
+ Scb->Vcb->BytesPerFileRecordSegment );
+
+ NtfsRestartInsertAttribute( IrpContext,
+ FileRecord,
+ PtrOffset( FileRecord, Attribute ),
+ NewAttribute,
+ NewAttributeName,
+ Add2Ptr( NewAttribute, NewAttribute->Form.Nonresident.MappingPairsOffset ),
+ NewAttribute->RecordLength - NewAttribute->Form.Nonresident.MappingPairsOffset );
+
+ } finally {
+
+ DebugUnwind( NtfsSetTotalAllocatedField );
+
+ if (NewAttribute != NULL) {
+
+ NtfsFreePool( NewAttribute );
+ }
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ return;
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+NtfsLookupInFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFILE_REFERENCE BaseFileReference OPTIONAL,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN PVCN Vcn OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength,
+ OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to find the fist occurrence of an attribute with
+ the specified AttributeTypeCode and the specified QueriedName in the
+ specified BaseFileReference. If we find one, its attribute record is
+ pinned and returned.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ BaseFileReference - The base entry for this file in the MFT. Only needed
+ on initial invocation.
+
+ QueriedTypeCode - The attribute code to search for, if present.
+
+ QueriedName - The attribute name to search for, if present.
+
+ Vcn - Search for the nonresident attribute instance that has this Vcn
+
+ IgnoreCase - Ignore case while comparing names. Ignored if QueriedName
+ not present.
+
+ QueriedValue - The actual attribute value to search for, if present.
+
+ QueriedValueLength - The length of the attribute value to search for.
+ Ignored if QueriedValue is not present.
+
+ Context - Describes the prior found attribute on invocation (if
+ this was not the initial enumeration), and contains the next found
+ attribute on return.
+
+Return Value:
+
+ BOOLEAN - True if we found an attribute, false otherwise.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsLookupInFileRecord\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("BaseFileReference = %08I64x\n",
+ ARGUMENT_PRESENT(BaseFileReference) ?
+ NtfsFullSegmentNumber( BaseFileReference ) :
+ 0xFFFFFFFFFFFF) );
+ DebugTrace( 0, Dbg, ("QueriedTypeCode = %08lx\n", QueriedTypeCode) );
+ DebugTrace( 0, Dbg, ("QueriedName = %08lx\n", QueriedName) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+ DebugTrace( 0, Dbg, ("QueriedValue = %08lx\n", QueriedValue) );
+ DebugTrace( 0, Dbg, ("QueriedValueLength = %08lx\n", QueriedValueLength) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ //
+ // Is this the initial enumeration? If so start at the beginning.
+ //
+
+ if (Context->FoundAttribute.Bcb == NULL) {
+
+ PBCB Bcb;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER TempAttribute;
+
+ ASSERT(!ARGUMENT_PRESENT(QueriedName) || !ARGUMENT_PRESENT(QueriedValue));
+
+ NtfsReadFileRecord( IrpContext,
+ Fcb->Vcb,
+ BaseFileReference,
+ &Bcb,
+ &FileRecord,
+ &TempAttribute,
+ &Context->FoundAttribute.MftFileOffset );
+
+ Attribute = TempAttribute;
+
+ //
+ // Initialize the found attribute context
+ //
+
+ Context->FoundAttribute.Bcb = Bcb;
+ Context->FoundAttribute.FileRecord = FileRecord;
+
+ //
+ // And show that we have neither found nor used the External
+ // Attributes List attribute.
+ //
+
+ Context->AttributeList.Bcb = NULL;
+ Context->AttributeList.AttributeList = NULL;
+
+ //
+ // Scan to see if there is an attribute list, and if so, defer
+ // immediately to NtfsLookupExternalAttribute - we must guide the
+ // enumeration by the attribute list.
+ //
+
+ while (TempAttribute->TypeCode <= $ATTRIBUTE_LIST) {
+
+ if (TempAttribute->RecordLength == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ if (TempAttribute->TypeCode == $ATTRIBUTE_LIST) {
+
+ ULONG AttributeListLength;
+ PATTRIBUTE_LIST_CONTEXT Ex = &Context->AttributeList;
+
+ Context->FoundAttribute.Attribute = TempAttribute;
+
+ if (QueriedTypeCode != $UNUSED &&
+ (QueriedTypeCode == $ATTRIBUTE_LIST)) {
+
+ //
+ // We found it. Return it in the enumeration context.
+ //
+
+ DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n",
+ TempAttribute) );
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (attribute list)\n") );
+
+ return TRUE;
+ }
+
+ //
+ // Build up the context for the attribute list by hand here
+ // for efficiency, so that we can call NtfsMapAttributeValue.
+ //
+
+ Ex->AttributeList = TempAttribute;
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *)&Ex->FirstEntry,
+ &AttributeListLength,
+ &Ex->Bcb,
+ Context );
+
+ Ex->Entry = Ex->FirstEntry;
+ Ex->BeyondFinalEntry = Add2Ptr( Ex->FirstEntry, AttributeListLength );
+
+ //
+ // If the list is non-resident then remember the correct Bcb for
+ // the list.
+ //
+
+ if (!NtfsIsAttributeResident( TempAttribute )) {
+
+ Ex->NonresidentListBcb = Ex->Bcb;
+ Ex->Bcb = Context->FoundAttribute.Bcb;
+ Context->FoundAttribute.Bcb = NULL;
+
+ //
+ // Otherwise unpin the Bcb for the current attribute.
+ //
+
+ } else {
+
+ NtfsUnpinBcb( &Context->FoundAttribute.Bcb );
+ }
+
+ //
+ // We are now ready to itterate through the external attributes.
+ // The Context->FoundAttribute.Bcb being NULL signals
+ // NtfsLookupExternalAttribute that is should start at
+ // Context->External.Entry instead of the entry immediately following.
+ //
+
+ return NtfsLookupExternalAttribute( IrpContext,
+ Fcb,
+ QueriedTypeCode,
+ QueriedName,
+ Vcn,
+ IgnoreCase,
+ QueriedValue,
+ QueriedValueLength,
+ Context );
+ }
+
+ TempAttribute = NtfsGetNextRecord( TempAttribute );
+ NtfsCheckRecordBound( TempAttribute, FileRecord, Fcb->Vcb->BytesPerFileRecordSegment );
+ }
+
+ if (QueriedTypeCode == $UNUSED ||
+ ((QueriedTypeCode == $STANDARD_INFORMATION) &&
+ (Attribute->TypeCode == $STANDARD_INFORMATION))) {
+
+ //
+ // We found it. Return it in the enumeration context.
+ //
+
+ Context->FoundAttribute.Attribute = Attribute;
+
+ DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n",
+ Attribute ));
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (No code or SI)\n") );
+
+ return TRUE;
+ }
+
+ } else {
+
+ //
+ // Special case if the prior found attribute was $END, this is
+ // because we cannot search for the next entry after $END.
+ //
+
+ Attribute = Context->FoundAttribute.Attribute;
+
+ if (!Context->FoundAttribute.AttributeDeleted) {
+ Attribute = NtfsGetNextRecord( Attribute );
+ }
+ NtfsCheckRecordBound( Attribute, Context->FoundAttribute.FileRecord, Fcb->Vcb->BytesPerFileRecordSegment );
+ Context->FoundAttribute.AttributeDeleted = FALSE;
+
+ if (Attribute->TypeCode == $END) {
+
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> FALSE ($END)\n") );
+
+ return FALSE;
+ }
+
+ if (Attribute->RecordLength == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ if (QueriedTypeCode == $UNUSED) {
+
+ //
+ // We found it. Return it in the enumeration context.
+ //
+
+ Context->FoundAttribute.Attribute = Attribute;
+
+ DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n",
+ Attribute) );
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord -> TRUE (No code)\n") );
+
+ return TRUE;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord ->\n") );
+
+ return NtfsFindInFileRecord( IrpContext,
+ Attribute,
+ &Context->FoundAttribute.Attribute,
+ QueriedTypeCode,
+ QueriedName,
+ IgnoreCase,
+ QueriedValue,
+ QueriedValueLength );
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+NtfsFindInFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ OUT PATTRIBUTE_RECORD_HEADER *ReturnAttribute,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine looks up an attribute in a file record. It returns
+ TRUE if the attribute was found, or FALSE if not found. If FALSE
+ is returned, the return attribute pointer points to the spot where
+ the described attribute should be inserted. Thus this routine
+ determines how attributes are collated within file records.
+
+Arguments:
+
+ Attribute - The attribute within the file record at which the search
+ should begin.
+
+ ReturnAttribute - Pointer to the found attribute if returning TRUE,
+ or to the position to insert the attribute if returning
+ FALSE.
+
+ QueriedTypeCode - The attribute code to search for, if present.
+
+ QueriedName - The attribute name to search for, if present.
+
+ IgnoreCase - Ignore case while comparing names. Ignored if QueriedName
+ not present.
+
+ QueriedValue - The actual attribute value to search for, if present.
+
+ QueriedValueLength - The length of the attribute value to search for.
+ Ignored if QueriedValue is not present.
+
+Return Value:
+
+ BOOLEAN - True if we found an attribute, false otherwise.
+
+--*/
+
+{
+ PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable;
+ ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize;
+
+ //
+ // Now walk through the base file record looking for the atttribute. If
+ // the query is "exhausted", i.e., if a type code, attribute name, or
+ // value is encountered which is greater than the one we are querying for,
+ // then we return FALSE immediately out of this loop. If an exact match
+ // is seen, we break, and return the match at the end of this routine.
+ // Otherwise we keep looping while the query is not exhausted.
+ //
+ // IMPORTANT NOTE:
+ //
+ // The exact semantics of this loop are important, as they determine the
+ // exact details of attribute ordering within the file record. A change
+ // in the order of the tests within this loop CHANGES THE FILE STRUCTURE,
+ // and possibly makes older NTFS volumes unreadable.
+ //
+
+ while ( TRUE ) {
+
+ //
+ // Mark this attribute position, since we may be returning TRUE
+ // or FALSE below.
+ //
+
+ *ReturnAttribute = Attribute;
+
+ //
+ // Leave with the correct current position intact, if we hit the
+ // end or a greater attribute type code.
+ //
+ // COLLATION RULE:
+ //
+ // Attributes are ordered by increasing attribute type code.
+ //
+
+ if (QueriedTypeCode < Attribute->TypeCode) {
+
+ DebugTrace( -1, Dbg, ("NtfsLookupInFileRecord->FALSE (Type Code)\n") );
+
+ return FALSE;
+
+ }
+
+ if (Attribute->RecordLength == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // If the attribute type code is a match, then need to check either
+ // the name or the value or return a match.
+ //
+ // COLLATION RULE:
+ //
+ // Within equal attribute type codes, attribute names are ordered
+ // by increasing lexigraphical order ignoring case. If two names
+ // exist which are equal when case is ignored, they must not be
+ // equal when compared with exact case, and within such equal
+ // names they are ordered by increasing lexical value with exact
+ // case.
+ //
+
+ if (QueriedTypeCode == Attribute->TypeCode) {
+
+ //
+ // Handle name-match case
+ //
+
+ if (ARGUMENT_PRESENT(QueriedName)) {
+
+ UNICODE_STRING AttributeName;
+ FSRTL_COMPARISON_RESULT Result;
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+
+ //
+ // See if we have a name match.
+ //
+
+ if (NtfsAreNamesEqual( UpcaseTable,
+ &AttributeName,
+ QueriedName,
+ IgnoreCase )) {
+
+ break;
+ }
+
+ //
+ // Compare the names ignoring case.
+ //
+
+ Result = NtfsCollateNames( UpcaseTable,
+ UpcaseTableSize,
+ QueriedName,
+ &AttributeName,
+ GreaterThan,
+ TRUE);
+
+ //
+ // Break out if the result is LessThan, or if the result
+ // is Equal to *and* the exact case compare yields LessThan.
+ //
+
+ if ((Result == LessThan) || ((Result == EqualTo) &&
+ (NtfsCollateNames( UpcaseTable,
+ UpcaseTableSize,
+ QueriedName,
+ &AttributeName,
+ GreaterThan,
+ FALSE) == LessThan))) {
+
+ return FALSE;
+ }
+
+ //
+ // Handle value-match case
+ //
+ // COLLATION RULE:
+ //
+ // Values are collated by increasing values with unsigned-byte
+ // compares. I.e., the first different byte is compared unsigned,
+ // and the value with the highest byte comes second. If a shorter
+ // value is exactly equal to the first part of a longer value, then
+ // the shorter value comes first.
+ //
+ // Note that for values which are actually Unicode strings, the
+ // collation is different from attribute name ordering above. However,
+ // attribute ordering is visible outside the file system (you can
+ // query "openable" attributes), whereas the ordering of indexed values
+ // is not visible (for example you cannot query links). In any event,
+ // the ordering of values must be considered up to the system, and
+ // *must* be considered nondetermistic from the standpoint of a user.
+ //
+
+ } else if (ARGUMENT_PRESENT(QueriedValue)) {
+
+ ULONG Diff, MinLength;
+
+ //
+ // Form the minimum of the ValueLength and the Attribute Value.
+ //
+
+ MinLength = Attribute->Form.Resident.ValueLength;
+
+ if (QueriedValueLength < MinLength) {
+
+ MinLength = QueriedValueLength;
+ }
+
+ //
+ // Find the first different byte.
+ //
+
+ Diff = RtlCompareMemory( QueriedValue,
+ NtfsGetValue(Attribute),
+ MinLength );
+
+ //
+ // The first substring was equal.
+ //
+
+ if (Diff == MinLength) {
+
+ //
+ // If the two lengths are equal, then we have an exact
+ // match.
+ //
+
+ if (QueriedValueLength == Attribute->Form.Resident.ValueLength) {
+
+ break;
+ }
+
+ //
+ // Otherwise the shorter guy comes first; we can return
+ // FALSE if the queried value is shorter.
+ //
+
+ if (QueriedValueLength < Attribute->Form.Resident.ValueLength) {
+
+ return FALSE;
+ }
+
+ //
+ // Otherwise some byte was different. Do an unsigned compare
+ // of that byte to determine the ordering. Time to leave if
+ // the queried value byte is less.
+ //
+
+ } else if (*((PUCHAR)QueriedValue + Diff) <
+ *((PUCHAR)NtfsGetValue(Attribute) + Diff)) {
+
+ return FALSE;
+ }
+
+ //
+ // Otherwise we have a simple match on code
+ //
+
+ } else {
+
+ break;
+ }
+ }
+
+ Attribute = NtfsGetNextRecord( Attribute );
+ NtfsCheckRecordBound( Attribute,
+ (ULONG)*ReturnAttribute & ~(IrpContext->Vcb->BytesPerFileRecordSegment - 1),
+ IrpContext->Vcb->BytesPerFileRecordSegment );
+ }
+
+ return TRUE;
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+NtfsLookupExternalAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN PVCN Vcn OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength,
+ OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to find the first occurrence of an attribute with
+ the specified AttributeTypeCode and the specified QueriedName and Value
+ among the external attributes described by the Context. If we find one,
+ its attribute record is pinned and returned.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ QueriedTypeCode - The attribute code to search for, if present.
+
+ QueriedName - The attribute name to search for, if present.
+
+ Vcn - Lookup nonresident attribute instance with this Vcn
+
+ IgnoreCase - Ignore case while comparing names. Ignored if QueriedName
+ not present.
+
+ QueriedValue - The actual attribute value to search for, if present.
+
+ QueriedValueLength - The length of the attribute value to search for.
+ Ignored if QueriedValue is not present.
+
+ Context - Describes the prior found attribute on invocation (if
+ this was not the initial enumeration), and contains the next found
+ attribute on return.
+
+Return Value:
+
+ BOOLEAN - True if we found an attribute, false otherwise.
+
+--*/
+
+{
+ PATTRIBUTE_LIST_ENTRY Entry, LastEntry;
+ PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable;
+ ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize;
+ BOOLEAN Terminating = FALSE;
+ BOOLEAN TerminateOnNext = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsLookupExternalAttribute\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("QueriedTypeCode = %08lx\n", QueriedTypeCode) );
+ DebugTrace( 0, Dbg, ("QueriedName = %08lx\n", QueriedName) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+ DebugTrace( 0, Dbg, ("QueriedValue = %08lx\n", QueriedValue) );
+ DebugTrace( 0, Dbg, ("QueriedValueLength = %08lx\n", QueriedValueLength) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ //
+ // Check that our list is kosher.
+ //
+
+ if ((Context->AttributeList.Entry >= Context->AttributeList.BeyondFinalEntry) &&
+ !Context->FoundAttribute.AttributeDeleted) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Is this the initial enumeration? If so start at the beginning.
+ //
+
+ LastEntry = NULL;
+ if (Context->FoundAttribute.Bcb == NULL) {
+
+ Entry = Context->AttributeList.Entry;
+
+ //
+ // Else set Entry and LastEntry appropriately.
+ //
+
+ } else if (!Context->FoundAttribute.AttributeDeleted) {
+
+ LastEntry = Context->AttributeList.Entry;
+ Entry = NtfsGetNextRecord( LastEntry );
+
+ } else {
+
+ Entry = Context->AttributeList.Entry;
+ Context->FoundAttribute.AttributeDeleted = FALSE;
+
+ //
+ // If we are beyond the attribute list, we return false. This will
+ // happen in the case where have removed an attribute record and
+ // there are no entries left in the attribute list.
+ //
+
+ if (Context->AttributeList.Entry >= Context->AttributeList.BeyondFinalEntry) {
+
+ //
+ // In case the caller is doing an insert, we will position him at the end
+ // of the first file record, an always try to insert new attributes there.
+ //
+
+ NtfsUnpinBcb( &Context->FoundAttribute.Bcb );
+
+ if (QueriedTypeCode != $UNUSED) {
+
+ NtfsReadFileRecord( IrpContext,
+ Fcb->Vcb,
+ &Fcb->FileReference,
+ &Context->FoundAttribute.Bcb,
+ &Context->FoundAttribute.FileRecord,
+ &Context->FoundAttribute.Attribute,
+ &Context->FoundAttribute.MftFileOffset );
+
+ //
+ // If returning FALSE, then take the time to really find the
+ // correct position in the file record for a subsequent insert.
+ //
+
+ NtfsFindInFileRecord( IrpContext,
+ Context->FoundAttribute.Attribute,
+ &Context->FoundAttribute.Attribute,
+ QueriedTypeCode,
+ QueriedName,
+ IgnoreCase,
+ QueriedValue,
+ QueriedValueLength );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> FALSE\n") );
+
+ return FALSE;
+ }
+ }
+
+ //
+ // Now walk through the entries looking for an atttribute.
+ //
+
+ while (TRUE) {
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ UNICODE_STRING EntryName;
+ UNICODE_STRING AttributeName;
+
+ PATTRIBUTE_LIST_ENTRY NextEntry;
+
+ BOOLEAN CorrespondingAttributeFound;
+
+ //
+ // Check to see if we are now pointing beyond the final entry
+ // and if so fall in to the loop to terminate pointing just
+ // after the last entry.
+ //
+
+ if (Entry >= Context->AttributeList.BeyondFinalEntry) {
+
+ Terminating = TRUE;
+ TerminateOnNext = TRUE;
+ Entry = Context->AttributeList.Entry;
+
+ } else {
+
+ NtfsCheckRecordBound( Entry,
+ Context->AttributeList.FirstEntry,
+ PtrOffset(Context->AttributeList.FirstEntry,
+ Context->AttributeList.BeyondFinalEntry) );
+
+ if (Entry->RecordLength == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ NextEntry = NtfsGetNextRecord(Entry);
+ }
+
+ Context->AttributeList.Entry = Entry;
+
+ //
+ // Compare the type codes. The external attribute entry list is
+ // ordered by type code, so if the queried type code is less than
+ // the entry type code we continue the while(), if it is
+ // greater than we break out of the while() and return failure.
+ // If equal, we move on to compare names.
+ //
+
+ if (QueriedTypeCode != $UNUSED && !Terminating &&
+ (QueriedTypeCode != Entry->AttributeTypeCode)) {
+
+ if ( QueriedTypeCode > Entry->AttributeTypeCode ) {
+
+ Entry = NextEntry;
+ continue;
+
+ //
+ // Set up to terminate on seeing a higher type code.
+ //
+
+ } else {
+
+ Terminating = TRUE;
+ }
+ }
+
+ //
+ // At this point we are OK by TypeCode, compare names.
+ //
+
+ EntryName.Length =
+ EntryName.MaximumLength = Entry->AttributeNameLength * 2;
+ EntryName.Buffer = Add2Ptr(Entry, Entry->AttributeNameOffset);
+
+ if (ARGUMENT_PRESENT(QueriedName) && !Terminating) {
+
+ FSRTL_COMPARISON_RESULT Result;
+
+ //
+ // See if we have a name match.
+ //
+
+ if (!NtfsAreNamesEqual( UpcaseTable,
+ &EntryName,
+ QueriedName,
+ IgnoreCase )) {
+
+ //
+ // Compare the names ignoring case.
+ //
+
+ Result = NtfsCollateNames( UpcaseTable,
+ UpcaseTableSize,
+ QueriedName,
+ &EntryName,
+ GreaterThan,
+ TRUE);
+
+ //
+ // Break out if the result is LessThan, or if the result
+ // is Equal to *and* the exact case compare yields LessThan.
+ //
+
+ if ((Result == LessThan) || ((Result == EqualTo) &&
+ (NtfsCollateNames( UpcaseTable,
+ UpcaseTableSize,
+ QueriedName,
+ &EntryName,
+ GreaterThan,
+ FALSE) == LessThan))) {
+
+ Terminating = TRUE;
+
+ } else {
+
+ Entry = NextEntry;
+ continue;
+ }
+ }
+ }
+
+ //
+ // Now search for the right Vcn range, if specified.
+ //
+
+ if (ARGUMENT_PRESENT(Vcn) && !Terminating) {
+
+ ASSERT(Entry->LowestVcn <= *Vcn);
+
+ //
+ // If there is a next entry for our attribute and its LowestVcn is still <=
+ // to the one we are looking for, then we have to go on.
+ //
+
+ if ((NextEntry < Context->AttributeList.BeyondFinalEntry) &&
+ (NextEntry->LowestVcn <= *Vcn) &&
+ (NextEntry->AttributeTypeCode == Entry->AttributeTypeCode) &&
+ (NextEntry->AttributeNameLength == Entry->AttributeNameLength) &&
+ (RtlEqualMemory(Add2Ptr(NextEntry, NextEntry->AttributeNameOffset),
+ Add2Ptr(Entry, Entry->AttributeNameOffset),
+ Entry->AttributeNameLength * 2 ))) {
+
+ Entry = NextEntry;
+ continue;
+ }
+ }
+
+ //
+ // Now we are also OK by name and Vcn, so now go find the attribute and
+ // compare against value, if specified.
+ //
+
+ if ((LastEntry == NULL) ||
+ (!NtfsEqualMftRef(&LastEntry->SegmentReference, &Entry->SegmentReference))) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ NtfsUnpinBcb( &Context->FoundAttribute.Bcb );
+
+ NtfsReadFileRecord( IrpContext,
+ Fcb->Vcb,
+ &Entry->SegmentReference,
+ &Context->FoundAttribute.Bcb,
+ &FileRecord,
+ &Attribute,
+ &Context->FoundAttribute.MftFileOffset );
+
+ Context->FoundAttribute.FileRecord = FileRecord;
+
+ //
+ // If we already have the right record pinned, reload this pointer.
+ //
+
+ } else {
+
+ Attribute = NtfsFirstAttribute(Context->FoundAttribute.FileRecord);
+ }
+
+ //
+ // Now quickly loop through looking for the correct attribute
+ // instance.
+ //
+
+ CorrespondingAttributeFound = FALSE;
+
+ while (TRUE) {
+
+ //
+ // Check that we can safely access this attribute.
+ //
+
+ NtfsCheckRecordBound( Attribute,
+ Context->FoundAttribute.FileRecord,
+ Fcb->Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Exit the loop if we have reached the $END record.
+ //
+
+ if (Attribute->TypeCode == $END) {
+
+ break;
+ }
+
+ //
+ // Check that the attribute has a non-zero length.
+ //
+
+ if (Attribute->RecordLength == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ if (Entry->Instance == Attribute->Instance) {
+
+ //
+ // Well, the attribute list saved us from having to compare
+ // type code and name as we went through this file record,
+ // however now that we have found our attribute by its
+ // instance number, we will do a quick check to see that
+ // we got the right one. Else the file is corrupt.
+ //
+
+ if (Entry->AttributeTypeCode != Attribute->TypeCode) {
+ break;
+ }
+
+ if (ARGUMENT_PRESENT(QueriedName)) {
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+
+ if (!NtfsAreNamesEqual( UpcaseTable, &AttributeName, &EntryName, FALSE)) {
+ break;
+ }
+ }
+
+ //
+ // Show that we correctly found the attribute described in
+ // the attribute list.
+ //
+
+ CorrespondingAttributeFound = TRUE;
+
+ Context->FoundAttribute.Attribute = Attribute;
+
+ //
+ // Now we may just be here because we are terminating the
+ // scan on seeing the end, a higher attribute code, or a
+ // higher name. If so, return FALSE here.
+ //
+
+ if (Terminating) {
+
+ //
+ // If we hit the end of the attribute list, then we
+ // are supposed to terminate after advancing the
+ // attribute list entry.
+ //
+
+ if (TerminateOnNext) {
+
+ Context->AttributeList.Entry = NtfsGetNextRecord(Entry);
+ }
+
+ //
+ // In case the caller is doing an insert, we will position him at the end
+ // of the first file record, an always try to insert new attributes there.
+ //
+
+ NtfsUnpinBcb( &Context->FoundAttribute.Bcb );
+
+ if (QueriedTypeCode != $UNUSED) {
+
+ NtfsReadFileRecord( IrpContext,
+ Fcb->Vcb,
+ &Fcb->FileReference,
+ &Context->FoundAttribute.Bcb,
+ &Context->FoundAttribute.FileRecord,
+ &Context->FoundAttribute.Attribute,
+ &Context->FoundAttribute.MftFileOffset );
+
+ //
+ // If returning FALSE, then take the time to really find the
+ // correct position in the file record for a subsequent insert.
+ //
+
+ NtfsFindInFileRecord( IrpContext,
+ Context->FoundAttribute.Attribute,
+ &Context->FoundAttribute.Attribute,
+ QueriedTypeCode,
+ QueriedName,
+ IgnoreCase,
+ QueriedValue,
+ QueriedValueLength );
+ }
+
+ DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n",
+ Attribute) );
+ DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> FALSE\n") );
+
+ return FALSE;
+ }
+
+ //
+ // Now compare the value, if so queried.
+ //
+
+ if ( !ARGUMENT_PRESENT( QueriedValue ) ||
+ NtfsEqualAttributeValue( Attribute,
+ QueriedValue,
+ QueriedValueLength ) ) {
+
+ //
+ // It matches. Return it in the enumeration context.
+ //
+
+ DebugTrace( 0, Dbg, ("Context->FoundAttribute.Attribute < %08lx\n",
+ Attribute ));
+ DebugTrace( -1, Dbg, ("NtfsLookupExternalAttribute -> TRUE\n") );
+
+ return TRUE;
+ }
+ }
+
+ //
+ // Get the next attribute, and continue.
+ //
+
+ Attribute = NtfsGetNextRecord( Attribute );
+ }
+
+ //
+ // Did we even find the attribute corresponding to the entry?
+ // If not, something is messed up. Raise file corrupt error.
+ //
+
+ if ( !CorrespondingAttributeFound ) {
+
+ //
+ // For the moment, ASSERT this falsehood so that we may have
+ // a chance to peek before raising.
+ //
+
+ ASSERT( CorrespondingAttributeFound );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ Entry = NtfsGetNextRecord( Entry );
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+NtfsGetSpaceForAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG Length,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine gets space for a new attribute record at the position indicated
+ in the Context structure. As required, it will move attributes around,
+ allocate an additional record in the Mft, or convert some other existing
+ attribute to nonresident form. The caller should already have checked if
+ the new attribute he is inserting should be stored resident or nonresident.
+
+ On return, it is invalid to continue to use any previously-retrieved pointers,
+ Bcbs, or other position-dependent information retrieved from the Context
+ structure, as any of these values are liable to change. The file record in
+ which the space has been found will already be pinned.
+
+ Note, this routine DOES NOT actually make space for the attribute, it only
+ verifies that sufficient space is there. The caller may call
+ NtfsRestartInsertAttribute to actually insert the attribute in place.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ Length - Quad-aligned length required in bytes.
+
+ Context - Describes the position for the new attribute, as returned from
+ the enumeration which failed to find an existing occurrence of
+ the attribute. This pointer will either be pointing to some
+ other attribute in the record, or to the first free quad-aligned
+ byte if the new attribute is to go at the end.
+
+Return Value:
+
+ FALSE - if a major move was necessary, and the caller should look up
+ its desired position again and call back.
+ TRUE - if the space was created
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER NextAttribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGetSpaceForAttribute\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ ASSERT( Length == QuadAlign( Length ));
+
+ NextAttribute = NtfsFoundAttribute( Context );
+ FileRecord = NtfsContainingFileRecord( Context );
+
+ //
+ // Make sure the buffer is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Fcb->Vcb, Context );
+
+ //
+ // If the space is not there now, then make room and return with FALSE
+ //
+
+ if ((FileRecord->BytesAvailable - FileRecord->FirstFreeByte) < Length ) {
+
+ MakeRoomForAttribute( IrpContext, Fcb, Length, Context );
+
+ DebugTrace( -1, Dbg, ("NtfsGetSpaceForAttribute -> FALSE\n") );
+ return FALSE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsGetSpaceForAttribute -> TRUE\n") );
+ return TRUE;
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+NtfsChangeAttributeSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG Length,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine adjustss the space occupied by the current attribute record
+ in the Context structure. As required, it will move attributes around,
+ allocate an additional record in the Mft, or convert some other existing
+ attribute to nonresident form. The caller should already have checked if
+ the current attribute he is inserting should rather be converted to
+ nonresident.
+
+ When done, this routine has updated any file records whose allocation was
+ changed, and also the RecordLength field in the adjusted attribute. No
+ other attribute fields are updated.
+
+ On return, it is invalid to continue to use any previously-retrieved pointers,
+ Bcbs, or other position-dependent information retrieved from the Context
+ structure, as any of these values are liable to change. The file record in
+ which the space has been found will already be pinned.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ Length - New quad-aligned length of attribute record in bytes
+
+ Context - Describes the current attribute.
+
+Return Value:
+
+ FALSE - if a major move was necessary, and the caller should look up
+ its desired position again and call back.
+ TRUE - if the space was created
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ LONG SizeChange;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsChangeAttributeSize\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+
+ ASSERT( Length == QuadAlign( Length ));
+
+ Attribute = NtfsFoundAttribute( Context );
+ FileRecord = NtfsContainingFileRecord( Context );
+
+ //
+ // Make sure the buffer is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Fcb->Vcb, Context );
+
+ //
+ // Calculate the change in attribute record size.
+ //
+
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ SizeChange = Length - Attribute->RecordLength;
+
+ //
+ // If there is not currently enough space, then we have to make room
+ // and return FALSE to our caller.
+ //
+
+ if ( (LONG)(FileRecord->BytesAvailable - FileRecord->FirstFreeByte) < SizeChange ) {
+
+ MakeRoomForAttribute( IrpContext, Fcb, SizeChange, Context );
+
+ DebugTrace( -1, Dbg, ("NtfsChangeAttributeSize -> FALSE\n") );
+
+ return FALSE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsChangeAttributeSize -> TRUE\n") );
+
+ return TRUE;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+MakeRoomForAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG SizeNeeded,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to make additional room for a new attribute or
+ a growing attribute in a file record. The algorithm is as follows.
+
+ First continuously loop through the record looking at the largest n
+ attributes, from the largest down, to see which one of these attributes
+ is big enough to move, and which one qualifies for one of the following
+ actions:
+
+ 1. For an index root attribute, the indexing package may be called
+ to "push" the index root, i.e., add another level to the BTree
+ leaving only an end index record in the root.
+
+ 2. For a resident attribute which is allowed to be made nonresident,
+ the attribute is made nonresident, leaving only run information
+ in the root.
+
+ 3. If the attribute is already nonresident, then it can be moved to
+ a separate file record.
+
+ If none of the above operations can be performed, or not enough free space
+ is recovered, then as a last resort the file record is split in two. This
+ would typically indicate that the file record is populated with a large
+ number of small attributes.
+
+ The first time step 3 above or a split of the file record occurs, the
+ attribute list must be created for the file.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ SizeNeeded - Supplies the total amount of free space needed, in bytes.
+
+ Context - Describes the insertion point for the attribute which does
+ not fit. NOTE -- This context is not valid on return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER LargestAttributes[MAX_MOVEABLE_ATTRIBUTES];
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ULONG i;
+ PVCB Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // Here is the current threshhold at which a move of an attribute will
+ // be considered.
+ //
+
+ FileRecord = NtfsContainingFileRecord( Context );
+
+ //
+ // Find the largest attributes for this file record.
+ //
+
+ FindLargestAttributes( FileRecord, MAX_MOVEABLE_ATTRIBUTES, LargestAttributes );
+
+ //
+ // Now loop from largest to smallest of the largest attributes,
+ // and see if there is something we can do.
+ //
+
+ for (i = 0; i < MAX_MOVEABLE_ATTRIBUTES; i += 1) {
+
+ Attribute = LargestAttributes[i];
+
+ //
+ // Look to the next attribute if there is no attribute at this array
+ // position.
+ //
+
+ if (Attribute == NULL) {
+
+ continue;
+
+ //
+ // If this is the Mft then any attribute that is 'BigEnoughToMove'
+ // except $DATA/$PROPERTY_SET attributes outside the base file record.
+ // We need to keep those where they are in order to enforce the
+ // boot-strap mapping.
+ //
+
+ } else if (Fcb == Vcb->MftScb->Fcb) {
+
+ if (Attribute->TypeCode == $DATA &&
+ ((*(PLONGLONG) &FileRecord->BaseFileRecordSegment != 0) ||
+ (Attribute->RecordLength < Vcb->BigEnoughToMove))) {
+
+ continue;
+ }
+
+ //
+ // Any attribute in a non-Mft file which is 'BigEnoughToMove' can
+ // be considered. We also accept an $ATTRIBUTE_LIST attribute
+ // in a non-Mft file which must go non-resident in order for
+ // the attribute name to fit. Otherwise we could be trying to
+ // add an attribute with a large name into the base file record.
+ // We will need space to store the name twice, once for the
+ // attribute list entry and once in the attribute. This can take
+ // up 1024 bytes by itself. We want to force the attribute list
+ // non-resident first so that the new attribute will fit. We
+ // look at whether the attribute list followed by just the new data
+ // will fit in the file record.
+ //
+
+ } else if (Attribute->RecordLength < Vcb->BigEnoughToMove) {
+
+ if ((Attribute->TypeCode != $ATTRIBUTE_LIST) ||
+ ((PtrOffset( FileRecord, Attribute ) + Attribute->RecordLength + SizeNeeded + sizeof( LONGLONG)) <= FileRecord->BytesAvailable)) {
+
+ continue;
+ }
+ }
+
+ //
+ // If this attribute is an index root, then we can just call the
+ // indexing support to allocate a new index buffer and push the
+ // current resident contents down.
+ //
+
+ if (Attribute->TypeCode == $INDEX_ROOT) {
+
+ PSCB IndexScb;
+ UNICODE_STRING IndexName;
+
+ IndexName.Length =
+ IndexName.MaximumLength = (USHORT)Attribute->NameLength << 1;
+ IndexName.Buffer = Add2Ptr( Attribute, Attribute->NameOffset );
+
+ IndexScb = NtfsCreateScb( IrpContext,
+ Fcb,
+ $INDEX_ALLOCATION,
+ &IndexName,
+ FALSE,
+ NULL );
+
+ NtfsPushIndexRoot( IrpContext, IndexScb );
+
+ return;
+
+ //
+ // Otherwise, if this is a resident attribute which can go nonresident,
+ // then make it nonresident now.
+ //
+
+ } else if ((Attribute->FormCode == RESIDENT_FORM) &&
+ !FlagOn(NtfsGetAttributeDefinition(Vcb,
+ Attribute->TypeCode)->Flags,
+ ATTRIBUTE_DEF_MUST_BE_RESIDENT)) {
+
+ NtfsConvertToNonresident( IrpContext, Fcb, Attribute, FALSE, NULL );
+
+ return;
+
+ //
+ // Finally, if the attribute is nonresident already, move it to its
+ // own record unless it is an attribute list.
+ //
+
+ } else if ((Attribute->FormCode == NONRESIDENT_FORM)
+ && (Attribute->TypeCode != $ATTRIBUTE_LIST)) {
+
+ LONGLONG MftFileOffset;
+
+ MftFileOffset = Context->FoundAttribute.MftFileOffset;
+
+ MoveAttributeToOwnRecord( IrpContext,
+ Fcb,
+ Attribute,
+ Context,
+ NULL,
+ NULL );
+
+ return;
+ }
+ }
+
+ //
+ // If we get here, it is because we failed to find enough space above.
+ // Our last resort is to split into two file records, and this has
+ // to work. We should never reach this point for the Mft.
+ //
+
+ if (Fcb == Vcb->MftScb->Fcb) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ SplitFileRecord( IrpContext, Fcb, SizeNeeded, Context );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+FindLargestAttributes (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG Number,
+ OUT PATTRIBUTE_RECORD_HEADER *AttributeArray
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the n largest attributes from a file record in an
+ array, ordered from largest to smallest.
+
+Arguments:
+
+ FileRecord - Supplies file record to scan for largest attributes.
+
+ Number - Supplies the number of entries in the array.
+
+ AttributeArray - Supplies the array which is to receive pointers to the
+ largest attributes. This array must be zeroed prior
+ to calling this routine.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG i, j;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ PAGED_CODE();
+
+ RtlZeroMemory( AttributeArray, Number * sizeof(PATTRIBUTE_RECORD_HEADER) );
+
+ Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset );
+
+ while (Attribute->TypeCode != $END) {
+
+ for (i = 0; i < Number; i++) {
+
+ if ((AttributeArray[i] == NULL)
+
+ ||
+
+ (AttributeArray[i]->RecordLength < Attribute->RecordLength)) {
+
+ for (j = Number - 1; j != i; j--) {
+
+ AttributeArray[j] = AttributeArray[j-1];
+ }
+
+ AttributeArray[i] = Attribute;
+ break;
+ }
+ }
+
+ Attribute = Add2Ptr( Attribute, Attribute->RecordLength );
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+LONGLONG
+MoveAttributeToOwnRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ OUT PBCB *NewBcb OPTIONAL,
+ OUT PFILE_RECORD_SEGMENT_HEADER *NewFileRecord OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to move a particular attribute to a separate
+ file record. If the file does not already have an attribute list, then
+ one is created (else it is updated).
+
+Arguments:
+
+ Fcb - Requested file.
+
+ Attribute - Supplies a pointer to the attribute which is to be moved.
+
+ Context - Supplies a pointer to a context which was used to look up
+ another attribute in the same file record. If this is an Mft
+ $DATA split we will point to the part that was split out of the
+ first file record on return. The call from NtfsAddAttributeAllocation
+ depends on this.
+
+ NewBcb - If supplied, returns the Bcb address for the file record
+ that the attribute was moved to. NewBcb and NewFileRecord must
+ either both be specified or neither specified.
+
+ NewFileRecord - If supplied, returns a pointer to the file record
+ that the attribute was moved to. The caller may assume
+ that the moved attribute is the first one in the file
+ record. NewBcb and NewFileRecord must either both be
+ specified or neither specified.
+
+Return Value:
+
+ LONGLONG - Segment reference number of new record without a sequence number.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT ListContext;
+ ATTRIBUTE_ENUMERATION_CONTEXT MoveContext;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord1, FileRecord2;
+ PATTRIBUTE_RECORD_HEADER Attribute2;
+ BOOLEAN FoundListContext;
+ MFT_SEGMENT_REFERENCE Reference2;
+ LONGLONG MftRecordNumber2;
+ WCHAR NameBuffer[8];
+ UNICODE_STRING AttributeName;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ VCN LowestVcn;
+ BOOLEAN Found;
+ BOOLEAN IsNonresident = FALSE;
+ PBCB Bcb = NULL;
+ PATTRIBUTE_TYPE_CODE NewEnd;
+ PVCB Vcb = Fcb->Vcb;
+ ULONG NewListSize = 0;
+ BOOLEAN MftData = FALSE;
+ PATTRIBUTE_RECORD_HEADER OldPosition = NULL;
+
+ PAGED_CODE();
+
+ //
+ // Make sure the attribute is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Vcb,
+ Context );
+
+ //
+ // See if we are being asked to move the Mft Data.
+ //
+
+ if ((Fcb == Vcb->MftScb->Fcb) && (Attribute->TypeCode == $DATA)) {
+
+ MftData = TRUE;
+ }
+
+ NtfsInitializeAttributeContext( &ListContext );
+ NtfsInitializeAttributeContext( &MoveContext );
+ FileRecord1 = NtfsContainingFileRecord(Context);
+
+ //
+ // Save a description of the attribute to help us look it up
+ // again, and to make clones if necessary.
+ //
+
+ ASSERT( Attribute->RecordLength == QuadAlign( Attribute->RecordLength ));
+ AttributeTypeCode = Attribute->TypeCode;
+ AttributeName.Length =
+ AttributeName.MaximumLength = (USHORT)Attribute->NameLength << 1;
+ AttributeName.Buffer = NameBuffer;
+
+ if (AttributeName.Length > sizeof(NameBuffer)) {
+
+ AttributeName.Buffer = NtfsAllocatePool( NonPagedPool, AttributeName.Length );
+ }
+
+ RtlCopyMemory( AttributeName.Buffer,
+ Add2Ptr(Attribute, Attribute->NameOffset),
+ AttributeName.Length );
+
+ if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ IsNonresident = TRUE;
+ LowestVcn = Attribute->Form.Nonresident.LowestVcn;
+ }
+
+ try {
+
+ //
+ // Lookup the list context so that we know where it is at.
+ //
+
+ FoundListContext =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $ATTRIBUTE_LIST,
+ &ListContext );
+
+ //
+ // If we do not already have an attribute list, then calculate
+ // how big it must be. Note, there must only be one file record
+ // at this point.
+ //
+
+ if (!FoundListContext) {
+
+ ASSERT( FileRecord1 == NtfsContainingFileRecord(&ListContext) );
+
+ NewListSize = GetSizeForAttributeList( FileRecord1 );
+
+ //
+ // Now if the attribute list already exists, we have to look up
+ // the first one we are going to move in order to update the
+ // attribute list later.
+ //
+
+ } else {
+
+ Found = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ Attribute->TypeCode,
+ &AttributeName,
+ IsNonresident ?
+ &LowestVcn :
+ NULL,
+ FALSE,
+ &MoveContext );
+
+ ASSERT(Found);
+ ASSERT(Attribute == NtfsFoundAttribute(&MoveContext));
+ }
+
+ //
+ // Allocate a new file record and move the attribute over.
+ //
+
+ FileRecord2 = NtfsCloneFileRecord( IrpContext, Fcb, MftData, &Bcb, &Reference2 );
+
+ //
+ // Remember the file record number for the new file record.
+ //
+
+ MftRecordNumber2 = NtfsFullSegmentNumber( &Reference2 );
+
+ Attribute2 = Add2Ptr( FileRecord2, FileRecord2->FirstAttributeOffset );
+ RtlCopyMemory( Attribute2, Attribute, (ULONG)Attribute->RecordLength );
+ Attribute2->Instance = FileRecord2->NextAttributeInstance++;
+ NewEnd = Add2Ptr( Attribute2, Attribute2->RecordLength );
+ *NewEnd = $END;
+ FileRecord2->FirstFreeByte = PtrOffset(FileRecord2, NewEnd)
+ + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ //
+ // If this is the Mft Data attribute, we cannot really move it, we
+ // have to move all but the first part of it.
+ //
+
+ if (MftData) {
+
+ PCHAR MappingPairs;
+ ULONG NewSize;
+ VCN OriginalLastVcn;
+ VCN LastVcn;
+ LONGLONG SavedFileSize = Attribute->Form.Nonresident.FileSize;
+ LONGLONG SavedValidDataLength = Attribute->Form.Nonresident.ValidDataLength;
+ PNTFS_MCB Mcb = &Vcb->MftScb->Mcb;
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $DATA,
+ Context );
+
+ ASSERT(Found);
+
+ //
+ // Calculate the number of clusters in the Mft up to (possibly past) the
+ // first user file record, and decrement to get LastVcn to stay in first
+ // file record.
+ //
+
+ LastVcn = LlClustersFromBytes( Vcb,
+ FIRST_USER_FILE_NUMBER *
+ Vcb->BytesPerFileRecordSegment ) - 1;
+ OriginalLastVcn = Attribute->Form.Nonresident.HighestVcn;
+
+ //
+ // Now truncate the first Mft record.
+ //
+
+ NtfsDeleteAttributeAllocation( IrpContext,
+ Vcb->MftScb,
+ TRUE,
+ &LastVcn,
+ Context,
+ FALSE );
+
+ //
+ // Now get the first Lcn for the new file record.
+ //
+
+ LastVcn = Attribute->Form.Nonresident.HighestVcn + 1;
+ Attribute2->Form.Nonresident.LowestVcn = LastVcn;
+
+ //
+ // Calculate the size of the attribute record we will need.
+ // We only create mapping pairs through the highest Vcn on the
+ // disk. We don't include any that are being added through the
+ // Mcb yet.
+ //
+
+ NewSize = SIZEOF_PARTIAL_NONRES_ATTR_HEADER
+ + QuadAlign( AttributeName.Length )
+ + QuadAlign( NtfsGetSizeForMappingPairs( Mcb,
+ MAXULONG,
+ LastVcn,
+ &OriginalLastVcn,
+ &LastVcn ));
+
+ Attribute2->RecordLength = NewSize;
+
+ //
+ // Assume no attribute name, and calculate where the Mapping Pairs
+ // will go. (Update below if we are wrong.)
+ //
+
+ MappingPairs = (PCHAR)Attribute2 + SIZEOF_PARTIAL_NONRES_ATTR_HEADER;
+
+ //
+ // If the attribute has a name, take care of that now.
+ //
+
+ if (AttributeName.Length != 0) {
+
+ Attribute2->NameLength = (UCHAR)(AttributeName.Length / sizeof(WCHAR));
+ Attribute2->NameOffset = (USHORT)PtrOffset(Attribute2, MappingPairs);
+ RtlCopyMemory( MappingPairs,
+ AttributeName.Buffer,
+ AttributeName.Length );
+ MappingPairs += QuadAlign( AttributeName.Length );
+ }
+
+ //
+ // We always need the mapping pairs offset.
+ //
+
+ Attribute2->Form.Nonresident.MappingPairsOffset =
+ (USHORT)PtrOffset(Attribute2, MappingPairs);
+ NewEnd = Add2Ptr( Attribute2, Attribute2->RecordLength );
+ *NewEnd = $END;
+ FileRecord2->FirstFreeByte = PtrOffset(FileRecord2, NewEnd)
+ + QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ //
+ // Now add the space in the file record.
+ //
+
+ *MappingPairs = 0;
+ NtfsBuildMappingPairs( Mcb,
+ Attribute2->Form.Nonresident.LowestVcn,
+ &LastVcn,
+ MappingPairs );
+
+ Attribute2->Form.Nonresident.HighestVcn = LastVcn;
+
+ } else {
+
+ //
+ // Now log these changes and fix up the first file record.
+ //
+
+ FileRecord1->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ DeleteAttribute,
+ NULL,
+ 0,
+ CreateAttribute,
+ Attribute,
+ Attribute->RecordLength,
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord1,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Remember the old position for the CreateAttributeList
+ //
+
+ OldPosition = Attribute;
+
+ NtfsRestartRemoveAttribute( IrpContext,
+ FileRecord1,
+ (PCHAR)Attribute - (PCHAR)FileRecord1 );
+ }
+
+ FileRecord2->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ Bcb,
+ InitializeFileRecordSegment,
+ FileRecord2,
+ FileRecord2->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromFileRecords( Vcb, MftRecordNumber2 ),
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Finally, create the attribute list attribute if needed.
+ //
+
+ if (!FoundListContext) {
+
+ NtfsCleanupAttributeContext( &ListContext );
+ NtfsInitializeAttributeContext( &ListContext );
+ CreateAttributeList( IrpContext,
+ Fcb,
+ FileRecord1,
+ MftData ? NULL : FileRecord2,
+ Reference2,
+ OldPosition,
+ NewListSize,
+ &ListContext );
+ //
+ // Otherwise we have to update the existing attribute list, but only
+ // if this is not the Mft data. In that case the attribute list is
+ // still correct since we haven't moved the attribute entirely.
+ //
+
+ } else if (!MftData) {
+
+ UpdateAttributeListEntry( IrpContext,
+ Fcb,
+ &MoveContext.AttributeList.Entry->SegmentReference,
+ MoveContext.AttributeList.Entry->Instance,
+ &Reference2,
+ Attribute2->Instance,
+ &ListContext );
+ }
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ &AttributeName,
+ IsNonresident ? &LowestVcn : NULL,
+ FALSE,
+ Context );
+
+ ASSERT(Found);
+ ASSERT(!IsNonresident || (LowestVcn == NtfsFoundAttribute(Context)->Form.Nonresident.LowestVcn));
+
+ //
+ // For the case of the Mft split, we now add the final entry.
+ //
+
+ if (MftData) {
+
+ //
+ // Finally, we have to add the entry to the attribute list.
+ // The routine we have to do this gets most of its inputs
+ // out of an attribute context. Our context at this point
+ // does not have quite the right information, so we have to
+ // update it here before calling AddToAttributeList.
+ //
+
+ Context->FoundAttribute.FileRecord = FileRecord2;
+ Context->FoundAttribute.Attribute = Attribute2;
+ Context->AttributeList.Entry =
+ NtfsGetNextRecord(Context->AttributeList.Entry);
+
+ NtfsAddToAttributeList( IrpContext, Fcb, Reference2, Context );
+
+ NtfsCleanupAttributeContext( Context );
+ NtfsInitializeAttributeContext( Context );
+
+ Found =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $DATA,
+ Context );
+
+ ASSERT(Found);
+
+ while (IsNonresident &&
+ (Attribute2->Form.Nonresident.LowestVcn !=
+ NtfsFoundAttribute(Context)->Form.Nonresident.LowestVcn)) {
+
+ Found =
+ NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $DATA,
+ Context );
+
+ ASSERT(Found);
+ }
+ }
+
+ } finally {
+
+ if (AttributeName.Buffer != NameBuffer) {
+ NtfsFreePool(AttributeName.Buffer);
+ }
+
+ if (ARGUMENT_PRESENT(NewBcb)) {
+
+ ASSERT(ARGUMENT_PRESENT(NewFileRecord));
+
+ *NewBcb = Bcb;
+ *NewFileRecord = FileRecord2;
+
+ } else {
+
+ ASSERT(!ARGUMENT_PRESENT(NewFileRecord));
+
+ NtfsUnpinBcb( &Bcb );
+ }
+
+ NtfsCleanupAttributeContext( &ListContext );
+ NtfsCleanupAttributeContext( &MoveContext );
+ }
+
+ return MftRecordNumber2;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+SplitFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG SizeNeeded,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine splits a file record in two, when it has been found that
+ there is no room for a new attribute. If the file does not already have
+ an attribute list attribute then one is created.
+
+ Essentially this routine finds the midpoint in the current file record
+ (accounting for a potential new attribute list and also the space needed).
+ Then it copies the second half of the file record over and fixes up the
+ first record. The attribute list is created at the end if required.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ SizeNeeded - Supplies the additional size needed, which is causing the split
+ to occur.
+
+ Context - Supplies the attribute enumeration context pointing to the spot
+ where the new attribute is to be inserted or grown.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT ListContext;
+ ATTRIBUTE_ENUMERATION_CONTEXT MoveContext;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord1, FileRecord2;
+ PATTRIBUTE_RECORD_HEADER Attribute1, Attribute2, Attribute;
+ ULONG NewListOffset = 0;
+ ULONG NewListSize = 0;
+ ULONG NewAttributeOffset;
+ ULONG SizeToStay;
+ ULONG CurrentOffset, FutureOffset;
+ ULONG SizeToMove;
+ BOOLEAN FoundListContext;
+ MFT_SEGMENT_REFERENCE Reference1, Reference2;
+ LONGLONG MftFileRecord2;
+ PBCB Bcb = NULL;
+ ATTRIBUTE_TYPE_CODE EndCode = $END;
+ PVCB Vcb = Fcb->Vcb;
+ ULONG AdjustedAvailBytes;
+
+ PAGED_CODE();
+
+ //
+ // Make sure the attribute is pinned.
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Vcb,
+ Context );
+
+ //
+ // Something is broken if we decide to split an Mft record.
+ //
+
+ ASSERT(Fcb != Vcb->MftScb->Fcb);
+
+ NtfsInitializeAttributeContext( &ListContext );
+ NtfsInitializeAttributeContext( &MoveContext );
+ FileRecord1 = NtfsContainingFileRecord(Context);
+ Attribute1 = NtfsFoundAttribute(Context);
+
+ try {
+
+ //
+ // Lookup the list context so that we know where it is at.
+ //
+
+ FoundListContext =
+ NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $ATTRIBUTE_LIST,
+ &ListContext );
+
+ //
+ // If we do not already have an attribute list, then calculate
+ // where it will go and how big it must be. Note, there must
+ // only be one file record at this point.
+ //
+
+ if (!FoundListContext) {
+
+ ASSERT( FileRecord1 == NtfsContainingFileRecord(&ListContext) );
+
+ NewListOffset = PtrOffset( FileRecord1,
+ NtfsFoundAttribute(&ListContext) );
+
+ NewListSize = GetSizeForAttributeList( FileRecord1 ) +
+ SIZEOF_RESIDENT_ATTRIBUTE_HEADER;
+ }
+
+ //
+ // Similarly describe where the new attribute is to go, and how
+ // big it is (already in SizeNeeded).
+ //
+
+ NewAttributeOffset = PtrOffset( FileRecord1, Attribute1 );
+
+ //
+ // Now calculate the approximate number of bytes that is to be split
+ // across two file records, and divide it in two, and that should give
+ // the amount that is to stay in the first record.
+ //
+
+ SizeToStay = (FileRecord1->FirstFreeByte + NewListSize +
+ SizeNeeded + sizeof(FILE_RECORD_SEGMENT_HEADER)) / 2;
+
+ //
+ // We know that since we called this routine we need to split at
+ // least one entry from this file record. We also base our
+ // split logic by finding the first attribute which WILL lie beyond
+ // the split point (after adding an attribute list and possibly
+ // an intermediate attribute). We shrink the split point to the
+ // position at the end of where the current last attribute will be
+ // after adding the attribute list. If we also add space before
+ // the last attribute then we know the last attribute will surely
+ // be split out.
+ //
+
+ if (SizeToStay > (FileRecord1->FirstFreeByte - sizeof( LONGLONG ) + NewListSize)) {
+
+ SizeToStay = FileRecord1->FirstFreeByte - sizeof( LONGLONG ) + NewListSize;
+ }
+
+ //
+ // Now begin the loop through the attributes to find the splitting
+ // point. We stop when we reach the end record or are past the attribute
+ // which contains the split point. We will split at the current attribute
+ // if the remaining bytes after this attribute won't allow us to add
+ // the bytes we need for the caller or create an attribute list if
+ // it doesn't exist.
+ //
+ // At this point the following variables indicate the following:
+ //
+ // FutureOffset - This the offset of the current attribute
+ // after adding an attribute list and the attribute we
+ // are making space for.
+ //
+ // CurrentOffset - Current position in the file record of
+ // of attribute being examined now.
+ //
+ // NewListOffset - Offset to insert new attribute list into
+ // file record (0 indicates the list already exists).
+ //
+ // NewAttributeOffset - Offset in the file record of the new
+ // attribute. This refers to the file record as it exists
+ // when this routine is called.
+ //
+
+ FutureOffset =
+ CurrentOffset = (ULONG)FileRecord1->FirstAttributeOffset;
+ Attribute1 = Add2Ptr( FileRecord1, CurrentOffset );
+ AdjustedAvailBytes = FileRecord1->BytesAvailable
+ - QuadAlign( sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ while (Attribute1->TypeCode != $END) {
+
+ //
+ // See if the attribute list goes here.
+ //
+
+ if (CurrentOffset == NewListOffset) {
+
+ //
+ // This attribute and all later attributes will be moved
+ // by the size of attribute list.
+ //
+
+ FutureOffset += NewListSize;
+ }
+
+ //
+ // See if the new attribute goes here.
+ //
+
+ if (CurrentOffset == NewAttributeOffset) {
+
+ //
+ // This attribute and all later attributes will be moved
+ // by the size of new attribute.
+ //
+
+ FutureOffset += SizeNeeded;
+ }
+
+ FutureOffset += Attribute1->RecordLength;
+
+ //
+ // Check if we are at the split point. We split at this point
+ // if the end of the current attribute will be at or beyond the
+ // split point after adjusting for adding either an attribute list
+ // or new attribute. We make this test >= since these two values
+ // will be equal if we reach the last attribute without finding
+ // the split point. This way we guarantee a split will happen.
+ //
+ // Note that we will go to the next attribute if the current attribute
+ // is the first attribute in the file record. This can happen if the
+ // first attribute is resident and must stay resident but takes up
+ // half the file record or more (i.e. large filename attribute).
+ // We must make sure to split at least one attribute out of this
+ // record.
+ //
+ // Never split when pointing at $STANDARD_INFORMATION or $ATTRIBUTE_LIST.
+ //
+
+ if ((Attribute1->TypeCode > $ATTRIBUTE_LIST) &&
+ (FutureOffset >= SizeToStay) &&
+ (CurrentOffset != FileRecord1->FirstAttributeOffset)) {
+
+ break;
+ }
+
+ CurrentOffset += Attribute1->RecordLength;
+
+ Attribute1 = Add2Ptr( Attribute1, Attribute1->RecordLength );
+ }
+
+ SizeToMove = FileRecord1->FirstFreeByte - CurrentOffset;
+
+ //
+ // If we are pointing at the attribute list or at the end record
+ // we don't do the split.
+ //
+
+ if ((Attribute1->TypeCode == $END) || (Attribute1->TypeCode <= $ATTRIBUTE_LIST)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Now if the attribute list already exists, we have to look up
+ // the first one we are going to move in order to update the
+ // attribute list later.
+ //
+
+ if (FoundListContext) {
+
+ UNICODE_STRING AttributeName;
+ BOOLEAN FoundIt;
+
+ AttributeName.Length =
+ AttributeName.MaximumLength = (USHORT)Attribute1->NameLength << 1;
+ AttributeName.Buffer = Add2Ptr( Attribute1, Attribute1->NameOffset );
+
+ FoundIt = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ Attribute1->TypeCode,
+ &AttributeName,
+ (Attribute1->FormCode == NONRESIDENT_FORM) ?
+ &Attribute1->Form.Nonresident.LowestVcn :
+ NULL,
+ FALSE,
+ &MoveContext );
+
+ //
+ // If we are splitting the file record between multiple attributes with
+ // the same name (i.e. FILE_NAME attributes) then we need to find the
+ // correct attribute. Since this is an unusual case we will just scan
+ // forwards from the current attribute until we find the correct attribute.
+ //
+
+ while (FoundIt && (Attribute1 != NtfsFoundAttribute( &MoveContext ))) {
+
+ FoundIt = NtfsLookupNextAttributeByName( IrpContext,
+ Fcb,
+ Attribute1->TypeCode,
+ &AttributeName,
+ FALSE,
+ &MoveContext );
+ }
+
+ ASSERT(FoundIt);
+ ASSERT(Attribute1 == NtfsFoundAttribute(&MoveContext));
+ }
+
+ //
+ // Now Attribute1 is pointing to the first attribute to move.
+ // Allocate a new file record and move the rest of our attributes
+ // over.
+ //
+
+ if (FoundListContext) {
+ Reference1 = MoveContext.AttributeList.Entry->SegmentReference;
+ }
+
+ FileRecord2 = NtfsCloneFileRecord( IrpContext, Fcb, FALSE, &Bcb, &Reference2 );
+
+ //
+ // Capture the file record number of the new file record.
+ //
+
+ MftFileRecord2 = NtfsFullSegmentNumber( &Reference2 );
+
+ Attribute2 = Add2Ptr( FileRecord2, FileRecord2->FirstAttributeOffset );
+ RtlCopyMemory( Attribute2, Attribute1, SizeToMove );
+ FileRecord2->FirstFreeByte = (ULONG)FileRecord2->FirstAttributeOffset +
+ SizeToMove;
+
+ //
+ // Loop to update all of the attribute instance codes
+ //
+
+ for (Attribute = Attribute2;
+ Attribute < (PATTRIBUTE_RECORD_HEADER)Add2Ptr(FileRecord2, FileRecord2->FirstFreeByte)
+ && Attribute->TypeCode != $END;
+ Attribute = NtfsGetNextRecord(Attribute)) {
+
+ NtfsCheckRecordBound( Attribute, FileRecord2, Vcb->BytesPerFileRecordSegment );
+
+ if (FoundListContext) {
+
+ UpdateAttributeListEntry( IrpContext,
+ Fcb,
+ &Reference1,
+ Attribute->Instance,
+ &Reference2,
+ FileRecord2->NextAttributeInstance,
+ &ListContext );
+ }
+
+ Attribute->Instance = FileRecord2->NextAttributeInstance++;
+ }
+
+ //
+ // Now log these changes and fix up the first file record.
+ //
+
+ FileRecord2->Lsn = NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ Bcb,
+ InitializeFileRecordSegment,
+ FileRecord2,
+ FileRecord2->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromFileRecords( Vcb, MftFileRecord2 ),
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ FileRecord1->Lsn = NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ WriteEndOfFileRecordSegment,
+ &EndCode,
+ sizeof(ATTRIBUTE_TYPE_CODE),
+ WriteEndOfFileRecordSegment,
+ Attribute1,
+ SizeToMove,
+ NtfsMftOffset( Context ),
+ CurrentOffset,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ NtfsRestartWriteEndOfFileRecord( FileRecord1,
+ Attribute1,
+ (PATTRIBUTE_RECORD_HEADER)&EndCode,
+ sizeof(ATTRIBUTE_TYPE_CODE) );
+
+ //
+ // Finally, create the attribute list attribute if needed.
+ //
+
+ if (!FoundListContext) {
+
+ NtfsCleanupAttributeContext( &ListContext );
+ NtfsInitializeAttributeContext( &ListContext );
+ CreateAttributeList( IrpContext,
+ Fcb,
+ FileRecord1,
+ FileRecord2,
+ Reference2,
+ NULL,
+ NewListSize - SIZEOF_RESIDENT_ATTRIBUTE_HEADER,
+ &ListContext );
+ }
+
+ } finally {
+
+ NtfsUnpinBcb( &Bcb );
+
+ NtfsCleanupAttributeContext( &ListContext );
+ NtfsCleanupAttributeContext( &MoveContext );
+ }
+}
+
+
+VOID
+NtfsRestartWriteEndOfFileRecord (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER OldAttribute,
+ IN PATTRIBUTE_RECORD_HEADER NewAttributes,
+ IN ULONG SizeOfNewAttributes
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called both in the running system and at restart to
+ modify the end of a file record, such as after it was split in two.
+
+Arguments:
+
+ FileRecord - Supplies the pointer to the file record.
+
+ OldAttribute - Supplies a pointer to the first attribute to be overwritten.
+
+ NewAttributes - Supplies a pointer to the new attribute(s) to be copied to
+ the spot above.
+
+ SizeOfNewAttributes - Supplies the size to be copied in bytes.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ RtlMoveMemory( OldAttribute, NewAttributes, SizeOfNewAttributes );
+
+ FileRecord->FirstFreeByte = PtrOffset(FileRecord, OldAttribute) +
+ SizeOfNewAttributes;
+
+ //
+ // The size coming in may not be quad aligned.
+ //
+
+ FileRecord->FirstFreeByte = QuadAlign( FileRecord->FirstFreeByte );
+}
+
+
+//
+// Internal support routine
+//
+
+PFILE_RECORD_SEGMENT_HEADER
+NtfsCloneFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN MftData,
+ OUT PBCB *Bcb,
+ OUT PMFT_SEGMENT_REFERENCE FileReference
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates an additional file record for an already existing
+ and open file, for the purpose of overflowing attributes to this record.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ MftData - TRUE if the file record is being cloned to describe the
+ $DATA attribute for the Mft.
+
+ Bcb - Returns a pointer to the Bcb for the new file record.
+
+ FileReference - returns the file reference for the new file record.
+
+Return Value:
+
+ Pointer to the allocated file record.
+
+--*/
+
+{
+ LONGLONG FileRecordOffset;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PVCB Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // First allocate the record.
+ //
+
+ *FileReference = NtfsAllocateMftRecord( IrpContext,
+ Vcb,
+ MftData );
+
+ //
+ // Read it in and pin it.
+ //
+
+ NtfsPinMftRecord( IrpContext,
+ Vcb,
+ FileReference,
+ TRUE,
+ Bcb,
+ &FileRecord,
+ &FileRecordOffset );
+
+ //
+ // Initialize it.
+ //
+
+ NtfsInitializeMftRecord( IrpContext,
+ Vcb,
+ FileReference,
+ FileRecord,
+ *Bcb,
+ BooleanIsDirectory( &Fcb->Info ));
+
+ FileRecord->BaseFileRecordSegment = Fcb->FileReference;
+ FileRecord->ReferenceCount = 0;
+ FileReference->SequenceNumber = FileRecord->SequenceNumber;
+
+ return FileRecord;
+}
+
+
+//
+// Internal support routine
+//
+
+ULONG
+GetSizeForAttributeList (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is designed to calculate the size that will be required for
+ an attribute list attribute, for a base file record which is just about
+ to split into two file record segments.
+
+Arguments:
+
+ FileRecord - Pointer to the file record which is just about to split.
+
+Return Value:
+
+ Size in bytes of the attribute list attribute that will be required,
+ not including the attribute header size.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ULONG Size = 0;
+
+ PAGED_CODE();
+
+ //
+ // Point to first attribute.
+ //
+
+ Attribute = Add2Ptr(FileRecord, FileRecord->FirstAttributeOffset);
+
+ //
+ // Loop to add up size of required attribute list entries.
+ //
+
+ while (Attribute->TypeCode != $END) {
+
+ Size += QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName )
+ + ((ULONG) Attribute->NameLength << 1));
+
+ Attribute = Add2Ptr( Attribute, Attribute->RecordLength );
+ }
+
+ return Size;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+CreateAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord1,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord2 OPTIONAL,
+ IN MFT_SEGMENT_REFERENCE SegmentReference2,
+ IN PATTRIBUTE_RECORD_HEADER OldPosition OPTIONAL,
+ IN ULONG SizeOfList,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is intended to be called to create the attribute list attribute
+ the first time. The caller must have already calculated the size required
+ for the list to pass into this routine. The caller must have already
+ removed any attributes from the base file record (FileRecord1) which are
+ not to remain there. He must then pass in a pointer to the base file record
+ and optionally a pointer to a second file record from which the new
+ attribute list is to be created.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ FileRecord1 - Pointer to the base file record, currently holding only those
+ attributes to be described there.
+
+ FileRecord2 - Optionally points to a second file record from which the
+ second half of the attribute list is to be constructed.
+
+ SegmentReference2 - The Mft segment reference of the second file record,
+ if one was supplied.
+
+ OldPosition - Should only be specified if FileRecord2 is specified. In this
+ case it must point to an attribute position in FileRecord1 from
+ which a single attribute was moved to file record 2. It will be
+ used as an indication of where the attribute list entry should
+ be inserted.
+
+ SizeOfList - Exact size of the attribute list which will be required.
+
+ ListContext - Context resulting from an attempt to look up the attribute
+ list attribute, which failed.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_LIST_ENTRY AttributeList, ListEntry;
+ MFT_SEGMENT_REFERENCE SegmentReference;
+
+ PAGED_CODE();
+
+ //
+ // Allocate space to construct the attribute list. (The list
+ // cannot be constructed in place, because that would destroy error
+ // recovery.)
+ //
+
+ ListEntry =
+ AttributeList = (PATTRIBUTE_LIST_ENTRY) NtfsAllocatePool(PagedPool, SizeOfList );
+
+ //
+ // Use try-finally to deallocate on the way out.
+ //
+
+ try {
+
+ //
+ // Loop to fill in the attribute list from the two file records
+ //
+
+ for (FileRecord = FileRecord1, SegmentReference = Fcb->FileReference;
+ FileRecord != NULL;
+ FileRecord = ((FileRecord == FileRecord1) ? FileRecord2 : NULL),
+ SegmentReference = SegmentReference2) {
+
+ //
+ // Point to first attribute.
+ //
+
+ Attribute = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset );
+
+ //
+ // Loop to add up size of required attribute list entries.
+ //
+
+ while (Attribute->TypeCode != $END) {
+
+ PATTRIBUTE_RECORD_HEADER NextAttribute;
+
+ //
+ // See if we are at the remembered position. If so:
+ //
+ // Save this attribute to be the next one.
+ // Point to the single attribute in FileRecord2 instead
+ // Clear FileRecord2, as we will "consume" it here.
+ // Set the Segment reference in the ListEntry
+ //
+
+ if ((Attribute == OldPosition) && (FileRecord2 != NULL)) {
+
+ NextAttribute = Attribute;
+ Attribute = Add2Ptr(FileRecord2, FileRecord2->FirstAttributeOffset);
+ FileRecord2 = NULL;
+ ListEntry->SegmentReference = SegmentReference2;
+
+ //
+ // Otherwise, this is the normal loop case. So:
+ //
+ // Set the next attribute pointer accordingly.
+ // Set the Segment reference from the loop control
+ //
+
+ } else {
+
+ NextAttribute = Add2Ptr(Attribute, Attribute->RecordLength);
+ ListEntry->SegmentReference = SegmentReference;
+ }
+
+ //
+ // Now fill in the list entry.
+ //
+
+ ListEntry->AttributeTypeCode = Attribute->TypeCode;
+ ListEntry->RecordLength = (USHORT) QuadAlign( FIELD_OFFSET( ATTRIBUTE_LIST_ENTRY, AttributeName )
+ + ((ULONG) Attribute->NameLength << 1));
+ ListEntry->AttributeNameLength = Attribute->NameLength;
+ ListEntry->AttributeNameOffset =
+ (UCHAR)PtrOffset( ListEntry, &ListEntry->AttributeName[0] );
+
+ ListEntry->Instance = Attribute->Instance;
+
+ ListEntry->LowestVcn = 0;
+
+ if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ ListEntry->LowestVcn = Attribute->Form.Nonresident.LowestVcn;
+ }
+
+ if (Attribute->NameLength != 0) {
+
+ RtlCopyMemory( &ListEntry->AttributeName[0],
+ Add2Ptr(Attribute, Attribute->NameOffset),
+ Attribute->NameLength << 1 );
+ }
+
+ ListEntry = Add2Ptr(ListEntry, ListEntry->RecordLength);
+ Attribute = NextAttribute;
+ }
+ }
+
+ //
+ // Now create the attribute list attribute.
+ //
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $ATTRIBUTE_LIST,
+ NULL,
+ AttributeList,
+ SizeOfList,
+ 0,
+ NULL,
+ TRUE,
+ ListContext );
+
+ } finally {
+
+ NtfsFreePool( AttributeList );
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+UpdateAttributeListEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PMFT_SEGMENT_REFERENCE OldFileReference,
+ IN USHORT OldInstance,
+ IN PMFT_SEGMENT_REFERENCE NewFileReference,
+ IN USHORT NewInstance,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT ListContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to update a range of the attribute list
+ as required by the movement of a range of attributes to a second record.
+ The caller must supply a pointer to the file record to which the attributes
+ have moved, along with the segment reference of that record.
+
+Arguments:
+
+ Fcb - Requested file.
+
+ OldFileReference - Old File Reference for attribute
+
+ OldInstance - Old Instance number for attribute
+
+ NewFileReference - New File Reference for attribute
+
+ NewInstance - New Instance number for attribute
+
+ ListContext - The attribute enumeration context which was used to locate
+ the attribute list.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_LIST_ENTRY AttributeList, ListEntry, BeyondList;
+ PBCB Bcb = NULL;
+ ULONG SizeOfList;
+ ATTRIBUTE_LIST_ENTRY NewEntry;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ PAGED_CODE();
+
+ //
+ // Map the attribute list if the attribute is non-resident. Otherwise the
+ // attribute is already mapped and we have a Bcb in the attribute context.
+ //
+
+ Attribute = NtfsFoundAttribute( ListContext );
+
+ if (!NtfsIsAttributeResident( Attribute )) {
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *) &AttributeList,
+ &SizeOfList,
+ &Bcb,
+ ListContext );
+
+ //
+ // Don't call the Map attribute routine because it NULLs the Bcb in the
+ // attribute list. This Bcb is needed for ChangeAttributeValue to mark
+ // the page dirty.
+ //
+
+ } else {
+
+ AttributeList = (PATTRIBUTE_LIST_ENTRY) NtfsAttributeValue( Attribute );
+ SizeOfList = Attribute->Form.Resident.ValueLength;
+ }
+
+ //
+ // Make sure we unpin the list.
+ //
+
+ try {
+
+ //
+ // Point beyond the end of the list.
+ //
+
+ BeyondList = (PATTRIBUTE_LIST_ENTRY)Add2Ptr( AttributeList, SizeOfList );
+
+ //
+ // Loop through all of the attribute list entries until we find the one
+ // we need to change.
+ //
+
+ for (ListEntry = AttributeList;
+ ListEntry < BeyondList;
+ ListEntry = NtfsGetNextRecord(ListEntry)) {
+
+ if ((ListEntry->Instance == OldInstance) &&
+ NtfsEqualMftRef(&ListEntry->SegmentReference, OldFileReference)) {
+
+ break;
+ }
+ }
+
+ //
+ // We better have found it!
+ //
+
+ ASSERT(ListEntry < BeyondList);
+
+ if (ListEntry >= BeyondList) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Make a copy of the fixed portion of the attribute list entry,
+ // and update to describe the new attribute location.
+ //
+
+ RtlCopyMemory( &NewEntry, ListEntry, sizeof(ATTRIBUTE_LIST_ENTRY) );
+
+ NewEntry.SegmentReference = *NewFileReference;
+ NewEntry.Instance = NewInstance;
+
+ //
+ // Update the attribute list entry.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ PtrOffset(AttributeList, ListEntry),
+ &NewEntry,
+ sizeof(ATTRIBUTE_LIST_ENTRY),
+ FALSE,
+ TRUE,
+ FALSE,
+ TRUE,
+ ListContext );
+
+ } finally {
+
+ NtfsUnpinBcb( &Bcb );
+ }
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsAddNameToParent (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN BOOLEAN IgnoreCase,
+ IN PBOOLEAN LogIt,
+ IN PFILE_NAME FileNameAttr,
+ OUT PUCHAR FileNameFlags,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ IN PNAME_PAIR NamePair OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will create the filename attribute with the given name.
+ Depending on the IgnoreCase flag, this is either a link or an Ntfs
+ name. If it is an Ntfs name, we check if it is also the Dos name.
+
+ We build a file name attribute and then add it via ThisFcb, we then
+ add this entry to the parent.
+
+ If the name is a Dos name and we are given tunneling information on
+ the long name, we will add the long name attribute as well.
+
+Arguments:
+
+ ParentScb - This is the parent directory for the file.
+
+ ThisFcb - This is the file to add the filename to.
+
+ IgnoreCase - Indicates if this name is case insensitive. Only for Posix
+ will this be FALSE.
+
+ LogIt - Indicates if we should log this operation. If FALSE and this is a large
+ name then log the file record and begin logging.
+
+ FileNameAttr - This contains a file name attribute structure to use.
+
+ FileNameFlags - We store a copy of the File name flags used in the file
+ name attribute.
+
+ QuickIndex - If specified, we store the information about the location of the
+ index entry added.
+
+ NamePair - If specified, we add the tunneled NTFS-only name if the name we are
+ directly adding is DOS-only.
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddNameToParent: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Decide whether the name is a link, Ntfs-Only or Ntfs/8.3 combined name.
+ // Update the filename attribute to reflect this.
+ //
+
+ if (!IgnoreCase) {
+
+ *FileNameFlags = 0;
+
+ } else {
+
+ UNICODE_STRING FileName;
+
+ FileName.Length = FileName.MaximumLength = (USHORT)(FileNameAttr->FileNameLength * sizeof(WCHAR));
+ FileName.Buffer = FileNameAttr->FileName;
+
+ *FileNameFlags = FILE_NAME_NTFS;
+
+ if (NtfsIsFatNameValid( &FileName, FALSE )) {
+
+ *FileNameFlags |= FILE_NAME_DOS;
+ }
+
+ //
+ // If the name is DOS and there was a tunneled NTFS name, add it first if both names
+ // exist in the pair (there may only be one in the long side). Note that we
+ // really need to do this first so we lay down the correct filename flags.
+ //
+
+ if (NamePair &&
+ (NamePair->Long.Length > 0) &&
+ (NamePair->Short.Length > 0) &&
+ (*FileNameFlags == (FILE_NAME_NTFS | FILE_NAME_DOS))) {
+
+ if (NtfsAddTunneledNtfsOnlyName(IrpContext,
+ ParentScb,
+ ThisFcb,
+ &NamePair->Long,
+ LogIt )) {
+
+ //
+ // Name didn't conflict and was added, so fix up the FileNameFlags
+ //
+
+ *FileNameFlags = FILE_NAME_DOS;
+
+ //
+ // We also need to upcase the short DOS name since we don't know the
+ // case of what the user handed us and all DOS names are upcase. Note
+ // that prior to tunneling being supported it was not possible for a user
+ // to specify a short name, so this is a new situation.
+ //
+
+ RtlUpcaseUnicodeString(&FileName, &FileName, FALSE);
+ }
+ }
+ }
+
+ //
+ // Now update the file name attribute.
+ //
+
+ FileNameAttr->Flags = *FileNameFlags;
+
+ //
+ // If we haven't been logging and this is a large name then begin logging.
+ //
+
+ if (!(*LogIt) &&
+ (FileNameAttr->FileNameLength > 100)) {
+
+ //
+ // Look up the file record and log its current state.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, ThisFcb );
+ }
+
+ NtfsPinMappedAttribute( IrpContext, ThisFcb->Vcb, &AttrContext );
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+
+ //
+ // Log the current state of the file record.
+ //
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ ThisFcb->Vcb->MftScb,
+ NtfsFoundBcb( &AttrContext ),
+ InitializeFileRecordSegment,
+ FileRecord,
+ FileRecord->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ NtfsMftOffset( &AttrContext ),
+ 0,
+ 0,
+ ThisFcb->Vcb->BytesPerFileRecordSegment );
+
+ *LogIt = TRUE;
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsInitializeAttributeContext( &AttrContext );
+ }
+
+ //
+ // Put it in the file record.
+ //
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ $FILE_NAME,
+ NULL,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ 0,
+ &FileNameAttr->ParentDirectory,
+ *LogIt,
+ &AttrContext );
+
+ //
+ // Now put it in the index entry.
+ //
+
+ NtfsAddIndexEntry( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ &ThisFcb->FileReference,
+ QuickIndex );
+
+ } finally {
+
+ DebugUnwind( NtfsAddNameToParent );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsAddNameToParent: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsAddDosOnlyName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN UNICODE_STRING FileName,
+ IN BOOLEAN LogIt,
+ IN PUNICODE_STRING SuggestedDosName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to build a Dos only name attribute an put it in
+ the file record and the parent index. We need to allocate pool large
+ enough to hold the name (easy for 8.3) and then check that the generated
+ names don't already exist in the parent. Use the suggested name first if
+ possible.
+
+Arguments:
+
+ ParentScb - This is the parent directory for the file.
+
+ ThisFcb - This is the file to add the filename to.
+
+ FileName - This is the file name to add.
+
+ LogIt - Indicates if we should log this operation.
+
+ SuggestedDosName - If supplied, a name to try to use before auto-generation
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ GENERATE_NAME_CONTEXT NameContext;
+ PFILE_NAME FileNameAttr;
+ UNICODE_STRING Name8dot3;
+
+ PINDEX_ENTRY IndexEntry;
+ PBCB IndexEntryBcb;
+ UCHAR TrailingDotAdj;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ BOOLEAN TrySuggestedDosName = TRUE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddDosOnlyName: Entered\n") );
+
+ IndexEntryBcb = NULL;
+
+ RtlZeroMemory( &NameContext, sizeof( GENERATE_NAME_CONTEXT ));
+
+ if (SuggestedDosName == NULL || SuggestedDosName->Length == 0) {
+
+ //
+ // The SuggestedDosName can be zero length if we have a tunneled
+ // link or a tunneled file which was created whilst short name
+ // generation was disabled. It is a bad thing to drop down null
+ // filenames ...
+ //
+
+ TrySuggestedDosName = FALSE;
+ }
+
+ //
+ // The maximum length is 24 bytes, but 2 are already defined with the
+ // FILE_NAME structure.
+ //
+
+ FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME ) + 22 );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Set up the string to hold the generated name. It will be part
+ // of the file name attribute structure.
+ //
+
+ Name8dot3.Buffer = FileNameAttr->FileName;
+ Name8dot3.MaximumLength = 24;
+
+ FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference;
+ FileNameAttr->Flags = FILE_NAME_DOS;
+
+ //
+ // Copy the info values into the filename attribute.
+ //
+
+ RtlCopyMemory( &FileNameAttr->Info,
+ &ThisFcb->Info,
+ sizeof( DUPLICATED_INFORMATION ));
+
+ //
+ // We will loop indefinitely. We generate a name, look in the parent
+ // for it. If found we continue generating. If not then we have the
+ // name we need. Attempt to use the suggested name first.
+ //
+
+ while( TRUE ) {
+
+ TrailingDotAdj = 0;
+
+ if (TrySuggestedDosName) {
+
+ Name8dot3.Length = SuggestedDosName->Length;
+ RtlCopyMemory(Name8dot3.Buffer, SuggestedDosName->Buffer, SuggestedDosName->Length);
+ Name8dot3.MaximumLength = SuggestedDosName->MaximumLength;
+
+ } else {
+
+ RtlGenerate8dot3Name( &FileName,
+ BooleanFlagOn(NtfsData.Flags,NTFS_FLAGS_ALLOW_EXTENDED_CHAR),
+ &NameContext,
+ &Name8dot3 );
+
+ if ((Name8dot3.Buffer[(Name8dot3.Length / 2) - 1] == L'.') &&
+ (Name8dot3.Length > sizeof( WCHAR ))) {
+
+ TrailingDotAdj = 1;
+ }
+ }
+
+ FileNameAttr->FileNameLength = (UCHAR)(Name8dot3.Length / 2) - TrailingDotAdj;
+
+ if (!NtfsFindIndexEntry( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ TRUE,
+ NULL,
+ &IndexEntryBcb,
+ &IndexEntry )) {
+
+ break;
+ }
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ if (TrySuggestedDosName) {
+
+ //
+ // Failed to use the suggested name, so fix up the 8.3 space
+ //
+
+ Name8dot3.Buffer = FileNameAttr->FileName;
+ Name8dot3.MaximumLength = 24;
+
+ TrySuggestedDosName = FALSE;
+ }
+ }
+
+ //
+ // We add this entry to the file record.
+ //
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ $FILE_NAME,
+ NULL,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ 0,
+ &FileNameAttr->ParentDirectory,
+ LogIt,
+ &AttrContext );
+
+ //
+ // We add this entry to the parent.
+ //
+
+ NtfsAddIndexEntry( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ &ThisFcb->FileReference,
+ NULL );
+
+ } finally {
+
+ DebugUnwind( NtfsAddDosOnlyName );
+
+ NtfsFreePool( FileNameAttr );
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsAddDosOnlyName: Exit -> %08lx\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsAddTunneledNtfsOnlyName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN PUNICODE_STRING FileName,
+ IN PBOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to attempt to insert a tunneled NTFS-only name
+ attribute and put it in the file record and the parent index. If the
+ name collides with an existing name nothing occurs.
+
+Arguments:
+
+ ParentScb - This is the parent directory for the file.
+
+ ThisFcb - This is the file to add the filename to.
+
+ FileName - This is the file name to add.
+
+ LogIt - Indicates if we should log this operation. If FALSE and this is a large
+ name then log the file record and begin logging.
+
+Return Value:
+
+ Boolean true if the name is added, false otherwise
+
+--*/
+
+{
+ PFILE_NAME FileNameAttr;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ PINDEX_ENTRY IndexEntry;
+ PBCB IndexEntryBcb;
+
+ BOOLEAN Added = FALSE;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddTunneledNtfsOnlyName: Entered\n") );
+
+ IndexEntryBcb = NULL;
+
+ //
+ // One WCHAR is already defined with the FILE_NAME structure. It is unfortunate
+ // that we need to go to pool to do this ...
+ //
+
+ FileNameAttr = NtfsAllocatePool(PagedPool, sizeof( FILE_NAME ) + FileName->Length - sizeof(WCHAR) );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ RtlCopyMemory( FileNameAttr->FileName,
+ FileName->Buffer,
+ FileName->Length );
+
+ FileNameAttr->FileNameLength = FileName->Length / sizeof(WCHAR);
+
+ FileNameAttr->ParentDirectory = ParentScb->Fcb->FileReference;
+ FileNameAttr->Flags = FILE_NAME_NTFS;
+
+ //
+ // Copy the info values into the filename attribute.
+ //
+
+ RtlCopyMemory( &FileNameAttr->Info,
+ &ThisFcb->Info,
+ sizeof( DUPLICATED_INFORMATION ));
+
+ //
+ // Try out the name
+ //
+
+ if (!NtfsFindIndexEntry( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ TRUE,
+ NULL,
+ &IndexEntryBcb,
+ &IndexEntry )) {
+
+ //
+ // Restore the case of the tunneled name
+ //
+
+ RtlCopyMemory( FileNameAttr->FileName,
+ FileName->Buffer,
+ FileName->Length );
+
+ //
+ // If we haven't been logging and this is a large name then begin logging.
+ //
+
+ if (!(*LogIt) &&
+ (FileName->Length > 200)) {
+
+ //
+ // Look up the file record and log its current state.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, ThisFcb );
+ }
+
+ NtfsPinMappedAttribute( IrpContext, ThisFcb->Vcb, &AttrContext );
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+
+ //
+ // Log the current state of the file record.
+ //
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ ThisFcb->Vcb->MftScb,
+ NtfsFoundBcb( &AttrContext ),
+ InitializeFileRecordSegment,
+ FileRecord,
+ FileRecord->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ NtfsMftOffset( &AttrContext ),
+ 0,
+ 0,
+ ThisFcb->Vcb->BytesPerFileRecordSegment );
+
+ *LogIt = TRUE;
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsInitializeAttributeContext( &AttrContext );
+ }
+
+ //
+ // We add this entry to the file record.
+ //
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ $FILE_NAME,
+ NULL,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ 0,
+ &FileNameAttr->ParentDirectory,
+ *LogIt,
+ &AttrContext );
+
+ //
+ // We add this entry to the parent.
+ //
+
+ NtfsAddIndexEntry( IrpContext,
+ ParentScb,
+ FileNameAttr,
+ NtfsFileNameSize( FileNameAttr ),
+ &ThisFcb->FileReference,
+ NULL );
+
+ //
+ // Flag the addition
+ //
+
+ Added = TRUE;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsAddTunneledNtfsOnlyName );
+
+ NtfsFreePool( FileNameAttr );
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsAddTunneledNtfsOnlyName: Exit -> %08lx\n", Added) );
+ }
+
+ return Added;
+}
+
+
+
diff --git a/private/ntos/cntfs/bitmpsup.c b/private/ntos/cntfs/bitmpsup.c
new file mode 100644
index 000000000..528ba1183
--- /dev/null
+++ b/private/ntos/cntfs/bitmpsup.c
@@ -0,0 +1,7977 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ BitmpSup.c
+
+Abstract:
+
+ This module implements the general bitmap allocation & deallocation
+ routines for Ntfs. It is defined into two main parts the first
+ section handles the bitmap file for clusters on the disk. The
+ second part is for bitmap attribute allocation (e.g., the mft bitmap).
+
+ So unlike other modules this one has local procedure prototypes and
+ definitions followed by the exported bitmap file routines, followed
+ by the local bitmap file routines, and then followed by the bitmap
+ attribute routines, followed by the local bitmap attribute allocation
+ routines.
+
+Author:
+
+ Gary Kimura [GaryKi] 23-Nov-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#ifdef NTFS_FRAGMENT_DISK
+BOOLEAN NtfsFragmentDisk = FALSE;
+ULONG NtfsFragmentLength = 2;
+#endif
+
+//
+// Define stack overflow threshhold.
+//
+
+#define OVERFLOW_RECORD_THRESHHOLD (0xF00)
+
+//
+// A mask of single bits used to clear and set bits in a byte
+//
+
+static UCHAR BitMask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+
+//
+// Temporary routines that need to be coded in Rtl\Bitmap.c
+//
+
+ULONG
+RtlFindNextForwardRunClear (
+ IN PRTL_BITMAP BitMapHeader,
+ IN ULONG FromIndex,
+ IN PULONG StartingRunIndex
+ );
+
+ULONG
+RtlFindLastBackwardRunClear (
+ IN PRTL_BITMAP BitMapHeader,
+ IN ULONG FromIndex,
+ IN PULONG StartingRunIndex
+ );
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_BITMPSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('BFtN')
+
+
+//
+// This is the size of our LRU array which dictates how much information
+// will be cached
+//
+
+#define CLUSTERS_MEDIUM_DISK (0x80000)
+#define CLUSTERS_LARGE_DISK (0x100000)
+
+//
+// Some local manifest constants
+//
+
+#define BYTES_PER_PAGE (PAGE_SIZE)
+#define BITS_PER_PAGE (BYTES_PER_PAGE * 8)
+
+#define LOG_OF_BYTES_PER_PAGE (PAGE_SHIFT)
+#define LOG_OF_BITS_PER_PAGE (PAGE_SHIFT + 3)
+
+//
+// Local procedure prototypes for direct bitmap manipulation
+//
+
+VOID
+NtfsAllocateBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ );
+
+VOID
+NtfsFreeBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ );
+
+BOOLEAN
+NtfsFindFreeBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LONGLONG NumberToFind,
+ IN LCN StartingSearchHint,
+ OUT PLCN ReturnedLcn,
+ OUT PLONGLONG ClusterCountFound
+ );
+
+BOOLEAN
+NtfsAddRecentlyDeallocated (
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ IN OUT PRTL_BITMAP Bitmap
+ );
+
+//
+// The following two prototype are macros for calling map or pin data
+//
+// VOID
+// NtfsMapPageInBitmap (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN LCN Lcn,
+// OUT PLCN StartingLcn,
+// IN OUT PRTL_BITMAP Bitmap,
+// OUT PBCB *BitmapBcb,
+// );
+//
+// VOID
+// NtfsPinPageInBitmap (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN LCN Lcn,
+// OUT PLCN StartingLcn,
+// IN OUT PRTL_BITMAP Bitmap,
+// OUT PBCB *BitmapBcb,
+// );
+//
+
+#define NtfsMapPageInBitmap(A,B,C,D,E,F) NtfsMapOrPinPageInBitmap(A,B,C,D,E,F,FALSE)
+
+#define NtfsPinPageInBitmap(A,B,C,D,E,F) NtfsMapOrPinPageInBitmap(A,B,C,D,E,F,TRUE)
+
+VOID
+NtfsMapOrPinPageInBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ OUT PLCN StartingLcn,
+ IN OUT PRTL_BITMAP Bitmap,
+ OUT PBCB *BitmapBcb,
+ IN BOOLEAN AlsoPinData
+ );
+
+//
+// Local procedures prototypes for cached run manipulation
+//
+
+typedef enum _NTFS_RUN_STATE {
+ RunStateUnknown = 1,
+ RunStateFree,
+ RunStateAllocated
+} NTFS_RUN_STATE;
+typedef NTFS_RUN_STATE *PNTFS_RUN_STATE;
+
+VOID
+NtfsInitializeCachedBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+BOOLEAN
+NtfsIsLcnInCachedFreeRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ OUT PLCN StartingLcn,
+ OUT PLONGLONG ClusterCount
+ );
+
+VOID
+NtfsAddCachedRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount,
+ IN NTFS_RUN_STATE RunState
+ );
+
+VOID
+NtfsRemoveCachedRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ );
+
+BOOLEAN
+NtfsGetNextCachedFreeRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG RunIndex,
+ OUT PLCN StartingLcn,
+ OUT PLONGLONG ClusterCount,
+ OUT PNTFS_RUN_STATE RunState
+ );
+
+//
+// Local procedure prototype for doing read ahead on our cached
+// run information
+//
+
+VOID
+NtfsReadAheadCachedBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn
+ );
+
+//
+// Local procedure prototypes for routines that help us find holes
+// that need to be filled with MCBs
+//
+
+BOOLEAN
+NtfsGetNextHoleToFill (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ OUT PVCN VcnToFill,
+ OUT PLONGLONG ClusterCountToFill,
+ OUT PLCN PrecedingLcn
+ );
+
+LONGLONG
+NtfsScanMcbForRealClusterCount (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn
+ );
+
+//
+// A local procedure prototype for masking out recently deallocated records
+//
+
+BOOLEAN
+NtfsAddDeallocatedRecords (
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN ULONG StartingIndexOfBitmap,
+ IN OUT PRTL_BITMAP Bitmap
+ );
+
+//
+// A local procedure prototype
+//
+
+BOOLEAN
+NtfsReduceMftZone (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+//
+// Local procedure prototype to check the stack usage in the record
+// package.
+//
+
+VOID
+NtfsCheckRecordStackUsage (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+//
+// Local procedure prototype to check for a continuos volume bitmap run
+//
+
+VOID
+NtfsRunIsClear (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG RunLength
+ );
+
+//
+// Local procedure prototype for dumping cached bitmap information
+//
+
+#ifdef NTFSDBG
+
+ULONG
+NtfsDumpCachedMcbInformation (
+ IN PVCB Vcb
+ );
+
+#else
+
+#define NtfsDumpCachedMcbInformation(V) (0)
+
+#endif // NTFSDBG
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAddBadCluster)
+#pragma alloc_text(PAGE, NtfsAddCachedRun)
+#pragma alloc_text(PAGE, NtfsAddDeallocatedRecords)
+#pragma alloc_text(PAGE, NtfsAddRecentlyDeallocated)
+#pragma alloc_text(PAGE, NtfsAllocateBitmapRun)
+#pragma alloc_text(PAGE, NtfsAllocateClusters)
+#pragma alloc_text(PAGE, NtfsAllocateMftReservedRecord)
+#pragma alloc_text(PAGE, NtfsAllocateRecord)
+#pragma alloc_text(PAGE, NtfsCheckRecordStackUsage)
+#pragma alloc_text(PAGE, NtfsCleanupClusterAllocationHints)
+#pragma alloc_text(PAGE, NtfsCreateMftHole)
+#pragma alloc_text(PAGE, NtfsDeallocateClusters)
+#pragma alloc_text(PAGE, NtfsDeallocateRecord)
+#pragma alloc_text(PAGE, NtfsDeallocateRecordsComplete)
+#pragma alloc_text(PAGE, NtfsFindFreeBitmapRun)
+#pragma alloc_text(PAGE, NtfsFindMftFreeTail)
+#pragma alloc_text(PAGE, NtfsFreeBitmapRun)
+#pragma alloc_text(PAGE, NtfsGetNextCachedFreeRun)
+#pragma alloc_text(PAGE, NtfsGetNextHoleToFill)
+#pragma alloc_text(PAGE, NtfsInitializeCachedBitmap)
+#pragma alloc_text(PAGE, NtfsInitializeClusterAllocation)
+#pragma alloc_text(PAGE, NtfsInitializeRecordAllocation)
+#pragma alloc_text(PAGE, NtfsIsLcnInCachedFreeRun)
+#pragma alloc_text(PAGE, NtfsIsRecordAllocated)
+#pragma alloc_text(PAGE, NtfsMapOrPinPageInBitmap)
+#pragma alloc_text(PAGE, NtfsReadAheadCachedBitmap)
+#pragma alloc_text(PAGE, NtfsReduceMftZone)
+#pragma alloc_text(PAGE, NtfsRemoveCachedRun)
+#pragma alloc_text(PAGE, NtfsReserveMftRecord)
+#pragma alloc_text(PAGE, NtfsRestartClearBitsInBitMap)
+#pragma alloc_text(PAGE, NtfsRestartSetBitsInBitMap)
+#pragma alloc_text(PAGE, NtfsScanEntireBitmap)
+#pragma alloc_text(PAGE, NtfsScanMcbForRealClusterCount)
+#pragma alloc_text(PAGE, NtfsScanMftBitmap)
+#pragma alloc_text(PAGE, NtfsUninitializeRecordAllocation)
+#pragma alloc_text(PAGE, RtlFindLastBackwardRunClear)
+#pragma alloc_text(PAGE, RtlFindNextForwardRunClear)
+#pragma alloc_text(PAGE, NtfsRunIsClear)
+#endif
+
+//
+// Temporary routines that need to be coded in Rtl\Bitmap.c
+//
+
+static ULONG FillMaskUlong[] = {
+ 0x00000000, 0x00000001, 0x00000003, 0x00000007,
+ 0x0000000f, 0x0000001f, 0x0000003f, 0x0000007f,
+ 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff,
+ 0x00000fff, 0x00001fff, 0x00003fff, 0x00007fff,
+ 0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff,
+ 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff,
+ 0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff,
+ 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff,
+ 0xffffffff
+};
+
+
+ULONG
+RtlFindNextForwardRunClear (
+ IN PRTL_BITMAP BitMapHeader,
+ IN ULONG FromIndex,
+ IN PULONG StartingRunIndex
+ )
+{
+ ULONG Start;
+ ULONG End;
+ PULONG PHunk, BitMapEnd;
+ ULONG Hunk;
+
+ PAGED_CODE();
+
+ //
+ // Take care of the boundary case of the null bitmap
+ //
+
+ if (BitMapHeader->SizeOfBitMap == 0) {
+
+ *StartingRunIndex = FromIndex;
+ return 0;
+ }
+
+ //
+ // Compute the last word address in the bitmap
+ //
+
+ BitMapEnd = BitMapHeader->Buffer + ((BitMapHeader->SizeOfBitMap - 1) / 32);
+
+ //
+ // Scan forward for the first clear bit
+ //
+
+ Start = FromIndex;
+
+ //
+ // Build pointer to the ULONG word in the bitmap
+ // containing the Start bit
+ //
+
+ PHunk = BitMapHeader->Buffer + (Start / 32);
+
+ //
+ // If the first subword is set then we can proceed to
+ // take big steps in the bitmap since we are now ULONG
+ // aligned in the search. Make sure we aren't improperly
+ // looking at the last word in the bitmap.
+ //
+
+ if (PHunk != BitMapEnd) {
+
+ //
+ // Read in the bitmap hunk. Set the previous bits in this word.
+ //
+
+ Hunk = *PHunk | FillMaskUlong[Start % 32];
+
+ if (Hunk == (ULONG)~0) {
+
+ //
+ // Adjust the pointers forward
+ //
+
+ Start += 32 - (Start % 32);
+ PHunk++;
+
+ while ( PHunk < BitMapEnd ) {
+
+ //
+ // Stop at first word with unset bits
+ //
+
+ if (*PHunk != (ULONG)~0) break;
+
+ PHunk++;
+ Start += 32;
+ }
+ }
+ }
+
+ //
+ // Bitwise search forward for the clear bit
+ //
+
+ while ((Start < BitMapHeader->SizeOfBitMap) && (RtlCheckBit( BitMapHeader, Start ) == 1)) { Start += 1; }
+
+ //
+ // Scan forward for the first set bit
+ //
+
+ End = Start;
+
+ //
+ // If we aren't in the last word of the bitmap we may be
+ // able to keep taking big steps
+ //
+
+ if (PHunk != BitMapEnd) {
+
+ //
+ // We know that the clear bit was in the last word we looked at,
+ // so continue from there to find the next set bit, clearing the
+ // previous bits in the word
+ //
+
+ Hunk = *PHunk & ~FillMaskUlong[End % 32];
+
+ if (Hunk == (ULONG)0) {
+
+ //
+ // Adjust the pointers forward
+ //
+
+ End += 32 - (End % 32);
+ PHunk++;
+
+ while ( PHunk < BitMapEnd ) {
+
+ //
+ // Stop at first word with set bits
+ //
+
+ if (*PHunk != (ULONG)0) break;
+
+ PHunk++;
+ End += 32;
+ }
+ }
+ }
+
+ //
+ // Bitwise search forward for the set bit
+ //
+
+ while ((End < BitMapHeader->SizeOfBitMap) && (RtlCheckBit( BitMapHeader, End ) == 0)) { End += 1; }
+
+ //
+ // Compute the index and return the length
+ //
+
+ *StartingRunIndex = Start;
+ return (End - Start);
+}
+
+
+ULONG
+RtlFindLastBackwardRunClear (
+ IN PRTL_BITMAP BitMapHeader,
+ IN ULONG FromIndex,
+ IN PULONG StartingRunIndex
+ )
+{
+ ULONG Start;
+ ULONG End;
+ PULONG PHunk;
+ ULONG Hunk;
+
+ PAGED_CODE();
+
+ //
+ // Take care of the boundary case of the null bitmap
+ //
+
+ if (BitMapHeader->SizeOfBitMap == 0) {
+
+ *StartingRunIndex = FromIndex;
+ return 0;
+ }
+
+ //
+ // Scan backwards for the first clear bit
+ //
+
+ End = FromIndex;
+
+ //
+ // Build pointer to the ULONG word in the bitmap
+ // containing the End bit, then read in the bitmap
+ // hunk. Set the rest of the bits in this word, NOT
+ // inclusive of the FromIndex bit.
+ //
+
+ PHunk = BitMapHeader->Buffer + (End / 32);
+ Hunk = *PHunk | ~FillMaskUlong[(End % 32) + 1];
+
+ //
+ // If the first subword is set then we can proceed to
+ // take big steps in the bitmap since we are now ULONG
+ // aligned in the search
+ //
+
+ if (Hunk == (ULONG)~0) {
+
+ //
+ // Adjust the pointers backwards
+ //
+
+ End -= (End % 32) + 1;
+ PHunk--;
+
+ while ( PHunk > BitMapHeader->Buffer ) {
+
+ //
+ // Stop at first word with set bits
+ //
+
+ if (*PHunk != (ULONG)~0) break;
+
+ PHunk--;
+ End -= 32;
+ }
+ }
+
+ //
+ // Bitwise search backward for the clear bit
+ //
+
+ while ((End != MAXULONG) && (RtlCheckBit( BitMapHeader, End ) == 1)) { End -= 1; }
+
+ //
+ // Scan backwards for the first set bit
+ //
+
+ Start = End;
+
+ //
+ // We know that the clear bit was in the last word we looked at,
+ // so continue from there to find the next set bit, clearing the
+ // previous bits in the word.
+ //
+
+ Hunk = *PHunk & FillMaskUlong[Start % 32];
+
+ //
+ // If the subword is unset then we can proceed in big steps
+ //
+
+ if (Hunk == (ULONG)0) {
+
+ //
+ // Adjust the pointers backward
+ //
+
+ Start -= (Start % 32) + 1;
+ PHunk--;
+
+ while ( PHunk > BitMapHeader->Buffer ) {
+
+ //
+ // Stop at first word with set bits
+ //
+
+ if (*PHunk != (ULONG)0) break;
+
+ PHunk--;
+ Start -= 32;
+ }
+ }
+
+ //
+ // Bitwise search backward for the set bit
+ //
+
+ while ((Start != MAXULONG) && (RtlCheckBit( BitMapHeader, Start ) == 0)) { Start -= 1; }
+
+ //
+ // Compute the index and return the length
+ //
+
+ *StartingRunIndex = Start + 1;
+ return (End - Start);
+}
+
+
+VOID
+NtfsInitializeClusterAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the cluster allocation structures within the
+ specified Vcb. It reads in as necessary the bitmap and scans it for
+ free space and builds the free space mcb based on this scan.
+
+ This procedure is multi-call save. That is, it can be used to
+ reinitialize the cluster allocation without first calling the
+ uninitialize cluster allocation routine.
+
+Arguments:
+
+ Vcb - Supplies the Vcb being initialized
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeClusterAllocation\n") );
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ try {
+
+ //
+ // Now initialize the recently deallocated cluster mcbs.
+ // We do this before the call to ScanEntireBitmap because
+ // that call uses the RecentlyDeallocatedMcbs to bias the
+ // bitmap.
+ //
+
+ FsRtlInitializeLargeMcb( &Vcb->DeallocatedClusters1.Mcb, PagedPool );
+ Vcb->PriorDeallocatedClusters = &Vcb->DeallocatedClusters1;
+
+ FsRtlInitializeLargeMcb( &Vcb->DeallocatedClusters2.Mcb, PagedPool );
+ Vcb->ActiveDeallocatedClusters = &Vcb->DeallocatedClusters2;
+
+ //
+ // The bitmap file currently doesn't have a paging IO resource.
+ // Create one here so that we won't serialize synchronization
+ // of the bitmap package with the lazy writer.
+ //
+
+ Vcb->BitmapScb->Header.PagingIoResource =
+ Vcb->BitmapScb->Fcb->PagingIoResource = NtfsAllocateEresource();
+
+ //
+ // Now call a bitmap routine to scan the entire bitmap. This
+ // routine will compute the number of free clusters in the
+ // bitmap and set the largest free runs that we find into the
+ // cached bitmap structures.
+ //
+
+ NtfsScanEntireBitmap( IrpContext, Vcb, FALSE );
+
+ //
+ // Our last operation is to set the hint lcn which is used by
+ // our allocation routine as a hint on where to find free space.
+ // In the running system it is the last lcn that we've allocated.
+ // But for startup we'll put it to be the first free run that
+ // is stored in the free space mcb.
+ //
+
+ {
+ LONGLONG ClusterCount;
+ NTFS_RUN_STATE RunState;
+
+ (VOID) NtfsGetNextCachedFreeRun( IrpContext,
+ Vcb,
+ 1,
+ &Vcb->LastBitmapHint,
+ &ClusterCount,
+ &RunState );
+ }
+
+ //
+ // Compute the mft zone. The mft zone is 1/8 of the disk starting
+ // at the beginning of the mft.
+ // Round the zone up and down to a ulong boundary to facilitate
+ // facilitate bitmap usage.
+ //
+
+ Vcb->MftZoneStart = Vcb->MftStartLcn & ~0x1f;
+ Vcb->MftZoneEnd = (Vcb->MftZoneStart + (Vcb->TotalClusters >> 3) + 0x1f) & ~0x1f;
+
+ } finally {
+
+ DebugUnwind( NtfsInitializeClusterAllocation );
+
+ NtfsReleaseScb(IrpContext, Vcb->BitmapScb);
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeClusterAllocation -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAllocateClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PSCB Scb,
+ IN VCN OriginalStartingVcn,
+ IN BOOLEAN AllocateAll,
+ IN LONGLONG ClusterCount,
+ IN OUT PLONGLONG DesiredClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates disk space. It fills in the unallocated holes in
+ input mcb with allocated clusters from starting Vcn to the cluster count.
+
+ The basic algorithm used by this procedure is as follows:
+
+ 1. Compute the EndingVcn from the StartingVcn and cluster count
+
+ 2. Compute the real number of clusters needed to allocate by scanning
+ the mcb from starting to ending vcn seeing where the real holes are
+
+ 3. If the real cluster count is greater than the known free cluster count
+ then the disk is full
+
+ 4. Call a routine that takes a starting Vcn, ending Vcn, and the Mcb and
+ returns the first hole that needs to be filled and while there is a hole
+ to be filled...
+
+ 5. Check if the run preceding the hole that we are trying to fill
+ has an ending Lcn and if it does then with that Lcn see if we
+ get a cache hit, if we do then allocate the cluster
+
+ 6. If we are still looking then enumerate through the cached free runs
+ and if we find a suitable one. Allocate the first suitable run we find that
+ satisfies our request. Also in the loop remember the largest
+ suitable run we find.
+
+ 8. If we are still looking then bite the bullet and scan the bitmap on
+ the disk for a free run using either the preceding Lcn as a hint if
+ available or the stored last bitmap hint in the Vcb.
+
+ 9. At this point we've located a run of clusters to allocate. To do the
+ actual allocation we allocate the space from the bitmap, decrement
+ the number of free clusters left, and update the hint.
+
+ 10. Before going back to step 4 we move the starting Vcn to be the point
+ one after the run we've just allocated.
+
+ 11. With the allocation complete we update the last bitmap hint stored in
+ the Vcb to be the last Lcn we've allocated, and we call a routine
+ to do the read ahead in the cached bitmap at the ending lcn.
+
+Arguments:
+
+ Vcb - Supplies the Vcb used in this operation
+
+ Scb - Supplies an Scb whose Mcb contains the current retrieval information
+ for the file and on exit will contain the updated retrieval
+ information
+
+ StartingVcn - Supplies a starting cluster for us to begin allocation
+
+ AllocateAll - If TRUE, allocate all the clusters here. Don't break
+ up request.
+
+ ClusterCount - Supplies the number of clusters to allocate
+
+ DesiredClusterCount - Supplies the number of clusters we would like allocated
+ and will allocate if it doesn't require additional runs. On return
+ this value is the number of clusters allocated.
+
+Return Value:
+
+ FALSE - if no clusters were allocated (they were already allocated)
+ TRUE - if clusters were allocated
+
+Important Note:
+
+ This routine will stop after allocating MAXIMUM_RUNS_AT_ONCE runs, in order
+ to limit the size of allocating transactions. The caller must be aware that
+ he may not get all of the space he asked for if the disk is real fragmented.
+
+--*/
+
+{
+ VCN StartingVcn = OriginalStartingVcn;
+ VCN EndingVcn;
+ VCN DesiredEndingVcn;
+
+ PNTFS_MCB Mcb = &Scb->Mcb;
+
+ LONGLONG RemainingDesiredClusterCount;
+
+ VCN VcnToFill;
+ LONGLONG ClusterCountToFill;
+ LCN PrecedingLcn;
+
+ BOOLEAN FoundClustersToAllocate;
+ LCN FoundLcn;
+ LONGLONG FoundClusterCount;
+
+ NTFS_RUN_STATE RunState;
+
+ ULONG RunIndex;
+
+ LCN HintLcn;
+
+ ULONG LoopCount = 0;
+
+ BOOLEAN ClustersAllocated = FALSE;
+ BOOLEAN GotAHoleToFill = TRUE;
+ BOOLEAN FoundRun = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateClusters\n") );
+ DebugTrace( 0, Dbg, ("StartVcn = %0I64x\n", StartingVcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %0I64x\n", ClusterCount) );
+ DebugTrace( 0, Dbg, ("DesiredClusterCount = %0I64x\n", *DesiredClusterCount) );
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ try {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS )) {
+
+ NtfsScanEntireBitmap( IrpContext, Vcb, TRUE );
+ }
+
+ //
+ // Check to see if we are defragmenting
+ //
+
+ if (Scb->Union.MoveData != NULL) {
+
+ //
+ // Check to make sure that the requested range does not conflict
+ // with the MFT zone.
+ //
+
+ if ((Scb->Union.MoveData->StartingLcn.QuadPart < Vcb->MftZoneEnd) &&
+ (Scb->Union.MoveData->StartingLcn.QuadPart + ClusterCount > Vcb->MftZoneStart)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ //
+ // Ensure that the run is NOT already allocated
+ //
+
+ NtfsRunIsClear(IrpContext, Vcb, Scb->Union.MoveData->StartingLcn.QuadPart, ClusterCount);
+
+ //
+ // Get the allocation data from the Scb
+ //
+
+ VcnToFill = OriginalStartingVcn;
+ FoundLcn = Scb->Union.MoveData->StartingLcn.QuadPart;
+ FoundClusterCount = ClusterCount;
+ *DesiredClusterCount = ClusterCount;
+
+ //
+ // Update the StartingLcn each time through the loop
+ //
+
+ Scb->Union.MoveData->StartingLcn.QuadPart = Scb->Union.MoveData->StartingLcn.QuadPart + ClusterCount;
+
+ GotAHoleToFill = FALSE;
+ ClustersAllocated = TRUE;
+ FoundRun = TRUE;
+
+ //
+ // We already have the allocation so skip over the allocation section
+ //
+
+ goto Defragment;
+ }
+
+ //
+ // Compute the ending vcn, and the cluster count of how much we really
+ // need to allocate (based on what is already allocated). Then check if we
+ // have space on the disk.
+ //
+
+ EndingVcn = (StartingVcn + ClusterCount) - 1;
+
+ ClusterCount = NtfsScanMcbForRealClusterCount( IrpContext, Mcb, StartingVcn, EndingVcn );
+
+ if ((ClusterCount + IrpContext->DeallocatedClusters) > Vcb->FreeClusters) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ //
+ // Let's see if it is ok to allocate clusters for this Scb now,
+ // in case compressed files have over-reserved the space. This
+ // calculation is done in such a way as to guarantee we do not
+ // have either of the terms subtracting through zero, even if
+ // we were to over-reserve the free space on the disk due to a
+ // hot fix or something. Always satisfy this request if we are
+ // in the paging IO path because we know we are using clusters
+ // already reserved for this stream.
+ //
+
+ NtfsAcquireReservedClusters( Vcb );
+ if ((Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
+ (IrpContext->OriginatingIrp != NULL) &&
+ !FlagOn( IrpContext->OriginatingIrp->Flags, IRP_PAGING_IO ) &&
+ (ClusterCount + Vcb->TotalReserved - Scb->ScbType.Data.TotalReserved) > Vcb->FreeClusters) {
+
+ NtfsReleaseReservedClusters( Vcb );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+ NtfsReleaseReservedClusters( Vcb );
+
+ //
+ // We need to check that the request won't fail because of clusters
+ // in the recently deallocated lists.
+ //
+
+ if (Vcb->FreeClusters < (Vcb->DeallocatedClusters + ClusterCount)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+ }
+
+ //
+ // Now compute the desired ending vcb and the real desired cluster count
+ //
+
+ DesiredEndingVcn = (StartingVcn + *DesiredClusterCount) - 1;
+ RemainingDesiredClusterCount = NtfsScanMcbForRealClusterCount( IrpContext, Mcb, StartingVcn, DesiredEndingVcn );
+
+ //
+ // While there are holes to fill we will do the following loop
+ //
+
+ while ((AllocateAll || (LoopCount < MAXIMUM_RUNS_AT_ONCE))
+
+ &&
+
+ (GotAHoleToFill = NtfsGetNextHoleToFill( IrpContext,
+ Mcb,
+ StartingVcn,
+ DesiredEndingVcn,
+ &VcnToFill,
+ &ClusterCountToFill,
+ &PrecedingLcn))) {
+
+ //
+ // Remember that we are will be allocating clusters.
+ //
+
+ ClustersAllocated = TRUE;
+
+ //
+ // First indicate that we haven't found anything suitable yet
+ //
+
+ FoundClustersToAllocate = FALSE;
+
+ //
+ // Check if the preceding lcn is anything other than -1 then with
+ // that as a hint check if we have a cache hit on a free run
+ //
+
+ if (PrecedingLcn != UNUSED_LCN) {
+
+
+ if (NtfsIsLcnInCachedFreeRun( IrpContext,
+ Vcb,
+ PrecedingLcn + 1,
+ &FoundLcn,
+ &FoundClusterCount )) {
+
+ FoundClustersToAllocate = TRUE;
+ }
+
+ //
+ // The following chunks of code will only try and find a fit in the cached
+ // free run information only for non-mft allocation without a hint. If we didn't get
+ // cache hit earlier for the mft then we will bite the bullet and hit the disk
+ // really trying to keep the mft contiguous.
+ //
+
+ //if ((Mcb != &Vcb->MftScb->Mcb) && XxEql(PrecedingLcn, UNUSED_LCN))
+ } else {
+
+ LCN LargestSuitableLcn;
+ LONGLONG LargestSuitableClusterCount;
+
+ LargestSuitableClusterCount = 0;
+
+ //
+ // If we are still looking then scan through all of the cached free runs
+ // and either take the first suitable one we find. We also will not
+ // consider allocating anything in the Mft Zone.
+ //
+
+ for (RunIndex = 0;
+
+ !FoundClustersToAllocate && NtfsGetNextCachedFreeRun( IrpContext,
+ Vcb,
+ RunIndex,
+ &FoundLcn,
+ &FoundClusterCount,
+ &RunState );
+
+ RunIndex += 1) {
+
+ if (RunState == RunStateFree) {
+
+ //
+ // At this point the run is free but now we need to check if it
+ // exists in the mft zone. If it does then bias the found run
+ // to go outside of the mft zone
+ //
+
+ if ((FoundLcn >= Vcb->MftZoneStart) &&
+ (FoundLcn < Vcb->MftZoneEnd)) {
+
+ FoundClusterCount = FoundClusterCount - (Vcb->MftZoneEnd - FoundLcn);
+ FoundLcn = Vcb->MftZoneEnd;
+ }
+
+ //
+ // Now if the preceding run state is unknown and because of the bias we still
+ // have a free run then check if the size of the find is large enough for the
+ // remaning desired cluster count, and if so then we have a one to use
+ // otherwise keep track of the largest suitable run found.
+ //
+
+ if (FoundClusterCount > RemainingDesiredClusterCount) {
+
+ FoundClustersToAllocate = TRUE;
+
+ } else if (FoundClusterCount > LargestSuitableClusterCount) {
+
+ LargestSuitableLcn = FoundLcn;
+ LargestSuitableClusterCount = FoundClusterCount;
+ }
+ }
+ }
+
+ //
+ // Now check if we still haven't found anything to allocate but we use the
+ // largest suitable run that wasn't quite big enough for the remaining
+ // desired cluter count
+ //
+
+ if (!FoundClustersToAllocate) {
+
+ if (LargestSuitableClusterCount > 0) {
+
+ FoundClustersToAllocate = TRUE;
+
+ FoundLcn = LargestSuitableLcn;
+ FoundClusterCount = LargestSuitableClusterCount;
+ }
+ }
+ }
+
+ //
+ // We've done everything we can with the cached bitmap information so
+ // now bite the bullet and scan the bitmap for a free cluster. If
+ // we have an hint lcn then use it otherwise use the hint stored in the
+ // vcb. But never use a hint that is part of the mft zone, and because
+ // the mft always has a preceding lcn we know we'll hint in the zone
+ // for the mft.
+ //
+
+ if (!FoundClustersToAllocate) {
+
+ BOOLEAN AllocatedFromZone;
+
+ //
+ // First check if we have already satisfied the core requirements
+ // and are now just going for the desired ending vcn. If so then
+ // we will not was time hitting the disk
+ //
+
+ if (StartingVcn > EndingVcn) {
+
+ break;
+ }
+
+ if (PrecedingLcn != UNUSED_LCN) {
+
+ HintLcn = PrecedingLcn;
+
+ } else {
+
+ HintLcn = Vcb->LastBitmapHint;
+
+ if ((HintLcn >= Vcb->MftZoneStart) &&
+ (HintLcn < Vcb->MftZoneEnd)) {
+
+ HintLcn = Vcb->MftZoneEnd;
+ }
+ }
+
+ AllocatedFromZone = NtfsFindFreeBitmapRun( IrpContext,
+ Vcb,
+ ClusterCountToFill,
+ HintLcn,
+ &FoundLcn,
+ &FoundClusterCount );
+
+ if (FoundClusterCount == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ //
+ // Check if we need to reduce the zone.
+ //
+
+ if (AllocatedFromZone &&
+ (Scb != Vcb->MftScb)) {
+
+ //
+ // If there is space to reduce the zone then do so now
+ // and rescan the bitmap.
+ //
+
+ if (NtfsReduceMftZone( IrpContext, Vcb )) {
+
+ FoundClusterCount = 0;
+
+ NtfsFindFreeBitmapRun( IrpContext,
+ Vcb,
+ ClusterCountToFill,
+ Vcb->MftZoneEnd,
+ &FoundLcn,
+ &FoundClusterCount );
+
+ if (FoundClusterCount == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+ }
+ }
+ }
+
+ //
+ // At this point we have found a run to allocate denoted by the
+ // values in FoundLcn and FoundClusterCount. We need to trim back
+ // the cluster count to be the amount we really need and then
+ // do the allocation. To do the allocation we zap the bitmap,
+ // decrement the free count, and add the run to the mcb we're
+ // using
+ //
+
+ if (FoundClusterCount > RemainingDesiredClusterCount) {
+
+ FoundClusterCount = RemainingDesiredClusterCount;
+ }
+
+ if (FoundClusterCount > ClusterCountToFill) {
+
+ FoundClusterCount = ClusterCountToFill;
+ }
+
+#ifdef NTFS_FRAGMENT_DISK
+ if (NtfsFragmentDisk && ((ULONG) FoundClusterCount > NtfsFragmentLength)) {
+
+ FoundLcn += 1;
+ FoundClusterCount = NtfsFragmentLength;
+ }
+#endif
+ ASSERT(Vcb->FreeClusters >= FoundClusterCount);
+
+ //
+ // Always remove the cached run information before logging the change.
+ // Otherwise we could log a partial change but get a log file full
+ // before removing the run from the free space Mcb.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MODIFIED_BITMAP );
+
+Defragment:
+
+ NtfsAddCachedRun( IrpContext, Vcb, FoundLcn, FoundClusterCount, RunStateAllocated ); // CHECK for span pages
+
+ NtfsAllocateBitmapRun( IrpContext, Vcb, FoundLcn, FoundClusterCount );
+
+ //
+ // Modify the total allocated for this file.
+ //
+
+ NtfsAcquireReservedClusters( Vcb );
+ Scb->TotalAllocated += (LlBytesFromClusters( Vcb, FoundClusterCount ));
+ NtfsReleaseReservedClusters( Vcb );
+
+ //
+ // Adjust the count of free clusters. Only store the change in
+ // the top level irp context in case of aborts.
+ //
+
+ Vcb->FreeClusters -= FoundClusterCount;
+
+ IrpContext->FreeClusterChange -= FoundClusterCount;
+
+ ASSERT_LCN_RANGE_CHECKING( Vcb, (FoundLcn + FoundClusterCount) );
+
+ ASSERT(FoundClusterCount != 0);
+
+ NtfsAddNtfsMcbEntry( Mcb, VcnToFill, FoundLcn, FoundClusterCount, FALSE );
+
+ //
+ // If this is the Mft file then put these into our AddedClusters Mcb
+ // as well.
+ //
+
+ if (Mcb == &Vcb->MftScb->Mcb) {
+
+ FsRtlAddLargeMcbEntry( &Vcb->MftScb->ScbType.Mft.AddedClusters,
+ VcnToFill,
+ FoundLcn,
+ FoundClusterCount );
+ }
+
+ //
+ // And update the last bitmap hint, but only if we used the hint to begin with
+ //
+
+ if (PrecedingLcn == UNUSED_LCN) {
+
+ Vcb->LastBitmapHint = FoundLcn;
+ }
+
+ //
+ // Now move the starting Vcn to the Vcn that we've just filled plus the
+ // found cluster count
+ //
+
+ StartingVcn = VcnToFill + FoundClusterCount;
+
+ //
+ // Decrement the remaining desired cluster count by the amount we just allocated
+ //
+
+ RemainingDesiredClusterCount = RemainingDesiredClusterCount - FoundClusterCount;
+
+ LoopCount += 1;
+
+ if(FoundRun == TRUE) {
+
+ break;
+ }
+ }
+
+ //
+ // Now we need to compute the total cluster that we've just allocated
+ // We'll call get next hole to fill. If the result is false then we
+ // allocated everything. If the result is true then we do some quick
+ // math to get the size allocated
+ //
+
+ if (GotAHoleToFill && NtfsGetNextHoleToFill( IrpContext,
+ Mcb,
+ OriginalStartingVcn,
+ DesiredEndingVcn,
+ &VcnToFill,
+ &ClusterCountToFill,
+ &PrecedingLcn)) {
+
+ *DesiredClusterCount = VcnToFill - OriginalStartingVcn;
+ }
+
+ //
+ // At this point we've allocated everything we were asked to do
+ // so now call a routine to read ahead into our cache the disk
+ // information at the last lcn we allocated. But only do the readahead
+ // if we allocated clusters
+ //
+
+ if (ClustersAllocated && ((FoundLcn + FoundClusterCount) < Vcb->TotalClusters)) {
+
+ NtfsReadAheadCachedBitmap( IrpContext, Vcb, FoundLcn + FoundClusterCount );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsAllocateClusters );
+
+ DebugTrace( 0, Dbg, ("%d\n", NtfsDumpCachedMcbInformation(Vcb)) );
+
+ NtfsReleaseScb(IrpContext, Vcb->BitmapScb);
+ }
+
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateClusters -> %08lx\n", ClustersAllocated) );
+
+ return ClustersAllocated;
+}
+
+
+VOID
+NtfsAddBadCluster (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine helps append a bad cluster to the bad cluster file.
+ It marks it as allocated in the volume bitmap and also adds
+ the Lcn to the MCB for the bad cluster file.
+
+Arguments:
+
+ Vcb - Supplies the Vcb used in this operation
+
+ Lcn - Supplies the Lcn of the new bad cluster
+
+Return:
+
+ None.
+
+--*/
+
+{
+ PNTFS_MCB Mcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddBadCluster\n") );
+ DebugTrace( 0, Dbg, ("Lcn = %0I64x\n", Lcn) );
+
+ //
+ // Reference the bad cluster mcb and grab exclusive access to the
+ // bitmap scb
+ //
+
+ Mcb = &Vcb->BadClusterFileScb->Mcb;
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ try {
+
+ //
+ // We are given the bad Lcn so all we need to do is
+ // allocate it in the bitmap, and take care of some
+ // bookkeeping
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MODIFIED_BITMAP );
+
+ NtfsAddCachedRun( IrpContext, Vcb, Lcn, 1, RunStateAllocated );
+
+ NtfsAllocateBitmapRun( IrpContext, Vcb, Lcn, 1 );
+
+ Vcb->FreeClusters -= 1;
+ IrpContext->FreeClusterChange -= 1;
+
+ ASSERT_LCN_RANGE_CHECKING( Vcb, (Lcn + 1) );
+
+ //
+ // Vcn == Lcn in the bad cluster file.
+ //
+
+ NtfsAddNtfsMcbEntry( Mcb, Lcn, Lcn, (LONGLONG)1, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsAddBadCluster );
+
+ NtfsReleaseScb(IrpContext, Vcb->BitmapScb);
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddBadCluster -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsDeallocateClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ OUT PLONGLONG TotalAllocated OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocates (i.e., frees) disk space. It free any clusters that
+ are specified as allocated in the input mcb with the specified range of starting
+ vcn to ending vcn inclusive.
+
+ The basic algorithm used by this procedure is as follows:
+
+ 1. With a Vcn value beginning at starting vcn and progressing to ending vcn
+ do the following steps...
+
+ 2. Lookup the Mcb entry at the vcn this will yield an lcn and a cluster count
+ if the entry exists (even if it is a hole). If the entry does not exist
+ then we are completely done because we have run off the end of allocation.
+
+ 3. If the entry is a hole (i.e., Lcn == -1) then add the cluster count to
+ Vcn and go back to step 1.
+
+ 4. At this point we have a real run of clusters that need to be deallocated but
+ the cluster count might put us over the ending vcn so adjust the cluster
+ count to keep us within the ending vcn.
+
+ 5. Now deallocate the clusters from the bitmap, and increment the free cluster
+ count stored in the vcb.
+
+ 6. Add (i.e., change) any cached bitmap information concerning this run to indicate
+ that it is now free.
+
+ 7. Remove the run from the mcb.
+
+ 8. Add the cluster count that we've just freed to Vcn and go back to step 1.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ Mcb - Supplies the mcb describing the runs to be deallocated
+
+ StartingVcn - Supplies the vcn to start deallocating at in the input mcb
+
+ EndingVcn - Supplies the vcn to end deallocating at in the input mcb
+
+ TotalAllocated - If specified we will modifify the total allocated clusters
+ for this file.
+
+Return Value:
+
+ FALSE - if nothing was deallocated.
+ TRUE - if some space was deallocated.
+
+--*/
+
+{
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG ClusterCount;
+ BOOLEAN ClustersDeallocated = FALSE;
+ BOOLEAN RaiseLogFull;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeallocateClusters\n") );
+ DebugTrace( 0, Dbg, ("StartingVcn = %016I64x\n", StartingVcn) );
+ DebugTrace( 0, Dbg, ("EndingVcn = %016I64\n", EndingVcn) );
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ try {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS )) {
+
+ NtfsScanEntireBitmap( IrpContext, Vcb, TRUE );
+ }
+
+ //
+ // The following loop scans through the mcb from starting vcn to ending vcn
+ // with a step of cluster count.
+ //
+
+ for (Vcn = StartingVcn; Vcn <= EndingVcn; Vcn = Vcn + ClusterCount) {
+
+ //
+ // Get the run information from the Mcb, and if this Vcn isn't specified
+ // in the mcb then return now to our caller
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( Mcb, Vcn, &Lcn, &ClusterCount, NULL, NULL, NULL, NULL )) {
+
+ try_return( NOTHING );
+ }
+
+ ASSERT_LCN_RANGE_CHECKING( Vcb, (Lcn + ClusterCount) );
+
+ //
+ // Make sure that the run we just looked up is not a hole otherwise
+ // if it is a hole we'll just continue with out loop continue with our
+ // loop
+ //
+
+ if (Lcn != UNUSED_LCN) {
+
+ //
+ // Now we have a real run to deallocate, but it might be too large
+ // to check for that the vcn plus cluster count must be less than
+ // or equal to the ending vcn plus 1.
+ //
+
+ if ((Vcn + ClusterCount) > EndingVcn) {
+
+ ClusterCount = (EndingVcn - Vcn) + 1;
+ }
+
+ //
+ // And to hold us off from reallocating the clusters right away we'll
+ // add this run to the recently deallocated mcb in the vcb. If this fails
+ // because we are growing the mapping then change the code to
+ // LOG_FILE_FULL to empty the mcb.
+ //
+
+ RaiseLogFull = FALSE;
+
+ try {
+
+ FsRtlAddLargeMcbEntry( &Vcb->ActiveDeallocatedClusters->Mcb,
+ Lcn,
+ Lcn,
+ ClusterCount );
+
+ } except (((GetExceptionCode() == STATUS_INSUFFICIENT_RESOURCES) &&
+ (IrpContext != NULL) &&
+ (IrpContext->MajorFunction == IRP_MJ_CLEANUP)) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH) {
+
+ RaiseLogFull = TRUE;
+ }
+
+ if (RaiseLogFull) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+ }
+
+ Vcb->ActiveDeallocatedClusters->ClusterCount += ClusterCount;
+
+ Vcb->DeallocatedClusters += ClusterCount;
+ IrpContext->DeallocatedClusters += ClusterCount;
+
+ //
+ // Now zap the bitmap, increment the free cluster count, and change
+ // the cached information on this run to indicate that it is now free
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_MODIFIED_BITMAP );
+
+ NtfsFreeBitmapRun( IrpContext, Vcb, Lcn, ClusterCount);
+ ClustersDeallocated = TRUE;
+
+ //
+ // Adjust the count of free clusters and adjust the IrpContext
+ // field for the change this transaction.
+ //
+
+ Vcb->FreeClusters += ClusterCount;
+
+ //
+ // If we had shrunk the Mft zone and there is at least 1/8
+ // of the volume now available, then grow the zone back.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_REDUCED_MFT ) &&
+ (Int64ShraMod32( Vcb->TotalClusters, 3 ) < Vcb->FreeClusters)) {
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_REDUCED_MFT );
+ Vcb->MftZoneEnd = (Vcb->MftZoneStart + (Vcb->TotalClusters >> 3) + 0x1f) & ~0x1f;
+ }
+
+ IrpContext->FreeClusterChange += ClusterCount;
+
+ //
+ // Modify the total allocated amount if the pointer is specified.
+ //
+
+ if (ARGUMENT_PRESENT( TotalAllocated )) {
+
+ NtfsAcquireReservedClusters( Vcb );
+ *TotalAllocated -= (LlBytesFromClusters( Vcb, ClusterCount ));
+
+ if (*TotalAllocated < 0) {
+
+ *TotalAllocated = 0;
+ }
+ NtfsReleaseReservedClusters( Vcb );
+ }
+
+ //
+ // Now remove this entry from the mcb and go back to the top of the
+ // loop
+ //
+
+ NtfsRemoveNtfsMcbEntry( Mcb, Vcn, ClusterCount );
+
+ //
+ // If this is the Mcb for the Mft file then remember this in the
+ // RemovedClusters Mcb.
+ //
+
+ if (Mcb == &Vcb->MftScb->Mcb) {
+
+ FsRtlAddLargeMcbEntry( &Vcb->MftScb->ScbType.Mft.RemovedClusters,
+ Vcn,
+ Lcn,
+ ClusterCount );
+ }
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsDeallocateClusters );
+
+ DebugTrace( 0, Dbg, ("%d\n", NtfsDumpCachedMcbInformation(Vcb)) );
+
+ NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeallocateClusters -> %02lx\n", ClustersDeallocated) );
+
+ return ClustersDeallocated;
+}
+
+
+VOID
+NtfsScanEntireBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Rescan
+ )
+
+/*++
+
+Routine Description:
+
+ This routine scans in the entire bitmap, It computes the number of free clusters
+ available, and at the same time remembers the largest free runs that it
+ then inserts into the cached bitmap structure.
+
+Arguments:
+
+ Vcb - Supplies the vcb used by this operation
+
+ Rescan - Indicates that we have already scanned the volume bitmap.
+ All we want from this call is to reinitialize the bitmap structures.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ BOOLEAN IsPreviousClusterFree;
+
+ LCN Lcn;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb;
+
+ BOOLEAN StuffAdded = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsScanEntireBitmap\n") );
+
+ BitmapBcb = NULL;
+
+ try {
+
+ if (Rescan) {
+
+ //
+ // Reinitialize the free space information.
+ //
+
+ FsRtlTruncateLargeMcb( &Vcb->FreeSpaceMcb, (LONGLONG) 0 );
+
+ } else {
+
+ //
+ // Now initialize the cached bitmap structures. This will setup the
+ // free space mcb/lru fields.
+ //
+
+ FsRtlUninitializeLargeMcb( &Vcb->FreeSpaceMcb );
+ RtlZeroMemory( &Vcb->FreeSpaceMcb, sizeof(LARGE_MCB) );
+
+ NtfsInitializeCachedBitmap( IrpContext, Vcb );
+ }
+
+ //
+ // Set the current total free space to zero and the following loop will compute
+ // the actual number of free clusters.
+ //
+
+ Vcb->FreeClusters = 0;
+
+ //
+ // For every bitmap page we read it in and check how many free clusters there are.
+ // While we have the page in memory we also scan for a large free space.
+ //
+
+ IsPreviousClusterFree = FALSE;
+
+ for (Lcn = 0; Lcn < Vcb->TotalClusters; Lcn = Lcn + Bitmap.SizeOfBitMap) {
+
+ ULONG LongestRun;
+ ULONG LongestRunSize;
+ LCN StartingLcn;
+
+ //
+ // Read in the bitmap page and make sure that we haven't messed up the math
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &StartingLcn, &Bitmap, &BitmapBcb );
+ ASSERTMSG("Math wrong for bits per page of bitmap", (Lcn == StartingLcn));
+
+ //
+ // Compute the number of clear bits in the bitmap each clear bit denotes
+ // a free cluster.
+ //
+
+ Vcb->FreeClusters += RtlNumberOfClearBits( &Bitmap );
+
+ //
+ // Now bias the bitmap with the RecentlyDeallocatedMcb.
+ //
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, StartingLcn, &Bitmap );
+
+ //
+ // Find the longest free run in the bitmap and add it to the cached bitmap.
+ // But before we add it check that there is a run of free clusters.
+ //
+
+ LongestRunSize = RtlFindLongestRunClear( &Bitmap, &LongestRun );
+
+ if (LongestRunSize > 0) {
+
+ NtfsAddCachedRun( IrpContext,
+ Vcb,
+ Lcn + LongestRun,
+ (LONGLONG)LongestRunSize,
+ RunStateFree );
+ }
+
+ //
+ // Now if the previous bitmap ended in a free cluster then we need to
+ // find if we start with free clusters and add those to the cached bitmap.
+ // But we only need to do this if the largest free run already didn't start
+ // at zero and if the first bit is clear.
+ //
+
+ if (IsPreviousClusterFree && (LongestRun != 0) && (RtlCheckBit(&Bitmap, 0) == 0)) {
+
+ ULONG Run;
+ ULONG Size;
+
+ Size = RtlFindNextForwardRunClear( &Bitmap, 0, &Run );
+
+ ASSERTMSG("First bit must be clear ", Run == 0);
+
+ NtfsAddCachedRun( IrpContext, Vcb, Lcn, (LONGLONG)Size, RunStateFree );
+ }
+
+ //
+ // If the largest run includes the last bit in the bitmap then we
+ // need to indicate that the last clusters is free
+ //
+
+ if ((LongestRun + LongestRunSize) == Bitmap.SizeOfBitMap) {
+
+ IsPreviousClusterFree = TRUE;
+
+ } else {
+
+ //
+ // Now the largest free run did not include the last cluster in the bitmap,
+ // So scan backwards in the bitmap until we hit a cluster that is not free. and
+ // then add the free space to the cached mcb. and indicate that the
+ // last cluster in the bitmap is free.
+ //
+
+ if (RtlCheckBit(&Bitmap, Bitmap.SizeOfBitMap - 1) == 0) {
+
+ ULONG Run;
+ ULONG Size;
+
+ Size = RtlFindLastBackwardRunClear( &Bitmap, Bitmap.SizeOfBitMap - 1, &Run );
+
+ NtfsAddCachedRun( IrpContext, Vcb, Lcn + Run, (LONGLONG)Size, RunStateFree );
+
+ IsPreviousClusterFree = TRUE;
+
+ } else {
+
+ IsPreviousClusterFree = FALSE;
+ }
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsScanEntireBitmap );
+
+ if (!AbnormalTermination()) {
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS );
+ }
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsScanEntireBitmap -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsCreateMftHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to create a hole within the Mft.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ BOOLEAN FoundHole = FALSE;
+ PBCB BitmapBcb = NULL;
+ BOOLEAN StuffAdded = FALSE;
+ RTL_BITMAP Bitmap;
+ PUCHAR BitmapBuffer;
+ ULONG SizeToMap;
+
+ ULONG BitmapOffset;
+ ULONG BitmapSize;
+ ULONG BitmapIndex;
+
+ ULONG StartIndex;
+ ULONG HoleCount;
+
+ VCN ThisVcn;
+ ULONG MftVcn;
+ ULONG MftClusterCount;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Compute the number of records in the Mft file and the full range to
+ // pin in the Mft bitmap.
+ //
+
+ BitmapIndex = (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart );
+
+ //
+ // Knock this index down to a hole boundary.
+ //
+
+ BitmapIndex &= Vcb->MftHoleInverseMask;
+
+ //
+ // Compute the values for the bitmap.
+ //
+
+ BitmapSize = (BitmapIndex + 7) / 8;
+
+ //
+ // Convert the index to the number of bits on this page.
+ //
+
+ BitmapIndex &= (BITS_PER_PAGE - 1);
+
+ if (BitmapIndex == 0) {
+
+ BitmapIndex = BITS_PER_PAGE;
+ }
+
+ //
+ // Set the Vcn count to the full size of the bitmap.
+ //
+
+ BitmapOffset = ROUND_TO_PAGES( BitmapSize );
+
+ //
+ // Loop through all of the pages of the Mft bitmap looking for an appropriate
+ // hole.
+ //
+
+ while (BitmapOffset != 0) {
+
+ //
+ // Move to the beginning of this page.
+ //
+
+ BitmapOffset -= BITS_PER_PAGE;
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ //
+ // Compute the number of bytes to map in the current page.
+ //
+
+ SizeToMap = BitmapSize - BitmapOffset;
+
+ if (SizeToMap > PAGE_SIZE) {
+
+ SizeToMap = PAGE_SIZE;
+ }
+
+ //
+ // Unmap any pages from a previous page and map the current page.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Initialize the bitmap for this page.
+ //
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftBitmapScb,
+ BitmapOffset,
+ SizeToMap,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap, (PULONG) BitmapBuffer, SizeToMap * 8 );
+
+ StuffAdded = NtfsAddDeallocatedRecords( Vcb,
+ Vcb->MftScb,
+ BitmapOffset * 8,
+ &Bitmap );
+
+ //
+ // Walk through the current page looking for a hole. Continue
+ // until we find a hole or have reached the beginning of the page.
+ //
+
+ do {
+
+ //
+ // Go back one Mft index and look for a clear run.
+ //
+
+ BitmapIndex -= 1;
+
+ HoleCount = RtlFindLastBackwardRunClear( &Bitmap,
+ BitmapIndex,
+ &BitmapIndex );
+
+ //
+ // If we couldn't find any run then break out of the loop.
+ //
+
+ if (HoleCount == 0) {
+
+ break;
+
+ //
+ // If this is too small to make a hole then continue on.
+ //
+
+ } else if (HoleCount < Vcb->MftHoleGranularity) {
+
+ BitmapIndex &= Vcb->MftHoleInverseMask;
+ continue;
+ }
+
+ //
+ // Round up the starting index for this clear run and
+ // adjust the hole count.
+ //
+
+ StartIndex = (BitmapIndex + Vcb->MftHoleMask) & Vcb->MftHoleInverseMask;
+ HoleCount -= (StartIndex - BitmapIndex);
+
+ //
+ // Round the hole count down to a hole boundary.
+ //
+
+ HoleCount &= Vcb->MftHoleInverseMask;
+
+ //
+ // If we couldn't find enough records for a hole then
+ // go to a previous index.
+ //
+
+ if (HoleCount < Vcb->MftHoleGranularity) {
+
+ BitmapIndex &= Vcb->MftHoleInverseMask;
+ continue;
+ }
+
+ //
+ // Convert the hole count to a cluster count.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ HoleCount <<= Vcb->MftToClusterShift;
+
+ } else {
+
+ HoleCount = 1;
+ }
+
+ //
+ // Loop by finding the run at the given Vcn and walk through
+ // subsequent runs looking for a hole.
+ //
+
+ do {
+
+ PVOID RangePtr;
+ ULONG McbIndex;
+ VCN ThisVcn;
+ LCN ThisLcn;
+ LONGLONG ThisClusterCount;
+
+ //
+ // Find the starting Vcn for this hole and initialize
+ // the cluster count for the current hole.
+ //
+
+ ThisVcn = StartIndex + (BitmapOffset * 3);
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ ThisVcn <<= Vcb->MftToClusterShift;
+
+ } else {
+
+ ThisVcn >>= Vcb->MftToClusterShift;
+ }
+
+ MftVcn = (ULONG) ThisVcn;
+ MftClusterCount = 0;
+
+ //
+ // Lookup the run at the current Vcn.
+ //
+
+ NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
+ ThisVcn,
+ &ThisLcn,
+ &ThisClusterCount,
+ NULL,
+ NULL,
+ &RangePtr,
+ &McbIndex );
+
+ //
+ // Now walk through this bitmap run and look for a run we
+ // can deallocate to create a hole.
+ //
+
+ do {
+
+ //
+ // Go to the next run in the Mcb.
+ //
+
+ McbIndex += 1;
+
+ //
+ // If this run extends beyond the end of the of the
+ // hole then truncate the clusters in this run.
+ //
+
+ if (ThisClusterCount > HoleCount) {
+
+ ThisClusterCount = HoleCount;
+ HoleCount = 0;
+
+ } else {
+
+ HoleCount -= (ULONG) ThisClusterCount;
+ }
+
+ //
+ // Check if this run is a hole then clear the count
+ // of clusters.
+ //
+
+ if (ThisLcn == UNUSED_LCN) {
+
+ //
+ // We want to skip this hole. If we have found a
+ // hole then we are done. Otherwise we want to
+ // find the next range in the Mft starting at the point beyond
+ // the current run (which is a hole). Nothing to do if we don't
+ // have enough clusters for a full hole.
+ //
+
+ if (!FoundHole &&
+ (HoleCount >= Vcb->MftClustersPerHole)) {
+
+ //
+ // Find the Vcn after the current Mft run.
+ //
+
+ ThisVcn += ThisClusterCount;
+
+ //
+ // If this isn't on a hole boundary then
+ // round up to a hole boundary. Adjust the
+ // available clusters for a hole.
+ //
+
+ MftVcn = (ULONG) (ThisVcn + Vcb->MftHoleClusterMask);
+ MftVcn = (ULONG) ThisVcn & Vcb->MftHoleClusterInverseMask;
+
+ //
+ // Now subtract this from the HoleClusterCount.
+ //
+
+ HoleCount -= MftVcn - (ULONG) ThisVcn;
+
+ //
+ // We need to convert the Vcn at this point to an Mft record
+ // number.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ StartIndex = MftVcn >> Vcb->MftToClusterShift;
+
+ } else {
+
+ StartIndex = MftVcn << Vcb->MftToClusterShift;
+ }
+ }
+
+ break;
+
+ //
+ // We found a run to deallocate.
+ //
+
+ } else {
+
+ //
+ // Add these clusters to the clusters already found.
+ // Set the flag indicating we found a hole if there
+ // are enough clusters to create a hole.
+ //
+
+ MftClusterCount += (ULONG) ThisClusterCount;
+
+ if (MftClusterCount >= Vcb->MftClustersPerHole) {
+
+ FoundHole = TRUE;
+ }
+ }
+
+ } while ((HoleCount != 0) &&
+ NtfsGetSequentialMcbEntry( &Vcb->MftScb->Mcb,
+ &RangePtr,
+ McbIndex,
+ &ThisVcn,
+ &ThisLcn,
+ &ThisClusterCount ));
+
+ } while (!FoundHole && (HoleCount >= Vcb->MftClustersPerHole));
+
+ //
+ // Round down to a hole boundary for the next search for
+ // a hole candidate.
+ //
+
+ BitmapIndex &= Vcb->MftHoleInverseMask;
+
+ } while (!FoundHole && (BitmapIndex >= Vcb->MftHoleGranularity));
+
+ //
+ // If we found a hole then deallocate the clusters and record
+ // the hole count change.
+ //
+
+ if (FoundHole) {
+
+ IO_STATUS_BLOCK IoStatus;
+ LONGLONG MftFileOffset;
+
+ //
+ // We want to flush the data in the Mft out to disk in
+ // case a lazywrite comes in during a window where we have
+ // removed the allocation but before a possible abort.
+ //
+
+ MftFileOffset = LlBytesFromClusters( Vcb, MftVcn );
+
+ //
+ // Round the cluster count and hole count down to a hole boundary.
+ //
+
+
+ MftClusterCount &= Vcb->MftHoleClusterInverseMask;
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ HoleCount = MftClusterCount >> Vcb->MftToClusterShift;
+
+ } else {
+
+ HoleCount = MftClusterCount << Vcb->MftToClusterShift;
+ }
+
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER) &MftFileOffset,
+ BytesFromClusters( Vcb, MftClusterCount ),
+ &IoStatus );
+
+ ASSERT( IoStatus.Status == STATUS_SUCCESS );
+
+ //
+ // Remove the clusters from the Mcb for the Mft.
+ //
+
+ NtfsDeleteAllocation( IrpContext,
+ Vcb->MftScb->FileObject,
+ Vcb->MftScb,
+ MftVcn,
+ (LONGLONG) MftVcn + (MftClusterCount - 1),
+ TRUE,
+ FALSE );
+
+ //
+ // Record the change to the hole count.
+ //
+
+ Vcb->MftHoleRecords += HoleCount;
+ Vcb->MftScb->ScbType.Mft.HoleRecordChange += HoleCount;
+
+ //
+ // Exit the loop.
+ //
+
+ break;
+ }
+
+ //
+ // Look at all of the bits on the previous page.
+ //
+
+ BitmapIndex = BITS_PER_PAGE;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateMftHole );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ return FoundHole;
+}
+
+
+BOOLEAN
+NtfsFindMftFreeTail (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PLONGLONG FileOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to find the file offset where the run of free records at
+ the end of the Mft file begins. If we can't find a minimal run of file records
+ we won't perform truncation.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume being defragged.
+
+ FileOffset - This is the offset where the truncation may begin.
+
+Return Value:
+
+ BOOLEAN - TRUE if there is an acceptable candidate for truncation at the end of
+ the file FALSE otherwise.
+
+--*/
+
+{
+ ULONG FinalIndex;
+ ULONG BaseIndex;
+ ULONG ThisIndex;
+
+ RTL_BITMAP Bitmap;
+ PULONG BitmapBuffer;
+
+ BOOLEAN StuffAdded = FALSE;
+ BOOLEAN MftTailFound = FALSE;
+ PBCB BitmapBcb = NULL;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilite cleanup.
+ //
+
+ try {
+
+ //
+ // Find the page and range of the last page of the Mft bitmap.
+ //
+
+ FinalIndex = (ULONG)Int64ShraMod32(Vcb->MftScb->Header.FileSize.QuadPart, Vcb->MftShift) - 1;
+
+ BaseIndex = FinalIndex & ~(BITS_PER_PAGE - 1);
+
+ Bitmap.SizeOfBitMap = FinalIndex - BaseIndex + 1;
+
+ //
+ // Pin this page. If the last bit is not clear then return immediately.
+ //
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftBitmapScb,
+ (LONGLONG)(BaseIndex / 8),
+ (Bitmap.SizeOfBitMap + 7) / 8,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, Bitmap.SizeOfBitMap );
+
+ StuffAdded = NtfsAddDeallocatedRecords( Vcb,
+ Vcb->MftScb,
+ BaseIndex,
+ &Bitmap );
+
+ //
+ // If the last bit isn't clear then there is nothing we can do.
+ //
+
+ if (RtlCheckBit( &Bitmap, Bitmap.SizeOfBitMap - 1 ) == 1) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Find the final free run of the page.
+ //
+
+ RtlFindLastBackwardRunClear( &Bitmap, Bitmap.SizeOfBitMap - 1, &ThisIndex );
+
+ //
+ // This Index is a relative value. Adjust by the page offset.
+ //
+
+ ThisIndex += BaseIndex;
+
+ //
+ // Round up the index to a trucate/extend granularity value.
+ //
+
+ ThisIndex += Vcb->MftHoleMask;
+ ThisIndex &= Vcb->MftHoleInverseMask;
+
+ if (ThisIndex <= FinalIndex) {
+
+ //
+ // Convert this value to a file offset and return it to our caller.
+ //
+
+ *FileOffset = LlBytesFromFileRecords( Vcb, ThisIndex );
+
+ MftTailFound = TRUE;
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFindMftFreeTail );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ return MftTailFound;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsAllocateBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates clusters in the bitmap within the specified range.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ StartingLcn - Supplies the starting Lcn index within the bitmap to
+ start allocating (i.e., setting to 1).
+
+ ClusterCount - Supplies the number of bits to set to 1 within the
+ bitmap.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LCN BaseLcn;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb;
+
+ ULONG BitOffset;
+ ULONG BitsToSet;
+
+ BITMAP_RANGE BitmapRange;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateBitmapRun\n") );
+ DebugTrace( 0, Dbg, ("StartingLcn = %016I64x\n", StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %016I64x\n", ClusterCount) );
+
+ BitmapBcb = NULL;
+
+ try {
+
+ //
+ // While the cluster count is greater than zero then we
+ // will loop through reading in a page in the bitmap
+ // setting bits, and then updating cluster count,
+ // and starting lcn
+ //
+
+ while (ClusterCount > 0) {
+
+ //
+ // Read in the base containing the starting lcn this will return
+ // a base lcn for the start of the bitmap
+ //
+
+ NtfsPinPageInBitmap( IrpContext, Vcb, StartingLcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ //
+ // Compute the bit offset within the bitmap of the first bit
+ // we are to set, and also compute the number of bits we need to
+ // set, which is the minimum of the cluster count and the
+ // number of bits left in the bitmap from BitOffset.
+ //
+
+ BitOffset = (ULONG)(StartingLcn - BaseLcn);
+
+ if (ClusterCount <= (Bitmap.SizeOfBitMap - BitOffset)) {
+
+ BitsToSet = (ULONG)ClusterCount;
+
+ } else {
+
+ BitsToSet = Bitmap.SizeOfBitMap - BitOffset;
+ }
+
+ //
+ // We can only make this check if it is not restart, because we have
+ // no idea whether the update is applied or not. Raise corrupt if
+ // already set to prevent cross-links.
+ //
+
+#ifdef NTFS_CHECK_BITMAP
+ if ((Vcb->BitmapCopy != NULL) &&
+ !NtfsCheckBitmap( Vcb,
+ (ULONG) BaseLcn + BitOffset,
+ BitsToSet,
+ FALSE )) {
+
+ NtfsBadBitmapCopy( IrpContext, (ULONG) BaseLcn + BitOffset, BitsToSet );
+ }
+#endif
+
+ if (!RtlAreBitsClear( &Bitmap, BitOffset, BitsToSet )) {
+
+ ASSERTMSG("Cannot set bits that are not clear ", FALSE );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now log this change as well.
+ //
+
+ BitmapRange.BitMapOffset = BitOffset;
+ BitmapRange.NumberOfBits = BitsToSet;
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Vcb->BitmapScb,
+ BitmapBcb,
+ SetBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ ClearBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ Int64ShraMod32( BaseLcn, 3 ),
+ 0,
+ 0,
+ Bitmap.SizeOfBitMap >> 3 );
+
+ //
+ // Now set the bits by calling the same routine used at restart.
+ //
+
+ NtfsRestartSetBitsInBitMap( IrpContext,
+ &Bitmap,
+ BitOffset,
+ BitsToSet );
+
+#ifdef NTFS_CHECK_BITMAP
+ if (Vcb->BitmapCopy != NULL) {
+
+ ULONG BitmapPage;
+ ULONG StartBit;
+
+ BitmapPage = ((ULONG) (BaseLcn + BitOffset)) / (PAGE_SIZE * 8);
+ StartBit = ((ULONG) (BaseLcn + BitOffset)) & ((PAGE_SIZE * 8) - 1);
+
+ RtlSetBits( Vcb->BitmapCopy + BitmapPage, StartBit, BitsToSet );
+ }
+#endif
+
+ //
+ // Unpin the Bcb now before possibly looping back.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Now decrement the cluster count and increment the starting lcn accordling
+ //
+
+ ClusterCount -= BitsToSet;
+ StartingLcn += BitsToSet;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsAllocateBitmapRun );
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateBitmapRun -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsRestartSetBitsInBitMap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRTL_BITMAP Bitmap,
+ IN ULONG BitMapOffset,
+ IN ULONG NumberOfBits
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is common to normal operation and restart, and sets a range of
+ bits within a single page (as determined by the system which wrote the log
+ record) of the volume bitmap.
+
+Arguments:
+
+ Bitmap - The bit map structure in which to set the bits
+
+ BitMapOffset - Bit offset to set
+
+ NumberOfBits - Number of bits to set
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( IrpContext );
+
+ PAGED_CODE();
+
+ //
+ // If not restart then check that the bits are clear.
+ //
+
+ ASSERT( FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )
+ || RtlAreBitsClear( Bitmap, BitMapOffset, NumberOfBits ));
+
+ //
+ // Now set the bits and mark the bcb dirty.
+ //
+
+ RtlSetBits( Bitmap, BitMapOffset, NumberOfBits );
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsFreeBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees clusters in the bitmap within the specified range.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ StartingLcn - Supplies the starting Lcn index within the bitmap to
+ start freeing (i.e., setting to 0).
+
+ ClusterCount - Supplies the number of bits to set to 0 within the
+ bitmap.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LCN BaseLcn;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb;
+
+ ULONG BitOffset;
+ ULONG BitsToClear;
+
+ BITMAP_RANGE BitmapRange;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFreeBitmapRun\n") );
+ DebugTrace( 0, Dbg, ("StartingLcn = %016I64\n", StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %016I64x\n", ClusterCount) );
+
+ BitmapBcb = NULL;
+
+ try {
+
+ //
+ // While the cluster count is greater than zero then we
+ // will loop through reading in a page in the bitmap
+ // clearing bits, and then updating cluster count,
+ // and starting lcn
+ //
+
+ while (ClusterCount > 0) {
+
+ //
+ // Read in the base containing the starting lcn this will return
+ // a base lcn for the start of the bitmap
+ //
+
+ NtfsPinPageInBitmap( IrpContext, Vcb, StartingLcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ //
+ // Compute the bit offset within the bitmap of the first bit
+ // we are to clear, and also compute the number of bits we need to
+ // clear, which is the minimum of the cluster count and the
+ // number of bits left in the bitmap from BitOffset.
+ //
+
+ BitOffset = (ULONG)(StartingLcn - BaseLcn);
+
+ if (ClusterCount <= Bitmap.SizeOfBitMap - BitOffset) {
+
+ BitsToClear = (ULONG)ClusterCount;
+
+ } else {
+
+ BitsToClear = Bitmap.SizeOfBitMap - BitOffset;
+ }
+
+ //
+ // We can only make this check if it is not restart, because we have
+ // no idea whether the update is applied or not. Raise corrupt if
+ // these bits aren't set.
+ //
+
+#ifdef NTFS_CHECK_BITMAP
+ if ((Vcb->BitmapCopy != NULL) &&
+ !NtfsCheckBitmap( Vcb,
+ (ULONG) BaseLcn + BitOffset,
+ BitsToClear,
+ TRUE )) {
+
+ NtfsBadBitmapCopy( IrpContext, (ULONG) BaseLcn + BitOffset, BitsToClear );
+ }
+#endif
+
+ if (!RtlAreBitsSet( &Bitmap, BitOffset, BitsToClear )) {
+
+ ASSERTMSG("Cannot clear bits that are not set ", FALSE );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now log this change as well.
+ //
+
+ BitmapRange.BitMapOffset = BitOffset;
+ BitmapRange.NumberOfBits = BitsToClear;
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Vcb->BitmapScb,
+ BitmapBcb,
+ ClearBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ SetBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ Int64ShraMod32( BaseLcn, 3 ),
+ 0,
+ 0,
+ Bitmap.SizeOfBitMap >> 3 );
+
+
+ //
+ // Now clear the bits by calling the same routine used at restart.
+ //
+
+ NtfsRestartClearBitsInBitMap( IrpContext,
+ &Bitmap,
+ BitOffset,
+ BitsToClear );
+
+#ifdef NTFS_CHECK_BITMAP
+ if (Vcb->BitmapCopy != NULL) {
+
+ ULONG BitmapPage;
+ ULONG StartBit;
+
+ BitmapPage = ((ULONG) (BaseLcn + BitOffset)) / (PAGE_SIZE * 8);
+ StartBit = ((ULONG) (BaseLcn + BitOffset)) & ((PAGE_SIZE * 8) - 1);
+
+ RtlClearBits( Vcb->BitmapCopy + BitmapPage, StartBit, BitsToClear );
+ }
+#endif
+
+ //
+ // Unpin the Bcb now before possibly looping back.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Now decrement the cluster count and increment the starting lcn accordling
+ //
+
+ ClusterCount -= BitsToClear;
+ StartingLcn += BitsToClear;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsFreeBitmapRun );
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFreeBitmapRun -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsRestartClearBitsInBitMap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRTL_BITMAP Bitmap,
+ IN ULONG BitMapOffset,
+ IN ULONG NumberOfBits
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is common to normal operation and restart, and clears a range of
+ bits within a single page (as determined by the system which wrote the log
+ record) of the volume bitmap.
+
+Arguments:
+
+ Bitmap - Bitmap structure in which to clear the bits
+
+ BitMapOffset - Bit offset to clear
+
+ NumberOfBits - Number of bits to clear
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( IrpContext );
+
+ PAGED_CODE();
+
+ ASSERT( FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )
+ || RtlAreBitsSet( Bitmap, BitMapOffset, NumberOfBits ));
+
+ //
+ // Now clear the bits and mark the bcb dirty.
+ //
+
+ RtlClearBits( Bitmap, BitMapOffset, NumberOfBits );
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsFindFreeBitmapRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LONGLONG NumberToFind,
+ IN LCN StartingSearchHint,
+ OUT PLCN ReturnedLcn,
+ OUT PLONGLONG ClusterCountFound
+ )
+
+/*++
+
+Routine Description:
+
+ This routine searches the bitmap for free clusters based on the
+ hint, and number needed. This routine is actually pretty dumb in
+ that it doesn't try for the best fit, we'll assume the caching worked
+ and already would have given us a good fit.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ NumberToFind - Supplies the number of clusters that we would
+ really like to find
+
+ StartingSearchHint - Supplies an Lcn to start the search from
+
+ ReturnedLcn - Recieves the Lcn of the free run of clusters that
+ we were able to find
+
+ ClusterCountFound - Receives the number of clusters in this run
+
+Return Value:
+
+ BOOLEAN - TRUE if clusters allocated from zone. FALSE otherwise.
+
+--*/
+
+{
+ RTL_BITMAP Bitmap;
+ PVOID BitmapBuffer;
+
+ PBCB BitmapBcb;
+
+ BOOLEAN AllocatedFromZone = FALSE;
+
+ BOOLEAN StuffAdded;
+
+ ULONG Count;
+
+ //
+ // As we walk through the bitmap pages we need to remember
+ // exactly where we are in the bitmap stream. We walk through
+ // the volume bitmap a page at a time but the current bitmap
+ // contained within the current page but may not be the full
+ // page.
+ //
+ // Lcn - Lcn used to find the bitmap page to pin. This Lcn
+ // will lie within the page to pin.
+ //
+ // BaseLcn - Bit offset of the start of the current bitmap in
+ // the bitmap stream.
+ //
+ // LcnFromHint - Bit offset of the start of the page after
+ // the page which contains the StartingSearchHint.
+ //
+ // BitOffset - Offset of found bits from the beginning
+ // of the current bitmap.
+ //
+
+ LCN Lcn = StartingSearchHint;
+ LCN BaseLcn;
+ LCN LcnFromHint;
+ ULONG BitOffset;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFindFreeBitmapRun\n") );
+ DebugTrace( 0, Dbg, ("NumberToFind = %016I64x\n", NumberToFind) );
+ DebugTrace( 0, Dbg, ("StartingSearchHint = %016I64x\n", StartingSearchHint) );
+
+ BitmapBcb = NULL;
+ StuffAdded = FALSE;
+
+ try {
+
+ //
+ // First trim the number of clusters that we are being asked
+ // for to fit in a ulong
+ //
+
+ if (NumberToFind > MAXULONG) {
+
+ Count = MAXULONG;
+
+ } else {
+
+ Count = (ULONG)NumberToFind;
+ }
+
+ //
+ // Now read in the first bitmap based on the search hint, this will return
+ // a base lcn that we can use to compute the real bit off for our hint. We also
+ // must bias the bitmap by whatever has been recently deallocated.
+ //
+
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ LcnFromHint = BaseLcn + Bitmap.SizeOfBitMap;
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, BaseLcn, &Bitmap );
+ BitmapBuffer = Bitmap.Buffer;
+
+ //
+ // We don't want to look in the Mft zone if it is at the beginning
+ // of this page unless the hint is within the zone. Adjust the
+ // bitmap so we skip this range.
+ //
+
+ if ((BaseLcn < Vcb->MftZoneEnd) && (Lcn > Vcb->MftZoneEnd)) {
+
+ //
+ // Find the number of bits to swallow. We know this will
+ // a multible of bytes since the Mft zone end is always
+ // on a ulong boundary.
+ //
+
+ BitOffset = (ULONG) (Vcb->MftZoneEnd - BaseLcn);
+
+ //
+ // Adjust the bitmap size and buffer to skip this initial
+ // range in the Mft zone.
+ //
+
+ Bitmap.Buffer = Add2Ptr( Bitmap.Buffer, BitOffset / 8 );
+ Bitmap.SizeOfBitMap -= BitOffset;
+
+ BaseLcn = Vcb->MftZoneEnd;
+ }
+
+ //
+ // The bit offset is from the base of this bitmap to our starting Lcn.
+ //
+
+ BitOffset = (ULONG)(Lcn - BaseLcn);
+
+ //
+ // Now search the bitmap for a clear number of bits based on our hint
+ // If we the returned bitoffset is not -1 then we have a hit
+ //
+
+ BitOffset = RtlFindClearBits( &Bitmap, Count, BitOffset );
+
+ if (BitOffset != -1) {
+
+ *ReturnedLcn = BitOffset + BaseLcn;
+ *ClusterCountFound = Count;
+
+ try_return(NOTHING);
+ }
+
+ //
+ // Well the first try didn't succeed so now just grab the longest free run in the
+ // current bitmap
+ //
+
+ Count = RtlFindLongestRunClear( &Bitmap, &BitOffset );
+
+ if (Count != 0) {
+
+ *ReturnedLcn = BitOffset + BaseLcn;
+ *ClusterCountFound = Count;
+
+ try_return(NOTHING);
+ }
+
+ //
+ // Well the current bitmap is full so now simply scan the disk looking
+ // for anything that is free, starting with the next bitmap.
+ // And again bias the bitmap with recently deallocated clusters.
+ //
+
+ for (Lcn = BaseLcn + Bitmap.SizeOfBitMap;
+ Lcn < Vcb->TotalClusters;
+ Lcn = BaseLcn + Bitmap.SizeOfBitMap) {
+
+ if (StuffAdded) { NtfsFreePool( BitmapBuffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &BaseLcn, &Bitmap, &BitmapBcb );
+ ASSERTMSG("Math wrong for bits per page of bitmap", (Lcn == BaseLcn));
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, BaseLcn, &Bitmap );
+ BitmapBuffer = Bitmap.Buffer;
+
+ Count = RtlFindLongestRunClear( &Bitmap, &BitOffset );
+
+ if (Count != 0) {
+
+ *ReturnedLcn = BitOffset + BaseLcn;
+ *ClusterCountFound = Count;
+
+ try_return(NOTHING);
+ }
+ }
+
+ //
+ // Now search the rest of the bitmap starting with right after the mft zone
+ // followed by the mft zone (or the beginning of the disk).
+ //
+
+ for (Lcn = Vcb->MftZoneEnd;
+ Lcn < LcnFromHint;
+ Lcn = BaseLcn + Bitmap.SizeOfBitMap) {
+
+ if (StuffAdded) { NtfsFreePool( BitmapBuffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, BaseLcn, &Bitmap );
+ BitmapBuffer = Bitmap.Buffer;
+
+ //
+ // Now adjust the starting Lcn if not at the beginning
+ // of the bitmap page. We know this will be a multiple
+ // of bytes since the MftZoneEnd is always on a ulong
+ // boundary in the bitmap.
+ //
+
+ if (BaseLcn != Lcn) {
+
+ BitOffset = (ULONG) (Lcn - BaseLcn);
+
+ Bitmap.SizeOfBitMap -= BitOffset;
+ Bitmap.Buffer = Add2Ptr( Bitmap.Buffer,
+ BitOffset / 8 );
+
+ BaseLcn = Lcn;
+ }
+
+ Count = RtlFindLongestRunClear( &Bitmap, &BitOffset );
+
+ if (Count != 0) {
+
+ *ReturnedLcn = BitOffset + BaseLcn;
+ *ClusterCountFound = Count;
+
+ try_return(NOTHING);
+ }
+ }
+
+ //
+ // Start a scan at the beginning of the disk.
+ //
+
+ for (Lcn = 0;
+ Lcn < Vcb->MftZoneEnd;
+ Lcn = BaseLcn + Bitmap.SizeOfBitMap) {
+
+ if (StuffAdded) { NtfsFreePool( BitmapBuffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, BaseLcn, &Bitmap );
+ BitmapBuffer = Bitmap.Buffer;
+
+ Count = RtlFindLongestRunClear( &Bitmap, &BitOffset );
+
+ if (Count != 0) {
+
+ *ReturnedLcn = BitOffset + BaseLcn;
+ *ClusterCountFound = Count;
+
+ AllocatedFromZone = TRUE;
+ try_return(NOTHING);
+ }
+ }
+
+ *ClusterCountFound = 0;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFindFreeBitmapRun );
+
+ if (StuffAdded) { NtfsFreePool( BitmapBuffer ); }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ DebugTrace( 0, Dbg, ("ReturnedLcn <- %016I64x\n", *ReturnedLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCountFound <- %016I64x\n", *ClusterCountFound) );
+ DebugTrace( -1, Dbg, ("NtfsFindFreeBitmapRun -> VOID\n") );
+
+ return AllocatedFromZone;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsAddRecentlyDeallocated (
+ IN PVCB Vcb,
+ IN LCN StartingBitmapLcn,
+ IN OUT PRTL_BITMAP Bitmap
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will modify the input bitmap by removing from it
+ any clusters that are in the recently deallocated mcb. If we
+ do add stuff then we will not modify the bitmap buffer itself but
+ will allocate a new copy for the bitmap.
+
+ We will always protect the boot sector on the disk by marking the
+ first 8K as allocated. This will prevent us from overwriting the
+ boot sector if the volume becomes corrupted.
+
+Arguments:
+
+ Vcb - Supplies the Vcb used in this operation
+
+ StartingBitmapLcn - Supplies the Starting Lcn of the bitmap
+
+ Bitmap - Supplies the bitmap being modified
+
+Return Value:
+
+ BOOLEAN - TRUE if the bitmap has been modified and FALSE
+ otherwise.
+
+--*/
+
+{
+ BOOLEAN Results;
+ PVOID NewBuffer;
+
+
+ LCN EndingBitmapLcn;
+
+ PLARGE_MCB Mcb;
+
+ ULONG i;
+ VCN StartingVcn;
+ LCN StartingLcn;
+ LCN EndingLcn;
+ LONGLONG ClusterCount;
+ PDEALLOCATED_CLUSTERS DeallocatedClusters;
+
+ ULONG StartingBit;
+ ULONG EndingBit;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddRecentlyDeallocated...\n") );
+
+ //
+ // Until shown otherwise we will assume that we haven't updated anything
+ //
+
+ Results = FALSE;
+
+ //
+ // If this is the first page of the bitmap then mark the first 8K as
+ // allocated. This will prevent us from accidentally allocating out
+ // of the boot sector even if the bitmap is corrupt.
+ //
+
+ if ((StartingBitmapLcn == 0) &&
+ !RtlAreBitsSet( Bitmap, 0, ClustersFromBytes( Vcb, 0x2000 ))) {
+
+ NewBuffer = NtfsAllocatePool(PagedPool, (Bitmap->SizeOfBitMap+7)/8 );
+ RtlCopyMemory( NewBuffer, Bitmap->Buffer, (Bitmap->SizeOfBitMap+7)/8 );
+ Bitmap->Buffer = NewBuffer;
+
+ Results = TRUE;
+
+ //
+ // Now mark the bits as allocated.
+ //
+
+ RtlSetBits( Bitmap, 0, ClustersFromBytes( Vcb, 0x2000 ));
+ }
+
+ //
+ // Now compute the ending bitmap lcn for the bitmap
+ //
+
+ EndingBitmapLcn = StartingBitmapLcn + (Bitmap->SizeOfBitMap - 1);
+
+ //
+ // For every run in the recently deallocated mcb we will check if it is real and
+ // then check if the run in contained in the bitmap.
+ //
+ // There are really six cases to consider:
+ //
+ // StartingBitmapLcn EndingBitmapLcn
+ // +=================================+
+ //
+ //
+ // 1 -------+ EndingLcn
+ //
+ // 2 StartingLcn +--------
+ //
+ // 3 -------------------+ EndingLcn
+ //
+ // 4 StartingLcn +-------------------------
+ //
+ // 5 ---------------------------------------------------------------
+ //
+ // 6 EndingLcn +-------------------+ StartingLcn
+ //
+ //
+ // 1. EndingLcn is before StartingBitmapLcn which means we haven't
+ // reached the bitmap yet.
+ //
+ // 2. StartingLcn is after EndingBitmapLcn which means we've gone
+ // beyond the bitmap
+ //
+ // 3, 4, 5, 6. There is some overlap between the bitmap and
+ // the run.
+ //
+
+ DeallocatedClusters = Vcb->PriorDeallocatedClusters;
+
+ while (TRUE) {
+
+ //
+ // Skip this Mcb if it has no entries.
+ //
+
+ if (DeallocatedClusters->ClusterCount != 0) {
+
+ Mcb = &DeallocatedClusters->Mcb;
+
+ for (i = 0; FsRtlGetNextLargeMcbEntry( Mcb, i, &StartingVcn, &StartingLcn, &ClusterCount ); i += 1) {
+
+ if (StartingVcn == StartingLcn) {
+
+ //
+ // Compute the ending lcn as the starting lcn minus cluster count plus 1.
+ //
+
+ EndingLcn = (StartingLcn + ClusterCount) - 1;
+
+ //
+ // Check if we haven't reached the bitmap yet.
+ //
+
+ if (EndingLcn < StartingBitmapLcn) {
+
+ NOTHING;
+
+ //
+ // Check if we've gone beyond the bitmap
+ //
+
+ } else if (EndingBitmapLcn < StartingLcn) {
+
+ break;
+
+ //
+ // Otherwise we overlap with the bitmap in some way
+ //
+
+ } else {
+
+ //
+ // First check if we have never set bit in the bitmap. and if so then
+ // now is the time to make an private copy of the bitmap buffer
+ //
+
+ if (Results == FALSE) {
+
+ NewBuffer = NtfsAllocatePool(PagedPool, (Bitmap->SizeOfBitMap+7)/8 );
+ RtlCopyMemory( NewBuffer, Bitmap->Buffer, (Bitmap->SizeOfBitMap+7)/8 );
+ Bitmap->Buffer = NewBuffer;
+
+ Results = TRUE;
+ }
+
+ //
+ // Now compute the begining and ending bit that we need to set in the bitmap
+ //
+
+ StartingBit = (StartingLcn < StartingBitmapLcn ?
+ 0
+ : (ULONG)(StartingLcn - StartingBitmapLcn));
+
+ EndingBit = (EndingLcn > EndingBitmapLcn ?
+ Bitmap->SizeOfBitMap - 1
+ : (ULONG)(EndingLcn - StartingBitmapLcn));
+
+ //
+ // And set those bits
+ //
+
+ RtlSetBits( Bitmap, StartingBit, EndingBit - StartingBit + 1 );
+ }
+ }
+ }
+ }
+
+ //
+ // Exit if we did both Mcb's, otherwise go to the second one.
+ //
+
+ if (DeallocatedClusters == Vcb->ActiveDeallocatedClusters) {
+
+ break;
+ }
+
+ DeallocatedClusters = Vcb->ActiveDeallocatedClusters;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddRecentlyDeallocated -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsMapOrPinPageInBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ OUT PLCN StartingLcn,
+ IN OUT PRTL_BITMAP Bitmap,
+ OUT PBCB *BitmapBcb,
+ IN BOOLEAN AlsoPinData
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads in a single page of the bitmap file and returns
+ an initialized bitmap variable for that page
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ Lcn - Supplies the Lcn index in the bitmap that we want to read in
+ In other words, this routine reads in the bitmap page containing
+ the lcn index
+
+ StartingLcn - Receives the base lcn index of the bitmap that we've
+ just read in.
+
+ Bitmap - Receives an initialized bitmap. The memory for the bitmap
+ header must be supplied by the caller
+
+ BitmapBcb - Receives the Bcb for the bitmap buffer
+
+ AlsoPinData - Indicates if this routine should also pin the page
+ in memory, used if we need to modify the page
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG BitmapSize;
+ PVOID Buffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMapOrPinPageInBitmap\n") );
+ DebugTrace( 0, Dbg, ("Lcn = %016I64x\n", Lcn) );
+
+ //
+ // Compute the starting lcn index of the page we're after
+ //
+
+ *StartingLcn = Lcn & ~(BITS_PER_PAGE-1);
+
+ //
+ // Compute how many bits there are in the page we need to read
+ //
+
+ BitmapSize = (ULONG)(Vcb->TotalClusters - *StartingLcn);
+
+ if (BitmapSize > BITS_PER_PAGE) {
+
+ BitmapSize = BITS_PER_PAGE;
+ }
+
+ //
+ // Now either Pin or Map the bitmap page, we will add 7 to the bitmap
+ // size before dividing it by 8. That way we will ensure we get the last
+ // byte read in. For example a bitmap size of 1 through 8 reads in 1 byte
+ //
+
+ if (AlsoPinData) {
+
+ NtfsPinStream( IrpContext,
+ Vcb->BitmapScb,
+ Int64ShraMod32( *StartingLcn, 3 ),
+ (BitmapSize+7)/8,
+ BitmapBcb,
+ &Buffer );
+
+ } else {
+
+ NtfsMapStream( IrpContext,
+ Vcb->BitmapScb,
+ Int64ShraMod32( *StartingLcn, 3 ),
+ (BitmapSize+7)/8,
+ BitmapBcb,
+ &Buffer );
+ }
+
+ //
+ // And initialize the bitmap
+ //
+
+ RtlInitializeBitMap( Bitmap, Buffer, BitmapSize );
+
+ DebugTrace( 0, Dbg, ("StartingLcn <- %016I64x\n", *StartingLcn) );
+ DebugTrace( -1, Dbg, ("NtfsMapOrPinPageInBitmap -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsInitializeCachedBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the cached free
+ mcb/lru structures of the input vcb
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ BOOLEAN UninitializeFreeSpaceMcb = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeCachedBitmap\n") );
+
+ //
+ // Use a try-finally so we can uninitialize if we don't complete the operation.
+ //
+
+ try {
+
+ //
+ // First initialize the free space information. This includes initializing
+ // the mcb, allocating an lru array, zeroing it out, and setting the
+ // tail and head.
+ //
+
+ FsRtlInitializeLargeMcb( &Vcb->FreeSpaceMcb, PagedPool );
+ UninitializeFreeSpaceMcb = TRUE;
+
+ //
+ // We will base the amount of cached bitmap information on the size of
+ // the system and the size of the disk.
+ //
+
+ //if (Vcb->TotalClusters < CLUSTERS_MEDIUM_DISK) {
+ //
+ // Vcb->FreeSpaceMcbMaximumSize = 16;
+ //
+ //} else if (Vcb->TotalClusters < CLUSTERS_LARGE_DISK) {
+ //
+ // Vcb->FreeSpaceMcbMaximumSize = 32;
+ //
+ //} else {
+ //
+ // Vcb->FreeSpaceMcbMaximumSize = 64;
+ //}
+ //
+ //if (FlagOn( NtfsData.Flags, NTFS_FLAGS_MEDIUM_SYSTEM )) {
+ //
+ // Vcb->FreeSpaceMcbMaximumSize *= 2;
+ //
+ //} else if (FlagOn( NtfsData.Flags, NTFS_FLAGS_LARGE_SYSTEM )) {
+ //
+ // Vcb->FreeSpaceMcbMaximumSize *= 4;
+ //}
+ //
+ //Vcb->FreeSpaceMcbTrimToSize = Vcb->FreeSpaceMcbMaximumSize / 2;
+
+ Vcb->FreeSpaceMcbMaximumSize = 8192;
+ Vcb->FreeSpaceMcbTrimToSize = 6144;
+
+ } finally {
+
+ if (AbnormalTermination()) {
+
+ if (UninitializeFreeSpaceMcb) {
+
+ FsRtlUninitializeLargeMcb( &Vcb->FreeSpaceMcb );
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeCachedBitmap -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsIsLcnInCachedFreeRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ OUT PLCN StartingLcn,
+ OUT PLONGLONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a query function on the cached bitmap information.
+ Given an input lcn it tell the caller if the lcn is contained
+ in a free run. The output variables are only defined if the input
+ lcn is within a free run.
+
+ The algorithm used by this procedure is as follows:
+
+ 2. Query the Free Space mcb at the input lcn this will give us
+ a starting lcn and cluster count. If we do not get a hit then
+ return false to the caller.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in the operation
+
+ Lcn - Supplies the input lcn being queried
+
+ StartingLcn - Receives the Lcn of the run containing the input lcn
+
+ ClusterCount - Receives the number of clusters in the run
+ containing the input lcn
+
+Return Value:
+
+ BOOLEAN - TRUE if the input lcn is within a cached free run and
+ FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN Result;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsLcnInCachedFreeRun\n") );
+ DebugTrace( 0, Dbg, ("Lcn = %016I64x\n", Lcn) );
+
+ //
+ // Check the free space mcb for a hit on the input lcn, if we don't get a
+ // hit or we get back a -1 as the output lcn then we are not looking
+ // at a free space lcn
+ //
+
+ if (!FsRtlLookupLargeMcbEntry( &Vcb->FreeSpaceMcb,
+ Lcn,
+ NULL,
+ NULL,
+ StartingLcn,
+ ClusterCount,
+ NULL )
+
+ ||
+
+ (*StartingLcn == UNUSED_LCN)) {
+
+ Result = FALSE;
+
+ } else {
+
+ Result = TRUE;
+ }
+
+ DebugTrace( 0, Dbg, ("StartingLcn <- %016I64x\n", *StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount <- %016I64x\n", *ClusterCount) );
+ DebugTrace( -1, Dbg, ("NtfsIsLcnInCachedFreeRun -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsAddCachedRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount,
+ IN NTFS_RUN_STATE RunState
+ )
+
+/*++
+
+Routine Description:
+
+ This procedure adds a new run to the cached free space
+ bitmap information. It also will trim back the cached information
+ if the Lru array is full.
+
+Arguments:
+
+ Vcb - Supplies the vcb for this operation
+
+ StartingLcn - Supplies the lcn for the run being added
+
+ ClusterCount - Supplies the number of clusters in the run being added
+
+ RunState - Supplies the state of the run being added. This state
+ must be either free or allocated.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLARGE_MCB Mcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddCachedRun\n") );
+ DebugTrace( 0, Dbg, ("StartingLcn = %016I64x\n", StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %016I64x\n", ClusterCount) );
+
+ //
+ // Based on whether we are adding a free or allocated run we
+ // setup or local variables to a point to the right
+ // vcb variables
+ //
+
+ if (RunState == RunStateFree) {
+
+ //
+ // We better not be setting Lcn 0 free.
+ //
+
+ if (StartingLcn == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ Mcb = &Vcb->FreeSpaceMcb;
+
+ //
+ // Trim back the MCB if necessary
+ //
+
+ if (Mcb->PairCount > Vcb->FreeSpaceMcbMaximumSize) {
+
+ Mcb->PairCount = Vcb->FreeSpaceMcbTrimToSize;
+ }
+
+ //
+ // Sanity check that we aren't adding bits beyond the end of the
+ // bitmap.
+ //
+
+ ASSERT( StartingLcn + ClusterCount <= Vcb->TotalClusters );
+
+ //
+ // Now try and add the run to our mcb, this operation might fail because
+ // of overlapping runs, and if it does then we'll simply remove the range from
+ // the mcb and then insert it.
+ //
+
+ if (!FsRtlAddLargeMcbEntry( Mcb, StartingLcn, StartingLcn, ClusterCount )) {
+
+ FsRtlRemoveLargeMcbEntry( Mcb, StartingLcn, ClusterCount );
+
+ (VOID) FsRtlAddLargeMcbEntry( Mcb, StartingLcn, StartingLcn, ClusterCount );
+ }
+
+ } else {
+
+ //
+ // Now remove the run from the free space mcb because it can potentially already be
+ // there.
+ //
+
+ FsRtlRemoveLargeMcbEntry( &Vcb->FreeSpaceMcb, StartingLcn, ClusterCount );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddCachedRun -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsRemoveCachedRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes a range of cached run information from both the
+ free space mcb.
+
+Arguments:
+
+ Vcb - Supplies the vcb for this operation
+
+ StartingLcn - Supplies the starting Lcn for the run being removed
+
+ ClusterCount - Supplies the size of the run being removed in clusters
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRemoveCachedRun\n") );
+ DebugTrace( 0, Dbg, ("StartingLcn = %016I64x\n", StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %016I64x\n", ClusterCount) );
+
+ //
+ // To remove a cached entry we only need to remove the run from both
+ // mcbs and we are done
+ //
+
+ FsRtlRemoveLargeMcbEntry( &Vcb->FreeSpaceMcb, StartingLcn, ClusterCount );
+
+ DebugTrace( -1, Dbg, ("NtfsRemoveCachedRun -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsGetNextCachedFreeRun (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG RunIndex,
+ OUT PLCN StartingLcn,
+ OUT PLONGLONG ClusterCount,
+ OUT PNTFS_RUN_STATE RunState
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to enumerate through the free runs stored in our
+ cached bitmap. It returns the specified free run if it exists.
+
+Arguments:
+
+ Vcb - Supplies the vcb used in this operation
+
+ RunIndex - Supplies the index of the free run to return. The runs
+ are ordered in ascending lcns and the indexing is zero based
+
+ StartingLcn - Receives the starting lcn of the free run indexed by
+ RunIndex if it exists. This is only set if the run state is free.
+
+ ClusterCount - Receives the cluster size of the free run indexed by
+ RunIndex if it exists. This is only set if the run state is free.
+
+ RunState - Receives the state of the run indexed by RunIndex it can
+ either be free or unknown
+
+Return Value:
+
+ BOOLEAN - TRUE if the run index exists and FALSE otherwise
+
+--*/
+
+{
+ BOOLEAN Result;
+
+ VCN LocalVcn;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGetNextCachedFreeRun\n") );
+ DebugTrace( 0, Dbg, ("RunIndex = %08lx\n", RunIndex) );
+
+ //
+ // First lookup and see if we have a hit in the free space mcb
+ //
+
+ if (FsRtlGetNextLargeMcbEntry( &Vcb->FreeSpaceMcb,
+ RunIndex,
+ &LocalVcn,
+ StartingLcn,
+ ClusterCount )) {
+
+ Result = TRUE;
+
+ //
+ // Now if the free space is really a hole then we set the run state
+ // to unknown
+ //
+
+ if (*StartingLcn == UNUSED_LCN) {
+
+ *RunState = RunStateUnknown;
+
+ } else {
+
+ *RunState = RunStateFree;
+
+ ASSERTMSG("Lcn zero can never be free ", (*StartingLcn != 0));
+ }
+
+ } else {
+
+ Result = FALSE;
+ }
+
+ DebugTrace( 0, Dbg, ("StartingLcn <- %016I64x\n", *StartingLcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount <- %016I64x\n", *ClusterCount) );
+ DebugTrace( -1, Dbg, ("NtfsGetNextCachedFreeRun -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsReadAheadCachedBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a read ahead of the bitmap into the cached bitmap
+ starting at the specified starting lcn.
+
+Arguments:
+
+ Vcb - Supplies the vcb to use in this operation
+
+ StartingLcn - Supplies the starting lcn to use in this read ahead
+ operation.
+
+Return Value:
+
+--*/
+
+{
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb;
+
+ BOOLEAN StuffAdded;
+
+ LCN BaseLcn;
+ ULONG Index;
+ LONGLONG Size;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReadAheadCachedBitmap\n") );
+ DebugTrace( 0, Dbg, ("StartingLcn = %016I64x\n", StartingLcn) );
+
+ BitmapBcb = NULL;
+ StuffAdded = FALSE;
+
+ try {
+
+ //
+ // Check if the lcn index is already in the free space mcb and if it is then
+ // our read ahead is done.
+ //
+
+ if (FsRtlLookupLargeMcbEntry( &Vcb->FreeSpaceMcb, StartingLcn, &BaseLcn, NULL, NULL, NULL, NULL )
+
+ &&
+
+ (BaseLcn != UNUSED_LCN)) {
+
+ try_return(NOTHING);
+ }
+
+ //
+ // Map in the page containing the starting lcn and compute the bit index for the
+ // starting lcn within the bitmap. And bias the bitmap with recently deallocated
+ // clusters.
+ //
+
+ NtfsMapPageInBitmap( IrpContext, Vcb, StartingLcn, &BaseLcn, &Bitmap, &BitmapBcb );
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, BaseLcn, &Bitmap );
+
+ Index = (ULONG)(StartingLcn - BaseLcn);
+
+ //
+ // Now if the index is clear then we can build up the hint at the starting index, we
+ // scan through the bitmap checking the size of the run and then adding the free run
+ // to the cached free space mcb
+ //
+
+ if (RtlCheckBit( &Bitmap, Index ) == 0) {
+
+ Size = RtlFindNextForwardRunClear( &Bitmap, Index, &Index );
+
+ NtfsAddCachedRun( IrpContext, Vcb, StartingLcn, (LONGLONG)Size, RunStateFree );
+
+ try_return(NOTHING);
+ }
+
+ //
+ // The hint lcn index is not free so we'll do the next best thing which is
+ // scan the bitmap for the longest free run and store that
+ //
+
+ Size = RtlFindLongestRunClear( &Bitmap, &Index );
+
+ if (Size != 0) {
+
+ NtfsAddCachedRun( IrpContext, Vcb, BaseLcn + Index, (LONGLONG)Size, RunStateFree );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsReadAheadCachedBitmap );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsReadAheadCachedBitmap -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsGetNextHoleToFill (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ OUT PVCN VcnToFill,
+ OUT PLONGLONG ClusterCountToFill,
+ OUT PLCN PrecedingLcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine takes a specified range within an mcb and returns the to
+ caller the first run that is not allocated to any lcn within the range
+
+Arguments:
+
+ Mcb - Supplies the mcb to use in this operation
+
+ StartingVcn - Supplies the starting vcn to search from
+
+ EndingVcn - Supplies the ending vcn to search to
+
+ VcnToFill - Receives the first Vcn within the range that is unallocated
+
+ ClusterCountToFill - Receives the size of the free run
+
+ PrecedingLcn - Receives the Lcn of the allocated cluster preceding the
+ free run. If the free run starts at Vcn 0 then the preceding lcn
+ is -1.
+
+Return Value:
+
+ BOOLEAN - TRUE if there is another hole to fill and FALSE otherwise
+
+--*/
+
+{
+ BOOLEAN Result;
+ BOOLEAN McbHit;
+ LCN Lcn;
+ LONGLONG MaximumRunSize;
+
+ LONGLONG LlTemp1;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGetNextHoleToFill\n") );
+ DebugTrace( 0, Dbg, ("StartingVcn = %016I64x\n", StartingVcn) );
+ DebugTrace( 0, Dbg, ("EndingVcn = %016I64x\n", EndingVcn) );
+
+ //
+ // We'll first assume that there is not a hole to fill unless
+ // the following loop finds one to fill
+ //
+
+ Result = FALSE;
+
+ for (*VcnToFill = StartingVcn;
+ *VcnToFill <= EndingVcn;
+ *VcnToFill += *ClusterCountToFill) {
+
+ //
+ // Check if the hole is already filled and it so then do nothing but loop back up
+ // to the top of our loop and try again
+ //
+
+ if ((McbHit = NtfsLookupNtfsMcbEntry( Mcb, *VcnToFill, &Lcn, ClusterCountToFill, NULL, NULL, NULL, NULL )) &&
+ (Lcn != UNUSED_LCN)) {
+
+ NOTHING;
+
+ } else {
+
+ //
+ // We have a hole to fill so now compute the maximum size hole that
+ // we are allowed to fill and then check if we got an miss on the lookup
+ // and need to set cluster count or if the size we got back is too large
+ //
+
+ MaximumRunSize = (EndingVcn - *VcnToFill) + 1;
+
+ if (!McbHit || (*ClusterCountToFill > MaximumRunSize)) {
+
+ *ClusterCountToFill = MaximumRunSize;
+ }
+
+ //
+ // Now set the preceding lcn to either -1 if there isn't a preceding vcn or
+ // set it to the lcn of the preceding vcn
+ //
+
+ if (*VcnToFill == 0) {
+
+ *PrecedingLcn = UNUSED_LCN;
+
+ } else {
+
+ LlTemp1 = *VcnToFill - 1;
+
+ if (!NtfsLookupNtfsMcbEntry( Mcb, LlTemp1, PrecedingLcn, NULL, NULL, NULL, NULL, NULL )) {
+
+ *PrecedingLcn = UNUSED_LCN;
+ }
+ }
+
+ //
+ // We found a hole so set our result to TRUE and break out of the loop
+ //
+
+ Result = TRUE;
+
+ break;
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("VcnToFill <- %016I64x\n", *VcnToFill) );
+ DebugTrace( 0, Dbg, ("ClusterCountToFill <- %016I64x\n", *ClusterCountToFill) );
+ DebugTrace( 0, Dbg, ("PrecedingLcn <- %016I64x\n", *PrecedingLcn) );
+ DebugTrace( -1, Dbg, ("NtfsGetNextHoleToFill -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+//
+// Local support routine
+//
+
+LONGLONG
+NtfsScanMcbForRealClusterCount (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine scans the input mcb within the specified range and returns
+ to the caller the exact number of clusters that a really free (i.e.,
+ not mapped to any Lcn) within the range.
+
+Arguments:
+
+ Mcb - Supplies the Mcb used in this operation
+
+ StartingVcn - Supplies the starting vcn to search from
+
+ EndingVcn - Supplies the ending vcn to search to
+
+Return Value:
+
+ LONGLONG - Returns the number of unassigned clusters from
+ StartingVcn to EndingVcn inclusive within the mcb.
+
+--*/
+
+{
+ LONGLONG FreeCount;
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG RunSize;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsScanMcbForRealClusterCount\n") );
+ DebugTrace( 0, Dbg, ("StartingVcn = %016I64x\n", StartingVcn) );
+ DebugTrace( 0, Dbg, ("EndingVcn = %016I64x\n", EndingVcn) );
+
+ //
+ // First compute free count as if the entire run is already unallocated
+ // and the in the following loop we march through the mcb looking for
+ // actual allocation and decrementing the free count appropriately
+ //
+
+ FreeCount = (EndingVcn - StartingVcn) + 1;
+
+ for (Vcn = StartingVcn; Vcn <= EndingVcn; Vcn = Vcn + RunSize) {
+
+ //
+ // Lookup the mcb entry and if we get back false then we're overrun
+ // the mcb and therefore nothing else above it can be allocated.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( Mcb, Vcn, &Lcn, &RunSize, NULL, NULL, NULL, NULL )) {
+
+ break;
+ }
+
+ //
+ // If the lcn we got back is not -1 then this run is actually already
+ // allocated, so first check if the run size puts us over the ending
+ // vcn and adjust as necessary and then decrement the free count
+ // by the run size
+ //
+
+ if (Lcn != UNUSED_LCN) {
+
+ if (RunSize > ((EndingVcn - Vcn) + 1)) {
+
+ RunSize = (EndingVcn - Vcn) + 1;
+ }
+
+ FreeCount = FreeCount - RunSize;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsScanMcbForRealClusterCount -> %016I64x\n", FreeCount) );
+
+ return FreeCount;
+}
+
+
+//
+// Local support routine, only defined with ntfs debug version
+//
+
+#ifdef NTFSDBG
+
+ULONG
+NtfsDumpCachedMcbInformation (
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine dumps out the cached bitmap information
+
+Arguments:
+
+ Vcb - Supplies the Vcb used by this operation
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ DbgPrint("Dump BitMpSup Information, Vcb@ %08lx\n", Vcb);
+
+ DbgPrint("TotalCluster: %016I64x\n", Vcb->TotalClusters);
+ DbgPrint("FreeClusters: %016I64x\n", Vcb->FreeClusters);
+
+ DbgPrint("FreeSpaceMcb@ %08lx ", &Vcb->FreeSpaceMcb );
+ DbgPrint("McbMaximumSize: %08lx ", Vcb->FreeSpaceMcbMaximumSize );
+ DbgPrint("McbTrimToSize: %08lx ", Vcb->FreeSpaceMcbTrimToSize );
+
+ return 1;
+}
+
+#endif // NTFSDBG
+
+
+//
+// The rest of this module implements the record allocation routines
+//
+
+
+VOID
+NtfsInitializeRecordAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB DataScb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute,
+ IN ULONG BytesPerRecord,
+ IN ULONG ExtendGranularity,
+ IN ULONG TruncateGranularity,
+ IN OUT PRECORD_ALLOCATION_CONTEXT RecordAllocationContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the record allocation context used for
+ allocating and deallocating fixed sized records from a data stream.
+
+ Note that the bitmap attribute size must always be at least a multiple
+ of 32 bits. However the data scb does not need to contain that many
+ records. If in the course of allocating a new record we discover that
+ the data scb is too small we will then add allocation to the data scb.
+
+Arguments:
+
+ DataScb - Supplies the Scb representing the data stream that is being
+ divided into fixed sized records with each bit in the bitmap corresponding
+ to one record in the data stream
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. The attribute can either be resident or nonresident
+ and this routine will handle both cases properly.
+
+ BytesPerRecord - Supplies the size of the homogenous records that
+ that the data stream is being divided into.
+
+ ExtendGranularity - Supplies the number of records (i.e., allocation units
+ to extend the data scb by each time).
+
+ TruncateGranularity - Supplies the number of records to use when truncating
+ the data scb. That is if the end of the data stream contains the
+ specified number of free records then we truncate.
+
+ RecordAllocationContext - Supplies the memory for an context record that is
+ utilized by this record allocation routines.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER AttributeRecordHeader;
+ RTL_BITMAP Bitmap;
+
+ ULONG ClearLength;
+ ULONG ClearIndex;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( DataScb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeRecordAllocation\n") );
+
+ ASSERT( BytesPerRecord * ExtendGranularity >= DataScb->Vcb->BytesPerCluster );
+ ASSERT( BytesPerRecord * TruncateGranularity >= DataScb->Vcb->BytesPerCluster );
+
+ //
+ // First zero out the context record except for the data scb.
+ //
+
+ RtlZeroMemory( &RecordAllocationContext->BitmapScb,
+ sizeof(RECORD_ALLOCATION_CONTEXT) -
+ FIELD_OFFSET( RECORD_ALLOCATION_CONTEXT, BitmapScb ));
+
+ //
+ // And then set the fields in the context record that do not depend on
+ // whether the bitmap attribute is resident or not
+ //
+
+ RecordAllocationContext->DataScb = DataScb;
+ RecordAllocationContext->BytesPerRecord = BytesPerRecord;
+ RecordAllocationContext->ExtendGranularity = ExtendGranularity;
+ RecordAllocationContext->TruncateGranularity = TruncateGranularity;
+
+ //
+ // Now get a reference to the bitmap record header and then take two
+ // different paths depending if the bitmap attribute is resident or not
+ //
+
+ AttributeRecordHeader = NtfsFoundAttribute(BitmapAttribute);
+
+ if (NtfsIsAttributeResident(AttributeRecordHeader)) {
+
+ ASSERTMSG("bitmap must be multiple quadwords", AttributeRecordHeader->Form.Resident.ValueLength % 8 == 0);
+
+ //
+ // For a resident bitmap attribute the bitmap scb field is null and we
+ // set the bitmap size from the value length. Also we will initialize
+ // our local bitmap variable and determine the number of free bits
+ // current available.
+ //
+ //
+
+ RecordAllocationContext->BitmapScb = NULL;
+
+ RecordAllocationContext->CurrentBitmapSize = 8 * AttributeRecordHeader->Form.Resident.ValueLength;
+
+ RtlInitializeBitMap( &Bitmap,
+ (PULONG)NtfsAttributeValue( AttributeRecordHeader ),
+ RecordAllocationContext->CurrentBitmapSize );
+
+ RecordAllocationContext->NumberOfFreeBits = RtlNumberOfClearBits( &Bitmap );
+
+ ClearLength = RtlFindLastBackwardRunClear( &Bitmap,
+ RecordAllocationContext->CurrentBitmapSize - 1,
+ &ClearIndex );
+
+ } else {
+
+ UNICODE_STRING BitmapName;
+
+ BOOLEAN ReturnedExistingScb;
+ PBCB BitmapBcb;
+ PVOID BitmapBuffer;
+
+ ASSERTMSG("bitmap must be multiple quadwords", ((ULONG)AttributeRecordHeader->Form.Nonresident.FileSize) % 8 == 0);
+
+ //
+ // For a non resident bitmap attribute we better have been given the
+ // record header for the first part and not somthing that has spilled
+ // into multiple segment records
+ //
+
+ ASSERT(AttributeRecordHeader->Form.Nonresident.LowestVcn == 0);
+
+ BitmapBcb = NULL;
+
+ try {
+
+ ULONG StartingByte;
+
+ ULONG BitsThisPage;
+ ULONG BytesThisPage;
+ ULONG RemainingBytes;
+
+ ULONG ThisClearIndex;
+ ULONG ThisClearLength;
+
+ //
+ // Create the bitmap scb for the bitmap attribute
+ //
+
+ BitmapName.MaximumLength =
+ BitmapName.Length = AttributeRecordHeader->NameLength * 2;
+ BitmapName.Buffer = Add2Ptr(AttributeRecordHeader, AttributeRecordHeader->NameOffset);
+
+ RecordAllocationContext->BitmapScb = NtfsCreateScb( IrpContext,
+ DataScb->Fcb,
+ AttributeRecordHeader->TypeCode,
+ &BitmapName,
+ FALSE,
+ &ReturnedExistingScb );
+
+ //
+ // Now determine the bitmap size, for now we'll only take bitmap attributes that are
+ // no more than 16 pages large.
+ //
+
+ RecordAllocationContext->CurrentBitmapSize = 8 * ((ULONG)AttributeRecordHeader->Form.Nonresident.FileSize);
+
+ //
+ // Create the stream file if not present.
+ //
+
+ if (RecordAllocationContext->BitmapScb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, RecordAllocationContext->BitmapScb, TRUE );
+ }
+
+ //
+ // Walk through each page of the bitmap and compute the number of set
+ // bits and the last set bit in the bitmap.
+ //
+
+ RecordAllocationContext->NumberOfFreeBits = 0;
+ RemainingBytes = (ULONG) AttributeRecordHeader->Form.Nonresident.FileSize;
+ StartingByte = 0;
+ ClearLength = 0;
+
+ while (TRUE) {
+
+ BytesThisPage = RemainingBytes;
+
+ if (RemainingBytes > PAGE_SIZE) {
+
+ BytesThisPage = PAGE_SIZE;
+ }
+
+ BitsThisPage = BytesThisPage * 8;
+
+ //
+ // Now map the bitmap data, initialize our local bitmap variable and
+ // calculate the number of free bits currently available
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ NtfsMapStream( IrpContext,
+ RecordAllocationContext->BitmapScb,
+ (LONGLONG)StartingByte,
+ BytesThisPage,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap,
+ BitmapBuffer,
+ BitsThisPage );
+
+ RecordAllocationContext->NumberOfFreeBits += RtlNumberOfClearBits( &Bitmap );
+
+ //
+ // We are interested in remembering the last set bit in this bitmap.
+ // If the bitmap ends with a clear run then the last set bit is
+ // immediately prior to this clear run. We need to check each page
+ // as we go through the bitmap to see if a clear run ends at the end
+ // of the current page.
+ //
+
+ ThisClearLength = RtlFindLastBackwardRunClear( &Bitmap,
+ BitsThisPage - 1,
+ &ThisClearIndex );
+
+ //
+ // If there is a run and it ends at the end of the page then
+ // either combine with a previous run or remember that this is the
+ // start of the run.
+ //
+
+ if ((ThisClearLength != 0) &&
+ ((ThisClearLength + ThisClearIndex) == BitsThisPage)) {
+
+ //
+ // If this is the entire page and the previous page ended
+ // with a clear run then just extend that run.
+ //
+
+ if ((ThisClearIndex == 0) && (ClearLength != 0)) {
+
+ ClearLength += ThisClearLength;
+
+ //
+ // Otherwise this is a new clear run. Bias the starting index
+ // by the bit offset of this page.
+ //
+
+ } else {
+
+ ClearLength = ThisClearLength;
+ ClearIndex = ThisClearIndex + (StartingByte * 8);
+ }
+
+ //
+ // This page does not end with a clear run.
+ //
+
+ } else {
+
+ ClearLength = 0;
+ }
+
+ //
+ // If we are not at the end of the bitmap then update our
+ // counters.
+ //
+
+ if (RemainingBytes != BytesThisPage) {
+
+ StartingByte += PAGE_SIZE;
+ RemainingBytes -= PAGE_SIZE;
+
+ } else {
+
+ break;
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsInitializeRecordAllocation );
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+ }
+
+ //
+ // With ClearLength and ClearIndex we can now deduce the last set bit in the
+ // bitmap
+ //
+
+ if ((ClearLength != 0) && ((ClearLength + ClearIndex) == RecordAllocationContext->CurrentBitmapSize)) {
+
+ RecordAllocationContext->IndexOfLastSetBit = ClearIndex - 1;
+
+ } else {
+
+ RecordAllocationContext->IndexOfLastSetBit = RecordAllocationContext->CurrentBitmapSize - 1;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeRecordAllocation -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsUninitializeRecordAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PRECORD_ALLOCATION_CONTEXT RecordAllocationContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to uninitialize the record allocation context.
+
+Arguments:
+
+ RecordAllocationContext - Supplies the record allocation context being
+ decommissioned.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUninitializeRecordAllocation\n") );
+
+ //
+ // And then for safe measure zero out the entire record except for the
+ // the data Scb.
+ //
+
+ RtlZeroMemory( &RecordAllocationContext->BitmapScb,
+ sizeof(RECORD_ALLOCATION_CONTEXT) -
+ FIELD_OFFSET( RECORD_ALLOCATION_CONTEXT, BitmapScb ));
+
+ DebugTrace( -1, Dbg, ("NtfsUninitializeRecordAllocation -> VOID\n") );
+
+ return;
+}
+
+
+ULONG
+NtfsAllocateRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Hint,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to allocate a new record for the specified record
+ allocation context.
+
+ It will return the index of a free record in the data scb as denoted by
+ the bitmap attribute. If necessary this routine will extend the bitmap
+ attribute size (including spilling over to the nonresident case), and
+ extend the data scb size.
+
+ On return the record is zeroed.
+
+Arguments:
+
+ RecordAllocationContext - Supplies the record allocation context used
+ in this operation
+
+ Hint - Supplies the hint index used for finding a free record.
+ Zero based.
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. This parameter is ignored if the bitmap attribute is
+ non resident, in which case we create an scb for the attribute and
+ store a pointer to it in the record allocation context.
+
+Return Value:
+
+ ULONG - Returns the index of the record just allocated, zero based.
+
+--*/
+
+{
+ PSCB DataScb;
+ LONGLONG DataOffset;
+
+ LONGLONG ClusterCount;
+
+ ULONG BytesPerRecord;
+ ULONG ExtendGranularity;
+ ULONG TruncateGranularity;
+
+ PULONG CurrentBitmapSize;
+ PULONG NumberOfFreeBits;
+
+ PSCB BitmapScb;
+ PBCB BitmapBcb;
+ RTL_BITMAP Bitmap;
+ PUCHAR BitmapBuffer;
+ ULONG BitmapOffset;
+ ULONG BitmapIndex;
+ ULONG BitmapSizeInBytes;
+ ULONG BitmapCurrentOffset = 0;
+ ULONG BitmapSizeInPages;
+
+ BOOLEAN StuffAdded = FALSE;
+ BOOLEAN Rescan;
+
+ PVCB Vcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateRecord\n") );
+
+ //
+ // Synchronize by acquiring the data scb exclusive, as an "end resource".
+ // Then use try-finally to insure we free it up.
+ //
+
+ DataScb = RecordAllocationContext->DataScb;
+ NtfsAcquireExclusiveScb( IrpContext, DataScb );
+
+ try {
+
+ //
+ // Remember some values for convenience.
+ //
+
+ BytesPerRecord = RecordAllocationContext->BytesPerRecord;
+ ExtendGranularity = RecordAllocationContext->ExtendGranularity;
+ TruncateGranularity = RecordAllocationContext->TruncateGranularity;
+
+ Vcb = DataScb->Vcb;
+
+ //
+ // See if someone made the bitmap nonresident, and we still think
+ // it is resident. If so, we must uninitialize and insure reinitialization
+ // below.
+ //
+
+ if ((RecordAllocationContext->BitmapScb == NULL) &&
+ !NtfsIsAttributeResident( NtfsFoundAttribute( BitmapAttribute ))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ RecordAllocationContext );
+
+ RecordAllocationContext->CurrentBitmapSize = MAXULONG;
+ }
+
+ //
+ // Reinitialize the record context structure if necessary.
+ //
+
+ if (RecordAllocationContext->CurrentBitmapSize == MAXULONG) {
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ TruncateGranularity,
+ RecordAllocationContext );
+ }
+
+ BitmapScb = RecordAllocationContext->BitmapScb;
+ CurrentBitmapSize = &RecordAllocationContext->CurrentBitmapSize;
+ NumberOfFreeBits = &RecordAllocationContext->NumberOfFreeBits;
+
+ BitmapSizeInBytes = *CurrentBitmapSize / 8;
+
+ //
+ // We will do different operations based on whether the bitmap is resident or nonresident
+ // The first case we will handle is the resident bitmap.
+ //
+
+ if (BitmapScb == NULL) {
+
+ BOOLEAN SizeExtended = FALSE;
+ UCHAR NewByte;
+
+ //
+ // Now now initialize the local bitmap variable and hunt for that free bit
+ //
+
+ BitmapBuffer = (PUCHAR) NtfsAttributeValue( NtfsFoundAttribute( BitmapAttribute ));
+
+ RtlInitializeBitMap( &Bitmap,
+ (PULONG)BitmapBuffer,
+ *CurrentBitmapSize );
+
+ StuffAdded = NtfsAddDeallocatedRecords( Vcb, DataScb, 0, &Bitmap );
+
+ BitmapIndex = RtlFindClearBits( &Bitmap, 1, Hint );
+
+ //
+ // Check if we have found a free record that can be allocated, If not then extend
+ // the size of the bitmap by 64 bits, and set the index to the bit first bit
+ // of the extension we just added
+ //
+
+ if (BitmapIndex == 0xffffffff) {
+
+ union {
+ QUAD Quad;
+ UCHAR Uchar[ sizeof(QUAD) ];
+ } ZeroQuadWord;
+
+ *(PLARGE_INTEGER)&(ZeroQuadWord.Uchar)[0] = Li0;
+
+ NtfsChangeAttributeValue( IrpContext,
+ DataScb->Fcb,
+ BitmapSizeInBytes,
+ &(ZeroQuadWord.Uchar)[0],
+ sizeof( QUAD ),
+ TRUE,
+ TRUE,
+ FALSE,
+ TRUE,
+ BitmapAttribute );
+
+ BitmapIndex = *CurrentBitmapSize;
+ *CurrentBitmapSize += BITMAP_EXTEND_GRANULARITY;
+ *NumberOfFreeBits += BITMAP_EXTEND_GRANULARITY;
+
+ BitmapSizeInBytes += (BITMAP_EXTEND_GRANULARITY / 8);
+
+ SizeExtended = TRUE;
+
+ //
+ // We now know that the byte value we should start with is 0
+ // We cannot safely access the bitmap attribute any more because
+ // it may have moved.
+ //
+
+ NewByte = 0;
+
+ } else {
+
+ //
+ // Capture the current value of the byte for the index if we
+ // are not extending. Notice that we always take this from the
+ // unbiased original bitmap.
+ //
+
+ NewByte = BitmapBuffer[ BitmapIndex / 8 ];
+ }
+
+ //
+ // Check if we made the Bitmap go non-resident and if so then
+ // we will reinitialize the record allocation context and fall through
+ // to the non-resident case
+ //
+
+ if (SizeExtended && !NtfsIsAttributeResident( NtfsFoundAttribute( BitmapAttribute ))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ RecordAllocationContext );
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ TruncateGranularity,
+ RecordAllocationContext );
+
+ BitmapScb = RecordAllocationContext->BitmapScb;
+
+ ASSERT( BitmapScb != NULL );
+
+ } else {
+
+ //
+ // Index is now the free bit so set the bit in the bitmap and also change
+ // the byte containing the bit in the attribute. Be careful to set the
+ // bit in the byte from the *original* bitmap, and not the one we merged
+ // the recently-deallocated bits with.
+ //
+
+ ASSERT(!FlagOn( NewByte, BitMask[BitmapIndex % 8]));
+
+ SetFlag( NewByte, BitMask[BitmapIndex % 8] );
+
+ NtfsChangeAttributeValue( IrpContext,
+ DataScb->Fcb,
+ BitmapIndex / 8,
+ &NewByte,
+ 1,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ BitmapAttribute );
+ }
+ }
+
+ //
+ // Use a loop here to handle the extreme case where extending the allocation
+ // of the volume bitmap causes us to renter this routine recursively.
+ // In that case the top level guy will fail expecting the first bit to
+ // be available in the added clusters. Instead we will return to the
+ // top of this loop after extending the bitmap and just do our normal
+ // scan.
+ //
+
+ while (BitmapScb != NULL) {
+
+ ULONG SizeToPin;
+ ULONG HoleIndex;
+
+ BitmapBcb = NULL;
+ Rescan = FALSE;
+ HoleIndex = 0;
+
+ try {
+
+ if (!FlagOn( BitmapScb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, BitmapScb, NULL );
+ }
+
+ //
+ // Snapshot the Scb values in case we change any of them.
+ //
+
+ NtfsSnapshotScb( IrpContext, BitmapScb );
+
+ //
+ // Create the stream file if not present.
+ //
+
+ if (BitmapScb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, BitmapScb, FALSE );
+ }
+
+ //
+ // Remember the starting offset for the page containing the hint.
+ //
+
+ BitmapCurrentOffset = (Hint / 8) & ~(PAGE_SIZE - 1);
+ Hint &= (BITS_PER_PAGE - 1);
+
+ BitmapSizeInPages = ROUND_TO_PAGES( BitmapSizeInBytes );
+
+ //
+ // Loop for the size of the bitmap plus one page, so that we will
+ // retry the initial page once starting from a hint offset of 0.
+ //
+
+ for (BitmapOffset = 0;
+ BitmapOffset <= BitmapSizeInPages;
+ BitmapOffset += PAGE_SIZE, BitmapCurrentOffset += PAGE_SIZE) {
+
+ ULONG LocalHint;
+
+ //
+ // If our current position is past the end of the bitmap
+ // then go to the beginning of the bitmap.
+ //
+
+ if (BitmapCurrentOffset >= BitmapSizeInBytes) {
+
+ BitmapCurrentOffset = 0;
+ }
+
+ //
+ // If this is the Mft and there are more than the system
+ // files in the first cluster of the Mft then move past
+ // the first cluster.
+ //
+
+ if ((BitmapCurrentOffset == 0) &&
+ (DataScb == Vcb->MftScb) &&
+ (Vcb->FileRecordsPerCluster > FIRST_USER_FILE_NUMBER) &&
+ (Hint < Vcb->FileRecordsPerCluster)) {
+
+ Hint = Vcb->FileRecordsPerCluster;
+ }
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap, or a page, whichever is less.
+ //
+
+ SizeToPin = BitmapSizeInBytes - BitmapCurrentOffset;
+
+ if (SizeToPin > PAGE_SIZE) { SizeToPin = PAGE_SIZE; }
+
+ //
+ // Unpin any Bcb from a previous loop.
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsPinStream( IrpContext,
+ BitmapScb,
+ (LONGLONG)BitmapCurrentOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // Initialize the bitmap and search for a free bit.
+ //
+
+ RtlInitializeBitMap( &Bitmap, (PULONG) BitmapBuffer, SizeToPin * 8 );
+
+ StuffAdded = NtfsAddDeallocatedRecords( Vcb,
+ DataScb,
+ BitmapCurrentOffset * 8,
+ &Bitmap );
+
+ //
+ // We make a loop here to test whether the index found is
+ // within an Mft hole. We will always use a hole last.
+ //
+
+ LocalHint = Hint;
+
+ while (TRUE) {
+
+ BitmapIndex = RtlFindClearBits( &Bitmap, 1, LocalHint );
+
+ //
+ // If this is the Mft Scb then check if this is a hole.
+ //
+
+ if ((BitmapIndex != 0xffffffff) &&
+ (DataScb == Vcb->MftScb)) {
+
+ ULONG ThisIndex;
+ ULONG HoleCount;
+
+ ThisIndex = BitmapIndex + (BitmapCurrentOffset * 8);
+
+ if (NtfsIsMftIndexInHole( IrpContext,
+ Vcb,
+ ThisIndex,
+ &HoleCount )) {
+
+ //
+ // There is a hole. Save this index if we haven't
+ // already saved one. If we can't find an index
+ // not part of a hole we will use this instead of
+ // extending the file.
+ //
+
+ if (HoleIndex == 0) {
+
+ HoleIndex = ThisIndex;
+ }
+
+ //
+ // Now update the hint and try this page again
+ // unless the reaches to the end of the page.
+ //
+
+ if (BitmapIndex + HoleCount < SizeToPin * 8) {
+
+ //
+ // Bias the bitmap with these Mft holes
+ // so the bitmap package doesn't see
+ // them if it rescans from the
+ // start of the page.
+ //
+
+ if (!StuffAdded) {
+
+ PVOID NewBuffer;
+
+ NewBuffer = NtfsAllocatePool(PagedPool, SizeToPin );
+ RtlCopyMemory( NewBuffer, Bitmap.Buffer, SizeToPin );
+ Bitmap.Buffer = NewBuffer;
+ StuffAdded = TRUE;
+ }
+
+ RtlSetBits( &Bitmap,
+ BitmapIndex,
+ HoleCount );
+
+ LocalHint = BitmapIndex + HoleCount;
+ continue;
+ }
+
+ //
+ // Store a -1 in Index to show we don't have
+ // anything yet.
+ //
+
+ BitmapIndex = 0xffffffff;
+ }
+ }
+
+ break;
+ }
+
+ //
+ // If we found something, then leave the loop.
+ //
+
+ if (BitmapIndex != 0xffffffff) {
+
+ break;
+ }
+
+ //
+ // If we get here, we could not find anything in the page of
+ // the hint, so clear out the page offset from the hint.
+ //
+
+ Hint = 0;
+ }
+
+ //
+ // Now check if we have located a record that can be allocated, If not then extend
+ // the size of the bitmap by 64 bits.
+ //
+
+ if (BitmapIndex == 0xffffffff) {
+
+ //
+ // Cleanup from previous loop.
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // If we have a hole index it means that we found a free record but
+ // it exists in a hole. Let's go back to this page and set up
+ // to fill in the hole. We will do an unsafe test of the
+ // defrag permitted flag. This is OK here because once set it
+ // will only go to the non-set state in order to halt
+ // future defragging.
+ //
+
+ if ((HoleIndex != 0) &&
+ FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED )) {
+
+ //
+ // Start by filling this hole.
+ //
+
+ NtfsCheckRecordStackUsage( IrpContext );
+ NtfsFillMftHole( IrpContext, Vcb, HoleIndex );
+
+ //
+ // Since filling the Mft hole may cause us to allocate
+ // a bit we will go back to the start of the routine
+ // and scan starting from the hole we just filled in.
+ //
+
+ Hint = HoleIndex;
+ Rescan = TRUE;
+ try_return( NOTHING );
+
+ } else {
+
+ //
+ // Allocate the first bit past the end of the bitmap.
+ //
+
+ BitmapIndex = *CurrentBitmapSize & (BITS_PER_PAGE - 1);
+
+ //
+ // Now advance the sizes and calculate the size in bytes to
+ // read.
+ //
+
+ *CurrentBitmapSize += BITMAP_EXTEND_GRANULARITY;
+ *NumberOfFreeBits += BITMAP_EXTEND_GRANULARITY;
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap.
+ //
+
+ BitmapSizeInBytes += BITMAP_EXTEND_GRANULARITY / 8;
+
+ BitmapCurrentOffset = BitmapScb->Header.FileSize.LowPart & ~(PAGE_SIZE - 1);
+
+ SizeToPin = BitmapSizeInBytes - BitmapCurrentOffset;
+
+ //
+ // Check for allocation first.
+ //
+
+ if (BitmapScb->Header.AllocationSize.LowPart < BitmapSizeInBytes) {
+
+ //
+ // Calculate number of clusters to next page boundary, and allocate
+ // that much.
+ //
+
+ ClusterCount = ((BitmapSizeInBytes + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1));
+
+ ClusterCount = LlClustersFromBytes( Vcb,
+ ((ULONG) ClusterCount - BitmapScb->Header.AllocationSize.LowPart) );
+
+ NtfsCheckRecordStackUsage( IrpContext );
+ NtfsAddAllocation( IrpContext,
+ BitmapScb->FileObject,
+ BitmapScb,
+ LlClustersFromBytes( Vcb,
+ BitmapScb->Header.AllocationSize.QuadPart ),
+ ClusterCount,
+ FALSE);
+ }
+
+ //
+ // Tell the cache manager about the new file size.
+ //
+
+ BitmapScb->Header.FileSize.QuadPart = BitmapSizeInBytes;
+
+ CcSetFileSizes( BitmapScb->FileObject,
+ (PCC_FILE_SIZES)&BitmapScb->Header.AllocationSize );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsPinStream( IrpContext,
+ BitmapScb,
+ (LONGLONG) BitmapCurrentOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // If we have just moved to the next page of the bitmap then
+ // set this page dirty so it doesn't leave memory while we
+ // twiddle valid data length. Otherwise it will be reread after
+ // we advance valid data and we will get garbage data from the
+ // disk.
+ //
+
+ if (FlagOn( BitmapSizeInBytes, PAGE_SIZE - 1 ) <= BITMAP_EXTEND_GRANULARITY / 8) {
+
+ *((volatile ULONG *) BitmapBuffer) = *((PULONG) BitmapBuffer);
+ CcSetDirtyPinnedData( BitmapBcb, NULL );
+ }
+
+ //
+ // Initialize the bitmap.
+ //
+
+ RtlInitializeBitMap( &Bitmap, (PULONG) BitmapBuffer, SizeToPin * 8 );
+
+ //
+ // Update the ValidDataLength, now that we have read (and possibly
+ // zeroed) the page.
+ //
+
+ BitmapScb->Header.ValidDataLength.QuadPart = BitmapSizeInBytes;
+
+ NtfsWriteFileSizes( IrpContext,
+ BitmapScb,
+ &BitmapScb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+
+ //
+ // Now look up a free bit in this page. We don't trust
+ // the index we already had since growing the MftBitmap
+ // allocation may have caused another bit in the bitmap
+ // to be set.
+ //
+
+ BitmapIndex = RtlFindClearBits( &Bitmap, 1, BitmapIndex );
+ }
+ }
+
+ //
+ // We can only make this check if it is not restart, because we have
+ // no idea whether the update is applied or not. Raise corrupt if
+ // the bits are not clear to prevent double allocation.
+ //
+
+ if (!RtlAreBitsClear( &Bitmap, BitmapIndex, 1 )) {
+
+ ASSERTMSG("Cannot set bits that are not clear ", FALSE );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Set the bit by calling the same routine used at restart.
+ // But first check if we should revert back to the orginal bitmap
+ // buffer.
+ //
+
+ if (StuffAdded) {
+
+ NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE;
+
+ Bitmap.Buffer = (PULONG) BitmapBuffer;
+ }
+
+ //
+ // Now log this change as well.
+ //
+
+ {
+ BITMAP_RANGE BitmapRange;
+
+ BitmapRange.BitMapOffset = BitmapIndex;
+ BitmapRange.NumberOfBits = 1;
+
+ (VOID) NtfsWriteLog( IrpContext,
+ BitmapScb,
+ BitmapBcb,
+ SetBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ ClearBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ BitmapCurrentOffset,
+ 0,
+ 0,
+ SizeToPin );
+
+ NtfsRestartSetBitsInBitMap( IrpContext,
+ &Bitmap,
+ BitmapIndex,
+ 1 );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsAllocateRecord );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ //
+ // If we added Mft allocation then go to the top of the loop.
+ //
+
+ if (Rescan) { continue; }
+
+ //
+ // The Index at this point is actually relative, so convert it to absolute
+ // before rejoining common code.
+ //
+
+ BitmapIndex += (BitmapCurrentOffset * 8);
+
+ //
+ // Always break out in the normal case.
+ //
+
+ break;
+ }
+
+ //
+ // Now that we've located an index we can subtract the number of free bits in the bitmap
+ //
+
+ *NumberOfFreeBits -= 1;
+
+ //
+ // Check if we need to extend the data stream.
+ //
+
+ DataOffset = UInt32x32To64( BitmapIndex + 1, BytesPerRecord );
+
+ //
+ // Now check if we are extending the file. We update the file size and
+ // valid data now.
+ //
+
+ if (DataOffset > DataScb->Header.FileSize.QuadPart) {
+
+ //
+ // Check for allocation first.
+ //
+
+ if (DataOffset > DataScb->Header.AllocationSize.QuadPart) {
+
+ //
+ // We want to allocate up to the next extend granularity
+ // boundary.
+ //
+
+ ClusterCount = UInt32x32To64( (BitmapIndex + ExtendGranularity) & ~(ExtendGranularity - 1),
+ BytesPerRecord );
+
+ ClusterCount -= DataScb->Header.AllocationSize.QuadPart;
+ ClusterCount = LlClustersFromBytesTruncate( Vcb, ClusterCount );
+
+ NtfsCheckRecordStackUsage( IrpContext );
+ NtfsAddAllocation( IrpContext,
+ DataScb->FileObject,
+ DataScb,
+ LlClustersFromBytes( Vcb,
+ DataScb->Header.AllocationSize.QuadPart ),
+ ClusterCount,
+ FALSE );
+ }
+
+ DataScb->Header.FileSize.QuadPart = DataOffset;
+ DataScb->Header.ValidDataLength.QuadPart = DataOffset;
+
+ NtfsWriteFileSizes( IrpContext,
+ DataScb,
+ &DataScb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+
+ //
+ // Tell the cache manager about the new file size.
+ //
+
+ CcSetFileSizes( DataScb->FileObject,
+ (PCC_FILE_SIZES)&DataScb->Header.AllocationSize );
+
+ //
+ // If we didn't extend the file then we have used a free file record in the file.
+ // Update our bookeeping count for free file records.
+ //
+
+ } else if (DataScb == Vcb->MftScb) {
+
+ DataScb->ScbType.Mft.FreeRecordChange -= 1;
+ Vcb->MftFreeRecords -= 1;
+ }
+
+ //
+ // Now determine if we extended the index of the last set bit
+ //
+
+ if ((LONG)BitmapIndex > RecordAllocationContext->IndexOfLastSetBit) {
+
+ RecordAllocationContext->IndexOfLastSetBit = BitmapIndex;
+ }
+
+ } finally {
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+
+ NtfsReleaseScb( IrpContext, DataScb );
+ }
+
+ //
+ // We shouldn't allocate within the same byte as the reserved index for
+ // the Mft.
+ //
+
+ ASSERT( (DataScb != DataScb->Vcb->MftScb) ||
+ ((BitmapIndex & ~7) != (DataScb->ScbType.Mft.ReservedIndex & ~7)) );
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateRecord -> %08lx\n", BitmapIndex) );
+
+ return BitmapIndex;
+}
+
+
+VOID
+NtfsDeallocateRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Index,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to deallocate a record from the specified record
+ allocation context.
+
+ If necessary this routine will also shrink the bitmap attribute and
+ the data scb (according to the truncation granularity used to initialize
+ the allocation context).
+
+Arguments:
+
+ RecordAllocationContext - Supplies the record allocation context used
+ in this operation
+
+ Index - Supplies the index of the record to deallocate, zero based.
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. This parameter is ignored if the bitmap attribute is
+ non resident, in which case we create an scb for the attribute and
+ store a pointer to it in the record allocation context.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSCB DataScb;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ DebugTrace( +1, Dbg, ("NtfsDeallocateRecord\n") );
+
+ //
+ // Synchronize by acquiring the data scb exclusive, as an "end resource".
+ // Then use try-finally to insure we free it up.
+ //
+
+ DataScb = RecordAllocationContext->DataScb;
+ NtfsAcquireExclusiveScb( IrpContext, DataScb );
+
+ try {
+
+ PVCB Vcb;
+ PSCB BitmapScb;
+
+ RTL_BITMAP Bitmap;
+
+ PLONG IndexOfLastSetBit;
+ ULONG BytesPerRecord;
+ ULONG TruncateGranularity;
+
+ ULONG ClearIndex;
+ ULONG BitmapOffset = 0;
+
+ Vcb = DataScb->Vcb;
+
+ {
+ ULONG ExtendGranularity;
+
+ //
+ // Remember the current values in the record context structure.
+ //
+
+ BytesPerRecord = RecordAllocationContext->BytesPerRecord;
+ TruncateGranularity = RecordAllocationContext->TruncateGranularity;
+ ExtendGranularity = RecordAllocationContext->ExtendGranularity;
+
+ //
+ // See if someone made the bitmap nonresident, and we still think
+ // it is resident. If so, we must uninitialize and insure reinitialization
+ // below.
+ //
+
+ if ((RecordAllocationContext->BitmapScb == NULL)
+ && !NtfsIsAttributeResident(NtfsFoundAttribute(BitmapAttribute))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ RecordAllocationContext );
+
+ RecordAllocationContext->CurrentBitmapSize = MAXULONG;
+ }
+
+ //
+ // Reinitialize the record context structure if necessary.
+ //
+
+ if (RecordAllocationContext->CurrentBitmapSize == MAXULONG) {
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ TruncateGranularity,
+ RecordAllocationContext );
+ }
+ }
+
+ BitmapScb = RecordAllocationContext->BitmapScb;
+ IndexOfLastSetBit = &RecordAllocationContext->IndexOfLastSetBit;
+
+ //
+ // We will do different operations based on whether the bitmap is resident or nonresident
+ // The first case will handle the resident bitmap
+ //
+
+ if (BitmapScb == NULL) {
+
+ UCHAR NewByte;
+
+ //
+ // Initialize the local bitmap
+ //
+
+ RtlInitializeBitMap( &Bitmap,
+ (PULONG)NtfsAttributeValue( NtfsFoundAttribute( BitmapAttribute )),
+ RecordAllocationContext->CurrentBitmapSize );
+
+ //
+ // And clear the indicated bit, and also change the byte containing the bit in the
+ // attribute
+ //
+
+ NewByte = ((PUCHAR)Bitmap.Buffer)[ Index / 8 ];
+
+ ASSERT(FlagOn( NewByte, BitMask[Index % 8]));
+
+ ClearFlag( NewByte, BitMask[Index % 8] );
+
+ NtfsChangeAttributeValue( IrpContext,
+ DataScb->Fcb,
+ Index / 8,
+ &NewByte,
+ 1,
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ BitmapAttribute );
+
+ //
+ // Now if the bit set just cleared is the same as the index for the last set bit
+ // then we must compute a new last set bit
+ //
+
+ if (Index == (ULONG)*IndexOfLastSetBit) {
+
+ RtlFindLastBackwardRunClear( &Bitmap, Index, &ClearIndex );
+ }
+
+ } else {
+
+ PBCB BitmapBcb = NULL;
+
+ try {
+
+ ULONG RelativeIndex;
+ ULONG SizeToPin;
+
+ PVOID BitmapBuffer;
+
+ //
+ // Snapshot the Scb values in case we change any of them.
+ //
+
+ if (!FlagOn( BitmapScb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, BitmapScb, NULL );
+ }
+
+ NtfsSnapshotScb( IrpContext, BitmapScb );
+
+ //
+ // Create the stream file if not present.
+ //
+
+ if (BitmapScb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, BitmapScb, FALSE );
+ }
+
+ //
+ // Calculate offset and relative index of the bit we will deallocate,
+ // from the nearest page boundary.
+ //
+
+ BitmapOffset = Index /8 & ~(PAGE_SIZE - 1);
+ RelativeIndex = Index & (BITS_PER_PAGE - 1);
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap.
+ //
+
+ SizeToPin = (RecordAllocationContext->CurrentBitmapSize / 8) - BitmapOffset;
+
+ if (SizeToPin > PAGE_SIZE) {
+
+ SizeToPin = PAGE_SIZE;
+ }
+
+ NtfsPinStream( IrpContext,
+ BitmapScb,
+ BitmapOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, SizeToPin * 8 );
+
+ //
+ // We can only make this check if it is not restart, because we have
+ // no idea whether the update is applied or not. Raise corrupt if
+ // we are trying to clear bits which aren't set.
+ //
+
+ if (!RtlAreBitsSet( &Bitmap, RelativeIndex, 1 )) {
+
+ ASSERTMSG("Cannot clear bits that are not set ", FALSE );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now log this change as well.
+ //
+
+ {
+ BITMAP_RANGE BitmapRange;
+
+ BitmapRange.BitMapOffset = RelativeIndex;
+ BitmapRange.NumberOfBits = 1;
+
+ (VOID) NtfsWriteLog( IrpContext,
+ BitmapScb,
+ BitmapBcb,
+ ClearBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ SetBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ BitmapOffset,
+ 0,
+ 0,
+ SizeToPin );
+ }
+
+ //
+ // Clear the bit by calling the same routine used at restart.
+ //
+
+ NtfsRestartClearBitsInBitMap( IrpContext,
+ &Bitmap,
+ RelativeIndex,
+ 1 );
+
+ //
+ // Now if the bit set just cleared is the same as the index for the last set bit
+ // then we must compute a new last set bit
+ //
+
+ if (Index == (ULONG)*IndexOfLastSetBit) {
+
+ ULONG ClearLength;
+
+ ClearLength = RtlFindLastBackwardRunClear( &Bitmap, RelativeIndex, &ClearIndex );
+
+ //
+ // If the last page of the bitmap is clear, then loop to
+ // find the first set bit in the previous page(s).
+ // When we reach the first page then we exit. The ClearBit
+ // value will be 0.
+ //
+
+ while ((ClearLength == (RelativeIndex + 1)) &&
+ (BitmapOffset != 0)) {
+
+ BitmapOffset -= PAGE_SIZE;
+ RelativeIndex = BITS_PER_PAGE - 1;
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+
+ NtfsMapStream( IrpContext,
+ BitmapScb,
+ BitmapOffset,
+ PAGE_SIZE,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, BITS_PER_PAGE );
+
+ ClearLength = RtlFindLastBackwardRunClear( &Bitmap, RelativeIndex, &ClearIndex );
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsDeallocateRecord );
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+ }
+
+ RecordAllocationContext->NumberOfFreeBits += 1;
+
+ //
+ // Now decide if we need to truncate the allocation. First check if we need to
+ // set the last set bit index and then check if the new last set bit index is
+ // small enough that we should now truncate the allocation. We will truncate
+ // if the last set bit index plus the trucate granularity is smaller than
+ // the current number of records in the data scb.
+ //
+ // **** For now, we will not truncate the Mft, since we do not synchronize
+ // reads and writes, and a truncate can collide with the Lazy Writer.
+ //
+
+ if (Index == (ULONG)*IndexOfLastSetBit) {
+
+ *IndexOfLastSetBit = ClearIndex - 1 + (BitmapOffset * 8);
+
+ if ((DataScb != Vcb->MftScb) &&
+ (DataScb->Header.AllocationSize.QuadPart >
+ Int32x32To64( *IndexOfLastSetBit + 1 + TruncateGranularity, BytesPerRecord ))) {
+
+ VCN StartingVcn;
+ LONGLONG EndOfIndexOffset;
+ LONGLONG TruncatePoint;
+
+ //
+ // We can get into a situation where there is so much extra allocation that
+ // we can't delete it without overflowing the log file. We can't perform
+ // checkpoints in this path so we will forget about truncating in
+ // this path unless this is the first truncate of the data scb. We
+ // only deallocate a small piece of the allocation.
+ //
+
+ TruncatePoint =
+ EndOfIndexOffset = Int32x32To64( *IndexOfLastSetBit + 1, BytesPerRecord );
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL )) {
+
+ //
+ // Use a fudge factor of 8 to allow for the overused bits in
+ // the snapshot allocation field.
+ //
+
+ if (DataScb->Header.AllocationSize.QuadPart + 8 >= DataScb->ScbSnapshot->AllocationSize) {
+
+ TruncatePoint = DataScb->Header.AllocationSize.QuadPart - (MAXIMUM_RUNS_AT_ONCE * Vcb->BytesPerCluster);
+
+ if (TruncatePoint < EndOfIndexOffset) {
+
+ TruncatePoint = EndOfIndexOffset;
+ }
+
+ } else {
+
+ TruncatePoint = DataScb->Header.AllocationSize.QuadPart;
+ }
+ }
+
+ StartingVcn = LlClustersFromBytes( Vcb, TruncatePoint );
+
+ NtfsDeleteAllocation( IrpContext,
+ DataScb->FileObject,
+ DataScb,
+ StartingVcn,
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+
+ //
+ // Now truncate the file sizes to the end of the last allocated record.
+ //
+
+ DataScb->Header.ValidDataLength.QuadPart =
+ DataScb->Header.FileSize.QuadPart = EndOfIndexOffset;
+
+ NtfsWriteFileSizes( IrpContext,
+ DataScb,
+ &DataScb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+
+ //
+ // Tell the cache manager about the new file size.
+ //
+
+ CcSetFileSizes( DataScb->FileObject,
+ (PCC_FILE_SIZES)&DataScb->Header.AllocationSize );
+
+ //
+ // We have truncated the index stream. Update the change count
+ // so that we won't trust any cached index entry information.
+ //
+
+ DataScb->ScbType.Index.ChangeCount += 1;
+ }
+ }
+
+ //
+ // As our final task we need to add this index to the recently deallocated
+ // queues for the Scb and the Irp Context. First scan through the IrpContext queue
+ // looking for a matching Scb. I do don't find one then we allocate a new one and insert
+ // it in the appropriate queues and lastly we add our index to the entry
+ //
+
+ {
+ PDEALLOCATED_RECORDS DeallocatedRecords;
+ PLIST_ENTRY Links;
+
+ //
+ // After the following loop either we've found an existing record in the irp context
+ // queue for the appropriate scb or deallocated records is null and we know we need
+ // to create a record
+ //
+
+ DeallocatedRecords = NULL;
+ for (Links = IrpContext->RecentlyDeallocatedQueue.Flink;
+ Links != &IrpContext->RecentlyDeallocatedQueue;
+ Links = Links->Flink) {
+
+ DeallocatedRecords = CONTAINING_RECORD( Links, DEALLOCATED_RECORDS, IrpContextLinks );
+
+ if (DeallocatedRecords->Scb == DataScb) {
+
+ break;
+ }
+
+ DeallocatedRecords = NULL;
+ }
+
+ //
+ // If we need to create a new record then allocate a record and insert it in both queues
+ // and initialize its other fields
+ //
+
+ if (DeallocatedRecords == NULL) {
+
+ DeallocatedRecords = (PDEALLOCATED_RECORDS)ExAllocateFromPagedLookasideList( &NtfsDeallocatedRecordsLookasideList );
+ InsertTailList( &DataScb->ScbType.Index.RecentlyDeallocatedQueue, &DeallocatedRecords->ScbLinks );
+ InsertTailList( &IrpContext->RecentlyDeallocatedQueue, &DeallocatedRecords->IrpContextLinks );
+ DeallocatedRecords->Scb = DataScb;
+ DeallocatedRecords->NumberOfEntries = DEALLOCATED_RECORD_ENTRIES;
+ DeallocatedRecords->NextFreeEntry = 0;
+ }
+
+ //
+ // At this point deallocated records points to a record that we are to fill in.
+ // We need to check whether there is space to add this entry. Otherwise we need
+ // to allocate a larger deallocated record structure from pool.
+ //
+
+ if (DeallocatedRecords->NextFreeEntry == DeallocatedRecords->NumberOfEntries) {
+
+ PDEALLOCATED_RECORDS NewDeallocatedRecords;
+ ULONG BytesInEntryArray;
+
+ //
+ // Double the number of entries in the current structure and
+ // allocate directly from pool.
+ //
+
+ BytesInEntryArray = 2 * DeallocatedRecords->NumberOfEntries * sizeof( ULONG );
+ NewDeallocatedRecords = NtfsAllocatePool( PagedPool,
+ DEALLOCATED_RECORDS_HEADER_SIZE + BytesInEntryArray );
+ RtlZeroMemory( NewDeallocatedRecords, DEALLOCATED_RECORDS_HEADER_SIZE + BytesInEntryArray );
+
+ //
+ // Initialize the structure by copying the existing structure. Then
+ // update the number of entries field.
+ //
+
+ RtlCopyMemory( NewDeallocatedRecords,
+ DeallocatedRecords,
+ DEALLOCATED_RECORDS_HEADER_SIZE + (BytesInEntryArray / 2) );
+
+ NewDeallocatedRecords->NumberOfEntries = DeallocatedRecords->NumberOfEntries * 2;
+
+ //
+ // Remove the previous structure from the list and insert the new structure.
+ //
+
+ RemoveEntryList( &DeallocatedRecords->ScbLinks );
+ RemoveEntryList( &DeallocatedRecords->IrpContextLinks );
+
+ InsertTailList( &DataScb->ScbType.Index.RecentlyDeallocatedQueue,
+ &NewDeallocatedRecords->ScbLinks );
+ InsertTailList( &IrpContext->RecentlyDeallocatedQueue,
+ &NewDeallocatedRecords->IrpContextLinks );
+
+ //
+ // Deallocate the previous structure and use the new structure in its place.
+ //
+
+ if (DeallocatedRecords->NumberOfEntries == DEALLOCATED_RECORD_ENTRIES) {
+
+ ExFreeToPagedLookasideList( &NtfsDeallocatedRecordsLookasideList, DeallocatedRecords );
+
+ } else {
+
+ NtfsFreePool( DeallocatedRecords );
+ }
+
+ DeallocatedRecords = NewDeallocatedRecords;
+ }
+
+ ASSERT(DeallocatedRecords->NextFreeEntry < DeallocatedRecords->NumberOfEntries);
+
+ DeallocatedRecords->Index[DeallocatedRecords->NextFreeEntry] = Index;
+ DeallocatedRecords->NextFreeEntry += 1;
+ }
+
+ } finally {
+
+ NtfsReleaseScb( IrpContext, DataScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeallocateRecord -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsReserveMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reserves a record, without actually allocating it, so that the
+ record may be allocated later via NtfsAllocateReservedRecord. This support
+ is used, for example, to reserve a record for describing Mft extensions in
+ the current Mft mapping. Only one record may be reserved at a time.
+
+ Note that even though the reserved record number is returned, it may not
+ be used until it is allocated.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume. We update flags in the Vcb on
+ completion of this operation.
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. This parameter is ignored if the bitmap attribute is
+ non resident, in which case we create an scb for the attribute and
+ store a pointer to it in the record allocation context.
+
+Return Value:
+
+ None - We update the Vcb and MftScb during this operation.
+
+--*/
+
+{
+ PSCB DataScb;
+
+ RTL_BITMAP Bitmap;
+
+ BOOLEAN StuffAdded = FALSE;
+ PBCB BitmapBcb = NULL;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReserveMftRecord\n") );
+
+ //
+ // Synchronize by acquiring the data scb exclusive, as an "end resource".
+ // Then use try-finally to insure we free it up.
+ //
+
+ DataScb = Vcb->MftBitmapAllocationContext.DataScb;
+ NtfsAcquireExclusiveScb( IrpContext, DataScb );
+
+ try {
+
+ PSCB BitmapScb;
+ ULONG BitmapClusters;
+ PULONG CurrentBitmapSize;
+ ULONG BitmapSizeInBytes;
+ LONGLONG EndOfIndexOffset;
+ LONGLONG ClusterCount;
+
+ ULONG Index;
+ ULONG BitOffset;
+ PVOID BitmapBuffer;
+ UCHAR BitmapByte = 0;
+
+ ULONG SizeToPin;
+
+ ULONG BitmapCurrentOffset;
+
+ //
+ // See if someone made the bitmap nonresident, and we still think
+ // it is resident. If so, we must uninitialize and insure reinitialization
+ // below.
+ //
+
+ {
+ ULONG BytesPerRecord = Vcb->MftBitmapAllocationContext.BytesPerRecord;
+ ULONG ExtendGranularity = Vcb->MftBitmapAllocationContext.ExtendGranularity;
+
+ if ((Vcb->MftBitmapAllocationContext.BitmapScb == NULL) &&
+ !NtfsIsAttributeResident( NtfsFoundAttribute( BitmapAttribute ))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ &Vcb->MftBitmapAllocationContext );
+
+ Vcb->MftBitmapAllocationContext.CurrentBitmapSize = MAXULONG;
+ }
+
+ //
+ // Reinitialize the record context structure if necessary.
+ //
+
+ if (Vcb->MftBitmapAllocationContext.CurrentBitmapSize == MAXULONG) {
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ ExtendGranularity,
+ &Vcb->MftBitmapAllocationContext );
+ }
+ }
+
+ BitmapScb = Vcb->MftBitmapAllocationContext.BitmapScb;
+ CurrentBitmapSize = &Vcb->MftBitmapAllocationContext.CurrentBitmapSize;
+ BitmapSizeInBytes = *CurrentBitmapSize / 8;
+
+ //
+ // Loop through the entire bitmap. We always start from the first user
+ // file number as our starting point.
+ //
+
+ BitOffset = FIRST_USER_FILE_NUMBER;
+
+ for (BitmapCurrentOffset = 0;
+ BitmapCurrentOffset < BitmapSizeInBytes;
+ BitmapCurrentOffset += PAGE_SIZE) {
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap, or a page, whichever is less.
+ //
+
+ SizeToPin = BitmapSizeInBytes - BitmapCurrentOffset;
+
+ if (SizeToPin > PAGE_SIZE) { SizeToPin = PAGE_SIZE; }
+
+ //
+ // Unpin any Bcb from a previous loop.
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsMapStream( IrpContext,
+ BitmapScb,
+ BitmapCurrentOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // Initialize the bitmap and search for a free bit.
+ //
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, SizeToPin * 8 );
+
+ StuffAdded = NtfsAddDeallocatedRecords( Vcb,
+ DataScb,
+ BitmapCurrentOffset * 8,
+ &Bitmap );
+
+ Index = RtlFindClearBits( &Bitmap, 1, BitOffset );
+
+ //
+ // If we found something, then leave the loop.
+ //
+
+ if (Index != 0xffffffff) {
+
+ //
+ // Remember the byte containing the reserved index.
+ //
+
+ BitmapByte = ((PCHAR) Bitmap.Buffer)[Index / 8];
+
+ break;
+ }
+
+ //
+ // For each subsequent page the page offset is zero.
+ //
+
+ BitOffset = 0;
+ }
+
+ //
+ // Now check if we have located a record that can be allocated, If not then extend
+ // the size of the bitmap by 64 bits.
+ //
+
+ if (Index == 0xffffffff) {
+
+ //
+ // Cleanup from previous loop.
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Calculate the page offset for the next page to pin.
+ //
+
+ BitmapCurrentOffset = BitmapSizeInBytes & ~(PAGE_SIZE - 1);
+
+ //
+ // Calculate the index of next file record to allocate.
+ //
+
+ Index = *CurrentBitmapSize;
+
+ //
+ // Now advance the sizes and calculate the size in bytes to
+ // read.
+ //
+
+ *CurrentBitmapSize += BITMAP_EXTEND_GRANULARITY;
+ Vcb->MftBitmapAllocationContext.NumberOfFreeBits += BITMAP_EXTEND_GRANULARITY;
+
+ //
+ // Calculate the new size of the bitmap in bits and check if we must grow
+ // the allocation.
+ //
+
+ BitmapSizeInBytes = *CurrentBitmapSize / 8;
+
+ //
+ // Check for allocation first.
+ //
+
+ if (BitmapScb->Header.AllocationSize.LowPart < BitmapSizeInBytes) {
+
+ //
+ // Calculate number of clusters to next page boundary, and allocate
+ // that much.
+ //
+
+ ClusterCount = ((BitmapSizeInBytes + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1));
+
+ ClusterCount = LlClustersFromBytes( Vcb,
+ ((ULONG) ClusterCount - BitmapScb->Header.AllocationSize.LowPart) );
+
+ NtfsAddAllocation( IrpContext,
+ BitmapScb->FileObject,
+ BitmapScb,
+ LlClustersFromBytes( Vcb,
+ BitmapScb->Header.AllocationSize.QuadPart ),
+ ClusterCount,
+ FALSE );
+ }
+
+ //
+ // Tell the cache manager about the new file size.
+ //
+
+ BitmapScb->Header.FileSize.QuadPart = BitmapSizeInBytes;
+
+ CcSetFileSizes( BitmapScb->FileObject,
+ (PCC_FILE_SIZES)&BitmapScb->Header.AllocationSize );
+
+ //
+ // Now read the page in and mark it dirty so that any new range will
+ // be zeroed.
+ //
+
+ SizeToPin = BitmapSizeInBytes - BitmapCurrentOffset;
+
+ if (SizeToPin > PAGE_SIZE) { SizeToPin = PAGE_SIZE; }
+
+ NtfsPinStream( IrpContext,
+ BitmapScb,
+ BitmapCurrentOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ CcSetDirtyPinnedData( BitmapBcb, NULL );
+
+ //
+ // Update the ValidDataLength, now that we have read (and possibly
+ // zeroed) the page.
+ //
+
+ BitmapScb->Header.ValidDataLength.LowPart = BitmapSizeInBytes;
+
+ NtfsWriteFileSizes( IrpContext,
+ BitmapScb,
+ &BitmapScb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+
+ } else {
+
+ //
+ // The Index at this point is actually relative, so convert it to absolute
+ // before rejoining common code.
+ //
+
+ Index += (BitmapCurrentOffset * 8);
+ }
+
+ //
+ // We now have an index. There are three possible states for the file
+ // record corresponding to this index within the Mft. They are:
+ //
+ // - File record could lie beyond the current end of the file.
+ // There is nothing to do in this case.
+ //
+ // - File record is part of a hole in the Mft. In that case
+ // we allocate space for it bring it into memory.
+ //
+ // - File record is already within allocated space. There is nothing
+ // to do in that case.
+ //
+ // We store the index as our reserved index and update the Vcb flags. If
+ // the hole filling operation fails then the RestoreScbSnapshots routine
+ // will clear these values.
+ //
+
+ DataScb->ScbType.Mft.ReservedIndex = Index;
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ if (NtfsIsMftIndexInHole( IrpContext, Vcb, Index, NULL )) {
+
+ //
+ // Make sure nothing is left pinned in the bitmap.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Try to fill the hole in the Mft. We will have this routine
+ // raise if unable to fill in the hole.
+ //
+
+ NtfsFillMftHole( IrpContext, Vcb, Index );
+ }
+
+ //
+ // At this point we have the index to reserve and the value of the
+ // byte in the bitmap which contains this bit. We make sure the
+ // Mft includes the allocation for this index and the other
+ // bits within the same byte. This is so we can uninitialize these
+ // file records so chkdsk won't look at stale data.
+ //
+
+ EndOfIndexOffset = LlBytesFromFileRecords( Vcb, (Index + 8) & ~(7));
+
+ //
+ // Now check if we are extending the file. We update the file size and
+ // valid data now.
+ //
+
+ if (EndOfIndexOffset > DataScb->Header.FileSize.QuadPart) {
+
+ ULONG AddedFileRecords;
+ ULONG CurrentIndex;
+
+ //
+ // Check for allocation first.
+ //
+
+ if (EndOfIndexOffset > DataScb->Header.AllocationSize.QuadPart) {
+
+ ClusterCount = ((Index + Vcb->MftBitmapAllocationContext.ExtendGranularity) &
+ ~(Vcb->MftBitmapAllocationContext.ExtendGranularity - 1));
+
+ ClusterCount = LlBytesFromFileRecords( Vcb, (ULONG) ClusterCount );
+
+ ClusterCount = LlClustersFromBytesTruncate( Vcb,
+ ClusterCount - DataScb->Header.AllocationSize.QuadPart );
+
+ NtfsAddAllocation( IrpContext,
+ DataScb->FileObject,
+ DataScb,
+ LlClustersFromBytes( Vcb,
+ DataScb->Header.AllocationSize.QuadPart ),
+ ClusterCount,
+ FALSE );
+ }
+
+ //
+ // Now we have to figure out how many file records we will be
+ // adding.
+ //
+
+ AddedFileRecords = (ULONG) (EndOfIndexOffset - DataScb->Header.FileSize.QuadPart);
+ AddedFileRecords = FileRecordsFromBytes( Vcb, AddedFileRecords );
+
+ DataScb->Header.FileSize.QuadPart = EndOfIndexOffset;
+ DataScb->Header.ValidDataLength.QuadPart = EndOfIndexOffset;
+
+ NtfsWriteFileSizes( IrpContext,
+ DataScb,
+ &DataScb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+
+ //
+ // Tell the cache manager about the new file size.
+ //
+
+ CcSetFileSizes( DataScb->FileObject,
+ (PCC_FILE_SIZES)&DataScb->Header.AllocationSize );
+
+ //
+ // Update our bookeeping to reflect the number of file records
+ // added.
+ //
+
+ DataScb->ScbType.Mft.FreeRecordChange += AddedFileRecords;
+ Vcb->MftFreeRecords += AddedFileRecords;
+
+ //
+ // We now have to go through each of the file records added
+ // and mark it as deallocated.
+ //
+
+ BitmapByte >>= (8 - AddedFileRecords);
+ CurrentIndex = Index;
+
+ while (AddedFileRecords) {
+
+ //
+ // If not allocated then uninitialize it now.
+ //
+
+ if (!FlagOn( BitmapByte, 0x1 )) {
+
+ NtfsInitializeMftHoleRecords( IrpContext,
+ Vcb,
+ CurrentIndex,
+ 1 );
+ }
+
+ BitmapByte >>= 1;
+ CurrentIndex += 1;
+ AddedFileRecords -= 1;
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsReserveMftRecord );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ NtfsReleaseScb( IrpContext, DataScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsReserveMftRecord -> Exit\n") );
+
+ return;
+}
+
+
+ULONG
+NtfsAllocateMftReservedRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates a previously reserved record, and returns its
+ number.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume.
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. This parameter is ignored if the bitmap attribute is
+ non resident, in which case we create an scb for the attribute and
+ store a pointer to it in the record allocation context.
+
+Return Value:
+
+ ULONG - Returns the index of the record just reserved, zero based.
+
+--*/
+
+{
+ PSCB DataScb;
+
+ ULONG ReservedIndex;
+
+ PBCB BitmapBcb = NULL;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateMftReservedRecord\n") );
+
+ //
+ // Synchronize by acquiring the data scb exclusive, as an "end resource".
+ // Then use try-finally to insure we free it up.
+ //
+
+ DataScb = Vcb->MftBitmapAllocationContext.DataScb;
+ NtfsAcquireExclusiveScb( IrpContext, DataScb );
+
+ try {
+
+ PSCB BitmapScb;
+ ULONG RelativeIndex;
+ ULONG SizeToPin;
+
+ RTL_BITMAP Bitmap;
+ PVOID BitmapBuffer;
+
+ BITMAP_RANGE BitmapRange;
+ ULONG BitmapCurrentOffset = 0;
+
+ //
+ // If we are going to allocate file record 15 then do so and set the
+ // flags in the IrpContext and Vcb.
+ //
+
+ if (!FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_15_USED )) {
+
+ SetFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_15_USED );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_15_USED );
+
+ try_return( ReservedIndex = FIRST_USER_FILE_NUMBER - 1 );
+ }
+
+ //
+ // See if someone made the bitmap nonresident, and we still think
+ // it is resident. If so, we must uninitialize and insure reinitialization
+ // below.
+ //
+
+ {
+ ULONG BytesPerRecord = Vcb->MftBitmapAllocationContext.BytesPerRecord;
+ ULONG ExtendGranularity = Vcb->MftBitmapAllocationContext.ExtendGranularity;
+
+ if ((Vcb->MftBitmapAllocationContext.BitmapScb == NULL) &&
+ !NtfsIsAttributeResident( NtfsFoundAttribute( BitmapAttribute ))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ &Vcb->MftBitmapAllocationContext );
+
+ Vcb->MftBitmapAllocationContext.CurrentBitmapSize = MAXULONG;
+ }
+
+ //
+ // Reinitialize the record context structure if necessary.
+ //
+
+ if (Vcb->MftBitmapAllocationContext.CurrentBitmapSize == MAXULONG) {
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ ExtendGranularity,
+ &Vcb->MftBitmapAllocationContext );
+ }
+ }
+
+ BitmapScb = Vcb->MftBitmapAllocationContext.BitmapScb;
+ ReservedIndex = DataScb->ScbType.Mft.ReservedIndex;
+
+ //
+ // Find the start of the page containing the reserved index.
+ //
+
+ BitmapCurrentOffset = (ReservedIndex / 8) & ~(PAGE_SIZE - 1);
+
+ RelativeIndex = ReservedIndex & (BITS_PER_PAGE - 1);
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap, or a page, whichever is less.
+ //
+
+ SizeToPin = (Vcb->MftBitmapAllocationContext.CurrentBitmapSize / 8)
+ - BitmapCurrentOffset;
+
+ if (SizeToPin > PAGE_SIZE) { SizeToPin = PAGE_SIZE; }
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsPinStream( IrpContext,
+ BitmapScb,
+ BitmapCurrentOffset,
+ SizeToPin,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // Initialize the bitmap.
+ //
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, SizeToPin * 8 );
+
+ //
+ // Now log this change as well.
+ //
+
+ BitmapRange.BitMapOffset = RelativeIndex;
+ BitmapRange.NumberOfBits = 1;
+
+ (VOID) NtfsWriteLog( IrpContext,
+ BitmapScb,
+ BitmapBcb,
+ SetBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ ClearBitsInNonresidentBitMap,
+ &BitmapRange,
+ sizeof(BITMAP_RANGE),
+ BitmapCurrentOffset,
+ 0,
+ 0,
+ Bitmap.SizeOfBitMap >> 3 );
+
+ NtfsRestartSetBitsInBitMap( IrpContext, &Bitmap, RelativeIndex, 1 );
+
+ //
+ // Now that we've located an index we can subtract the number of free bits in the bitmap
+ //
+
+ Vcb->MftBitmapAllocationContext.NumberOfFreeBits -= 1;
+
+ //
+ // If we didn't extend the file then we have used a free file record in the file.
+ // Update our bookeeping count for free file records.
+ //
+
+ DataScb->ScbType.Mft.FreeRecordChange -= 1;
+ Vcb->MftFreeRecords -= 1;
+
+ //
+ // Now determine if we extended the index of the last set bit
+ //
+
+ if (ReservedIndex > (ULONG)Vcb->MftBitmapAllocationContext.IndexOfLastSetBit) {
+
+ Vcb->MftBitmapAllocationContext.IndexOfLastSetBit = ReservedIndex;
+ }
+
+ //
+ // Clear the fields that indicate we have a reserved index.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ DataScb->ScbType.Mft.ReservedIndex = 0;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsAllocateMftReserveRecord );
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ NtfsReleaseScb( IrpContext, DataScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateMftReserveRecord -> %08lx\n", ReservedIndex) );
+
+ return ReservedIndex;
+}
+
+
+VOID
+NtfsDeallocateRecordsComplete (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes recently deallocated record information from
+ the Scb structures based on the input irp context.
+
+Arguments:
+
+ IrpContext - Supplies the Queue of recently deallocate records
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PDEALLOCATED_RECORDS DeallocatedRecords;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeallocateRecordsComplete\n") );
+
+ //
+ // Now while the irp context's recently deallocated queue is not empty
+ // we will grap the first entry off the queue, remove it from both
+ // the scb and irp context queue, and free the record
+ //
+
+ while (!IsListEmpty( &IrpContext->RecentlyDeallocatedQueue )) {
+
+ DeallocatedRecords = CONTAINING_RECORD( IrpContext->RecentlyDeallocatedQueue.Flink,
+ DEALLOCATED_RECORDS,
+ IrpContextLinks );
+
+ RemoveEntryList( &DeallocatedRecords->ScbLinks );
+
+ //
+ // Now remove the record from the irp context queue and deallocate the
+ // record
+ //
+
+ RemoveEntryList( &DeallocatedRecords->IrpContextLinks );
+
+ //
+ // If this record is the default size then return it to our private list.
+ // Otherwise deallocate it to pool.
+ //
+
+ if (DeallocatedRecords->NumberOfEntries == DEALLOCATED_RECORD_ENTRIES) {
+
+ ExFreeToPagedLookasideList( &NtfsDeallocatedRecordsLookasideList, DeallocatedRecords );
+
+ } else {
+
+ NtfsFreePool( DeallocatedRecords );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeallocateRecordsComplete -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsIsRecordAllocated (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Index,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to query if a record is currently allocated for
+ the specified record allocation context.
+
+Arguments:
+
+ RecordAllocationContext - Supplies the record allocation context used
+ in this operation
+
+ Index - Supplies the index of the record being queried, zero based.
+
+ BitmapAttribute - Supplies the enumeration context for the bitmap
+ attribute. This parameter is ignored if the bitmap attribute is
+ non resident, in which case we create an scb for the attribute and
+ store a pointer to it in the record allocation context.
+
+Return Value:
+
+ BOOLEAN - TRUE if the record is currently allocated and FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN Results;
+
+ PSCB DataScb;
+ PSCB BitmapScb;
+ ULONG CurrentBitmapSize;
+
+ PVCB Vcb;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb = NULL;
+
+ PATTRIBUTE_RECORD_HEADER AttributeRecordHeader;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsRecordAllocated\n") );
+
+ //
+ // Synchronize by acquiring the data scb exclusive, as an "end resource".
+ // Then use try-finally to insure we free it up.
+ //
+
+ DataScb = RecordAllocationContext->DataScb;
+ NtfsAcquireExclusiveScb( IrpContext, DataScb );
+
+ try {
+
+ Vcb = DataScb->Fcb->Vcb;
+
+ //
+ // See if someone made the bitmap nonresident, and we still think
+ // it is resident. If so, we must uninitialize and insure reinitialization
+ // below.
+ //
+
+ BitmapScb = RecordAllocationContext->BitmapScb;
+
+ {
+ ULONG ExtendGranularity;
+ ULONG BytesPerRecord;
+ ULONG TruncateGranularity;
+
+ //
+ // Remember the current values in the record context structure.
+ //
+
+ BytesPerRecord = RecordAllocationContext->BytesPerRecord;
+ TruncateGranularity = RecordAllocationContext->TruncateGranularity;
+ ExtendGranularity = RecordAllocationContext->ExtendGranularity;
+
+ if ((BitmapScb == NULL) && !NtfsIsAttributeResident(NtfsFoundAttribute(BitmapAttribute))) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ RecordAllocationContext );
+
+ RecordAllocationContext->CurrentBitmapSize = MAXULONG;
+ }
+
+ //
+ // Reinitialize the record context structure if necessary.
+ //
+
+ if (RecordAllocationContext->CurrentBitmapSize == MAXULONG) {
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ DataScb,
+ BitmapAttribute,
+ BytesPerRecord,
+ ExtendGranularity,
+ TruncateGranularity,
+ RecordAllocationContext );
+ }
+ }
+
+ BitmapScb = RecordAllocationContext->BitmapScb;
+ CurrentBitmapSize = RecordAllocationContext->CurrentBitmapSize;
+
+ //
+ // We will do different operations based on whether the bitmap is resident or nonresident
+ // The first case will handle the resident bitmap
+ //
+
+ if (BitmapScb == NULL) {
+
+ UCHAR NewByte;
+
+ //
+ // Initialize the local bitmap
+ //
+
+ AttributeRecordHeader = NtfsFoundAttribute( BitmapAttribute );
+
+ RtlInitializeBitMap( &Bitmap,
+ (PULONG)NtfsAttributeValue( AttributeRecordHeader ),
+ CurrentBitmapSize );
+
+ //
+ // And check if the indcated bit is Set. If it is set then the record is allocated.
+ //
+
+ NewByte = ((PUCHAR)Bitmap.Buffer)[ Index / 8 ];
+
+ Results = BooleanFlagOn( NewByte, BitMask[Index % 8] );
+
+ } else {
+
+ PVOID BitmapBuffer;
+ ULONG SizeToMap;
+ ULONG RelativeIndex;
+ ULONG BitmapCurrentOffset;
+
+ //
+ // Calculate Vcn and relative index of the bit we will deallocate,
+ // from the nearest page boundary.
+ //
+
+ BitmapCurrentOffset = (Index / 8) & ~(PAGE_SIZE - 1);
+ RelativeIndex = Index & (BITS_PER_PAGE - 1);
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap.
+ //
+
+ SizeToMap = CurrentBitmapSize / 8 - BitmapCurrentOffset;
+
+ if (SizeToMap > PAGE_SIZE) { SizeToMap = PAGE_SIZE; }
+
+ NtfsMapStream( IrpContext,
+ BitmapScb,
+ BitmapCurrentOffset,
+ SizeToMap,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap( &Bitmap, BitmapBuffer, SizeToMap * 8 );
+
+ //
+ // Now check if the indicated bit is set. If it is set then the record is allocated.
+ // no idea whether the update is applied or not.
+ //
+
+ Results = RtlAreBitsSet(&Bitmap, RelativeIndex, 1);
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsIsRecordDeallocated );
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ NtfsReleaseScb( IrpContext, DataScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsIsRecordAllocated -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+VOID
+NtfsScanMftBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called during mount to initialize the values related to
+ the Mft in the Vcb. These include the number of free records and hole
+ records. Also whether we have already used file record 15. We also scan
+ the Mft to check whether there is any excess mapping.
+
+Arguments:
+
+ Vcb - Supplies the Vcb for the volume.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PBCB BitmapBcb = NULL;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsScanMftBitmap...\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ ULONG SizeToMap;
+ ULONG FileRecords;
+ ULONG RemainingRecords;
+ ULONG BitmapCurrentOffset;
+ ULONG BitmapBytesToRead;
+ PUCHAR BitmapBuffer;
+ UCHAR NextByte;
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG Clusters;
+
+ //
+ // Start by walking through the file records for the Mft
+ // checking for excess mapping.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Vcb->MftScb, NULL, &AttrContext );
+
+ //
+ // We don't care about the first one. Let's find the rest of them.
+ //
+
+ while (NtfsLookupNextAttributeForScb( IrpContext,
+ Vcb->MftScb,
+ &AttrContext )) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ SetFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_15_USED );
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+
+ //
+ // Now check for the free space.
+ //
+
+ if (FileRecord->BytesAvailable - FileRecord->FirstFreeByte < Vcb->MftReserved) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ break;
+ }
+ }
+
+ //
+ // We now want to find the number of free records within the Mft
+ // bitmap. We need to figure out how many file records are in
+ // the Mft and then map the necessary bytes in the bitmap and
+ // find the count of set bits. We will round the bitmap length
+ // down to a byte boundary and then look at the last byte
+ // separately.
+ //
+
+ FileRecords = (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart );
+
+ //
+ // Remember how many file records are in the last byte of the bitmap.
+ //
+
+ RemainingRecords = FileRecords & 7;
+
+ FileRecords &= ~(7);
+ BitmapBytesToRead = FileRecords / 8;
+
+ for (BitmapCurrentOffset = 0;
+ BitmapCurrentOffset < BitmapBytesToRead;
+ BitmapCurrentOffset += PAGE_SIZE) {
+
+ RTL_BITMAP Bitmap;
+ ULONG MapAdjust;
+
+ //
+ // Calculate the size to read from this point to the end of
+ // bitmap, or a page, whichever is less.
+ //
+
+ SizeToMap = BitmapBytesToRead - BitmapCurrentOffset;
+
+ if (SizeToMap > PAGE_SIZE) { SizeToMap = PAGE_SIZE; }
+
+ //
+ // If we aren't pinning a full page and have some bits
+ // in the next byte then pin an extra byte.
+ //
+
+ if ((SizeToMap != PAGE_SIZE) && (RemainingRecords != 0)) {
+
+ MapAdjust = 1;
+
+ } else {
+
+ MapAdjust = 0;
+ }
+
+ //
+ // Unpin any Bcb from a previous loop.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftBitmapScb,
+ BitmapCurrentOffset,
+ SizeToMap + MapAdjust,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // Initialize the bitmap and search for a free bit.
+ //
+
+ RtlInitializeBitMap( &Bitmap, (PULONG) BitmapBuffer, SizeToMap * 8 );
+
+ Vcb->MftFreeRecords += RtlNumberOfClearBits( &Bitmap );
+ }
+
+ //
+ // If there are some remaining bits in the next byte then process
+ // them now.
+ //
+
+ if (RemainingRecords) {
+
+ PVOID RangePtr;
+ ULONG Index;
+
+ //
+ // Hopefully this byte is on the same page. Otherwise we will
+ // free this page and go to the next. In this case the Vcn will
+ // have the correct value because we walked past the end of the
+ // current file records already.
+ //
+
+ if (SizeToMap == PAGE_SIZE) {
+
+ //
+ // Unpin any Bcb from a previous loop.
+ //
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Read the desired bitmap page.
+ //
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftBitmapAllocationContext.BitmapScb,
+ BitmapCurrentOffset,
+ 1,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ //
+ // Set this to the byte prior to the last byte. This will
+ // set this to the same state as if on the same page.
+ //
+
+ SizeToMap = 0;
+ }
+
+ //
+ // We look at the next byte in the page and figure out how
+ // many bits are set.
+ //
+
+ NextByte = *((PUCHAR) Add2Ptr( BitmapBuffer, SizeToMap + 1 ));
+
+ while (RemainingRecords--) {
+
+ if (!FlagOn( NextByte, 0x01 )) {
+
+ Vcb->MftFreeRecords += 1;
+ }
+
+ NextByte >>= 1;
+ }
+
+ //
+ // We are now ready to look for holes within the Mft. We will look
+ // through the Mcb for the Mft looking for holes. The holes must
+ // always be an integral number of file records.
+ //
+
+ RangePtr = NULL;
+ Index = 0;
+
+ while (NtfsGetSequentialMcbEntry( &Vcb->MftScb->Mcb,
+ &RangePtr,
+ Index,
+ &Vcn,
+ &Lcn,
+ &Clusters )) {
+
+ //
+ // Look for a hole and count the clusters.
+ //
+
+ if (Lcn == UNUSED_LCN) {
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ Vcb->MftHoleRecords += (((ULONG)Clusters) >> Vcb->MftToClusterShift);
+
+ } else {
+
+ Vcb->MftHoleRecords += (((ULONG)Clusters) << Vcb->MftToClusterShift);
+ }
+ }
+
+ Index += 1;
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsScanMftBitmap );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ DebugTrace( -1, Dbg, ("NtfsScanMftBitmap...\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsAddDeallocatedRecords (
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN ULONG StartingIndexOfBitmap,
+ IN OUT PRTL_BITMAP Bitmap
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will modify the input bitmap by removing from it
+ any records that are in the recently deallocated queue of the scb.
+ If we do add stuff then we will not modify the bitmap buffer itself but
+ will allocate a new copy for the bitmap.
+
+Arguments:
+
+ Vcb - Supplies the Vcb for the volume
+
+ Scb - Supplies the Scb used in this operation
+
+ StartingIndexOfBitmap - Supplies the base index to use to bias the bitmap
+
+ Bitmap - Supplies the bitmap being modified
+
+Return Value:
+
+ BOOLEAN - TRUE if the bitmap has been modified and FALSE
+ otherwise.
+
+--*/
+
+{
+ BOOLEAN Results;
+ ULONG EndingIndexOfBitmap;
+ PLIST_ENTRY Links;
+ PDEALLOCATED_RECORDS DeallocatedRecords;
+ ULONG i;
+ ULONG Index;
+ PVOID NewBuffer;
+ ULONG SizeOfBitmapInBytes;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddDeallocatedRecords...\n") );
+
+ //
+ // Until shown otherwise we will assume that we haven't updated anything
+ //
+
+ Results = FALSE;
+
+ //
+ // Calculate the last index in the bitmap
+ //
+
+ EndingIndexOfBitmap = StartingIndexOfBitmap + Bitmap->SizeOfBitMap - 1;
+ SizeOfBitmapInBytes = (Bitmap->SizeOfBitMap + 7) / 8;
+
+ //
+ // Check if we need to bias the bitmap with the reserved index
+ //
+
+ if ((Scb == Vcb->MftScb) &&
+ FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED ) &&
+ (StartingIndexOfBitmap <= Scb->ScbType.Mft.ReservedIndex) &&
+ (Scb->ScbType.Mft.ReservedIndex <= EndingIndexOfBitmap)) {
+
+ //
+ // The index is a hit so now bias the index with the start of the bitmap
+ // and allocate an extra buffer to hold the bitmap
+ //
+
+ Index = Scb->ScbType.Mft.ReservedIndex - StartingIndexOfBitmap;
+
+ NewBuffer = NtfsAllocatePool(PagedPool, SizeOfBitmapInBytes );
+ RtlCopyMemory( NewBuffer, Bitmap->Buffer, SizeOfBitmapInBytes );
+ Bitmap->Buffer = NewBuffer;
+
+ Results = TRUE;
+
+ //
+ // And now set the bits in the bitmap to indicate that the record
+ // cannot be reallocated yet. Also set the other bits within the
+ // same byte so we can put all of the file records for the Mft
+ // within the same pages of the Mft.
+ //
+
+ ((PUCHAR) Bitmap->Buffer)[ Index / 8 ] = 0xff;
+ }
+
+ //
+ // Scan through the recently deallocated queue looking for any indexes that
+ // we need to modify
+ //
+
+ for (Links = Scb->ScbType.Index.RecentlyDeallocatedQueue.Flink;
+ Links != &Scb->ScbType.Index.RecentlyDeallocatedQueue;
+ Links = Links->Flink) {
+
+ DeallocatedRecords = CONTAINING_RECORD( Links, DEALLOCATED_RECORDS, ScbLinks );
+
+ //
+ // For every index in the record check if the index is within the range
+ // of the bitmap we are working with
+ //
+
+ for (i = 0; i < DeallocatedRecords->NextFreeEntry; i += 1) {
+
+ if ((StartingIndexOfBitmap <= DeallocatedRecords->Index[i]) &&
+ (DeallocatedRecords->Index[i] <= EndingIndexOfBitmap)) {
+
+ //
+ // The index is a hit so now bias the index with the start of the bitmap
+ // and check if we need to allocate an extra buffer to hold the bitmap
+ //
+
+ Index = DeallocatedRecords->Index[i] - StartingIndexOfBitmap;
+
+ if (!Results) {
+
+ NewBuffer = NtfsAllocatePool(PagedPool, SizeOfBitmapInBytes );
+ RtlCopyMemory( NewBuffer, Bitmap->Buffer, SizeOfBitmapInBytes );
+ Bitmap->Buffer = NewBuffer;
+
+ Results = TRUE;
+ }
+
+ //
+ // And now set the bit in the bitmap to indicate that the record
+ // cannot be reallocated yet. It's possible that the bit is
+ // already set if we have aborted a transaction which then
+ // restores the bit.
+ //
+
+ SetFlag( ((PUCHAR)Bitmap->Buffer)[ Index / 8 ], BitMask[Index % 8] );
+ }
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsAddDeallocatedRecords -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsReduceMftZone (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when it appears that there is no disk space left on the
+ disk except the Mft zone. We will try to reduce the zone to make space
+ available for user files.
+
+Arguments:
+
+ Vcb - Supplies the Vcb for the volume
+
+Return Value:
+
+ BOOLEAN - TRUE if the Mft zone was shrunk. FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN ReduceMft = FALSE;
+
+ LONGLONG FreeClusters;
+ LONGLONG TargetFreeClusters;
+ LONGLONG PrevFreeClusters;
+
+ ULONG CurrentOffset;
+
+ LCN Lcn;
+ LCN StartingLcn;
+ LCN SplitLcn;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb = NULL;
+
+ PAGED_CODE();
+
+ //
+ // Nothing to do if disk is almost empty.
+ //
+
+ if (Vcb->FreeClusters < (4 * MFT_EXTEND_GRANULARITY)) {
+
+ return FALSE;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // We want to find the number of free clusters in the Mft zone and
+ // return half of them to the pool of clusters for users files.
+ //
+
+ FreeClusters = 0;
+
+ for (Lcn = Vcb->MftZoneStart;
+ Lcn < Vcb->MftZoneEnd;
+ Lcn = Lcn + Bitmap.SizeOfBitMap) {
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &StartingLcn, &Bitmap, &BitmapBcb );
+
+ if ((StartingLcn + Bitmap.SizeOfBitMap) > Vcb->MftZoneEnd) {
+
+ Bitmap.SizeOfBitMap = (ULONG) (Vcb->MftZoneEnd - StartingLcn);
+ }
+
+ if (StartingLcn != Lcn) {
+
+ Bitmap.SizeOfBitMap -= (ULONG) (Lcn - StartingLcn);
+ Bitmap.Buffer = Add2Ptr( Bitmap.Buffer,
+ (ULONG) (Lcn - StartingLcn) / 8 );
+ }
+
+ FreeClusters += RtlNumberOfClearBits( &Bitmap );
+ }
+
+ //
+ // If we are below our threshold then don't do the split.
+ //
+
+ if (FreeClusters < (4 * MFT_EXTEND_GRANULARITY)) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Now we want to calculate 1/2 of this number of clusters and set the
+ // zone end to this point.
+ //
+
+ TargetFreeClusters = Int64ShraMod32( FreeClusters, 1 );
+
+ //
+ // Now look for the page which contains the split point.
+ //
+
+ FreeClusters = 0;
+
+ for (Lcn = Vcb->MftZoneStart;
+ Lcn < Vcb->MftZoneEnd;
+ Lcn = Lcn + Bitmap.SizeOfBitMap) {
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &StartingLcn, &Bitmap, &BitmapBcb );
+
+ if ((StartingLcn + Bitmap.SizeOfBitMap) > Vcb->MftZoneEnd) {
+
+ Bitmap.SizeOfBitMap = (ULONG) (Vcb->MftZoneEnd - StartingLcn);
+ }
+
+ if (StartingLcn != Lcn) {
+
+ Bitmap.SizeOfBitMap -= (ULONG) (Lcn - StartingLcn);
+ Bitmap.Buffer = Add2Ptr( Bitmap.Buffer,
+ (ULONG) (Lcn - StartingLcn) / 8 );
+ }
+
+ PrevFreeClusters = FreeClusters;
+ FreeClusters += RtlNumberOfClearBits( &Bitmap );
+
+ //
+ // Check if we found the page containing the split point.
+ //
+
+ if (FreeClusters >= TargetFreeClusters) {
+
+ CurrentOffset = 0;
+
+ while (TRUE) {
+
+ if (!RtlCheckBit( &Bitmap, CurrentOffset )) {
+
+ PrevFreeClusters += 1;
+
+ if (PrevFreeClusters == TargetFreeClusters) {
+
+ break;
+ }
+ }
+
+ CurrentOffset += 1;
+ }
+
+ SplitLcn = Lcn + CurrentOffset;
+ ReduceMft = TRUE;
+ break;
+ }
+ }
+
+ //
+ // If we are to reduce the Mft zone then set the split point and exit.
+ // We always round the split point up to an eight cluster boundary so
+ // that the bitmap for the zone fills the last byte.
+ //
+
+ if (ReduceMft) {
+
+ Vcb->MftZoneEnd = (SplitLcn + 0x1f) & ~0x1f;
+ SetFlag( Vcb->VcbState, VCB_STATE_REDUCED_MFT );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ NtfsUnpinBcb( &BitmapBcb );
+ }
+
+ return ReduceMft;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsCheckRecordStackUsage (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called in the record package prior to adding allocation
+ to either a data stream or bitmap stream. The purpose is to verify
+ that there is room on the stack to perform a log file full in the
+ AddAllocation operation. This routine will check the stack space and
+ the available log file space and raise LOG_FILE_FULL defensively if
+ both of these reach a critical threshold.
+
+Arguments:
+
+Return Value:
+
+ None - this routine will raise if necessary.
+
+--*/
+
+{
+ LOG_FILE_INFORMATION LogFileInfo;
+ ULONG InfoSize;
+ LONGLONG RemainingLogFile;
+
+ PAGED_CODE();
+
+ //
+ // Check the stack usage first.
+ //
+
+ if (IoGetRemainingStackSize() < OVERFLOW_RECORD_THRESHHOLD) {
+
+ //
+ // Now check the log file space.
+ //
+
+ InfoSize = sizeof( LOG_FILE_INFORMATION );
+
+ RtlZeroMemory( &LogFileInfo, InfoSize );
+
+ LfsReadLogFileInformation( IrpContext->Vcb->LogHandle,
+ &LogFileInfo,
+ &InfoSize );
+
+ //
+ // Check that 1/4 of the log file is available.
+ //
+
+ if (InfoSize != 0) {
+
+ RemainingLogFile = LogFileInfo.CurrentAvailable - LogFileInfo.TotalUndoCommitment;
+
+ if ((RemainingLogFile <= 0) ||
+ (RemainingLogFile < Int64ShraMod32(LogFileInfo.TotalAvailable, 2))) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+ }
+ }
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsRunIsClear (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN StartingLcn,
+ IN LONGLONG RunLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine verifies that a group of clusters are unallocated.
+
+Arguments:
+
+ StartingLcn - Supplies the start of the cluster run
+
+ RunLength - Supplies the length of the cluster run
+
+Return Value:
+
+ STATUS_SUCCESS if run is unallocated
+
+--*/
+{
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb = NULL;
+ BOOLEAN StuffAdded = FALSE;
+ LONGLONG BitOffset;
+ LONGLONG BitCount;
+ LCN BaseLcn;
+ LCN Lcn = StartingLcn;
+ LONGLONG ValidDataLength;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRunIsClear\n") );
+
+ ValidDataLength = Vcb->BitmapScb->Header.ValidDataLength.QuadPart;
+
+ try {
+
+ //
+ // Ensure that StartingLcn is not past the length of the bitmap.
+ //
+
+ if (StartingLcn > ValidDataLength * 8) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ while (RunLength > 0){
+
+ //
+ // Access the next page of bitmap and update it
+ //
+
+ NtfsMapPageInBitmap(IrpContext, Vcb, Lcn, &BaseLcn, &Bitmap, &BitmapBcb);
+
+ StuffAdded = NtfsAddRecentlyDeallocated(Vcb, BaseLcn, &Bitmap);
+
+ //
+ // Get offset into this page and bits to end of this page
+ //
+
+ BitOffset = Lcn - BaseLcn;
+ BitCount = Bitmap.SizeOfBitMap - BitOffset;
+
+ //
+ // Adjust for bits to end of page
+ //
+
+ if (BitCount > RunLength){
+
+ BitCount = RunLength;
+ }
+
+ //
+ // If any bit is set get out
+ //
+
+ if (!RtlAreBitsClear( &Bitmap, (ULONG)BitOffset, (ULONG)BitCount)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_ALREADY_COMMITTED, NULL, NULL );
+ }
+
+ //
+ // Free up resources
+ //
+
+ if(StuffAdded) { NtfsFreePool(Bitmap.Buffer); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb(&BitmapBcb);
+
+ //
+ // Decrease remaining bits by amount checked in this page and move Lcn to beginning
+ // of next page
+ //
+
+ RunLength = RunLength - BitCount;
+ Lcn = BaseLcn + Bitmap.SizeOfBitMap;
+ }
+
+ } finally {
+
+ DebugUnwind(NtfsRunIsClear);
+
+ //
+ // Free up resources
+ //
+
+ if(StuffAdded){ NtfsFreePool(Bitmap.Buffer); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb(&BitmapBcb);
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsRunIsClear -> VOID\n") );
+
+ return;
+}
+
+
+
diff --git a/private/ntos/cntfs/cachesup.c b/private/ntos/cntfs/cachesup.c
new file mode 100644
index 000000000..2c9c4811a
--- /dev/null
+++ b/private/ntos/cntfs/cachesup.c
@@ -0,0 +1,1481 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ CacheSup.c
+
+Abstract:
+
+ This module implements the cache management routines for Ntfs
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_CACHESUP)
+
+#define MAX_ZERO_THRESHOLD (0x00400000)
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CACHESUP)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCompleteMdl)
+#pragma alloc_text(PAGE, NtfsCreateInternalStreamCommon)
+#pragma alloc_text(PAGE, NtfsDeleteInternalAttributeStream)
+#pragma alloc_text(PAGE, NtfsMapStream)
+#pragma alloc_text(PAGE, NtfsPinMappedData)
+#pragma alloc_text(PAGE, NtfsPinStream)
+#pragma alloc_text(PAGE, NtfsPreparePinWriteStream)
+#pragma alloc_text(PAGE, NtfsZeroData)
+#ifdef _CAIRO_
+#pragma alloc_text(PAGE, NtOfsPutData)
+#endif _CAIRO_
+#endif
+
+
+VOID
+NtfsCreateInternalStreamCommon (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN UpdateScb,
+ IN BOOLEAN CompressedStream
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to prepare a stream file associated with a
+ particular attribute of a file. On return, the Scb for the attribute
+ will have an associated stream file object. On return, this
+ stream file will have been initialized through the cache manager.
+
+ TEMPCODE The following assumptions have been made or if open issue,
+ still unresolved.
+
+ - Assume. The call to create Scb will initialize the Mcb for
+ the non-resident case.
+
+ - Assume. When this file is created I increment the open count
+ but not the unclean count for this Scb. When we are done with
+ the stream file, we should uninitialize it and dereference it.
+ We also set the file object pointer to NULL. Close will then
+ do the correct thing.
+
+ - Assume. Since this call is likely to be followed shortly by
+ either a read or write, the cache map is initialized here.
+
+Arguments:
+
+ Scb - Supplies the address to store the Scb for this attribute and
+ stream file. This will exist on return from this function.
+
+ UpdateScb - Indicates if the caller wants to update the Scb from the
+ attribute.
+
+ CompressedStream - Supplies TRUE if caller wishes to create the
+ compressed stream.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+
+ CC_FILE_SIZES CcFileSizes;
+ PFILE_OBJECT CallersFileObject;
+ PFILE_OBJECT *FileObjectPtr = &Scb->FileObject;
+ PFILE_OBJECT UnwindStreamFile = NULL;
+
+ BOOLEAN UnwindInitializeCacheMap = FALSE;
+ BOOLEAN DecrementScbCleanup = FALSE;
+
+ BOOLEAN AcquiredFastMutex = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateInternalAttributeStream\n") );
+ DebugTrace( 0, Dbg, ("Scb -> %08lx\n", Scb) );
+
+ //
+ // Change FileObjectPtr if he wants the compressed stream
+ //
+
+ if (CompressedStream) {
+ FileObjectPtr = &Scb->Header.FileObjectC;
+ }
+
+ //
+ // If there is no file object, we create one and initialize
+ // it.
+ //
+
+ if (*FileObjectPtr == NULL) {
+
+ //
+ // Only acquire the mutex if we don't have the file exclusive.
+ //
+
+ if (!NtfsIsExclusiveScb( Scb )) {
+
+ ExAcquireFastMutexUnsafe( &StreamFileCreationFastMutex );
+ AcquiredFastMutex = TRUE;
+ }
+
+ try {
+
+ //
+ // Someone could have gotten there first.
+ //
+
+ if (*FileObjectPtr == NULL) {
+
+ UnwindStreamFile = IoCreateStreamFileObject( NULL, Scb->Vcb->Vpb->RealDevice );
+
+ //
+ // Propagate any flags from the caller's FileObject to our
+ // stream file that the Cache Manager may look at, so we do not
+ // miss hints like sequential only or temporary.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE) &&
+ (IrpContext->OriginatingIrp != NULL) &&
+ (CallersFileObject = IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject)) {
+
+ SetFlag( UnwindStreamFile->Flags,
+ CallersFileObject->Flags & NTFS_FO_PROPAGATE_TO_STREAM );
+ }
+
+ UnwindStreamFile->SectionObjectPointer = &Scb->NonpagedScb->SegmentObject;
+
+ //
+ // For a compressed stream, we have to use separate section
+ // object pointers.
+ //
+
+ if (CompressedStream) {
+ UnwindStreamFile->SectionObjectPointer = &Scb->NonpagedScb->SegmentObjectC;
+
+ }
+
+ //
+ // If we have created the stream file, we set it to type
+ // 'StreamFileOpen'
+ //
+
+ NtfsSetFileObject( UnwindStreamFile,
+ StreamFileOpen,
+ Scb,
+ NULL );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( UnwindStreamFile->Flags, FO_TEMPORARY_FILE );
+ }
+
+ //
+ // Initialize the fields of the file object.
+ //
+
+ UnwindStreamFile->ReadAccess = TRUE;
+ UnwindStreamFile->WriteAccess = TRUE;
+ UnwindStreamFile->DeleteAccess = TRUE;
+
+ //
+ // Increment the open count and set the section
+ // object pointers. We don't set the unclean count as the
+ // cleanup call has already occurred.
+ //
+
+ NtfsIncrementCloseCounts( Scb, TRUE, FALSE );
+
+ //
+ // Increment the cleanup count in this Scb to prevent the
+ // Scb from going away if the cache call fails.
+ //
+
+ InterlockedIncrement( &Scb->CleanupCount );
+ DecrementScbCleanup = TRUE;
+
+ //
+ // If the Scb header has not been initialized, we will do so now.
+ //
+
+ if (UpdateScb
+ && !FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // We also want to set the MODIFIED_NO_WRITE flag so that
+ // we will tell the Cache Manager that we do not want to allow
+ // modified page writing, and so that we will tell the FT driver to
+ // serialize writes. Set this stream to MODIFIED_NO_WRITE if
+ //
+ // 1 - Any stream with with non-$DATA type code (or)
+ // 2 - This stream is USA protected (or)
+ // 3 - This is a stream of compressed data (or)
+ // 4 - This stream is the volume bitmap stream (or)
+ // 5 - A restart is in progress (or)
+ //
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ if ((Scb->AttributeTypeCode != $DATA) ||
+ FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT) ||
+ (Scb == Vcb->BitmapScb) ||
+ FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
+
+ } else if (!CompressedStream) {
+
+ SetFlag(Scb->Header.Flags2, FSRTL_FLAG2_DO_MODIFIED_WRITE);
+ }
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // Check if we need to initialize the cache map for the stream file.
+ // The size of the section to map will be the current allocation
+ // for the stream file.
+ //
+
+ if (UnwindStreamFile->PrivateCacheMap == NULL) {
+
+ BOOLEAN PinAccess;
+
+ CcFileSizes = *(PCC_FILE_SIZES)&Scb->Header.AllocationSize;
+
+ //
+ // If this is a stream with Usa protection, we want to tell
+ // the Cache Manager we do not need to get any valid data
+ // callbacks. We do this by having xxMax sitting in
+ // ValidDataLength for the call, but we have to restore the
+ // correct value afterwards.
+ //
+ // We also do this for all of the stream files created during
+ // restart. This has the effect of telling Mm to always
+ // fault the page in from disk. Don't generate a zero page if
+ // push up the file size during restart.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_USA_PRESENT ) ||
+ (Scb == Vcb->BitmapScb) ||
+ (Scb->AttributeTypeCode == $BITMAP) ||
+ FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
+
+ CcFileSizes.ValidDataLength.QuadPart = MAXLONGLONG;
+ }
+
+ PinAccess =
+ (BOOLEAN) (Scb->AttributeTypeCode != $DATA ||
+ FlagOn( Scb->Fcb->FcbState, FCB_STATE_PAGING_FILE ) ||
+ NtfsSegmentNumber( &Scb->Fcb->FileReference ) < FIRST_USER_FILE_NUMBER);
+
+ CcInitializeCacheMap( UnwindStreamFile,
+ &CcFileSizes,
+ PinAccess,
+ &NtfsData.CacheManagerCallbacks,
+ (PCHAR)Scb + CompressedStream );
+
+ UnwindInitializeCacheMap = TRUE;
+ }
+
+ //
+ // Now call Cc to set the log handle for the file.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE ) &&
+ (Scb != Vcb->LogFileScb)) {
+
+ CcSetLogHandleForFile( UnwindStreamFile,
+ Vcb->LogHandle,
+ &LfsFlushToLsn );
+ }
+
+ //
+ // It is now safe to store the stream file in the Scb. We wait
+ // until now because we don't want an unsafe tester to use the
+ // file object until the cache is initialized.
+ //
+
+ *FileObjectPtr = UnwindStreamFile;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateInternalAttributeStream );
+
+ //
+ // Undo our work if an error occurred.
+ //
+
+ if (AbnormalTermination()) {
+
+ //
+ // Uninitialize the cache file if we initialized it.
+ //
+
+ if (UnwindInitializeCacheMap) {
+
+ CcUninitializeCacheMap( UnwindStreamFile, NULL, NULL );
+ }
+
+ //
+ // Dereference the stream file if we created it.
+ //
+
+ if (UnwindStreamFile != NULL) {
+
+ ObDereferenceObject( UnwindStreamFile );
+ }
+ }
+
+ //
+ // Restore the Scb cleanup count.
+ //
+
+ if (DecrementScbCleanup) {
+
+ InterlockedDecrement( &Scb->CleanupCount );
+ }
+
+ if (AcquiredFastMutex) {
+
+ ExReleaseFastMutexUnsafe( &StreamFileCreationFastMutex );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateInternalAttributeStream -> VOID\n") );
+ }
+ }
+
+ return;
+}
+
+
+BOOLEAN
+NtfsDeleteInternalAttributeStream (
+ IN PSCB Scb,
+ IN BOOLEAN ForceClose
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the inverse of NtfsCreateInternalAttributeStream. It
+ uninitializes the cache map and dereferences the stream file object.
+ It is coded defensively, in case the stream file object does not exist
+ or the cache map has not been initialized.
+
+Arguments:
+
+ Scb - Supplies the Scb for which the stream file is to be deleted.
+
+ ForceClose - Indicates if we to immediately close everything down or
+ if we are willing to let Mm slowly migrate things out.
+
+Return Value:
+
+ BOOLEAN - TRUE if we dereference a file object, FALSE otherwise.
+
+--*/
+
+{
+ PFILE_OBJECT FileObject;
+ PFILE_OBJECT FileObjectC;
+
+ BOOLEAN Dereferenced = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // We normally already have the paging Io resource. If we do
+ // not, then it is typically some cleanup path of create or
+ // whatever. This code assumes that if we cannot get the paging
+ // Io resource, then there is other activity still going on,
+ // and it is ok to not delete the stream! For example, it could
+ // be the lazy writer, who definitely needs the stream.
+ //
+
+ if (((Scb->FileObject != NULL) || (Scb->Header.FileObjectC != NULL)) &&
+ ((Scb->Header.PagingIoResource == NULL) ||
+ ExAcquireResourceExclusive( Scb->Header.PagingIoResource, FALSE ))) {
+
+ ExAcquireFastMutex( &StreamFileCreationFastMutex );
+
+ //
+ // Capture both file objects and clear the fields so no one else
+ // can access them.
+ //
+
+ FileObject = Scb->FileObject;
+ Scb->FileObject = NULL;
+
+ FileObjectC = Scb->Header.FileObjectC;
+ Scb->Header.FileObjectC = NULL;
+
+ ExReleaseFastMutex( &StreamFileCreationFastMutex );
+
+ if (Scb->Header.PagingIoResource != NULL) {
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+
+ //
+ // Now dereference each file object.
+ //
+
+ if (FileObject != NULL) {
+
+ if (FileObject->PrivateCacheMap != NULL) {
+
+ CcUninitializeCacheMap( FileObject,
+ (ForceClose ? &Li0 : NULL),
+ NULL );
+ }
+
+ ObDereferenceObject( FileObject );
+ Dereferenced = TRUE;
+ }
+
+ if (FileObjectC != NULL) {
+
+ if (FileObjectC->PrivateCacheMap != NULL) {
+
+ CcUninitializeCacheMap( FileObjectC,
+ (ForceClose ? &Li0 : NULL),
+ NULL );
+ }
+
+ //
+ // For the compressed stream, deallocate the additional
+ // section object pointers.
+ //
+
+ ObDereferenceObject( FileObjectC );
+ Dereferenced = TRUE;
+ }
+ }
+
+ return Dereferenced;
+}
+
+
+VOID
+NtfsMapStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to map a range of bytes within the stream file
+ for an Scb. The allowed range to map is bounded by the allocation
+ size for the Scb. This operation is only valid on a non-resident
+ Scb.
+
+ TEMPCODE - The following need to be resolved for this routine.
+
+ - Can the caller specify either an empty range or an invalid range.
+ In that case we need to able to return the actual length of the
+ mapped range.
+
+Arguments:
+
+ Scb - This is the Scb for the operation.
+
+ FileOffset - This is the offset within the Scb where the data is to
+ be pinned.
+
+ Length - This is the number of bytes to pin.
+
+ Bcb - Returns a pointer to the Bcb for this range of bytes.
+
+ Buffer - Returns a pointer to the range of bytes. We can fault them in
+ by touching them, but they aren't guaranteed to stay unless
+ we pin them via the Bcb.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT( Length != 0 );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMapStream\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // The file object should already exist in the Scb.
+ //
+
+ ASSERT( Scb->FileObject != NULL );
+
+ //
+ // If we are trying to go beyond the end of the allocation, assume
+ // we have some corruption.
+ //
+
+ if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Call the cache manager to map the data. This call may raise, but
+ // will never return an error (including CANT_WAIT).
+ //
+
+ if (!CcMapData( Scb->FileObject,
+ (PLARGE_INTEGER)&FileOffset,
+ Length,
+ TRUE,
+ Bcb,
+ Buffer )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) );
+ DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsPinMappedData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ IN OUT PVOID *Bcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to pin a previously mapped range of bytes
+ within the stream file for an Scb, for the purpose of subsequently
+ modifying this byte range. The allowed range to map is
+ bounded by the allocation size for the Scb. This operation is only
+ valid on a non-resident Scb.
+
+ The data is guaranteed to stay at the same virtual address as previously
+ returned from NtfsMapStream.
+
+ TEMPCODE - The following need to be resolved for this routine.
+
+ - Can the caller specify either an empty range or an invalid range.
+ In that case we need to able to return the actual length of the
+ mapped range.
+
+Arguments:
+
+ Scb - This is the Scb for the operation.
+
+ FileOffset - This is the offset within the Scb where the data is to
+ be pinned.
+
+ Length - This is the number of bytes to pin.
+
+ Bcb - Returns a pointer to the Bcb for this range of bytes.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT( Length != 0 );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPinMappedData\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // The file object should already exist in the Scb.
+ //
+
+ ASSERT( Scb->FileObject != NULL );
+
+ //
+ // If we are trying to go beyond the end of the allocation, assume
+ // we have some corruption.
+ //
+
+ if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Call the cache manager to map the data. This call may raise, but
+ // will never return an error (including CANT_WAIT).
+ //
+
+ if (!CcPinMappedData( Scb->FileObject,
+ (PLARGE_INTEGER)&FileOffset,
+ Length,
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
+ Bcb )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsPinStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to pin a range of bytes within the stream file
+ for an Scb. The allowed range to pin is bounded by the allocation
+ size for the Scb. This operation is only valid on a non-resident
+ Scb.
+
+ TEMPCODE - The following need to be resolved for this routine.
+
+ - Can the caller specify either an empty range or an invalid range.
+ In that case we need to able to return the actual length of the
+ pinned range.
+
+Arguments:
+
+ Scb - This is the Scb for the operation.
+
+ FileOffset - This is the offset within the Scb where the data is to
+ be pinned.
+
+ Length - This is the number of bytes to pin.
+
+ Bcb - Returns a pointer to the Bcb for this range of bytes.
+
+ Buffer - Returns a pointer to the range of bytes pinned in memory.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT( Length != 0 );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPinStream\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // The file object should already exist in the Scb.
+ //
+
+ ASSERT( Scb->FileObject != NULL );
+
+ //
+ // If we are trying to go beyond the end of the allocation, assume
+ // we have some corruption.
+ //
+
+ if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Call the cache manager to map the data. This call may raise, or
+ // will return FALSE if waiting is required.
+ //
+
+ if (!CcPinRead( Scb->FileObject,
+ (PLARGE_INTEGER)&FileOffset,
+ Length,
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
+ Bcb,
+ Buffer )) {
+
+ ASSERT( !FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
+
+ //
+ // Could not pin the data without waiting (cache miss).
+ //
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Bcb -> %08lx\n", *Bcb) );
+ DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) );
+ DebugTrace( -1, Dbg, ("NtfsMapStream -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsPreparePinWriteStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Zero,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPreparePinWriteStream\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", FileOffset) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // The file object should already exist in the Scb.
+ //
+
+ ASSERT( Scb->FileObject != NULL );
+
+ //
+ // If we are trying to go beyond the end of the allocation, assume
+ // we have some corruption.
+ //
+
+ if ((FileOffset + Length) > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Call the cache manager to do it. This call may raise, or
+ // will return FALSE if waiting is required.
+ //
+
+ if (!CcPreparePinWrite( Scb->FileObject,
+ (PLARGE_INTEGER)&FileOffset,
+ Length,
+ Zero,
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
+ Bcb,
+ Buffer )) {
+
+ ASSERT( !FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
+
+ //
+ // Could not pin the data without waiting (cache miss).
+ //
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Bcb -> %08lx\n", *Bcb) );
+ DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", *Buffer) );
+ DebugTrace( -1, Dbg, ("NtfsPreparePinWriteStream -> VOID\n") );
+
+ return;
+}
+
+
+NTSTATUS
+NtfsCompleteMdl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the function of completing Mdl read and write
+ requests. It should be called only from NtfsFsdRead and NtfsFsdWrite.
+
+Arguments:
+
+ Irp - Supplies the originating Irp.
+
+Return Value:
+
+ NTSTATUS - Will always be STATUS_PENDING or STATUS_SUCCESS.
+
+--*/
+
+{
+ PFILE_OBJECT FileObject;
+ PIO_STACK_LOCATION IrpSp;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCompleteMdl\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Do completion processing.
+ //
+
+ FileObject = IoGetCurrentIrpStackLocation( Irp )->FileObject;
+
+ switch( IrpContext->MajorFunction ) {
+
+ case IRP_MJ_READ:
+
+ CcMdlReadComplete( FileObject, Irp->MdlAddress );
+ break;
+
+ case IRP_MJ_WRITE:
+
+ ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ CcMdlWriteComplete( FileObject, &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress );
+
+ break;
+
+ default:
+
+ DebugTrace( DEBUG_TRACE_ERROR, 0, ("Illegal Mdl Complete.\n") );
+
+ ASSERTMSG("Illegal Mdl Complete, About to bugcheck ", FALSE);
+ NtfsBugCheck( IrpContext->MajorFunction, 0, 0 );
+ }
+
+ //
+ // Mdl is now deallocated.
+ //
+
+ Irp->MdlAddress = NULL;
+
+ //
+ // Complete the request and exit right away.
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ DebugTrace( -1, Dbg, ("NtfsCompleteMdl -> STATUS_SUCCESS\n") );
+
+ return STATUS_SUCCESS;
+}
+
+
+BOOLEAN
+NtfsZeroData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFILE_OBJECT FileObject,
+ IN LONGLONG StartingZero,
+ IN LONGLONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to zero a range of a file in order to
+ advance valid data length.
+
+Arguments:
+
+ Scb - Scb for the stream to zero.
+
+ FileObject - FileObject for the stream.
+
+ StartingZero - Offset to begin the zero operation.
+
+ ByteCount - Length of range to zero.
+
+Return Value:
+
+ BOOLEAN - TRUE if the entire range was zeroed, FALSE if the request
+ is broken up or the cache manager would block.
+
+--*/
+
+{
+ LONGLONG Temp;
+
+ ULONG SectorSize;
+
+ BOOLEAN Finished;
+ BOOLEAN CompleteZero = TRUE;
+ BOOLEAN ScbAcquired = FALSE;
+
+ PVCB Vcb = Scb->Vcb;
+
+ LONGLONG ZeroStart;
+ LONGLONG BeyondZeroEnd;
+ ULONG CompressionUnit = Scb->CompressionUnit;
+
+ BOOLEAN Wait;
+
+ PAGED_CODE();
+
+ Wait = BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ SectorSize = Vcb->BytesPerSector;
+
+ //
+ // If this is a non-compressed file and the amount to zero is larger
+ // than our threshold then limit the range.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_COMPRESSED ) &&
+ (ByteCount > MAX_ZERO_THRESHOLD)) {
+
+ ByteCount = MAX_ZERO_THRESHOLD;
+ CompleteZero = FALSE;
+ }
+
+ ZeroStart = StartingZero + (SectorSize - 1);
+ (ULONG)ZeroStart &= ~(SectorSize - 1);
+
+ BeyondZeroEnd = StartingZero + ByteCount + (SectorSize - 1);
+ (ULONG)BeyondZeroEnd &= ~(SectorSize - 1);
+
+ //
+ // If this is a compressed file and we are zeroing a lot, then let's
+ // just delete the space instead of writing tons of zeros and deleting
+ // the space in the noncached path!
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_COMPRESSED) &&
+ (ByteCount > (Scb->CompressionUnit * 2))) {
+
+ //
+ // Find the end of the first compression unit being zeroed.
+ //
+
+ Temp = ZeroStart + (CompressionUnit - 1);
+ (ULONG)Temp &= ~(CompressionUnit - 1);
+
+ //
+ // Zero the first compression unit.
+ //
+
+ if ((ULONG)Temp != (ULONG)ZeroStart) {
+
+ Finished = CcZeroData( FileObject, (PLARGE_INTEGER)&ZeroStart, (PLARGE_INTEGER)&Temp, Wait );
+
+ if (!Finished) {return FALSE;}
+
+ ZeroStart = Temp;
+ }
+
+ //
+ // Calculate the start of the last compression unit.
+ //
+
+ Temp = BeyondZeroEnd;
+ (ULONG)Temp &= ~(CompressionUnit - 1);
+
+ //
+ // Zero the beginning of the last compression unit.
+ //
+
+ if ((ULONG)Temp != (ULONG)BeyondZeroEnd) {
+
+ Finished = CcZeroData( FileObject, (PLARGE_INTEGER)&Temp, (PLARGE_INTEGER)&BeyondZeroEnd, Wait );
+
+ if (!Finished) {return FALSE;}
+
+ BeyondZeroEnd = Temp;
+ }
+
+ //
+ // Now delete all of the compression units in between.
+ //
+
+
+ Temp = LlClustersFromBytes( Vcb, BeyondZeroEnd ) - 1;
+
+ //
+ // If the caller has not already started a transaction (like write.c),
+ // then let's just do the delete as an atomic action.
+ //
+
+ if (!ExIsResourceAcquiredExclusive( Scb->Header.Resource )) {
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+ }
+
+ try {
+
+ //
+ // Delete the space.
+ //
+
+ NtfsDeleteAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes(Vcb, ZeroStart),
+ Temp,
+ TRUE,
+ TRUE );
+
+ //
+ // If we didn't raise then update the Scb values.
+ //
+
+ Scb->ValidDataToDisk = BeyondZeroEnd;
+
+ //
+ // If we succeed, commit the atomic action. Release all of the exclusive
+ // resources if our user explicitly acquired the Fcb here.
+ //
+
+ if (ScbAcquired) {
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ ScbAcquired = FALSE;
+ }
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ } finally {
+
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+ }
+
+ return TRUE;
+ }
+
+ //
+ // If we were called to just zero part of a sector we are screwed.
+ //
+
+ if (ZeroStart == BeyondZeroEnd) {
+
+ return TRUE;
+ }
+
+ Finished = CcZeroData( FileObject,
+ (PLARGE_INTEGER)&ZeroStart,
+ (PLARGE_INTEGER)&BeyondZeroEnd,
+ Wait );
+
+ //
+ // If we are breaking this request up then commit the current
+ // transaction (including updating the valid data length in
+ // in the Scb) and return FALSE.
+ //
+
+ if (Finished && !CompleteZero) {
+
+ //
+ // Synchronize the valid data length change using the mutex.
+ //
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ Scb->Header.ValidDataLength.QuadPart = BeyondZeroEnd;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ return FALSE;
+ }
+
+ return Finished;
+}
+
+
+#ifdef _CAIRO_
+NTFSAPI
+VOID
+NtOfsPutData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG Offset,
+ IN ULONG Length,
+ IN PVOID Data OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update a range of a recoverable stream.
+
+Arguments:
+
+ Scb - Scb for the stream to zero.
+
+ Offset - Offset in stream to update.
+
+ Length - Length of stream to update in bytes.
+
+ Data - Data to update stream with if specified, else range should be zeroed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ ASSERT((Offset + Length) <= Scb->Header.FileSize.QuadPart);
+ ASSERT(FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE));
+
+ //
+ // First handle the resident case.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ULONG RecordOffset, AttributeOffset;
+ PVCB Vcb = Scb->Vcb;
+
+ NtfsInitializeAttributeContext( &Context );
+
+ try {
+
+ //
+ // Lookup and pin the attribute.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &Context );
+ NtfsPinMappedAttribute( IrpContext, Vcb, &Context );
+
+ //
+ // Extract the relevant pointers and calculate offsets.
+ //
+
+ FileRecord = NtfsContainingFileRecord(&Context);
+ Attribute = NtfsFoundAttribute(&Context);
+ RecordOffset = PtrOffset(FileRecord, Attribute);
+ AttributeOffset = Attribute->Form.Resident.ValueOffset + (ULONG)Offset;
+
+ //
+ // Log the change while we still have the old data.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(&Context),
+ UpdateResidentValue,
+ Data,
+ Length,
+ UpdateResidentValue,
+ Add2Ptr(Attribute, Attribute->Form.Resident.ValueOffset + (ULONG)Offset),
+ Length,
+ NtfsMftOffset(&Context),
+ RecordOffset,
+ AttributeOffset,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Now update this data by calling the same routine as restart.
+ //
+
+ NtfsRestartChangeValue( IrpContext,
+ FileRecord,
+ RecordOffset,
+ AttributeOffset,
+ Data,
+ Length,
+ FALSE );
+
+ //
+ // If there is a stream for this attribute, then we must update it in the
+ // cache, copying from the attribute itself in order to handle the zeroing
+ // (Data == NULL) case.
+ //
+
+ if (Scb->FileObject != NULL) {
+ CcCopyWrite( Scb->FileObject,
+ (PLARGE_INTEGER)&Offset,
+ Length,
+ TRUE,
+ Add2Ptr(Attribute, AttributeOffset) );
+ }
+
+ //
+ // Optionally update ValidDataLength
+ //
+
+ Offset += Length;
+ if (Offset > Scb->Header.ValidDataLength.QuadPart) {
+ Scb->Header.ValidDataLength.QuadPart = Offset;
+ }
+
+ } finally {
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ //
+ // Now handle the nonresident case.
+ //
+
+ } else {
+
+ PVOID Buffer;
+ LONGLONG NewValidDataLength = Offset + Length;
+ PBCB Bcb = NULL;
+ ULONG PageOffset = (ULONG)Offset & (PAGE_SIZE - 1);
+ ULONG MovingBackwards = FALSE;
+
+ ASSERT(Scb->FileObject != NULL);
+ ASSERT((Offset & ~(VACB_MAPPING_GRANULARITY - 1)) == ((Offset + Length - 1) & ~(VACB_MAPPING_GRANULARITY - 1)));
+
+ //
+ // If we are starting beyond ValidDataLength, then recurse to
+ // zero what we need.
+ //
+
+ if (Offset > Scb->Header.ValidDataLength.QuadPart) {
+
+ ASSERT((Offset - Scb->Header.ValidDataLength.QuadPart) <= MAXULONG);
+
+ NtOfsPutData( IrpContext,
+ Scb,
+ Scb->Header.ValidDataLength.QuadPart,
+ (ULONG)(Offset - Scb->Header.ValidDataLength.QuadPart),
+ NULL );
+ }
+
+ try {
+
+ //
+ // Now loop until there are no more pages with new data
+ // to log.
+ //
+
+ while (Length != 0) {
+
+ ULONG BytesThisPage;
+
+ NtfsPinStream( IrpContext,
+ Scb,
+ Offset,
+ 1,
+ &Bcb,
+ &Buffer );
+
+ //
+ // Compute the number of bytes of for this page, assuming a
+ // forward move.
+ //
+
+ BytesThisPage = PAGE_SIZE - PageOffset;
+
+ if (BytesThisPage > Length) {
+ BytesThisPage = Length;
+ }
+
+ //
+ // See if we need to switch to moving backwards.
+ //
+
+ if (!MovingBackwards &&
+ ((PCHAR)Buffer > (PCHAR)Data) &&
+ (Data != NULL) &&
+ ((PageOffset + Length) > PAGE_SIZE)) {
+
+ //
+ // We are now doing the move backwards - we will only do this once.
+ //
+
+ MovingBackwards = TRUE;
+
+ //
+ // Figure out how many bytes there are to move in the last page, and
+ // then see how much we have to adjust our Offset and pointers by to
+ // get to the last page (temporarily in PageOffset).
+ //
+
+ BytesThisPage = ((PageOffset + Length - 1) & (PAGE_SIZE - 1)) + 1;
+ PageOffset = Length - BytesThisPage;
+
+ //
+ // Now adjust everyone by the right amount.
+ //
+
+ Offset += PageOffset;
+ Data = Add2Ptr( Data, PageOffset );
+ Buffer = Add2Ptr( Buffer, PageOffset );
+
+ //
+ // Of course the page offset in the last page is 0.
+ //
+
+ PageOffset = 0;
+ }
+
+ //
+ // Now log the changes to this page.
+ //
+
+ (VOID)
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Bcb,
+ UpdateNonresidentValue,
+ Data,
+ BytesThisPage,
+ UpdateNonresidentValue,
+ Buffer,
+ BytesThisPage,
+ Offset - PageOffset,
+ PageOffset,
+ 0,
+ PageOffset + BytesThisPage );
+
+ //
+ // Move the data into place.
+ //
+
+ if (Data != NULL) {
+ RtlMoveMemory( Buffer, Data, BytesThisPage );
+ } else {
+ RtlZeroMemory( Buffer, BytesThisPage );
+ }
+
+ //
+ // Now we pin the page and calculate the beginning
+ // buffer in the page.
+ //
+
+ NtfsUnpinBcb( &Bcb );
+
+ Length -= BytesThisPage;
+ PageOffset = 0;
+
+ if (MovingBackwards) {
+
+ //
+ // Now decrement the counts and move through the
+ // caller's buffer.
+ //
+
+ BytesThisPage = PAGE_SIZE;
+ if (Length < PAGE_SIZE) {
+ PageOffset = PAGE_SIZE - Length;
+ BytesThisPage = Length;
+ }
+ Data = Add2Ptr( Data, (0 - BytesThisPage) );
+ Offset -= BytesThisPage;
+
+ } else {
+
+ //
+ // Now decrement the counts and move through the
+ // caller's buffer.
+ //
+
+ if (Data != NULL) {
+ Data = Add2Ptr( Data, BytesThisPage );
+ }
+ Offset += BytesThisPage;
+ }
+ }
+
+ //
+ // Optionally update ValidDataLength
+ //
+
+ if (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) {
+
+ Scb->Header.ValidDataLength.QuadPart = NewValidDataLength;
+ NtfsWriteFileSizes( IrpContext, Scb, &Offset, TRUE, TRUE );
+ }
+
+ } finally {
+ NtfsUnpinBcb( &Bcb );
+ }
+ }
+}
+#endif _CAIRO_
+
+
diff --git a/private/ntos/cntfs/checksup.c b/private/ntos/cntfs/checksup.c
new file mode 100644
index 000000000..38ff25fda
--- /dev/null
+++ b/private/ntos/cntfs/checksup.c
@@ -0,0 +1,862 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ CheckSup.c
+
+Abstract:
+
+ This module implements check routines for Ntfs structures.
+
+Author:
+
+ Tom Miller [TomM] 14-4-92
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Array for log records which require a target attribute.
+// A TRUE indicates that the corresponding restart operation
+// requires a target attribute.
+//
+
+BOOLEAN TargetAttributeRequired[] = {FALSE, FALSE, TRUE, TRUE,
+ TRUE, TRUE, TRUE, TRUE,
+ TRUE, TRUE, FALSE, TRUE,
+ TRUE, TRUE, TRUE, TRUE,
+ TRUE, TRUE, TRUE, TRUE,
+ TRUE, TRUE, TRUE, TRUE,
+ FALSE, FALSE, FALSE, FALSE,
+ TRUE, FALSE, FALSE, FALSE,
+ FALSE, TRUE, TRUE };
+
+//
+// Local procedure prototypes
+//
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCheckAttributeRecord)
+#pragma alloc_text(PAGE, NtfsCheckFileRecord)
+#pragma alloc_text(PAGE, NtfsCheckIndexBuffer)
+#pragma alloc_text(PAGE, NtfsCheckIndexHeader)
+#pragma alloc_text(PAGE, NtfsCheckIndexRoot)
+#pragma alloc_text(PAGE, NtfsCheckLogRecord)
+#pragma alloc_text(PAGE, NtfsCheckRestartTable)
+#endif
+
+
+BOOLEAN
+NtfsCheckFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord
+ )
+
+{
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PFILE_RECORD_SEGMENT_HEADER EndOfFileRecord;
+ ULONG BytesPerFileRecordSegment = Vcb->BytesPerFileRecordSegment;
+ BOOLEAN StandardInformationSeen = FALSE;
+ BOOLEAN AttributeListPresent = FALSE;
+ BOOLEAN FirstPass = TRUE;
+
+ PAGED_CODE();
+
+ EndOfFileRecord = Add2Ptr( FileRecord, BytesPerFileRecordSegment );
+
+ //
+ // Check the file record header for consistency.
+ //
+
+ if ((*(PULONG)FileRecord->MultiSectorHeader.Signature != *(PULONG)FileSignature)
+
+ ||
+
+ ((ULONG)FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset >
+ (SEQUENCE_NUMBER_STRIDE -
+ (PAGE_SIZE / SEQUENCE_NUMBER_STRIDE + 1) * sizeof(USHORT)))
+
+ ||
+
+ ((ULONG)((FileRecord->MultiSectorHeader.UpdateSequenceArraySize - 1) * SEQUENCE_NUMBER_STRIDE) !=
+ BytesPerFileRecordSegment)
+
+ ||
+
+ (((ULONG)FileRecord->FirstAttributeOffset < sizeof(FILE_RECORD_SEGMENT_HEADER)) ||
+ ((ULONG)FileRecord->FirstAttributeOffset >
+ BytesPerFileRecordSegment - SIZEOF_RESIDENT_ATTRIBUTE_HEADER))
+
+ ||
+
+ !FlagOn(FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE)
+
+ ||
+
+ (FileRecord->BytesAvailable != BytesPerFileRecordSegment)) {
+
+ DebugTrace( 0, 0, ("Invalid file record: %08lx\n", FileRecord) );
+
+ ASSERTMSG( "Invalid resident file record\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ //
+ // Loop to check all of the attributes.
+ //
+
+ for (Attribute = NtfsFirstAttribute(FileRecord);
+ Attribute->TypeCode != $END;
+ Attribute = NtfsGetNextRecord(Attribute)) {
+
+// if (!StandardInformationSeen &&
+// (Attribute->TypeCode != $STANDARD_INFORMATION) &&
+// XxEqlZero(FileRecord->BaseFileRecordSegment)) {
+//
+// DebugTrace( 0, 0, ("Standard Information missing: %08lx\n", Attribute) );
+//
+// ASSERTMSG( "Standard Information missing\n", FALSE );
+//
+// NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+// return FALSE;
+// }
+
+ StandardInformationSeen = TRUE;
+
+ //
+ // Check if we are in a second Mft record.
+ //
+
+ if (FirstPass) {
+
+ FirstPass = FALSE;
+
+ if (Attribute->TypeCode != $STANDARD_INFORMATION) {
+
+ AttributeListPresent = TRUE;
+ }
+ }
+
+ if (Attribute->TypeCode == $ATTRIBUTE_LIST) {
+ AttributeListPresent = TRUE;
+ }
+
+ if (!NtfsCheckAttributeRecord( IrpContext,
+ Vcb,
+ FileRecord,
+ Attribute )) {
+
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsCheckAttributeRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ )
+
+{
+ PVOID NextAttribute;
+ PVOID EndOfFileRecord;
+ PVOID Data;
+ ULONG Length;
+ ULONG BytesPerFileRecordSegment = Vcb->BytesPerFileRecordSegment;
+
+ PAGED_CODE();
+
+ EndOfFileRecord = Add2Ptr( FileRecord, BytesPerFileRecordSegment );
+
+ NextAttribute = NtfsGetNextRecord(Attribute);
+
+ //
+ // Check the fixed part of the attribute record header.
+ //
+
+ if ((Attribute->RecordLength >= BytesPerFileRecordSegment)
+
+ ||
+
+ (NextAttribute >= EndOfFileRecord)
+
+ ||
+
+ ((Attribute->NameLength != 0) &&
+ (((ULONG)Attribute->NameOffset + (ULONG)Attribute->NameLength) >
+ Attribute->RecordLength))) {
+
+ DebugTrace( 0, 0, ("Invalid attribute record header: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid attribute record header\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ //
+ // Check the resident attribute fields.
+ //
+
+ if (Attribute->FormCode == RESIDENT_FORM) {
+
+ if ((Attribute->Form.Resident.ValueLength >= Attribute->RecordLength)
+
+ ||
+
+ (((ULONG)Attribute->Form.Resident.ValueOffset +
+ Attribute->Form.Resident.ValueLength) > Attribute->RecordLength)) {
+
+ DebugTrace( 0, 0, ("Invalid resident attribute record header: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid resident attribute record header\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ //
+ // Check the nonresident attribute fields
+ //
+
+ } else if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ VCN CurrentVcn, NextVcn;
+ LCN CurrentLcn;
+ LONGLONG Change;
+ PCHAR ch;
+ ULONG VcnBytes;
+ ULONG LcnBytes;
+
+ if ((Attribute->Form.Nonresident.LowestVcn >
+ (Attribute->Form.Nonresident.HighestVcn + 1))
+
+ ||
+
+ ((ULONG)Attribute->Form.Nonresident.MappingPairsOffset >=
+ Attribute->RecordLength)
+
+ ||
+
+ (Attribute->Form.Nonresident.ValidDataLength >
+ Attribute->Form.Nonresident.FileSize)
+
+ ||
+
+ (Attribute->Form.Nonresident.FileSize >
+ Attribute->Form.Nonresident.AllocatedLength)) {
+
+ DebugTrace( 0, 0, ("Invalid nonresident attribute record header: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid nonresident attribute record header\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+
+ //
+ // Implement the decompression algorithm, as defined in ntfs.h.
+ // (This code should look remarkably similar to what goes on in
+ // NtfsLookupAllocation!)
+ //
+
+ NextVcn = Attribute->Form.Nonresident.LowestVcn;
+ CurrentLcn = 0;
+ ch = (PCHAR)Attribute + Attribute->Form.Nonresident.MappingPairsOffset;
+
+ //
+ // Loop to process mapping pairs, insuring we do not run off the end
+ // of the attribute, and that we do not map to nonexistant Lcns.
+ //
+
+ while (!IsCharZero(*ch)) {
+
+ //
+ // Set Current Vcn from initial value or last pass through loop.
+ //
+
+ CurrentVcn = NextVcn;
+
+ //
+ // Extract the counts from the two nibbles of this byte.
+ //
+
+ VcnBytes = *ch & 0xF;
+ LcnBytes = *ch++ >> 4;
+
+ //
+ // Neither of these should be larger than a VCN.
+ //
+
+ if ((VcnBytes > sizeof( VCN )) ||
+ (LcnBytes > sizeof( VCN ))) {
+
+ DebugTrace( 0, 0, ("Invalid maping pair byte count: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid maping pair byte count\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ //
+ // Extract the Vcn change (use of RtlCopyMemory works for little-Endian)
+ // and update NextVcn.
+ //
+
+ Change = 0;
+
+ //
+ // Make sure we are not going beyond the end of the attribute
+ // record, and that the Vcn change is not negative or zero.
+ //
+
+ if (((ULONG)(ch + VcnBytes + LcnBytes + 1) > (ULONG)NextAttribute)
+
+ ||
+
+ IsCharLtrZero(*(ch + VcnBytes - 1))) {
+
+ DebugTrace( 0, 0, ("Invalid maping pairs array: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid maping pairs array\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ RtlCopyMemory( &Change, ch, VcnBytes );
+ ch += VcnBytes;
+ NextVcn = NextVcn + Change;
+
+ //
+ // Extract the Lcn change and update CurrentLcn.
+ //
+
+ Change = 0;
+ if (IsCharLtrZero(*(ch + LcnBytes - 1))) {
+ Change = Change - 1;
+ }
+ RtlCopyMemory( &Change, ch, LcnBytes );
+ ch += LcnBytes;
+ CurrentLcn = CurrentLcn + Change;
+
+ if ((LcnBytes != 0) &&
+ ((CurrentLcn + (NextVcn - CurrentVcn)) > Vcb->TotalClusters)) {
+
+ DebugTrace( 0, 0, ("Invalid Lcn: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid Lcn\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+ }
+
+ //
+ // Finally, check HighestVcn.
+ //
+
+ if (NextVcn != (Attribute->Form.Nonresident.HighestVcn + 1)) {
+
+ DebugTrace( 0, 0, ("Disagreement with mapping pairs: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Disagreement with mapping pairs\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ } else {
+
+ DebugTrace( 0, 0, ("Invalid attribute form code: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid attribute form code\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ //
+ // Now check the attributes by type code, if they are resident. Not all
+ // attributes require specific checks (such as $STANDARD_INFORMATION and $DATA).
+ //
+
+ if (!NtfsIsAttributeResident(Attribute)) {
+
+ return TRUE;
+ }
+
+ Data = NtfsAttributeValue(Attribute);
+ Length = Attribute->Form.Resident.ValueLength;
+
+ switch (Attribute->TypeCode) {
+
+ case $FILE_NAME:
+
+ {
+ if ((ULONG)((PFILE_NAME)Data)->FileNameLength * 2 >
+ (Length - (ULONG)sizeof(FILE_NAME) + 2)) {
+
+ DebugTrace( 0, 0, ("Invalid File Name attribute: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid File Name attribute\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+ break;
+ }
+
+ case $INDEX_ROOT:
+
+ {
+ return NtfsCheckIndexRoot( IrpContext, Vcb, (PINDEX_ROOT)Data, Length );
+ }
+
+ case $STANDARD_INFORMATION:
+
+#ifdef _CARIO_
+ {
+ if (Length < sizeof( STANDARD_INFORMATION ) &&
+ Length != SIZEOF_OLD_STANDARD_INFORMATION)
+ {
+ DebugTrace( 0, 0, ("Invalid Standard Information attribute: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Invalid Standard Information attribute size\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ break;
+ }
+#endif
+
+ case $ATTRIBUTE_LIST:
+ case $OBJECT_ID:
+ case $SECURITY_DESCRIPTOR:
+ case $VOLUME_NAME:
+ case $VOLUME_INFORMATION:
+ case $DATA:
+ case $INDEX_ALLOCATION:
+ case $BITMAP:
+ case $SYMBOLIC_LINK:
+ case $EA_INFORMATION:
+ case $EA:
+#ifdef _CAIRO_
+ case $PROPERTY_SET:
+#endif // _CAIRO_
+
+ break;
+
+ default:
+
+ {
+ DebugTrace( 0, 0, ("Bad Attribute type code: %08lx\n", Attribute) );
+
+ ASSERTMSG( "Bad Attribute type code\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsCheckIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PINDEX_ROOT IndexRoot,
+ IN ULONG AttributeSize
+ )
+
+{
+ UCHAR ShiftValue;
+ PAGED_CODE();
+
+ //
+ // Check whether this index root uses clusters or if the cluster size is larger than
+ // the index block.
+ //
+
+ if (IndexRoot->BytesPerIndexBuffer >= Vcb->BytesPerCluster) {
+
+ ShiftValue = (UCHAR) Vcb->ClusterShift;
+
+ } else {
+
+ ShiftValue = DEFAULT_INDEX_BLOCK_BYTE_SHIFT;
+ }
+
+ if ((AttributeSize < sizeof(INDEX_ROOT))
+
+ ||
+
+#ifdef _CAIRO_
+
+ ((IndexRoot->IndexedAttributeType != $FILE_NAME) && (IndexRoot->IndexedAttributeType != $UNUSED))
+
+ ||
+
+ ((IndexRoot->IndexedAttributeType == $FILE_NAME) && (IndexRoot->CollationRule != COLLATION_FILE_NAME))
+
+#else
+
+ (IndexRoot->IndexedAttributeType != $FILE_NAME)
+
+ ||
+
+ (IndexRoot->CollationRule != COLLATION_FILE_NAME)
+
+#endif _CAIRO_
+
+ ||
+
+
+ (IndexRoot->BytesPerIndexBuffer !=
+ BytesFromIndexBlocks( IndexRoot->BlocksPerIndexBuffer, ShiftValue ))
+
+ ||
+
+ ((IndexRoot->BlocksPerIndexBuffer != 1) &&
+ (IndexRoot->BlocksPerIndexBuffer != 2) &&
+ (IndexRoot->BlocksPerIndexBuffer != 4) &&
+ (IndexRoot->BlocksPerIndexBuffer != 8) &&
+ (IndexRoot->BlocksPerIndexBuffer != 16) &&
+ (IndexRoot->BlocksPerIndexBuffer != 32) &&
+ (IndexRoot->BlocksPerIndexBuffer != 64) &&
+ (IndexRoot->BlocksPerIndexBuffer != 128))) {
+
+ DebugTrace( 0, 0, ("Bad Index Root: %08lx\n", IndexRoot) );
+
+ ASSERTMSG( "Bad Index Root\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ return NtfsCheckIndexHeader( IrpContext,
+ &IndexRoot->IndexHeader,
+ AttributeSize - sizeof(INDEX_ROOT) + sizeof(INDEX_HEADER) );
+}
+
+
+BOOLEAN
+NtfsCheckIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ )
+
+{
+ ULONG BytesPerIndexBuffer = Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ PAGED_CODE();
+
+ //
+ // Check the index buffer for consistency.
+ //
+
+ if ((*(PULONG)IndexBuffer->MultiSectorHeader.Signature != *(PULONG)IndexSignature)
+
+ ||
+
+ ((ULONG)IndexBuffer->MultiSectorHeader.UpdateSequenceArrayOffset >
+ (SEQUENCE_NUMBER_STRIDE - (PAGE_SIZE / SEQUENCE_NUMBER_STRIDE + 1) * sizeof(USHORT)))
+
+ ||
+
+ ((ULONG)((IndexBuffer->MultiSectorHeader.UpdateSequenceArraySize - 1) * SEQUENCE_NUMBER_STRIDE) !=
+ BytesPerIndexBuffer)) {
+
+ DebugTrace( 0, 0, ("Invalid Index Buffer: %08lx\n", IndexBuffer) );
+
+ ASSERTMSG( "Invalid resident Index Buffer\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ return NtfsCheckIndexHeader( IrpContext,
+ &IndexBuffer->IndexHeader,
+ BytesPerIndexBuffer -
+ FIELD_OFFSET(INDEX_ALLOCATION_BUFFER, IndexHeader) );
+}
+
+
+BOOLEAN
+NtfsCheckIndexHeader (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_HEADER IndexHeader,
+ IN ULONG BytesAvailable
+ )
+
+{
+ PINDEX_ENTRY IndexEntry, NextIndexEntry;
+ PINDEX_ENTRY EndOfIndex;
+ ULONG MinIndexEntry = sizeof(INDEX_ENTRY);
+
+ PAGED_CODE();
+
+ if (FlagOn(IndexHeader->Flags, INDEX_NODE)) {
+
+ MinIndexEntry += sizeof(VCN);
+ }
+
+ if ((IndexHeader->FirstIndexEntry > (BytesAvailable - MinIndexEntry))
+
+ ||
+
+ (IndexHeader->FirstFreeByte > BytesAvailable)
+
+ ||
+
+ (IndexHeader->BytesAvailable > BytesAvailable)
+
+ ||
+
+ ((IndexHeader->FirstIndexEntry + MinIndexEntry) > IndexHeader->FirstFreeByte)
+
+ ||
+
+ (IndexHeader->FirstFreeByte > IndexHeader->BytesAvailable)) {
+
+ DebugTrace( 0, 0, ("Bad Index Header: %08lx\n", IndexHeader) );
+
+ ASSERTMSG( "Bad Index Header\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ IndexEntry = NtfsFirstIndexEntry(IndexHeader);
+
+ EndOfIndex = Add2Ptr(IndexHeader, IndexHeader->FirstFreeByte);
+
+ while (TRUE) {
+
+ NextIndexEntry = NtfsNextIndexEntry(IndexEntry);
+
+ if (((ULONG)IndexEntry->Length < MinIndexEntry)
+
+ ||
+
+ (NextIndexEntry > EndOfIndex)
+
+ ||
+
+// ((ULONG)IndexEntry->AttributeLength >
+// ((ULONG)IndexEntry->Length - MinIndexEntry))
+//
+// ||
+
+ (BooleanFlagOn(IndexEntry->Flags, INDEX_ENTRY_NODE) !=
+ BooleanFlagOn(IndexHeader->Flags, INDEX_NODE))) {
+
+ DebugTrace( 0, 0, ("Bad Index Entry: %08lx\n", IndexEntry) );
+
+ ASSERTMSG( "Bad Index Entry\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ if (FlagOn(IndexEntry->Flags, INDEX_ENTRY_END)) {
+ break;
+ }
+ IndexEntry = NextIndexEntry;
+ }
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsCheckLogRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN ULONG LogRecordLength,
+ IN TRANSACTION_ID TransactionId
+ )
+
+{
+ PAGED_CODE();
+
+ //
+ // We make the following checks on the log record.
+ //
+ // - Minimum length must contain an NTFS_LOG_RECORD_HEADER
+ // - Transaction Id must be a valid value (a valid index offset)
+ //
+ // The following are values in the log record.
+ //
+ // - Redo/Undo offset must be quadaligned
+ // - Redo/Undo offset + length must be contained in the log record
+ // - Target attribute must be a valid value (either 0 or valid index offset)
+ // - Record offset must be quad-aligned and less than the file record size.
+ // - Log record size must be sufficient for Lcn's to follow.
+ //
+
+ if (LogRecordLength < sizeof( NTFS_LOG_RECORD_HEADER )
+
+ ||
+
+ (TransactionId == 0)
+
+ ||
+
+ ((TransactionId - sizeof( RESTART_TABLE )) % sizeof( TRANSACTION_ENTRY ))
+
+ ||
+
+ (LogRecord->RedoOffset & 7)
+
+ ||
+
+ (LogRecord->UndoOffset & 7)
+
+ ||
+
+ ((ULONG) LogRecord->RedoOffset + LogRecord->RedoLength > LogRecordLength)
+
+ ||
+
+ ((LogRecord->UndoOperation != CompensationLogRecord) &&
+ ((ULONG) LogRecord->UndoOffset + LogRecord->UndoLength > LogRecordLength))
+
+ ||
+
+ ((LogRecord->TargetAttribute == 0) &&
+ (((LogRecord->RedoOperation <= UpdateRecordDataAllocation) &&
+ TargetAttributeRequired[LogRecord->RedoOperation]) ||
+ ((LogRecord->UndoOperation <= UpdateRecordDataAllocation) &&
+ TargetAttributeRequired[LogRecord->UndoOperation])))
+
+ ||
+
+ ((LogRecord->LcnsToFollow != 0) &&
+ ((LogRecord->TargetAttribute - sizeof( RESTART_TABLE )) % SIZEOF_OPEN_ATTRIBUTE_ENTRY))
+
+ ||
+
+ (LogRecordLength < (sizeof( NTFS_LOG_RECORD_HEADER )
+ + (LogRecord->LcnsToFollow != 0
+ ? sizeof( LCN ) * (LogRecord->LcnsToFollow - 1)
+ : 0)))) {
+
+ ASSERTMSG( "Invalid log record\n", FALSE );
+
+ NtfsMarkVolumeDirty( IrpContext, IrpContext->Vcb );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsCheckRestartTable (
+ IN PRESTART_TABLE RestartTable,
+ IN ULONG TableSize
+ )
+{
+ ULONG ActualTableSize;
+ ULONG Index;
+ PDIRTY_PAGE_ENTRY NextEntry;
+
+ PAGED_CODE();
+
+ //
+ // We want to make the following checks.
+ //
+ // EntrySize - Must be less than table size and non-zero.
+ //
+ // NumberEntries - The table size must contain at least this many entries
+ // plus the table header.
+ //
+ // NumberAllocated - Must be less than/equal to NumberEntries
+ //
+ // FreeGoal - Must lie in the table.
+ //
+ // FirstFree
+ // LastFree - Must either be 0 or be on a restart entry boundary.
+ //
+
+ if ((RestartTable->EntrySize == 0) ||
+ (RestartTable->EntrySize > TableSize) ||
+ ((RestartTable->EntrySize + sizeof( RESTART_TABLE )) > TableSize) ||
+ (((TableSize - sizeof( RESTART_TABLE )) / RestartTable->EntrySize) < RestartTable->NumberEntries) ||
+ (RestartTable->NumberAllocated > RestartTable->NumberEntries)) {
+
+ ASSERTMSG( "Invalid Restart Table sizes\n", FALSE );
+ return FALSE;
+ }
+
+ ActualTableSize = (RestartTable->EntrySize * RestartTable->NumberEntries) +
+ sizeof( RESTART_TABLE );
+
+ if ((RestartTable->FirstFree > ActualTableSize) ||
+ (RestartTable->LastFree > ActualTableSize) ||
+ ((RestartTable->FirstFree != 0) && (RestartTable->FirstFree < sizeof( RESTART_TABLE ))) ||
+ ((RestartTable->LastFree != 0) && (RestartTable->LastFree < sizeof( RESTART_TABLE )))) {
+
+ ASSERTMSG( "Invalid Restart Table List Head\n", FALSE );
+ return FALSE;
+ }
+
+ //
+ // Make a pass through the table verifying that each entry
+ // is either allocated or points to a valid offset in the
+ // table.
+ //
+
+ for (Index = 0;Index < RestartTable->NumberEntries; Index++) {
+
+ NextEntry = (PDIRTY_PAGE_ENTRY) Add2Ptr( RestartTable,
+ ((Index * RestartTable->EntrySize) +
+ sizeof( RESTART_TABLE )));
+
+ if ((NextEntry->AllocatedOrNextFree != RESTART_ENTRY_ALLOCATED) &&
+ (NextEntry->AllocatedOrNextFree != 0) &&
+ ((NextEntry->AllocatedOrNextFree < sizeof( RESTART_TABLE )) ||
+ (((NextEntry->AllocatedOrNextFree - sizeof( RESTART_TABLE )) % RestartTable->EntrySize) != 0))) {
+
+ ASSERTMSG( "Invalid Restart Table Entry\n", FALSE );
+ return FALSE;
+ }
+ }
+
+ //
+ // Walk through the list headed by the first entry to make sure none
+ // of the entries are currently being used.
+ //
+
+ for (Index = RestartTable->FirstFree; Index != 0; Index = NextEntry->AllocatedOrNextFree) {
+
+ if (Index == RESTART_ENTRY_ALLOCATED) {
+
+ ASSERTMSG( "Invalid Restart Table Free List\n", FALSE );
+ return FALSE;
+ }
+
+ NextEntry = (PDIRTY_PAGE_ENTRY) Add2Ptr( RestartTable, Index );
+ }
+
+ return TRUE;
+}
+
diff --git a/private/ntos/cntfs/cleanup.c b/private/ntos/cntfs/cleanup.c
new file mode 100644
index 000000000..9cf876c7d
--- /dev/null
+++ b/private/ntos/cntfs/cleanup.c
@@ -0,0 +1,2129 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Cleanup.c
+
+Abstract:
+
+ This module implements the File Cleanup routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_CLEANUP)
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CLEANUP)
+
+VOID
+NtfsContractQuotaToFileSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonCleanup)
+#pragma alloc_text(PAGE, NtfsFsdCleanup)
+#pragma alloc_text(PAGE, NtfsContractQuotaToFileSize)
+#endif
+
+
+NTSTATUS
+NtfsFsdCleanup (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Cleanup.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+ ULONG LogFileFullCount = 0;
+
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // If we were called with our file system device object instead of a
+ // volume device object, just complete this request with STATUS_SUCCESS
+ //
+
+ if (VolumeDeviceObject->DeviceObject.Size == (USHORT)sizeof(DEVICE_OBJECT)) {
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = FILE_OPENED;
+
+ IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+
+ return STATUS_SUCCESS;
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsFsdCleanup\n") );
+
+ //
+ // Call the common Cleanup routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ //
+ // Do the following in a loop to catch the log file full and cant wait
+ // calls.
+ //
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+
+ if (++LogFileFullCount >= 2) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
+ }
+ }
+
+ Status = NtfsCommonCleanup( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdCleanup -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonCleanup (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Cleanup called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ PLCB Lcb;
+ PLCB LcbForUpdate;
+ PLCB LcbForCounts;
+ PSCB ParentScb = NULL;
+ PFCB ParentFcb = NULL;
+
+ PLCB ThisLcb;
+ PSCB ThisScb;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PLONGLONG TruncateSize = NULL;
+ LONGLONG LocalTruncateSize;
+
+ BOOLEAN DeleteFile = FALSE;
+ BOOLEAN DeleteStream = FALSE;
+ BOOLEAN OpenById;
+ BOOLEAN RemoveLink;
+
+ BOOLEAN AcquiredParentScb = FALSE;
+ BOOLEAN AcquiredScb = FALSE;
+
+ BOOLEAN CleanupAttrContext = FALSE;
+
+ BOOLEAN UpdateDuplicateInfo = FALSE;
+ BOOLEAN AddToDelayQueue = TRUE;
+
+ USHORT TotalLinkAdj = 0;
+ PLIST_ENTRY Links;
+
+ NAME_PAIR NamePair;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ NtfsInitializeNamePair(&NamePair);
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonCleanup\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Special case the unopened file object and stream files.
+ //
+
+ if ((TypeOfOpen == UnopenedFileObject) ||
+ (TypeOfOpen == StreamFileOpen)) {
+
+ //
+ // Just set the FO_CLEANUP_COMPLETE flag, and get outsky...
+ //
+
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ //
+ // Theoretically we should never hit this case. It means an app
+ // tried to close a handle he didn't open (call NtClose with a handle
+ // value that happens to be in the handle table). It is safe to
+ // simply return SUCCESS in this case.
+ //
+ // Trigger an assert so we can find the bad app though.
+ //
+
+ ASSERT( TypeOfOpen != StreamFileOpen );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ //
+ // Let's make sure we can wait.
+ //
+
+ if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ //
+ // Remember if this is an open by file Id open.
+ //
+
+ OpenById = BooleanFlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID );
+
+ //
+ // Acquire exclusive access to the Vcb and enqueue the irp if we didn't
+ // get access
+ //
+
+ if (TypeOfOpen == UserVolumeOpen) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ }
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ } else {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ LcbForUpdate = LcbForCounts = Lcb = Ccb->Lcb;
+
+ if (Lcb != NULL) {
+
+ ParentScb = Lcb->Scb;
+
+ if (ParentScb != NULL) {
+
+ ParentFcb = ParentScb->Fcb;
+ }
+ }
+
+ //
+ // Acquire Paging I/O first, since we may be deleting or truncating.
+ // Testing for the PagingIoResource is not really safe without
+ // holding the main resource, so we correct for that below.
+ //
+
+ if (Fcb->PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ } else {
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ //
+ // If we now do not see a paging I/O resource we are golden,
+ // othewise we can absolutely release and acquire the resources
+ // safely in the right order, since a resource in the Fcb is
+ // not going to go away.
+ //
+
+ if (Fcb->PagingIoResource != NULL) {
+ NtfsReleaseScb( IrpContext, Scb );
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ }
+ }
+
+ AcquiredScb = TRUE;
+
+ //
+ // Update the Lcb/Scb to reflect the case where this opener had
+ // specified delete on close.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE )) {
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ BOOLEAN LastLink;
+ BOOLEAN NonEmptyIndex;
+
+ //
+ // It is ok to get rid of this guy. All we need to do is
+ // mark this Lcb for delete and decrement the link count
+ // in the Fcb. If this is a primary link, then we
+ // indicate that the primary link has been deleted.
+ //
+
+ if (!LcbLinkIsDeleted( Lcb ) &&
+ (!IsDirectory( &Fcb->Info ) ||
+ NtfsIsLinkDeleteable( IrpContext, Fcb, &NonEmptyIndex, &LastLink))) {
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
+
+ SetFlag( Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
+ }
+
+ Fcb->LinkCount -= 1;
+
+ SetFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
+
+ //
+ // Call into the notify package to close any handles on
+ // a directory being deleted.
+ //
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ FsRtlNotifyFullChangeDirectory( Vcb->NotifySync,
+ &Vcb->DirNotifyList,
+ FileObject->FsContext,
+ NULL,
+ FALSE,
+ FALSE,
+ 0,
+ NULL,
+ NULL,
+ NULL );
+ }
+
+ }
+
+ //
+ // Otherwise we are simply removing the attribute.
+ //
+
+ } else {
+
+ SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ //
+ // Clear the flag so we will ignore it in the log file full case.
+ //
+
+ ClearFlag( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
+ }
+
+ //
+ // If we are going to try and delete something, anything, knock the file
+ // size and valid data down to zero. Then update the snapshot
+ // so that the sizes will be zero even if the operation fails.
+ //
+ // If we're deleting the file, go through all of the Scb's.
+ //
+
+ if ((Fcb->CleanupCount == 1) &&
+ (Fcb->LinkCount == 0)) {
+
+ DeleteFile = TRUE;
+ NtfsFreeSnapshotsForFcb( IrpContext, Scb->Fcb );
+
+ for (Links = Fcb->ScbQueue.Flink;
+ Links != &Fcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ //
+ // Set the Scb sizes to zero except for the attribute list.
+ //
+
+ if (ThisScb->AttributeTypeCode != $ATTRIBUTE_LIST) {
+
+ ThisScb->Header.FileSize =
+ ThisScb->Header.ValidDataLength = Li0;
+ }
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+ }
+ }
+
+ //
+ // Otherwise we may only be deleting this stream.
+ //
+
+ } else if ((Scb->CleanupCount == 1) &&
+ FlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE )) {
+
+ DeleteStream = TRUE;
+ Scb->Header.FileSize =
+ Scb->Header.ValidDataLength = Li0;
+
+ NtfsFreeSnapshotsForFcb( IrpContext, Scb->Fcb );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+ }
+
+ //
+ // Let's do a sanity check.
+ //
+
+ ASSERT( Fcb->CleanupCount != 0 );
+ ASSERT( Scb->CleanupCount != 0 );
+
+ //
+ // If the cleanup count on the file will go to zero and there is
+ // a large security descriptor and we haven't exceeded the security
+ // creation count for this Fcb then dereference and possibly deallocate
+ // the security descriptor for the Fcb. This is to prevent us from
+ // holding onto pool while waiting for closes to come in.
+ //
+
+ if ((Fcb->CleanupCount == 1) &&
+ (Fcb->SharedSecurity != NULL) &&
+ (Fcb->CreateSecurityCount < FCB_CREATE_SECURITY_COUNT) &&
+ (GetSharedSecurityLength( Fcb->SharedSecurity ) > FCB_LARGE_ACL_SIZE)) {
+
+ NtfsAcquireFcbSecurity( Fcb->Vcb );
+ NtfsDereferenceSharedSecurity( Fcb );
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+ }
+
+ //
+ // Case on the type of open that we are trying to cleanup.
+ //
+
+ switch (TypeOfOpen) {
+
+ case UserVolumeOpen :
+
+ DebugTrace( 0, Dbg, ("Cleanup on user volume\n") );
+
+ //
+ // First set the FO_CLEANUP_COMPLETE flag.
+ //
+
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ //
+ // For a volume open, we check if this open locked the volume.
+ // All the other work is done in common code below.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED ) &&
+ ((Vcb->FileObjectWithVcbLocked == FileObject) ||
+ ((ULONG)Vcb->FileObjectWithVcbLocked == ((ULONG)FileObject)+1))) {
+
+ if ((ULONG)Vcb->FileObjectWithVcbLocked == ((ULONG)FileObject)+1) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ //
+ // Purge the volume for the autocheck case.
+ //
+
+ } else if (FlagOn( FileObject->Flags, FO_FILE_MODIFIED )) {
+
+ //
+ // Drop the Scb for the volume Dasd around this call.
+ //
+
+ NtfsReleaseScb( IrpContext, Scb );
+ AcquiredScb = FALSE;
+
+ NtfsFlushVolume( IrpContext, Vcb, FALSE, TRUE, TRUE, FALSE );
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ AcquiredScb = TRUE;
+
+ //
+ // If this is not the boot partition then dismount the Vcb.
+ //
+
+ if ((Vcb->CleanupCount == 1) &&
+ ((Vcb->CloseCount - Vcb->SystemFileCloseCount) == 1)) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+ }
+ }
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_EXPLICIT_LOCK );
+ Vcb->FileObjectWithVcbLocked = NULL;
+
+#ifdef _CAIRO_
+
+ //
+ // If the quota tracking has been requested and the quotas
+ // need to be repaired then try to repair them now.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED) &&
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT |
+ QUOTA_FLAG_PENDING_DELETES)) {
+
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+
+#endif // _CAIRO_
+
+ }
+
+ break;
+
+ case UserDirectoryOpen :
+
+ DebugTrace( 0, Dbg, ("Cleanup on user directory/file\n") );
+
+ NtfsSnapshotScb( IrpContext, Scb );
+
+ //
+ // Capture any changes to the time stamps for this file.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // Now set the FO_CLEANUP_COMPLETE flag.
+ //
+
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ //
+ // To perform cleanup on a directory, we first complete any
+ // Irps watching from this directory. If we are deleting the
+ // file then we remove all prefix entries for all the Lcb's going
+ // into this directory and delete the file. We then report to
+ // dir notify that this file is going away.
+ //
+
+ //
+ // Complete any Notify Irps on this file handle.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_DIR_NOTIFY )) {
+
+ FsRtlNotifyCleanup( Vcb->NotifySync, &Vcb->DirNotifyList, Ccb );
+ ClearFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY );
+ InterlockedDecrement( &Vcb->NotifyCount );
+ }
+
+ //
+ // When cleaning up a user directory, we always remove the
+ // share access and modify the file counts. If the Fcb
+ // has been marked as delete on close and this is the last
+ // open file handle, we remove the file from the Mft and
+ // remove it from it's parent index entry.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) &&
+ (NodeType( Scb ) == NTFS_NTC_SCB_INDEX)) {
+
+ if (DeleteFile) {
+
+ ASSERT( (Lcb == NULL) ||
+ (LcbLinkIsDeleted( Lcb ) && Lcb->CleanupCount == 1 ));
+
+ //
+ // If we don't have an Lcb and there is one on the Fcb then
+ // let's use it.
+ //
+
+ if ((Lcb == NULL) && !IsListEmpty( &Fcb->LcbQueue )) {
+
+ Lcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ ParentScb = Lcb->Scb;
+ if (ParentScb != NULL) {
+
+ ParentFcb = ParentScb->Fcb;
+ }
+ }
+
+ //
+ // Now acquire the Parent Scb exclusive while still holding
+ // the Vcb, to avoid deadlocks. The Parent Scb is required
+ // since we will be deleting index entries in it.
+ //
+
+ if (ParentScb != NULL) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ AcquiredParentScb = TRUE;
+ }
+
+ try {
+
+ NtfsDeleteFile( IrpContext, Fcb, ParentScb, NULL);
+ TotalLinkAdj += 1;
+
+ //
+ // Remove all tunneling entries for this directory
+ //
+
+ FsRtlDeleteKeyFromTunnelCache(&Vcb->Tunnel, *(PULONGLONG)&Fcb->FileReference);
+
+ if (ParentFcb != NULL) {
+
+ NtfsUpdateFcb( ParentFcb );
+ }
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ if (!OpenById && (Vcb->NotifyCount != 0)) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ FILE_ACTION_REMOVED,
+ ParentFcb );
+ }
+
+ SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED );
+
+ //
+ // We need to mark all of the links on the file as gone.
+ // If there is a parent Scb then it will be the parent
+ // for all of the links.
+ //
+
+ for (Links = Fcb->LcbQueue.Flink;
+ Links != &Fcb->LcbQueue;
+ Links = Links->Flink) {
+
+ ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
+
+ //
+ // Remove all remaining prefixes on this link.
+ //
+
+ NtfsRemovePrefix( ThisLcb );
+
+ SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
+
+ //
+ // We don't need to report any changes on this link.
+ //
+
+ ThisLcb->InfoFlags = 0;
+ }
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = Fcb->ScbQueue.Flink;
+ Links != &Fcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ ClearFlag( Scb->ScbState,
+ SCB_STATE_NOTIFY_ADD_STREAM |
+ SCB_STATE_NOTIFY_REMOVE_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM );
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+
+ ThisScb->ValidDataToDisk =
+ ThisScb->Header.AllocationSize.QuadPart =
+ ThisScb->Header.FileSize.QuadPart =
+ ThisScb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+
+ //
+ // We certainly don't need to any on disk update for this
+ // file now.
+ //
+
+ Fcb->InfoFlags = 0;
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ ClearFlag( Ccb->Flags,
+ CCB_FLAG_USER_SET_LAST_MOD_TIME |
+ CCB_FLAG_USER_SET_LAST_CHANGE_TIME |
+ CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
+ AddToDelayQueue = FALSE;
+ }
+
+ } else {
+
+ AddToDelayQueue = FALSE;
+ }
+
+ //
+ // Determine if we should put this on the delayed close list.
+ // The following must be true.
+ //
+ // - This is not the root directory
+ // - This directory is not about to be deleted
+ // - This is the last handle and last file object for this
+ // directory.
+ // - There are no other file objects on this file.
+ // - We are not currently reducing the delayed close queue.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ if (AddToDelayQueue &&
+ !FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) &&
+ (NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount) &&
+ (Fcb->CloseCount == 1)) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+ }
+ NtfsReleaseFsrtlHeader( Scb );
+
+ break;
+
+ case UserFileOpen :
+#ifdef _CAIRO_
+ case UserPropertySetOpen :
+#endif // _CAIRO_
+
+ DebugTrace( 0, Dbg, ("Cleanup on user file\n") );
+
+ //
+ // If the Scb is uninitialized, we read it from the disk.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ try {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+ }
+
+ NtfsSnapshotScb( IrpContext, Scb );
+
+ //
+ // Coordinate the cleanup operation with the oplock state.
+ // Cleanup operations can always cleanup immediately.
+ //
+
+ FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NULL,
+ NULL );
+
+ //
+ // In this case, we have to unlock all the outstanding file
+ // locks, update the time stamps for the file and sizes for
+ // this attribute, and set the archive bit if necessary.
+ //
+
+ if (Scb->ScbType.Data.FileLock != NULL) {
+
+ (VOID) FsRtlFastUnlockAll( Scb->ScbType.Data.FileLock,
+ FileObject,
+ IoGetRequestorProcess( Irp ),
+ NULL );
+ }
+
+ //
+ // Update the FastIoField.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ //
+ // If the Fcb is in valid shape, we check on the cases where we delete
+ // the file or attribute.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ //
+ // Capture any changes to the time stamps for this file.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // Now set the FO_CLEANUP_COMPLETE flag.
+ //
+
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ //
+ // We are checking here for special actions we take when
+ // we have the last user handle on a link and the link has
+ // been marked for delete. We could either be removing the
+ // file or removing a link.
+ //
+
+ if ((Lcb == NULL) || (LcbLinkIsDeleted( Lcb ) && (Lcb->CleanupCount == 1))) {
+
+ if (DeleteFile) {
+
+ //
+ // If we don't have an Lcb and the Fcb has some entries then
+ // grab one of these to do the update.
+ //
+
+ if (Lcb == NULL) {
+
+ for (Links = Fcb->LcbQueue.Flink;
+ Links != &Fcb->LcbQueue;
+ Links = Links->Flink) {
+
+ ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ if (!FlagOn( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE )) {
+
+ Lcb = ThisLcb;
+
+ ParentScb = Lcb->Scb;
+ if (ParentScb != NULL) {
+
+ ParentFcb = ParentScb->Fcb;
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Now acquire the Parent Scb exclusive while still holding
+ // the Vcb, to avoid deadlocks. The Parent Scb is required
+ // since we will be deleting index entries in it.
+ //
+
+ if (ParentScb != NULL) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ AcquiredParentScb = TRUE;
+ }
+
+ try {
+
+ AddToDelayQueue = FALSE;
+ NtfsDeleteFile( IrpContext, Fcb, ParentScb, &NamePair );
+ TotalLinkAdj += 1;
+
+ //
+ // Stash property information in the tunnel if the object was
+ // opened by name, has a parent directory caller was treating it
+ // as a non-POSIX object and we had an good, active link
+ //
+
+ if (!OpenById &&
+ ParentScb &&
+ Ccb->Lcb &&
+ !FlagOn(FileObject->Flags, FO_OPENED_CASE_SENSITIVE)) {
+
+ FsRtlAddToTunnelCache( &Vcb->Tunnel,
+ *(PULONGLONG)&ParentScb->Fcb->FileReference,
+ &NamePair.Short,
+ &NamePair.Long,
+ BooleanFlagOn(Ccb->Lcb->FileNameAttr->Flags, FILE_NAME_DOS),
+ sizeof(LONGLONG),
+ &Fcb->Info.CreationTime);
+ }
+
+ if (ParentFcb != NULL) {
+
+ NtfsUpdateFcb( ParentFcb );
+ }
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ if (!OpenById && (Vcb->NotifyCount != 0)) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ FILE_ACTION_REMOVED,
+ ParentFcb );
+ }
+
+ SetFlag( Fcb->FcbState, FCB_STATE_FILE_DELETED );
+
+ //
+ // We need to mark all of the links on the file as gone.
+ //
+
+ for (Links = Fcb->LcbQueue.Flink;
+ Links != &Fcb->LcbQueue;
+ Links = Links->Flink) {
+
+ ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
+
+ if (ThisLcb->Scb == ParentScb) {
+
+ //
+ // Remove all remaining prefixes on this link.
+ //
+
+ NtfsRemovePrefix( ThisLcb );
+ SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
+
+ //
+ // We don't need to report any changes on this link.
+ //
+
+ ThisLcb->InfoFlags = 0;
+ }
+ }
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = Fcb->ScbQueue.Flink;
+ Links != &Fcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ ClearFlag( Scb->ScbState,
+ SCB_STATE_NOTIFY_ADD_STREAM |
+ SCB_STATE_NOTIFY_REMOVE_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM );
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+
+ ThisScb->ValidDataToDisk =
+ ThisScb->Header.AllocationSize.QuadPart =
+ ThisScb->Header.FileSize.QuadPart =
+ ThisScb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+
+ //
+ // We certainly don't need to any on disk update for this
+ // file now.
+ //
+
+ Fcb->InfoFlags = 0;
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ ClearFlag( Ccb->Flags,
+ CCB_FLAG_USER_SET_LAST_MOD_TIME |
+ CCB_FLAG_USER_SET_LAST_CHANGE_TIME |
+ CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
+
+ //
+ // We will truncate the attribute to size 0.
+ //
+
+ TruncateSize = (PLONGLONG)&Li0;
+
+ //
+ // Now we want to check for the last user's handle on a
+ // link (or the last handle on a Ntfs/8.3 pair). In this
+ // case we want to remove the links from the disk.
+ //
+
+ } else if (Lcb != NULL) {
+
+ ThisLcb = NULL;
+ RemoveLink = TRUE;
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ) &&
+ (Lcb->FileNameAttr->Flags != (FILE_NAME_NTFS | FILE_NAME_DOS))) {
+
+ //
+ // Walk through all the links looking for a link
+ // with a flag set which is not the same as the
+ // link we already have.
+ //
+
+ for (Links = Fcb->LcbQueue.Flink;
+ Links != &Fcb->LcbQueue;
+ Links = Links->Flink) {
+
+ ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
+
+ //
+ // If this has a flag set and is not the Lcb
+ // for this cleanup, then we check if there
+ // are no Ccb's left for this.
+ //
+
+ if (FlagOn( ThisLcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )
+
+ &&
+
+ (ThisLcb != Lcb)) {
+
+ if (ThisLcb->CleanupCount != 0) {
+
+ RemoveLink = FALSE;
+ }
+
+ break;
+ }
+
+ ThisLcb = NULL;
+ }
+ }
+
+ //
+ // If we are to remove the link, we do so now. This removes
+ // the filename attributes and the entries in the parent
+ // indexes for this link. In addition, we mark the links
+ // as having been removed and decrement the number of links
+ // left on the file.
+ //
+
+ if (RemoveLink) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ AcquiredParentScb = TRUE;
+
+ try {
+
+ AddToDelayQueue = FALSE;
+ NtfsRemoveLink( IrpContext,
+ Fcb,
+ ParentScb,
+ Lcb->ExactCaseLink.LinkName,
+ &NamePair );
+
+ //
+ // Stash property information in the tunnel if caller opened the
+ // object by name and was treating it as a non-POSIX object
+ //
+
+ if (!OpenById && !FlagOn(FileObject->Flags, FO_OPENED_CASE_SENSITIVE)) {
+
+ FsRtlAddToTunnelCache( &Vcb->Tunnel,
+ *(PULONGLONG)&ParentScb->Fcb->FileReference,
+ &NamePair.Short,
+ &NamePair.Long,
+ BooleanFlagOn(Lcb->FileNameAttr->Flags, FILE_NAME_DOS),
+ sizeof(LONGLONG),
+ &Fcb->Info.CreationTime);
+ }
+
+ TotalLinkAdj += 1;
+ NtfsUpdateFcb( ParentFcb );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ if (!OpenById && (Vcb->NotifyCount != 0)) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ FILE_ACTION_REMOVED,
+ ParentFcb );
+ }
+
+ //
+ // Remove all remaining prefixes on this link.
+ //
+
+ NtfsRemovePrefix( Lcb );
+
+ //
+ // Mark the links as being removed.
+ //
+
+ SetFlag( Lcb->LcbState, LCB_STATE_LINK_IS_GONE );
+
+ if (ThisLcb != NULL) {
+
+ //
+ // Remove all remaining prefixes on this link.
+ //
+
+ NtfsRemovePrefix( ThisLcb );
+ SetFlag( ThisLcb->LcbState, LCB_STATE_LINK_IS_GONE );
+ ThisLcb->InfoFlags = 0;
+ }
+
+ //
+ // Since the link is gone we don't want to update the
+ // duplicate information for this link.
+ //
+
+ Lcb->InfoFlags = 0;
+ LcbForUpdate = NULL;
+
+ //
+ // Update the time stamps for removing the link. Clear the
+ // FO_CLEANUP_COMPLETE flag around this call so the time
+ // stamp change is not nooped.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
+ ClearFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+ }
+ }
+ }
+
+ //
+ // If the file/attribute is not going away, we update the
+ // attribute size now rather than waiting for the Lazy
+ // Writer to catch up. If the cleanup count isn't 1 then
+ // defer the following actions.
+ //
+
+ if ((Scb->CleanupCount == 1) && (Fcb->LinkCount != 0)) {
+
+ //
+ // We may also have to delete this attribute only.
+ //
+
+ if (DeleteStream) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+
+ try {
+
+ //
+ // Delete the attribute only.
+ //
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ do {
+
+ NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &AttrContext );
+
+ } while (NtfsLookupNextAttributeForScb( IrpContext, Scb, &AttrContext ));
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ //
+ // Set the Scb flag to indicate that the attribute is
+ // gone.
+ //
+
+ Scb->ValidDataToDisk =
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+
+ SetFlag( Scb->ScbState, SCB_STATE_NOTIFY_REMOVE_STREAM );
+
+ ClearFlag( Scb->ScbState,
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM |
+ SCB_STATE_NOTIFY_ADD_STREAM );
+
+ //
+ // Update the time stamps for removing the link. Clear the
+ // FO_CLEANUP_COMPLETE flag around this call so the time
+ // stamp change is not nooped.
+ //
+
+ SetFlag( Ccb->Flags,
+ CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
+ ClearFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ TruncateSize = (PLONGLONG)&Li0;
+
+ //
+ // Check if we're to modify the allocation size or file size.
+ //
+
+ } else {
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
+
+ //
+ // Acquire the parent now so we enforce our locking
+ // rules that the Mft Scb must be acquired after
+ // the normal file resources.
+ //
+
+ NtfsPrepareForUpdateDuplicate( IrpContext,
+ Fcb,
+ &LcbForUpdate,
+ &ParentScb,
+ TRUE );
+
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+
+ //
+ // For the non-resident streams we will write the file
+ // size to disk.
+ //
+
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Setting AdvanceOnly to FALSE guarantees we will not
+ // incorrectly advance the valid data size.
+ //
+
+ try {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ //
+ // For resident streams we will write the correct size to
+ // the resident attribute.
+ //
+
+ } else {
+
+ //
+ // We need to lookup the attribute and change
+ // the attribute value. We can point to
+ // the attribute itself as the changing
+ // value.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ try {
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ Scb->Header.FileSize.LowPart,
+ NULL,
+ 0,
+ TRUE,
+ TRUE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ //
+ // Verify the allocation size is now correct.
+ //
+
+ if (QuadAlign( Scb->Header.FileSize.LowPart ) != Scb->Header.AllocationSize.LowPart) {
+
+ Scb->Header.AllocationSize.LowPart = QuadAlign(Scb->Header.FileSize.LowPart);
+ }
+ }
+
+ //
+ // Update the size change to the Fcb.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+ }
+
+#ifdef _CAIRO_
+ if (NtfsPerformQuotaOperation( Fcb )) {
+
+ if ( FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED )) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ ASSERT( FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ));
+
+ //
+ // Acquire the parent now so we enforce our locking
+ // rules that the Mft Scb must be acquired after
+ // the normal file resources.
+ //
+
+ NtfsPrepareForUpdateDuplicate( IrpContext,
+ Fcb,
+ &LcbForUpdate,
+ &ParentScb,
+ TRUE );
+
+ NtfsContractQuotaToFileSize( IrpContext, Scb );
+ }
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ }
+#endif // _CAIRO_
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE )) {
+
+ //
+ // Acquire the parent now so we enforce our locking
+ // rules that the Mft Scb must be acquired after
+ // the normal file resources.
+ //
+ NtfsPrepareForUpdateDuplicate( IrpContext,
+ Fcb,
+ &LcbForUpdate,
+ &ParentScb,
+ TRUE );
+
+ ClearFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ //
+ // We have two cases:
+ //
+ // Resident: We are looking for the case where the
+ // valid data length is less than the file size.
+ // In this case we shrink the attribute.
+ //
+ // NonResident: We are looking for unused clusters
+ // past the end of the file.
+ //
+ // We skip the following if we had any previous errors.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // We don't need to truncate if the file size is 0.
+ //
+
+ if (Scb->Header.AllocationSize.QuadPart != 0) {
+
+ VCN StartingCluster;
+ VCN EndingCluster;
+
+ //
+ // **** Do we need to give up the Vcb for this
+ // call.
+ //
+
+ StartingCluster = LlClustersFromBytes( Vcb, Scb->Header.FileSize.QuadPart );
+ EndingCluster = LlClustersFromBytes( Vcb, Scb->Header.AllocationSize.QuadPart );
+
+ //
+ // If there are clusters to delete, we do so now.
+ //
+
+ if (EndingCluster != StartingCluster) {
+
+ try {
+ NtfsDeleteAllocation( IrpContext,
+ FileObject,
+ Scb,
+ StartingCluster,
+ MAXLONGLONG,
+ TRUE,
+ TRUE );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+ }
+ }
+
+ LocalTruncateSize = Scb->Header.FileSize.QuadPart;
+ TruncateSize = &LocalTruncateSize;
+ }
+
+ //
+ // This is the resident case.
+ //
+
+ } else {
+
+ //
+ // Check if the file size length is less than
+ // the allocated size.
+ //
+
+ if (QuadAlign( Scb->Header.FileSize.LowPart ) < Scb->Header.AllocationSize.LowPart) {
+
+ //
+ // We need to lookup the attribute and change
+ // the attribute value. We can point to
+ // the attribute itself as the changing
+ // value.
+ //
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ try {
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ Scb->Header.FileSize.LowPart,
+ NULL,
+ 0,
+ TRUE,
+ TRUE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+ }
+
+ //
+ // Remember the smaller allocation size
+ //
+
+ Scb->Header.AllocationSize.LowPart = QuadAlign(Scb->Header.FileSize.LowPart);
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+ }
+ }
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+ }
+ }
+ }
+
+ //
+ // If this was the last cached open, and there are open
+ // non-cached handles, attempt a flush and purge operation
+ // to avoid cache coherency overhead from these non-cached
+ // handles later. We ignore any I/O errors from the flush
+ // except for CANT_WAIT and LOG_FILE_FULL.
+ //
+
+ if (!FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
+ (Scb->NonCachedCleanupCount != 0) &&
+ (Scb->CleanupCount == (Scb->NonCachedCleanupCount + 1)) &&
+ (Scb->CompressionUnit == 0) &&
+ (Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL) &&
+ (Scb->NonpagedScb->SegmentObject.ImageSectionObject == NULL) &&
+ MmCanFileBeTruncated( &Scb->NonpagedScb->SegmentObject, NULL )) {
+
+ //
+ // Flush and purge the stream.
+ //
+
+ NtfsFlushAndPurgeScb( IrpContext,
+ Scb,
+ NULL );
+
+ //
+ // Ignore any errors in this path.
+ //
+
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+ }
+
+ if (AddToDelayQueue &&
+ !FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) &&
+ NtfsData.DelayedCloseCount <= NtfsMaxDelayedCloseCount &&
+ Fcb->CloseCount == 1) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+ }
+
+ //
+ // If the Fcb is bad, we will truncate the cache to size zero.
+ //
+
+ } else {
+
+ //
+ // Now set the FO_CLEANUP_COMPLETE flag.
+ //
+
+ SetFlag( FileObject->Flags, FO_CLEANUP_COMPLETE );
+
+ TruncateSize = (PLONGLONG)&Li0;
+ }
+
+ break;
+
+ default :
+
+ NtfsBugCheck( TypeOfOpen, 0, 0 );
+ }
+
+ //
+ // If any of the Fcb Info flags are set we call the routine
+ // to update the duplicated information in the parent directories.
+ // We need to check here in case none of the flags are set but
+ // we want to update last access time.
+ //
+
+ if (Fcb->Info.LastAccessTime != Fcb->CurrentLastAccess) {
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+
+ } else if (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
+
+ NtfsCheckLastAccess( IrpContext, Fcb );
+ }
+ }
+
+ //
+ // We check if we have to the standard information attribute.
+ // We can only update attributes on mounted volumes.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO ) &&
+ (Status == STATUS_SUCCESS) &&
+ !FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
+
+ ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
+ ASSERT( TypeOfOpen != UserVolumeOpen );
+
+ try {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+ }
+
+ //
+ // Now update the duplicate information as well for volumes that are still mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ //
+ // We shouldn't try to write the duplicate info to a dismounted volume.
+ //
+
+ UpdateDuplicateInfo = FALSE;
+
+ } else if (FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ) ||
+ ((LcbForUpdate != NULL) &&
+ FlagOn( LcbForUpdate->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ))) {
+
+ ASSERT( !FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ));
+
+ NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &LcbForUpdate, &ParentScb, TRUE );
+
+ //
+ // Now update the duplicate info.
+ //
+
+ try {
+
+ NtfsUpdateDuplicateInfo( IrpContext, Fcb, LcbForUpdate, ParentScb );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ UpdateDuplicateInfo = TRUE;
+ }
+
+ //
+ // If we have modified the Info structure or security, we report this
+ // to the dir-notify package (except for OpenById cases).
+ //
+
+ if (!OpenById) {
+
+ ULONG FilterMatch;
+
+ //
+ // Check whether we need to report on file changes.
+ //
+
+ if ((Vcb->NotifyCount != 0) &&
+ (UpdateDuplicateInfo || FlagOn( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY ))) {
+
+ //
+ // We map the Fcb info flags into the dir notify flags.
+ //
+
+ FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
+ (Fcb->InfoFlags |
+ (LcbForUpdate ? LcbForUpdate->InfoFlags : 0) ));
+
+ //
+ // If the filter match is non-zero, that means we also need to do a
+ // dir notify call.
+ //
+
+ if (FilterMatch != 0) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ FILE_ACTION_MODIFIED,
+ ParentFcb );
+ }
+ }
+
+ ClearFlag( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY );
+
+ //
+ // If this is a named stream with changes then report them as well.
+ //
+
+ if ((Scb->AttributeName.Length != 0) &&
+ NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
+
+ if ((Vcb->NotifyCount != 0) &&
+ FlagOn( Scb->ScbState,
+ SCB_STATE_NOTIFY_REMOVE_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM )) {
+
+ ULONG Action;
+
+ FilterMatch = 0;
+
+ //
+ // Start by checking for a delete.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_REMOVE_STREAM )) {
+
+ FilterMatch = FILE_NOTIFY_CHANGE_STREAM_NAME;
+ Action = FILE_ACTION_REMOVED_STREAM;
+
+ } else {
+
+ //
+ // Check if the file size changed.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM )) {
+
+ FilterMatch = FILE_NOTIFY_CHANGE_STREAM_SIZE;
+ }
+
+ //
+ // Now check if the stream data was modified.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_NOTIFY_MODIFY_STREAM )) {
+
+ SetFlag( FilterMatch, FILE_NOTIFY_CHANGE_STREAM_WRITE );
+ }
+
+ Action = FILE_ACTION_MODIFIED_STREAM;
+ }
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ &Scb->AttributeName,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ Action,
+ ParentFcb );
+ }
+
+ ClearFlag( Scb->ScbState,
+ SCB_STATE_NOTIFY_ADD_STREAM |
+ SCB_STATE_NOTIFY_REMOVE_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM );
+ }
+ }
+
+ if (UpdateDuplicateInfo) {
+
+ NtfsUpdateLcbDuplicateInfo( Fcb, LcbForUpdate );
+ Fcb->InfoFlags = 0;
+ }
+
+ //
+ // Always clear the update standard information flag.
+ //
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ //
+ // Let's give up the parent Fcb if we have acquired it. This will
+ // prevent deadlocks in any uninitialize code below.
+ //
+
+ if (AcquiredParentScb) {
+
+ NtfsReleaseScb( IrpContext, ParentScb );
+ AcquiredParentScb = FALSE;
+ }
+
+ //
+ // Uninitialize the cache map if this file has been cached or we are
+ // trying to delete.
+ //
+
+ if ((FileObject->PrivateCacheMap != NULL) || (TruncateSize != NULL)) {
+
+ CcUninitializeCacheMap( FileObject, (PLARGE_INTEGER)TruncateSize, NULL );
+ }
+
+ //
+ // Check that the non-cached handle count is consistent.
+ //
+
+ ASSERT( !FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) ||
+ (Scb->NonCachedCleanupCount != 0 ));
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ CleanupAttrContext = FALSE;
+ }
+
+ //
+ // Now decrement the cleanup counts.
+ //
+
+ NtfsDecrementCleanupCounts( Scb,
+ LcbForCounts,
+ BooleanFlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
+
+ //
+ // We remove the share access from the Scb.
+ //
+
+ IoRemoveShareAccess( FileObject, &Scb->ShareAccess );
+
+ //
+ // Modify the delete counts in the Fcb.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_DELETE_FILE )) {
+
+ Fcb->FcbDeleteFile -= 1;
+ ClearFlag( Ccb->Flags, CCB_FLAG_DELETE_FILE );
+ }
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_DENY_DELETE )) {
+
+ Fcb->FcbDenyDelete -= 1;
+ ClearFlag( Ccb->Flags, CCB_FLAG_DENY_DELETE );
+ }
+
+ //
+ // Since this request has completed we can adjust the total link count
+ // in the Fcb.
+ //
+
+ Fcb->TotalLinks -= TotalLinkAdj;
+
+#ifdef _CAIRO_
+
+ //
+ // Release the quota control block. This does not have to be done
+ // here however, it allows us to free up the quota control block
+ // before the fcb is removed from the table. This keeps the assert
+ // about quota table empty from triggering in
+ // NtfsClearAndVerifyQuotaIndex.
+ //
+
+ if (NtfsPerformQuotaOperation(Fcb) &&
+ FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
+ NtfsDereferenceQuotaControlBlock( Vcb,
+ &Fcb->QuotaControl );
+ }
+
+#endif // _CAIRO_
+
+
+ } finally {
+
+ DebugUnwind( NtfsCommonCleanup );
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ //
+ // Release any resources held.
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ //
+ // We clear the file object pointer in the Ccb.
+ // This prevents us from trying to access this in a
+ // rename operation.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_CLEANUP );
+
+ if (AcquiredScb) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ if (NamePair.Long.Buffer != NamePair.LongBuffer) {
+
+ NtfsFreePool(NamePair.Long.Buffer);
+ }
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCommonCleanup -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+#ifdef _CAIRO_
+VOID
+NtfsContractQuotaToFileSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine converts the quota charged for a stream from allocation size
+ to file size. This should only be called for cleanup.
+
+Arguments:
+
+ Scb - Supplies a pointer to the being changed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG Delta;
+ NTSTATUS Status;
+
+ PAGED_CODE();
+ ASSERT( IrpContext->MajorFunction == IRP_MJ_CLEANUP );
+ ASSERT(!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ));
+
+ try {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+ ASSERT( FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ));
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ Delta = (LONG) Scb->Header.FileSize.LowPart -
+ NtfsResidentStreamQuota( Scb->Vcb );
+ } else {
+ Delta = Scb->Header.FileSize.QuadPart -
+ Scb->Header.AllocationSize.QuadPart;
+ }
+
+ if (Delta != 0) {
+
+ NtfsUpdateFileQuota( IrpContext,
+ Scb->Fcb,
+ &Delta,
+ TRUE,
+ FALSE );
+ }
+
+ ClearFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+
+ } except( (((Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT) ||
+ !FsRtlIsNtstatusExpected( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+}
+#endif // _CAIRO_
+
+
diff --git a/private/ntos/cntfs/close.c b/private/ntos/cntfs/close.c
new file mode 100644
index 000000000..483ef645e
--- /dev/null
+++ b/private/ntos/cntfs/close.c
@@ -0,0 +1,1187 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Close.c
+
+Abstract:
+
+ This module implements the File Close routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CLOSE)
+
+ULONG NtfsAsyncPassCount = 0;
+
+//
+// Local procedure prototypes
+//
+
+NTSTATUS
+NtfsCommonClose (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN PVCB Vcb,
+ IN PCCB Ccb,
+ IN PBOOLEAN AcquiredVcb OPTIONAL,
+ IN PBOOLEAN ExclusiveVcb OPTIONAL,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN BOOLEAN ReadOnly
+ );
+
+VOID
+NtfsQueueClose (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN DelayClose
+ );
+
+PIRP_CONTEXT
+NtfsRemoveClose (
+ IN PVCB Vcb OPTIONAL,
+ IN BOOLEAN ThrottleCreate
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonClose)
+#pragma alloc_text(PAGE, NtfsFsdClose)
+#pragma alloc_text(PAGE, NtfsFspClose)
+#endif
+
+
+NTSTATUS
+NtfsFsdClose (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Close.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ PAGED_CODE();
+
+ ASSERT_IRP( Irp );
+
+ //
+ // If we were called with our file system device object instead of a
+ // volume device object, just complete this request with STATUS_SUCCESS
+ //
+
+ if (VolumeDeviceObject->DeviceObject.Size == (USHORT)sizeof(DEVICE_OBJECT)) {
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = FILE_OPENED;
+
+ IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+
+ return STATUS_SUCCESS;
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsFsdClose\n") );
+
+ //
+ // Extract and decode the file object, we are willing to handle the unmounted
+ // file object.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ //
+ // Special case the unopened file object
+ //
+
+ if (TypeOfOpen == UnopenedFileObject) {
+
+ DebugTrace( 0, Dbg, ("Close unopened file object\n") );
+
+ Status = STATUS_SUCCESS;
+ NtfsCompleteRequest( NULL, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsFsdClose -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Remember if this Ccb has gone through close.
+ //
+
+ if (Ccb != NULL) {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_CLOSE );
+ }
+
+ //
+ // Call the common Close routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // Jam Wait to FALSE when we create the IrpContext, to avoid
+ // deadlocks when coming in from cleanup.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, FALSE );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ //
+ // If this is a top level Ntfs request and we are not in the
+ // system process, then we can wait. If it is a top level
+ // Ntfs request and we are in the system process then we would
+ // rather not block this thread at all. If the number of pending
+ // async closes is not too large we will post this immediately.
+ //
+
+ if (NtfsIsTopLevelNtfs( IrpContext )) {
+
+ if (PsGetCurrentProcess() != NtfsData.OurProcess) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // This close is coming from a system thread. It could
+ // be the segment dereference thread trying to make pages
+ // available. Rather than try to do all of the work here
+ // we will post 3 out of 4 closes to our async close
+ // workqueue so that there will be two threads doing
+ // the work.
+ //
+
+ } else {
+
+ NtfsAsyncPassCount += 1;
+
+ if (FlagOn( NtfsAsyncPassCount, 3 )) {
+
+ Status = STATUS_PENDING;
+ break;
+ }
+ }
+
+ //
+ // This is a recursive Ntfs call. Post this unless we already
+ // own this file. Otherwise we could deadlock walking
+ // up the tree.
+ //
+
+ } else if (!NtfsIsExclusiveScb( Scb )) {
+
+ Status = STATUS_PENDING;
+ break;
+ }
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ //
+ // If this Scb should go on the delayed close queue then
+ // status is STATUS_PENDING;
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) &&
+ (Scb->Fcb->DelayedCloseCount == 0)) {
+
+ Status = STATUS_PENDING;
+
+ } else {
+
+ Status = NtfsCommonClose( IrpContext,
+ FileObject,
+ Scb,
+ Fcb,
+ Vcb,
+ Ccb,
+ NULL,
+ NULL,
+ TypeOfOpen,
+ (BOOLEAN)IsFileObjectReadOnly(FileObject) );
+ }
+
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ //
+ // If this is a normal termination then complete the request.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ } else if (Status == STATUS_PENDING) {
+
+ //
+ // If the status is can't wait, then let's get the information we
+ // need into the IrpContext, complete the request,
+ // and post the IrpContext.
+ //
+
+ IrpContext->OriginatingIrp = (PIRP) Scb;
+ IrpContext->Union.SubjectContext = (PSECURITY_SUBJECT_CONTEXT) Ccb;
+ IrpContext->TransactionId = (TRANSACTION_ID) TypeOfOpen;
+
+ if (IsFileObjectReadOnly( FileObject )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_READ_ONLY_FO );
+ }
+
+ NtfsCompleteRequest( NULL, &Irp, STATUS_SUCCESS );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_DELAY_CLOSE ) &&
+ (Scb->Fcb->DelayedCloseCount == 0)) {
+
+ NtfsQueueClose( IrpContext, TRUE );
+
+ } else {
+
+ NtfsQueueClose( IrpContext, FALSE );
+ }
+ }
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdClose -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+VOID
+NtfsFspClose (
+ IN PVCB ThisVcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSP part of Close.
+
+Arguments:
+
+ ThisVcb - If specified then we want to remove all closes for a given Vcb.
+ Otherwise this routine will close all of the async closes and as many
+ of the delayed closes as possible.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIRP_CONTEXT IrpContext;
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PSCB Scb;
+ PCCB Ccb;
+ BOOLEAN ReadOnly;
+
+ BOOLEAN AcquiredVcb = FALSE;
+ PBOOLEAN AcquiredVcbPtr;
+ BOOLEAN ExclusiveVcb = FALSE;
+ PVCB CurrentVcb = NULL;
+
+ ULONG VcbHoldCount = 0;
+
+ BOOLEAN SinglePass = FALSE;
+
+ DebugTrace( +1, Dbg, ("NtfsFspClose\n") );
+
+ PAGED_CODE();
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Occasionally we are called from some other routine to try to
+ // reduce the backlog of closes. This is indicated by a pointer
+ // value of 1.
+ //
+
+ if (ThisVcb == (PVCB) 1) {
+
+ ThisVcb = NULL;
+ SinglePass = TRUE;
+ }
+
+ //
+ // If we were passed a Vcb then we don't need CommonClose to hold the
+ // Vcb open.
+ //
+
+ if (ARGUMENT_PRESENT( ThisVcb )) {
+
+ AcquiredVcbPtr = NULL;
+
+ } else {
+
+ AcquiredVcbPtr = &AcquiredVcb;
+ }
+
+ //
+ // Extract and decode the file object, we are willing to handle the unmounted
+ // file object. Note we normally get here via an IrpContext which really
+ // just points to a file object. We should never see an Irp, unless it can
+ // happen for verify or some other reason.
+ //
+
+ while (IrpContext = NtfsRemoveClose( ThisVcb, SinglePass )) {
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ //
+ // Recover the information about the file object being closed from
+ // the data stored in the IrpContext. The following fields are
+ // used for this.
+ //
+ // OriginatingIrp - Contains the Scb
+ // SubjectContext - Contains the Ccb
+ // TransactionId - Contains the TypeOfOpen
+ // Flags - Has bit for read-only file.
+ //
+
+ Scb = (PSCB) IrpContext->OriginatingIrp;
+ IrpContext->OriginatingIrp = NULL;
+
+ Ccb = (PCCB) IrpContext->Union.SubjectContext;
+ IrpContext->Union.SubjectContext = NULL;
+
+ TypeOfOpen = (TYPE_OF_OPEN) IrpContext->TransactionId;
+ IrpContext->TransactionId = 0;
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_READ_ONLY_FO )) {
+
+ ReadOnly = TRUE;
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_READ_ONLY_FO );
+
+ } else {
+
+ ReadOnly = FALSE;
+ }
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAGS_CLEAR_ON_POST );
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_IN_FSP | IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // Call the common Close routine.
+ //
+
+ try {
+
+ //
+ // Release the previous Vcb if held.
+ //
+
+ if (AcquiredVcb &&
+ IrpContext->Vcb != CurrentVcb) {
+
+ ExReleaseResource( &CurrentVcb->Resource );
+ AcquiredVcb = FALSE;
+ VcbHoldCount = 0;
+ }
+
+ CurrentVcb = IrpContext->Vcb;
+
+ (VOID)NtfsCommonClose( IrpContext,
+ NULL,
+ Scb,
+ Scb->Fcb,
+ IrpContext->Vcb,
+ Ccb,
+ AcquiredVcbPtr,
+ &ExclusiveVcb,
+ TypeOfOpen,
+ ReadOnly );
+
+ //
+ // If we are currently holding the Vcb exclusive then convert
+ // to shared but only there was no input Vcb. Otherwise we
+ // might change how a top level request is holding the Vcb.
+ //
+
+ if (AcquiredVcb) {
+
+ //
+ // We must periodically release this resource in case there
+ // is an exclusive waiter.
+ //
+
+ if (VcbHoldCount > NtfsMinDelayedCloseCount) {
+
+ ExReleaseResource( &CurrentVcb->Resource );
+ AcquiredVcb = FALSE;
+ VcbHoldCount = 0;
+
+ } else if (ExclusiveVcb) {
+
+ ExConvertExclusiveToShared( &CurrentVcb->Resource );
+ ExclusiveVcb = FALSE;
+ }
+
+ VcbHoldCount += 1;
+
+ } else {
+
+ VcbHoldCount = 0;
+ }
+
+ } except( NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NOTHING;
+ }
+
+ //
+ // Now just "complete" the IrpContext.
+ //
+
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+
+ //
+ // If we were just to do a single pass and we don't currently
+ // hold the Vcb then exit.
+ //
+
+ if (SinglePass && !AcquiredVcb) {
+
+ break;
+ }
+ }
+
+ //
+ // Release the previously held Vcb, if any.
+ //
+
+ if (AcquiredVcb) {
+
+ ExReleaseResource( &CurrentVcb->Resource );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFspClose -> NULL\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAddScbToFspClose (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN DelayClose
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to add an entry for the current Scb onto one
+ of the Fsp close queues. This is used when we want to guarantee that
+ a teardown will be called on an Scb or Fcb when the current operation
+ can't begin the operation.
+
+Arguments:
+
+ Scb - Scb to add to the queue.
+
+ DelayClose - Indicates which queue this should go into.
+
+Return Value:
+
+ BOOLEAN - Indicates whether or not the SCB was added to the delayed
+ close queue
+
+--*/
+
+{
+ PIRP_CONTEXT NewIrpContext;
+ BOOLEAN Result = TRUE;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-except to catch any allocation failures. The only valid
+ // error here is an allocation failure for the new irp context.
+ //
+
+ try {
+
+ NewIrpContext = NtfsCreateIrpContext( NULL, TRUE );
+
+ //
+ // Set the necessary fields to post this to the workqueue.
+ //
+
+ NewIrpContext->Vcb = Scb->Vcb;
+ NewIrpContext->MajorFunction = IRP_MJ_CLOSE;
+
+ NewIrpContext->OriginatingIrp = (PIRP) Scb;
+ NewIrpContext->TransactionId = (TRANSACTION_ID) StreamFileOpen;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_READ_ONLY_FO );
+
+ //
+ // Now increment the close counts for this Scb.
+ //
+
+ NtfsIncrementCloseCounts( Scb, TRUE, FALSE );
+
+ //
+ // Now add this to the correct queue.
+ //
+
+ NtfsQueueClose( NewIrpContext, DelayClose );
+
+ } except( FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH ) {
+
+ Result = FALSE;
+ }
+
+ return Result;
+}
+
+
+//
+// Internal support routine
+//
+
+NTSTATUS
+NtfsCommonClose (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN PVCB Vcb,
+ IN PCCB Ccb,
+ IN PBOOLEAN AcquiredVcb OPTIONAL,
+ IN PBOOLEAN ExclusiveVcb OPTIONAL,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN BOOLEAN ReadOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Close called by both the fsd and fsp
+ threads. Key for this routine is how to acquire the Vcb and whether to
+ leave the Vcb acquired on exit.
+
+ ExclusiveVcb - Acquire the Vcb exclusively if the file being closed has
+ multiple links. Also if the volume is not mounted or we are closing
+ one of the system streams used only by the filesystem or we are
+ performing system shutdown.
+
+ AcquiredVcb - Release the Vcb in this routine if the AcquiredVcb pointer
+ was not supplied. Also if the volume is not mounted or we are closing
+ a system file.
+
+Arguments:
+
+ FileObject - This is the file object for this open. Won't be specified if this
+ call is from the Fsp path.
+
+ Scb - Scb for this stream.
+
+ Fcb - Fcb for this stream.
+
+ Vcb - Vcb for this volume.
+
+ Ccb - User's Ccb for user files.
+
+ AcquiredVcb - If specified and TRUE then our caller has already acquired the
+ Vcb. If specified and FALSE then our caller hasn't acquired the Vcb but
+ would like to have it held on exit from this routine.
+
+ Look at the ExclusiveVcb boolean to determine how it was acquired.
+ Set to FALSE if this routine will free the Vcb.
+
+ ExclusiveVcb - If AcquiredVcb is TRUE then this boolean will indicate how
+ our caller has acquired this Vcb. Updated on exit if we leave the
+ Vcb held.
+
+ TypeOfOpen - Indicates the type of open for this stream.
+
+ ReadOnly - Indicates if the file object was for read-only access.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ BOOLEAN ReleaseVcb;
+ BOOLEAN LocalAcquiredVcb;
+ BOOLEAN LocalExclusiveVcb;
+
+ BOOLEAN SystemFile;
+ BOOLEAN RemovedFcb = FALSE;
+ BOOLEAN DontWait;
+ BOOLEAN NeedVcbExclusive = FALSE;
+
+ PLCB Lcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ DebugTrace( +1, Dbg, ("NtfsCommonClose\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ DontWait = FALSE;
+
+ } else {
+
+ DontWait = TRUE;
+ }
+
+ //
+ // Look at the input parameters to determine if we should hold the Vcb on
+ // exit.
+ //
+
+ if (ARGUMENT_PRESENT( AcquiredVcb )) {
+
+ //
+ // If the volume isn't mounted or this is a system file then
+ // acquire the Vcb exclusively and release on exit.
+ //
+
+ if ((FlagOn( Vcb->VcbState,
+ VCB_STATE_VOLUME_MOUNTED | VCB_STATE_FLAG_SHUTDOWN | VCB_STATE_PERFORMED_DISMOUNT ) == VCB_STATE_VOLUME_MOUNTED)
+
+ &&
+
+ (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER ||
+ NtfsSegmentNumber( &Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER ||
+ NtfsSegmentNumber( &Fcb->FileReference ) == VOLUME_DASD_NUMBER)) {
+
+ ReleaseVcb = FALSE;
+
+ } else {
+
+ //
+ // We want to release the Vcb but also want to acquire it exclusively.
+ //
+
+ ReleaseVcb = TRUE;
+ NeedVcbExclusive = TRUE;
+
+ if (!(*ExclusiveVcb) &&
+ *AcquiredVcb) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ *AcquiredVcb = FALSE;
+ }
+ }
+
+ } else {
+
+ ReleaseVcb = TRUE;
+ AcquiredVcb = &LocalAcquiredVcb;
+ ExclusiveVcb = &LocalExclusiveVcb;
+
+ *AcquiredVcb = FALSE;
+ }
+
+ //
+ // Loop here to acquire both the Vcb and Fcb. We want to acquire
+ // the Vcb exclusively if the file has multiple links.
+ //
+
+ while (TRUE) {
+
+ //
+ // If we haven't acquired the Vcb then perform an unsafe test and
+ // optimistically acquire it.
+ //
+
+ if (!(*AcquiredVcb)) {
+
+ if (NeedVcbExclusive ||
+ (Fcb->LcbQueue.Flink != Fcb->LcbQueue.Blink) ||
+ FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ if (!NtfsAcquireExclusiveVcb( IrpContext, Vcb, FALSE )) {
+
+ return STATUS_PENDING;
+ }
+
+ *ExclusiveVcb = TRUE;
+
+ } else {
+
+ if (!NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE )) {
+
+ return STATUS_PENDING;
+ }
+
+ *ExclusiveVcb = FALSE;
+ }
+
+ *AcquiredVcb = TRUE;
+ }
+
+ //
+ // Now try to acquire the Fcb. If we are unable to acquire it then
+ // release the Vcb and return. This can only be from the Fsd path
+ // since otherwise Wait will be TRUE.
+ //
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, DontWait )) {
+
+ //
+ // Always release the Vcb. This can only be from the Fsd thread.
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ *AcquiredVcb = FALSE;
+ return STATUS_PENDING;
+ }
+
+ if (*ExclusiveVcb) {
+
+ break;
+ }
+
+ //
+ // Otherwise we need to confirm that our unsafe test above was correct.
+ //
+
+ if ((Fcb->LcbQueue.Flink != Fcb->LcbQueue.Blink) ||
+ FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ NeedVcbExclusive = TRUE;
+ NtfsReleaseFcb( IrpContext, Fcb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ *AcquiredVcb = FALSE;
+
+ } else {
+
+ break;
+ }
+ }
+
+ //
+ // Set the wait flag in the IrpContext so we can acquire any other files
+ // we encounter.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ try {
+
+ //
+ // We take the same action for all open files. We
+ // delete the Ccb if present, and we decrement the close
+ // file counts.
+ //
+
+ if (Ccb != NULL) {
+
+ Lcb = Ccb->Lcb;
+ NtfsUnlinkCcbFromLcb( IrpContext, Ccb );
+ NtfsDeleteCcb( IrpContext, Fcb, &Ccb );
+
+ } else {
+
+ Lcb = NULL;
+ }
+
+ SystemFile = FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) || (TypeOfOpen == StreamFileOpen);
+ NtfsDecrementCloseCounts( IrpContext,
+ Scb,
+ Lcb,
+ SystemFile,
+ ReadOnly,
+ FALSE );
+
+ //
+ // If we had to write a log record for close, it can only be for duplicate
+ // information. We will commit that transaction here and remove
+ // the entry from the transaction table. We do it here so we won't
+ // fail inside the 'except' of a 'try-except'.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ try {
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ if (IrpContext->TransactionId != 0) {
+
+ //
+ // We couldn't write the commit record, we clean up as
+ // best we can.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+
+ IrpContext->TransactionId = 0;
+ }
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCommonClose );
+
+ if (ReleaseVcb) {
+
+ NtfsReleaseVcbCheckDelete( IrpContext, Vcb, IRP_MJ_CLOSE, FileObject );
+ *AcquiredVcb = FALSE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonClose -> returning\n") );
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Internal support routine, spinlock wrapper.
+//
+
+VOID
+NtfsQueueClose (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN DelayClose
+ )
+{
+ KIRQL SavedIrql;
+ BOOLEAN StartWorker = FALSE;
+
+
+ if (DelayClose) {
+
+ //
+ // Increment the delayed close count for the Fcb for this
+ // file.
+ //
+
+ InterlockedIncrement( &((PSCB) IrpContext->OriginatingIrp)->Fcb->DelayedCloseCount );
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &SavedIrql );
+
+ InsertTailList( &NtfsData.DelayedCloseList,
+ &IrpContext->WorkQueueItem.List );
+
+ NtfsData.DelayedCloseCount += 1;
+
+ if (NtfsData.DelayedCloseCount > NtfsMaxDelayedCloseCount) {
+
+ NtfsData.ReduceDelayedClose = TRUE;
+
+ if (!NtfsData.AsyncCloseActive) {
+
+ NtfsData.AsyncCloseActive = TRUE;
+ StartWorker = TRUE;
+ }
+ }
+
+ } else {
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &SavedIrql );
+
+ InsertTailList( &NtfsData.AsyncCloseList,
+ &IrpContext->WorkQueueItem.List );
+
+ NtfsData.AsyncCloseCount += 1;
+
+ if (!NtfsData.AsyncCloseActive) {
+
+ NtfsData.AsyncCloseActive = TRUE;
+
+ StartWorker = TRUE;
+ }
+ }
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, SavedIrql );
+
+ if (StartWorker) {
+
+ ExQueueWorkItem( &NtfsData.NtfsCloseItem, CriticalWorkQueue );
+ }
+}
+
+
+//
+// Internal support routine, spinlock wrapper.
+//
+
+PIRP_CONTEXT
+NtfsRemoveClose (
+ IN PVCB Vcb OPTIONAL,
+ IN BOOLEAN ThrottleCreate
+ )
+{
+
+ PLIST_ENTRY Entry;
+ KIRQL SavedIrql;
+ PIRP_CONTEXT IrpContext = NULL;
+ BOOLEAN FromDelayedClose = FALSE;
+
+ ASSERT( Vcb == NULL || NtfsIsExclusiveVcb( Vcb ));
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &SavedIrql );
+
+ //
+ // First check the list of async closes.
+ //
+
+ if (!IsListEmpty( &NtfsData.AsyncCloseList )) {
+
+ Entry = NtfsData.AsyncCloseList.Flink;
+
+ while (Entry != &NtfsData.AsyncCloseList) {
+
+ //
+ // Extract the IrpContext.
+ //
+
+ IrpContext = CONTAINING_RECORD( Entry,
+ IRP_CONTEXT,
+ WorkQueueItem.List );
+
+ //
+ // If no Vcb was specified or this Vcb is for our volume
+ // then perform the close.
+ //
+
+ if (!ARGUMENT_PRESENT( Vcb ) ||
+ IrpContext->Vcb == Vcb) {
+
+ RemoveEntryList( Entry );
+ NtfsData.AsyncCloseCount -= 1;
+
+ break;
+
+ } else {
+
+ IrpContext = NULL;
+ Entry = Entry->Flink;
+ }
+ }
+ }
+
+ //
+ // If we didn't find anything look through the delayed close
+ // queue.
+ //
+
+ if (IrpContext == NULL) {
+
+ //
+ // Now check our delayed close list.
+ //
+
+ if (ARGUMENT_PRESENT( Vcb )) {
+
+ Entry = NtfsData.DelayedCloseList.Flink;
+ IrpContext = NULL;
+
+ //
+ // If we were given a Vcb, only do the closes for this volume.
+ //
+
+ while (Entry != &NtfsData.DelayedCloseList) {
+
+ //
+ // Extract the IrpContext.
+ //
+
+ IrpContext = CONTAINING_RECORD( Entry,
+ IRP_CONTEXT,
+ WorkQueueItem.List );
+
+ //
+ // Is this close on our volume?
+ //
+
+ if (IrpContext->Vcb == Vcb) {
+
+ RemoveEntryList( Entry );
+ NtfsData.DelayedCloseCount -= 1;
+ FromDelayedClose = TRUE;
+ break;
+
+ } else {
+
+ IrpContext = NULL;
+ Entry = Entry->Flink;
+ }
+ }
+
+ //
+ // Check if need to reduce the delayed close count.
+ //
+
+ } else if (NtfsData.ReduceDelayedClose) {
+
+ if (NtfsData.DelayedCloseCount > NtfsMinDelayedCloseCount) {
+
+ //
+ // Do any closes over the limit.
+ //
+
+ Entry = RemoveHeadList( &NtfsData.DelayedCloseList );
+
+ NtfsData.DelayedCloseCount -= 1;
+
+ //
+ // Extract the IrpContext.
+ //
+
+ IrpContext = CONTAINING_RECORD( Entry,
+ IRP_CONTEXT,
+ WorkQueueItem.List );
+ FromDelayedClose = TRUE;
+
+ } else {
+
+ NtfsData.ReduceDelayedClose = FALSE;
+ }
+ }
+ }
+
+ //
+ // If this is the delayed close case then decrement the delayed close count
+ // on this Fcb.
+ //
+
+ if (FromDelayedClose) {
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, SavedIrql );
+
+ InterlockedDecrement( &((PSCB) IrpContext->OriginatingIrp)->Fcb->DelayedCloseCount );
+
+ //
+ // If we are returning NULL, show that we are done.
+ //
+
+ } else {
+
+ if (!ARGUMENT_PRESENT( Vcb ) &&
+ (IrpContext == NULL) &&
+ !ThrottleCreate) {
+
+ NtfsData.AsyncCloseActive = FALSE;
+ }
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, SavedIrql );
+ }
+
+ return IrpContext;
+}
diff --git a/private/ntos/cntfs/colatsup.c b/private/ntos/cntfs/colatsup.c
new file mode 100644
index 000000000..f8acedbd8
--- /dev/null
+++ b/private/ntos/cntfs/colatsup.c
@@ -0,0 +1,678 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ ColatSup.c
+
+Abstract:
+
+ This module implements the collation routine callbacks for Ntfs
+
+Author:
+
+ Tom Miller [TomM] 26-Nov-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_INDEXSUP)
+
+FSRTL_COMPARISON_RESULT
+NtfsFileCompareValues (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN FSRTL_COMPARISON_RESULT WildCardIs,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+NtfsFileIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+NtfsFileIsEqual (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+NtfsFileContainsWildcards (
+ IN PVOID Value
+ );
+
+VOID
+NtfsFileUpcaseValue (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value
+ );
+
+FSRTL_COMPARISON_RESULT
+DummyCompareValues (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN FSRTL_COMPARISON_RESULT WildCardIs,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+DummyIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+DummyIsEqual (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+DummyContainsWildcards (
+ IN PVOID Value
+ );
+
+VOID
+DummyUpcaseValue (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN OUT PVOID Value
+ );
+
+PCOMPARE_VALUES NtfsCompareValues[COLLATION_NUMBER_RULES] = {&DummyCompareValues,
+ &NtfsFileCompareValues,
+ &DummyCompareValues};
+
+PIS_IN_EXPRESSION NtfsIsInExpression[COLLATION_NUMBER_RULES] = {&DummyIsInExpression,
+ &NtfsFileIsInExpression,
+ &DummyIsInExpression};
+
+PARE_EQUAL NtfsIsEqual[COLLATION_NUMBER_RULES] = {&DummyIsEqual,
+ &NtfsFileIsEqual,
+ &DummyIsEqual};
+
+PCONTAINS_WILDCARD NtfsContainsWildcards[COLLATION_NUMBER_RULES] = {&DummyContainsWildcards,
+ &NtfsFileContainsWildcards,
+ &DummyContainsWildcards};
+
+PUPCASE_VALUE NtfsUpcaseValue[COLLATION_NUMBER_RULES] = {&DummyUpcaseValue,
+ &NtfsFileUpcaseValue,
+ &DummyUpcaseValue};
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, DummyCompareValues)
+#pragma alloc_text(PAGE, DummyContainsWildcards)
+#pragma alloc_text(PAGE, DummyIsEqual)
+#pragma alloc_text(PAGE, DummyIsInExpression)
+#pragma alloc_text(PAGE, DummyUpcaseValue)
+#pragma alloc_text(PAGE, NtfsFileCompareValues)
+#pragma alloc_text(PAGE, NtfsFileContainsWildcards)
+#pragma alloc_text(PAGE, NtfsFileIsEqual)
+#pragma alloc_text(PAGE, NtfsFileIsInExpression)
+#pragma alloc_text(PAGE, NtfsFileUpcaseValue)
+#endif
+
+
+FSRTL_COMPARISON_RESULT
+NtfsFileCompareValues (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN FSRTL_COMPARISON_RESULT WildCardIs,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+RoutineDescription:
+
+ This routine is called to compare a file name expression (the value) with
+ a file name from the index to see if it is less than, equal to or greater
+ than. If a wild card is encountered in the expression, WildCardIs is
+ returned.
+
+Arguments:
+
+ Value - Pointer to the value expression, which is a FILE_NAME.
+
+ IndexEntry - Pointer to the index entry being compared to.
+
+ WildCardIs - Value to be returned if a wild card is encountered in the
+ expression.
+
+ IgnoreCase - whether case should be ignored or not.
+
+ReturnValue:
+
+ Result of the comparison
+
+--*/
+
+{
+ PFILE_NAME ValueName, IndexName;
+ UNICODE_STRING ValueString, IndexString;
+
+ PAGED_CODE();
+
+ //
+ // Point to the file name attribute records.
+ //
+
+ ValueName = (PFILE_NAME)Value;
+ IndexName = (PFILE_NAME)(IndexEntry + 1);
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ValueString.Length =
+ ValueString.MaximumLength = (USHORT)ValueName->FileNameLength << 1;
+ ValueString.Buffer = &ValueName->FileName[0];
+
+ IndexString.Length =
+ IndexString.MaximumLength = (USHORT)IndexName->FileNameLength << 1;
+ IndexString.Buffer = &IndexName->FileName[0];
+
+ return NtfsCollateNames( UnicodeTable,
+ UnicodeTableSize,
+ &ValueString,
+ &IndexString,
+ WildCardIs,
+ IgnoreCase );
+}
+
+
+BOOLEAN
+NtfsFileIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+RoutineDescription:
+
+ This routine is called to compare a file name expression (the value) with
+ a file name from the index to see if the file name is a match in this expression.
+
+Arguments:
+
+ Value - Pointer to the value expression, which is a FILE_NAME.
+
+ IndexEntry - Pointer to the index entry being compared to.
+
+ IgnoreCase - whether case should be ignored or not.
+
+ReturnValue:
+
+ TRUE - if the file name is in the specified expression.
+
+--*/
+
+{
+ PFILE_NAME ValueName, IndexName;
+ UNICODE_STRING ValueString, IndexString;
+
+ PAGED_CODE();
+
+ if (NtfsSegmentNumber( &IndexEntry->FileReference ) < FIRST_USER_FILE_NUMBER &&
+ NtfsProtectSystemFiles) {
+
+ return FALSE;
+ }
+
+ //
+ // Point to the file name attribute records.
+ //
+
+ ValueName = (PFILE_NAME)Value;
+ IndexName = (PFILE_NAME)(IndexEntry + 1);
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ValueString.Length =
+ ValueString.MaximumLength = (USHORT)ValueName->FileNameLength << 1;
+ ValueString.Buffer = &ValueName->FileName[0];
+
+ IndexString.Length =
+ IndexString.MaximumLength = (USHORT)IndexName->FileNameLength << 1;
+ IndexString.Buffer = &IndexName->FileName[0];
+
+ return NtfsIsNameInExpression( UnicodeTable,
+ &ValueString,
+ &IndexString,
+ IgnoreCase );
+}
+
+
+BOOLEAN
+NtfsFileIsEqual (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+RoutineDescription:
+
+ This routine is called to compare a constant file name (the value) with
+ a file name from the index to see if the file name is an exact match.
+
+Arguments:
+
+ Value - Pointer to the value expression, which is a FILE_NAME.
+
+ IndexEntry - Pointer to the index entry being compared to.
+
+ IgnoreCase - whether case should be ignored or not.
+
+ReturnValue:
+
+ TRUE - if the file name is a constant match.
+
+--*/
+
+{
+ PFILE_NAME ValueName, IndexName;
+ UNICODE_STRING ValueString, IndexString;
+
+ PAGED_CODE();
+
+ //
+ // Point to the file name attribute records.
+ //
+
+ ValueName = (PFILE_NAME)Value;
+ IndexName = (PFILE_NAME)(IndexEntry + 1);
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ValueString.Length =
+ ValueString.MaximumLength = (USHORT)ValueName->FileNameLength << 1;
+ ValueString.Buffer = &ValueName->FileName[0];
+
+ IndexString.Length =
+ IndexString.MaximumLength = (USHORT)IndexName->FileNameLength << 1;
+ IndexString.Buffer = &IndexName->FileName[0];
+
+ return NtfsAreNamesEqual( UnicodeTable,
+ &ValueString,
+ &IndexString,
+ IgnoreCase );
+}
+
+
+BOOLEAN
+NtfsFileContainsWildcards (
+ IN PVOID Value
+ )
+
+/*++
+
+RoutineDescription:
+
+ This routine is called to see if a file name attribute contains wildcards.
+
+Arguments:
+
+ Value - Pointer to the value expression, which is a FILE_NAME.
+
+
+ReturnValue:
+
+ TRUE - if the file name contains a wild card.
+
+--*/
+
+{
+ PFILE_NAME ValueName;
+ UNICODE_STRING ValueString;
+
+ PAGED_CODE();
+
+ //
+ // Point to the file name attribute records.
+ //
+
+ ValueName = (PFILE_NAME)Value;
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ValueString.Length =
+ ValueString.MaximumLength = (USHORT)ValueName->FileNameLength << 1;
+ ValueString.Buffer = &ValueName->FileName[0];
+
+ return FsRtlDoesNameContainWildCards( &ValueString );
+}
+
+
+VOID
+NtfsFileUpcaseValue (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value
+ )
+
+/*++
+
+RoutineDescription:
+
+ This routine is called to upcase a file name attribute in place.
+
+Arguments:
+
+ Value - Pointer to the value expression, which is a FILE_NAME.
+
+ ValueLength - Length of the value expression in bytes.
+
+ReturnValue:
+
+ None.
+
+--*/
+
+{
+ PFILE_NAME ValueName;
+ UNICODE_STRING ValueString;
+
+ PAGED_CODE();
+
+ //
+ // Point to the file name attribute records.
+ //
+
+ ValueName = (PFILE_NAME)Value;
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ValueString.Length =
+ ValueString.MaximumLength = (USHORT)ValueName->FileNameLength << 1;
+ ValueString.Buffer = &ValueName->FileName[0];
+
+ NtfsUpcaseName( UnicodeTable, UnicodeTableSize, &ValueString );
+
+ return;
+}
+
+
+//
+// The other collation rules are currently unused.
+//
+
+FSRTL_COMPARISON_RESULT
+DummyCompareValues (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN FSRTL_COMPARISON_RESULT WildCardIs,
+ IN BOOLEAN IgnoreCase
+ )
+
+{
+ //
+ // Most parameters are ignored since this is a catch-all for
+ // a corrupt volume. We simply raise to indicate the corruption
+ //
+
+ UNREFERENCED_PARAMETER( UnicodeTable );
+ UNREFERENCED_PARAMETER( UnicodeTableSize );
+ UNREFERENCED_PARAMETER( IgnoreCase );
+ UNREFERENCED_PARAMETER( WildCardIs );
+ UNREFERENCED_PARAMETER( IndexEntry );
+ UNREFERENCED_PARAMETER( Value );
+
+ PAGED_CODE();
+
+ ASSERTMSG("Unused collation rule\n", FALSE);
+
+ return EqualTo;
+}
+
+BOOLEAN
+DummyIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ )
+
+{
+ //
+ // Most parameters are ignored since this is a catch-all for
+ // a corrupt volume. We simply raise to indicate the corruption
+ //
+
+ UNREFERENCED_PARAMETER( UnicodeTable );
+ UNREFERENCED_PARAMETER( Value );
+ UNREFERENCED_PARAMETER( IndexEntry );
+ UNREFERENCED_PARAMETER( IgnoreCase );
+
+ PAGED_CODE();
+
+ ASSERTMSG("Unused collation rule\n", FALSE);
+ return EqualTo;
+}
+
+BOOLEAN
+DummyIsEqual (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ )
+
+{
+ //
+ // Most parameters are ignored since this is a catch-all for
+ // a corrupt volume. We simply raise to indicate the corruption
+ //
+
+ UNREFERENCED_PARAMETER( UnicodeTable );
+ UNREFERENCED_PARAMETER( Value );
+ UNREFERENCED_PARAMETER( IndexEntry );
+ UNREFERENCED_PARAMETER( IgnoreCase );
+
+ PAGED_CODE();
+
+ ASSERTMSG("Unused collation rule\n", FALSE);
+ return EqualTo;
+}
+
+BOOLEAN
+DummyContainsWildcards (
+ IN PVOID Value
+ )
+
+{
+ //
+ // Most parameters are ignored since this is a catch-all for
+ // a corrupt volume. We simply raise to indicate the corruption
+ //
+
+ UNREFERENCED_PARAMETER( Value );
+
+ PAGED_CODE();
+
+ ASSERTMSG("Unused collation rule\n", FALSE);
+ return EqualTo;
+}
+
+VOID
+DummyUpcaseValue (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value
+ )
+
+{
+ //
+ // Most parameters are ignored since this is a catch-all for
+ // a corrupt volume. We simply raise to indicate the corruption
+ //
+
+ UNREFERENCED_PARAMETER( UnicodeTable );
+ UNREFERENCED_PARAMETER( UnicodeTableSize );
+ UNREFERENCED_PARAMETER( Value );
+
+ PAGED_CODE();
+
+ ASSERTMSG("Unused collation rule\n", FALSE);
+ return;
+}
+
+//
+// The following routines are not general index match functions, but rather
+// specific file name match functions used only for automatic Dos Name generation.
+//
+
+
+BOOLEAN
+NtfsFileNameIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PFILE_NAME ExpressionName,
+ IN PFILE_NAME FileName,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+RoutineDescription:
+
+ This is a special match routine for matching FILE_NAME attributes only,
+ which is used only by the special code paths dealing with automatically
+ generated short names.
+
+ This routine is called to compare a file name expression (the value) with
+ a file name from the index to see if the file name is a match in this expression.
+
+Arguments:
+
+ ExpressionName - pointer to the expression for file name.
+
+ FileName - Pointer to the FileName to match.
+
+ IgnoreCase - whether case should be ignored or not.
+
+ReturnValue:
+
+ TRUE - if the file name is in the specified expression.
+
+--*/
+
+{
+ UNICODE_STRING ExpressionString, FileString;
+
+ PAGED_CODE();
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ExpressionString.Length =
+ ExpressionString.MaximumLength = (USHORT)ExpressionName->FileNameLength << 1;
+ ExpressionString.Buffer = &ExpressionName->FileName[0];
+
+ FileString.Length =
+ FileString.MaximumLength = (USHORT)FileName->FileNameLength << 1;
+ FileString.Buffer = &FileName->FileName[0];
+
+ return NtfsIsNameInExpression( UnicodeTable,
+ &ExpressionString,
+ &FileString,
+ IgnoreCase );
+}
+
+
+BOOLEAN
+NtfsFileNameIsEqual (
+ IN PWCH UnicodeTable,
+ IN PFILE_NAME ExpressionName,
+ IN PFILE_NAME FileName,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+RoutineDescription:
+
+ This is a special match routine for matching FILE_NAME attributes only,
+ which is used only by the special code paths dealing with automatically
+ generated short names.
+
+ This routine is called to compare a constant file name (the value) with
+ a file name from the index to see if the file name is an exact match.
+
+Arguments:
+
+ ExpressionName - pointer to the expression for file name.
+
+ FileName - Pointer to the FileName to match.
+
+ IgnoreCase - whether case should be ignored or not.
+
+ReturnValue:
+
+ TRUE - if the file name is a constant match.
+
+--*/
+
+{
+ UNICODE_STRING ExpressionString, FileString;
+
+ PAGED_CODE();
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ ExpressionString.Length =
+ ExpressionString.MaximumLength = (USHORT)ExpressionName->FileNameLength << 1;
+ ExpressionString.Buffer = &ExpressionName->FileName[0];
+
+ FileString.Length =
+ FileString.MaximumLength = (USHORT)FileName->FileNameLength << 1;
+ FileString.Buffer = &FileName->FileName[0];
+
+ return NtfsAreNamesEqual( UnicodeTable,
+ &ExpressionString,
+ &FileString,
+ IgnoreCase );
+}
+
diff --git a/private/ntos/cntfs/create.c b/private/ntos/cntfs/create.c
new file mode 100644
index 000000000..c2706b3f3
--- /dev/null
+++ b/private/ntos/cntfs/create.c
@@ -0,0 +1,11241 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Create.c
+
+Abstract:
+
+ This module implements the File Create routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Brian Andrew [BrianAn] 10-Dec-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CREATE)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('CFtN')
+
+//
+// Check for stack usage prior to the create call.
+//
+
+#ifdef _X86_
+#define OVERFLOW_CREATE_THRESHHOLD (0x1200)
+#else
+#define OVERFLOW_CREATE_THRESHHOLD (0x1B00)
+#endif // _X86_
+
+//
+// Local macros
+//
+
+//
+// BOOLEAN
+// NtfsVerifyNameIsDirectory (
+// IN PIRP_CONTEXT IrpContext,
+// IN PUNICODE_STRING AttrName,
+// IN PUNICODE_STRING AttrCodeName
+// )
+//
+
+#define NtfsVerifyNameIsDirectory( IC, AN, ACN ) \
+ ( ( ((ACN)->Length == 0) \
+ || NtfsAreNamesEqual( IC->Vcb->UpcaseTable, ACN, &NtfsIndexAllocation, TRUE )) \
+ && \
+ ( ((AN)->Length == 0) \
+ || NtfsAreNamesEqual( IC->Vcb->UpcaseTable, AN, &NtfsFileNameIndex, TRUE )))
+
+//
+// These are the flags used by the I/O system in deciding whether
+// to apply the share access modes.
+//
+
+#define NtfsAccessDataFlags ( \
+ FILE_EXECUTE \
+ | FILE_READ_DATA \
+ | FILE_WRITE_DATA \
+ | FILE_APPEND_DATA \
+ | DELETE \
+)
+
+//
+// Local definitions
+//
+
+typedef enum _SHARE_MODIFICATION_TYPE {
+
+ CheckShareAccess,
+ UpdateShareAccess,
+ SetShareAccess
+
+} SHARE_MODIFICATION_TYPE, *PSHARE_MODIFICATION_TYPE;
+
+UNICODE_STRING NtfsVolumeDasd = CONSTANT_UNICODE_STRING ( L"$Volume" );
+
+LUID NtfsSecurityPrivilege = { SE_SECURITY_PRIVILEGE, 0 };
+
+//
+// Local support routines.
+//
+
+NTSTATUS
+NtfsOpenFcbById (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PLCB ParentLcb OPTIONAL,
+ IN OUT PFCB *CurrentFcb,
+ IN BOOLEAN UseCurrentFcb,
+ IN FILE_REFERENCE FileReference,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOpenExistingPrefixFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PLCB Lcb OPTIONAL,
+ IN ULONG FullPathNameLength,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOpenTargetDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PLCB ParentLcb OPTIONAL,
+ IN OUT PUNICODE_STRING FullPathName,
+ IN ULONG FinalNameLength,
+ IN BOOLEAN TargetExisted,
+ IN BOOLEAN DosOnlyComponent,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOpenFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PSCB ParentScb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN UNICODE_STRING FullPathName,
+ IN UNICODE_STRING FinalName,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN OpenById,
+ IN PQUICK_INDEX QuickIndex,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsCreateNewFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PSCB ParentScb,
+ IN PFILE_NAME FileNameAttr,
+ IN UNICODE_STRING FullPathName,
+ IN UNICODE_STRING FinalName,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN OpenById,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+PLCB
+NtfsOpenSubdirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN UNICODE_STRING Name,
+ IN BOOLEAN TraverseAccessCheck,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ IN PINDEX_ENTRY IndexEntry
+ );
+
+NTSTATUS
+NtfsOpenAttributeInExistingFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOpenExistingAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ IN BOOLEAN DirectoryOpen,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOverwriteAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN BOOLEAN Supersede,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+NTSTATUS
+NtfsOpenNewAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN OpenById,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+BOOLEAN
+NtfsParseNameForCreate (
+ IN PIRP_CONTEXT IrpContext,
+ IN UNICODE_STRING String,
+ IN OUT PUNICODE_STRING FileObjectString,
+ IN OUT PUNICODE_STRING OriginalString,
+ IN OUT PUNICODE_STRING NewNameString,
+ OUT PUNICODE_STRING AttrName,
+ OUT PUNICODE_STRING AttrCodeName
+ );
+
+NTSTATUS
+NtfsCheckValidAttributeAccess (
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PDUPLICATED_INFORMATION Info OPTIONAL,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN TrailingBackslash,
+ OUT PATTRIBUTE_TYPE_CODE AttrTypeCode,
+ OUT PULONG CcbFlags,
+ OUT PBOOLEAN IndexedAttribute
+ );
+
+NTSTATUS
+NtfsOpenAttributeCheck (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ OUT PSCB *ThisScb,
+ OUT PSHARE_MODIFICATION_TYPE ShareModificationType
+ );
+
+VOID
+NtfsAddEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB ThisFcb,
+ IN PFILE_FULL_EA_INFORMATION EaBuffer OPTIONAL,
+ IN ULONG EaLength,
+ OUT PIO_STATUS_BLOCK Iosb
+ );
+
+VOID
+NtfsInitializeFcbAndStdInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ThisFcb,
+ IN BOOLEAN Directory,
+ IN BOOLEAN Compressed,
+ IN ULONG FileAttributes,
+ IN PLONGLONG SetCreationTime OPTIONAL
+ );
+
+VOID
+NtfsCreateAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN OUT PFCB ThisFcb,
+ IN OUT PSCB ThisScb,
+ IN PLCB ThisLcb,
+ IN LONGLONG AllocationSize,
+ IN BOOLEAN LogIt
+ );
+
+VOID
+NtfsRemoveDataAttributes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ThisFcb,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFILE_OBJECT FileObject,
+ IN ULONG LastFileNameOffset,
+ IN BOOLEAN OpenById
+ );
+
+VOID
+NtfsReplaceAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PSCB ThisScb,
+ IN PLCB ThisLcb,
+ IN LONGLONG AllocationSize
+ );
+
+NTSTATUS
+NtfsOpenAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN SHARE_MODIFICATION_TYPE ShareModificationType,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN ULONG CcbFlags,
+ IN PVOID NetworkInfo OPTIONAL,
+ IN OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ );
+
+VOID
+NtfsBackoutFailedOpens (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PFCB ThisFcb,
+ IN PSCB ThisScb OPTIONAL,
+ IN PCCB ThisCcb OPTIONAL
+ );
+
+VOID
+NtfsUpdateScbFromMemory (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN POLD_SCB_SNAPSHOT ScbSizes
+ );
+
+VOID
+NtfsOplockPrePostIrp (
+ IN PVOID Context,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCheckExistingFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG CcbFlags
+ );
+
+NTSTATUS
+NtfsBreakBatchOplock (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ OUT PSCB *ThisScb
+ );
+
+NTSTATUS
+NtfsCompleteLargeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PLCB Lcb,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN BOOLEAN CreateFileCase,
+ IN BOOLEAN DeleteOnClose
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAddEa)
+#pragma alloc_text(PAGE, NtfsBackoutFailedOpens)
+#pragma alloc_text(PAGE, NtfsBreakBatchOplock)
+#pragma alloc_text(PAGE, NtfsCheckExistingFile)
+#pragma alloc_text(PAGE, NtfsCheckValidAttributeAccess)
+#pragma alloc_text(PAGE, NtfsCommonCreate)
+#pragma alloc_text(PAGE, NtfsCommonVolumeOpen)
+#pragma alloc_text(PAGE, NtfsCompleteLargeAllocation)
+#pragma alloc_text(PAGE, NtfsCreateAttribute)
+#pragma alloc_text(PAGE, NtfsCreateNewFile)
+#pragma alloc_text(PAGE, NtfsFsdCreate)
+#pragma alloc_text(PAGE, NtfsInitializeFcbAndStdInfo)
+#pragma alloc_text(PAGE, NtfsNetworkOpenCreate)
+#pragma alloc_text(PAGE, NtfsOpenAttribute)
+#pragma alloc_text(PAGE, NtfsOpenAttributeCheck)
+#pragma alloc_text(PAGE, NtfsOpenAttributeInExistingFile)
+#pragma alloc_text(PAGE, NtfsOpenExistingAttr)
+#pragma alloc_text(PAGE, NtfsOpenExistingPrefixFcb)
+#pragma alloc_text(PAGE, NtfsOpenFcbById)
+#pragma alloc_text(PAGE, NtfsOpenFile)
+#pragma alloc_text(PAGE, NtfsOpenNewAttr)
+#pragma alloc_text(PAGE, NtfsOpenSubdirectory)
+#pragma alloc_text(PAGE, NtfsOpenTargetDirectory)
+#pragma alloc_text(PAGE, NtfsOplockPrePostIrp)
+#pragma alloc_text(PAGE, NtfsOverwriteAttr)
+#pragma alloc_text(PAGE, NtfsParseNameForCreate)
+#pragma alloc_text(PAGE, NtfsRemoveDataAttributes)
+#pragma alloc_text(PAGE, NtfsReplaceAttribute)
+#pragma alloc_text(PAGE, NtfsUpdateScbFromMemory)
+#endif
+
+
+NTSTATUS
+NtfsFsdCreate (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Create.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext;
+
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // If we were called with our file system device object instead of a
+ // volume device object, just complete this request with STATUS_SUCCESS
+ //
+
+ if (VolumeDeviceObject->DeviceObject.Size == (USHORT)sizeof(DEVICE_OBJECT)) {
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = FILE_OPENED;
+
+ IoCompleteRequest( Irp, IO_DISK_INCREMENT );
+
+ return STATUS_SUCCESS;
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsFsdCreate\n") );
+
+ //
+ // Call the common Create routine
+ //
+
+ IrpContext = NULL;
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_DASD_OPEN )) {
+
+ Status = NtfsCommonVolumeOpen( IrpContext, Irp );
+
+ } else {
+
+ Status = NtfsCommonCreate( IrpContext, Irp, NULL );
+ }
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // exception code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdCreate -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsNetworkOpenCreate (
+ IN PIRP Irp,
+ OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the fast open create for path-based queries.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+ Buffer - Buffer to return the network query information
+
+ DeviceObject - Supplies the volume device object where the file exists
+
+Return Value:
+
+ BOOLEAN - Indicates whether or not the fast path could be taken.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+ BOOLEAN Result = TRUE;
+ BOOLEAN DasdOpen = FALSE;
+
+ NTSTATUS Status;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ //
+ // Call the common Create routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ try {
+
+ IrpContext = NtfsCreateIrpContext( Irp, TRUE );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ Status = NtfsCommonCreate( IrpContext, Irp, Buffer );
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // Catch the case where someone in attempting this on a DASD open.
+ //
+
+ if ((IrpContext != NULL) &&
+ FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_DASD_OPEN )) {
+
+ DasdOpen = TRUE;
+ }
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // exception code. Since there is no Irp the exception package
+ // will always deallocate the IrpContext so we won't do
+ // any retry in this path.
+ //
+
+ Status = NtfsProcessException( IrpContext, NULL, GetExceptionCode() );
+
+ //
+ // Always fail the DASD case.
+ //
+
+ if (DasdOpen) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ //
+ // Return STATUS_FILE_LOCK_CONFLICT for any retryable error.
+ //
+
+ if ((Status == STATUS_CANT_WAIT) || (Status == STATUS_LOG_FILE_FULL)) {
+
+ Result = FALSE;
+ Status = STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ Irp->IoStatus.Status = Status;
+ return Result;
+}
+
+
+NTSTATUS
+NtfsCommonCreate (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ OUT PFILE_NETWORK_OPEN_INFORMATION NetworkInfo OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Create called by both the fsd and fsp
+ threads. If this open has already been detected to be a volume open then
+ take we will take the volume open path instead.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+ NetworkInfo - Optional buffer to return the queried data for
+ NetworkInformation. Its presence indicates that we should not
+ do a full open.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PFILE_OBJECT RelatedFileObject;
+
+ UNICODE_STRING AttrName;
+ UNICODE_STRING AttrCodeName;
+
+ PVCB Vcb;
+
+ //
+ // The following are used to teardown any Lcb/Fcb this
+ // routine is responsible for.
+ //
+
+ PLCB LcbForTeardown = NULL;
+
+ //
+ // The following indicate how far down the tree we have scanned.
+ //
+
+ PFCB ParentFcb;
+ PLCB CurrentLcb;
+ PFCB CurrentFcb = NULL;
+ PSCB LastScb = NULL;
+ PSCB CurrentScb;
+ PLCB NextLcb;
+
+ BOOLEAN AcquiredVcb = FALSE;
+ BOOLEAN DeleteVcb = FALSE;
+
+ //
+ // The following are the results of open operations.
+ //
+
+ PSCB ThisScb = NULL;
+ PCCB ThisCcb = NULL;
+
+ //
+ // The following are the in-memory structures associated with
+ // the relative file object.
+ //
+
+ TYPE_OF_OPEN RelatedFileObjectTypeOfOpen;
+ PFCB RelatedFcb;
+ PSCB RelatedScb;
+ PCCB RelatedCcb;
+
+ BOOLEAN DosOnlyComponent = FALSE;
+ BOOLEAN CreateFileCase = FALSE;
+ BOOLEAN DeleteOnClose = FALSE;
+ BOOLEAN TrailingBackslash = FALSE;
+ BOOLEAN TraverseAccessCheck;
+ BOOLEAN IgnoreCase;
+ BOOLEAN OpenFileById;
+ UCHAR CreateDisposition;
+
+ BOOLEAN CheckForValidName;
+ BOOLEAN FirstPass;
+
+ BOOLEAN FoundEntry = FALSE;
+
+ PFILE_NAME FileNameAttr = NULL;
+ USHORT FileNameAttrLength = 0;
+
+ PINDEX_ENTRY IndexEntry;
+ PBCB IndexEntryBcb = NULL;
+
+ QUICK_INDEX QuickIndex;
+
+ //
+ // The following unicode strings are used to track the names
+ // during the open operation. They may point to the same
+ // buffer so careful checks must be done at cleanup.
+ //
+ // OriginalFileName - This is the value to restore to the file
+ // object on error cleanup. This will containg the
+ // attribute type codes and attribute names if present.
+ //
+ // FullFileName - This is the constructed string which contains
+ // only the name components. It may point to the same
+ // buffer as the original name but the length value is
+ // adjusted to cut off the attribute code and name.
+ //
+ // ExactCaseName - This is the version of the full filename
+ // exactly as given by the caller. Used to preserve the
+ // case given by the caller in the event we do a case
+ // insensitive lookup. May point to the same buffer as
+ // the original name.
+ //
+ // RemainingName - This is the portion of the full name still
+ // to parse.
+ //
+ // FinalName - This is the current component of the full name.
+ //
+ // CaseInsensitiveIndex - This is the offset in the full file
+ // where we performed upcasing. We need to restore the
+ // exact case on failures and if we are creating a file.
+ //
+
+ OPLOCK_CLEANUP OplockCleanup;
+
+ PUNICODE_STRING OriginalFileName = &OplockCleanup.OriginalFileName;
+ PUNICODE_STRING FullFileName = &OplockCleanup.FullFileName;
+ PUNICODE_STRING ExactCaseName = &OplockCleanup.ExactCaseName;
+
+ UNICODE_STRING RemainingName;
+ UNICODE_STRING FinalName;
+ ULONG CaseInsensitiveIndex;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ OplockCleanup.FileObject = IrpSp->FileObject;
+
+ DebugTrace( +1, Dbg, ("NtfsCommonCreate: Entered\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("->Flags = %08lx\n", Irp->Flags) );
+ DebugTrace( 0, Dbg, ("->FileObject = %08lx\n", IrpSp->FileObject) );
+ DebugTrace( 0, Dbg, ("->RelatedFileObject = %08lx\n", IrpSp->FileObject->RelatedFileObject) );
+ DebugTrace( 0, Dbg, ("->FileName = %Z\n", &IrpSp->FileObject->FileName) );
+ DebugTrace( 0, Dbg, ("->AllocationSize = %08lx %08lx\n", Irp->Overlay.AllocationSize.LowPart,
+ Irp->Overlay.AllocationSize.HighPart ) );
+ DebugTrace( 0, Dbg, ("->EaBuffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+ DebugTrace( 0, Dbg, ("->EaLength = %08lx\n", IrpSp->Parameters.Create.EaLength) );
+ DebugTrace( 0, Dbg, ("->DesiredAccess = %08lx\n", IrpSp->Parameters.Create.SecurityContext->DesiredAccess) );
+ DebugTrace( 0, Dbg, ("->Options = %08lx\n", IrpSp->Parameters.Create.Options) );
+ DebugTrace( 0, Dbg, ("->FileAttributes = %04x\n", IrpSp->Parameters.Create.FileAttributes) );
+ DebugTrace( 0, Dbg, ("->ShareAccess = %04x\n", IrpSp->Parameters.Create.ShareAccess) );
+ DebugTrace( 0, Dbg, ("->Directory = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_DIRECTORY_FILE )) );
+ DebugTrace( 0, Dbg, ("->NonDirectoryFile = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NON_DIRECTORY_FILE )) );
+ DebugTrace( 0, Dbg, ("->NoIntermediateBuffering = %04x\n", FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NO_INTERMEDIATE_BUFFERING )) );
+ DebugTrace( 0, Dbg, ("->CreateDisposition = %04x\n", (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff) );
+ DebugTrace( 0, Dbg, ("->IsPagingFile = %04x\n", FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) );
+ DebugTrace( 0, Dbg, ("->OpenTargetDirectory = %04x\n", FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) );
+ DebugTrace( 0, Dbg, ("->CaseSensitive = %04x\n", FlagOn( IrpSp->Flags, SL_CASE_SENSITIVE )) );
+
+ //
+ // Verify that we can wait and acquire the Vcb exclusively.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ DebugTrace( 0, Dbg, ("Can't wait in create\n") );
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonCreate: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Update the IrpContext with the oplock cleanup structure.
+ //
+
+ IrpContext->Union.OplockCleanup = &OplockCleanup;
+
+ //
+ // Locate the volume device object and Vcb that we are trying to access.
+ //
+
+ Vcb = &((PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject)->Vcb;
+
+ if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Let's do some work here if the async close list has exceeded
+ // some threshold. Cast 1 to a pointer to indicate who is calling
+ // FspClose.
+ //
+
+ if (NtfsData.AsyncCloseCount > NtfsMinDelayedCloseCount) {
+
+ NtfsFspClose( (PVCB) 1 );
+ }
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ } else {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ }
+
+ AcquiredVcb = TRUE;
+
+ //
+ // Set up local pointers to the file name.
+ //
+
+ *FullFileName = *OriginalFileName = OplockCleanup.FileObject->FileName;
+
+ ExactCaseName->Buffer = NULL;
+
+ //
+ // If the Vcb is locked then we cannot open another file. If we have performed
+ // a dismount then make sure we have the Vcb acquired exclusive so we can
+ // check if we should dismount this volume.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ DebugTrace( 0, Dbg, ("Volume is locked\n") );
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT ) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ }
+
+ DeleteVcb = TRUE;
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+
+ //
+ // Verify that this isn't an open for a structured storage type.
+ //
+
+ if ((IrpSp->Parameters.Create.Options & FILE_STORAGE_TYPE_SPECIFIED) == FILE_STORAGE_TYPE_SPECIFIED) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Initialize local copies of the stack values.
+ //
+
+ RelatedFileObject = OplockCleanup.FileObject->RelatedFileObject;
+ IgnoreCase = !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE );
+ OpenFileById = BooleanFlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID );
+
+ //
+ // Acquire the paging io resource if we are superseding/overwriting a
+ // file or if we are opening for non-cached access.
+ //
+
+ CreateDisposition = (UCHAR) ((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff);
+
+ if ((CreateDisposition == FILE_SUPERSEDE) ||
+ (CreateDisposition == FILE_OVERWRITE) ||
+ (CreateDisposition == FILE_OVERWRITE_IF) ||
+ FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_INTERMEDIATE_BUFFERING )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
+ }
+
+ //
+ // We don't allow an open for an existing paging file. To insure that the
+ // delayed close Scb is not for this paging file we will unconditionally
+ // dereference it if this is a paging file open.
+ //
+
+ if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
+ (!IsListEmpty( &NtfsData.AsyncCloseList ) ||
+ !IsListEmpty( &NtfsData.DelayedCloseList ))) {
+
+ NtfsFspClose( Vcb );
+ }
+
+ //
+ // Set up the file object's Vpb pointer in case anything happens.
+ // This will allow us to get a reasonable pop-up.
+ //
+
+ if (RelatedFileObject != NULL) {
+
+ OplockCleanup.FileObject->Vpb = RelatedFileObject->Vpb;
+ }
+
+ //
+ // Ping the volume to make sure the Vcb is still mounted. If we need
+ // to verify the volume then do it now, and if it comes out okay
+ // then clear the verify volume flag in the device object and continue
+ // on. If it doesn't verify okay then dismount the volume and
+ // either tell the I/O system to try and create again (with a new mount)
+ // or that the volume is wrong. This later code is returned if we
+ // are trying to do a relative open and the vcb is no longer mounted.
+ //
+
+ if (!NtfsPingVolume( IrpContext, Vcb )) {
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ DeleteVcb = TRUE;
+
+ if (RelatedFileObject == NULL) {
+
+ Irp->IoStatus.Information = IO_REMOUNT;
+ NtfsRaiseStatus( IrpContext, STATUS_REPARSE, NULL, NULL );
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
+ }
+ }
+
+ //
+ // The volume verified correctly so now clear the verify bit
+ // and continue with the create
+ //
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+ }
+
+ //
+ // Make sure there is sufficient stack to perform the create.
+ //
+
+ if (IoGetRemainingStackSize( ) < OVERFLOW_CREATE_THRESHHOLD) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Let's handle the open by Id case immediately.
+ //
+
+ if (OpenFileById) {
+
+ FILE_REFERENCE FileReference;
+
+ if (OriginalFileName->Length != sizeof( FILE_REFERENCE )) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ try_return( Status );
+ }
+
+ //
+ // Perform a safe copy of the data to our local variable.
+ //
+
+ RtlCopyMemory( &FileReference,
+ OplockCleanup.FileObject->FileName.Buffer,
+ sizeof( FILE_REFERENCE ));
+
+ //
+ // Clear the name in the file object.
+ //
+
+ OplockCleanup.FileObject->FileName.Buffer = NULL;
+ OplockCleanup.FileObject->FileName.MaximumLength = OplockCleanup.FileObject->FileName.Length = 0;
+
+ Status = NtfsOpenFcbById( IrpContext,
+ Irp,
+ IrpSp,
+ Vcb,
+ NULL,
+ &CurrentFcb,
+ FALSE,
+ FileReference,
+ NtfsEmptyString,
+ NtfsEmptyString,
+ NetworkInfo,
+ &ThisScb,
+ &ThisCcb );
+
+ //
+ // Put the name back into the file object so that the IO system doesn't
+ // think this is a dasd handle. Leave the max length at zero so
+ // we know this is not a real name.
+ //
+
+ OplockCleanup.FileObject->FileName.Buffer = OriginalFileName->Buffer;
+ OplockCleanup.FileObject->FileName.Length = OriginalFileName->Length;
+
+ try_return( Status );
+ }
+
+ //
+ // Here is the "M A R K L U C O V S K Y" hack from hell.
+ //
+ // It's here because Mark says he can't avoid sending me double beginning
+ // backslashes win the Win32 layer.
+ //
+
+ if ((OplockCleanup.FileObject->FileName.Length > sizeof( WCHAR )) &&
+ (OplockCleanup.FileObject->FileName.Buffer[1] == L'\\') &&
+ (OplockCleanup.FileObject->FileName.Buffer[0] == L'\\')) {
+
+ OplockCleanup.FileObject->FileName.Length -= sizeof( WCHAR );
+
+ RtlMoveMemory( &OplockCleanup.FileObject->FileName.Buffer[0],
+ &OplockCleanup.FileObject->FileName.Buffer[1],
+ OplockCleanup.FileObject->FileName.Length );
+
+ *FullFileName = *OriginalFileName = OplockCleanup.FileObject->FileName;
+
+ //
+ // If there are still two beginning backslashes, the name is bogus.
+ //
+
+ if ((OplockCleanup.FileObject->FileName.Length > sizeof( WCHAR )) &&
+ (OplockCleanup.FileObject->FileName.Buffer[1] == L'\\')) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+ try_return( Status );
+ }
+ }
+
+ //
+ // If there is a related file object, we decode it to verify that this
+ // is a valid relative open.
+ //
+
+ if (RelatedFileObject != NULL) {
+
+ PVCB DecodeVcb;
+
+ //
+ // Check for a valid name. The name can't begin with a backslash
+ // and can't end with two backslashes.
+ //
+
+ if (OriginalFileName->Length != 0) {
+
+ //
+ // Check for a leading backslash.
+ //
+
+ if (OriginalFileName->Buffer[0] == L'\\') {
+
+ DebugTrace( 0, Dbg, ("Invalid name for relative open\n") );
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Trim off any trailing backslash.
+ //
+
+ if (OriginalFileName->Buffer[ (OriginalFileName->Length / 2) - 1 ] == L'\\') {
+
+ TrailingBackslash = TRUE;
+
+ OplockCleanup.FileObject->FileName.Length -= 2;
+
+ *OriginalFileName = *FullFileName = OplockCleanup.FileObject->FileName;
+ }
+
+ //
+ // Now check if there is a trailing backslash. Note that if
+ // there was already a trailing backslash then there must
+ // be at least one more character or we would have failed
+ // with the original test.
+ //
+
+ if (OriginalFileName->Buffer[ (OriginalFileName->Length / 2) - 1 ] == L'\\') {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+ try_return( Status );
+ }
+ }
+
+ RelatedFileObjectTypeOfOpen = NtfsDecodeFileObject( IrpContext,
+ RelatedFileObject,
+ &DecodeVcb,
+ &RelatedFcb,
+ &RelatedScb,
+ &RelatedCcb,
+ TRUE );
+
+ //
+ // The relative file has to have been opened as a file. We
+ // cannot do relative opens relative to an opened attribute.
+ //
+
+ if (!FlagOn( RelatedCcb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ DebugTrace( 0, Dbg, ("Invalid File object for relative open\n") );
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // If the related Ccb is was opened by file Id, we will
+ // remember that for future use.
+ //
+
+ if (FlagOn( RelatedCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ OpenFileById = TRUE;
+ }
+
+ //
+ // Remember if the related Ccb was opened through a Dos-Only
+ // component.
+ //
+
+ if (FlagOn( RelatedCcb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT )) {
+
+ DosOnlyComponent = TRUE;
+ }
+
+ } else {
+
+ RelatedFileObjectTypeOfOpen = UnopenedFileObject;
+
+ if ((OriginalFileName->Length > 2) &&
+ (OriginalFileName->Buffer[ (OriginalFileName->Length / 2) - 1 ] == L'\\')) {
+
+ TrailingBackslash = TRUE;
+
+ OplockCleanup.FileObject->FileName.Length -= 2;
+
+ *OriginalFileName = *FullFileName = OplockCleanup.FileObject->FileName;
+
+ //
+ // If there is still a trailing backslash on the name then
+ // the name is invalid.
+ //
+
+
+ if ((OriginalFileName->Length > 2) &&
+ (OriginalFileName->Buffer[ (OriginalFileName->Length / 2) - 1 ] == L'\\')) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+ try_return( Status );
+ }
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("Related File Object, TypeOfOpen -> %08lx\n", RelatedFileObjectTypeOfOpen) );
+
+ //
+ // We check if this is a user volume open in that there is no name
+ // specified and the related file object is valid if present. In that
+ // case set the correct flags in the IrpContext and raise so we can take
+ // the volume open path.
+ //
+
+ if (OriginalFileName->Length == 0
+ && (RelatedFileObjectTypeOfOpen == UnopenedFileObject
+ || RelatedFileObjectTypeOfOpen == UserVolumeOpen)) {
+
+ DebugTrace( 0, Dbg, ("Attempting to open entire volume\n") );
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX | IRP_CONTEXT_FLAG_DASD_OPEN );
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // If the related file object was a volume open, then this open is
+ // illegal.
+ //
+
+ if (RelatedFileObjectTypeOfOpen == UserVolumeOpen) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Remember if we need to perform any traverse access checks.
+ //
+
+ {
+ PIO_SECURITY_CONTEXT SecurityContext;
+
+ SecurityContext = IrpSp->Parameters.Create.SecurityContext;
+
+ if (!FlagOn( SecurityContext->AccessState->Flags,
+ TOKEN_HAS_TRAVERSE_PRIVILEGE )) {
+
+ DebugTrace( 0, Dbg, ("Performing traverse access on this open\n") );
+
+ TraverseAccessCheck = TRUE;
+
+ } else {
+
+ TraverseAccessCheck = FALSE;
+ }
+ }
+
+ //
+ // We enter the loop that does the processing for the prefix lookup.
+ // We optimize the case where we can match a prefix hit. If there is
+ // no hit we will check if the name is legal or might possibly require
+ // parsing to handle the case where there is a named data stream.
+ //
+
+ FirstPass = TRUE;
+ CheckForValidName = TRUE;
+
+ AttrName.Length = 0;
+ AttrCodeName.Length = 0;
+
+ while (TRUE) {
+
+ BOOLEAN ComplexName;
+ PUNICODE_STRING FileObjectName;
+ LONG Index;
+
+ //
+ // Lets make sure we have acquired the starting point for our
+ // name search. If we have a relative file object then use
+ // that. Otherwise we will start from the root.
+ //
+
+ if (RelatedFileObject != NULL) {
+
+ CurrentFcb = RelatedFcb;
+
+ } else {
+
+ CurrentFcb = Vcb->RootIndexScb->Fcb;
+ }
+
+ NtfsAcquireFcbWithPaging( IrpContext, CurrentFcb, FALSE );
+
+ //
+ // Parse the file object name if we need to.
+ //
+
+ FileObjectName = &OplockCleanup.FileObject->FileName;
+
+ if (!FirstPass) {
+
+ if (!NtfsParseNameForCreate( IrpContext,
+ RemainingName,
+ FileObjectName,
+ OriginalFileName,
+ FullFileName,
+ &AttrName,
+ &AttrCodeName )) {
+
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+
+ //
+ // If we might be creating a named stream acquire the
+ // paging IO as well. This will keep anyone from peeking
+ // at the allocation size of any other streams we are converting
+ // to non-resident.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) &&
+ (AttrName.Length != 0) &&
+ ((CreateDisposition == FILE_OPEN_IF) ||
+ (CreateDisposition == FILE_CREATE))) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
+ }
+ CheckForValidName = FALSE;
+
+ //
+ // Build up the full name if this is not the open by file Id case.
+ //
+
+ } else if (!OpenFileById) {
+
+ //
+ // If we have a related file object, then we build up the
+ // combined name.
+ //
+
+ if (RelatedFileObject != NULL) {
+
+ WCHAR *CurrentPosition;
+ USHORT AddSeparator;
+
+ if ((FileObjectName->Length == 0) ||
+ (RelatedCcb->FullFileName.Length == 2) ||
+ (FileObjectName->Buffer[0] == L':')) {
+
+ AddSeparator = 0;
+
+ } else {
+
+ AddSeparator = sizeof( WCHAR );
+ }
+
+ FullFileName->Length = RelatedCcb->FullFileName.Length +
+ FileObjectName->Length +
+ AddSeparator;
+
+ FullFileName->MaximumLength = FullFileName->Length;
+
+ //
+ // We need to allocate a name buffer.
+ //
+
+ FullFileName->Buffer = FsRtlAllocatePoolWithTag(PagedPool, FullFileName->Length, MODULE_POOL_TAG);
+
+ CurrentPosition = (WCHAR *) FullFileName->Buffer;
+
+ RtlCopyMemory( CurrentPosition,
+ RelatedCcb->FullFileName.Buffer,
+ RelatedCcb->FullFileName.Length );
+
+ CurrentPosition = (WCHAR *) Add2Ptr( CurrentPosition, RelatedCcb->FullFileName.Length );
+
+ if (AddSeparator != 0) {
+
+ *CurrentPosition = L'\\';
+
+ CurrentPosition += 1;
+ }
+
+ if (FileObjectName->Length != 0) {
+
+ RtlCopyMemory( CurrentPosition,
+ FileObjectName->Buffer,
+ FileObjectName->Length );
+ }
+
+ //
+ // If the user specified a case sensitive comparison, then the
+ // case insensitive index is the full length of the resulting
+ // string. Otherwise it is the length of the string in
+ // the related file object. We adjust for the case when the
+ // original file name length is zero.
+ //
+
+ if (!IgnoreCase) {
+
+ CaseInsensitiveIndex = FullFileName->Length;
+
+ } else {
+
+ CaseInsensitiveIndex = RelatedCcb->FullFileName.Length +
+ AddSeparator;
+ }
+
+ //
+ // The entire name is in the FileObjectName. We check the buffer for
+ // validity.
+ //
+
+ } else {
+
+ //
+ // We look at the name string for detectable errors. The
+ // length must be non-zero and the first character must be
+ // '\'
+ //
+
+ if (FileObjectName->Length == 0) {
+
+ DebugTrace( 0, Dbg, ("There is no name to open\n") );
+ try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
+ }
+
+ if (FileObjectName->Buffer[0] != L'\\') {
+
+ DebugTrace( 0, Dbg, ("Name does not begin with a backslash\n") );
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // If the user specified a case sensitive comparison, then the
+ // case insensitive index is the full length of the resulting
+ // string. Otherwise it is zero.
+ //
+
+ if (!IgnoreCase) {
+
+ CaseInsensitiveIndex = FullFileName->Length;
+
+ } else {
+
+ CaseInsensitiveIndex = 0;
+ }
+ }
+
+ } else if (IgnoreCase) {
+
+ CaseInsensitiveIndex = 0;
+
+ } else {
+
+ CaseInsensitiveIndex = FullFileName->Length;
+ }
+
+ //
+ // The remaining name is stored in the FullFileName variable.
+ // If we are doing a case-insensitive operation and have to
+ // upcase part of the remaining name then allocate a buffer
+ // now.
+ //
+
+ if (IgnoreCase &&
+ CaseInsensitiveIndex < FullFileName->Length) {
+
+ UNICODE_STRING StringToUpcase;
+
+ ExactCaseName->Buffer = FsRtlAllocatePoolWithTag(PagedPool, FullFileName->MaximumLength, MODULE_POOL_TAG);
+ ExactCaseName->MaximumLength = FullFileName->MaximumLength;
+
+ RtlCopyMemory( ExactCaseName->Buffer,
+ FullFileName->Buffer,
+ FullFileName->MaximumLength );
+
+ ExactCaseName->Length = FullFileName->Length;
+
+ StringToUpcase.Buffer = Add2Ptr( FullFileName->Buffer,
+ CaseInsensitiveIndex );
+
+ StringToUpcase.Length = FullFileName->Length - (USHORT) CaseInsensitiveIndex;
+ StringToUpcase.MaximumLength = FullFileName->MaximumLength - (USHORT) CaseInsensitiveIndex;
+
+ NtfsUpcaseName( Vcb->UpcaseTable, Vcb->UpcaseTableSize, &StringToUpcase );
+ }
+
+ RemainingName = *FullFileName;
+
+ //
+ // If this is the traverse access case or the open by file id case we start
+ // relative to the file object we have or the root directory.
+ // This is also true for the case where the file name in the file object is
+ // empty.
+ //
+
+ if (TraverseAccessCheck
+ || FileObjectName->Length == 0) {
+
+ if (RelatedFileObject != NULL) {
+
+ CurrentLcb = RelatedCcb->Lcb;
+ CurrentScb = RelatedScb;
+
+ if (FileObjectName->Length == 0) {
+
+ RemainingName.Length = 0;
+
+ } else if (!OpenFileById) {
+
+ USHORT Increment;
+
+ Increment = RelatedCcb->FullFileName.Length
+ + (RelatedCcb->FullFileName.Length == 2
+ ? 0
+ : 2);
+
+ RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
+ Increment );
+
+ RemainingName.Length -= Increment;
+ }
+
+ } else {
+
+ CurrentLcb = Vcb->RootLcb;
+ CurrentScb = Vcb->RootIndexScb;
+
+ RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer, sizeof( WCHAR ));
+ RemainingName.Length -= sizeof( WCHAR );
+ }
+
+ //
+ // Otherwise we will try a prefix lookup.
+ //
+
+ } else {
+
+ if (RelatedFileObject != NULL) {
+
+ if (!OpenFileById) {
+
+ //
+ // Skip over the characters in the related file object.
+ //
+
+ RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
+ RelatedCcb->FullFileName.Length );
+ RemainingName.Length -= RelatedCcb->FullFileName.Length;
+ }
+
+ CurrentLcb = RelatedCcb->Lcb;
+ CurrentScb = RelatedScb;
+
+ } else {
+
+ CurrentLcb = Vcb->RootLcb;
+ CurrentScb = Vcb->RootIndexScb;
+
+ //
+ // Skip over the lead-in '\' character.
+ //
+
+ RemainingName.Buffer = (WCHAR *) Add2Ptr( RemainingName.Buffer,
+ sizeof( WCHAR ));
+ RemainingName.Length -= sizeof( WCHAR );
+ }
+
+ LcbForTeardown = NULL;
+
+ NextLcb = NtfsFindPrefix( IrpContext,
+ CurrentScb,
+ &CurrentFcb,
+ &LcbForTeardown,
+ RemainingName,
+ IgnoreCase,
+ &DosOnlyComponent,
+ &RemainingName );
+
+ //
+ // If we found another link then update the CurrentLcb value.
+ //
+
+ if (NextLcb != NULL) {
+
+ CurrentLcb = NextLcb;
+ }
+ }
+
+ if (!FirstPass
+ || RemainingName.Length == 0) {
+
+ break;
+ }
+
+ //
+ // If we get here, it means that this is the first pass and we didn't
+ // have a prefix match. If there is a colon in the
+ // remaining name, then we need to analyze the name in more detail.
+ //
+
+ ComplexName = FALSE;
+
+ for (Index = (RemainingName.Length / 2) - 1, ComplexName = FALSE;
+ Index >= 0;
+ Index -= 1) {
+
+ if (RemainingName.Buffer[Index] == L':') {
+
+ ComplexName = TRUE;
+ break;
+ }
+ }
+
+ if (!ComplexName) {
+
+ break;
+ }
+
+ FirstPass = FALSE;
+
+ //
+ // Copy the exact case back into the full name and deallocate
+ // any buffer we may have allocated.
+ //
+
+ if (ExactCaseName->Buffer != NULL) {
+
+ RtlCopyMemory( FullFileName->Buffer,
+ ExactCaseName->Buffer,
+ ExactCaseName->Length );
+
+ NtfsFreePool( ExactCaseName->Buffer );
+ ExactCaseName->Buffer = NULL;
+ }
+
+ //
+ // Let's release the Fcb we have currently acquired.
+ //
+
+ NtfsReleaseFcbWithPaging( IrpContext, CurrentFcb );
+ LcbForTeardown = NULL;
+ }
+
+ //
+ // Check if the link or the Fcb is pending delete.
+ //
+
+ if ((CurrentLcb != NULL && LcbLinkIsDeleted( CurrentLcb )) ||
+ CurrentFcb->LinkCount == 0) {
+
+ try_return( Status = STATUS_DELETE_PENDING );
+ }
+
+ //
+ // Put the new name into the file object.
+ //
+
+ OplockCleanup.FileObject->FileName = *FullFileName;
+
+ //
+ // If the entire path was parsed, then we have access to the Fcb to
+ // open. We either open the parent of the prefix match or the prefix
+ // match itself, depending on whether the user wanted to open
+ // the target directory.
+ //
+
+ if (RemainingName.Length == 0) {
+
+ //
+ // Check the attribute name length.
+ //
+
+ if (AttrName.Length > (NTFS_MAX_ATTR_NAME_LEN * sizeof( WCHAR ))) {
+
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+
+ //
+ // If this is a target directory we check that the open is for the
+ // entire file.
+ // We assume that the final component can only have an attribute
+ // which corresponds to the type of file this is. Meaning
+ // $INDEX_ALLOCATION for directory, $DATA (unnamed) for a file.
+ // We verify that the matching Lcb is not the root Lcb.
+ //
+
+ if (FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
+
+ if (CurrentLcb == Vcb->RootLcb) {
+
+ DebugTrace( 0, Dbg, ("Can't open parent of root\n") );
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // We don't allow attribute names or attribute codes to
+ // be specified.
+ //
+
+ if (AttrName.Length != 0
+ || AttrCodeName.Length != 0) {
+
+ DebugTrace( 0, Dbg, ("Can't specify complex name for rename\n") );
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+
+ //
+ // We want to copy the exact case of the name back into the
+ // input buffer for this case.
+ //
+
+ if (ExactCaseName->Buffer != NULL) {
+
+ RtlCopyMemory( FullFileName->Buffer,
+ ExactCaseName->Buffer,
+ ExactCaseName->Length );
+ }
+
+ //
+ // Acquire the parent of the last Fcb. This is the actual file we
+ // are opening.
+ //
+
+ ParentFcb = CurrentLcb->Scb->Fcb;
+ NtfsAcquireFcbWithPaging( IrpContext, ParentFcb, FALSE );
+
+ //
+ // Call our open target directory, remembering the target
+ // file existed.
+ //
+
+ Status = NtfsOpenTargetDirectory( IrpContext,
+ Irp,
+ IrpSp,
+ ParentFcb,
+ NULL,
+ &OplockCleanup.FileObject->FileName,
+ CurrentLcb->ExactCaseLink.LinkName.Length,
+ TRUE,
+ DosOnlyComponent,
+ &ThisScb,
+ &ThisCcb );
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Otherwise we simply attempt to open the Fcb we matched.
+ //
+
+ if (OpenFileById) {
+
+ Status = NtfsOpenFcbById( IrpContext,
+ Irp,
+ IrpSp,
+ Vcb,
+ CurrentLcb,
+ &CurrentFcb,
+ TRUE,
+ CurrentFcb->FileReference,
+ AttrName,
+ AttrCodeName,
+ NetworkInfo,
+ &ThisScb,
+ &ThisCcb );
+
+
+ //
+ // Set the maximum length in the file object name to
+ // zero so we know that this is not a full name.
+ //
+
+ OplockCleanup.FileObject->FileName.MaximumLength = 0;
+
+ } else {
+
+ //
+ // The current Fcb is acquired.
+ //
+
+ Status = NtfsOpenExistingPrefixFcb( IrpContext,
+ Irp,
+ IrpSp,
+ CurrentFcb,
+ CurrentLcb,
+ FullFileName->Length,
+ AttrName,
+ AttrCodeName,
+ DosOnlyComponent,
+ TrailingBackslash,
+ NetworkInfo,
+ &ThisScb,
+ &ThisCcb );
+ }
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Check if the current Lcb is a Dos-Only Name.
+ //
+
+ if (CurrentLcb != NULL &&
+ CurrentLcb->FileNameAttr->Flags == FILE_NAME_DOS) {
+
+ DosOnlyComponent = TRUE;
+ }
+
+ //
+ // We have a remaining portion of the file name which was unmatched in the
+ // prefix table. We walk through these name components until we reach the
+ // last element. If necessary, we add Fcb and Scb's into the graph as we
+ // walk through the names.
+ //
+
+ FirstPass = TRUE;
+
+ while (TRUE) {
+
+ PFILE_NAME IndexFileName;
+
+ //
+ // We check that the last Fcb we have is in fact a directory.
+ //
+
+ if (!IsDirectory( &CurrentFcb->Info )) {
+
+ DebugTrace( 0, Dbg, ("Intermediate node is not a directory\n") );
+ try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
+ }
+
+ //
+ // We dissect the name into the next component and the remaining name string.
+ // We don't need to check for a valid name if we examined the name already.
+ //
+
+ NtfsDissectName( RemainingName,
+ &FinalName,
+ &RemainingName );
+
+ DebugTrace( 0, Dbg, ("Final name -> %Z\n", &FinalName) );
+ DebugTrace( 0, Dbg, ("Remaining Name -> %Z\n", &RemainingName) );
+
+ //
+ // If the final name is too long then either the path or the
+ // name is invalid.
+ //
+
+ if (FinalName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) {
+
+ if (RemainingName.Length == 0) {
+
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+
+ } else {
+
+ try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
+ }
+ }
+
+ //
+ // Catch single dot names (.) before scanning the index. We don't
+ // want to allow someone to open the self entry in the root.
+ //
+
+ if ((FinalName.Length == 2) &&
+ (FinalName.Buffer[0] == L'.')) {
+
+ if (RemainingName.Length != 0) {
+
+ DebugTrace( 0, Dbg, ("Intermediate component in path doesn't exist\n") );
+ try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
+
+ //
+ // If the final component is illegal, then return the appropriate error.
+ //
+
+ } else {
+
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+ }
+
+ //
+ // Get the index allocation Scb for the current Fcb.
+ //
+
+ //
+ // We need to look for the next component in the name string in the directory
+ // we've reached. We need to get a Scb to perform the index search.
+ // To do the search we need to build a filename attribute to perform the
+ // search with and then call the index package to perform the search.
+ //
+
+ CurrentScb = NtfsCreateScb( IrpContext,
+ CurrentFcb,
+ $INDEX_ALLOCATION,
+ &NtfsFileNameIndex,
+ FALSE,
+ NULL );
+
+ //
+ // If the CurrentScb does not have its normalized name and we have a valid
+ // parent, then update the normalized name.
+ //
+
+ if ((LastScb != NULL) &&
+ (CurrentScb->ScbType.Index.NormalizedName.Buffer == NULL) &&
+ (LastScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsUpdateNormalizedName( IrpContext, LastScb, CurrentScb, IndexFileName, FALSE );
+ }
+
+ //
+ // Release the parent Scb if we own it.
+ //
+
+ if (!FirstPass) {
+
+ NtfsReleaseFcbWithPaging( IrpContext, ParentFcb );
+ }
+
+ LastScb = CurrentScb;
+
+ //
+ // If traverse access is required, we do so now before accessing the
+ // disk.
+ //
+
+ if (TraverseAccessCheck) {
+
+ NtfsTraverseCheck( IrpContext,
+ CurrentFcb,
+ Irp );
+ }
+
+ //
+ // Look on the disk to see if we can find the last component on the path.
+ //
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ //
+ // Check that the name is valid before scanning the disk.
+ //
+
+ if (CheckForValidName && !NtfsIsFileNameValid( &FinalName, FALSE )) {
+
+ DebugTrace( 0, Dbg, ("Component name is invalid\n") );
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+
+ FoundEntry = NtfsLookupEntry( IrpContext,
+ CurrentScb,
+ IgnoreCase,
+ &FinalName,
+ &FileNameAttr,
+ &FileNameAttrLength,
+ &QuickIndex,
+ &IndexEntry,
+ &IndexEntryBcb );
+
+ //
+ // This call to NtfsLookupEntry may decide to push the root index.
+ // Create needs to free resources as it walks down the tree to prevent
+ // deadlocks. If there is a transaction, commit it now so we will be
+ // able to free this resource.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+#ifdef _CAIRO_
+ //
+ // Go through and free any Scb's in the queue of shared
+ // Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+ ASSERT( IrpContext->SharedScb == NULL );
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+#endif // _CAIRO_
+
+ }
+
+ if (FoundEntry) {
+
+ //
+ // Get the file name attribute so we can get the name out of it.
+ //
+
+ IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
+
+ if (IgnoreCase) {
+
+ RtlCopyMemory( FinalName.Buffer,
+ IndexFileName->FileName,
+ FinalName.Length );
+ }
+ }
+
+ //
+ // If we didn't find a matching entry in the index, we need to check if the
+ // name is illegal or simply isn't present on the disk.
+ //
+
+ if (!FoundEntry) {
+
+ if (RemainingName.Length != 0) {
+
+ DebugTrace( 0, Dbg, ("Intermediate component in path doesn't exist\n") );
+ try_return( Status = STATUS_OBJECT_PATH_NOT_FOUND );
+ }
+
+ //
+ // Now copy the exact case of the name specified by the user back
+ // in the file name buffer and file name attribute in order to
+ // create the name.
+ //
+
+ if (IgnoreCase) {
+
+ RtlCopyMemory( FinalName.Buffer,
+ Add2Ptr( ExactCaseName->Buffer,
+ ExactCaseName->Length - FinalName.Length ),
+ FinalName.Length );
+
+ RtlCopyMemory( FileNameAttr->FileName,
+ Add2Ptr( ExactCaseName->Buffer,
+ ExactCaseName->Length - FinalName.Length ),
+ FinalName.Length );
+ }
+ }
+
+ //
+ // If we're at the last component in the path, then this is the file
+ // to open or create
+ //
+
+ if (RemainingName.Length == 0) {
+
+ break;
+ }
+
+ //
+ // Otherwise we create an Fcb for the subdirectory and the link between
+ // it and its parent Scb.
+ //
+
+ //
+ // Remember that the current values will become the parent values.
+ //
+
+ ParentFcb = CurrentFcb;
+
+ CurrentLcb = NtfsOpenSubdirectory( IrpContext,
+ CurrentScb,
+ FinalName,
+ TraverseAccessCheck,
+ &CurrentFcb,
+ &LcbForTeardown,
+ IndexEntry );
+
+ //
+ // Check that this link is a valid existing link.
+ //
+
+ if (LcbLinkIsDeleted( CurrentLcb ) ||
+ CurrentFcb->LinkCount == 0) {
+
+ try_return( Status = STATUS_DELETE_PENDING );
+ }
+
+ //
+ // Go ahead and insert this link into the splay tree.
+ //
+
+ NtfsInsertPrefix( CurrentLcb,
+ IgnoreCase );
+
+ //
+ // Since we have the location of this entry store the information into
+ // the Lcb.
+ //
+
+ RtlCopyMemory( &CurrentLcb->QuickIndex,
+ &QuickIndex,
+ sizeof( QUICK_INDEX ));
+
+ //
+ // Check if the current Lcb is a Dos-Only Name.
+ //
+
+ if (CurrentLcb->FileNameAttr->Flags == FILE_NAME_DOS) {
+
+ DosOnlyComponent = TRUE;
+ }
+
+ FirstPass = FALSE;
+ }
+
+ //
+ // We now have the parent of the file to open and know whether the file exists on
+ // the disk. At this point we either attempt to open the target directory or
+ // the file itself.
+ //
+
+ if (FlagOn( IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY )) {
+
+ //
+ // We don't allow attribute names or attribute codes to
+ // be specified.
+ //
+
+ if (AttrName.Length != 0
+ || AttrCodeName.Length != 0) {
+
+ DebugTrace( 0, Dbg, ("Can't specify complex name for rename\n") );
+ try_return( Status = STATUS_OBJECT_NAME_INVALID );
+ }
+
+ //
+ // We want to copy the exact case of the name back into the
+ // input buffer for this case.
+ //
+
+ if (ExactCaseName->Buffer != NULL) {
+
+ RtlCopyMemory( FullFileName->Buffer,
+ ExactCaseName->Buffer,
+ ExactCaseName->Length );
+ }
+
+ //
+ // Call our open target directory, remembering the target
+ // file existed.
+ //
+
+ Status = NtfsOpenTargetDirectory( IrpContext,
+ Irp,
+ IrpSp,
+ CurrentFcb,
+ CurrentLcb,
+ &OplockCleanup.FileObject->FileName,
+ FinalName.Length,
+ FoundEntry,
+ DosOnlyComponent,
+ &ThisScb,
+ &ThisCcb );
+
+ try_return( Status );
+ }
+
+ //
+ // If we didn't find an entry, we will try to create the file.
+ //
+
+ if (!FoundEntry) {
+
+ //
+ // Update our pointers to reflect that we are at the
+ // parent of the file we want.
+ //
+
+ ParentFcb = CurrentFcb;
+
+ Status = NtfsCreateNewFile( IrpContext,
+ Irp,
+ IrpSp,
+ CurrentScb,
+ FileNameAttr,
+ *FullFileName,
+ FinalName,
+ AttrName,
+ AttrCodeName,
+ IgnoreCase,
+ OpenFileById,
+ DosOnlyComponent,
+ TrailingBackslash,
+ &CurrentFcb,
+ &LcbForTeardown,
+ &ThisScb,
+ &ThisCcb );
+
+ CreateFileCase = TRUE;
+
+ //
+ // Otherwise we call our routine to open the file.
+ //
+
+ } else {
+
+ ParentFcb = CurrentFcb;
+
+ Status = NtfsOpenFile( IrpContext,
+ Irp,
+ IrpSp,
+ CurrentScb,
+ IndexEntry,
+ *FullFileName,
+ FinalName,
+ AttrName,
+ AttrCodeName,
+ IgnoreCase,
+ OpenFileById,
+ &QuickIndex,
+ DosOnlyComponent,
+ TrailingBackslash,
+ NetworkInfo,
+ &CurrentFcb,
+ &LcbForTeardown,
+ &ThisScb,
+ &ThisCcb );
+ }
+
+ try_exit: NOTHING;
+
+ //
+ // Abort transaction on err by raising.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCommonCreate );
+
+ //
+ // Unpin the index entry.
+ //
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ //
+ // Free the file name attribute if we allocated it.
+ //
+
+ if (FileNameAttr != NULL) {
+
+ NtfsFreePool( FileNameAttr );
+ }
+
+ //
+ // Capture the status code from the IrpContext if we are in the exception path.
+ //
+
+ if (AbnormalTermination()) {
+
+ Status = IrpContext->ExceptionStatus;
+ }
+
+ //
+ // If this is the oplock completion path then don't do any of this completion work,
+ // The Irp may already have been posted to another thread.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ //
+ // If we successfully opened the file, we need to update the in-memory
+ // structures.
+ //
+
+ if (NT_SUCCESS( Status ) && (Status != STATUS_REPARSE)) {
+
+ //
+ // If we modified the original file name, we can delete the original
+ // buffer.
+ //
+
+ if ((OriginalFileName->Buffer != NULL) &&
+ (OriginalFileName->Buffer != FullFileName->Buffer)) {
+
+ NtfsFreePool( OriginalFileName->Buffer );
+ DebugDoit( OriginalFileName->Buffer = NULL );
+ }
+
+ //
+ // Do our normal processing if this is not a Network Info query.
+ //
+
+ if (!ARGUMENT_PRESENT( NetworkInfo )) {
+
+ //
+ // Find the Lcb for this open.
+ //
+
+ CurrentLcb = ThisCcb->Lcb;
+
+ //
+ // Check if we were opening a paging file and if so then make sure that
+ // the internal attribute stream is all closed down
+ //
+
+ if (FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
+
+ NtfsDeleteInternalAttributeStream( ThisScb, TRUE );
+ }
+
+ //
+ // If we are not done with a large allocation for a new attribute,
+ // then we must make sure that no one can open the file until we
+ // try to get it extended. Do this before dropping the Vcb.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_LARGE_ALLOCATION )) {
+
+ //
+ // For a new file, we can clear the link count and mark the
+ // Lcb (if there is one) delete on close.
+ //
+
+ if (CreateFileCase) {
+
+ CurrentFcb->LinkCount = 0;
+
+ SetFlag( CurrentLcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
+
+ //
+ // If we just created an attribute, then we will mark that attribute
+ // delete on close to prevent it from being opened.
+ //
+
+ } else {
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+ }
+
+ //
+ // Remember the POSIX flag and whether we had to do any traverse
+ // access checking.
+ //
+
+ if (IgnoreCase) {
+
+ SetFlag( ThisCcb->Flags, CCB_FLAG_IGNORE_CASE );
+ }
+
+ if (TraverseAccessCheck) {
+
+ SetFlag( ThisCcb->Flags, CCB_FLAG_TRAVERSE_CHECK );
+ }
+
+ //
+ // We don't do "delete on close" for directories or open
+ // by ID files.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE ) &&
+ !FlagOn( ThisCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ DeleteOnClose = TRUE;
+
+ //
+ // We modify the Scb and Lcb here only if we aren't in the
+ // large allocation case.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_LARGE_ALLOCATION )) {
+
+ SetFlag( ThisCcb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
+ }
+ }
+
+ //
+ // If this is a named stream open and we have set any of our notify
+ // flags then report the changes.
+ //
+
+ if ((Vcb->NotifyCount != 0) &&
+ !FlagOn( ThisCcb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
+ (ThisScb->AttributeName.Length != 0) &&
+ NtfsIsTypeCodeUserData( ThisScb->AttributeTypeCode ) &&
+ FlagOn( ThisScb->ScbState,
+ SCB_STATE_NOTIFY_ADD_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM )) {
+
+ ULONG Filter = 0;
+ ULONG Action;
+
+ //
+ // Start by checking for an add.
+ //
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_NOTIFY_ADD_STREAM )) {
+
+ Filter = FILE_NOTIFY_CHANGE_STREAM_NAME;
+ Action = FILE_ACTION_ADDED_STREAM;
+
+ } else {
+
+ //
+ // Check if the file size changed.
+ //
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM )) {
+
+ Filter = FILE_NOTIFY_CHANGE_STREAM_SIZE;
+ }
+
+ //
+ // Now check if the stream data was modified.
+ //
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_NOTIFY_MODIFY_STREAM )) {
+
+ Filter |= FILE_NOTIFY_CHANGE_STREAM_WRITE;
+ }
+
+ Action = FILE_ACTION_MODIFIED_STREAM;
+ }
+
+ NtfsUnsafeReportDirNotify( IrpContext,
+ Vcb,
+ &ThisCcb->FullFileName,
+ ThisCcb->LastFileNameOffset,
+ &ThisScb->AttributeName,
+ ((FlagOn( ThisCcb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ ThisCcb->Lcb != NULL &&
+ ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &ThisCcb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ Filter,
+ Action,
+ NULL );
+ }
+
+ ClearFlag( ThisScb->ScbState,
+ SCB_STATE_NOTIFY_ADD_STREAM |
+ SCB_STATE_NOTIFY_REMOVE_STREAM |
+ SCB_STATE_NOTIFY_RESIZE_STREAM |
+ SCB_STATE_NOTIFY_MODIFY_STREAM );
+
+ //
+ // Otherwise copy the data out of the Scb/Fcb and return to our caller.
+ //
+
+ } else {
+
+ RtlZeroMemory( NetworkInfo, sizeof( FILE_NETWORK_OPEN_INFORMATION ));
+
+ //
+ // Fill in the basic information fields
+ //
+
+ NetworkInfo->CreationTime.QuadPart = CurrentFcb->Info.CreationTime;
+ NetworkInfo->LastWriteTime.QuadPart = CurrentFcb->Info.LastModificationTime;
+ NetworkInfo->ChangeTime.QuadPart = CurrentFcb->Info.LastChangeTime;
+
+ NetworkInfo->LastAccessTime.QuadPart = CurrentFcb->CurrentLastAccess;
+
+ NetworkInfo->FileAttributes = CurrentFcb->Info.FileAttributes;
+
+ ClearFlag( NetworkInfo->FileAttributes,
+ ~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_TEMPORARY );
+
+ //
+ // DARRYLH - Do you want the directory bit set for streams in
+ // the directory.
+ //
+
+ if (ThisScb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ if (IsDirectory( &CurrentFcb->Info )) {
+
+ SetFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
+
+ //
+ // If this is not the main stream then copy the compression
+ // value from this Scb.
+ //
+
+ } else if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+
+ //
+ // Return a non-zero size only for data streams.
+ //
+
+ } else {
+
+ NetworkInfo->AllocationSize.QuadPart = ThisScb->TotalAllocated;
+ NetworkInfo->EndOfFile = ThisScb->Header.FileSize;
+
+ //
+ // If not the unnamed data stream then use the Scb
+ // compression value.
+ //
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+ }
+ }
+
+ //
+ // Set the temporary flag if set in the ThisScb.
+ //
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( NetworkInfo->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
+ }
+
+ //
+ // If there are no flags set then explicitly set the NORMAL flag.
+ //
+
+ if (NetworkInfo->FileAttributes == 0) {
+
+ NetworkInfo->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ //
+ // Teardown the Fcb if we should.
+ //
+
+ if (!ThisScb->CleanupCount && !ThisScb->Fcb->DelayedCloseCount) {
+ if (!NtfsAddScbToFspClose( IrpContext, ThisScb, TRUE )) {
+ NtfsTeardownStructures( IrpContext,
+ CurrentFcb,
+ LcbForTeardown,
+ (BOOLEAN) (IrpContext->TransactionId != 0),
+ NtfsIsExclusiveScb( Vcb->MftScb ),
+ NULL );
+ }
+ }
+
+ Irp->IoStatus.Information = sizeof( FILE_NETWORK_OPEN_INFORMATION );
+
+ Status = Irp->IoStatus.Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Start a teardown on the last Fcb found and restore the name strings on
+ // a retryable error.
+ //
+
+ } else {
+
+ //
+ // Start the cleanup process if we have looked at any Fcb's.
+ // We tell TeardownStructures not to remove any Scb's in
+ // the open attribute table if there is a transaction underway.
+ //
+
+ if (CurrentFcb != NULL) {
+
+ NtfsTeardownStructures( IrpContext,
+ (ThisScb != NULL) ? (PVOID) ThisScb : CurrentFcb,
+ LcbForTeardown,
+ (BOOLEAN) (IrpContext->TransactionId != 0),
+ NtfsIsExclusiveScb( Vcb->MftScb ),
+ NULL );
+
+ //
+ // Someone may have tried to open the $Bitmap stream. We catch that and
+ // fail it but the Fcb won't be in the exclusive list to be released.
+ //
+
+ if (NtfsEqualMftRef( &CurrentFcb->FileReference, &BitmapFileReference )) {
+
+ NtfsReleaseFcb( IrpContext, CurrentFcb );
+ }
+ }
+
+ if ((Status == STATUS_LOG_FILE_FULL) ||
+ (Status == STATUS_CANT_WAIT)) {
+
+ //
+ // Recover the exact case name if present for a retryable condition.
+ //
+
+ if (ExactCaseName->Buffer != NULL) {
+
+ RtlCopyMemory( FullFileName->Buffer,
+ ExactCaseName->Buffer,
+ ExactCaseName->MaximumLength );
+ }
+ }
+
+ //
+ // Free any buffer we allocated.
+ //
+
+ if ((FullFileName->Buffer != NULL) &&
+ (OriginalFileName->Buffer != FullFileName->Buffer)) {
+
+ NtfsFreePool( FullFileName->Buffer );
+ DebugDoit( FullFileName->Buffer = NULL );
+ }
+
+ //
+ // Set the file name in the file object back to it's original value.
+ //
+
+ OplockCleanup.FileObject->FileName = *OriginalFileName;
+
+ //
+ // Always clear the LARGE_ALLOCATION flag so we don't get
+ // spoofed by STATUS_REPARSE.
+ //
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_LARGE_ALLOCATION );
+ }
+ }
+
+ //
+ // Always free the exact case name if allocated.
+ //
+
+ if (ExactCaseName->Buffer != NULL) {
+
+ NtfsFreePool( ExactCaseName->Buffer );
+ DebugDoit( ExactCaseName->Buffer = NULL );
+ }
+
+ //
+ // We always give up the Vcb.
+ //
+
+ if (AcquiredVcb) {
+
+ if (DeleteVcb) {
+
+ NtfsReleaseVcbCheckDelete( IrpContext, Vcb, IRP_MJ_CREATE, NULL );
+
+ } else {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+ }
+ }
+
+ //
+ // If we didn't post this Irp then take action to complete the irp.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ //
+ // If the current status is success and there is more allocation to
+ // allocate then complete the allocation.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_LARGE_ALLOCATION ) &&
+ NT_SUCCESS( Status )) {
+
+ //
+ // If the Create was successful, but we did not get all of the space
+ // allocated that we wanted, we have to complete the allocation now.
+ // Basically what we do is commit the current transaction and call
+ // NtfsAddAllocation to get the rest of the space. Then if the log
+ // file fills up (or we are posting for other reasons) we turn the
+ // Irp into an Irp which is just trying to extend the file. If we
+ // get any other kind of error, then we just delete the file and
+ // return with the error from create.
+ //
+
+ Status = NtfsCompleteLargeAllocation( IrpContext,
+ Irp,
+ CurrentLcb,
+ ThisScb,
+ ThisCcb,
+ CreateFileCase,
+ DeleteOnClose );
+ }
+
+ NtfsCompleteRequest( &IrpContext,
+ (ARGUMENT_PRESENT( NetworkInfo ) ? NULL : &Irp),
+ Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonCreate: Exit -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonVolumeOpen (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is opening the Volume Dasd file. We have already done all the
+ checks needed to verify that the user is opening the $DATA attribute.
+ We check the security attached to the file and take some special action
+ based on a volume open.
+
+Arguments:
+
+Return Value:
+
+ NTSTATUS - The result of this operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ PVCB Vcb;
+ PFCB ThisFcb;
+ PCCB ThisCcb;
+
+ BOOLEAN VcbAcquired = FALSE;
+ BOOLEAN FcbAcquired = FALSE;
+ BOOLEAN DeleteVcb = FALSE;
+
+ BOOLEAN SharingViolation;
+ BOOLEAN LockVolume = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCommonVolumeOpen: Entered\n") );
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Start by checking the create disposition. We can only open this
+ // file.
+ //
+
+ {
+ ULONG CreateDisposition;
+
+ CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
+
+ if (CreateDisposition != FILE_OPEN
+ && CreateDisposition != FILE_OPEN_IF) {
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+ }
+
+ //
+ // Make sure the directory flag isn't set for the volume open.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE )) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Acquire the Vcb and verify the volume isn't locked.
+ //
+
+ Vcb = &((PVOLUME_DEVICE_OBJECT) IrpSp->DeviceObject)->Vcb;
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ DeleteVcb = TRUE;
+ }
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+
+ //
+ // Ping the volume to make sure the Vcb is still mounted. If we need
+ // to verify the volume then do it now, and if it comes out okay
+ // then clear the verify volume flag in the device object and continue
+ // on. If it doesn't verify okay then dismount the volume and
+ // either tell the I/O system to try and create again (with a new mount)
+ // or that the volume is wrong. This later code is returned if we
+ // are trying to do a relative open and the vcb is no longer mounted.
+ //
+
+ if (!NtfsPingVolume( IrpContext, Vcb )) {
+
+ if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ DeleteVcb = TRUE;
+ NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
+ }
+
+ //
+ // The volume verified correctly so now clear the verify bit
+ // and continue with the create
+ //
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+ }
+
+ //
+ // Now acquire the Fcb for the VolumeDasd and verify the user has
+ // permission to open the volume.
+ //
+
+ ThisFcb = Vcb->VolumeDasdScb->Fcb;
+
+ if (ThisFcb->PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, ThisFcb );
+ }
+
+ ExAcquireResourceExclusive( ThisFcb->Resource, TRUE );
+ FcbAcquired = TRUE;
+
+ NtfsOpenCheck( IrpContext, ThisFcb, NULL, Irp );
+
+ //
+ // If the user does not want to share write or delete then we will try
+ // and take out a lock on the volume.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.ShareAccess,
+ FILE_SHARE_WRITE | FILE_SHARE_DELETE )) {
+
+ //
+ // Do a quick test of the volume cleanup count if this opener won't
+ // share with anyone. We can safely examine the cleanup count without
+ // further synchronization because we are guaranteed to have the
+ // Vcb exclusive at this point.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ) &&
+ Vcb->CleanupCount != 0) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+ }
+
+ //
+ // Go ahead and flush and purge the volume. Then test to see if all
+ // of the user file objects were closed.
+ //
+
+ Status = NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE );
+
+ //
+ // If the flush and purge was successful but there are still file objects
+ // that block this open it is possible that the FspClose thread is
+ // blocked behind the Vcb. Drop the Fcb and Vcb to allow this thread
+ // to get in and then reacquire them. This will give this Dasd open
+ // another chance to succeed on the first try.
+ //
+
+ SharingViolation = FALSE;
+
+ if (FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ)) {
+
+ if (Vcb->ReadOnlyCloseCount != (Vcb->CloseCount - Vcb->SystemFileCloseCount)) {
+
+ SharingViolation = TRUE;
+ }
+
+ } else if (Vcb->CloseCount != Vcb->SystemFileCloseCount) {
+
+ SharingViolation = TRUE;
+ }
+
+ if (SharingViolation && NT_SUCCESS( Status )) {
+
+ //
+ // We need to commit the current transaction and release any
+ // resources. This will release the Fcb for the volume as
+ // well. Explicitly release the Vcb.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
+
+ NtfsReleaseFcbWithPaging( IrpContext,
+ (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ if (ThisFcb->PagingIoResource != NULL) {
+
+ NtfsReleasePagingIo( IrpContext, ThisFcb );
+ }
+
+ ExReleaseResource( ThisFcb->Resource );
+ FcbAcquired = FALSE;
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ VcbAcquired = FALSE;
+
+ //
+ // Now explicitly reacquire the Vcb and Fcb. Test that no one
+ // else got in to lock the volume in the meantime.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT )) {
+
+ DeleteVcb = TRUE;
+ }
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+
+ //
+ // Now acquire the Fcb for the VolumeDasd.
+ //
+
+ if (ThisFcb->PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, ThisFcb );
+ }
+
+ ExAcquireResourceExclusive( ThisFcb->Resource, TRUE );
+ FcbAcquired = TRUE;
+
+ //
+ // Duplicate the flush/purge and test if there is no sharing
+ // violation.
+ //
+
+ Status = NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE );
+
+ SharingViolation = FALSE;
+
+ if (FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_READ)) {
+
+ if (Vcb->ReadOnlyCloseCount != (Vcb->CloseCount - Vcb->SystemFileCloseCount)) {
+
+ SharingViolation = TRUE;
+ }
+
+ } else if (Vcb->CloseCount != Vcb->SystemFileCloseCount) {
+
+ SharingViolation = TRUE;
+ }
+ }
+
+ //
+ // Return an error if there are still conflicting file objects.
+ //
+
+ if (SharingViolation) {
+
+ //
+ // If there was an error in the flush then return it. Otherwise
+ // return SHARING_VIOLATION.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ } else { try_return( Status ); }
+ }
+
+
+ if (!NT_SUCCESS( Status )) {
+
+ //
+ // If there are no conflicts but the status indicates disk corruption
+ // or a section that couldn't be removed then ignore the error. We
+ // allow this open to succeed so that chkdsk can open the volume to
+ // repair the damage.
+ //
+
+ if ((Status == STATUS_UNABLE_TO_DELETE_SECTION) ||
+ (Status == STATUS_DISK_CORRUPT_ERROR) ||
+ (Status == STATUS_FILE_CORRUPT_ERROR)) {
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // Fail this request on any other failures.
+ //
+
+ } else {
+
+ try_return( Status );
+ }
+ }
+
+ //
+ // Remember that we want to lock the volume.
+ //
+
+ LockVolume = TRUE;
+
+ //
+ // Just flush the volume data if the user requested read or write.
+ // No need to purge or lock the volume.
+ //
+
+ } else if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
+ FILE_READ_DATA | FILE_WRITE_DATA | FILE_APPEND_DATA )) {
+
+ if (!NT_SUCCESS( Status = NtfsFlushVolume( IrpContext, Vcb, TRUE, FALSE, TRUE, FALSE ))) {
+
+ try_return( Status );
+ }
+ }
+
+ //
+ // Put the Volume Dasd name in the file object.
+ //
+
+ {
+ PVOID Temp = IrpSp->FileObject->FileName.Buffer;
+
+ IrpSp->FileObject->FileName.Buffer = FsRtlAllocatePoolWithTag(PagedPool, 8*2, MODULE_POOL_TAG );
+
+ if (Temp != NULL) {
+
+ NtfsFreePool( Temp );
+ }
+
+ RtlCopyMemory( IrpSp->FileObject->FileName.Buffer, L"\\$Volume", 8*2 );
+ IrpSp->FileObject->FileName.MaximumLength =
+ IrpSp->FileObject->FileName.Length = 8*2;
+ }
+
+ //
+ // We never allow cached access to the volume file.
+ //
+
+ ClearFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
+ SetFlag( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING );
+
+ //
+ // Go ahead open the attribute. This should only fail if there is an
+ // allocation failure or share access failure.
+ //
+
+ if (NT_SUCCESS( Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ Vcb,
+ NULL,
+ ThisFcb,
+ 2,
+ NtfsEmptyString,
+ $DATA,
+ (ThisFcb->CleanupCount == 0 ?
+ SetShareAccess :
+ CheckShareAccess),
+ UserVolumeOpen,
+ CCB_FLAG_OPEN_AS_FILE,
+ NULL,
+ &Vcb->VolumeDasdScb,
+ &ThisCcb ))) {
+
+ //
+ // Perform the final initialization.
+ //
+
+ //
+ // If we are locking the volume, do so now.
+ //
+
+ if (LockVolume) {
+
+ SetFlag( Vcb->VcbState, VCB_STATE_LOCKED );
+ Vcb->FileObjectWithVcbLocked = IrpSp->FileObject;
+ }
+
+ //
+ // Report that we opened the volume.
+ //
+
+ Irp->IoStatus.Information = FILE_OPENED;
+ }
+
+ try_exit: NOTHING;
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ //
+ // If we have a successful open then remove the name out of
+ // the file object. The IO system gets confused when it
+ // is there. We will deallocate the buffer with the Ccb
+ // when the handle is closed.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ IrpSp->FileObject->FileName.Buffer = NULL;
+ IrpSp->FileObject->FileName.MaximumLength =
+ IrpSp->FileObject->FileName.Length = 0;
+
+ SetFlag( ThisCcb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCommonVolumeOpen );
+
+ if (VcbAcquired) {
+
+ if (DeleteVcb) {
+
+ NtfsReleaseVcbCheckDelete( IrpContext, Vcb, IRP_MJ_CREATE, NULL );
+
+ } else {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+ }
+
+ if (FcbAcquired) { ExReleaseResource( ThisFcb->Resource ); }
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonVolumeOpen: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOpenFcbById (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PLCB ParentLcb OPTIONAL,
+ IN OUT PFCB *CurrentFcb,
+ IN BOOLEAN UseCurrentFcb,
+ IN FILE_REFERENCE FileReference,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to open a file by its file Id. We need to
+ verify that this file Id exists and then compare the type of the
+ file with the requested type of open.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ Vcb - Vcb for this volume.
+
+ ParentLcb - Lcb used to reach this Fcb. Only specified when opening
+ a file by name relative to a directory opened by file Id.
+
+ CurrentFcb - Address of Fcb pointer. It will either be the
+ Fcb to open or we will store the Fcb we find here.
+
+ UseCurrentFcb - Indicate in the CurrentFcb above points to the target
+ Fcb or if we should find it here.
+
+ FileReference - This is the file Id for the file to open.
+
+ AttrName - This is the name of the attribute to open.
+
+ AttrCodeName - This is the name of the attribute code to open.
+
+ NetworkInfo - If specified then this call is a fast open call to query
+ the network information. We don't update any of the in-memory structures
+ for this.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+Return Value:
+
+ NTSTATUS - Indicates the result of this create file operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ LONGLONG MftOffset;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PBCB Bcb = NULL;
+
+ BOOLEAN IndexedAttribute;
+
+ PFCB ThisFcb;
+ BOOLEAN ExistingFcb = FALSE;
+
+ ULONG CcbFlags = 0;
+ ATTRIBUTE_TYPE_CODE AttrTypeCode;
+ OLD_SCB_SNAPSHOT ScbSizes;
+ BOOLEAN HaveScbSizes = FALSE;
+ BOOLEAN DecrementCloseCount = FALSE;
+
+ PSCB ParentScb = NULL;
+ PLCB Lcb = ParentLcb;
+ BOOLEAN AcquiredParentScb = FALSE;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+
+ UNREFERENCED_PARAMETER( NetworkInfo );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenFcbById: Entered\n") );
+
+ //
+ // The next thing to do is to figure out what type
+ // of attribute the caller is trying to open. This involves the
+ // directory/non-directory bits, the attribute name and code strings,
+ // the type of file, whether he passed in an ea buffer and whether
+ // there was a trailing backslash.
+ //
+
+ if (NtfsEqualMftRef( &FileReference,
+ &VolumeFileReference )) {
+
+ if (AttrName.Length != 0
+ || AttrCodeName.Length != 0) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ DebugTrace( -1, Dbg, ("NtfsOpenFcbById: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX | IRP_CONTEXT_FLAG_DASD_OPEN );
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If we don't already have the Fcb then look up the file record
+ // from the disk.
+ //
+
+ if (!UseCurrentFcb) {
+
+ //
+ // We start by reading the disk and checking that the file record
+ // sequence number matches and that the file record is in use.
+ // We remember whether this is a directory. We will only go to
+ // the file if the file Id will lie within the Mft File.
+ //
+
+ MftOffset = NtfsFullSegmentNumber( &FileReference );
+
+ MftOffset = Int64ShllMod32(MftOffset, Vcb->MftShift);
+
+ if (MftOffset >= Vcb->MftScb->Header.FileSize.QuadPart) {
+
+ DebugTrace( 0, Dbg, ("File Id doesn't lie within Mft\n") );
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ NtfsReadMftRecord( IrpContext,
+ Vcb,
+ &FileReference,
+ &Bcb,
+ &FileRecord,
+ NULL );
+
+ //
+ // This file record better be in use, have a matching sequence number and
+ // be the primary file record for this file.
+ //
+
+ if (FileRecord->SequenceNumber != FileReference.SequenceNumber
+ || !FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE )
+ || (*((PLONGLONG)&FileRecord->BaseFileRecordSegment) != 0)) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // We perform a check to see whether we will allow the system
+ // files to be opened.
+ //
+
+ if (NtfsProtectSystemFiles) {
+
+ //
+ // We only allow user opens on the Volume Dasd file and the
+ // root directory.
+ //
+
+ if (NtfsSegmentNumber( &FileReference ) < FIRST_USER_FILE_NUMBER
+ && NtfsSegmentNumber( &FileReference ) != VOLUME_DASD_NUMBER
+ && NtfsSegmentNumber( &FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
+
+ Status = STATUS_ACCESS_DENIED;
+ DebugTrace( 0, Dbg, ("Attempting to open system files\n") );
+
+ try_return( NOTHING );
+ }
+ }
+
+ //
+ // If indexed then use the name for the file name index.
+ //
+
+ if (FlagOn( FileRecord->Flags, FILE_FILE_NAME_INDEX_PRESENT )) {
+
+ AttrName = NtfsFileNameIndex;
+ AttrCodeName = NtfsIndexAllocation;
+ }
+
+ NtfsUnpinBcb( &Bcb );
+
+ } else {
+
+ ThisFcb = *CurrentFcb;
+ ExistingFcb = TRUE;
+ }
+
+ Status = NtfsCheckValidAttributeAccess( IrpSp,
+ Vcb,
+ ExistingFcb ? &ThisFcb->Info : NULL,
+ AttrName,
+ AttrCodeName,
+ FALSE,
+ &AttrTypeCode,
+ &CcbFlags,
+ &IndexedAttribute );
+
+ if (!NT_SUCCESS( Status )) {
+
+ try_return( Status );
+ }
+
+ //
+ // If we don't have an Fcb then create one now.
+ //
+
+ if (!UseCurrentFcb) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ //
+ // We know that it is safe to continue the open. We start by creating
+ // an Fcb for this file. It is possible that the Fcb exists.
+ // We create the Fcb first, if we need to update the Fcb info structure
+ // we copy the one from the index entry. We look at the Fcb to discover
+ // if it has any links, if it does then we make this the last Fcb we
+ // reached. If it doesn't then we have to clean it up from here.
+ //
+
+ ThisFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ FileReference,
+ BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
+ TRUE,
+ &ExistingFcb );
+
+ ThisFcb->ReferenceCount += 1;
+
+ //
+ // Try to do a fast acquire, otherwise we need to release
+ // the Fcb table, acquire the Fcb, acquire the Fcb table to
+ // dereference Fcb.
+ //
+
+ if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, TRUE )) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, FALSE );
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ }
+
+ ThisFcb->ReferenceCount -= 1;
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // Store this Fcb into our caller's parameter and remember to
+ // to show we acquired it.
+ //
+
+ *CurrentFcb = ThisFcb;
+ }
+
+ //
+ // If the Fcb existed and this is a paging file then either return
+ // sharing violation or force the Fcb and Scb's to go away.
+ // Do this for the case where the user is opening a paging file
+ // but the Fcb is non-paged or the user is opening a non-paging
+ // file and the Fcb is for a paging file.
+ //
+
+ if (ExistingFcb &&
+
+ ((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
+ !FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
+
+ (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ !FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )))) {
+
+ if (ThisFcb->CleanupCount != 0) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If we have a persistent paging file then give up and
+ // return SHARING_VIOLATION.
+ //
+
+ } else if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP )) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If there was an existing Fcb for a paging file we need to force
+ // all of the Scb's to be torn down. The easiest way to do this
+ // is to flush and purge all of the Scb's (saving any attribute list
+ // for last) and then raise LOG_FILE_FULL to allow this request to
+ // be posted.
+ //
+
+ } else {
+
+ //
+ // Reference the Fcb so it doesn't go away.
+ //
+
+ InterlockedIncrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = TRUE;
+
+ //
+ // Flush and purge this Fcb.
+ //
+
+ NtfsFlushAndPurgeFcb( IrpContext, ThisFcb );
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = FALSE;
+
+ //
+ // Force this request to be posted and then raise
+ // CANT_WAIT.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+
+ //
+ // If the Fcb Info field needs to be initialized, we do so now.
+ // We read this information from the disk.
+ //
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ ThisFcb,
+ NULL,
+ &ScbSizes );
+
+ HaveScbSizes = TRUE;
+
+ //
+ // Fix the quota for this file if necessary.
+ //
+
+ NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
+
+ }
+
+ //
+ // If the link count is zero on this Fcb, then delete is pending.
+ //
+
+ if (ThisFcb->LinkCount == 0) {
+
+ try_return( Status = STATUS_DELETE_PENDING );
+ }
+
+ //
+ // We now call the worker routine to open an attribute on an existing file.
+ //
+
+ Status = NtfsOpenAttributeInExistingFile( IrpContext,
+ Irp,
+ IrpSp,
+ ParentLcb,
+ ThisFcb,
+ 0,
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ TRUE,
+ NULL,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // Check to see if we should update the last access time.
+ //
+
+ if (NT_SUCCESS( Status ) && (Status != STATUS_PENDING)) {
+
+ PSCB Scb = *ThisScb;
+
+ //
+ // Now look at whether we need to update the Fcb and on disk
+ // structures.
+ //
+
+ NtfsCheckLastAccess( IrpContext, ThisFcb );
+
+ //
+ // Perform the last bit of work. If this a user file open, we need
+ // to check if we initialize the Scb.
+ //
+
+ if (!IndexedAttribute) {
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ //
+ // We may have the sizes from our Fcb update call.
+ //
+
+ if (HaveScbSizes &&
+ (AttrTypeCode == $DATA) &&
+ (AttrName.Length == 0) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
+
+ NtfsUpdateScbFromMemory( IrpContext, Scb, &ScbSizes );
+
+ } else {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+ }
+
+ //
+ // If there is a potential for a write call to be issued, then we
+ // need to expand the quota.
+ //
+
+ if (IrpSp->FileObject->WriteAccess) {
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+
+ }
+
+ //
+ // Let's check if we need to set the cache bit.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_INTERMEDIATE_BUFFERING )) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
+ }
+ }
+
+ //
+ // If this operation was a supersede/overwrite or we created a new
+ // attribute stream then we want to perform the file record and
+ // directory update now. Otherwise we will defer the updates until
+ // the user closes his handle.
+ //
+
+ if ((Irp->IoStatus.Information == FILE_CREATED) ||
+ (Irp->IoStatus.Information == FILE_SUPERSEDED) ||
+ (Irp->IoStatus.Information == FILE_OVERWRITTEN)) {
+
+ NtfsUpdateScbFromFileObject( IrpContext, IrpSp->FileObject, *ThisScb, TRUE );
+
+ //
+ // Do the standard information, file sizes and then duplicate information
+ // if needed.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, ThisFcb );
+ }
+
+ if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
+
+ NtfsWriteFileSizes( IrpContext,
+ *ThisScb,
+ &(*ThisScb)->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+ }
+
+ if (FlagOn( ThisFcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
+
+ NtfsPrepareForUpdateDuplicate( IrpContext, ThisFcb, &Lcb, &ParentScb, FALSE );
+ NtfsUpdateDuplicateInfo( IrpContext, ThisFcb, NULL, NULL );
+ NtfsUpdateLcbDuplicateInfo( ThisFcb, Lcb );
+ ThisFcb->InfoFlags = 0;
+ }
+
+ ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ NtfsAcquireFsrtlHeader( *ThisScb );
+ ClearFlag( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ NtfsReleaseFsrtlHeader( *ThisScb );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsOpenFcbById );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ //
+ // If this operation was not totally successful we need to
+ // back out the following changes.
+ //
+ // Modifications to the Info fields in the Fcb.
+ // Any changes to the allocation of the Scb.
+ // Any changes in the open counts in the various structures.
+ // Changes to the share access values in the Fcb.
+ //
+
+ if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
+
+ NtfsBackoutFailedOpens( IrpContext,
+ IrpSp->FileObject,
+ ThisFcb,
+ *ThisScb,
+ *ThisCcb );
+ }
+
+ if (DecrementCloseCount) {
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ }
+
+ NtfsUnpinBcb( &Bcb );
+
+ DebugTrace( -1, Dbg, ("NtfsOpenFcbById: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOpenExistingPrefixFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PLCB Lcb OPTIONAL,
+ IN ULONG FullPathNameLength,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will open an attribute in a file whose Fcb was found
+ with a prefix search.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ ThisFcb - This is the Fcb to open.
+
+ Lcb - This is the Lcb used to reach this Fcb. Not specified if this is a volume open.
+
+ FullPathNameLength - This is the length of the full path name.
+
+ AttrName - This is the name of the attribute to open.
+
+ AttrCodeName - This is the name of the attribute code to open.
+
+ DosOnlyComponent - Indicates if there is a DOS-ONLY component in an ancestor
+ of this open.
+
+ TrailingBackslash - Indicates if caller had a terminating backslash on the
+ name.
+
+ NetworkInfo - If specified then this call is a fast open call to query
+ the network information. We don't update any of the in-memory structures
+ for this.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+Return Value:
+
+ NTSTATUS - Indicates the result of this attribute based operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ATTRIBUTE_TYPE_CODE AttrTypeCode;
+ ULONG CcbFlags;
+ BOOLEAN IndexedAttribute;
+ BOOLEAN DecrementCloseCount = FALSE;
+
+ ULONG LastFileNameOffset;
+
+ OLD_SCB_SNAPSHOT ScbSizes;
+ BOOLEAN HaveScbSizes = FALSE;
+
+ ULONG CreateDisposition;
+
+ PSCB ParentScb = NULL;
+ PFCB ParentFcb = NULL;
+ BOOLEAN AcquiredParentScb = FALSE;
+
+ LONGLONG CurrentTime;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenExistingPrefixFcb: Entered\n") );
+
+ if (DosOnlyComponent) {
+
+ CcbFlags = CCB_FLAG_PARENT_HAS_DOS_COMPONENT;
+
+ } else {
+
+ CcbFlags = 0;
+ }
+
+ //
+ // The first thing to do is to figure out what type
+ // of attribute the caller is trying to open. This involves the
+ // directory/non-directory bits, the attribute name and code strings,
+ // the type of file, whether he passed in an ea buffer and whether
+ // there was a trailing backslash.
+ //
+
+ if (NtfsEqualMftRef( &ThisFcb->FileReference, &VolumeFileReference )) {
+
+ if ((AttrName.Length != 0) || (AttrCodeName.Length != 0)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX | IRP_CONTEXT_FLAG_DASD_OPEN );
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ ParentScb = Lcb->Scb;
+
+ LastFileNameOffset = FullPathNameLength - Lcb->ExactCaseLink.LinkName.Length;
+
+ if (ParentScb != NULL) {
+
+ ParentFcb = ParentScb->Fcb;
+ }
+
+ Status = NtfsCheckValidAttributeAccess( IrpSp,
+ ThisFcb->Vcb,
+ &ThisFcb->Info,
+ AttrName,
+ AttrCodeName,
+ TrailingBackslash,
+ &AttrTypeCode,
+ &CcbFlags,
+ &IndexedAttribute );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If the Fcb existed and this is a paging file then either return
+ // sharing violation or force the Fcb and Scb's to go away.
+ // Do this for the case where the user is opening a paging file
+ // but the Fcb is non-paged or the user is opening a non-paging
+ // file and the Fcb is for a paging file.
+ //
+
+ if ((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
+ !FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
+
+ (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ !FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ))) {
+
+ if (ThisFcb->CleanupCount != 0) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If we have a persistent paging file then give up and
+ // return SHARING_VIOLATION.
+ //
+
+ } else if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP )) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If there was an existing Fcb for a paging file we need to force
+ // all of the Scb's to be torn down. The easiest way to do this
+ // is to flush and purge all of the Scb's (saving any attribute list
+ // for last) and then raise LOG_FILE_FULL to allow this request to
+ // be posted.
+ //
+
+ } else {
+
+ //
+ // Make sure this Fcb won't go away as a result of purging
+ // the Fcb.
+ //
+
+ InterlockedIncrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = TRUE;
+
+ //
+ // Flush and purge this Fcb.
+ //
+
+ NtfsFlushAndPurgeFcb( IrpContext, ThisFcb );
+
+ //
+ // Now decrement the close count we have already biased.
+ //
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = FALSE;
+
+ //
+ // Force this request to be posted and then raise
+ // CANT_WAIT.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+
+ //
+ // If this is a directory, it's possible that we hav an existing Fcb
+ // in the prefix table which needs to be initialized from the disk.
+ // We look in the InfoInitialized flag to know whether to go to
+ // disk.
+ //
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ //
+ // If we have a parent Fcb then make sure to acquire it.
+ //
+
+ if (ParentScb != NULL) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ AcquiredParentScb = TRUE;
+ }
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ ThisFcb,
+ ParentFcb,
+ &ScbSizes );
+
+ HaveScbSizes = TRUE;
+
+ NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
+ }
+
+ //
+ // Check now whether we will need to acquire the parent to
+ // perform a update duplicate info. We need to acquire it
+ // now to enforce our locking order in case any of the
+ // routines below acquire the Mft Scb. Acquire it if we
+ // are doing a supersede/overwrite or possibly creating
+ // a named data stream.
+ //
+
+ if ((CreateDisposition == FILE_SUPERSEDE) ||
+ (CreateDisposition == FILE_OVERWRITE) ||
+ (CreateDisposition == FILE_OVERWRITE_IF) ||
+ ((AttrName.Length != 0) &&
+ ((CreateDisposition == FILE_OPEN_IF) ||
+ (CreateDisposition == FILE_CREATE)))) {
+
+ NtfsPrepareForUpdateDuplicate( IrpContext,
+ ThisFcb,
+ &Lcb,
+ &ParentScb,
+ FALSE );
+ }
+
+ //
+ // Call to open an attribute on an existing file.
+ // Remember we need to restore the Fcb info structure
+ // on errors.
+ //
+
+ Status = NtfsOpenAttributeInExistingFile( IrpContext,
+ Irp,
+ IrpSp,
+ Lcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ FALSE,
+ NetworkInfo,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // Check to see if we should update the last access time.
+ //
+
+ if (NT_SUCCESS( Status ) && (Status != STATUS_PENDING)) {
+
+ PSCB Scb = *ThisScb;
+
+ //
+ // This is a rare case. There must have been an allocation failure
+ // to cause this but make sure the normalized name is stored.
+ //
+
+ if ((SafeNodeType( Scb ) == NTFS_NTC_SCB_INDEX) &&
+ (Scb->ScbType.Index.NormalizedName.Buffer == NULL) &&
+ (ParentScb != NULL) &&
+ (ParentScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ ParentScb,
+ Scb,
+ NULL,
+ FALSE );
+ }
+
+ //
+ // Perform the last bit of work. If this a user file open, we need
+ // to check if we initialize the Scb.
+ //
+
+ if (!IndexedAttribute) {
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ //
+ // We may have the sizes from our Fcb update call.
+ //
+
+ if (HaveScbSizes &&
+ (AttrTypeCode == $DATA) &&
+ (AttrName.Length == 0) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
+
+ NtfsUpdateScbFromMemory( IrpContext, Scb, &ScbSizes );
+
+ } else {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ }
+ }
+
+ if (IrpSp->FileObject->WriteAccess) {
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+ }
+
+ //
+ // Let's check if we need to set the cache bit.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NO_INTERMEDIATE_BUFFERING )) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
+ }
+ }
+
+ //
+ // If this is the paging file, we want to be sure the allocation
+ // is loaded.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )
+ && (Scb->Header.AllocationSize.QuadPart != 0)
+ && !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ LCN Lcn;
+ VCN Vcn;
+ VCN AllocatedVcns;
+
+ AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
+
+ //
+ // First make sure the Mcb is loaded.
+ //
+
+ NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
+
+ //
+ // Now make sure the allocation is correctly loaded. The last
+ // Vcn should correspond to the allocation size for the file.
+ //
+
+ if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
+ &Vcn,
+ &Lcn ) ||
+ (Vcn + 1) != AllocatedVcns) {
+
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ ThisFcb );
+ }
+ }
+
+ //
+ // If this open is for an executable image we will want to update the
+ // last access time.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess, FILE_EXECUTE ) &&
+ (Scb->AttributeTypeCode == $DATA)) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
+ }
+
+ //
+ // If this operation was a supersede/overwrite or we created a new
+ // attribute stream then we want to perform the file record and
+ // directory update now. Otherwise we will defer the updates until
+ // the user closes his handle.
+ //
+
+ if ((Irp->IoStatus.Information == FILE_CREATED) ||
+ (Irp->IoStatus.Information == FILE_SUPERSEDED) ||
+ (Irp->IoStatus.Information == FILE_OVERWRITTEN)) {
+
+ NtfsUpdateScbFromFileObject( IrpContext, IrpSp->FileObject, *ThisScb, TRUE );
+
+ //
+ // Do the standard information, file sizes and then duplicate information
+ // if needed.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, ThisFcb );
+ }
+
+ if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
+
+ NtfsWriteFileSizes( IrpContext,
+ *ThisScb,
+ &(*ThisScb)->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+ }
+
+ if (FlagOn( ThisFcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
+
+ ULONG FilterMatch;
+
+ NtfsUpdateDuplicateInfo( IrpContext, ThisFcb, Lcb, ParentScb );
+
+ if (ThisFcb->Vcb->NotifyCount != 0) {
+
+ //
+ // We map the Fcb info flags into the dir notify flags.
+ //
+
+ FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
+ ThisFcb->InfoFlags | Lcb->InfoFlags );
+
+ //
+ // If the filter match is non-zero, that means we also need to do a
+ // dir notify call.
+ //
+
+ if ((FilterMatch != 0) && (*ThisCcb != NULL)) {
+
+ NtfsReportDirNotify( IrpContext,
+ ThisFcb->Vcb,
+ &(*ThisCcb)->FullFileName,
+ (*ThisCcb)->LastFileNameOffset,
+ NULL,
+ ((FlagOn( (*ThisCcb)->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ (*ThisCcb)->Lcb != NULL &&
+ (*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &(*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ FILE_ACTION_MODIFIED,
+ ParentFcb );
+ }
+ }
+
+ NtfsUpdateLcbDuplicateInfo( ThisFcb, Lcb );
+ ThisFcb->InfoFlags = 0;
+ }
+
+ ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ NtfsAcquireFsrtlHeader( *ThisScb );
+ ClearFlag( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ NtfsReleaseFsrtlHeader( *ThisScb );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsOpenExistingPrefixFcb );
+
+ if (DecrementCloseCount) {
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ }
+
+ //
+ // If this operation was not totally successful we need to
+ // back out the following changes.
+ //
+ // Modifications to the Info fields in the Fcb.
+ // Any changes to the allocation of the Scb.
+ // Any changes in the open counts in the various structures.
+ // Changes to the share access values in the Fcb.
+ //
+
+ if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
+
+ NtfsBackoutFailedOpens( IrpContext,
+ IrpSp->FileObject,
+ ThisFcb,
+ *ThisScb,
+ *ThisCcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOpenTargetDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PLCB ParentLcb OPTIONAL,
+ IN OUT PUNICODE_STRING FullPathName,
+ IN ULONG FinalNameLength,
+ IN BOOLEAN TargetExisted,
+ IN BOOLEAN DosOnlyComponent,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will perform the work of opening a target directory. When the
+ open is complete the Ccb and Lcb for this file object will be identical
+ to any other open. We store the full name for the rename in the
+ file object but set the 'Length' field to include only the
+ name upto the parent directory. We use the 'MaximumLength' field to
+ indicate the full name.
+
+Arguments:
+
+ Irp - This is the Irp for this create operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ ThisFcb - This is the Fcb for the directory to open.
+
+ ParentLcb - This is the Lcb used to reach the parent directory. If not
+ specified, we will have to find it here. There will be no Lcb to
+ find if this Fcb was opened by Id.
+
+ FullPathName - This is the normalized string for open operation. It now
+ contains the full name as it appears on the disk for this open path.
+ It may not reach all the way to the root if the relative file object
+ was opened by Id.
+
+ FinalNameLength - This is the length of the final component in the
+ full path name.
+
+ TargetExisted - Indicates if the file indicated by the FinalName string
+ currently exists on the disk.
+
+ DosOnlyComponent - Indicates if there is a DOS-ONLY component in an ancestor
+ of this open.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+Return Value:
+
+ NTSTATUS - Indicating the outcome of opening this target directory.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG CcbFlags = CCB_FLAG_OPEN_AS_FILE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenTargetDirectory: Entered\n") );
+
+ if (DosOnlyComponent) {
+
+ SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
+ }
+
+ //
+ // If the name doesn't begin with a backslash, remember this as
+ // an open by file ID.
+ //
+
+ if (FullPathName->Buffer[0] != L'\\') {
+
+ SetFlag( CcbFlags, CCB_FLAG_OPEN_BY_FILE_ID );
+ }
+
+ //
+ // Modify the full path name so that the Maximum length field describes
+ // the full name and the Length field describes the name for the
+ // parent.
+ //
+
+ FullPathName->MaximumLength = FullPathName->Length;
+
+ //
+ // If we don't have an Lcb, we will find it now. We look at each Lcb
+ // for the parent Fcb and find one which matches the component
+ // ahead of the last component of the full name.
+ //
+
+ FullPathName->Length -= (USHORT)FinalNameLength;
+
+ //
+ // If we are not at the root then subtract the bytes for the '\\'
+ // separator.
+ //
+
+ if (FullPathName->Length > sizeof( WCHAR )) {
+
+ FullPathName->Length -= sizeof( WCHAR );
+ }
+
+ if (!ARGUMENT_PRESENT( ParentLcb ) && (FullPathName->Length != 0)) {
+
+ PLIST_ENTRY Links;
+ PLCB NextLcb;
+
+ //
+ // If the length is two then the parent Lcb is the root Lcb.
+ //
+
+ if (FullPathName->Length == sizeof( WCHAR )
+ && FullPathName->Buffer[0] == L'\\') {
+
+ ParentLcb = (PLCB) ThisFcb->Vcb->RootLcb;
+
+ } else {
+
+ for (Links = ThisFcb->LcbQueue.Flink;
+ Links != &ThisFcb->LcbQueue;
+ Links = Links->Flink) {
+
+ SHORT NameOffset;
+
+ NextLcb = CONTAINING_RECORD( Links,
+ LCB,
+ FcbLinks );
+
+ NameOffset = (SHORT) FullPathName->Length - (SHORT) NextLcb->ExactCaseLink.LinkName.Length;
+
+ if (NameOffset >= 0) {
+
+ if (RtlEqualMemory( Add2Ptr( FullPathName->Buffer,
+ NameOffset ),
+ NextLcb->ExactCaseLink.LinkName.Buffer,
+ NextLcb->ExactCaseLink.LinkName.Length )) {
+
+ //
+ // We found a matching Lcb. Remember this and exit
+ // the loop.
+ //
+
+ ParentLcb = NextLcb;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // Check this open for security access.
+ //
+
+ NtfsOpenCheck( IrpContext, ThisFcb, NULL, Irp );
+
+ //
+ // Now actually open the attribute.
+ //
+
+ Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ ThisFcb->Vcb,
+ ParentLcb,
+ ThisFcb,
+ (ARGUMENT_PRESENT( ParentLcb )
+ ? FullPathName->Length - ParentLcb->ExactCaseLink.LinkName.Length
+ : 0),
+ NtfsFileNameIndex,
+ $INDEX_ALLOCATION,
+ (ThisFcb->CleanupCount == 0 ? SetShareAccess : CheckShareAccess),
+ UserDirectoryOpen,
+ CcbFlags,
+ NULL,
+ ThisScb,
+ ThisCcb );
+
+ if (NT_SUCCESS( Status )) {
+
+ //
+ // If the Scb does not have a normalized name then update it now.
+ //
+
+ if (((*ThisScb)->ScbType.Index.NormalizedName.Buffer == NULL) ||
+ ((*ThisScb)->ScbType.Index.NormalizedName.Length == 0)) {
+
+ NtfsBuildNormalizedName( IrpContext,
+ *ThisScb,
+ &(*ThisScb)->ScbType.Index.NormalizedName );
+ }
+
+ //
+ // If the file object name is not from the root then use the normalized name
+ // to obtain the full name.
+ //
+
+ if (FlagOn( CcbFlags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ USHORT BytesNeeded;
+ USHORT Index;
+ ULONG ComponentCount;
+ ULONG NormalizedComponentCount;
+ PWCHAR NewBuffer;
+ PWCHAR NextChar;
+
+ //
+ // Count the number of components in the directory portion of the
+ // name in the file object.
+ //
+
+ ComponentCount = 0;
+
+ if (FullPathName->Length != 0) {
+
+ ComponentCount = 1;
+ Index = (FullPathName->Length / sizeof( WCHAR )) - 1;
+
+ do {
+
+ if (FullPathName->Buffer[Index] == L'\\') {
+
+ ComponentCount += 1;
+ }
+
+ Index -= 1;
+
+ } while (Index != 0);
+ }
+
+ //
+ // Count back this number of components in the normalized name.
+ //
+
+ NormalizedComponentCount = 0;
+ Index = (*ThisScb)->ScbType.Index.NormalizedName.Length / sizeof( WCHAR );
+
+ //
+ // Special case the root to point directory to the leading backslash.
+ //
+
+ if (Index == 1) {
+
+ Index = 0;
+ }
+
+ while (NormalizedComponentCount < ComponentCount) {
+
+ Index -= 1;
+ while ((*ThisScb)->ScbType.Index.NormalizedName.Buffer[Index] != L'\\') {
+
+ Index -= 1;
+ }
+
+ NormalizedComponentCount += 1;
+ }
+
+ //
+ // Compute the size of the buffer needed for the full name. This
+ // will be:
+ //
+ // - Portion of normalized name used plus a separator
+ // - MaximumLength currently in FullPathName
+ //
+
+ BytesNeeded = ((Index + 1) * sizeof( WCHAR )) + FullPathName->MaximumLength;
+
+ NextChar =
+ NewBuffer = NtfsAllocatePool( PagedPool, BytesNeeded );
+
+ //
+ // Copy over the portion of the name from the normalized name.
+ //
+
+ if (Index != 0) {
+
+ RtlCopyMemory( NextChar,
+ (*ThisScb)->ScbType.Index.NormalizedName.Buffer,
+ Index * sizeof( WCHAR ));
+
+ NextChar += Index;
+ }
+
+ *NextChar = L'\\';
+ NextChar += 1;
+
+ //
+ // Now copy over the remaining part of the name from the file object.
+ //
+
+ RtlCopyMemory( NextChar,
+ FullPathName->Buffer,
+ FullPathName->MaximumLength );
+
+ //
+ // Now free the pool from the file object and update with the newly
+ // allocated pool. Don't forget to update the Ccb to point to this new
+ // buffer.
+ //
+
+ NtfsFreePool( FullPathName->Buffer );
+
+ FullPathName->Buffer = NewBuffer;
+ FullPathName->MaximumLength =
+ FullPathName->Length = BytesNeeded;
+ FullPathName->Length -= (USHORT) FinalNameLength;
+
+ if (FullPathName->Length > sizeof( WCHAR )) {
+
+ FullPathName->Length -= sizeof( WCHAR );
+ }
+
+ (*ThisCcb)->FullFileName = *FullPathName;
+ (*ThisCcb)->LastFileNameOffset = FullPathName->MaximumLength - (USHORT) FinalNameLength;
+ }
+
+ Irp->IoStatus.Information = (TargetExisted ? FILE_EXISTS : FILE_DOES_NOT_EXIST);
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsOpenTargetDirectory: Exit -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOpenFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PSCB ParentScb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN UNICODE_STRING FullPathName,
+ IN UNICODE_STRING FinalName,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN OpenById,
+ IN PQUICK_INDEX QuickIndex,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when we need to open an attribute on a file
+ which currently exists. We have the ParentScb and the file reference
+ for the existing file. We will create the Fcb for this file and the
+ link between it and its parent directory. We will add this link to the
+ prefix table as well as the link for its parent Scb if specified.
+
+ On entry the caller owns the parent Scb.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ ParentScb - This is the Scb for the parent directory.
+
+ IndexEntry - This is the index entry from the disk for this file.
+
+ FullPathName - This is the string containing the full path name of
+ this Fcb. Meaningless for an open by Id call.
+
+ FinalName - This is the string for the final component only. If the length
+ is zero then this is an open by Id call.
+
+ AttrName - This is the name of the attribute to open.
+
+ AttriCodeName - This is the name of the attribute code to open.
+
+ IgnoreCase - Indicates the type of open.
+
+ OpenById - Indicates if we are opening this file relative to a file opened by Id.
+
+ DosOnlyComponent - Indicates if there is a DOS-ONLY component in an ancestor
+ of this open.
+
+ TrailingBackslash - Indicates if caller had a terminating backslash on the
+ name.
+
+ NetworkInfo - If specified then this call is a fast open call to query
+ the network information. We don't update any of the in-memory structures
+ for this.
+
+ CurrentFcb - This is the address to store the Fcb if we successfully find
+ one in the Fcb/Scb tree.
+
+ LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
+ into the tree.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+Return Value:
+
+ NTSTATUS - Indicates the result of this create file operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ATTRIBUTE_TYPE_CODE AttrTypeCode;
+ ULONG CcbFlags = 0;
+ BOOLEAN IndexedAttribute;
+ PFILE_NAME IndexFileName;
+ BOOLEAN UpdateFcbInfo = FALSE;
+
+ OLD_SCB_SNAPSHOT ScbSizes;
+ BOOLEAN HaveScbSizes = FALSE;
+
+ PVCB Vcb = ParentScb->Vcb;
+
+ PFCB LocalFcbForTeardown = NULL;
+ PFCB ThisFcb;
+ PLCB ThisLcb;
+ BOOLEAN DecrementCloseCount = FALSE;
+ BOOLEAN ExistingFcb;
+ BOOLEAN AcquiredFcbTable = FALSE;
+
+ FILE_REFERENCE PreviousFileReference;
+ BOOLEAN DroppedParent = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenFile: Entered\n") );
+
+ IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
+
+ if (DosOnlyComponent) {
+
+ SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
+ }
+
+ //
+ // The first thing to do is to figure out what type
+ // of attribute the caller is trying to open. This involves the
+ // directory/non-directory bits, the attribute name and code strings,
+ // the type of file, whether he passed in an ea buffer and whether
+ // there was a trailing backslash.
+ //
+
+ if (NtfsEqualMftRef( &IndexEntry->FileReference,
+ &VolumeFileReference )) {
+
+ if (AttrName.Length != 0
+ || AttrCodeName.Length != 0) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ DebugTrace( -1, Dbg, ("NtfsOpenExistingPrefixFcb: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX | IRP_CONTEXT_FLAG_DASD_OPEN );
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ Status = NtfsCheckValidAttributeAccess( IrpSp,
+ Vcb,
+ &IndexFileName->Info,
+ AttrName,
+ AttrCodeName,
+ TrailingBackslash,
+ &AttrTypeCode,
+ &CcbFlags,
+ &IndexedAttribute );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsOpenFile: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // We know that it is safe to continue the open. We start by creating
+ // an Fcb and Lcb for this file. It is possible that the Fcb and Lcb
+ // both exist. If the Lcb exists, then the Fcb must definitely exist.
+ // We create the Fcb first, if we need to update the Fcb info structure
+ // we copy the one from the index entry. We look at the Fcb to discover
+ // if it has any links, if it does then we make this the last Fcb we
+ // reached. If it doesn't then we have to clean it up from here.
+ //
+
+ ThisFcb = NtfsCreateFcb( IrpContext,
+ ParentScb->Vcb,
+ IndexEntry->FileReference,
+ BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
+ BooleanFlagOn( IndexFileName->Info.FileAttributes,
+ DUP_FILE_NAME_INDEX_PRESENT ),
+ &ExistingFcb );
+
+ ThisFcb->ReferenceCount += 1;
+
+ //
+ // If we created this Fcb we must make sure to start teardown
+ // on it.
+ //
+
+ if (!ExistingFcb) {
+
+ LocalFcbForTeardown = ThisFcb;
+
+ } else {
+
+ *LcbForTeardown = NULL;
+ *CurrentFcb = ThisFcb;
+ }
+
+ //
+ // Try to do a fast acquire, otherwise we need to release
+ // the Fcb table, acquire the Fcb, acquire the Fcb table to
+ // dereference Fcb.
+ //
+
+ if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, TRUE )) {
+
+ //
+ // Remember the current file reference in the index entry.
+ // We want to be able to detect whether an entry is removed.
+ //
+
+ PreviousFileReference = IndexEntry->FileReference;
+ DroppedParent = TRUE;
+
+ ParentScb->Fcb->ReferenceCount += 1;
+ InterlockedIncrement( &ParentScb->CleanupCount );
+
+ //
+ // Set the IrpContext to acquire paging io resources if our target
+ // has one. This will lock the MappedPageWriter out of this file.
+ //
+
+ if (ThisFcb->PagingIoResource != NULL) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
+ }
+
+ NtfsReleaseScbWithPaging( IrpContext, ParentScb );
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, FALSE );
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ InterlockedDecrement( &ParentScb->CleanupCount );
+ ParentScb->Fcb->ReferenceCount -= 1;
+ }
+
+ ThisFcb->ReferenceCount -= 1;
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // Check if something happened to this file in the window where
+ // we dropped the parent.
+ //
+
+ if (DroppedParent) {
+
+ //
+ // Check if the file has been deleted.
+ //
+
+ if (ExistingFcb && (ThisFcb->LinkCount == 0)) {
+
+ try_return( Status = STATUS_DELETE_PENDING );
+
+ //
+ // Check if the link may have been deleted.
+ //
+
+ } else if (!NtfsEqualMftRef( &IndexEntry->FileReference,
+ &PreviousFileReference )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+
+ //
+ // If the Fcb existed and this is a paging file then either return
+ // sharing violation or force the Fcb and Scb's to go away.
+ // Do this for the case where the user is opening a paging file
+ // but the Fcb is non-paged or the user is opening a non-paging
+ // file and the Fcb is for a paging file.
+ //
+
+ if (ExistingFcb &&
+
+ ((FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
+ !FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) ||
+
+ (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ !FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )))) {
+
+ if (ThisFcb->CleanupCount != 0) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If we have a persistent paging file then give up and
+ // return SHARING_VIOLATION.
+ //
+
+ } else if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP )) {
+
+ try_return( Status = STATUS_SHARING_VIOLATION );
+
+ //
+ // If there was an existing Fcb for a paging file we need to force
+ // all of the Scb's to be torn down. The easiest way to do this
+ // is to flush and purge all of the Scb's (saving any attribute list
+ // for last) and then raise LOG_FILE_FULL to allow this request to
+ // be posted.
+ //
+
+ } else {
+
+ //
+ // Reference the Fcb so it won't go away on any flushes.
+ //
+
+ InterlockedIncrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = TRUE;
+
+ //
+ // Flush and purge this Fcb.
+ //
+
+ NtfsFlushAndPurgeFcb( IrpContext, ThisFcb );
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = FALSE;
+
+ //
+ // Force this request to be posted and then raise
+ // CANT_WAIT. The Fcb should be torn down in the finally
+ // clause below.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+
+ //
+ // We perform a check to see whether we will allow the system
+ // files to be opened.
+ //
+
+ if (NtfsProtectSystemFiles) {
+
+ //
+ // We only allow user opens on the Volume Dasd file and the
+ // root directory.
+ //
+
+ if (NtfsSegmentNumber( &ThisFcb->FileReference ) < FIRST_USER_FILE_NUMBER
+ && NtfsSegmentNumber( &ThisFcb->FileReference ) != VOLUME_DASD_NUMBER
+ && NtfsSegmentNumber( &ThisFcb->FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
+
+ Status = STATUS_ACCESS_DENIED;
+ DebugTrace( 0, Dbg, ("Attempting to open system files\n") );
+
+ try_return( NOTHING );
+ }
+ }
+
+ //
+ // If the Fcb Info field needs to be initialized, we do so now.
+ // We read this information from the disk as the duplicate information
+ // in the index entry is not guaranteed to be correct.
+ //
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ ThisFcb,
+ ParentScb->Fcb,
+ &ScbSizes );
+
+ HaveScbSizes = TRUE;
+
+ NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
+ }
+
+ //
+ // We have the actual data from the disk stored in the duplicate
+ // information in the Fcb. We compare this with the duplicate
+ // information in the DUPLICATE_INFORMATION structure in the
+ // filename attribute. If they don't match, we remember that
+ // we need to update the duplicate information.
+ //
+
+ if (!RtlEqualMemory( &ThisFcb->Info,
+ &IndexFileName->Info,
+ sizeof( DUPLICATED_INFORMATION ))) {
+
+ UpdateFcbInfo = TRUE;
+
+ //
+ // We expect this to be very rare but let's find the ones being changed.
+ //
+
+ if (ThisFcb->Info.CreationTime != IndexFileName->Info.CreationTime) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
+ }
+
+ if (ThisFcb->Info.LastModificationTime != IndexFileName->Info.LastModificationTime) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_MOD );
+ }
+
+ if (ThisFcb->Info.LastChangeTime != IndexFileName->Info.LastChangeTime) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ }
+
+ if (ThisFcb->Info.LastAccessTime != IndexFileName->Info.LastAccessTime) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+ }
+
+ if (ThisFcb->Info.AllocatedLength != IndexFileName->Info.AllocatedLength) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ if (ThisFcb->Info.FileSize != IndexFileName->Info.FileSize) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
+ }
+
+ if (ThisFcb->Info.FileAttributes != IndexFileName->Info.FileAttributes) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
+ }
+
+ if (ThisFcb->Info.PackedEaSize != IndexFileName->Info.PackedEaSize) {
+
+ SetFlag( ThisFcb->InfoFlags, FCB_INFO_CHANGED_EA_SIZE );
+ }
+ }
+
+ //
+ // Now get the link for this traversal.
+ //
+
+ ThisLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ ThisFcb,
+ FinalName,
+ IndexFileName->Flags,
+ NULL );
+
+ //
+ // We now know the Fcb is linked into the tree.
+ //
+
+ LocalFcbForTeardown = NULL;
+
+ *LcbForTeardown = ThisLcb;
+ *CurrentFcb = ThisFcb;
+
+ //
+ // If the link has been deleted, we cut off the open.
+ //
+
+ if (LcbLinkIsDeleted( ThisLcb )) {
+
+ try_return( Status = STATUS_DELETE_PENDING );
+ }
+
+ //
+ // We now call the worker routine to open an attribute on an existing file.
+ //
+
+ Status = NtfsOpenAttributeInExistingFile( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ (OpenById
+ ? 0
+ : FullPathName.Length - FinalName.Length),
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ OpenById,
+ NetworkInfo,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // Check to see if we should insert any prefix table entries
+ // and update the last access time.
+ //
+
+ if (NT_SUCCESS( Status ) && (Status != STATUS_PENDING)) {
+
+ PSCB Scb = *ThisScb;
+
+ //
+ // Now we insert the Lcb for this Fcb.
+ //
+
+ NtfsInsertPrefix( ThisLcb,
+ IgnoreCase );
+
+ //
+ // If this is a directory open and the normalized name is not in
+ // the Scb then do so now.
+ //
+
+ if ((SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_INDEX) &&
+ ((*ThisScb)->ScbType.Index.NormalizedName.Buffer == NULL) &&
+ (ParentScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ ParentScb,
+ *ThisScb,
+ IndexFileName,
+ FALSE );
+ }
+
+ //
+ // Perform the last bit of work. If this a user file open, we need
+ // to check if we initialize the Scb.
+ //
+
+ if (!IndexedAttribute) {
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ //
+ // We may have the sizes from our Fcb update call.
+ //
+
+ if (HaveScbSizes &&
+ (AttrTypeCode == $DATA) &&
+ (AttrName.Length == 0) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB )) {
+
+ NtfsUpdateScbFromMemory( IrpContext, Scb, &ScbSizes );
+
+ } else {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+ }
+
+ if (IrpSp->FileObject->WriteAccess) {
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+ }
+
+ //
+ // Let's check if we need to set the cache bit.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NO_INTERMEDIATE_BUFFERING )) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
+ }
+ }
+
+ //
+ // If this is the paging file, we want to be sure the allocation
+ // is loaded.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )
+ && (Scb->Header.AllocationSize.QuadPart != 0)
+ && !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ LCN Lcn;
+ VCN Vcn;
+ VCN AllocatedVcns;
+
+ AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
+
+ NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
+
+ //
+ // Now make sure the allocation is correctly loaded. The last
+ // Vcn should correspond to the allocation size for the file.
+ //
+
+ if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
+ &Vcn,
+ &Lcn ) ||
+ (Vcn + 1) != AllocatedVcns) {
+
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ ThisFcb );
+ }
+ }
+
+ //
+ // If this open is for an executable image we update the last
+ // access time.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess, FILE_EXECUTE ) &&
+ (Scb->AttributeTypeCode == $DATA)) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
+ }
+
+ //
+ // Let's update the quick index information in the Lcb.
+ //
+
+ RtlCopyMemory( &ThisLcb->QuickIndex,
+ QuickIndex,
+ sizeof( QUICK_INDEX ));
+
+ //
+ // If this operation was a supersede/overwrite or we created a new
+ // attribute stream then we want to perform the file record and
+ // directory update now. Otherwise we will defer the updates until
+ // the user closes his handle.
+ //
+
+ if (UpdateFcbInfo ||
+ (Irp->IoStatus.Information == FILE_CREATED) ||
+ (Irp->IoStatus.Information == FILE_SUPERSEDED) ||
+ (Irp->IoStatus.Information == FILE_OVERWRITTEN)) {
+
+ NtfsUpdateScbFromFileObject( IrpContext, IrpSp->FileObject, *ThisScb, TRUE );
+
+ //
+ // Do the standard information, file sizes and then duplicate information
+ // if needed.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, ThisFcb );
+ }
+
+ if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
+
+ NtfsWriteFileSizes( IrpContext,
+ *ThisScb,
+ &(*ThisScb)->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+ }
+
+ if (FlagOn( ThisFcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
+
+ ULONG FilterMatch;
+
+ NtfsUpdateDuplicateInfo( IrpContext, ThisFcb, *LcbForTeardown, ParentScb );
+
+ if (Vcb->NotifyCount != 0) {
+
+ //
+ // We map the Fcb info flags into the dir notify flags.
+ //
+
+ FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
+ ThisFcb->InfoFlags | ThisLcb->InfoFlags );
+
+ //
+ // If the filter match is non-zero, that means we also need to do a
+ // dir notify call.
+ //
+
+ if ((FilterMatch != 0) && (*ThisCcb != NULL)) {
+
+ NtfsReportDirNotify( IrpContext,
+ ThisFcb->Vcb,
+ &(*ThisCcb)->FullFileName,
+ (*ThisCcb)->LastFileNameOffset,
+ NULL,
+ ((FlagOn( (*ThisCcb)->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ (*ThisCcb)->Lcb != NULL &&
+ (*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &(*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ FILE_ACTION_MODIFIED,
+ ParentScb->Fcb );
+ }
+ }
+
+ NtfsUpdateLcbDuplicateInfo( ThisFcb, *LcbForTeardown );
+ ThisFcb->InfoFlags = 0;
+ }
+
+ ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ NtfsAcquireFsrtlHeader( *ThisScb );
+ ClearFlag( (*ThisScb)->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ NtfsReleaseFsrtlHeader( *ThisScb );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsOpenFile );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ //
+ // If this operation was not totally successful we need to
+ // back out the following changes.
+ //
+ // Modifications to the Info fields in the Fcb.
+ // Any changes to the allocation of the Scb.
+ // Any changes in the open counts in the various structures.
+ // Changes to the share access values in the Fcb.
+ //
+
+ if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
+
+ NtfsBackoutFailedOpens( IrpContext,
+ IrpSp->FileObject,
+ ThisFcb,
+ *ThisScb,
+ *ThisCcb );
+ }
+
+ if (DecrementCloseCount) {
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ }
+
+ //
+ // If we are to cleanup the Fcb we, look to see if we created it.
+ // If we did we can call our teardown routine. Otherwise we
+ // leave it alone.
+ //
+
+ if ((LocalFcbForTeardown != NULL) &&
+ (Status != STATUS_PENDING)) {
+
+ NtfsTeardownStructures( IrpContext,
+ ThisFcb,
+ NULL,
+ (BOOLEAN) (IrpContext->TransactionId != 0),
+ FALSE,
+ NULL );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenFile: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsCreateNewFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PSCB ParentScb,
+ IN PFILE_NAME FileNameAttr,
+ IN UNICODE_STRING FullPathName,
+ IN UNICODE_STRING FinalName,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN OpenById,
+ IN BOOLEAN DosOnlyComponent,
+ IN BOOLEAN TrailingBackslash,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when we need to open an attribute on a file
+ which does not exist yet. We have the ParentScb and the name to use
+ for this create. We will attempt to create the file and necessary
+ attributes. This will cause us to create an Fcb and the link between
+ it and its parent Scb. We will add this link to the prefix table as
+ well as the link for its parent Scb if specified.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ ParentScb - This is the Scb for the parent directory.
+
+ FileNameAttr - This is the file name attribute we used to perform the
+ search. The file name is correct but the other fields need to
+ be initialized.
+
+ FullPathName - This is the string containing the full path name of
+ this Fcb.
+
+ FinalName - This is the string for the final component only.
+
+ AttrName - This is the name of the attribute to open.
+
+ AttriCodeName - This is the name of the attribute code to open.
+
+ IgnoreCase - Indicates how we looked up the name.
+
+ OpenById - Indicates if we are opening this file relative to a file opened by Id.
+
+ DosOnlyComponent - Indicates if there is a DOS-ONLY component in an ancestor
+ of this open.
+
+ TrailingBackslash - Indicates if caller had a terminating backslash on the
+ name.
+
+ CurrentFcb - This is the address to store the Fcb if we successfully find
+ one in the Fcb/Scb tree.
+
+ LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
+ into the tree.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+ Tunnel - This is the property tunnel to search for restoration
+
+Return Value:
+
+ NTSTATUS - Indicates the result of this create file operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PVCB Vcb;
+
+ ULONG CcbFlags = 0;
+ BOOLEAN IndexedAttribute;
+ ATTRIBUTE_TYPE_CODE AttrTypeCode;
+
+ BOOLEAN CleanupAttrContext = FALSE;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PBCB FileRecordBcb = NULL;
+ LONGLONG FileRecordOffset;
+ FILE_REFERENCE ThisFileReference;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ PSCB Scb;
+ PLCB ThisLcb = NULL;
+ PFCB ThisFcb = NULL;
+ BOOLEAN AcquiredFcbTable = FALSE;
+ BOOLEAN RemovedFcb = FALSE;
+ BOOLEAN DecrementCloseCount = FALSE;
+ BOOLEAN QuotaIndexAcquired = FALSE;
+ BOOLEAN SecurityStreamAcquired = FALSE;
+
+ PACCESS_STATE AccessState;
+ BOOLEAN ReturnedExistingFcb;
+
+ BOOLEAN LoggedFileRecord = FALSE;
+
+ BOOLEAN HaveTunneledInformation = FALSE;
+ NAME_PAIR NamePair;
+ LONGLONG TunneledCreationTime;
+ ULONG TunneledDataSize;
+
+ VCN Cluster;
+ LCN Lcn;
+ VCN Vcn;
+
+ UCHAR FileNameFlags;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateNewFile: Entered\n") );
+
+ NtfsInitializeNamePair(&NamePair);
+
+ if (DosOnlyComponent) {
+
+ SetFlag( CcbFlags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT );
+ }
+
+ //
+ // We will do all the checks to see if this open can fail.
+ // This includes checking the specified attribute names, checking
+ // the security access and checking the create disposition.
+ //
+
+ {
+ ULONG CreateDisposition;
+
+ CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
+
+ if ((CreateDisposition == FILE_OPEN) ||
+ (CreateDisposition == FILE_OVERWRITE)) {
+
+ Status = STATUS_OBJECT_NAME_NOT_FOUND;
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
+ return Status;
+
+ } else if (FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_DIRECTORY_FILE ) &&
+ (CreateDisposition == FILE_OVERWRITE_IF)) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+ }
+
+ Vcb = ParentScb->Vcb;
+
+ Status = NtfsCheckValidAttributeAccess( IrpSp,
+ Vcb,
+ NULL,
+ AttrName,
+ AttrCodeName,
+ TrailingBackslash,
+ &AttrTypeCode,
+ &CcbFlags,
+ &IndexedAttribute );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+ //
+ // Fail this request if this is an indexed attribute and the TEMPORARY
+ // bit is set.
+ //
+
+ if (IndexedAttribute &&
+ FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // We won't allow someone to create a read-only file with DELETE_ON_CLOSE.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_READONLY ) &&
+ FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", STATUS_CANNOT_DELETE) );
+ return STATUS_CANNOT_DELETE;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Now perform the security checks. The first is to check if we
+ // may create a file in the parent. The second checks if the user
+ // desires ACCESS_SYSTEM_SECURITY and has the required privilege.
+ //
+
+ AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
+ if (!(AccessState->Flags & TOKEN_HAS_RESTORE_PRIVILEGE)) {
+
+ NtfsCreateCheck( IrpContext, ParentScb->Fcb, Irp );
+ }
+
+ //
+ // Check if the remaining privilege includes ACCESS_SYSTEM_SECURITY.
+ //
+
+ if (FlagOn( AccessState->RemainingDesiredAccess, ACCESS_SYSTEM_SECURITY )) {
+
+ if (!SeSinglePrivilegeCheck( NtfsSecurityPrivilege,
+ UserMode )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_PRIVILEGE_NOT_HELD, NULL, NULL );
+ }
+
+ //
+ // Move this privilege from the Remaining access to Granted access.
+ //
+
+ ClearFlag( AccessState->RemainingDesiredAccess, ACCESS_SYSTEM_SECURITY );
+ SetFlag( AccessState->PreviouslyGrantedAccess, ACCESS_SYSTEM_SECURITY );
+ }
+
+ //
+ // We want to allow this user maximum access to this file. We will
+ // use his desired access and check if he specified MAXIMUM_ALLOWED.
+ //
+
+ SetFlag( AccessState->PreviouslyGrantedAccess,
+ AccessState->RemainingDesiredAccess );
+
+ if (FlagOn( AccessState->PreviouslyGrantedAccess, MAXIMUM_ALLOWED )) {
+
+ SetFlag( AccessState->PreviouslyGrantedAccess, FILE_ALL_ACCESS );
+ ClearFlag( AccessState->PreviouslyGrantedAccess, MAXIMUM_ALLOWED );
+ }
+
+ AccessState->RemainingDesiredAccess = 0;
+
+#ifdef _CAIRO_
+
+ //
+ // The security stream and quota index must be acquired before
+ // the mft scb is acquired.
+ //
+
+ NtfsAcquireSecurityStream( IrpContext, Vcb, &SecurityStreamAcquired );
+
+ if (FlagOn(Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED)) {
+
+ ASSERT(!NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsExclusiveScb( Vcb->QuotaTableScb ));
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
+ QuotaIndexAcquired = TRUE;
+ }
+
+#endif // _CAIRO_
+
+ //
+ // We will now try to do all of the on-disk operations. This means first
+ // allocating and initializing an Mft record. After that we create
+ // an Fcb to use to access this record.
+ //
+
+ ThisFileReference = NtfsAllocateMftRecord( IrpContext,
+ Vcb,
+ FALSE );
+
+ //
+ // Pin the file record we need.
+ //
+
+ NtfsPinMftRecord( IrpContext,
+ Vcb,
+ &ThisFileReference,
+ TRUE,
+ &FileRecordBcb,
+ &FileRecord,
+ &FileRecordOffset );
+
+ //
+ // Initialize the file record header.
+ //
+
+ NtfsInitializeMftRecord( IrpContext,
+ Vcb,
+ &ThisFileReference,
+ FileRecord,
+ FileRecordBcb,
+ IndexedAttribute );
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ ThisFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ ThisFileReference,
+ BooleanFlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ),
+ IndexedAttribute,
+ &ReturnedExistingFcb );
+
+ NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, FALSE );
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // Reference the Fcb so it won't go away.
+ //
+
+ InterlockedIncrement( &ThisFcb->CloseCount );
+ DecrementCloseCount = TRUE;
+
+ //
+ // The first thing to create is the Ea's for the file. This will
+ // update the Ea length field in the Fcb.
+ // We test here that the opener is opening the entire file and
+ // is not Ea blind.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE )
+ || !FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+ }
+
+ //
+ // We're about to start creating structures on the disk, for which we'll
+ // possibly need to have tunneling infomation. Get it, non-POSIX only.
+ //
+
+ if (!IndexedAttribute && IgnoreCase) {
+
+ TunneledDataSize = sizeof(LONGLONG);
+
+ if (FsRtlFindInTunnelCache( &Vcb->Tunnel,
+ *(PULONGLONG)&ParentScb->Fcb->FileReference,
+ &FinalName,
+ &NamePair.Short,
+ &NamePair.Long,
+ &TunneledDataSize,
+ &TunneledCreationTime)) {
+
+ ASSERT(TunneledDataSize == sizeof(LONGLONG));
+
+ HaveTunneledInformation = TRUE;
+ }
+ }
+
+#ifdef _CAIRO_
+
+ SetFlag( ThisFcb->FcbState, FCB_STATE_LARGE_STD_INFO );
+
+ //
+ // BUGBUG - remove this test when all volumes are CAIRO
+ //
+
+ if (Vcb->SecurityDescriptorStream != NULL)
+ {
+
+ //
+ // We assign the security for this object in order to generate a SecurityId
+ // that will be stored in the standard info.
+ //
+
+ NtfsAssignSecurity( IrpContext,
+ ParentScb->Fcb,
+ Irp,
+ ThisFcb,
+ NULL, // BUGBUG delete
+ NULL, // BUGBUG delete
+ 0i64, // BUGBUG delete
+ &LoggedFileRecord );// BUGBUG delete
+ }
+
+ //
+ // If quota tracking is enabled then the quota index will have
+ // been acquired and a owner id should be assigned to the the
+ // new file.
+ //
+
+ if (QuotaIndexAcquired) {
+
+ PSID Sid;
+ BOOLEAN OwnerDefaulted;
+
+ ASSERT(ThisFcb->SharedSecurity != NULL);
+
+ //
+ // Extract the security id from the security descriptor.
+ //
+
+ Status = RtlGetOwnerSecurityDescriptor(
+ ThisFcb->SharedSecurity->SecurityDescriptor,
+ &Sid,
+ &OwnerDefaulted );
+
+ if (!NT_SUCCESS(Status)) {
+ leave;
+ }
+
+ //
+ // Generate a owner id for the Fcb.
+ //
+
+ ThisFcb->OwnerId = NtfsGetOwnerId( IrpContext,
+ Sid,
+ NULL );
+
+ NtfsInitializeQuotaControlBlock( ThisFcb );
+ }
+#endif // _CAIRO_
+
+ //
+ // The changes to make on disk are first to create a standard information
+ // attribute. We start by filling the Fcb with the information we
+ // know and creating the attribute on disk.
+ //
+
+ NtfsInitializeFcbAndStdInfo( IrpContext,
+ ThisFcb,
+ IndexedAttribute,
+ (BOOLEAN) (!FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION ) &&
+ !FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE ) &&
+ FlagOn( ParentScb->ScbState, SCB_STATE_COMPRESSED )),
+ IrpSp->Parameters.Create.FileAttributes,
+ (HaveTunneledInformation? &TunneledCreationTime : NULL) );
+
+ //
+ // Next we create the Index for a directory or the unnamed data for
+ // a file if they are not explicitly being opened.
+ //
+
+ if (!IndexedAttribute) {
+
+ if (!FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ $DATA,
+ NULL,
+ NULL,
+ 0,
+ (USHORT) ((!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ !FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) ?
+ (ParentScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) :
+ 0),
+ NULL,
+ FALSE,
+ &AttrContext );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ CleanupAttrContext = FALSE;
+
+ ThisFcb->Info.AllocatedLength = 0;
+ ThisFcb->Info.FileSize = 0;
+
+ }
+
+ } else {
+
+ NtfsCreateIndex( IrpContext,
+ ThisFcb,
+ $FILE_NAME,
+ COLLATION_FILE_NAME,
+ Vcb->DefaultBytesPerIndexAllocationBuffer,
+ (UCHAR)Vcb->DefaultBlocksPerIndexAllocationBuffer,
+ NULL,
+ (USHORT) (!FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NO_COMPRESSION ) ?
+ (ParentScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) :
+ 0),
+ TRUE,
+ FALSE );
+ }
+
+ //
+ // Now we create the Lcb, this means that this Fcb is in the graph.
+ //
+
+ ThisLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ ThisFcb,
+ FinalName,
+ 0,
+ NULL );
+
+ //
+ // Finally we create and open the desired attribute for the user.
+ //
+
+ if (AttrTypeCode == $INDEX_ALLOCATION) {
+
+ Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ Vcb,
+ ThisLcb,
+ ThisFcb,
+ (OpenById
+ ? 0
+ : FullPathName.Length - FinalName.Length),
+ NtfsFileNameIndex,
+ $INDEX_ALLOCATION,
+ SetShareAccess,
+ UserDirectoryOpen,
+ (OpenById
+ ? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
+ : CcbFlags),
+ NULL,
+ ThisScb,
+ ThisCcb );
+
+ } else {
+
+ Status = NtfsOpenNewAttr( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ (OpenById
+ ? 0
+ : FullPathName.Length - FinalName.Length),
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ FALSE,
+ OpenById,
+ ThisScb,
+ ThisCcb );
+ }
+
+ //
+ // If we are successful, we add the parent Lcb to the prefix table if
+ // desired. We will always add our link to the prefix queue.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ Scb = *ThisScb;
+
+ //
+ // Initialize the Scb if we need to do so.
+ //
+
+ if (!IndexedAttribute) {
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ if (!FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NO_INTERMEDIATE_BUFFERING )) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_CACHE_SUPPORTED );
+ }
+
+ //
+ // If this is the unnamed data attribute, we store the sizes
+ // in the Fcb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ ThisFcb->Info.AllocatedLength = Scb->TotalAllocated;
+ ThisFcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+ }
+ }
+
+ //
+ // Next add this entry to parent. It is possible that this is a link,
+ // an Ntfs name, a DOS name or Ntfs/Dos name. We use the filename
+ // attribute structure from earlier, but need to add more information.
+ //
+
+ NtfsAddLink( IrpContext,
+ (BOOLEAN) !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE ),
+ ParentScb,
+ ThisFcb,
+ FileNameAttr,
+ &LoggedFileRecord,
+ &FileNameFlags,
+ &ThisLcb->QuickIndex,
+ (HaveTunneledInformation? &NamePair : NULL) );
+
+ //
+ // We created the Lcb without knowing the correct value for the
+ // flags. We update it now.
+ //
+
+ ThisLcb->FileNameAttr->Flags = FileNameFlags;
+ FileNameAttr->Flags = FileNameFlags;
+
+ //
+ // We also have to fix up the ExactCaseLink of the Lcb since we may have had
+ // a short name create turned into a tunneled long name create, meaning that
+ // it should be full uppercase. And the filename in the IRP.
+ //
+
+ if (FileNameFlags == FILE_NAME_DOS) {
+
+ RtlUpcaseUnicodeString(&ThisLcb->ExactCaseLink.LinkName, &ThisLcb->ExactCaseLink.LinkName, FALSE);
+ RtlUpcaseUnicodeString(&IrpSp->FileObject->FileName, &IrpSp->FileObject->FileName, FALSE);
+ }
+
+ //
+ // If this is a directory open and the normalized name is not in
+ // the Scb then do so now.
+ //
+
+ if ((SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_INDEX) &&
+ ((*ThisScb)->ScbType.Index.NormalizedName.Buffer == NULL) &&
+ (ParentScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ ParentScb,
+ *ThisScb,
+ FileNameAttr,
+ FALSE );
+ }
+
+ //
+ // Clear the flags in the Fcb that indicate we need to update on
+ // disk structures. Also clear any file object and Ccb flags
+ // which also indicate we may need to do an update.
+ //
+
+ ThisFcb->InfoFlags = 0;
+ ClearFlag( ThisFcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ ClearFlag( IrpSp->FileObject->Flags,
+ FO_FILE_MODIFIED | FO_FILE_FAST_IO_READ | FO_FILE_SIZE_CHANGED );
+
+ ClearFlag( (*ThisCcb)->Flags,
+ (CCB_FLAG_UPDATE_LAST_MODIFY |
+ CCB_FLAG_UPDATE_LAST_CHANGE |
+ CCB_FLAG_SET_ARCHIVE) );
+
+ //
+ // BUGBUG begin section to delete when all volumes are CAIRO
+ //
+
+ #ifdef _CAIRO_
+ if (Vcb->SecurityDescriptorStream == NULL)
+ {
+ #endif
+ //
+ // Next we will assign security to this new file.
+ //
+
+ NtfsAssignSecurity( IrpContext,
+ ParentScb->Fcb,
+ Irp,
+ ThisFcb,
+ FileRecord,
+ FileRecordBcb,
+ FileRecordOffset,
+ &LoggedFileRecord );
+ #ifdef _CAIRO_
+ }
+ #endif // _CAIRO_
+
+ //
+ // BUGBUG end section to delete when all volumes are CAIRO
+ //
+
+ //
+ // Log the file record.
+ //
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ FileRecordBcb,
+ InitializeFileRecordSegment,
+ FileRecord,
+ FileRecord->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ FileRecordOffset,
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+#if 0 // _CAIRO_
+ ASSERT(!NtfsPerformQuotaOperation(ThisFcb) || NtfsCalculateQuotaAdjustment( IrpContext, ThisFcb) == 0);
+#endif
+
+ //
+ // Now add the eas for the file. We need to add them now because
+ // they are logged and we have to make sure we don't modify the
+ // attribute record after adding them.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ NtfsAddEa( IrpContext,
+ Vcb,
+ ThisFcb,
+ (PFILE_FULL_EA_INFORMATION) Irp->AssociatedIrp.SystemBuffer,
+ IrpSp->Parameters.Create.EaLength,
+ &Irp->IoStatus );
+ }
+
+ //
+ // Change the last modification time and last change time for the
+ // parent.
+ //
+
+ NtfsUpdateFcb( ParentScb->Fcb );
+
+ //
+ // If this is the paging file, we want to be sure the allocation
+ // is loaded.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ Cluster = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
+
+ NtfsPreloadAllocation( IrpContext, Scb, 0, Cluster );
+
+ //
+ // Now make sure the allocation is correctly loaded. The last
+ // Vcn should correspond to the allocation size for the file.
+ //
+
+ if (!NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
+ &Vcn,
+ &Lcn ) ||
+ (Vcn + 1) != Cluster) {
+
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ ThisFcb );
+ }
+ }
+
+ //
+ // We report to our parent that we created a new file.
+ //
+
+ if (!OpenById && (Vcb->NotifyCount != 0)) {
+
+ NtfsReportDirNotify( IrpContext,
+ ThisFcb->Vcb,
+ &(*ThisCcb)->FullFileName,
+ (*ThisCcb)->LastFileNameOffset,
+ NULL,
+ ((FlagOn( (*ThisCcb)->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ (*ThisCcb)->Lcb != NULL &&
+ (*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &(*ThisCcb)->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ (IndexedAttribute
+ ? FILE_NOTIFY_CHANGE_DIR_NAME
+ : FILE_NOTIFY_CHANGE_FILE_NAME),
+ FILE_ACTION_ADDED,
+ ParentScb->Fcb );
+ }
+
+ ThisFcb->InfoFlags = 0;
+
+ //
+ // Now we insert the Lcb for this Fcb.
+ //
+
+ NtfsInsertPrefix( ThisLcb,
+ IgnoreCase );
+
+ Irp->IoStatus.Information = FILE_CREATED;
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsCreateNewFile );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ NtfsUnpinBcb( &FileRecordBcb );
+
+ NtfsReleaseQuotaIndex( IrpContext, Vcb, QuotaIndexAcquired );
+ NtfsReleaseSecurityStream( IrpContext, Vcb, SecurityStreamAcquired );
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ if (DecrementCloseCount) {
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ }
+
+ if (NamePair.Long.Buffer != NamePair.LongBuffer) {
+
+ NtfsFreePool(NamePair.Long.Buffer);
+ }
+
+ //
+ // We need to cleanup any changes to the in memory
+ // structures if there is an error.
+ //
+
+ if (!NT_SUCCESS( Status ) || AbnormalTermination()) {
+
+ NtfsBackoutFailedOpens( IrpContext,
+ IrpSp->FileObject,
+ ThisFcb,
+ *ThisScb,
+ *ThisCcb );
+
+ //
+ // Always force the Fcb to reinitialized.
+ //
+
+ if (ThisFcb != NULL) {
+
+ PSCB Scb;
+ PLIST_ENTRY Links;
+
+ ClearFlag( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED );
+
+ //
+ // Mark the Fcb and all Scb's as deleted to force all subsequent
+ // operations to fail.
+ //
+
+ SetFlag( ThisFcb->FcbState, FCB_STATE_FILE_DELETED );
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = ThisFcb->ScbQueue.Flink;
+ Links != &ThisFcb->ScbQueue;
+ Links = Links->Flink) {
+
+ Scb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ Scb->ValidDataToDisk =
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+
+ //
+ // Clear the Scb field so our caller doesn't try to teardown
+ // from this point.
+ //
+
+ *ThisScb = NULL;
+
+ //
+ // If we created an Fcb then we want to check if we need to
+ // unwind any structure allocation. We don't want to remove any
+ // structures needed for the coming AbortTransaction. This
+ // includes the parent Scb as well as the current Fcb if we
+ // logged the ACL creation.
+ //
+
+ //
+ // Make sure the parent Fcb doesn't go away. Then
+ // start a teardown from the Fcb we just found.
+ //
+
+ InterlockedIncrement( &ParentScb->CleanupCount );
+
+ NtfsTeardownStructures( IrpContext,
+ ThisFcb,
+ NULL,
+ LoggedFileRecord,
+ FALSE,
+ &RemovedFcb );
+
+ //
+ // If the Fcb was removed then both the Fcb and Lcb are gone.
+ //
+
+ if (RemovedFcb) {
+
+ ThisFcb = NULL;
+ ThisLcb = NULL;
+ }
+
+ InterlockedDecrement( &ParentScb->CleanupCount );
+ }
+ }
+
+ //
+ // If the new Fcb is still present then either return it as the
+ // deepest Fcb encountered in this open or release it.
+ //
+
+ if (ThisFcb != NULL) {
+
+ //
+ // If the Lcb is present then this is part of the tree. Our
+ // caller knows to release it.
+ //
+
+ if (ThisLcb != NULL) {
+
+ *LcbForTeardown = ThisLcb;
+ *CurrentFcb = ThisFcb;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateNewFile: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+PLCB
+NtfsOpenSubdirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN UNICODE_STRING Name,
+ IN BOOLEAN TraverseAccessCheck,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ IN PINDEX_ENTRY IndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will create an Fcb for an intermediate node on an open path.
+ We use the ParentScb and the information in the FileName attribute returned
+ from the disk to create the Fcb and create a link between the Scb and Fcb.
+ It's possible that the Fcb and Lcb already exist but the 'CreateXcb' calls
+ handle that already. This routine does not expect to fail.
+
+Arguments:
+
+ ParentScb - This is the Scb for the parent directory.
+
+ Name - This is the name for the entry.
+
+ TraverseAccessCheck - Indicates if this open is using traverse access checking.
+
+ CurrentFcb - This is the address to store the Fcb if we successfully find
+ one in the Fcb/Scb tree.
+
+ LcbForTeardown - This is the Lcb to use in teardown if we add an Lcb
+ into the tree.
+
+ IndexEntry - This is the entry found in searching the parent directory.
+
+Return Value:
+
+ PLCB - Pointer to the Link control block between the Fcb and its parent.
+
+--*/
+
+{
+ PFCB ThisFcb;
+ PLCB ThisLcb;
+ PFCB LocalFcbForTeardown = NULL;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+ BOOLEAN ExistingFcb;
+
+ PVCB Vcb = ParentScb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenSubdirectory: Entered\n") );
+ DebugTrace( 0, Dbg, ("ParentScb -> %08lx\n") );
+ DebugTrace( 0, Dbg, ("IndexEntry -> %08lx\n") );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ //
+ // The steps here are very simple create the Fcb, remembering if it
+ // already existed. We don't update the information in the Fcb as
+ // we can't rely on the information in the duplicated information.
+ // A subsequent open of this Fcb will need to perform that work.
+ //
+
+ ThisFcb = NtfsCreateFcb( IrpContext,
+ ParentScb->Vcb,
+ IndexEntry->FileReference,
+ FALSE,
+ TRUE,
+ &ExistingFcb );
+
+ ThisFcb->ReferenceCount += 1;
+
+ //
+ // If we created this Fcb we must make sure to start teardown
+ // on it.
+ //
+
+ if (!ExistingFcb) {
+
+ LocalFcbForTeardown = ThisFcb;
+
+ } else {
+
+ *CurrentFcb = ThisFcb;
+ *LcbForTeardown = NULL;
+ }
+
+ //
+ // Try to do a fast acquire, otherwise we need to release
+ // the Fcb table, acquire the Fcb, acquire the Fcb table to
+ // dereference Fcb.
+ //
+
+ if (!NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, TRUE )) {
+
+ ParentScb->Fcb->ReferenceCount += 1;
+ InterlockedIncrement( &ParentScb->CleanupCount );
+
+ //
+ // Set the IrpContext to acquire paging io resources if our target
+ // has one. This will lock the MappedPageWriter out of this file.
+ //
+
+ if (ThisFcb->PagingIoResource != NULL) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
+ }
+
+ NtfsReleaseScbWithPaging( IrpContext, ParentScb );
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, FALSE );
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ InterlockedDecrement( &ParentScb->CleanupCount );
+ ParentScb->Fcb->ReferenceCount -= 1;
+ }
+
+ ThisFcb->ReferenceCount -= 1;
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // If this is a directory, it's possible that we hav an existing Fcb
+ // in the prefix table which needs to be initialized from the disk.
+ // We look in the InfoInitialized flag to know whether to go to
+ // disk.
+ //
+
+ ThisLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ ThisFcb,
+ Name,
+ ((PFILE_NAME) NtfsFoundIndexEntry( IndexEntry ))->Flags,
+ NULL );
+
+ LocalFcbForTeardown = NULL;
+
+ *LcbForTeardown = ThisLcb;
+ *CurrentFcb = ThisFcb;
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TraverseAccessCheck,
+ ThisFcb,
+ ParentScb->Fcb,
+ NULL );
+
+ NtfsConditionallyFixupQuota( IrpContext, ThisFcb );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsOpenSubdirectory );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ //
+ // If we are to cleanup the Fcb we, look to see if we created it.
+ // If we did we can call our teardown routine. Otherwise we
+ // leave it alone.
+ //
+
+ if (LocalFcbForTeardown != NULL) {
+
+ NtfsTeardownStructures( IrpContext,
+ ThisFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenSubdirectory: Lcb -> %08lx\n", ThisLcb) );
+ }
+
+ return ThisLcb;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsOpenAttributeInExistingFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the worker routine for opening an attribute on an
+ existing file. It will handle volume opens, indexed opens, opening
+ or overwriting existing attributes as well as creating new attributes.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the stack location for this open.
+
+ ThisLcb - This is the Lcb we used to reach this Fcb.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ LastFileNameOffset - This is the offset in the full path name of the
+ final component.
+
+ AttrName - This is the attribute name in case we need to create
+ an Scb.
+
+ AttrTypeCode - This is the attribute type code to use to create
+ the Scb.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+ OpenById - Indicates if this open is an open by Id.
+
+ NetworkInfo - If specified then this call is a fast open call to query
+ the network information. We don't update any of the in-memory structures
+ for this.
+
+ ThisScb - This is the address to store the Scb from this open.
+
+ ThisCcb - This is the address to store the Ccb from this open.
+
+Return Value:
+
+ NTSTATUS - The result of opening this indexed attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG CreateDisposition;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenAttributeInExistingFile: Entered\n") );
+
+ //
+ // If the caller is ea blind, let's check the need ea count on the
+ // file. We skip this check if he is accessing a named data stream.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE )
+ && FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ PEA_INFORMATION ThisEaInformation;
+ ATTRIBUTE_ENUMERATION_CONTEXT EaInfoAttrContext;
+
+ NtfsInitializeAttributeContext( &EaInfoAttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If we find the Ea information attribute we look in there for
+ // Need ea count.
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ $EA_INFORMATION,
+ &EaInfoAttrContext )) {
+
+ ThisEaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &EaInfoAttrContext ));
+
+ if (ThisEaInformation->NeedEaCount != 0) {
+
+ Status = STATUS_ACCESS_DENIED;
+ }
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &EaInfoAttrContext );
+ }
+
+ if (Status != STATUS_SUCCESS) {
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit\n") );
+
+ return Status;
+ }
+ }
+
+ CreateDisposition = (IrpSp->Parameters.Create.Options >> 24) & 0x000000ff;
+
+ //
+ // If the result is a directory operation, then we know the attribute
+ // must exist.
+ //
+
+ if (AttrTypeCode == $INDEX_ALLOCATION) {
+
+ //
+ // Check the create disposition.
+ //
+
+ if ((CreateDisposition != FILE_OPEN) && (CreateDisposition != FILE_OPEN_IF)) {
+
+ Status = (ThisLcb == ThisFcb->Vcb->RootLcb
+ ? STATUS_ACCESS_DENIED
+ : STATUS_OBJECT_NAME_COLLISION);
+
+ } else {
+
+ Status = NtfsOpenExistingAttr( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ NtfsFileNameIndex,
+ $INDEX_ALLOCATION,
+ CcbFlags,
+ OpenById,
+ TRUE,
+ NetworkInfo,
+ ThisScb,
+ ThisCcb );
+ }
+
+ } else {
+
+ BOOLEAN FoundAttribute;
+
+ //
+ // If it exists, we first check if the caller wanted to open that attribute.
+ //
+
+ if (AttrName.Length == 0
+ && AttrTypeCode == $DATA) {
+
+ FoundAttribute = TRUE;
+
+ //
+ // Otherwise we see if the attribute exists.
+ //
+
+ } else {
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ FoundAttribute = NtfsLookupAttributeByName( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ AttrTypeCode,
+ &AttrName,
+ NULL,
+ (BOOLEAN) !BooleanFlagOn( IrpSp->Flags, SL_CASE_SENSITIVE ),
+ &AttrContext );
+
+ if (FoundAttribute) {
+
+ //
+ // If there is an attribute name, we will copy the case of the name
+ // to the input attribute name.
+ //
+
+ PATTRIBUTE_RECORD_HEADER FoundAttribute;
+
+ FoundAttribute = NtfsFoundAttribute( &AttrContext );
+
+ RtlCopyMemory( AttrName.Buffer,
+ Add2Ptr( FoundAttribute, FoundAttribute->NameOffset ),
+ AttrName.Length );
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+ }
+
+ if (FoundAttribute) {
+
+ //
+ // In this case we call our routine to open this attribute.
+ //
+
+ if ((CreateDisposition == FILE_OPEN) ||
+ (CreateDisposition == FILE_OPEN_IF)) {
+
+ Status = NtfsOpenExistingAttr( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ OpenById,
+ FALSE,
+ NetworkInfo,
+ ThisScb,
+ ThisCcb );
+
+ if ((Status != STATUS_PENDING) &&
+ (*ThisScb != NULL)) {
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
+ }
+
+ //
+ // If he wanted to overwrite this attribute, we call our overwrite routine.
+ //
+
+ } else if ((CreateDisposition == FILE_SUPERSEDE) ||
+ (CreateDisposition == FILE_OVERWRITE) ||
+ (CreateDisposition == FILE_OVERWRITE_IF)) {
+
+ //
+ // Check if mm will allow us to modify this file.
+ //
+
+ Status = NtfsOverwriteAttr( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ (BOOLEAN) (CreateDisposition == FILE_SUPERSEDE),
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ OpenById,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // Remember that this Scb was modified.
+ //
+
+ if ((Status != STATUS_PENDING) &&
+ (*ThisScb != NULL)) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_MODIFIED );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
+ }
+
+ //
+ // Otherwise he is trying to create the attribute.
+ //
+
+ } else {
+
+ Status = STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ //
+ // The attribute doesn't exist. If the user expected it to exist, we fail.
+ // Otherwise we call our routine to create an attribute.
+ //
+
+ } else if ((CreateDisposition == FILE_OPEN) ||
+ (CreateDisposition == FILE_OVERWRITE)) {
+
+ Status = STATUS_OBJECT_NAME_NOT_FOUND;
+
+ } else {
+
+ //
+ // Perform the open check for this existing file.
+ //
+
+ Status = NtfsCheckExistingFile( IrpContext,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ CcbFlags );
+
+ //
+ // If this didn't fail then attempt to create the stream.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ Status = NtfsOpenNewAttr( IrpContext,
+ Irp,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ CcbFlags,
+ TRUE,
+ OpenById,
+ ThisScb,
+ ThisCcb );
+ }
+
+ if (*ThisScb != NULL) {
+
+ if (*ThisCcb != NULL) {
+
+ SetFlag( (*ThisCcb)->Flags,
+ CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
+ }
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CREATE_MOD_SCB );
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit\n") );
+
+ return Status;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsOpenExistingAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ IN BOOLEAN DirectoryOpen,
+ IN PVOID NetworkInfo OPTIONAL,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to open an existing attribute. We check the
+ requested file access, the existance of
+ an Ea buffer and the security on this file. If these succeed then
+ we check the batch oplocks and regular oplocks on the file.
+ If we have gotten this far, we simply call our routine to open the
+ attribute.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the Irp stack pointer for the filesystem.
+
+ ThisLcb - This is the Lcb used to reach this Fcb.
+
+ ThisFcb - This is the Fcb to open.
+
+ LastFileNameOffset - This is the offset in the full path name of the
+ final component.
+
+ AttrName - This is the attribute name in case we need to create
+ an Scb.
+
+ AttrTypeCode - This is the attribute type code to use to create
+ the Scb.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+ OpenById - Indicates if this open is by file Id.
+
+ DirectoryOpen - Indicates whether this open is a directory open or a data stream.
+
+ NetworkInfo - If specified then this call is a fast open call to query
+ the network information. We don't update any of the in-memory structures
+ for this.
+
+ ThisScb - This is the address to store the address of the Scb.
+
+ ThisCcb - This is the address to store the address of the Ccb.
+
+Return Value:
+
+ NTSTATUS - The result of opening this indexed attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ NTSTATUS OplockStatus;
+
+ SHARE_MODIFICATION_TYPE ShareModificationType;
+ TYPE_OF_OPEN TypeOfOpen;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenExistingAttr: Entered\n") );
+
+ //
+ // For data streams we need to do a check that includes an oplock check.
+ // For directories we just need to figure the share modification type.
+ //
+ // We also figure the type of open and the node type code based on the
+ // directory flag.
+ //
+
+ if (DirectoryOpen) {
+
+ //
+ // Check for valid access on an existing file.
+ //
+
+ Status = NtfsCheckExistingFile( IrpContext,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ CcbFlags );
+
+ ShareModificationType = (ThisFcb->CleanupCount == 0 ? SetShareAccess : CheckShareAccess);
+ TypeOfOpen = UserDirectoryOpen;
+
+ } else {
+
+ //
+ // Don't break the batch oplock if opening to query the network info.
+ //
+
+ if (!ARGUMENT_PRESENT( NetworkInfo )) {
+
+ Status = NtfsBreakBatchOplock( IrpContext,
+ Irp,
+ IrpSp,
+ ThisFcb,
+ AttrName,
+ AttrTypeCode,
+ ThisScb );
+
+ if (Status != STATUS_PENDING) {
+
+ if (NT_SUCCESS( Status = NtfsCheckExistingFile( IrpContext,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ CcbFlags ))) {
+
+ Status = NtfsOpenAttributeCheck( IrpContext,
+ Irp,
+ IrpSp,
+ ThisScb,
+ &ShareModificationType );
+
+#ifndef _CAIRO_
+ TypeOfOpen = UserFileOpen;
+#else // _CAIRO_
+ TypeOfOpen =
+ AttrTypeCode == $DATA ? UserFileOpen : UserPropertySetOpen;
+#endif // _CAIRO_
+
+ ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
+ }
+ }
+
+ //
+ // We want to perform the ACL check but not break any oplocks for the
+ // NetworkInformation query.
+ //
+
+ } else {
+
+ Status = NtfsCheckExistingFile( IrpContext,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ CcbFlags );
+
+#ifndef _CAIRO_
+ TypeOfOpen = UserFileOpen;
+#else // _CAIRO_
+ TypeOfOpen =
+ AttrTypeCode == $DATA ? UserFileOpen : UserPropertySetOpen;
+#endif // _CAIRO_
+
+ ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
+ }
+ }
+
+ //
+ // If we didn't post the Irp and the operation was successful, we
+ // proceed with the open.
+ //
+
+ if (NT_SUCCESS( Status )
+ && Status != STATUS_PENDING) {
+
+ //
+ // Now actually open the attribute.
+ //
+
+ OplockStatus = Status;
+
+ Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ ThisFcb->Vcb,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ ShareModificationType,
+ TypeOfOpen,
+ (OpenById
+ ? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
+ : CcbFlags),
+ NetworkInfo,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // If there are no errors at this point, we set the caller's Iosb.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ //
+ // We need to remember if the oplock break is in progress.
+ //
+
+ Status = OplockStatus;
+
+ Irp->IoStatus.Information = FILE_OPENED;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenExistingAttr: Exit -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsOverwriteAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN BOOLEAN Supersede,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN OpenById,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to overwrite an existing attribute. We do all of
+ the same work as opening an attribute except that we can change the
+ allocation of a file. This routine will handle the case where a
+ file is being overwritten and the case where just an attribute is
+ being overwritten. In the case of the former, we may change the
+ file attributes of the file as well as modify the Ea's on the file.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the stack location for this open.
+
+ ThisLcb - This is the Lcb we used to reach this Fcb.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ Supersede - This indicates whether this is a supersede or overwrite
+ operation.
+
+ LastFileNameOffset - This is the offset in the full path name of the
+ final component.
+
+ AttrName - This is the attribute name in case we need to create
+ an Scb.
+
+ AttrTypeCode - This is the attribute type code to use to create
+ the Scb.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+ OpenById - Indicates if this open is by file Id.
+
+ ThisScb - This is the address to store the address of the Scb.
+
+ ThisCcb - This is the address to store the address of the Ccb.
+
+Return Value:
+
+ NTSTATUS - The result of opening this indexed attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ NTSTATUS OplockStatus;
+
+ ULONG FileAttributes;
+ PACCESS_MASK DesiredAccess;
+ ACCESS_MASK AddedAccess = 0;
+ BOOLEAN MaximumRequested = FALSE;
+
+ SHARE_MODIFICATION_TYPE ShareModificationType;
+
+ PFILE_FULL_EA_INFORMATION FullEa = NULL;
+ ULONG FullEaLength = 0;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOverwriteAttr: Entered\n") );
+
+ DesiredAccess = &IrpSp->Parameters.Create.SecurityContext->DesiredAccess;
+
+ if (FlagOn( *DesiredAccess, MAXIMUM_ALLOWED )) {
+
+ MaximumRequested = TRUE;
+ }
+
+ //
+ // Check the oplock state of this file.
+ //
+
+ Status = NtfsBreakBatchOplock( IrpContext,
+ Irp,
+ IrpSp,
+ ThisFcb,
+ AttrName,
+ AttrTypeCode,
+ ThisScb );
+
+ if (Status == STATUS_PENDING) {
+
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // We first want to check that the caller's desired access and specified
+ // file attributes are compatible with the state of the file. There
+ // are the two overwrite cases to consider.
+ //
+ // OverwriteFile - The hidden and system bits passed in by the
+ // caller must match the current values.
+ //
+ // OverwriteAttribute - We also modify the requested desired access
+ // to explicitly add the implicit access needed by overwrite.
+ //
+ // We also check that for the overwrite attribute case, there isn't
+ // an Ea buffer specified.
+ //
+
+ if (FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ BOOLEAN Hidden;
+ BOOLEAN System;
+
+ //
+ // Get the file attributes and clear any unsupported bits.
+ //
+
+ FileAttributes = (ULONG) IrpSp->Parameters.Create.FileAttributes;
+
+ SetFlag( FileAttributes, FILE_ATTRIBUTE_ARCHIVE );
+ ClearFlag( FileAttributes,
+ ~(FILE_ATTRIBUTE_READONLY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_ARCHIVE) );
+
+ DebugTrace( 0, Dbg, ("Checking hidden/system for overwrite/supersede\n") );
+
+ Hidden = BooleanIsHidden( &ThisFcb->Info );
+ System = BooleanIsSystem( &ThisFcb->Info );
+
+ if ((Hidden && !FlagOn(FileAttributes, FILE_ATTRIBUTE_HIDDEN)
+ ||
+ System && !FlagOn(FileAttributes, FILE_ATTRIBUTE_SYSTEM))
+
+ &&
+
+ !FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
+
+ DebugTrace( 0, Dbg, ("The hidden and/or system bits do not match\n") );
+
+ Status = STATUS_ACCESS_DENIED;
+
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // If the user specified an Ea buffer and they are Ea blind, we deny
+ // access.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_EA_KNOWLEDGE )
+ && Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ DebugTrace( 0, Dbg, ("This opener cannot create Ea's\n") );
+
+ Status = STATUS_ACCESS_DENIED;
+
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ if (!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
+
+ SetFlag( AddedAccess,
+ (FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES) & ~(*DesiredAccess) );
+
+ SetFlag( *DesiredAccess, FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES );
+ }
+
+ } else if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ DebugTrace( 0, Dbg, ("Can't specifiy an Ea buffer on an attribute overwrite\n") );
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ if (!FlagOn( IrpSp->Flags, SL_OPEN_PAGING_FILE )) {
+
+ if (Supersede) {
+
+ SetFlag( AddedAccess,
+ DELETE & ~(*DesiredAccess) );
+
+ SetFlag( *DesiredAccess, DELETE );
+
+ } else {
+
+ SetFlag( AddedAccess,
+ FILE_WRITE_DATA & ~(*DesiredAccess) );
+
+ SetFlag( *DesiredAccess, FILE_WRITE_DATA );
+ }
+ }
+
+ //
+ // Check whether we can open this existing file.
+ //
+
+ Status = NtfsCheckExistingFile( IrpContext,
+ IrpSp,
+ ThisLcb,
+ ThisFcb,
+ CcbFlags );
+
+ //
+ // If we have a success status then proceed with the oplock check and
+ // open the attribute.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ Status = NtfsOpenAttributeCheck( IrpContext,
+ Irp,
+ IrpSp,
+ ThisScb,
+ &ShareModificationType );
+
+ //
+ // If we biased the desired access we need to remove the same
+ // bits from the granted access. If maximum allowed was
+ // requested then we can skip this.
+ //
+
+ if (!MaximumRequested) {
+
+ ClearFlag( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
+ AddedAccess );
+ }
+
+ //
+ // Also remove the bits from the desired access field so we won't
+ // see them if this request gets posted for any reason.
+ //
+
+ ClearFlag( *DesiredAccess, AddedAccess );
+
+ //
+ // If we didn't post the Irp and the operation was successful, we
+ // proceed with the open.
+ //
+
+ if (NT_SUCCESS( Status )
+ && Status != STATUS_PENDING) {
+
+ //
+ // Reference the Fcb so it doesn't go away.
+ //
+
+ InterlockedIncrement( &ThisFcb->CloseCount );
+
+ //
+ // Use a try-finally to restore the close count correctly.
+ //
+
+ try {
+
+ //
+ // If we can't truncate the file size then return now.
+ //
+
+ if (!MmCanFileBeTruncated( &(*ThisScb)->NonpagedScb->SegmentObject,
+ &Li0 )) {
+
+ Status = STATUS_USER_MAPPED_FILE;
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+
+ try_return( Status );
+ }
+
+ //
+ // Remember the status from the oplock check.
+ //
+
+ OplockStatus = Status;
+
+ //
+ // We perform the on-disk changes. For a file overwrite, this includes
+ // the Ea changes and modifying the file attributes. For an attribute,
+ // this refers to modifying the allocation size. We need to keep the
+ // Fcb updated and remember which values we changed.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ //
+ // Remember the values in the Irp.
+ //
+
+ FullEa = (PFILE_FULL_EA_INFORMATION) Irp->AssociatedIrp.SystemBuffer;
+ FullEaLength = IrpSp->Parameters.Create.EaLength;
+ }
+
+ //
+ // Now do the file attributes and either remove or mark for
+ // delete all of the other $DATA attributes on the file.
+ //
+
+ if (FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ //
+ // Replace the current Ea's on the file. This operation will update
+ // the Fcb for the file.
+ //
+
+ NtfsAddEa( IrpContext,
+ ThisFcb->Vcb,
+ ThisFcb,
+ FullEa,
+ FullEaLength,
+ &Irp->IoStatus );
+
+ //
+ // Copy the directory bit from the current Info structure.
+ //
+
+ if (IsDirectory( &ThisFcb->Info)) {
+
+ SetFlag( FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+ }
+
+ //
+ // Now either add to the current attributes or replace them.
+ //
+
+ if (Supersede) {
+
+ ThisFcb->Info.FileAttributes = FileAttributes;
+
+ } else {
+
+ ThisFcb->Info.FileAttributes |= FileAttributes;
+ }
+
+ //
+ // Get rid of any named $DATA attributes in the file.
+ //
+
+ NtfsRemoveDataAttributes( IrpContext,
+ ThisFcb,
+ ThisLcb,
+ IrpSp->FileObject,
+ LastFileNameOffset,
+ OpenById );
+ }
+
+ //
+ // Now we perform the operation of opening the attribute.
+ //
+
+ NtfsReplaceAttribute( IrpContext,
+ IrpSp,
+ ThisFcb,
+ *ThisScb,
+ ThisLcb,
+ *(PLONGLONG)&Irp->Overlay.AllocationSize );
+
+ //
+ // If we are overwriting a fle and the user doesn't want it marked as
+ // compressed, then change the attribute flag.
+ //
+
+ if (!FlagOn( (*ThisScb)->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) &&
+ FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_COMPRESSED ) &&
+ FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ ClearFlag( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+
+ //
+ // Now attempt to open the attribute.
+ //
+
+ ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
+
+ Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ ThisFcb->Vcb,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ ShareModificationType,
+#ifndef _CAIRO
+ UserFileOpen,
+#else // _CAIRO_
+ AttrTypeCode == $DATA ? UserFileOpen : UserPropertySetOpen,
+#endif // _CAIRO_
+ (OpenById
+ ? CcbFlags | CCB_FLAG_OPEN_BY_FILE_ID
+ : CcbFlags),
+ NULL,
+ ThisScb,
+ ThisCcb );
+
+ try_exit: NOTHING;
+ } finally {
+
+ InterlockedDecrement( &ThisFcb->CloseCount );
+ }
+
+ if (NT_SUCCESS( Status )) {
+
+ //
+ // Set the flag in the Scb to indicate that the size of the
+ // attribute has changed.
+ //
+
+ SetFlag( (*ThisScb)->ScbState, SCB_STATE_NOTIFY_RESIZE_STREAM );
+
+ //
+ // Since this is an supersede/overwrite, purge the section
+ // so that mappers will see zeros.
+ //
+
+ CcPurgeCacheSection( IrpSp->FileObject->SectionObjectPointer,
+ NULL,
+ 0,
+ FALSE );
+
+ //
+ // Remember the status of the oplock in the success code.
+ //
+
+ Status = OplockStatus;
+
+ //
+ // Now update the Iosb information.
+ //
+
+ if (Supersede) {
+
+ Irp->IoStatus.Information = FILE_SUPERSEDED;
+
+ } else {
+
+ Irp->IoStatus.Information = FILE_OVERWRITTEN;
+ }
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOverwriteAttr: Exit -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsOpenNewAttr (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN ULONG CcbFlags,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN OpenById,
+ OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to create a new attribute on the disk.
+ All access and security checks have been done outside of this
+ routine, all we do is create the attribute and open it.
+ We test if the attribute will fit in the Mft record. If so we
+ create it there. Otherwise we call the create attribute through
+ allocation.
+
+ We then open the attribute with our common routine. In the
+ resident case the Scb will have all file values set to
+ the allocation size. We set the valid data size back to zero
+ and mark the Scb as truncate on close.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the stack location for this open.
+
+ ThisLcb - This is the Lcb we used to reach this Fcb.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ LastFileNameOffset - This is the offset in the full path name of the
+ final component.
+
+ AttrName - This is the attribute name in case we need to create
+ an Scb.
+
+ AttrTypeCode - This is the attribute type code to use to create
+ the Scb.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+ LogIt - Indicates if we need to log the create operations.
+
+ OpenById - Indicates if this open is related to a OpenByFile open.
+
+ ThisScb - This is the address to store the address of the Scb.
+
+ ThisCcb - This is the address to store the address of the Ccb.
+
+Return Value:
+
+ NTSTATUS - The result of opening this indexed attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ BOOLEAN ScbExisted;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenNewAttr: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // We create the Scb because we will use it.
+ //
+
+ *ThisScb = NtfsCreateScb( IrpContext,
+ ThisFcb,
+ AttrTypeCode,
+ &AttrName,
+ FALSE,
+ &ScbExisted );
+
+ //
+ // An attribute has gone away but the Scb hasn't left yet.
+ // Also mark the header as unitialized.
+ //
+
+ ClearFlag( (*ThisScb)->ScbState, SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_ATTRIBUTE_RESIDENT |
+ SCB_STATE_FILE_SIZE_LOADED );
+
+ //
+ // Create the attribute on disk and update the Scb and Fcb.
+ //
+
+ NtfsCreateAttribute( IrpContext,
+ IrpSp,
+ ThisFcb,
+ *ThisScb,
+ ThisLcb,
+ *(PLONGLONG)&Irp->Overlay.AllocationSize,
+ LogIt );
+
+ //
+ // Now actually open the attribute.
+ //
+
+ ASSERT( NtfsIsTypeCodeUserData( AttrTypeCode ));
+
+ Status = NtfsOpenAttribute( IrpContext,
+ IrpSp,
+ ThisFcb->Vcb,
+ ThisLcb,
+ ThisFcb,
+ LastFileNameOffset,
+ AttrName,
+ AttrTypeCode,
+ (ThisFcb->CleanupCount != 0 ? CheckShareAccess : SetShareAccess),
+#ifndef _CAIRO_
+ UserFileOpen,
+#else // _CAIRO_
+ AttrTypeCode == $DATA ? UserFileOpen : UserPropertySetOpen,
+#endif // _CAIRO_
+ (CcbFlags | (OpenById ? CCB_FLAG_OPEN_BY_FILE_ID : 0)),
+ NULL,
+ ThisScb,
+ ThisCcb );
+
+ //
+ // If there are no errors at this point, we set the caller's Iosb.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ //
+ // Read the attribute information from the disk.
+ //
+
+ NtfsUpdateScbFromAttribute( IrpContext, *ThisScb, NULL );
+
+ //
+ // Set the flag to indicate that we created a stream and also remember to
+ // to check if we need to truncate on close.
+ //
+
+ NtfsAcquireFsrtlHeader( *ThisScb );
+ SetFlag( (*ThisScb)->ScbState,
+ SCB_STATE_TRUNCATE_ON_CLOSE | SCB_STATE_NOTIFY_ADD_STREAM );
+
+ //
+ // If we created a temporary stream then mark the Scb.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
+
+ SetFlag( (*ThisScb)->ScbState, SCB_STATE_TEMPORARY );
+ SetFlag( IrpSp->FileObject->Flags, FO_TEMPORARY_FILE );
+ }
+
+ NtfsReleaseFsrtlHeader( *ThisScb );
+
+ Irp->IoStatus.Information = FILE_CREATED;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsOpenNewAttr );
+
+ //
+ // Uninitialize the attribute context.
+ //
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsOpenNewAttr: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsParseNameForCreate (
+ IN PIRP_CONTEXT IrpContext,
+ IN UNICODE_STRING String,
+ IN OUT PUNICODE_STRING FileObjectString,
+ IN OUT PUNICODE_STRING OriginalString,
+ IN OUT PUNICODE_STRING NewNameString,
+ OUT PUNICODE_STRING AttrName,
+ OUT PUNICODE_STRING AttrCodeName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine parses the input string and remove any intermediate
+ named attributes from intermediate nodes. It verifies that all
+ intermediate nodes specify the file name index attribute if any
+ at all. On output it will store the modified string which contains
+ component names only, into the file object name pointer pointer. It is legal
+ for the last component to have attribute strings. We pass those
+ back via the attribute name strings. We also construct the string to be stored
+ back in the file object if we need to post this request.
+
+Arguments:
+
+ String - This is the string to normalize.
+
+ FileObjectString - We store the normalized string into this pointer, removing the
+ attribute and attribute code strings from all component.
+
+ OriginalString - This is the same as the file object string except we append the
+ attribute name and attribute code strings. We assume that the buffer for this
+ string is the same as the buffer for the FileObjectString.
+
+ NewNameString - This is the string which contains the full name being parsed.
+ If the buffer is different than the buffer for the Original string then any
+ character shifts will be duplicated here.
+
+ AttrName - We store the attribute name specified in the last component
+ in this string.
+
+ AttrCodeName - We store the attribute code name specified in the last
+ component in this string.
+
+Return Value:
+
+ BOOLEAN - TRUE if the path is legal, FALSE otherwise.
+
+--*/
+
+{
+ PARSE_TERMINATION_REASON TerminationReason;
+ UNICODE_STRING ParsedPath;
+
+ NTFS_NAME_DESCRIPTOR NameDescript;
+
+ BOOLEAN RemovedIndexName = FALSE;
+
+ LONG FileObjectIndex;
+ LONG NewNameIndex;
+
+ BOOLEAN SameBuffers = (OriginalString->Buffer == NewNameString->Buffer);
+
+ PUNICODE_STRING TestAttrName;
+ PUNICODE_STRING TestAttrCodeName;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsParseNameForCreate: Entered\n") );
+
+ //
+ // We loop through the input string calling ParsePath to swallow the
+ // biggest chunk we can. The main case we want to deal with is
+ // when we encounter a non-simple name. If this is not the
+ // final component, the attribute name and code type better
+ // indicate that this is a directory. The only other special
+ // case we consider is the case where the string is an
+ // attribute only. This is legal only for the first component
+ // of the file, and then only if there is no leading backslash.
+ //
+
+ //
+ // Initialize some return values.
+ //
+
+ AttrName->Length = 0;
+ AttrCodeName->Length = 0;
+
+ //
+ // Set up the indexes into our starting file object string.
+ //
+
+ FileObjectIndex = (LONG) FileObjectString->Length - (LONG) String.Length;
+ NewNameIndex = (LONG) NewNameString->Length - (LONG) String.Length;
+
+ //
+ // We don't allow trailing colons.
+ //
+
+ if (String.Buffer[(String.Length / 2) - 1] == L':') {
+
+ return FALSE;
+ }
+
+ if (String.Length != 0) {
+
+ while (TRUE) {
+
+ //
+ // Parse the next chunk in the input string.
+ //
+
+ TerminationReason = NtfsParsePath( String,
+ FALSE,
+ &ParsedPath,
+ &NameDescript,
+ &String );
+
+ //
+ // Analyze the termination reason to discover if we can abort the
+ // parse process.
+ //
+
+ switch (TerminationReason) {
+
+ case NonSimpleName :
+
+ //
+ // We will do the work below.
+ //
+
+ break;
+
+ case IllegalCharacterInName :
+ case VersionNumberPresent :
+ case MalFormedName :
+
+ //
+ // We simply return an error.
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Illegal character\n") );
+ return FALSE;
+
+ case AttributeOnly :
+
+ //
+ // This is legal only if it is the only component of a relative open. We
+ // test this by checking that we are at the end of string and the file
+ // object name has a lead in ':' character or this is the root directory
+ // and the lead in characters are '\:'.
+ //
+
+ if ((String.Length != 0) ||
+ RemovedIndexName ||
+ (FileObjectString->Buffer[0] == L'\\' ?
+ FileObjectString->Buffer[1] != L':' :
+ FileObjectString->Buffer[0] != L':')) {
+
+ DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Illegal character\n") );
+ return FALSE;
+ }
+
+ //
+ // We can drop down to the EndOfPath case as it will copy over
+ // the parsed path portion.
+ //
+
+ case EndOfPathReached :
+
+ NOTHING;
+ }
+
+ //
+ // We add the filename part of the non-simple name to the parsed
+ // path. Check if we can include the separator.
+ //
+
+ if ((TerminationReason != EndOfPathReached)
+ && (FlagOn( NameDescript.FieldsPresent, FILE_NAME_PRESENT_FLAG ))) {
+
+ if (ParsedPath.Length > 2
+ || (ParsedPath.Length == 2
+ && ParsedPath.Buffer[0] != L'\\')) {
+
+ ParsedPath.Length +=2;
+ }
+
+ ParsedPath.Length += NameDescript.FileName.Length;
+ }
+
+ FileObjectIndex += ParsedPath.Length;
+ NewNameIndex += ParsedPath.Length;
+
+ //
+ // If the remaining string is empty, then we remember any attributes and
+ // exit now.
+ //
+
+ if (String.Length == 0) {
+
+ //
+ // If the name specified either an attribute or attribute
+ // name, we remember them.
+ //
+
+ if (FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_NAME_PRESENT_FLAG )) {
+
+ *AttrName = NameDescript.AttributeName;
+ }
+
+ if (FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_TYPE_PRESENT_FLAG )) {
+
+ *AttrCodeName = NameDescript.AttributeType;
+ }
+
+ break;
+ }
+
+ //
+ // This can only be the non-simple case. If there is more to the
+ // name, then the attributes better describe a directory. We also shift the
+ // remaining bytes of the string down.
+ //
+
+ ASSERT( FlagOn( NameDescript.FieldsPresent, ATTRIBUTE_NAME_PRESENT_FLAG | ATTRIBUTE_TYPE_PRESENT_FLAG ));
+
+ TestAttrName = FlagOn( NameDescript.FieldsPresent,
+ ATTRIBUTE_NAME_PRESENT_FLAG )
+ ? &NameDescript.AttributeName
+ : &NtfsEmptyString;
+
+ TestAttrCodeName = FlagOn( NameDescript.FieldsPresent,
+ ATTRIBUTE_TYPE_PRESENT_FLAG )
+ ? &NameDescript.AttributeType
+ : &NtfsEmptyString;
+
+ if (!NtfsVerifyNameIsDirectory( IrpContext,
+ TestAttrName,
+ TestAttrCodeName )) {
+
+ DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Invalid intermediate component\n") );
+ return FALSE;
+ }
+
+ RemovedIndexName = TRUE;
+
+ //
+ // We need to insert a separator and then move the rest of the string
+ // down.
+ //
+
+ FileObjectString->Buffer[FileObjectIndex / 2] = L'\\';
+
+ if (!SameBuffers) {
+
+ NewNameString->Buffer[NewNameIndex / 2] = L'\\';
+ }
+
+ FileObjectIndex += 2;
+ NewNameIndex += 2;
+
+ RtlMoveMemory( &FileObjectString->Buffer[FileObjectIndex / 2],
+ String.Buffer,
+ String.Length );
+
+ if (!SameBuffers) {
+
+ RtlMoveMemory( &NewNameString->Buffer[NewNameIndex / 2],
+ String.Buffer,
+ String.Length );
+ }
+ }
+ }
+
+ //
+ // At this point the original string is the same as the file object string.
+ //
+
+ FileObjectString->Length = (USHORT) FileObjectIndex;
+ NewNameString->Length = (USHORT) NewNameIndex;
+
+ OriginalString->Length = FileObjectString->Length;
+
+ //
+ // We want to store the attribute index values in the original name
+ // string. We just need to extend the original name length.
+ //
+
+ if (AttrName->Length != 0
+ || AttrCodeName->Length != 0) {
+
+ OriginalString->Length += (2 + AttrName->Length);
+
+ if (AttrCodeName->Length != 0) {
+
+ OriginalString->Length += (2 + AttrCodeName->Length);
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsParseNameForCreate: Exit\n") );
+
+ return TRUE;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsCheckValidAttributeAccess (
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PDUPLICATED_INFORMATION Info OPTIONAL,
+ IN UNICODE_STRING AttrName,
+ IN UNICODE_STRING AttrCodeName,
+ IN BOOLEAN TrailingBackslash,
+ OUT PATTRIBUTE_TYPE_CODE AttrTypeCode,
+ OUT PULONG CcbFlags,
+ OUT PBOOLEAN IndexedAttribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine looks at the file, the specified attribute name and
+ code to determine if an attribute of this file may be opened
+ by this user. If there is a conflict between the file type
+ and the attribute name and code, or the specified type of attribute
+ (directory/nondirectory) we will return FALSE.
+ We also check that the attribute code string is defined for the
+ volume at this time.
+
+ The final check of this routine is just whether a user is allowed
+ to open the particular attribute or if Ntfs will guard them.
+
+Arguments:
+
+ IrpSp - This is the stack location for this open.
+
+ Vcb - This is the Vcb for this volume.
+
+ Info - If specified, this is the duplicated information for this file.
+
+ AttrName - This is the attribute name specified.
+
+ AttrCodeName - This is the attribute code name to use to open the attribute.
+
+ AttrTypeCode - Used to store the attribute type code determined here.
+
+ TrailingBackslash - Indicates if caller had a terminating backslash on the
+ name.
+
+ CcbFlags - We set the Ccb flags here to store in the Ccb later.
+
+ IndexedAttribute - Set to indicate the type of open.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS if access is allowed, the status code indicating
+ the reason for denial otherwise.
+
+--*/
+
+{
+ BOOLEAN Indexed;
+ ATTRIBUTE_TYPE_CODE AttrType;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckValidAttributeAccess: Entered\n") );
+
+ //
+ // If the user specified a attribute code string, we find the
+ // corresponding attribute. If there is no matching attribute
+ // type code then we report that this access is invalid.
+ //
+
+ if (AttrCodeName.Length != 0) {
+
+ AttrType = NtfsGetAttributeTypeCode( Vcb,
+ AttrCodeName );
+
+ if (AttrType == $UNUSED) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Bad attribute name for index\n") );
+ return STATUS_INVALID_PARAMETER;
+
+ //
+ // If the type code is Index allocation, then the name better be the filename
+ // index. If so then we clear the name length value to make our other
+ // tests work.
+ //
+
+ } else if (AttrType == $INDEX_ALLOCATION) {
+
+ if (AttrName.Length != 0) {
+
+ if (!NtfsAreNamesEqual( Vcb->UpcaseTable, &AttrName, &NtfsFileNameIndex, TRUE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Bad name for index allocation\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ AttrName.Length = 0;
+ }
+ }
+#ifdef _CAIRO_
+ //
+ // BUGBUG - Ntfs does not correctly handle compressing streams that
+ // are logged. Despite the fact that property sets can be large,
+ // we forcibly disable compression on these streams.
+ //
+
+ else if (AttrType == $PROPERTY_SET) {
+ SetFlag( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION );
+ }
+#endif // _CAIRO_
+
+
+ DebugTrace( 0, Dbg, ("Attribute type code -> %04x\n", AttrType) );
+
+ } else {
+
+ AttrType = $UNUSED;
+ }
+
+ //
+ // Pull some values out of the Irp and IrpSp.
+ //
+
+ Indexed = BooleanFlagOn( IrpSp->Parameters.Create.Options,
+ FILE_DIRECTORY_FILE );
+
+ //
+ // We need to determine whether the user expects to open an
+ // indexed or non-indexed attribute. If either of the
+ // directory/non-directory flags in the Irp stack are set,
+ // we will use those.
+ //
+ // Otherwise we need to examine some of the other input parameters.
+ // We have the following information:
+ //
+ // 1 - We may have a duplicated information structure for the file.
+ // (Not present on a create).
+ // 2 - The user specified the name with a trailing backslash.
+ // 3 - The user passed in an attribute name.
+ // 4 - The user passed in an attribute type.
+ //
+ // We first look at the attribute type code and name. If they are
+ // both unspecified we determine the type of access by following
+ // the following steps.
+ //
+ // 1 - If there is a duplicated information structure we
+ // set the code to $INDEX_ALLOCATION and remember
+ // this is indexed. Otherwise this is a $DATA/$PROPERTY_SET
+ // attribute.
+ //
+ // 2 - If there is a trailing backslash we assume this is
+ // an indexed attribute.
+ //
+ // If have an attribute code type or name, then if the code type is
+ // $INDEX_ALLOCATION without a name this is an indexed attribute.
+ // Otherwise we assume a non-indexed attribute.
+ //
+
+ if (!FlagOn( IrpSp->Parameters.Create.Options,
+ FILE_NON_DIRECTORY_FILE | FILE_DIRECTORY_FILE) &&
+ (AttrName.Length == 0)) {
+
+ if (AttrType == $UNUSED) {
+
+ if (ARGUMENT_PRESENT( Info )) {
+
+ Indexed = BooleanIsDirectory( Info );
+
+ } else {
+
+ Indexed = FALSE;
+ }
+
+ } else if (AttrType == $INDEX_ALLOCATION) {
+
+ Indexed = TRUE;
+ }
+ }
+
+ //
+ // If the type code was unspecified, we can assume it from the attribute
+ // name and the type of the file. If the file is a directory and
+ // there is no attribute name, we assume this is an indexed open.
+ // Otherwise it is a non-indexed open.
+ //
+
+ if (AttrType == $UNUSED) {
+
+ if (Indexed && AttrName.Length == 0) {
+
+ AttrType = $INDEX_ALLOCATION;
+
+ } else {
+
+ AttrType = $DATA;
+ }
+ }
+
+ //
+ // If the user specified directory all we need to do is check the
+ // following condition.
+ //
+ // 1 - If the file was specified, it must be a directory.
+ // 2 - The attribute type code must be $INDEX_ALLOCATION with no attribute name.
+ // 3 - The user isn't trying to open the volume.
+ //
+
+ if (Indexed) {
+
+ if ((AttrType != $INDEX_ALLOCATION)
+ || (AttrName.Length != 0)) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Conflict in directory\n") );
+ return STATUS_NOT_A_DIRECTORY;
+
+ //
+ // If there is a current file and it is not a directory and
+ // the caller wanted to perform a create. We return
+ // STATUS_OBJECT_NAME_COLLISION, otherwise we return STATUS_NOT_A_DIRECTORY.
+ //
+
+ } else if (ARGUMENT_PRESENT( Info ) && !IsDirectory( Info )) {
+
+ if (((IrpSp->Parameters.Create.Options >> 24) & 0x000000ff) == FILE_CREATE) {
+
+ return STATUS_OBJECT_NAME_COLLISION;
+
+ } else {
+
+ return STATUS_NOT_A_DIRECTORY;
+ }
+ }
+
+ SetFlag( *CcbFlags, CCB_FLAG_OPEN_AS_FILE );
+
+ //
+ // If the user specified a non-directory that means he is opening a non-indexed
+ // attribute. We check for the following condition.
+ //
+ // 1 - Only the unnamed data attribute may be opened for a volume.
+ // 2 - We can't be opening an unnamed $INDEX_ALLOCATION attribute.
+ //
+
+ } else {
+
+ //
+ // Now determine if we are opening the entire file.
+ //
+
+ if ((AttrType == $DATA)
+ && (AttrName.Length == 0)) {
+
+ SetFlag( *CcbFlags, CCB_FLAG_OPEN_AS_FILE );
+ }
+
+ if (ARGUMENT_PRESENT( Info ) &&
+ IsDirectory( Info ) &&
+ FlagOn( *CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Can't open directory as file\n") );
+ return STATUS_FILE_IS_A_DIRECTORY;
+ }
+ }
+
+ //
+ // If we make it this far, lets check that we will allow access to
+ // the attribute specified. Typically we only allow the user to
+ // access non system files. Also only the Data attributes and
+ // attributes created by the user may be opened. We will protect
+ // these with boolean flags to allow the developers to enable
+ // reading any attributes.
+ //
+
+ if (NtfsProtectSystemAttributes) {
+
+ if ((AttrType < $FIRST_USER_DEFINED_ATTRIBUTE) &&
+ !NtfsIsTypeCodeUserData( AttrType ) &&
+ ((AttrType != $INDEX_ALLOCATION) || !Indexed)) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: System attribute code\n") );
+ return STATUS_ACCESS_DENIED;
+ }
+
+ }
+
+ //
+ // Now check if the trailing backslash is compatible with the
+ // file being opened.
+ //
+
+ if (TrailingBackslash) {
+
+ if (!Indexed ||
+ FlagOn( IrpSp->Parameters.Create.Options, FILE_NON_DIRECTORY_FILE )) {
+
+ return STATUS_OBJECT_NAME_INVALID;
+
+ } else {
+
+ Indexed = TRUE;
+ AttrType = $INDEX_ALLOCATION;
+ }
+ }
+
+ *IndexedAttribute = Indexed;
+ *AttrTypeCode = AttrType;
+
+ DebugTrace( -1, Dbg, ("NtfsCheckValidAttributeAccess: Exit\n") );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsOpenAttributeCheck (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ OUT PSCB *ThisScb,
+ OUT PSHARE_MODIFICATION_TYPE ShareModificationType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is a general routine which checks if an existing
+ non-indexed attribute may be opened. It considers only the oplock
+ state of the file and the current share access. In the course of
+ performing these checks, the Scb for the attribute may be
+ created and the share modification for the actual OpenAttribute
+ call is determined.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the stack location for this open.
+
+ ThisScb - Address to store the Scb if found or created.
+
+ ShareModificationType - Address to store the share modification type
+ for a subsequent OpenAttribute call.
+
+Return Value:
+
+ NTSTATUS - The result of opening this indexed attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ BOOLEAN DeleteOnClose;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenAttributeCheck: Entered\n") );
+
+ //
+ // We should already have the Scb for this file.
+ //
+
+ ASSERT_SCB( *ThisScb );
+
+ //
+ // If there are other opens on this file, we need to check the share
+ // access before we check the oplocks. We remember that
+ // we did the share access check by simply updating the share
+ // access we open the attribute.
+ //
+
+ if ((*ThisScb)->CleanupCount != 0) {
+
+ //
+ // We check the share access for this file without updating it.
+ //
+
+ Status = IoCheckShareAccess( IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess,
+ IrpSp->Parameters.Create.ShareAccess,
+ IrpSp->FileObject,
+ &(*ThisScb)->ShareAccess,
+ FALSE );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ DebugTrace( 0, Dbg, ("Check oplock state of existing Scb\n") );
+
+ if (SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_DATA) {
+
+ //
+ // If the handle count is greater than 1 then fail this
+ // open now if the caller wants a filter oplock.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER ) &&
+ ((*ThisScb)->CleanupCount > 1)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_OPLOCK_NOT_GRANTED, NULL, NULL );
+ }
+
+ Status = FsRtlCheckOplock( &(*ThisScb)->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsOplockPrePostIrp );
+
+ //
+ // Update the FastIoField.
+ //
+
+ NtfsAcquireFsrtlHeader( *ThisScb );
+ (*ThisScb)->Header.IsFastIoPossible = NtfsIsFastIoPossible( *ThisScb );
+ NtfsReleaseFsrtlHeader( *ThisScb );
+
+ //
+ // If the return value isn't success or oplock break in progress
+ // the irp has been posted. We return right now.
+ //
+
+ if (Status == STATUS_PENDING) {
+
+ DebugTrace( 0, Dbg, ("Irp posted through oplock routine\n") );
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+ }
+
+ *ShareModificationType = UpdateShareAccess;
+
+ //
+ // If the unclean count in the Fcb is 0, we will simply set the
+ // share access.
+ //
+
+ } else {
+
+ *ShareModificationType = SetShareAccess;
+ }
+
+ //
+ // If the user wants write access access to the file make sure there
+ // is process mapping this file as an image. Any attempt to delete
+ // the file will be stopped in fileinfo.c
+ //
+ // If the user wants to delete on close, we must check at this
+ // point though.
+ //
+
+ DeleteOnClose = BooleanFlagOn( IrpSp->Parameters.Create.Options,
+ FILE_DELETE_ON_CLOSE );
+
+ if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
+ FILE_WRITE_DATA )
+ || DeleteOnClose) {
+
+ //
+ // Use a try-finally to decrement the open count. This is a little
+ // bit of trickery to keep the scb around while we are doing the
+ // flush call.
+ //
+
+ InterlockedIncrement( &(*ThisScb)->CloseCount );
+
+ try {
+
+ //
+ // If there is an image section then we better have the file
+ // exclusively.
+ //
+
+ if ((*ThisScb)->NonpagedScb->SegmentObject.ImageSectionObject != NULL) {
+
+ if (!MmFlushImageSection( &(*ThisScb)->NonpagedScb->SegmentObject,
+ MmFlushForWrite )) {
+
+ DebugTrace( 0, Dbg, ("Couldn't flush image section\n") );
+
+ Status = DeleteOnClose ? STATUS_CANNOT_DELETE :
+ STATUS_SHARING_VIOLATION;
+ }
+ }
+
+ } finally {
+
+ InterlockedDecrement( &(*ThisScb)->CloseCount );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeCheck: Exit -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsAddEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB ThisFcb,
+ IN PFILE_FULL_EA_INFORMATION EaBuffer OPTIONAL,
+ IN ULONG EaLength,
+ OUT PIO_STATUS_BLOCK Iosb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will add an ea set to the file. It writes the attributes
+ to disk and updates the Fcb info structure with the packed ea size.
+
+Arguments:
+
+ Vcb - This is the volume being opened.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ EaBuffer - This is the buffer passed by the user.
+
+ EaLength - This is the stated length of the buffer.
+
+ Iosb - This is the Status Block to use to fill in the offset of an
+ offending Ea.
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ EA_LIST_HEADER EaList;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddEa: Entered\n") );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Initialize the EaList header.
+ //
+
+ EaList.PackedEaSize = 0;
+ EaList.NeedEaCount = 0;
+ EaList.UnpackedEaSize = 0;
+ EaList.BufferSize = 0;
+ EaList.FullEa = NULL;
+
+ if (ARGUMENT_PRESENT( EaBuffer )) {
+
+ //
+ // Check the user's buffer for validity.
+ //
+
+ Status = IoCheckEaBufferValidity( EaBuffer,
+ EaLength,
+ &Iosb->Information );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsAddEa: Invalid ea list\n") );
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // **** Maybe this routine should raise.
+ //
+
+ Status = NtfsBuildEaList( IrpContext,
+ Vcb,
+ &EaList,
+ EaBuffer,
+ &Iosb->Information );
+
+ if (!NT_SUCCESS( Status )) {
+
+ DebugTrace( -1, Dbg, ("NtfsAddEa: Couldn't build Ea list\n") );
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ }
+
+ //
+ // Now replace the existing EAs.
+ //
+
+ NtfsReplaceFileEas( IrpContext, ThisFcb, &EaList );
+
+ } finally {
+
+ DebugUnwind( NtfsAddEa );
+
+ //
+ // Free the in-memory copy of the Eas.
+ //
+
+ if (EaList.FullEa != NULL) {
+
+ NtfsFreePool( EaList.FullEa );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddEa: Exit -> %08lx\n", Status) );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsInitializeFcbAndStdInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ThisFcb,
+ IN BOOLEAN Directory,
+ IN BOOLEAN Compressed,
+ IN ULONG FileAttributes,
+ IN PLONGLONG SetCreationTime OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will initialize an Fcb for a newly created file and create
+ the standard information attribute on disk. We assume that some information
+ may already have been placed in the Fcb so we don't zero it out. We will
+ initialize the allocation size to zero, but that may be changed later in
+ the create process.
+
+Arguments:
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ Directory - Indicates if this is a directory file.
+
+ Compressed - Indicates if this is a compressed file.
+
+ FileAttributes - These are the attributes the user wants to attach to
+ the file. We will just clear any unsupported bits.
+
+ SetCreationTime - Optionally force the creation time to a given value
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ STANDARD_INFORMATION StandardInformation;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeFcbAndStdInfo: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Mask out the invalid bits of the file atributes. Then set the
+ // file name index bit if this is a directory.
+ //
+
+ if (!Directory) {
+
+ SetFlag( FileAttributes, FILE_ATTRIBUTE_ARCHIVE );
+ }
+
+ ClearFlag( FileAttributes,
+ ~(FILE_ATTRIBUTE_READONLY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_TEMPORARY |
+ FILE_ATTRIBUTE_ARCHIVE) );
+
+ if (Directory) {
+
+ SetFlag( FileAttributes, DUP_FILE_NAME_INDEX_PRESENT );
+ }
+
+ if (Compressed) {
+
+ SetFlag( FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+
+ ThisFcb->Info.FileAttributes = FileAttributes;
+
+ //
+ // Fill in the rest of the Fcb Info structure.
+ //
+
+ if (SetCreationTime == NULL) {
+
+ NtfsGetCurrentTime( IrpContext, ThisFcb->Info.CreationTime );
+
+ ThisFcb->Info.LastModificationTime = ThisFcb->Info.CreationTime;
+ ThisFcb->Info.LastChangeTime = ThisFcb->Info.CreationTime;
+ ThisFcb->Info.LastAccessTime = ThisFcb->Info.CreationTime;
+
+ ThisFcb->CurrentLastAccess = ThisFcb->Info.CreationTime;
+
+ } else {
+
+ ThisFcb->Info.CreationTime = *SetCreationTime;
+
+ NtfsGetCurrentTime( IrpContext, ThisFcb->Info.LastModificationTime );
+
+ ThisFcb->Info.LastChangeTime = ThisFcb->Info.LastModificationTime;
+ ThisFcb->Info.LastAccessTime = ThisFcb->Info.LastModificationTime;
+
+ ThisFcb->CurrentLastAccess = ThisFcb->Info.LastModificationTime;
+ }
+
+ //
+ // We assume these sizes are zero.
+ //
+
+ ThisFcb->Info.AllocatedLength = 0;
+ ThisFcb->Info.FileSize = 0;
+
+ //
+ // Copy the standard information fields from the Fcb and create the
+ // attribute.
+ //
+
+ RtlZeroMemory( &StandardInformation, sizeof( STANDARD_INFORMATION ));
+
+ StandardInformation.CreationTime = ThisFcb->Info.CreationTime;
+ StandardInformation.LastModificationTime = ThisFcb->Info.LastModificationTime;
+ StandardInformation.LastChangeTime = ThisFcb->Info.LastChangeTime;
+ StandardInformation.LastAccessTime = ThisFcb->Info.LastAccessTime;
+
+ StandardInformation.FileAttributes = ThisFcb->Info.FileAttributes;
+
+#ifdef _CAIRO_
+ StandardInformation.ClassId = ThisFcb->ClassId;
+ StandardInformation.OwnerId = ThisFcb->OwnerId;
+ StandardInformation.SecurityId = ThisFcb->SecurityId;
+ StandardInformation.Usn = ThisFcb->Usn;
+
+ SetFlag(ThisFcb->FcbState, FCB_STATE_LARGE_STD_INFO);
+#endif
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ $STANDARD_INFORMATION,
+ NULL,
+ &StandardInformation,
+ sizeof( STANDARD_INFORMATION ),
+ 0,
+ NULL,
+ FALSE,
+ &AttrContext );
+
+ //
+ // We know that the open call will generate a single link.
+ // (Remember that a separate 8.3 name is not considered a link)
+ //
+
+ ThisFcb->LinkCount =
+ ThisFcb->TotalLinks = 1;
+
+ //
+ // Now set the header initialized flag in the Fcb.
+ //
+
+ SetFlag( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED );
+
+ } finally {
+
+ DebugUnwind( NtfsInitializeFcbAndStdInfo );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeFcbAndStdInfo: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsCreateAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN OUT PFCB ThisFcb,
+ IN OUT PSCB ThisScb,
+ IN PLCB ThisLcb,
+ IN LONGLONG AllocationSize,
+ IN BOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to create an attribute of a given size on the
+ disk. This path will only create non-resident attributes unless the
+ allocation size is zero.
+
+ The Scb will contain the attribute name and type code on entry.
+
+Arguments:
+
+ IrpSp - Stack location in the Irp for this request.
+
+ ThisFcb - This is the Fcb for the file to create the attribute in.
+
+ ThisScb - This is the Scb for the attribute to create.
+
+ ThisLcb - This is the Lcb for propagating compression parameters
+
+ AllocationSize - This is the size of the attribute to create.
+
+ LogIt - Indicates whether we should log the creation of the attribute.
+ Also indicates if this is a create file operation.
+
+Return Value:
+
+ None - This routine will raise on error.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PATTRIBUTE_RECORD_HEADER ThisAttribute = NULL;
+
+ USHORT AttributeFlags = 0;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateAttribute: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ !FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) {
+
+ //
+ // If this is the root directory then use the Scb from the Vcb.
+ //
+
+ if (ThisLcb == ThisFcb->Vcb->RootLcb) {
+
+ AttributeFlags = (USHORT)(ThisFcb->Vcb->RootIndexScb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
+
+ } else {
+
+ AttributeFlags = (USHORT)(ThisLcb->Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
+ }
+ }
+
+#ifdef _CAIRO_
+
+ if (NtfsPerformQuotaOperation( ThisFcb) &&
+ FlagOn( ThisScb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( ThisScb->AttributeTypeCode ));
+
+ //
+ // Since this is a new stream indicate quota is set to
+ // allocation size.
+ //
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+ }
+#endif
+
+ //
+ // We lookup that attribute again and it better not be there.
+ // We need the file record in order to know whether the attribute
+ // is resident or not.
+ //
+
+ if (AllocationSize != 0) {
+
+ DebugTrace( 0, Dbg, ("Create non-resident attribute\n") );
+
+ if (!NtfsAllocateAttribute( IrpContext,
+ ThisScb,
+ ThisScb->AttributeTypeCode,
+ (ThisScb->AttributeName.Length != 0
+ ? &ThisScb->AttributeName
+ : NULL),
+ AttributeFlags,
+ FALSE,
+ LogIt,
+ AllocationSize,
+ NULL )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_LARGE_ALLOCATION );
+ }
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ } else {
+
+#ifdef _CAIRO_
+
+ //
+ // Update the quota if this is a user stream.
+ //
+
+ if ( FlagOn( ThisScb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA )) {
+
+ LONGLONG Delta = NtfsResidentStreamQuota( ThisFcb->Vcb );
+
+ NtfsConditionallyUpdateQuota( IrpContext,
+ ThisFcb,
+ &Delta,
+ LogIt,
+ TRUE );
+ }
+
+#endif // _CAIRO_
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ ThisFcb,
+ ThisScb->AttributeTypeCode,
+ &ThisScb->AttributeName,
+ NULL,
+ (ULONG) AllocationSize,
+ AttributeFlags,
+ NULL,
+ LogIt,
+ &AttrContext );
+
+ ThisAttribute = NtfsFoundAttribute( &AttrContext );
+
+ }
+
+ //
+ // Clear the header initialized bit and read the sizes from the
+ // disk.
+ //
+
+ ClearFlag( ThisScb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+ NtfsUpdateScbFromAttribute( IrpContext,
+ ThisScb,
+ ThisAttribute );
+
+#if 0 // _CAIRO_
+ ASSERT( !NtfsPerformQuotaOperation( ThisFcb ) || NtfsCalculateQuotaAdjustment( IrpContext, ThisFcb ) == 0 );
+#endif // _CAIRO_
+
+ } finally {
+
+ DebugUnwind( NtfsCreateAttribute );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( +1, Dbg, ("NtfsCreateAttribute: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsRemoveDataAttributes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ThisFcb,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFILE_OBJECT FileObject,
+ IN ULONG LastFileNameOffset,
+ IN BOOLEAN OpenById
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to remove (or mark for delete) all of the named
+ data or property set attributes on a file. This is done during an overwrite
+ or supersede operation.
+
+Arguments:
+
+ Context - Pointer to the IrpContext to be queued to the Fsp
+
+ ThisFcb - This is the Fcb for the file in question.
+
+ ThisLcb - This is the Lcb used to reach this Fcb (if specified).
+
+ FileObject - This is the file object for the file.
+
+ LastFileNameOffset - This is the offset of the file in the full name.
+
+ OpenById - Indicates if this open is being performed by file id.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ATTRIBUTE_TYPE_CODE TypeCode = $DATA;
+
+ UNICODE_STRING AttributeName;
+ PSCB ThisScb;
+
+ BOOLEAN MoreToGo;
+
+ ASSERT_EXCLUSIVE_FCB( ThisFcb );
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+
+ while (TRUE) {
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Enumerate all of the attributes with the matching type code
+ //
+
+ MoreToGo = NtfsLookupAttributeByCode( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ TypeCode,
+ &Context );
+
+ while (MoreToGo) {
+
+ //
+ // Point to the current attribute.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+
+ //
+ // We only look at named data attributes.
+ //
+
+ if (Attribute->NameLength != 0) {
+
+ //
+ // Construct the name and find the Scb for the attribute.
+ //
+
+ AttributeName.Buffer = (PWSTR) Add2Ptr( Attribute, Attribute->NameOffset );
+ AttributeName.MaximumLength = AttributeName.Length = Attribute->NameLength * sizeof( WCHAR );
+
+ ThisScb = NtfsCreateScb( IrpContext,
+ ThisFcb,
+ TypeCode,
+ &AttributeName,
+ FALSE,
+ NULL );
+
+ //
+ // If there is an open handle on this file, we simply mark
+ // the Scb as delete pending.
+ //
+
+ if (ThisScb->CleanupCount != 0) {
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+
+ //
+ // Otherwise we remove the attribute and mark the Scb as
+ // deleted. The Scb will be cleaned up when the Fcb is
+ // cleaned up.
+ //
+
+ } else {
+
+#ifdef _CAIRO_
+ LONGLONG Delta;
+
+ ASSERT( !FlagOn( ThisScb->ScbState, SCB_STATE_QUOTA_ENLARGED ));
+
+ if (NtfsPerformQuotaOperation(ThisFcb)) {
+
+ if (NtfsIsAttributeResident( Attribute )) {
+ Delta = -(LONG) Attribute->Form.Resident.ValueLength;
+ } else {
+ Delta = -(LONGLONG) Attribute->Form.Nonresident.FileSize;
+ }
+
+ NtfsUpdateFileQuota( IrpContext,
+ ThisFcb,
+ &Delta,
+ TRUE,
+ FALSE );
+
+ }
+#endif
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ ThisFcb,
+ TRUE,
+ FALSE,
+ &Context );
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+
+ //
+ // If this is a named stream, then report this to the dir notify
+ // package.
+ //
+
+ if (!OpenById &&
+ (ThisScb->Vcb->NotifyCount != 0) &&
+ (ThisScb->AttributeName.Length != 0) &&
+ (ThisScb->AttributeTypeCode == TypeCode)) {
+
+ NtfsReportDirNotify( IrpContext,
+ ThisFcb->Vcb,
+ &FileObject->FileName,
+ LastFileNameOffset,
+ &ThisScb->AttributeName,
+ ((ARGUMENT_PRESENT( ThisLcb ) &&
+ ThisLcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &ThisLcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FILE_NOTIFY_CHANGE_STREAM_NAME,
+ FILE_ACTION_REMOVED_STREAM,
+ NULL );
+ }
+ }
+ }
+
+ //
+ // Get the next attribute.
+ //
+
+ MoreToGo = NtfsLookupNextAttributeByCode( IrpContext,
+ ThisFcb,
+ TypeCode,
+ &Context );
+ }
+
+ //
+ // We've deleted one set of attributes. Check to see if
+ // we're done deleting or whether we need to start deleting
+ // another type code.
+ //
+
+#ifndef _CAIRO_
+ break;
+#else // _CAIRO_
+ if (TypeCode == $PROPERTY_SET) {
+ break;
+ } else {
+
+ NtfsCleanupAttributeContext( &Context );
+ TypeCode = $PROPERTY_SET;
+
+ }
+#endif // _CAIRO_
+ }
+
+ } finally {
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsReplaceAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN PSCB ThisScb,
+ IN PLCB ThisLcb,
+ IN LONGLONG AllocationSize
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to replace an existing attribute with
+ an attribute of the given allocation size. This routine will
+ handle the case whether the existing attribute is resident
+ or non-resident and the resulting attribute is resident or
+ non-resident.
+
+ There are two cases to consider. The first is the case where the
+ attribute is currently non-resident. In this case we will always
+ leave the attribute non-resident regardless of the new allocation
+ size. The argument being that the file will probably be used
+ as it was before. In this case we will add or delete allocation.
+ The second case is where the attribute is currently resident. In
+ This case we will remove the old attribute and add a new one.
+
+Arguments:
+
+ IrpSp - This is the Irp stack location for this request.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ ThisScb - This is the Scb for the given attribute.
+
+ ThisLcb - This is the Lcb via which this file is created. It
+ is used to propagate compression info.
+
+ AllocationSize - This is the new allocation size.
+
+Return Value:
+
+ None. This routine will raise.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReplaceAttribute: Entered\n") );
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Initialize the Scb if needed.
+ //
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, ThisScb, NULL );
+ }
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+
+ //
+ // Expand quota to the expected state.
+ //
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, ThisScb );
+
+ //
+ // If the attribute is resident, simply remove the old attribute and create
+ // a new one.
+ //
+
+ if (FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Find the attribute on the disk.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext,
+ ThisScb,
+ NULL,
+ &AttrContext );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ ThisFcb,
+ TRUE,
+ FALSE,
+ &AttrContext );
+
+ //
+ // Set all the attribute sizes to zero.
+ //
+
+ ThisScb->ValidDataToDisk =
+ ThisScb->Header.AllocationSize.QuadPart =
+ ThisScb->Header.ValidDataLength.QuadPart =
+ ThisScb->Header.FileSize.QuadPart = 0;
+ ThisScb->TotalAllocated = 0;
+
+ //
+ // Create a stream file for the attribute in order to
+ // truncate the cache. Set the initialized bit in
+ // the Scb so we don't go to disk, but clear it afterwords.
+ //
+
+ NtfsCreateInternalAttributeStream( IrpContext, ThisScb, FALSE );
+
+ CcSetFileSizes( ThisScb->FileObject,
+ (PCC_FILE_SIZES)&ThisScb->Header.AllocationSize );
+
+ //
+ // Call our create attribute routine.
+ //
+
+ NtfsCreateAttribute( IrpContext,
+ IrpSp,
+ ThisFcb,
+ ThisScb,
+ ThisLcb,
+ AllocationSize,
+ TRUE );
+
+ //
+ // Otherwise the attribute will stay non-resident, we simply need to
+ // add or remove allocation.
+ //
+
+ } else {
+
+ //
+ // Create an internal attribute stream for the file.
+ //
+
+ NtfsCreateInternalAttributeStream( IrpContext,
+ ThisScb,
+ FALSE );
+
+ AllocationSize = LlClustersFromBytes( ThisScb->Vcb, AllocationSize );
+ AllocationSize = LlBytesFromClusters( ThisScb->Vcb, AllocationSize );
+
+ //
+ // Set the file size and valid data size to zero.
+ //
+
+ ThisScb->ValidDataToDisk = 0;
+ ThisScb->Header.ValidDataLength = Li0;
+ ThisScb->Header.FileSize = Li0;
+
+ DebugTrace( 0, Dbg, ("AllocationSize -> %016I64x\n", AllocationSize) );
+
+ //
+ // Write these changes to the file
+ //
+
+ //
+ // If the attribute is currently compressed then go ahead and discard
+ // all of the allocation.
+ //
+
+ if (FlagOn( ThisScb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ NtfsDeleteAllocation( IrpContext,
+ ThisScb->FileObject,
+ ThisScb,
+ 0,
+ MAXLONGLONG,
+ TRUE,
+ TRUE );
+
+ //
+ // Checkpoint the current transaction so we have these clusters
+ // available again.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // If the user doesn't want this stream to be compressed then
+ // remove the entire stream and recreate it non-compressed.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ) ||
+ FlagOn( IrpSp->Parameters.Create.Options, FILE_NO_COMPRESSION )) {
+
+ NtfsLookupAttributeForScb( IrpContext,
+ ThisScb,
+ NULL,
+ &AttrContext );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ ThisFcb,
+ TRUE,
+ FALSE,
+ &AttrContext );
+
+ //
+ // Call our create attribute routine.
+ //
+
+ NtfsCreateAttribute( IrpContext,
+ IrpSp,
+ ThisFcb,
+ ThisScb,
+ ThisLcb,
+ AllocationSize,
+ TRUE );
+ }
+ }
+
+ //
+ // Now if the file allocation is being increased then we need to only add allocation
+ // to the attribute
+ //
+
+ if (ThisScb->Header.AllocationSize.QuadPart < AllocationSize) {
+
+ NtfsAddAllocation( IrpContext,
+ ThisScb->FileObject,
+ ThisScb,
+ LlClustersFromBytes( ThisScb->Vcb, ThisScb->Header.AllocationSize.QuadPart ),
+ LlClustersFromBytes( ThisScb->Vcb, AllocationSize - ThisScb->Header.AllocationSize.QuadPart ),
+ FALSE );
+ //
+ // Otherwise the allocation is being decreased so we need to delete some allocation
+ //
+
+ } else if (ThisScb->Header.AllocationSize.QuadPart > AllocationSize) {
+
+ NtfsDeleteAllocation( IrpContext,
+ ThisScb->FileObject,
+ ThisScb,
+ LlClustersFromBytes( ThisScb->Vcb, AllocationSize ),
+ MAXLONGLONG,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // We always unitialize the cache size to zero and write the new
+ // file size to disk.
+ //
+
+ NtfsWriteFileSizes( IrpContext,
+ ThisScb,
+ &ThisScb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ CcSetFileSizes( ThisScb->FileObject,
+ (PCC_FILE_SIZES)&ThisScb->Header.AllocationSize );
+
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsReplaceAttribute );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ DebugTrace( -1, Dbg, ("NtfsReplaceAttribute: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOpenAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PVCB Vcb,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG LastFileNameOffset,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ IN SHARE_MODIFICATION_TYPE ShareModificationType,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN ULONG CcbFlags,
+ IN PVOID NetworkInfo OPTIONAL,
+ IN OUT PSCB *ThisScb,
+ OUT PCCB *ThisCcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does the work of creating the Scb and updating the
+ ShareAccess in the Fcb. It also initializes the Scb if neccessary
+ and creates Ccb. Its final job is to set the file object type of
+ open.
+
+Arguments:
+
+ IrpSp - This is the stack location for this volume. We use it to get the
+ file object, granted access and share access for this open.
+
+ Vcb - Vcb for this volume.
+
+ ThisLcb - This is the Lcb to the Fcb for the file being opened. Not present
+ if this is an open by id.
+
+ ThisFcb - This is the Fcb for this file.
+
+ LastFileNameOffset - This is the offset in the full path of the final component.
+
+ AttrName - This is the attribute name to open.
+
+ AttrTypeCode - This is the type code for the attribute being opened.
+
+ ShareModificationType - This indicates how we should modify the
+ current share modification on the Fcb.
+
+ TypeOfOpen - This indicates how this attribute is being opened.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+ NetworkInfo - If specified then this open is on behalf of a fast query
+ and we don't want to increment the counts or modify the share
+ access on the file.
+
+ ThisScb - If this points to a non-NULL value, it is the Scb to use. Otherwise we
+ store the Scb we create here.
+
+ ThisCcb - Address to store address of created Ccb.
+
+Return Value:
+
+ NTSTATUS - Indicating the outcome of opening this attribute.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ BOOLEAN RemoveShareAccess = FALSE;
+ ACCESS_MASK GrantedAccess;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenAttribute: Entered\n") );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Remember the granted access.
+ //
+
+ GrantedAccess = IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess;
+
+ //
+ // Create the Scb for this attribute if it doesn't exist.
+ //
+
+ if (*ThisScb == NULL) {
+
+ DebugTrace( 0, Dbg, ("Looking for Scb\n") );
+
+ *ThisScb = NtfsCreateScb( IrpContext,
+ ThisFcb,
+ AttrTypeCode,
+ &AttrName,
+ FALSE,
+ NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("ThisScb -> %08lx\n", *ThisScb) );
+ DebugTrace( 0, Dbg, ("ThisLcb -> %08lx\n", ThisLcb) );
+
+ //
+ // If this Scb is delete pending, we return an error.
+ //
+
+ if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_DELETE_ON_CLOSE )) {
+
+ DebugTrace( 0, Dbg, ("Scb delete is pending\n") );
+
+ Status = STATUS_DELETE_PENDING;
+ try_return( NOTHING );
+ }
+
+ //
+ // Skip all of the operations below if the user is doing a fast
+ // path open.
+ //
+
+ if (!ARGUMENT_PRESENT( NetworkInfo )) {
+
+ //
+ // If this caller wanted a filter oplock and the cleanup count
+ // is non-zero then fail the request.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER )) {
+
+ if (SafeNodeType( *ThisScb ) != NTFS_NTC_SCB_DATA) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ try_return( NOTHING );
+
+ //
+ // This must be the only open on the file and the requested
+ // access must be FILE_READ/WRITE_ATTRIBUTES and the
+ // share access must share with everyone.
+ //
+
+ } else if (((*ThisScb)->CleanupCount != 0) ||
+ (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
+ ~(FILE_READ_ATTRIBUTES))) ||
+ ((IrpSp->Parameters.Create.ShareAccess &
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)) !=
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE))) {
+
+ Status = STATUS_OPLOCK_NOT_GRANTED;
+ try_return( NOTHING );
+ }
+ }
+
+ //
+ // Update the share access structure.
+ //
+
+ //
+ // Case on the requested share modification value.
+ //
+
+ switch (ShareModificationType) {
+
+ case UpdateShareAccess :
+
+ DebugTrace( 0, Dbg, ("Updating share access\n") );
+
+ IoUpdateShareAccess( IrpSp->FileObject,
+ &(*ThisScb)->ShareAccess );
+ break;
+
+ case SetShareAccess :
+
+ DebugTrace( 0, Dbg, ("Setting share access\n") );
+
+ //
+ // This case is when this is the first open for the file
+ // and we simply set the share access.
+ //
+
+ IoSetShareAccess( GrantedAccess,
+ IrpSp->Parameters.Create.ShareAccess,
+ IrpSp->FileObject,
+ &(*ThisScb)->ShareAccess );
+ break;
+
+ default:
+
+ DebugTrace( 0, Dbg, ("Checking share access\n") );
+
+ //
+ // For this case we need to check the share access and
+ // fail this request if access is denied.
+ //
+
+ if (!NT_SUCCESS( Status = IoCheckShareAccess( GrantedAccess,
+ IrpSp->Parameters.Create.ShareAccess,
+ IrpSp->FileObject,
+ &(*ThisScb)->ShareAccess,
+ TRUE ))) {
+
+ try_return( NOTHING );
+ }
+ }
+
+ RemoveShareAccess = TRUE;
+
+ //
+ // If this happens to be the first time we see write access on this
+ // Scb, then we need to remember it, and check if we have a disk full
+ // condition.
+ //
+
+ if (IrpSp->FileObject->WriteAccess &&
+ !FlagOn((*ThisScb)->ScbState, SCB_STATE_WRITE_ACCESS_SEEN) &&
+ (SafeNodeType( (*ThisScb) ) == NTFS_NTC_SCB_DATA)) {
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ //
+ // Does this Scb have reserved space that causes us to exceed the free
+ // space on the volume?
+ //
+
+ if (((*ThisScb)->ScbType.Data.TotalReserved != 0) &&
+ ((LlClustersFromBytes(Vcb, (*ThisScb)->ScbType.Data.TotalReserved) + Vcb->TotalReserved) >
+ Vcb->FreeClusters)) {
+
+ NtfsReleaseReservedClusters( Vcb );
+
+ try_return( Status = STATUS_DISK_FULL );
+ }
+
+ //
+ // Otherwise tally in the reserved space now for this Scb, and
+ // remember that we have seen write access.
+ //
+
+ Vcb->TotalReserved += LlClustersFromBytes(Vcb, (*ThisScb)->ScbType.Data.TotalReserved);
+ SetFlag( (*ThisScb)->ScbState, SCB_STATE_WRITE_ACCESS_SEEN );
+
+ NtfsReleaseReservedClusters( Vcb );
+
+ }
+
+ //
+ // Create the Ccb and put the remaining name in it.
+ //
+
+ *ThisCcb = NtfsCreateCcb( IrpContext,
+ ThisFcb,
+ (BOOLEAN) (AttrTypeCode == $INDEX_ALLOCATION),
+ ThisFcb->EaModificationCount,
+ CcbFlags,
+ IrpSp->FileObject->FileName,
+ LastFileNameOffset );
+
+ //
+ // Link the Ccb into the Lcb.
+ //
+
+ if (ARGUMENT_PRESENT( ThisLcb )) {
+
+ NtfsLinkCcbToLcb( IrpContext, *ThisCcb, ThisLcb );
+ }
+
+ //
+ // Update the Fcb delete counts if necessary.
+ //
+
+ if (RemoveShareAccess) {
+
+ //
+ // Update the count in the Fcb and store a flag in the Ccb
+ // if the user is not sharing the file for deletes. We only
+ // set these values if the user is accessing the file
+ // for read/write/delete access. The I/O system ignores
+ // the sharing mode unless the file is opened with one
+ // of these accesses.
+ //
+
+ if (FlagOn( GrantedAccess, NtfsAccessDataFlags )
+ && !FlagOn( IrpSp->Parameters.Create.ShareAccess,
+ FILE_SHARE_DELETE )) {
+
+ ThisFcb->FcbDenyDelete += 1;
+ SetFlag( (*ThisCcb)->Flags, CCB_FLAG_DENY_DELETE );
+ }
+
+ //
+ // Do the same for the file delete count for any user
+ // who opened the file as a file and requested delete access.
+ //
+
+ if (FlagOn( (*ThisCcb)->Flags, CCB_FLAG_OPEN_AS_FILE )
+ && FlagOn( GrantedAccess,
+ DELETE )) {
+
+ ThisFcb->FcbDeleteFile += 1;
+ SetFlag( (*ThisCcb)->Flags, CCB_FLAG_DELETE_FILE );
+ }
+ }
+
+ //
+ // Let our cleanup routine undo the share access change now.
+ //
+
+ RemoveShareAccess = FALSE;
+
+ //
+ // Increment the cleanup and close counts
+ //
+
+ NtfsIncrementCleanupCounts( *ThisScb,
+ ThisLcb,
+ BooleanFlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
+
+ NtfsIncrementCloseCounts( *ThisScb,
+ BooleanFlagOn( ThisFcb->FcbState, FCB_STATE_PAGING_FILE ),
+ (BOOLEAN) IsFileObjectReadOnly( IrpSp->FileObject ));
+
+ if (TypeOfOpen != UserDirectoryOpen) {
+
+ DebugTrace( 0, Dbg, ("Updating Vcb and File object for user open\n") );
+
+ //
+ // Set the section object pointer if this is a data Scb
+ //
+
+ IrpSp->FileObject->SectionObjectPointer = &(*ThisScb)->NonpagedScb->SegmentObject;
+ }
+
+ //
+ // Set the file object type.
+ //
+
+ NtfsSetFileObject( IrpSp->FileObject,
+ TypeOfOpen,
+ *ThisScb,
+ *ThisCcb );
+
+ //
+ // If this is a non-cached open and there is a data section and
+ // there are only non-cached opens then go ahead and try to
+ // delete the section.
+ //
+
+ if (FlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
+ ((*ThisScb)->AttributeTypeCode == $DATA) &&
+ ((*ThisScb)->CleanupCount == (*ThisScb)->NonCachedCleanupCount) &&
+ ((*ThisScb)->NonpagedScb->SegmentObject.DataSectionObject != NULL) &&
+ ((*ThisScb)->NonpagedScb->SegmentObject.ImageSectionObject == NULL) &&
+ ((*ThisScb)->CompressionUnit == 0) &&
+ MmCanFileBeTruncated( &(*ThisScb)->NonpagedScb->SegmentObject, NULL )) {
+
+ //
+ // Only do this in the Fsp so we have enough stack space for the flush.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Flush and purge the stream.
+ //
+
+ NtfsFlushAndPurgeScb( IrpContext,
+ *ThisScb,
+ (ARGUMENT_PRESENT( ThisLcb ) ?
+ ThisLcb->Scb :
+ NULL) );
+ }
+
+ //
+ // Check if we should request a filter oplock.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER )) {
+
+ FsRtlOplockFsctrl( &(*ThisScb)->ScbType.Data.Oplock,
+ IrpContext->OriginatingIrp,
+ 1 );
+ }
+
+ //
+ // Mark the Scb if this is a temporary file.
+ //
+
+ if (FlagOn( (*ThisScb)->ScbState, SCB_STATE_TEMPORARY ) ||
+ (FlagOn( ThisFcb->Info.FileAttributes, FILE_ATTRIBUTE_TEMPORARY ) &&
+ FlagOn( (*ThisCcb)->Flags, CCB_FLAG_OPEN_AS_FILE ))) {
+
+ SetFlag( (*ThisScb)->ScbState, SCB_STATE_TEMPORARY );
+ SetFlag( IrpSp->FileObject->Flags, FO_TEMPORARY_FILE );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsOpenAttribute );
+
+ //
+ // Back out local actions on error.
+ //
+
+ if (AbnormalTermination()
+ && RemoveShareAccess) {
+
+ IoRemoveShareAccess( IrpSp->FileObject, &(*ThisScb)->ShareAccess );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttribute: Status -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsBackoutFailedOpens (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PFCB ThisFcb,
+ IN PSCB ThisScb OPTIONAL,
+ IN PCCB ThisCcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called during an open that has failed after
+ modifying in-memory structures. We will repair the following
+ structures.
+
+ Vcb - Decrement the open counts. Check if we locked the volume.
+
+ ThisFcb - Restore he Share Access fields and decrement open counts.
+
+ ThisScb - Decrement the open counts.
+
+ ThisCcb - Remove from the Lcb and delete.
+
+Arguments:
+
+ FileObject - This is the file object for this open.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ ThisScb - This is the Scb for the given attribute.
+
+ ThisCcb - This is the Ccb for this open.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsBackoutFailedOpens: Entered\n") );
+
+ //
+ // If there is an Scb and Ccb, we remove the share access from the
+ // Fcb. We also remove all of the open and unclean counts incremented
+ // by us.
+ //
+
+ if (ARGUMENT_PRESENT( ThisScb )
+ && ARGUMENT_PRESENT( ThisCcb )) {
+
+ PLCB Lcb;
+ PVCB Vcb = ThisFcb->Vcb;
+
+ //
+ // Remove this Ccb from the Lcb.
+ //
+
+ Lcb = ThisCcb->Lcb;
+ NtfsUnlinkCcbFromLcb( IrpContext, ThisCcb );
+
+ //
+ // Check if we need to remove the share access for this open.
+ //
+
+ IoRemoveShareAccess( FileObject, &ThisScb->ShareAccess );
+
+ //
+ // Modify the delete counts in the Fcb.
+ //
+
+ if (FlagOn( ThisCcb->Flags, CCB_FLAG_DELETE_FILE )) {
+
+ ThisFcb->FcbDeleteFile -= 1;
+ ClearFlag( ThisCcb->Flags, CCB_FLAG_DELETE_FILE );
+ }
+
+ if (FlagOn( ThisCcb->Flags, CCB_FLAG_DENY_DELETE )) {
+
+ ThisFcb->FcbDenyDelete -= 1;
+ ClearFlag( ThisCcb->Flags, CCB_FLAG_DENY_DELETE );
+ }
+
+ //
+ // Decrement the cleanup and close counts
+ //
+
+ NtfsDecrementCleanupCounts( ThisScb,
+ Lcb,
+ BooleanFlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ));
+
+ NtfsDecrementCloseCounts( IrpContext,
+ ThisScb,
+ Lcb,
+ (BOOLEAN) BooleanFlagOn(ThisFcb->FcbState, FCB_STATE_PAGING_FILE),
+ (BOOLEAN) IsFileObjectReadOnly( FileObject ),
+ TRUE );
+
+ //
+ // Now clean up the Ccb.
+ //
+
+ NtfsDeleteCcb( IrpContext, ThisFcb, &ThisCcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsBackoutFailedOpens: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsUpdateScbFromMemory (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN POLD_SCB_SNAPSHOT ScbSizes
+ )
+
+/*++
+
+Routine Description:
+
+ All of the information from the attribute is stored in the snapshot. We process
+ this data identically to NtfsUpdateScbFromAttribute.
+
+Arguments:
+
+ Scb - This is the Scb to update.
+
+ ScbSizes - This contains the sizes to store in the scb.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateScbFromMemory: Entered\n") );
+
+ //
+ // Check whether this is resident or nonresident
+ //
+
+ if (ScbSizes->Resident) {
+
+ Scb->Header.AllocationSize.QuadPart = ScbSizes->FileSize;
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
+
+ Scb->Header.ValidDataLength =
+ Scb->Header.FileSize = Scb->Header.AllocationSize;
+ }
+
+ Scb->Header.AllocationSize.LowPart =
+ QuadAlign( Scb->Header.AllocationSize.LowPart );
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+
+ //
+ // Set the resident flag in the Scb.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
+
+ } else {
+
+ VCN FileClusters;
+ VCN AllocationClusters;
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
+
+ Scb->Header.ValidDataLength.QuadPart = ScbSizes->ValidDataLength;
+ Scb->Header.FileSize.QuadPart = ScbSizes->FileSize;
+ Scb->ValidDataToDisk = ScbSizes->ValidDataLength;
+ }
+
+ Scb->TotalAllocated = ScbSizes->TotalAllocated;
+ Scb->Header.AllocationSize.QuadPart = ScbSizes->AllocationSize;
+
+ ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT );
+
+ //
+ // Get the size of the compression unit.
+ //
+
+ ASSERT((ScbSizes->CompressionUnit == 0) ||
+ (ScbSizes->CompressionUnit == NTFS_CLUSTERS_PER_COMPRESSION));
+
+ if ((ScbSizes->CompressionUnit != 0) &&
+ (ScbSizes->CompressionUnit < 31)) {
+ Scb->CompressionUnit = BytesFromClusters( Scb->Vcb,
+ 1 << ScbSizes->CompressionUnit );
+ Scb->CompressionUnitShift = ScbSizes->CompressionUnit;
+ }
+
+ ASSERT( Scb->CompressionUnit == 0
+ || Scb->AttributeTypeCode == $INDEX_ROOT
+ || NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode )
+ );
+
+ //
+ // Compute the clusters for the file and its allocation.
+ //
+
+ AllocationClusters = LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart );
+
+ if (Scb->CompressionUnit == 0) {
+
+ FileClusters = LlClustersFromBytes(Scb->Vcb, Scb->Header.FileSize.QuadPart);
+
+ } else {
+
+ FileClusters = Scb->Header.FileSize.QuadPart + Scb->CompressionUnit - 1;
+ FileClusters &= ~(Scb->CompressionUnit - 1);
+ }
+
+ //
+ // If allocated clusters are greater than file clusters, mark
+ // the Scb to truncate on close.
+ //
+
+ if (AllocationClusters > FileClusters) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+ }
+ }
+
+ Scb->AttributeFlags = ScbSizes->AttributeFlags;
+
+ if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_SPARSE )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_ACCESS_DENIED, NULL, NULL );
+ }
+
+ if (FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+
+ //
+ // If the attribute is resident, then we will use our current
+ // default.
+ //
+
+ if (Scb->CompressionUnit == 0) {
+
+ Scb->CompressionUnit = BytesFromClusters( Scb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION );
+ Scb->CompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
+
+ ASSERT( (Scb->AttributeTypeCode == $INDEX_ROOT) ||
+ NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
+ }
+ }
+
+ //
+ // If the compression unit is non-zero or this is a resident file
+ // then set the flag in the common header for the Modified page writer.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ SetFlag( Scb->ScbState,
+ SCB_STATE_UNNAMED_DATA | SCB_STATE_FILE_SIZE_LOADED | SCB_STATE_HEADER_INITIALIZED );
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateScbFromMemory: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsOplockPrePostIrp (
+ IN PVOID Context,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs any neccessary work before STATUS_PENDING is
+ returned with the Fsd thread. This routine is called within the
+ filesystem and by the oplock package. This routine will update
+ the originating Irp in the IrpContext and release all of the Fcbs and
+ paging io resources in the IrpContext.
+
+Arguments:
+
+ Context - Pointer to the IrpContext to be queued to the Fsp
+
+ Irp - I/O Request Packet
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIRP_CONTEXT IrpContext;
+ POPLOCK_CLEANUP OplockCleanup;
+ PFCB Fcb;
+
+ PAGED_CODE();
+
+ IrpContext = (PIRP_CONTEXT) Context;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ IrpContext->OriginatingIrp = Irp;
+ OplockCleanup = IrpContext->Union.OplockCleanup;
+
+ //
+ // Adjust the filename strings as needed.
+ //
+
+ if (OplockCleanup->ExactCaseName.Buffer != NULL) {
+
+ RtlCopyMemory( OplockCleanup->FullFileName.Buffer,
+ OplockCleanup->ExactCaseName.Buffer,
+ OplockCleanup->ExactCaseName.MaximumLength );
+ }
+
+ //
+ // Free any buffer we allocated.
+ //
+
+ if ((OplockCleanup->FullFileName.Buffer != NULL) &&
+ (OplockCleanup->OriginalFileName.Buffer != OplockCleanup->FullFileName.Buffer)) {
+
+ NtfsFreePool( OplockCleanup->FullFileName.Buffer );
+ OplockCleanup->FullFileName.Buffer = NULL;
+ }
+
+ //
+ // Set the file name in the file object back to it's original value.
+ //
+
+ OplockCleanup->FileObject->FileName = OplockCleanup->OriginalFileName;
+
+ Fcb = IrpContext->FcbWithPagingExclusive;
+ if (Fcb != NULL) {
+
+ if (Fcb->NodeTypeCode == NTFS_NTC_FCB) {
+
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+ }
+
+ //
+ // Release all of the Fcb's in the exlusive lists.
+ //
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ //
+ // Mark that we've already returned pending to the user
+ //
+
+ IoMarkIrpPending( Irp );
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsCheckExistingFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PLCB ThisLcb OPTIONAL,
+ IN PFCB ThisFcb,
+ IN ULONG CcbFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to check the desired access on an existing file
+ against the ACL's and the read-only status of the file. If we fail on
+ the access check, that routine will raise. Otherwise we will return a
+ status to indicate success or the failure cause. This routine will access
+ and update the PreviouslyGrantedAccess field in the security context.
+
+Arguments:
+
+ IrpSp - This is the Irp stack location for this open.
+
+ ThisLcb - This is the Lcb used to reach the Fcb to open.
+
+ ThisFcb - This is the Fcb where the open will occur.
+
+ CcbFlags - This is the flag field for the Ccb.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ BOOLEAN MaximumAllowed = FALSE;
+
+ PACCESS_STATE AccessState;
+
+ PAGED_CODE();
+
+ //
+ // Save a pointer to the access state for convenience.
+ //
+
+ AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
+
+ //
+ // Start by checking that there are no bits in the desired access that
+ // conflict with the read-only state of the file.
+ //
+
+ if (IsReadOnly( &ThisFcb->Info )) {
+
+ if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
+ FILE_WRITE_DATA
+ | FILE_APPEND_DATA
+ | FILE_ADD_SUBDIRECTORY
+ | FILE_DELETE_CHILD )) {
+
+ return STATUS_ACCESS_DENIED;
+
+ } else if (FlagOn( IrpSp->Parameters.Create.Options, FILE_DELETE_ON_CLOSE )) {
+
+ return STATUS_CANNOT_DELETE;
+ }
+ }
+
+ //
+ // Otherwise we need to check the requested access vs. the allowable
+ // access in the ACL on the file. We will want to remember if
+ // MAXIMUM_ALLOWED was requested and remove the invalid bits for
+ // a read-only file.
+ //
+
+ //
+ // Remember if maximum allowed was requested.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.SecurityContext->DesiredAccess,
+ MAXIMUM_ALLOWED )) {
+
+ MaximumAllowed = TRUE;
+ }
+
+ NtfsOpenCheck( IrpContext,
+ ThisFcb,
+ (((ThisLcb != NULL) && (ThisLcb != ThisFcb->Vcb->RootLcb))
+ ? ThisLcb->Scb->Fcb
+ : NULL),
+ IrpContext->OriginatingIrp );
+
+ //
+ // If this is a read-only file and we requested maximum allowed then
+ // remove the invalid bits.
+ //
+
+ if (MaximumAllowed
+ && IsReadOnly( &ThisFcb->Info )) {
+
+ ClearFlag( AccessState->PreviouslyGrantedAccess,
+ FILE_WRITE_DATA
+ | FILE_APPEND_DATA
+ | FILE_ADD_SUBDIRECTORY
+ | FILE_DELETE_CHILD );
+ }
+
+ //
+ // We do a check here to see if we conflict with the delete status on the
+ // file. Right now we check if there is already an opener who has delete
+ // access on the file and this opener doesn't allow delete access.
+ // We can skip this test if the opener is not requesting read, write or
+ // delete access.
+ //
+
+ if (ThisFcb->FcbDeleteFile != 0
+ && FlagOn( AccessState->PreviouslyGrantedAccess, NtfsAccessDataFlags )
+ && !FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_DELETE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsOpenAttributeInExistingFile: Exit\n") );
+ return STATUS_SHARING_VIOLATION;
+ }
+
+ //
+ // We do a check here to see if we conflict with the delete status on the
+ // file. If we are opening the file and requesting delete, then there can
+ // be no current handles which deny delete.
+ //
+
+ if (ThisFcb->FcbDenyDelete != 0
+ && FlagOn( AccessState->PreviouslyGrantedAccess, DELETE )
+ && FlagOn( CcbFlags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ return STATUS_SHARING_VIOLATION;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsBreakBatchOplock (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PIO_STACK_LOCATION IrpSp,
+ IN PFCB ThisFcb,
+ IN UNICODE_STRING AttrName,
+ IN ATTRIBUTE_TYPE_CODE AttrTypeCode,
+ OUT PSCB *ThisScb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called for each open of an existing attribute to
+ check for current batch oplocks on the file. We will also check
+ whether we will want to flush and purge this stream in the case
+ where only non-cached handles remain on the file. We only want
+ to do that in an Fsp thread because we will require every bit
+ of stack we can get.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ IrpSp - This is the stack location for this open.
+
+ ThisFcb - This is the Fcb for the file being opened.
+
+ AttrName - This is the attribute name in case we need to create
+ an Scb.
+
+ AttrTypeCode - This is the attribute type code to use to create
+ the Scb.
+
+ ThisScb - Address to store the Scb if found or created.
+
+Return Value:
+
+ NTSTATUS - Will be either STATUS_SUCCESS or STATUS_PENDING.
+
+--*/
+
+{
+ BOOLEAN ScbExisted;
+ PSCB NextScb;
+ PLIST_ENTRY Links;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsBreakBatchOplock: Entered\n") );
+
+ //
+ // In general we will just break the batch oplock for the stream we
+ // are trying to open. However if we are trying to delete the file
+ // and someone has a batch oplock on a different stream which
+ // will cause our open to fail then we need to try to break those
+ // batch oplocks. Likewise if we are opening a stream and won't share
+ // with a file delete then we need to break any batch oplocks on the main
+ // stream of the file.
+ //
+
+ //
+ // Consider the case where we are opening a stream and there is a
+ // batch oplock on the main data stream.
+ //
+
+ if (AttrName.Length != 0) {
+
+ if (ThisFcb->FcbDeleteFile != 0 &&
+ !FlagOn( IrpSp->Parameters.Create.ShareAccess, FILE_SHARE_DELETE )) {
+
+ Links = ThisFcb->ScbQueue.Flink;
+
+ while (Links != &ThisFcb->ScbQueue) {
+
+ NextScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ if (NextScb->AttributeTypeCode == $DATA &&
+ NextScb->AttributeName.Length == 0) {
+
+ if (FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
+
+ //
+ // We remember if a batch oplock break is underway for the
+ // case where the sharing check fails.
+ //
+
+ Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
+
+ //
+ // We wait on the oplock.
+ //
+
+ if (FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
+ Irp,
+ (PVOID) IrpContext,
+ NtfsOplockComplete,
+ NtfsOplockPrePostIrp ) == STATUS_PENDING) {
+
+ return STATUS_PENDING;
+ }
+ }
+
+ break;
+ }
+
+ Links = Links->Flink;
+ }
+ }
+
+ //
+ // Now consider the case where we are opening the main stream and want to
+ // delete the file but an opener on a stream is preventing us.
+ //
+
+ } else if (ThisFcb->FcbDenyDelete != 0 &&
+ FlagOn( IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess,
+ MAXIMUM_ALLOWED | DELETE )) {
+
+ //
+ // Find all of the other data Scb and check their oplock status.
+ //
+
+ Links = ThisFcb->ScbQueue.Flink;
+
+ while (Links != &ThisFcb->ScbQueue) {
+
+ NextScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ if (NextScb->AttributeTypeCode == $DATA &&
+ NextScb->AttributeName.Length != 0) {
+
+ if (FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
+
+ //
+ // We remember if a batch oplock break is underway for the
+ // case where the sharing check fails.
+ //
+
+ Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
+
+ //
+ // We wait on the oplock.
+ //
+
+ if (FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
+ Irp,
+ (PVOID) IrpContext,
+ NtfsOplockComplete,
+ NtfsOplockPrePostIrp ) == STATUS_PENDING) {
+
+ return STATUS_PENDING;
+ }
+
+ Irp->IoStatus.Information = 0;
+ }
+ }
+
+ Links = Links->Flink;
+ }
+ }
+
+ //
+ // We try to find the Scb for this file.
+ //
+
+ *ThisScb = NtfsCreateScb( IrpContext,
+ ThisFcb,
+ AttrTypeCode,
+ &AttrName,
+ FALSE,
+ &ScbExisted );
+
+ //
+ // If there was a previous Scb, we examine the oplocks.
+ //
+
+ if (ScbExisted &&
+ (SafeNodeType( *ThisScb ) == NTFS_NTC_SCB_DATA)) {
+
+ //
+ // If we have to flush and purge then we want to be in the Fsp.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP ) &&
+ FlagOn( IrpSp->FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING ) &&
+ ((*ThisScb)->CleanupCount == (*ThisScb)->NonCachedCleanupCount) &&
+ ((*ThisScb)->NonpagedScb->SegmentObject.DataSectionObject != NULL)) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ if (FsRtlCurrentBatchOplock( &(*ThisScb)->ScbType.Data.Oplock )) {
+
+ //
+ // If the handle count is greater than 1 then fail this
+ // open now.
+ //
+
+ if (FlagOn( IrpSp->Parameters.Create.Options, FILE_RESERVE_OPFILTER ) &&
+ ((*ThisScb)->CleanupCount > 1)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_OPLOCK_NOT_GRANTED, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Breaking batch oplock\n") );
+
+ //
+ // We remember if a batch oplock break is underway for the
+ // case where the sharing check fails.
+ //
+
+ Irp->IoStatus.Information = FILE_OPBATCH_BREAK_UNDERWAY;
+
+ if (FsRtlCheckOplock( &(*ThisScb)->ScbType.Data.Oplock,
+ Irp,
+ (PVOID) IrpContext,
+ NtfsOplockComplete,
+ NtfsOplockPrePostIrp ) == STATUS_PENDING) {
+
+ return STATUS_PENDING;
+ }
+
+ Irp->IoStatus.Information = 0;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsBreakBatchOplock: Exit - %08lx\n", STATUS_SUCCESS) );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsCompleteLargeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PLCB Lcb OPTIONAL,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN BOOLEAN CreateFileCase,
+ IN BOOLEAN DeleteOnClose
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when we need to add more allocation to a stream
+ being opened. This stream could have been reallocated or created with
+ this call but we didn't allocate all of the space in the main path.
+
+Arguments:
+
+ Irp - This is the Irp for this open operation.
+
+ Lcb - This is the Lcb used to reach the stream being opened. Won't be
+ specified in the open by ID case.
+
+ Scb - This is the Scb for the stream being opened.
+
+ Ccb - This is the Ccb for the this user handle.
+
+ CreateFileCase - Indicates if we reallocated or created this stream.
+
+ DeleteOnClose - Indicates if this handle requires delete on close.
+
+Return Value:
+
+ NTSTATUS - the result of this operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+ FILE_ALLOCATION_INFORMATION AllInfo;
+
+ PAGED_CODE();
+
+ //
+ // Commit the current transaction and free all resources.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Free any exclusive paging I/O resource.
+ //
+
+ if (IrpContext->FcbWithPagingExclusive != NULL) {
+ NtfsReleasePagingIo( IrpContext, IrpContext->FcbWithPagingExclusive );
+ }
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB) CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CALL_SELF );
+ AllInfo.AllocationSize = Irp->Overlay.AllocationSize;
+ Status = IoSetInformation( IoGetCurrentIrpStackLocation( Irp )->FileObject,
+ FileAllocationInformation,
+ sizeof( FILE_ALLOCATION_INFORMATION ),
+ &AllInfo );
+
+ //
+ // Success! We will reacquire the Vcb quickly to undo the
+ // actions taken above to block access to the new file/attribute.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ NtfsAcquireExclusiveVcb( IrpContext, Scb->Vcb, TRUE );
+
+ //
+ // Enable access to new file.
+ //
+
+ if (CreateFileCase) {
+
+ Scb->Fcb->LinkCount = 1;
+
+ if (ARGUMENT_PRESENT( Lcb )) {
+
+ ClearFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
+
+ ClearFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
+ }
+ }
+
+ //
+ // Enable access to new attribute.
+ //
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ //
+ // If this is the DeleteOnClose case, we mark the Scb and Lcb
+ // appropriately.
+ //
+
+ if (DeleteOnClose) {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_DELETE_ON_CLOSE );
+ }
+
+ NtfsReleaseVcb( IrpContext, Scb->Vcb );
+
+ //
+ // Else there was some sort of error, and we need to let cleanup
+ // and close execute, since when we complete Create with an error
+ // cleanup and close would otherwise never occur. Cleanup will
+ // delete or truncate a file or attribute as appropriate, based on
+ // how we left the Fcb/Lcb or Scb above.
+ //
+
+ } else {
+
+ NtfsIoCallSelf( IrpContext,
+ IoGetCurrentIrpStackLocation( Irp )->FileObject,
+ IRP_MJ_CLEANUP );
+
+ NtfsIoCallSelf( IrpContext,
+ IoGetCurrentIrpStackLocation( Irp )->FileObject,
+ IRP_MJ_CLOSE );
+ }
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CALL_SELF );
+ return Status;
+}
+
+#ifdef _CAIRO_
+NTSTATUS
+NtfsTryOpenFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PFCB *CurrentFcb,
+ IN FILE_REFERENCE FileReference
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to open a file by its file segment number.
+ We need to verify that this file Id exists. This code is
+ patterned after open by Id.
+
+Arguments:
+
+ Vcb - Vcb for this volume.
+
+ CurrentFcb - Address of Fcb pointer. Store the Fcb we find here.
+
+ FileReference - This is the file Id for the file to open the
+ sequence number is ignored.
+
+Return Value:
+
+ NTSTATUS - Indicates the result of this create file operation.
+
+Note:
+
+ If the status is successful then the FCB is returned with its reference
+ count incremented and the FCB held exclusive.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ LONGLONG MftOffset;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PBCB Bcb = NULL;
+
+ PFCB ThisFcb;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+ BOOLEAN AcquiredMft = TRUE;
+ BOOLEAN ThisFcbFree = TRUE;
+
+ PAGED_CODE();
+
+ ASSERT( *CurrentFcb == NULL );
+
+ //
+ // Do not bother with system files.
+ //
+
+ //
+ // If this is a system fcb then return.
+ //
+
+ if (NtfsSegmentNumber( &FileReference ) < FIRST_USER_FILE_NUMBER &&
+ NtfsSegmentNumber( &FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
+
+ return STATUS_NOT_FOUND;
+ }
+
+ //
+ // Calculate the offset in the MFT.
+ //
+
+ MftOffset = NtfsSegmentNumber( &FileReference );
+
+ MftOffset = Int64ShllMod32(MftOffset, Vcb->MftShift);
+
+ //
+ // Acquire the MFT shared so it cannot shrink on us.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Vcb->MftScb );
+
+ try {
+
+ if (MftOffset >= Vcb->MftScb->Header.FileSize.QuadPart) {
+
+ DebugTrace( 0, Dbg, ("File Id doesn't lie within Mft\n") );
+
+ Status = STATUS_END_OF_FILE;
+ leave;
+ }
+
+ NtfsReadMftRecord( IrpContext,
+ Vcb,
+ &FileReference,
+ &Bcb,
+ &FileRecord,
+ NULL );
+
+ //
+ // This file record better be in use, have a matching sequence number and
+ // be the primary file record for this file.
+ //
+
+ if (!FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE )
+ || (*((PLONGLONG)&FileRecord->BaseFileRecordSegment) != 0)) {
+
+ Status = STATUS_NOT_FOUND;
+ leave;
+ }
+
+ //
+ // Get the current sequence number.
+ //
+
+ FileReference.SequenceNumber = FileRecord->SequenceNumber;
+
+ NtfsUnpinBcb( &Bcb );
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ //
+ // We know that it is safe to continue the open. We start by creating
+ // an Fcb for this file. It is possible that the Fcb exists.
+ // We create the Fcb first, if we need to update the Fcb info structure
+ // we copy the one from the index entry. We look at the Fcb to discover
+ // if it has any links, if it does then we make this the last Fcb we
+ // reached. If it doesn't then we have to clean it up from here.
+ //
+
+ ThisFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ FileReference,
+ FALSE,
+ TRUE,
+ NULL );
+
+ //
+ // ReferenceCount the fcb so it does no go away.
+ //
+
+ ThisFcb->ReferenceCount += 1;
+
+ //
+ // Release the mft and fcb table before acquiring the FCB exclusive.
+ //
+
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredMft = FALSE;
+ AcquiredFcbTable = FALSE;
+
+ NtfsAcquireFcbWithPaging( IrpContext, ThisFcb, FALSE );
+ ThisFcbFree = FALSE;
+
+ //
+ // Skip any deleted files.
+ //
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_FILE_DELETED )) {
+
+ DbgPrint( "NtfsTryOpenFcb: Deleted fcb found. Fcb = %lx\n", ThisFcb );
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ ASSERT(ThisFcb->ReferenceCount > 0);
+ ThisFcb->ReferenceCount--;
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ NtfsTeardownStructures( IrpContext,
+ ThisFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ &ThisFcbFree );
+
+ //
+ // Release the fcb if it has not been deleted.
+ //
+
+ if (!ThisFcbFree) {
+ NtfsReleaseFcb( IrpContext, ThisFcb );
+ ThisFcbFree = TRUE;
+ }
+
+ //
+ // Teardown may generate a transaction clean it up.
+ //
+
+ NtfsCompleteRequest( &IrpContext, NULL, Status );
+
+ Status = STATUS_NOT_FOUND;
+ leave;
+ }
+
+ //
+ // Store this Fcb into our caller's parameter and remember to
+ // to show we acquired it.
+ //
+
+ *CurrentFcb = ThisFcb;
+ ThisFcbFree = TRUE;
+
+
+ //
+ // If the Fcb Info field needs to be initialized, we do so now.
+ // We read this information from the disk.
+ //
+
+ if (!FlagOn( ThisFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ ThisFcb,
+ NULL,
+ NULL );
+
+ }
+
+ } finally {
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ NtfsUnpinBcb( &Bcb );
+
+ if (AcquiredMft) {
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+ }
+
+ if (!ThisFcbFree) {
+ NtfsReleaseFcb( IrpContext, ThisFcb );
+ }
+ }
+
+ return Status;
+
+}
+#endif // _CAIRO_
diff --git a/private/ntos/cntfs/devctrl.c b/private/ntos/cntfs/devctrl.c
new file mode 100644
index 000000000..9dd3f780e
--- /dev/null
+++ b/private/ntos/cntfs/devctrl.c
@@ -0,0 +1,271 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ DevCtrl.c
+
+Abstract:
+
+ This module implements the Device Control routines for Ntfs called by
+ the dispatch driver.
+
+Author:
+
+ Gary Kimura [GaryKi] 28-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_DEVCTRL)
+
+//
+// Local procedure prototypes
+//
+
+NTSTATUS
+DeviceControlCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonDeviceControl)
+#pragma alloc_text(PAGE, NtfsFsdDeviceControl)
+#endif
+
+
+NTSTATUS
+NtfsFsdDeviceControl (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Device Control.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdDeviceControl\n") );
+
+ //
+ // Call the common Device Control routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonDeviceControl( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdDeviceControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonDeviceControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Device Control called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PIO_STACK_LOCATION NextIrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonDeviceControl\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // The only type of opens we accept are user volume opens.
+ //
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonDeviceControl -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Get the next stack location, and copy over the stack parameter
+ // information
+ //
+
+ NextIrpSp = IoGetNextIrpStackLocation( Irp );
+
+ *NextIrpSp = *IrpSp;
+
+ //
+ // Send the request.
+ //
+
+ Status = IoCallDriver(Vcb->TargetDeviceObject, Irp);
+
+ //
+ // Free the IrpContext and return to the caller.
+ //
+
+ NtfsDeleteIrpContext( &IrpContext );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonDeviceControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+DeviceControlCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+{
+ UNREFERENCED_PARAMETER( DeviceObject );
+ UNREFERENCED_PARAMETER( Contxt );
+
+ //
+ // Add the hack-o-ramma to fix formats.
+ //
+
+ if ( Irp->PendingReturned ) {
+
+ IoMarkIrpPending( Irp );
+ }
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/private/ntos/cntfs/deviosup.c b/private/ntos/cntfs/deviosup.c
new file mode 100644
index 000000000..506c0de82
--- /dev/null
+++ b/private/ntos/cntfs/deviosup.c
@@ -0,0 +1,7934 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ DevIoSup.c
+
+Abstract:
+
+ This module implements the low lever disk read/write support for Ntfs
+
+Author:
+
+ Brian Andrew BrianAn
+ Tom Miller TomM
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+#include <ntdskreg.h>
+#include <ntddft.h>
+
+#ifdef SYSCACHE
+//
+// Tom's nifty Scsi Analyzer for syscache
+//
+
+#define ScsiLines (4096)
+ULONG NextLine = 0;
+ULONG ScsiAnal[ScsiLines][4];
+
+VOID
+CallDisk (
+ PIRP_CONTEXT IrpContext,
+ PDEVICE_OBJECT DeviceObject,
+ PIRP Irp,
+ IN ULONG Single
+ )
+
+{
+ PIO_STACK_LOCATION IrpSp;
+ PSCB Scb;
+ ULONG i;
+
+ IrpSp = IoGetCurrentIrpStackLocation(Irp);
+
+ Scb = (PSCB)IrpContext->OriginatingIrp->Tail.Overlay.OriginalFileObject->FsContext;
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE) ||
+ (IrpSp->MajorFunction != IRP_MJ_WRITE)) {
+ IoCallDriver( DeviceObject, Irp );
+ return;
+ }
+
+ i = NextLine++;
+ if (i >= ScsiLines) {
+ i = 0;
+ NextLine = 1;
+ }
+
+ ScsiAnal[i][0] = IrpSp->Parameters.Write.ByteOffset.LowPart;
+ ScsiAnal[i][2] = IrpSp->Parameters.Write.Length;
+ ScsiAnal[i][3] = *(PULONG)NtfsMapUserBuffer(Irp);
+ IrpSp = IoGetNextIrpStackLocation(Irp);
+ ScsiAnal[i][1] = IrpSp->Parameters.Write.ByteOffset.LowPart;
+
+ IoCallDriver( DeviceObject, Irp );
+
+ if (Single) {
+
+ KeWaitForSingleObject( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL );
+
+ ScsiAnal[i][2] += Irp->IoStatus.Status << 16;
+ }
+
+
+}
+
+#endif SYSCACHE
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_DEVIOSUP)
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_DEVIOSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('DFtN')
+
+//
+// We need a special test for success, whenever we are seeing if we should
+// hot fix, because the FT driver returns one of two success codes if a read or
+// write failed to only one of the members.
+//
+
+#define FT_SUCCESS(STS) (NT_SUCCESS(STS) && \
+ ((STS) != STATUS_FT_READ_RECOVERY_FROM_BACKUP) && \
+ ((STS) != STATUS_FT_WRITE_RECOVERY))
+
+
+#if DBG || defined(NTFS_ALLOW_COMPRESSED)
+BOOLEAN NtfsHotFixTrace = FALSE;
+#define HotFixTrace(X) {if (NtfsHotFixTrace) KdPrint(X);}
+#else
+#define HotFixTrace(X) {NOTHING;}
+#endif
+
+#ifdef SYSCACHE
+BOOLEAN NtfsStopOnDecompressError = TRUE;
+#else
+BOOLEAN NtfsStopOnDecompressError = FALSE;
+#endif
+
+
+#define CollectDiskIoStats(VCB,SCB,FUNCTION,COUNT) { \
+ PFILESYSTEM_STATISTICS FsStats = &(VCB)->Statistics[KeGetCurrentProcessorNumber()]; \
+ ASSERT((SCB)->Fcb != NULL); \
+ if (NtfsIsTypeCodeUserData( (SCB)->AttributeTypeCode ) && \
+ !FlagOn( (SCB)->Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) { \
+ if ((FUNCTION) == IRP_MJ_WRITE) { \
+ FsStats->UserDiskWrites += (COUNT); \
+ } else { \
+ FsStats->UserDiskReads += (COUNT); \
+ } \
+ } else if ((SCB) != (VCB)->LogFileScb) { \
+ if ((FUNCTION) == IRP_MJ_WRITE) { \
+ FsStats->MetaDataDiskWrites += (COUNT); \
+ } else { \
+ FsStats->MetaDataDiskReads += (COUNT); \
+ } \
+ } \
+}
+
+//
+// Define a context for holding the context the compression state
+// for buffers.
+//
+
+typedef struct COMPRESSION_CONTEXT {
+
+ //
+ // Pointer to allocated compression buffer, and its length
+ //
+
+ PUCHAR CompressionBuffer;
+ ULONG CompressionBufferLength;
+
+ //
+ // Saved fields from originating Irp
+ //
+
+ PMDL SavedMdl;
+ PVOID SavedUserBuffer;
+
+ //
+ // System Buffer pointer and offset in the System (user's) buffer
+ //
+
+ PVOID SystemBuffer;
+ ULONG SystemBufferOffset;
+
+ //
+ // IoRuns array in use. This array may be extended one time
+ // in NtfsPrepareBuffers.
+ //
+
+ PIO_RUN IoRuns;
+ ULONG AllocatedRuns;
+
+ //
+ // Workspace pointer, so that cleanup can occur in the caller.
+ //
+
+ PVOID WorkSpace;
+
+ //
+ // Write acquires the Scb.
+ //
+
+ BOOLEAN ScbAcquired;
+
+} COMPRESSION_CONTEXT, *PCOMPRESSION_CONTEXT;
+
+//
+// Local support routines
+//
+
+VOID
+NtfsAllocateCompressionBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ThisScb,
+ IN PIRP Irp,
+ IN PCOMPRESSION_CONTEXT CompressionContext,
+ IN OUT PULONG CompressionBufferLength
+ );
+
+VOID
+NtfsDeallocateCompressionBuffer (
+ IN PIRP Irp,
+ IN PCOMPRESSION_CONTEXT CompressionContext
+ );
+
+LONG
+NtfsCompressionFilter (
+ IN PIRP_CONTEXT IrpContext,
+ IN PEXCEPTION_POINTERS ExceptionPointer
+ );
+
+ULONG
+NtfsPrepareBuffers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PVBO StartingVbo,
+ IN ULONG ByteCount,
+ OUT PULONG NumberRuns,
+ OUT PCOMPRESSION_CONTEXT CompressionContext,
+ IN ULONG CompressedStream
+ );
+
+NTSTATUS
+NtfsFinishBuffers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PVBO StartingVbo,
+ IN ULONG ByteCount,
+ IN PCOMPRESSION_CONTEXT CompressionContext,
+ IN ULONG CompressedStream
+ );
+
+VOID
+NtfsMultipleAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP MasterIrp,
+ IN ULONG MultipleIrpCount,
+ IN PIO_RUN IoRuns
+ );
+
+VOID
+NtfsSingleAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN LBO StartingLbo,
+ IN ULONG ByteCount,
+ IN PIRP Irp
+ );
+
+VOID
+NtfsWaitSync (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+NTSTATUS
+NtfsMultiAsyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsMultiSyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsSingleAsyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsSingleSyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsPagingFileCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID MasterIrp
+ );
+
+BOOLEAN
+NtfsVerifyAndRevertUsaBlock (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PVOID Buffer,
+ IN ULONG Length,
+ IN LONGLONG FileOffset
+ );
+
+VOID
+NtfsSingleNonAlignedSync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PUCHAR Buffer,
+ IN VBO Vbo,
+ IN LBO Lbo,
+ IN ULONG ByteCount,
+ IN PIRP Irp
+ );
+
+BOOLEAN
+NtfsIsReadAheadThread (
+ );
+
+VOID
+NtfsFixDataError (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP MasterIrp,
+ IN ULONG MultipleIrpCount,
+ IN PIO_RUN IoRuns
+ );
+
+VOID
+NtfsPostHotFix(
+ IN PIRP Irp,
+ IN PLONGLONG BadVbo,
+ IN LONGLONG BadLbo,
+ IN ULONG ByteLength,
+ IN BOOLEAN DelayIrpCompletion
+ );
+
+VOID
+NtfsPerformHotFix (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+BOOLEAN
+NtfsGetReservedBuffer (
+ IN PFCB ThisFcb,
+ OUT PVOID *Buffer,
+ OUT PULONG Length,
+ IN UCHAR Need2
+ );
+
+BOOLEAN
+NtfsFreeReservedBuffer (
+ IN PVOID Buffer
+ );
+
+//****#ifdef ALLOC_PRAGMA
+//****#pragma alloc_text(PAGE, NtfsCreateMdlAndBuffer)
+//****#pragma alloc_text(PAGE, NtfsFixDataError)
+//****#pragma alloc_text(PAGE, NtfsMapUserBuffer)
+//****#pragma alloc_text(PAGE, NtfsMultipleAsync)
+//****#pragma alloc_text(PAGE, NtfsNonCachedIo)
+//****#pragma alloc_text(PAGE, NtfsPrepareBuffers)
+//****#pragma alloc_text(PAGE, NtfsFinishBuffers)
+//****#pragma alloc_text(PAGE, NtfsNonCachedNonAlignedIo)
+//****#pragma alloc_text(PAGE, NtfsPerformHotFix)
+//****#pragma alloc_text(PAGE, NtfsSingleAsync)
+//****#pragma alloc_text(PAGE, NtfsSingleNonAlignedSync)
+//****#pragma alloc_text(PAGE, NtfsTransformUsaBlock)
+//****#pragma alloc_text(PAGE, NtfsVerifyAndRevertUsaBlock)
+//****#pragma alloc_text(PAGE, NtfsVolumeDasdIo)
+//****#pragma alloc_text(PAGE, NtfsWaitSync)
+//****#pragma alloc_text(PAGE, NtfsWriteClusters)
+//****#endif
+
+
+VOID
+NtfsLockUserBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PIRP Irp,
+ IN LOCK_OPERATION Operation,
+ IN ULONG BufferLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine locks the specified buffer for the specified type of
+ access. The file system requires this routine since it does not
+ ask the I/O system to lock its buffers for direct I/O. This routine
+ may only be called from the Fsd while still in the user context.
+
+Arguments:
+
+ Irp - Pointer to the Irp for which the buffer is to be locked.
+
+ Operation - IoWriteAccess for read operations, or IoReadAccess for
+ write operations.
+
+ BufferLength - Length of user buffer.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PMDL Mdl = NULL;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ if (Irp->MdlAddress == NULL) {
+
+ //
+ // Allocate the Mdl, and Raise if we fail.
+ //
+
+ Mdl = IoAllocateMdl( Irp->UserBuffer, BufferLength, FALSE, FALSE, Irp );
+
+ if (Mdl == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Now probe the buffer described by the Irp. If we get an exception,
+ // deallocate the Mdl and return the appropriate "expected" status.
+ //
+
+ try {
+
+ MmProbeAndLockPages( Mdl, Irp->RequestorMode, Operation );
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ NTSTATUS Status;
+
+ Status = GetExceptionCode();
+
+ IoFreeMdl( Mdl );
+ Irp->MdlAddress = NULL;
+
+ NtfsRaiseStatus( IrpContext,
+ FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER,
+ NULL,
+ NULL );
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+PVOID
+NtfsMapUserBuffer (
+ IN OUT PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine conditionally maps the user buffer for the current I/O
+ request in the specified mode. If the buffer is already mapped, it
+ just returns its address.
+
+Arguments:
+
+ Irp - Pointer to the Irp for the request.
+
+Return Value:
+
+ Mapped address
+
+--*/
+
+{
+ PVOID SystemBuffer;
+ PAGED_CODE();
+
+ //
+ // If there is no Mdl, then we must be in the Fsd, and we can simply
+ // return the UserBuffer field from the Irp.
+ //
+
+ if (Irp->MdlAddress == NULL) {
+
+ return Irp->UserBuffer;
+
+ } else {
+
+ //
+ // MM can return NULL if there are no system ptes.
+ //
+
+ if ((SystemBuffer = MmGetSystemAddressForMdl( Irp->MdlAddress )) == NULL) {
+
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ return SystemBuffer;
+ }
+}
+
+
+NTSTATUS
+NtfsVolumeDasdIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the non-cached disk io for Volume Dasd, as described
+ in its parameters.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Vcb - Supplies the Vcb for the volume
+
+ StartingVbo - Starting offset within the file for the operation.
+
+ ByteCount - The lengh of the operation.
+
+Return Value:
+
+ The result of the Io operation. STATUS_PENDING if this is an asynchronous
+ open.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsVolumeDasdIo\n") );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("StartingVbo = %016I64x\n", StartingVbo) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", ByteCount) );
+
+ //
+ // For nonbuffered I/O, we need the buffer locked in all
+ // cases.
+ //
+ // This call may raise. If this call succeeds and a subsequent
+ // condition is raised, the buffers are unlocked automatically
+ // by the I/O system when the request is completed, via the
+ // Irp->MdlAddress field.
+ //
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ (IrpContext->MajorFunction == IRP_MJ_READ) ?
+ IoWriteAccess : IoReadAccess,
+ ByteCount );
+
+ //
+ // Read the data and wait for the results
+ //
+
+ NtfsSingleAsync( IrpContext,
+ Vcb->TargetDeviceObject,
+ StartingVbo,
+ ByteCount,
+ Irp );
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ //
+ // We can get rid of the IrpContext now.
+ //
+
+ IrpContext->Union.NtfsIoContext = NULL;
+ NtfsDeleteIrpContext( &IrpContext );
+
+ DebugTrace( -1, Dbg, ("NtfsVolumeDasdIo -> STATUS_PENDING\n") );
+ return STATUS_PENDING;
+ }
+
+ NtfsWaitSync( IrpContext );
+
+ DebugTrace( -1, Dbg, ("NtfsVolumeDasdIo -> %08lx\n", Irp->IoStatus.Status) );
+
+ return Irp->IoStatus.Status;
+}
+
+
+VOID
+NtfsPagingFileIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the non-cached disk io described in its parameters.
+ This routine nevers blocks, and should only be used with the paging
+ file since no completion processing is performed.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Scb - Supplies the file to act on.
+
+ StartingVbo - Starting offset within the file for the operation.
+
+ ByteCount - The lengh of the operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // Declare some local variables for enumeration through the
+ // runs of the file.
+ //
+
+ LONGLONG ThisClusterCount;
+ ULONG ThisByteCount;
+
+ LCN ThisLcn;
+ LBO ThisLbo;
+
+ VCN ThisVcn;
+
+ PIRP AssocIrp;
+ PIRP ContextIrp;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG BufferOffset;
+ PDEVICE_OBJECT DeviceObject;
+ PFILE_OBJECT FileObject;
+ PDEVICE_OBJECT OurDeviceObject;
+
+ PVCB Vcb = Scb->Vcb;
+
+ LIST_ENTRY AssociatedIrps;
+ ULONG AssociatedIrpCount;
+
+ ULONG ClusterOffset;
+ VCN BeyondLastCluster;
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME ); //****ignore verify for now
+
+ //
+ // Check whether we want to set the low order bit in the Irp to pass
+ // as a context value to the completion routine.
+ //
+
+ ContextIrp = Irp;
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY )) {
+
+ SetFlag( ((ULONG) ContextIrp), 0x1 );
+ }
+
+ //
+ // Check that we are sector aligned.
+ //
+
+ ASSERT( (((ULONG)StartingVbo) & (Vcb->BytesPerSector - 1)) == 0 );
+
+ //
+ // Initialize some locals.
+ //
+
+ BufferOffset = 0;
+ ClusterOffset = (ULONG) StartingVbo & Vcb->ClusterMask;
+ DeviceObject = Vcb->TargetDeviceObject;
+ BeyondLastCluster = LlClustersFromBytes( Vcb, StartingVbo + ByteCount );
+
+ //
+ // Try to lookup the first run. If there is just a single run,
+ // we may just be able to pass it on.
+ //
+
+ ThisVcn = LlClustersFromBytesTruncate( Vcb, StartingVbo );
+
+ //
+ // Paging files reads/ writes should always be correct. If we didn't
+ // find the allocation, something bad has happened.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ ThisVcn,
+ &ThisLcn,
+ &ThisClusterCount,
+ NULL,
+ NULL,
+ NULL,
+ NULL )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Adjust from Lcn to Lbo.
+ //
+
+ ThisLbo = LlBytesFromClusters( Vcb, ThisLcn ) + ClusterOffset;
+
+ //
+ // Now set up the Irp->IoStatus. It will be modified by the
+ // multi-completion routine in case of error or verify required.
+ //
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = ByteCount;
+
+ //
+ // Save the FileObject.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FileObject = IrpSp->FileObject;
+ OurDeviceObject = IrpSp->DeviceObject;
+
+ //
+ // See if the write covers a single valid run, and if so pass
+ // it on.
+ //
+
+ if (ThisVcn + ThisClusterCount >= BeyondLastCluster) {
+
+ DebugTrace( 0, Dbg, ("Passing Irp on to Disk Driver\n") );
+
+ //
+ // We use our stack location to store request information in a
+ // rather strange way, to give us enough context to post a
+ // hot fix on error. It's ok, because it is our stack location!
+ //
+
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = ThisLbo;
+ IrpSp->Parameters.Read.Key = ((ULONG)StartingVbo);
+
+ //
+ // Set up the completion routine address in our stack frame.
+ // This is only invoked on error or cancel, and just copies
+ // the error Status into master irp's iosb.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ &NtfsPagingFileCompletionRoutine,
+ ContextIrp,
+ (BOOLEAN)!FlagOn(Vcb->VcbState, VCB_STATE_NO_SECONDARY_AVAILABLE),
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location for the disk driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to do a read from the disk driver.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = ByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = ThisLbo;
+
+ //
+ // Issue the read/write request
+ //
+ // If IoCallDriver returns an error, it has completed the Irp
+ // and the error will be dealt with as a normal IO error.
+ //
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsPagingFileIo -> VOID\n") );
+ return;
+ }
+
+ //
+ // Loop while there are still byte writes to satisfy. Always keep the
+ // associated irp count one up, so that the master irp won't get
+ // completed prematurly.
+ //
+
+ try {
+
+ //
+ // We will allocate and initialize all of the Irps and then send
+ // them down to the driver. We will queue them off of our
+ // AssociatedIrp queue.
+ //
+
+ InitializeListHead( &AssociatedIrps );
+ AssociatedIrpCount = 0;
+
+ while (TRUE) {
+
+ //
+ // Reset this for unwinding purposes
+ //
+
+ AssocIrp = NULL;
+
+ //
+ // If next run is larger than we need, "ya get what you need".
+ //
+
+ ThisByteCount = BytesFromClusters( Vcb, (ULONG) ThisClusterCount ) - ClusterOffset;
+ if (ThisVcn + ThisClusterCount >= BeyondLastCluster) {
+
+ ThisByteCount = ByteCount;
+ }
+
+ //
+ // Now that we have properly bounded this piece of the
+ // transfer, it is time to read/write it.
+ //
+
+ AssocIrp = IoMakeAssociatedIrp( Irp, (CCHAR)(DeviceObject->StackSize + 1) );
+
+ if (AssocIrp == NULL) {
+
+ Irp->IoStatus.Information = 0;
+
+ //
+ // If we have an error then complete the Irp and return.
+ //
+
+ NtfsCompleteRequest( NULL, &Irp, STATUS_INSUFFICIENT_RESOURCES );
+ try_return( NOTHING );
+ }
+
+ //
+ // Now add the Irp to our queue of Irps.
+ //
+
+ InsertTailList( &AssociatedIrps, &AssocIrp->Tail.Overlay.ListEntry );
+
+ //
+ // Allocate and build a partial Mdl for the request.
+ //
+
+ {
+ PMDL Mdl;
+
+ Mdl = IoAllocateMdl( (PCHAR)Irp->UserBuffer + BufferOffset,
+ ThisByteCount,
+ FALSE,
+ FALSE,
+ AssocIrp );
+
+ if (Mdl == NULL) {
+
+ Irp->IoStatus.Information = 0;
+ NtfsCompleteRequest( NULL, &Irp, STATUS_INSUFFICIENT_RESOURCES );
+ try_return( NOTHING );
+ }
+
+ IoBuildPartialMdl( Irp->MdlAddress,
+ Mdl,
+ (PCHAR)Irp->UserBuffer + BufferOffset,
+ ThisByteCount );
+ }
+
+ AssociatedIrpCount += 1;
+
+ //
+ // Get the first IRP stack location in the associated Irp
+ //
+
+ IoSetNextIrpStackLocation( AssocIrp );
+ IrpSp = IoGetCurrentIrpStackLocation( AssocIrp );
+
+ //
+ // We use our stack location to store request information in a
+ // rather strange way, to give us enough context to post a
+ // hot fix on error. It's ok, because it is our stack location!
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = ThisByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = ThisLbo;
+ IrpSp->Parameters.Read.Key = ((ULONG)StartingVbo);
+ IrpSp->FileObject = FileObject;
+ IrpSp->DeviceObject = OurDeviceObject;
+
+ //
+ // Set up the completion routine address in our stack frame.
+ // This is only invoked on error or cancel, and just copies
+ // the error Status into master irp's iosb.
+ //
+
+ IoSetCompletionRoutine( AssocIrp,
+ &NtfsPagingFileCompletionRoutine,
+ ContextIrp,
+ (BOOLEAN)!FlagOn(Vcb->VcbState, VCB_STATE_NO_SECONDARY_AVAILABLE),
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location in the associated Irp for the disk
+ // driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( AssocIrp );
+
+ //
+ // Setup the Stack location to do a read from the disk driver.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = ThisByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = ThisLbo;
+
+ //
+ // Now adjust everything for the next pass through the loop but
+ // break out now if all the irps have been created for the io.
+ //
+
+ if (ByteCount == ThisByteCount) {
+
+ break;
+ }
+
+ StartingVbo += ThisByteCount;
+ BufferOffset += ThisByteCount;
+ ByteCount -= ThisByteCount;
+ ClusterOffset = 0;
+ ThisVcn += ThisClusterCount;
+
+ //
+ // Try to lookup the next run (if we are not done).
+ // Paging files reads/ writes should always be correct. If
+ // we didn't find the allocation, something bad has happened.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ ThisVcn,
+ &ThisLcn,
+ &ThisClusterCount,
+ NULL,
+ NULL,
+ NULL,
+ NULL )) {;
+
+ NtfsBugCheck( 0, 0, 0 );
+ }
+
+ ThisLbo = LlBytesFromClusters( Vcb, ThisLcn );
+
+ } // while (ByteCount != 0)
+
+ //
+ // We have now created all of the Irps that we need. We will set the
+ // Irp count in the master Irp and then fire off the associated irps.
+ //
+
+ Irp->AssociatedIrp.IrpCount = AssociatedIrpCount;
+
+ while (!IsListEmpty( &AssociatedIrps )) {
+
+ AssocIrp = CONTAINING_RECORD( AssociatedIrps.Flink,
+ IRP,
+ Tail.Overlay.ListEntry );
+
+ RemoveHeadList( &AssociatedIrps );
+
+ (VOID) IoCallDriver( DeviceObject, AssocIrp );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsPagingFileIo );
+
+ //
+ // In the case of an error we must clean up any of the associated Irps
+ // we have created.
+ //
+
+ while (!IsListEmpty( &AssociatedIrps )) {
+
+ AssocIrp = CONTAINING_RECORD( AssociatedIrps.Flink,
+ IRP,
+ Tail.Overlay.ListEntry );
+
+ RemoveHeadList( &AssociatedIrps );
+
+ if (AssocIrp->MdlAddress != NULL) {
+
+ IoFreeMdl( AssocIrp->MdlAddress );
+ AssocIrp->MdlAddress = NULL;
+ }
+
+ IoFreeIrp( AssocIrp );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsPagingFileIo -> VOID\n") );
+ return;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+NtfsAllocateCompressionBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ThisScb,
+ IN PIRP Irp,
+ IN PCOMPRESSION_CONTEXT CompressionContext,
+ IN OUT PULONG CompressionBufferLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates a compression buffer of the desired length, and
+ describes it with an Mdl. It updates the Irp to describe the new buffer.
+ Note that whoever allocates the CompressionContext must initially zero it.
+
+Arguments:
+
+ ThisScb - The stream where the IO is taking place.
+
+ Irp - Irp for the current request
+
+ CompressionContext - Pointer to the compression context for the request.
+
+ CompressionBufferLength - Supplies length required for the compression buffer.
+ Returns length available.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PMDL Mdl;
+
+ //
+ // If no compression buffer is allocated, or it is too small, then we must
+ // take action here.
+ //
+
+ if (*CompressionBufferLength > CompressionContext->CompressionBufferLength) {
+
+ //
+ // If there already is an Mdl, then there must also be a compression
+ // buffer (since we are part of main-line processing), and we must
+ // free these first.
+ //
+
+ if (CompressionContext->SavedMdl != NULL) {
+
+ //
+ // Restore the byte count for which the Mdl was created, and free it.
+ //
+
+ Irp->MdlAddress->ByteCount = CompressionContext->CompressionBufferLength;
+
+ NtfsDeleteMdlAndBuffer( Irp->MdlAddress,
+ CompressionContext->CompressionBuffer );
+
+ //
+ // Restore the Mdl and UserBuffer fields in the Irp.
+ //
+
+ Irp->MdlAddress = CompressionContext->SavedMdl;
+ Irp->UserBuffer = CompressionContext->SavedUserBuffer;
+ CompressionContext->SavedMdl = NULL;
+ CompressionContext->CompressionBuffer = NULL;
+ }
+
+ CompressionContext->CompressionBufferLength = *CompressionBufferLength;
+
+ //
+ // Allocate the compression buffer or raise
+ //
+
+ NtfsCreateMdlAndBuffer( IrpContext,
+ ThisScb,
+ (UCHAR) ((IrpContext->MajorFunction == IRP_MJ_WRITE) ? 1 : 0),
+ &CompressionContext->CompressionBufferLength,
+ &Mdl,
+ &CompressionContext->CompressionBuffer );
+
+ //
+ // Finally save the Mdl and Buffer fields from the Irp, and replace
+ // with the ones we just allocated.
+ //
+
+ CompressionContext->SavedMdl = Irp->MdlAddress;
+ CompressionContext->SavedUserBuffer = Irp->UserBuffer;
+ Irp->MdlAddress = Mdl;
+ Irp->UserBuffer = CompressionContext->CompressionBuffer;
+ }
+
+ //
+ // Update the caller's length field in all cases.
+ //
+
+ *CompressionBufferLength = CompressionContext->CompressionBufferLength;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+NtfsDeallocateCompressionBuffer (
+ IN PIRP Irp,
+ IN PCOMPRESSION_CONTEXT CompressionContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine peforms all necessary cleanup for a compressed I/O, as described
+ by the compression context.
+
+Arguments:
+
+ Irp - Irp for the current request
+
+ CompressionContext - Pointer to the compression context for the request.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // If there is a saved mdl, then we have to restore the original
+ // byte count it was allocated with and free it. Then restore the
+ // Irp fields we modified.
+ //
+
+ if (CompressionContext->SavedMdl != NULL) {
+
+ Irp->MdlAddress->ByteCount = CompressionContext->CompressionBufferLength;
+
+ NtfsDeleteMdlAndBuffer( Irp->MdlAddress,
+ CompressionContext->CompressionBuffer );
+ } else {
+
+ NtfsDeleteMdlAndBuffer( NULL,
+ CompressionContext->CompressionBuffer );
+ }
+
+ //
+ // If there is a saved mdl, then we have to restore the original
+ // byte count it was allocated with and free it. Then restore the
+ // Irp fields we modified.
+ //
+
+ if (CompressionContext->SavedMdl != NULL) {
+
+ Irp->MdlAddress = CompressionContext->SavedMdl;
+ Irp->UserBuffer = CompressionContext->SavedUserBuffer;
+ }
+
+ //
+ // If the IoRuns array was extended, deallocate that.
+ //
+
+ if (CompressionContext->AllocatedRuns != NTFS_MAX_PARALLEL_IOS) {
+ NtfsFreePool( CompressionContext->IoRuns );
+ }
+
+ //
+ // If there is a work space structure allocated, free it.
+ //
+
+ if (CompressionContext->WorkSpace != NULL) {
+ NtfsDeleteMdlAndBuffer( NULL, CompressionContext->WorkSpace );
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+LONG
+NtfsCompressionFilter (
+ IN PIRP_CONTEXT IrpContext,
+ IN PEXCEPTION_POINTERS ExceptionPointer
+ )
+
+{
+ UNREFERENCED_PARAMETER( IrpContext );
+ UNREFERENCED_PARAMETER( ExceptionPointer );
+
+ ASSERT(NT_SUCCESS(STATUS_INVALID_USER_BUFFER));
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+
+//
+// Internal support routine
+//
+
+ULONG
+NtfsPrepareBuffers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PVBO StartingVbo,
+ IN ULONG ByteCount,
+ OUT PULONG NumberRuns,
+ OUT PCOMPRESSION_CONTEXT CompressionContext,
+ IN ULONG CompressedStream
+ )
+
+/*++
+
+Routine Description:
+
+ This routine prepares the buffers for a noncached transfer, and fills
+ in the IoRuns array to describe all of the separate transfers which must
+ take place.
+
+ For compressed reads, the exact size of the compressed data is
+ calculated by scanning the run information, and a buffer is allocated
+ to receive the compressed data.
+
+ For compressed writes, an estimate is made on how large of a compressed
+ buffer will be required. Then the compression is performed, as much as
+ possible, into the compressed buffer which was allocated.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Scb - Supplies the stream file to act on.
+
+ StartingVbo - The starting point for the operation.
+
+ ByteCount - The lengh of the operation.
+
+ NumberRuns - Returns the number of runs filled in to the IoRuns array.
+
+ CompressionContext - Returns information related to the compression
+ to be cleaned up after the transfer.
+
+ CompressedStream - Supplies nonzero if I/O is to compressed stream
+
+Return Value:
+
+ Returns uncompressed bytes remaining to be processed, or 0 if all buffers
+ are prepared in the IoRuns and CompressionContext.
+
+--*/
+
+{
+ PVOID RangePtr;
+ ULONG Index;
+
+ LBO NextLbo;
+ LCN NextLcn;
+ VBO TempVbo;
+
+ ULONG NextLcnOffset;
+
+ VCN StartingVcn;
+
+ ULONG NextByteCount, ReturnByteCount;
+ LONGLONG NextClusterCount;
+
+ BOOLEAN NextIsAllocated;
+
+ ULONG BufferOffset;
+
+ ULONG StructureSize;
+ ULONG UsaOffset;
+ ULONG BytesInIoRuns;
+ BOOLEAN StopForUsa;
+
+ PVOID SystemBuffer;
+
+ ULONG CompressionUnit, CompressionUnitInClusters;
+ ULONG UncompressedOffset, CompressedOffset, CompressionUnitOffset;
+ ULONG CompressedSize, FinalCompressedSize;
+ LONGLONG FinalCompressedClusters;
+ ULONG LastStartUsaIoRun;
+
+ PIO_RUN IoRuns;
+
+ NTSTATUS Status;
+
+ VBO StartVbo = *StartingVbo;
+ PVCB Vcb = Scb->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // Initialize some locals.
+ //
+
+ IoRuns = CompressionContext->IoRuns;
+ *NumberRuns = 0;
+
+ //
+ // For nonbuffered I/O, we need the buffer locked in all
+ // cases.
+ //
+ // This call may raise. If this call succeeds and a subsequent
+ // condition is raised, the buffers are unlocked automatically
+ // by the I/O system when the request is completed, via the
+ // Irp->MdlAddress field.
+ //
+
+ ASSERT( FIELD_OFFSET(IO_STACK_LOCATION, Parameters.Read.Length) ==
+ FIELD_OFFSET(IO_STACK_LOCATION, Parameters.Write.Length) );
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ (IrpContext->MajorFunction == IRP_MJ_READ) ?
+ IoWriteAccess : IoReadAccess,
+ IoGetCurrentIrpStackLocation(Irp)->Parameters.Read.Length );
+
+ //
+ // First handle read/write case where compression not enabled or we want
+ // to read the raw compressed data or we are defragging and want to read
+ // the data as it is on disk.
+ //
+
+ if ((Scb->CompressionUnit == 0) ||
+ ((IrpContext->MajorFunction == IRP_MJ_READ) &&
+ (CompressedStream ||
+ ((Scb->Union.MoveData != NULL) && !FlagOn( Scb->ScbState, SCB_STATE_COMPRESSED ))))) {
+
+ //
+ // If this is a Usa-protected structure and we are reading, figure out
+ // what units we want to access it in.
+ //
+
+ BufferOffset = CompressionContext->SystemBufferOffset;
+ StructureSize = ByteCount;
+ if (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT) &&
+ (IrpContext->MajorFunction == IRP_MJ_READ)) {
+
+ //
+ // Get the the number of blocks, based on what type of stream it is.
+ // First check for Mft or Log file.
+ //
+
+ if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ ASSERT((Scb == Vcb->MftScb) || (Scb == Vcb->Mft2Scb));
+
+ StructureSize = Vcb->BytesPerFileRecordSegment;
+
+ //
+ // Otherwise it is an index, so we can get the count out of the Scb.
+ //
+
+ } else if (Scb->Header.NodeTypeCode != NTFS_NTC_SCB_DATA) {
+
+ StructureSize = Scb->ScbType.Index.BytesPerIndexBuffer;
+ }
+
+ //
+ // Remember the last index in the IO runs array which will allow us to
+ // read in a full USA structure in the worst case.
+ //
+
+ LastStartUsaIoRun = ClustersFromBytes( Vcb, StructureSize );
+
+ if (LastStartUsaIoRun > NTFS_MAX_PARALLEL_IOS) {
+
+ LastStartUsaIoRun = 0;
+
+ } else {
+
+ LastStartUsaIoRun = NTFS_MAX_PARALLEL_IOS - LastStartUsaIoRun;
+ }
+ }
+
+ BytesInIoRuns = 0;
+ UsaOffset = 0;
+ StopForUsa = FALSE;
+
+ while ((ByteCount != 0) && (*NumberRuns != NTFS_MAX_PARALLEL_IOS) && !StopForUsa) {
+
+ //
+ // Lookup next run
+ //
+
+ StartingVcn = Int64ShraMod32(StartVbo, Vcb->ClusterShift);
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ &RangePtr,
+ &Index );
+
+ ASSERT( NextIsAllocated
+ || FlagOn(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS)
+ || (Scb == Vcb->MftScb)
+ || CompressedStream );
+
+ //
+ // Adjust from NextLcn to Lbo. NextByteCount may overflow out of 32 bits
+ // but we will catch that below when we compare clusters.
+ //
+
+ NextLcnOffset = ((ULONG)StartVbo) & Vcb->ClusterMask;
+
+ NextByteCount = BytesFromClusters( Vcb, (ULONG)NextClusterCount ) - NextLcnOffset;
+
+ //
+ // Adjust if the Lcn offset isn't zero.
+ //
+
+ NextLbo = LlBytesFromClusters( Vcb, NextLcn );
+ NextLbo = NextLbo + NextLcnOffset;
+
+ //
+ // If next run is larger than we need, "ya get what you need".
+ // Note that after this we are guaranteed that the HighPart of
+ // NextByteCount is 0.
+ //
+
+ if ((ULONG)NextClusterCount >= ClustersFromBytes( Vcb, ByteCount + NextLcnOffset )) {
+
+ NextByteCount = ByteCount;
+ }
+
+ //
+ // If the byte count is zero then we will spin indefinitely. Raise
+ // corrupt here so the system doesn't hang.
+ //
+
+ if (NextByteCount == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // If this is a USA-protected structure, broken up in
+ // multiple runs, then we want to guarantee that we do
+ // not end up in the middle of a Usa-protected structure.
+ // Therefore, on the first run we will calculate the
+ // initial UsaOffset. Then in the worst case it can
+ // take the remaining four runs to finish the Usa structure.
+ //
+ // On the first subsequent run to complete a Usa structure,
+ // we set the count to end exactly on a Usa boundary.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT)) {
+
+ //
+ // So long as we know there are more IO runs left than the maximum
+ // number needed for the USA structure just maintain the current
+ // Usa offset.
+ //
+
+ if (*NumberRuns < LastStartUsaIoRun) {
+
+ UsaOffset = (UsaOffset + NextByteCount) & (StructureSize - 1);
+
+ //
+ // Now we will stop on the next Usa boundary, but we may not
+ // have it yet.
+ //
+
+ } else {
+
+ if (((NextByteCount + UsaOffset) >= StructureSize) &&
+ (IrpContext->MajorFunction == IRP_MJ_READ)) {
+
+ NextByteCount = ((NextByteCount + UsaOffset) & ~(StructureSize - 1)) -
+ (UsaOffset & (StructureSize - 1));
+ StopForUsa = TRUE;
+ }
+
+ UsaOffset += NextByteCount;
+ }
+ }
+
+ //
+ // Only fill in the run array if the run is allocated.
+ //
+
+ if (NextIsAllocated) {
+
+ //
+ // Now that we have properly bounded this piece of the
+ // transfer, it is time to write it.
+ //
+ // We remember each piece of a parallel run by saving the
+ // essential information in the IoRuns array. The tranfers
+ // are started up in parallel below.
+ //
+
+ IoRuns[*NumberRuns].StartingVbo = StartVbo;
+ IoRuns[*NumberRuns].StartingLbo = NextLbo;
+ IoRuns[*NumberRuns].BufferOffset = BufferOffset;
+ IoRuns[*NumberRuns].ByteCount = NextByteCount;
+ BytesInIoRuns += NextByteCount;
+ *NumberRuns += 1;
+ } else {
+
+ if ((IrpContext->MajorFunction == IRP_MJ_READ) && !CompressedStream) {
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+ RtlZeroMemory( Add2Ptr(SystemBuffer, BufferOffset), NextByteCount );
+ }
+ }
+
+ //
+ // Now adjust everything for the next pass through the loop.
+ //
+
+ StartVbo = StartVbo + NextByteCount;
+ BufferOffset += NextByteCount;
+ ByteCount -= NextByteCount;
+ }
+
+ return ByteCount;
+ }
+
+ ASSERT(Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA);
+
+ //
+ // Initialize the compression parameters.
+ //
+
+ CompressionUnit = Scb->CompressionUnit;
+ CompressionUnitInClusters = ClustersFromBytes(Vcb, CompressionUnit);
+ CompressionUnitOffset = ((ULONG)StartVbo) & (CompressionUnit - 1);
+ UncompressedOffset = 0;
+ BufferOffset = 0;
+
+ //
+ // We want to make sure and wait to get byte count and things correctly.
+ //
+
+ if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Handle the compressed read case.
+ //
+
+ if (IrpContext->MajorFunction == IRP_MJ_READ) {
+
+ //
+ // If we have not already mapped the user buffer, then do it.
+ //
+
+ if (CompressionContext->SystemBuffer == NULL) {
+ CompressionContext->SystemBuffer = NtfsMapUserBuffer( Irp );
+ }
+
+ BytesInIoRuns = 0;
+
+ //
+ // Adjust StartVbo and ByteCount by the offset.
+ //
+
+ ((ULONG)StartVbo) -= CompressionUnitOffset;
+ ByteCount += CompressionUnitOffset;
+
+ //
+ // Capture this value for maintaining the byte count to
+ // return.
+ //
+
+ ReturnByteCount = ByteCount;
+
+ //
+ // Now, the ByteCount we actually have to process has to
+ // be rounded up to the next compression unit.
+ //
+
+ ByteCount += CompressionUnit - 1;
+ ByteCount &= ~(CompressionUnit - 1);
+
+ //
+ // Make sure we never try to handle more than a LARGE_BUFFER_SIZE
+ // at once, forcing our caller to call back.
+ //
+
+ if (ByteCount > LARGE_BUFFER_SIZE) {
+ ByteCount = LARGE_BUFFER_SIZE;
+ }
+
+ //
+ // In case we find no allocation....
+ //
+
+ IoRuns[0].ByteCount = 0;
+
+ while (ByteCount != 0) {
+
+ //
+ // Try to lookup the first run. If there is just a single run,
+ // we may just be able to pass it on.
+ //
+
+ StartingVcn = LlClustersFromBytes( Vcb, StartVbo );
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ &RangePtr,
+ &Index );
+
+ //
+ // Adjust from NextLcn to Lbo.
+ //
+ // If next run is larger than we need, "ya get what you need".
+ // Note that after this we are guaranteed that the HighPart of
+ // NextByteCount is 0.
+ //
+
+
+ if ((ULONG)NextClusterCount >= ClustersFromBytes( Vcb, ByteCount )) {
+
+ NextByteCount = ByteCount;
+
+ } else {
+
+ NextByteCount = BytesFromClusters( Vcb, (ULONG)NextClusterCount );
+ }
+
+ //
+ // Adjust if the Lcn offset isn't zero.
+ //
+
+ NextLbo = LlBytesFromClusters( Vcb, NextLcn );
+
+ //
+ // Only fill in the run array if the run is allocated.
+ //
+
+ if (NextIsAllocated) {
+
+ //
+ // If the Lbos are contiguous, then we can do a contiguous
+ // transfer, so we just increase the current byte count.
+ //
+
+ if ((*NumberRuns != 0) && (NextLbo ==
+ (IoRuns[*NumberRuns - 1].StartingLbo +
+ (IoRuns[*NumberRuns - 1].ByteCount)))) {
+
+ //
+ // Stop on the first compression unit boundary after the
+ // the penultimate run in the default io array.
+ //
+
+ if (*NumberRuns >= NTFS_MAX_PARALLEL_IOS - 1) {
+
+ //
+ // First, if we are beyond the penultimate run and we are starting
+ // a run in a different compression unit than the previous
+ // run, then we can just break out and not use the current
+ // run. (*NumberRuns has not yet been incremented.)
+ //
+
+ if ((*NumberRuns > NTFS_MAX_PARALLEL_IOS - 1) &&
+ ((((ULONG)StartVbo) & ~(CompressionUnit - 1)) !=
+ ((((ULONG)IoRuns[*NumberRuns - 1].StartingVbo) +
+ IoRuns[*NumberRuns - 1].ByteCount - 1) &
+ ~(CompressionUnit - 1)))) {
+
+ break;
+
+ //
+ // Else detect the case where this run ends on or
+ // crosses a compression unit boundary. In this case,
+ // just make sure the run stops on a compression unit
+ // boundary, and break out to return it.
+ //
+
+ } else if ((((ULONG)StartVbo) & ~(CompressionUnit - 1)) !=
+ ((((ULONG)StartVbo) + NextByteCount) & ~(CompressionUnit - 1))) {
+
+ NextByteCount -= (((ULONG)StartVbo) + NextByteCount) & (CompressionUnit - 1);
+ BytesInIoRuns += NextByteCount;
+
+ if (ReturnByteCount > NextByteCount) {
+ ReturnByteCount -= NextByteCount;
+ } else {
+ ReturnByteCount = 0;
+ }
+
+ IoRuns[*NumberRuns - 1].ByteCount += NextByteCount;
+
+ break;
+ }
+ }
+
+ IoRuns[*NumberRuns - 1].ByteCount += NextByteCount;
+
+ //
+ // Otherwise it is time to start a new run, if there is space for one.
+ //
+
+ } else {
+
+ //
+ // If we have filled up the current I/O runs array, then we
+ // will grow it once to a size which would allow the worst
+ // case compression unit (all noncontiguous clusters) to
+ // start at index NTFS_MAX_PARALLEL_IOS - 1.
+ // The following if statement enforces
+ // this case as the worst case. With 16 clusters per compression
+ // unit, the theoretical maximum number of parallel I/Os
+ // would be 16 + NTFS_MAX_PARALLEL_IOS - 1, since we stop on the
+ // first compression unit boundary after the penultimate run.
+ // Normally, of course we will do much fewer.
+ //
+
+ if ((*NumberRuns == NTFS_MAX_PARALLEL_IOS) &&
+ (CompressionContext->AllocatedRuns == NTFS_MAX_PARALLEL_IOS)) {
+
+ PIO_RUN NewIoRuns;
+
+ NewIoRuns = NtfsAllocatePool( NonPagedPool,
+ (CompressionUnitInClusters + NTFS_MAX_PARALLEL_IOS - 1) * sizeof(IO_RUN) );
+
+ RtlCopyMemory( NewIoRuns,
+ CompressionContext->IoRuns,
+ NTFS_MAX_PARALLEL_IOS * sizeof(IO_RUN) );
+
+ IoRuns = CompressionContext->IoRuns = NewIoRuns;
+ CompressionContext->AllocatedRuns = CompressionUnitInClusters + NTFS_MAX_PARALLEL_IOS - 1;
+ }
+
+ //
+ // We remember each piece of a parallel run by saving the
+ // essential information in the IoRuns array. The tranfers
+ // will be started up in parallel below.
+ //
+
+ ASSERT(*NumberRuns < CompressionContext->AllocatedRuns);
+
+ IoRuns[*NumberRuns].StartingVbo = StartVbo;
+ IoRuns[*NumberRuns].StartingLbo = NextLbo;
+ IoRuns[*NumberRuns].BufferOffset = BufferOffset;
+ IoRuns[*NumberRuns].ByteCount = NextByteCount;
+ if ((*NumberRuns + 1) < CompressionContext->AllocatedRuns) {
+ IoRuns[*NumberRuns + 1].ByteCount = 0;
+ }
+
+ //
+ // Stop on the first compression unit boundary after the
+ // penultimate run in the default array.
+ //
+
+ if (*NumberRuns >= NTFS_MAX_PARALLEL_IOS - 1) {
+
+ //
+ // First, if we are beyond penultimate run and we are starting
+ // a run in a different compression unit than the previous
+ // run, then we can just break out and not use the current
+ // run. (*NumberRuns has not yet been incremented.)
+ //
+
+ if ((*NumberRuns > NTFS_MAX_PARALLEL_IOS - 1) &&
+ ((((ULONG)StartVbo) & ~(CompressionUnit - 1)) !=
+ ((((ULONG)IoRuns[*NumberRuns - 1].StartingVbo) +
+ IoRuns[*NumberRuns - 1].ByteCount - 1) &
+ ~(CompressionUnit - 1)))) {
+
+ break;
+
+ //
+ // Else detect the case where this run ends on or
+ // crosses a compression unit boundary. In this case,
+ // just make sure the run stops on a compression unit
+ // boundary, and break out to return it.
+ //
+
+ } else if ((((ULONG)StartVbo) & ~(CompressionUnit - 1)) !=
+ ((((ULONG)StartVbo) + NextByteCount) & ~(CompressionUnit - 1))) {
+
+ NextByteCount -= (((ULONG)StartVbo) + NextByteCount) & (CompressionUnit - 1);
+ IoRuns[*NumberRuns].ByteCount = NextByteCount;
+ BytesInIoRuns += NextByteCount;
+
+ if (ReturnByteCount > NextByteCount) {
+ ReturnByteCount -= NextByteCount;
+ } else {
+ ReturnByteCount = 0;
+ }
+
+ *NumberRuns += 1;
+ break;
+ }
+ }
+ *NumberRuns += 1;
+ }
+
+ BytesInIoRuns += NextByteCount;
+ BufferOffset += NextByteCount;
+ }
+
+ //
+ // Now adjust everything for the next pass through the loop.
+ //
+
+ StartVbo = StartVbo + NextByteCount;
+ ByteCount -= NextByteCount;
+
+ if (ReturnByteCount > NextByteCount) {
+ ReturnByteCount -= NextByteCount;
+ } else {
+ ReturnByteCount = 0;
+ }
+ }
+
+ //
+ // Allocate the compressed buffer if it is not already allocated.
+ //
+
+ if (BytesInIoRuns < CompressionUnit) {
+ BytesInIoRuns = CompressionUnit;
+ }
+ NtfsAllocateCompressionBuffer( IrpContext, Scb, Irp, CompressionContext, &BytesInIoRuns );
+
+ return ReturnByteCount;
+
+ //
+ // Otherwise handle the compressed write case.
+ //
+
+ } else {
+
+ LONGLONG SavedValidDataToDisk;
+ PUCHAR UncompressedBuffer;
+ PBCB Bcb;
+
+ ASSERT(IrpContext->MajorFunction == IRP_MJ_WRITE);
+
+ //
+ // Do not adjust offset and counts for the compressed stream.
+ //
+
+ ASSERT((CompressionUnitOffset == 0) || !CompressedStream);
+
+ //
+ // Adjust StartVbo and ByteCount by the offset.
+ //
+
+ ((ULONG)StartVbo) -= CompressionUnitOffset;
+ ByteCount += CompressionUnitOffset;
+
+ //
+ // Maintain additional bytes to be returned in ReturnByteCount,
+ // and adjust this if we are larger than a LARGE_BUFFER_SIZE.
+ //
+
+ ReturnByteCount = 0;
+ if (ByteCount > LARGE_BUFFER_SIZE) {
+ ReturnByteCount = ByteCount - LARGE_BUFFER_SIZE;
+ ByteCount = LARGE_BUFFER_SIZE;
+ }
+
+ if (!CompressedStream) {
+
+ //
+ // To reduce pool consumption, make an educated/optimistic guess on
+ // how much pool we need to store the compressed data. If we are wrong
+ // we will just have to do some more I/O.
+ //
+
+ CompressedSize = ByteCount;
+ CompressedSize = (CompressedSize + CompressionUnit - 1) & ~(CompressionUnit - 1);
+ CompressedSize += Vcb->BytesPerCluster;
+
+ if (CompressedSize > (PAGE_SIZE * 3)) {
+
+ if (CompressedSize > LARGE_BUFFER_SIZE) {
+ CompressedSize = LARGE_BUFFER_SIZE;
+ }
+
+ //
+ // Assume we may get compression to 5/8 original size, but we also
+ // need some extra to make the last call to compress the buffers
+ // for the last chunk. *** FOR NOW DEPEND ON Reserved Buffers...
+ //
+
+ // CompressedSize = ((CompressedSize / 8) * 5) + (CompressionUnit / 2);
+ }
+
+ //
+ // Allocate the compressed buffer if it is not already allocated, and this
+ // isn't the compressed stream.
+ //
+
+ NtfsAllocateCompressionBuffer( IrpContext, Scb, Irp, CompressionContext, &CompressedSize );
+ }
+
+ //
+ // Loop to compress the user's buffer.
+ //
+
+ CompressedOffset = 0;
+ BufferOffset = 0;
+
+ Bcb = NULL;
+
+ try {
+
+ BOOLEAN ChangeAllocation;
+
+ //
+ // Loop as long as we will not overflow our compressed buffer, and we
+ // are also guanteed that we will not overflow the extended IoRuns array
+ // in the worst case (and as long as we have more write to satisfy!).
+ //
+
+ while ((ByteCount != 0) && (*NumberRuns <= NTFS_MAX_PARALLEL_IOS - 1) &&
+ (((CompressedOffset + CompressionUnit) <= CompressedSize) || CompressedStream)) {
+
+ LONGLONG SizeToCompress;
+
+ //
+ // Assume we are only compressing to FileSize, or else
+ // reduce to one compression unit. The maximum compression size
+ // we can accept is saving at least one cluster.
+ //
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+
+ SizeToCompress = Scb->Header.FileSize.QuadPart - StartVbo;
+
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // It is possible that if this is the lazy writer that the file
+ // size was rolled back from a cached write which is aborting.
+ // In that case we either truncate the write or can exit this
+ // loop if there is nothing left to write.
+ //
+
+ if (SizeToCompress <= 0) {
+
+ ByteCount = 0;
+ break;
+ }
+
+ if (SizeToCompress > CompressionUnit) {
+ SizeToCompress = (LONGLONG)CompressionUnit;
+ }
+
+ //
+ // For the normal uncompressed stream, map the data and compress it
+ // into the allocated buffer.
+ //
+
+ if (!CompressedStream) {
+
+ //
+ // Map the aligned range, set it dirty, and flush. We have to
+ // loop, because the Cache Manager limits how much and over what
+ // boundaries we can map. Only do this if there a file
+ // object. Otherwise we will assume we are writing the
+ // clusters directly to disk (via NtfsWriteClusters).
+ //
+
+ if (Scb->FileObject != NULL) {
+
+ CcMapData( Scb->FileObject,
+ (PLARGE_INTEGER)&StartVbo,
+ (ULONG)SizeToCompress,
+ TRUE,
+ &Bcb,
+ &UncompressedBuffer );
+
+ //
+ // This is the NtfsWriteClusters path. We can get a pointer to
+ // the data for the file from the Mdl stored away in the
+ // compression context.
+ //
+
+ } else {
+
+ UncompressedBuffer = MmGetSystemAddressForMdl( CompressionContext->SavedMdl );
+ }
+
+ //
+ // If we have not already allocated the workspace, then do it.
+ //
+
+ if (CompressionContext->WorkSpace == NULL) {
+ ULONG CompressWorkSpaceSize;
+ ULONG FragmentWorkSpaceSize;
+
+ ASSERT((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0);
+
+ (VOID) RtlGetCompressionWorkSpaceSize( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ &CompressWorkSpaceSize,
+ &FragmentWorkSpaceSize );
+
+ NtfsCreateMdlAndBuffer( IrpContext,
+ Scb,
+ 2,
+ &CompressWorkSpaceSize,
+ NULL,
+ &CompressionContext->WorkSpace );
+ }
+
+ try {
+
+ //
+ // If we are writing compressed, compress it now.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_COMPRESSED) ||
+ ((Status =
+ RtlCompressBuffer( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ UncompressedBuffer,
+ (ULONG)SizeToCompress,
+ CompressionContext->CompressionBuffer + CompressedOffset,
+ (CompressionUnit - Vcb->BytesPerCluster),
+ NTFS_CHUNK_SIZE,
+ &FinalCompressedSize,
+ CompressionContext->WorkSpace )) ==
+
+ STATUS_BUFFER_TOO_SMALL)) {
+
+ //
+ // If it did not compress, just copy it over, sigh. This looks bad,
+ // but it should virtually never occur assuming compression is working
+ // ok. In the case where FileSize is in this unit, make sure we
+ // at least copy to a sector boundary.
+ //
+
+ FinalCompressedSize = CompressionUnit;
+
+ RtlCopyMemory( CompressionContext->CompressionBuffer + CompressedOffset,
+ UncompressedBuffer,
+ ((ULONG)SizeToCompress + Vcb->BytesPerSector - 1) &
+ ~(Vcb->BytesPerSector - 1));
+
+ Status = STATUS_SUCCESS;
+ }
+
+ ASSERT(NT_SUCCESS(Status));
+ ASSERT(FinalCompressedSize <= (CompressedSize - CompressedOffset));
+
+ //
+ // Probably Gary's compression routine faulted, but blaim it on
+ // the user buffer!
+ //
+
+ } except(NtfsCompressionFilter(IrpContext, GetExceptionInformation())) {
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_USER_BUFFER, NULL, NULL );
+ }
+
+ //
+ // For the compressed stream, we need to scan the compressed data
+ // to see how much we actually have to write.
+ //
+
+ } else {
+
+ //
+ // Don't walk off the end of the data being written, because that
+ // would cause bogus faults in the compressed stream.
+ //
+
+ if (SizeToCompress > ByteCount) {
+ SizeToCompress = ByteCount;
+ }
+
+ //
+ // Map the compressed data.
+ //
+
+ CcMapData( Scb->Header.FileObjectC,
+ (PLARGE_INTEGER)&StartVbo,
+ (ULONG)SizeToCompress,
+ TRUE,
+ &Bcb,
+ &UncompressedBuffer );
+
+ FinalCompressedSize = 0;
+
+ //
+ // Loop until we get an error or stop advancing.
+ //
+
+ RangePtr = UncompressedBuffer + SizeToCompress;
+ do {
+ Status = RtlDescribeChunk( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ &UncompressedBuffer,
+ (PUCHAR)RangePtr,
+ (PUCHAR *)&SystemBuffer,
+ &CompressedSize );
+
+ //
+ // Remember if we see any nonzero chunks
+ //
+
+ FinalCompressedSize |= CompressedSize;
+
+ } while (NT_SUCCESS(Status));
+
+ //
+ // If we terminated on anything but STATUS_NO_MORE_ENTRIES, we
+ // somehow picked up some bad data.
+ //
+
+ if (Status != STATUS_NO_MORE_ENTRIES) {
+ ASSERT(Status == STATUS_NO_MORE_ENTRIES);
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ Status = STATUS_SUCCESS;
+
+ //
+ // If we got any nonzero chunks, then calculate size of buffer to write.
+ // (Size does not include terminating Ushort of 0.)
+ //
+
+ if (FinalCompressedSize != 0) {
+ FinalCompressedSize = (ULONG)UncompressedBuffer & (CompressionUnit - 1);
+ }
+ }
+
+ NtfsUnpinBcb( &Bcb );
+
+ //
+ // Round the FinalCompressedSize up to a cluster boundary now.
+ //
+
+ FinalCompressedSize = (FinalCompressedSize + Vcb->BytesPerCluster - 1) &
+ ~(Vcb->BytesPerCluster - 1);
+
+ //
+ // If the Status was not success, then we have to do something.
+ //
+
+ if (Status != STATUS_SUCCESS) {
+
+ //
+ // If it was actually an error, then we will raise out of
+ // here.
+ //
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+
+ //
+ // If the buffer compressed to all zeros, then we will
+ // not allocate anything.
+ //
+
+ } else if (Status == STATUS_BUFFER_ALL_ZEROS) {
+ FinalCompressedSize = 0;
+ }
+ }
+
+ StartingVcn = Int64ShraMod32(StartVbo, Vcb->ClusterShift);
+
+ //
+ // Time to get the Scb if we do not have it already. We
+ // need to serialize our changes of the Mcb.
+ //
+
+ if (!CompressionContext->ScbAcquired) {
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ CompressionContext->ScbAcquired = TRUE;
+ }
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ &RangePtr,
+ &Index );
+
+ //
+ // If the StartingVcn is allocated, we always have to check
+ // if we need to delete something, or if in the unusual case
+ // there is a hole there smaller than a compression unit.
+ //
+
+ FinalCompressedClusters = ClustersFromBytes( Vcb, FinalCompressedSize );
+
+ ChangeAllocation = FALSE;
+
+ if (NextIsAllocated || (NextClusterCount < CompressionUnitInClusters) ||
+ (Scb->Union.MoveData != NULL)){
+
+ VCN TempClusterCount;
+
+ //
+ // If we need fewer clusters than allocated, then just allocate them.
+ // But if we need more clusters, then deallocate all the ones we have
+ // now, otherwise we could corrupt file data if we back out a write
+ // after actually having written the sectors. (For example, we could
+ // extend from 5 to 6 clusters and write 6 clusters of compressed data.
+ // If we have to back that out we will have a 6-cluster pattern of
+ // compressed data with one sector deallocated!).
+ //
+
+ NextIsAllocated = NextIsAllocated &&
+ (NextClusterCount >= FinalCompressedClusters);
+
+ //
+ // If we are cleaning up a hole, or the next run is unuseable,
+ // then make sure we just delete it rather than sliding the
+ // tiny run up with SplitMcb. Note that we have the Scb exclusive,
+ // and that since all compressed files go through the cache, we
+ // know that the dirty pages can't go away even if we spin out
+ // of here with ValidDataToDisk bumped up too high.
+ //
+
+ SavedValidDataToDisk = Scb->ValidDataToDisk;
+ if (!NextIsAllocated && ((StartVbo + CompressionUnit) > Scb->ValidDataToDisk)) {
+ Scb->ValidDataToDisk = StartVbo + CompressionUnit;
+ }
+
+ //
+ // Also, we need to handle the case where a range within
+ // ValidDataToDisk is fully allocated. If we are going to compress
+ // now, then we have the same problem with failing after writing
+ // the compressed data out, i.e., because we are fully allocated
+ // we would see the data as uncompressed after an abort, yet we
+ // have written compressed data. We do not implement the entire
+ // loop necessary to really see if the compression unit is fully
+ // allocated - we just verify that NextClusterCount is less than
+ // a compression unit and that the next run is not allocated. Just
+ // because the next contiguous run is also allocated does not guarantee
+ // that the compression unit is fully allocated, but maybe we will
+ // get some small defrag gain by reallocating what we need in a
+ // single run.
+ //
+
+ NextIsAllocated = NextIsAllocated &&
+ ((StartVbo >= Scb->ValidDataToDisk) ||
+ (FinalCompressedClusters == CompressionUnitInClusters) ||
+ ((NextClusterCount < CompressionUnitInClusters) &&
+ (!NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn + NextClusterCount,
+ &NextLbo,
+ &TempClusterCount,
+ &RangePtr,
+ &Index ) ||
+ (NextLbo != UNUSED_LCN))));
+
+ //
+ // If we are defragmenting make sure we delete the allocation
+ //
+
+ if(Scb->Union.MoveData != NULL) {
+
+ NextIsAllocated = FALSE;
+ }
+
+ //
+ // If we are not keeping any allocation, or we need less
+ // than a compression unit, then call NtfsDeleteAllocation.
+ //
+
+
+ if (!NextIsAllocated ||
+ (FinalCompressedClusters < CompressionUnitInClusters)) {
+
+ NtfsDeleteAllocation( IrpContext,
+ IoGetCurrentIrpStackLocation(Irp)->FileObject,
+ Scb,
+ StartingVcn + (NextIsAllocated ? FinalCompressedClusters : 0),
+ StartingVcn + ClustersFromBytes(Vcb, CompressionUnit) - 1,
+ TRUE,
+ FALSE );
+
+ ChangeAllocation = TRUE;
+ }
+
+ Scb->ValidDataToDisk = SavedValidDataToDisk;
+ }
+
+ //
+ // Now deal with the case where we do need to allocate space.
+ //
+
+ if (FinalCompressedSize != 0) {
+
+ //
+ // If this compression unit is not (sufficiently) allocated, then
+ // do it now.
+ //
+
+ if (!NextIsAllocated || (NextClusterCount < FinalCompressedClusters)) {
+
+ NtfsAddAllocation( IrpContext,
+ NULL,
+ Scb,
+ StartingVcn,
+ FinalCompressedClusters,
+ FALSE );
+
+ ChangeAllocation = TRUE;
+ }
+
+ //
+ // If we added space, something may have moved, so we must
+ // look up our position and get a new index.
+ //
+
+ if (ChangeAllocation) {
+
+ NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ &RangePtr,
+ &Index );
+ }
+
+ //
+ // Now loop to update the IoRuns array.
+ //
+
+ CompressedOffset += FinalCompressedSize;
+ TempVbo = StartVbo;
+ while (FinalCompressedSize != 0) {
+
+ LONGLONG RunOffset;
+
+ //
+ // Try to lookup the first run. If there is just a single run,
+ // we may just be able to pass it on. Index into the Mcb directly
+ // for greater speed.
+ //
+
+ NextIsAllocated = NtfsGetSequentialMcbEntry( &Scb->Mcb,
+ &RangePtr,
+ Index,
+ &StartingVcn,
+ &NextLcn,
+ &NextClusterCount );
+
+ Index += 1;
+
+ ASSERT(NextIsAllocated);
+ ASSERT(NextLcn != UNUSED_LCN);
+
+ //
+ // Our desired Vcn could be in the middle of this run, so do
+ // some adjustments.
+ //
+
+ RunOffset = Int64ShraMod32(TempVbo, Vcb->ClusterShift) - StartingVcn;
+
+ ASSERT( ((PLARGE_INTEGER)&RunOffset)->HighPart >= 0 );
+ ASSERT( NextClusterCount > RunOffset );
+
+ NextLcn = NextLcn + RunOffset;
+ NextClusterCount = NextClusterCount - RunOffset;
+
+ //
+ // Adjust from NextLcn to Lbo. NextByteCount may overflow out of 32 bits
+ // but we will catch that below when we compare clusters.
+ //
+
+ NextLbo = LlBytesFromClusters( Vcb, NextLcn );
+ NextByteCount = BytesFromClusters( Vcb, (ULONG)NextClusterCount );
+
+ //
+ // If next run is larger than we need, "ya get what you need".
+ // Note that after this we are guaranteed that the HighPart of
+ // NextByteCount is 0.
+ //
+
+ if (NextClusterCount >= FinalCompressedClusters) {
+
+ NextByteCount = FinalCompressedSize;
+ }
+
+ //
+ // If the Lbos are contiguous, then we can do a contiguous
+ // transfer, so we just increase the current byte count.
+ //
+
+ if ((*NumberRuns != 0) &&
+ (NextLbo == (IoRuns[*NumberRuns - 1].StartingLbo +
+ IoRuns[*NumberRuns - 1].ByteCount))) {
+
+ IoRuns[*NumberRuns - 1].ByteCount += NextByteCount;
+
+ //
+ // Otherwise it is time to start a new run, if there is space for one.
+ //
+
+ } else {
+
+ //
+ // If we have filled up the current I/O runs array, then we
+ // will grow it once to a size which would allow the worst
+ // case compression unit (all noncontiguous clusters) to
+ // start at the penultimate index. The following if
+ // statement enforces this case as the worst case. With 16
+ // clusters per compression unit, the theoretical maximum
+ // number of parallel I/Os would be 16 + NTFS_MAX_PARALLEL_IOS - 1,
+ // since we stop on the first compression unit
+ // boundary after the penultimate run. Normally, of course we
+ // will do much fewer.
+ //
+
+ if ((*NumberRuns == NTFS_MAX_PARALLEL_IOS) &&
+ (CompressionContext->AllocatedRuns == NTFS_MAX_PARALLEL_IOS)) {
+
+ PIO_RUN NewIoRuns;
+
+ NewIoRuns = NtfsAllocatePool( NonPagedPool,
+ (CompressionUnitInClusters + NTFS_MAX_PARALLEL_IOS - 1) * sizeof(IO_RUN) );
+
+ RtlCopyMemory( NewIoRuns,
+ CompressionContext->IoRuns,
+ NTFS_MAX_PARALLEL_IOS * sizeof(IO_RUN) );
+
+ IoRuns = CompressionContext->IoRuns = NewIoRuns;
+ CompressionContext->AllocatedRuns = CompressionUnitInClusters + NTFS_MAX_PARALLEL_IOS - 1;
+ }
+
+ //
+ // We remember each piece of a parallel run by saving the
+ // essential information in the IoRuns array. The tranfers
+ // will be started up in parallel below.
+ //
+
+ IoRuns[*NumberRuns].StartingVbo = TempVbo;
+ IoRuns[*NumberRuns].StartingLbo = NextLbo;
+ IoRuns[*NumberRuns].BufferOffset = BufferOffset;
+ IoRuns[*NumberRuns].ByteCount = NextByteCount;
+ *NumberRuns += 1;
+ }
+
+ //
+ // Now adjust everything for the next pass through the loop.
+ //
+
+ BufferOffset += NextByteCount;
+ TempVbo = TempVbo + NextByteCount;
+ FinalCompressedSize -= NextByteCount;
+ FinalCompressedClusters = ClustersFromBytes( Vcb, FinalCompressedSize );
+ }
+ }
+
+ //
+ // If this is the unnamed data stream then we need to update
+ // the total allocated size.
+ //
+
+ if (ChangeAllocation && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ UncompressedOffset += CompressionUnit - CompressionUnitOffset;
+
+ //
+ // Now reduce the byte counts by the compression unit we just
+ // transferred.
+
+ if (ByteCount > CompressionUnit) {
+ StartVbo += CompressionUnit;
+ ByteCount -= CompressionUnit;
+ } else {
+ StartVbo += ByteCount;
+ ByteCount = 0;
+ }
+
+ CompressionUnitOffset = 0;
+ }
+
+ } finally {
+
+ NtfsUnpinBcb( &Bcb );
+ }
+
+ //
+ // See if we need to advance ValidDataToDisk.
+ //
+
+ if (StartVbo > Scb->ValidDataToDisk) {
+ Scb->ValidDataToDisk = StartVbo;
+ }
+
+ return ByteCount + ReturnByteCount;
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+NTSTATUS
+NtfsFinishBuffers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PVBO StartingVbo,
+ IN ULONG ByteCount,
+ IN PCOMPRESSION_CONTEXT CompressionContext,
+ IN ULONG CompressedStream
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs post processing for noncached transfers of
+ compressed data. For reads, the decompression actually takes place
+ here. For reads and writes, all necessary cleanup operations are
+ performed.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Scb - Supplies the stream file to act on.
+
+ StartingVbo - The starting point for the operation.
+
+ ByteCount - The lengh of the operation.
+
+ CompressionContext - Supplies information related to the compression
+ filled in by NtfsPrepareBuffers.
+
+ CompressedStream - Supplies nonzero if I/O is to compressed stream
+
+Return Value:
+
+ Status from the operation
+
+--*/
+
+{
+ VCN CurrentVcn, NextVcn;
+ LCN NextLcn;
+
+ ULONG NextByteCount;
+ LONGLONG NextClusterCount;
+
+ BOOLEAN NextIsAllocated;
+ BOOLEAN AlreadyFilled;
+
+ PVOID SystemBuffer;
+
+ ULONG CompressionUnit, CompressionUnitInClusters;
+ ULONG StartingOffset, UncompressedOffset, CompressedOffset;
+ ULONG CompressedSize;
+ LONGLONG UncompressedSize;
+
+ LONGLONG CurrentAllocatedClusterCount;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PVCB Vcb = Scb->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // If this is a normal termination of a read, then let's give him the
+ // data...
+ //
+
+ ASSERT(Scb->CompressionUnit != 0);
+
+ //
+ // If we are defragmenting we don't need to do this so then just return
+ //
+
+ if(Scb->Union.MoveData != NULL && !FlagOn(Scb->ScbState, SCB_STATE_COMPRESSED)) {
+ return Status;
+ }
+
+ if (IrpContext->MajorFunction == IRP_MJ_READ) {
+
+ if (!CompressedStream) {
+
+ //
+ // Initialize remaining context for the loop.
+ //
+
+ CompressionUnit = Scb->CompressionUnit;
+ CompressionUnitInClusters = ClustersFromBytes(Vcb, CompressionUnit);
+ CompressedOffset = 0;
+ UncompressedOffset = 0;
+ Status = STATUS_SUCCESS;
+
+ //
+ // Map the user buffer.
+ //
+
+ SystemBuffer = (PVOID)((PCHAR)CompressionContext->SystemBuffer +
+ CompressionContext->SystemBufferOffset);
+
+
+ //
+ // Calculate the first Vcn and offset within the compression
+ // unit of the start of the transfer, and lookup the first
+ // run.
+ //
+
+ StartingOffset = *((PULONG)StartingVbo) & (CompressionUnit - 1);
+ CurrentVcn = LlClustersFromBytes(Vcb, *StartingVbo - StartingOffset);
+
+ NextIsAllocated =
+ NtfsLookupAllocation( IrpContext,
+ Scb,
+ CurrentVcn,
+ &NextLcn,
+ &CurrentAllocatedClusterCount,
+ NULL,
+ NULL );
+
+ //
+ // Set NextIsAllocated and NextLcn as the Mcb package would, to show if
+ // we are off the end.
+ //
+
+ if (!NextIsAllocated) {
+ NextLcn = UNUSED_LCN;
+ }
+
+ NextIsAllocated = (BOOLEAN)(CurrentAllocatedClusterCount < (MAXLONGLONG - CurrentVcn));
+
+ //
+ // If this is actually a hole or there was no entry in the Mcb, then
+ // set CurrentAllocatedClusterCount to zero so we will always make the first
+ // pass in the embedded while loop below.
+ //
+
+ if (!NextIsAllocated || (NextLcn == UNUSED_LCN)) {
+ CurrentAllocatedClusterCount = 0;
+ }
+
+ //
+ // Prepare for the initial Mcb scan below by pretending that the
+ // next run has been looked up, and is a contiguous run of 0 clusters!
+ //
+
+ NextVcn = CurrentVcn + CurrentAllocatedClusterCount;
+ NextClusterCount = 0;
+
+ //
+ // Loop to return the data.
+ //
+
+ while (ByteCount != 0) {
+
+ //
+ // Loop to determine the compressed size of the next compression
+ // unit. I.e., loop until we either find the end of the current
+ // range of contiguous Vcns, or until we find that the current
+ // compression unit is fully allocated.
+ //
+
+ while (NextIsAllocated &&
+ (CurrentAllocatedClusterCount < CompressionUnitInClusters) &&
+ ((CurrentVcn + CurrentAllocatedClusterCount) == NextVcn)) {
+
+ if ((CurrentVcn + CurrentAllocatedClusterCount) > NextVcn) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ CurrentAllocatedClusterCount = CurrentAllocatedClusterCount + NextClusterCount;
+
+ //
+ // Loop to find the next allocated Vcn, or the end of the Mcb.
+ // None of the interfaces using RangePtr and Index as inputs
+ // can be used here, such as NtfsGetSequentialMcbEntry, because
+ // we do not have the Scb main resource acquired, and writers can
+ // be moving stuff around in parallel.
+ //
+
+ while (TRUE) {
+
+ //
+ // Set up NextVcn for next call
+ //
+
+ NextVcn += NextClusterCount;
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ NextVcn,
+ &NextLcn,
+ &NextClusterCount,
+ NULL,
+ NULL );
+
+ //
+ // Set NextIsAllocated and NextLcn as the Mcb package would, to show if
+ // we are off the end.
+ //
+
+ if (!NextIsAllocated) {
+ NextLcn = UNUSED_LCN;
+ }
+
+ NextIsAllocated = (BOOLEAN)(NextClusterCount < (MAXLONGLONG - NextVcn));
+
+ //
+ // Get out if we hit the end or see something allocated.
+ //
+
+ if (!NextIsAllocated || (NextLcn != UNUSED_LCN)) {
+ break;
+ }
+ }
+ }
+
+ //
+ // The compression unit is fully allocated.
+ //
+
+ if (CurrentAllocatedClusterCount >= CompressionUnitInClusters) {
+
+ CompressedSize = CompressionUnit;
+ CurrentAllocatedClusterCount = CurrentAllocatedClusterCount - CompressionUnitInClusters;
+
+ //
+ // Otherwise calculate how much is allocated at the current Vcn
+ // (if any).
+ //
+
+ } else {
+
+ CompressedSize = BytesFromClusters(Vcb, (ULONG)CurrentAllocatedClusterCount);
+ CurrentAllocatedClusterCount = 0;
+ }
+
+ //
+ // The next time through this loop, we will be working on the next
+ // compression unit.
+ //
+
+ CurrentVcn = CurrentVcn + CompressionUnitInClusters;
+
+ //
+ // Calculate uncompressed size of the desired fragment, or
+ // entire compression unit.
+ //
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ UncompressedSize = Scb->Header.FileSize.QuadPart -
+ (*StartingVbo + UncompressedOffset);
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ if (UncompressedSize > CompressionUnit) {
+ (ULONG)UncompressedSize = CompressionUnit;
+ }
+
+ //
+ // Calculate how much we want now, based on StartingOffset and
+ // ByteCount.
+ //
+
+ NextByteCount = CompressionUnit - StartingOffset;
+ if (NextByteCount > ByteCount) {
+ NextByteCount = ByteCount;
+ }
+
+ //
+ // Practice safe access
+ //
+
+ try {
+
+ //
+ // There were no clusters allocated, return 0's.
+ //
+
+ AlreadyFilled = FALSE;
+ if (CompressedSize == 0) {
+
+ RtlZeroMemory( (PUCHAR)SystemBuffer + UncompressedOffset,
+ NextByteCount );
+
+ //
+ // The compression unit was fully allocated, just copy.
+ //
+
+ } else if (CompressedSize == CompressionUnit) {
+
+ RtlCopyMemory( (PUCHAR)SystemBuffer + UncompressedOffset,
+ CompressionContext->CompressionBuffer +
+ CompressedOffset + StartingOffset,
+ NextByteCount );
+
+#ifdef SYSCACHE
+ if (FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE)) {
+
+ FsRtlVerifySyscacheData( IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject,
+ Add2Ptr( SystemBuffer, UncompressedOffset ),
+ NextByteCount,
+ (ULONG)*StartingVbo + UncompressedOffset );
+ }
+#endif
+
+ //
+ // Caller does not want the entire compression unit, decompress
+ // a fragment.
+ //
+
+ } else if (NextByteCount < CompressionUnit) {
+
+ //
+ // If we have not already allocated the workspace, then do it.
+ //
+
+ if (CompressionContext->WorkSpace == NULL) {
+ ULONG CompressWorkSpaceSize;
+ ULONG FragmentWorkSpaceSize;
+
+ ASSERT((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) != 0);
+
+ (VOID) RtlGetCompressionWorkSpaceSize( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ &CompressWorkSpaceSize,
+ &FragmentWorkSpaceSize );
+
+ //
+ // Allocate first from non-paged, then paged. The typical
+ // size of this workspace is just over a single page so
+ // if both allocations fail then the system is running
+ // a reduced capacity. Return an error to the user
+ // and let him retry.
+ //
+
+ CompressionContext->WorkSpace = ExAllocatePool( NonPagedPool, FragmentWorkSpaceSize );
+
+ if (CompressionContext->WorkSpace == NULL) {
+
+ CompressionContext->WorkSpace =
+ NtfsAllocatePool( PagedPool, FragmentWorkSpaceSize );
+ }
+ }
+
+ while (TRUE) {
+
+ Status =
+ RtlDecompressFragment( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ (PUCHAR)SystemBuffer + UncompressedOffset,
+ NextByteCount,
+ CompressionContext->CompressionBuffer + CompressedOffset,
+ CompressedSize,
+ StartingOffset,
+ (PULONG)&UncompressedSize,
+ CompressionContext->WorkSpace );
+
+ ASSERT(NT_SUCCESS( Status ) || !NtfsStopOnDecompressError);
+
+ if (NT_SUCCESS(Status)) {
+
+ RtlZeroMemory( (PUCHAR)SystemBuffer + UncompressedOffset + (ULONG)UncompressedSize,
+ NextByteCount - (ULONG)UncompressedSize );
+
+#ifdef SYSCACHE
+ if (FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE)) {
+
+ FsRtlVerifySyscacheData( IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject,
+ Add2Ptr( SystemBuffer, UncompressedOffset ),
+ NextByteCount,
+ (ULONG)*StartingVbo + UncompressedOffset );
+ }
+#endif
+ break;
+
+ } else {
+
+ //
+ // The compressed buffer could have been bad. We need to fill
+ // it with a pattern and get on with life. Someone could be
+ // faulting it in just to overwrite it, or it could be a rare
+ // case of corruption. We fill the data with a pattern, but
+ // we must return success so a pagefault will succeed. We
+ // do this once, then loop back to decompress what we can.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ if (!AlreadyFilled) {
+
+ RtlFillMemory( (PUCHAR)SystemBuffer + UncompressedOffset,
+ NextByteCount,
+ 0xDF );
+ AlreadyFilled = TRUE;
+
+ } else {
+ break;
+ }
+ }
+ }
+
+ //
+ // Decompress the entire compression unit.
+ //
+
+ } else {
+
+ ASSERT(StartingOffset == 0);
+
+ while (TRUE) {
+
+ Status =
+ RtlDecompressBuffer( (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1),
+ (PUCHAR)SystemBuffer + UncompressedOffset,
+ NextByteCount,
+ CompressionContext->CompressionBuffer + CompressedOffset,
+ CompressedSize,
+ (PULONG)&UncompressedSize );
+
+ ASSERT(NT_SUCCESS( Status ) || !NtfsStopOnDecompressError);
+
+ if (NT_SUCCESS(Status)) {
+
+ RtlZeroMemory( (PUCHAR)SystemBuffer + UncompressedOffset + (ULONG)UncompressedSize,
+ NextByteCount - (ULONG)UncompressedSize );
+#ifdef SYSCACHE
+ if (FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE)) {
+
+ FsRtlVerifySyscacheData( IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject,
+ Add2Ptr( SystemBuffer, UncompressedOffset ),
+ NextByteCount,
+ (ULONG)*StartingVbo + UncompressedOffset );
+ }
+#endif
+ break;
+
+ } else {
+
+ //
+ // The compressed buffer could have been bad. We need to fill
+ // it with a pattern and get on with life. Someone could be
+ // faulting it in just to overwrite it, or it could be a rare
+ // case of corruption. We fill the data with a pattern, but
+ // we must return success so a pagefault will succeed. We
+ // do this once, then loop back to decompress what we can.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ if (!AlreadyFilled) {
+
+ RtlFillMemory( (PUCHAR)SystemBuffer + UncompressedOffset,
+ NextByteCount,
+ 0xDB );
+ AlreadyFilled = TRUE;
+
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ //
+ // Probably Gary's decompression routine faulted, but blaim it on
+ // the user buffer!
+ //
+
+ } except(NtfsCompressionFilter(IrpContext, GetExceptionInformation())) {
+ Status = STATUS_INVALID_USER_BUFFER;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ //
+ // Advance these fields for the next pass through.
+ //
+
+ StartingOffset = 0;
+ UncompressedOffset += NextByteCount;
+ CompressedOffset += CompressedSize;
+ ByteCount -= NextByteCount;
+ }
+
+ //
+ // We now flush the user's buffer to memory.
+ //
+
+ KeFlushIoBuffers( CompressionContext->SavedMdl, TRUE, FALSE );
+ }
+
+
+ //
+ // For compressed writes we just checkpoint the transaction and
+ // free all snapshots and resources, then get the Scb back. Only do this if the
+ // request is for the same Irp as the original Irp. We don't want to checkpoint
+ // if called from NtfsWriteClusters.
+ //
+
+ } else if (Irp == IrpContext->OriginatingIrp) {
+
+ if (CompressionContext->ScbAcquired) {
+
+ BOOLEAN Reinsert = FALSE;
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // We want to empty the exclusive Fcb list but still hold
+ // the current file. Go ahead and remove it from the exclusive
+ // list and reinsert it after freeing the other entries.
+ //
+
+ while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
+
+ if ((PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ) == Scb->Fcb) {
+
+ RemoveEntryList( &Scb->Fcb->ExclusiveFcbLinks );
+ Reinsert = TRUE;
+
+ } else {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+ }
+
+ if (Reinsert) {
+
+ InsertTailList( &IrpContext->ExclusiveFcbList,
+ &Scb->Fcb->ExclusiveFcbLinks );
+ }
+ }
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsNonCachedIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount,
+ IN ULONG CompressedStream
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the non-cached disk io described in its parameters.
+ The choice of a single run is made if possible, otherwise multiple runs
+ are executed.
+
+ Sparse files are supported. If "holes" are encountered, then the user
+ buffer is zeroed over the specified range. This should only happen on
+ reads during normal operation, but it can also happen on writes during
+ restart, in which case it is also appropriate to zero the buffer.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Scb - Supplies the stream file to act on.
+
+ StartingVbo - The starting point for the operation.
+
+ ByteCount - The lengh of the operation.
+
+ CompressedStream - Supplies nonzero if I/O is to compressed stream
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG OriginalByteCount, RemainingByteCount;
+ ULONG NumberRuns;
+ IO_RUN IoRuns[NTFS_MAX_PARALLEL_IOS];
+ COMPRESSION_CONTEXT CompressionContext;
+ NTSTATUS Status = STATUS_SUCCESS;
+ PMDL Mdl1, Mdl2;
+
+ PVCB Vcb = Scb->Vcb;
+
+ BOOLEAN Wait;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsNonCachedIo\n") );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("StartingVbo = %016I64x\n", StartingVbo) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", ByteCount) );
+
+ //
+ // Initialize some locals.
+ //
+
+ OriginalByteCount = ByteCount;
+
+ Wait = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // Check if we need to do sequential writes.
+ //
+
+ if ((IrpContext->MajorFunction == IRP_MJ_WRITE) &&
+ FlagOn( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE )) {
+
+ IrpContext->Union.NtfsIoContext->IrpSpFlags = SL_FT_SEQUENTIAL_WRITE | SL_WRITE_THROUGH;
+ }
+
+ //
+ // Prepare the (first set) of buffers for I/O.
+ //
+
+ RtlZeroMemory( &CompressionContext, sizeof(COMPRESSION_CONTEXT) );
+ CompressionContext.IoRuns = IoRuns;
+ CompressionContext.AllocatedRuns = NTFS_MAX_PARALLEL_IOS;
+
+ Mdl1 = Mdl2 = NULL;
+
+ try {
+
+ //
+ // If this is a write to a compressed file, we want to make sure here
+ // that any fragments of compression units get locked in memory, so
+ // no one will be reading them into the cache while we are mucking with
+ // the Mcb, etc. We do this right here at the top so that we have
+ // more stack(!), and we get this over with before we have to acquire
+ // the Scb exclusive.
+ //
+
+ if ((IrpContext->MajorFunction == IRP_MJ_WRITE) && (Scb->CompressionUnit != 0) && !CompressedStream) {
+
+ PVOID UncompressedBuffer;
+ LONGLONG TempOffset;
+ PBCB Bcb;
+ ULONG CompressionUnit = Scb->CompressionUnit;
+ PMDL *MdlPtr = NULL;
+
+ //
+ // This better be paging I/O, because we ignore the caller's buffer
+ // and write the entire compression unit out of the section.
+ //
+ // We don't want to map in the data in the case where we are called
+ // from write clusters because MM is creating the section for the
+ // file. Otherwise we will deadlock when Cc tries to create the
+ // section.
+ //
+
+ if ((Irp == IrpContext->OriginatingIrp) ||
+ (Scb->NonpagedScb->SegmentObject.SharedCacheMap != NULL)) {
+
+ if (Scb->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ //
+ // If there is no one who will cause this stream to
+ // be dereferenced then add an entry on the delayed
+ // close queue for this. We can do this test without
+ // worrying about synchronization since it is OK to have
+ // an extra entry in the delayed queue.
+ //
+
+ if ((Scb->CleanupCount == 0) &&
+ (Scb->Fcb->DelayedCloseCount == 0)) {
+
+ NtfsAddScbToFspClose( IrpContext, Scb, TRUE );
+ }
+ }
+
+ //
+ // Loop to optionally lock first start then end of buffer.
+ //
+
+ while (MdlPtr != &Mdl2) {
+
+ //
+ // First look at the start of the buffer.
+ //
+
+ if (MdlPtr == NULL) {
+
+ MdlPtr = &Mdl1;
+
+ //
+ // If we are starting on a compression unit boundary,
+ // no work at the start of the buffer.
+ //
+
+ if ((StartingVbo & (CompressionUnit - 1)) == 0) {
+ continue;
+ }
+
+ //
+ // Show which compression unit to lock.
+ //
+
+ TempOffset = StartingVbo;
+
+ //
+ // Now look at the tail of the buffer.
+ //
+
+ } else {
+
+ MdlPtr = &Mdl2;
+
+ //
+ // Get offset at end of transfer.
+ //
+
+ TempOffset = (StartingVbo + ByteCount);
+
+ //
+ // If we end on a compression unit boundary, or we end in
+ // the same compression unit as the first Mdl, and we created
+ // it, then we are done with this nonsense and can get out.
+ //
+
+ if (((TempOffset & (CompressionUnit - 1)) == 0) ||
+ ((TempOffset & ~(CompressionUnit - 1)) == (StartingVbo & ~(CompressionUnit - 1)) &&
+ (Mdl1 != NULL))) {
+ break;
+ }
+ }
+
+ //
+ // Calculate start of this compression unit and how
+ // much to lock.
+ //
+
+ TempOffset &= ~(CompressionUnit - 1);
+
+ //
+ // Map the aligned range.
+ //
+
+ CcMapData( Scb->FileObject, (PLARGE_INTEGER)&TempOffset, CompressionUnit, TRUE, &Bcb, &UncompressedBuffer );
+
+ //
+ // Lock the data into memory so that we can safely reallocate the
+ // space. Don't tell Mm here that we plan to write it, as he sets
+ // dirty now and at the unlock below if we do.
+ //
+
+ try {
+
+ //
+ // Now attempt to allocate an Mdl to describe the mapped data.
+ //
+
+ *MdlPtr = IoAllocateMdl( UncompressedBuffer, CompressionUnit, FALSE, FALSE, NULL );
+
+ if (*MdlPtr == NULL) {
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ MmProbeAndLockPages( *MdlPtr, KernelMode, IoReadAccess );
+
+ //
+ // Catch any raises here and clean up appropriately.
+ //
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ Status = GetExceptionCode();
+
+ CcUnpinData(Bcb);
+
+ if (*MdlPtr != NULL) {
+
+ IoFreeMdl( *MdlPtr );
+ *MdlPtr = NULL;
+ }
+
+ NtfsRaiseStatus( IrpContext,
+ FsRtlIsNtstatusExpected(Status) ? Status : STATUS_UNEXPECTED_IO_ERROR,
+ NULL,
+ NULL );
+ }
+
+ CcUnpinData(Bcb);
+ }
+
+ } else {
+
+ //
+ // This had better be a convert to non-resident.
+ //
+
+ ASSERT( StartingVbo == 0 );
+ ASSERT( ByteCount <= Scb->CompressionUnit );
+ }
+ }
+
+ RemainingByteCount = NtfsPrepareBuffers( IrpContext,
+ Irp,
+ Scb,
+ &StartingVbo,
+ ByteCount,
+ &NumberRuns,
+ &CompressionContext,
+ CompressedStream );
+
+ ASSERT( RemainingByteCount < ByteCount );
+
+ if (FlagOn(Irp->Flags, IRP_PAGING_IO)) {
+ CollectDiskIoStats(Vcb, Scb, IrpContext->MajorFunction, NumberRuns);
+ }
+
+ //
+ // See if the write covers a single valid run, and if so pass
+ // it on. Notice that if there is a single run but it does not
+ // begin at the beginning of the buffer then we will still need to
+ // allocate an associated Irp for this.
+ //
+
+ if ((RemainingByteCount == 0) &&
+ (((NumberRuns == 1)
+ && (CompressionContext.IoRuns[0].BufferOffset == 0))
+
+ ||
+
+ (NumberRuns == 0))) {
+
+ DebugTrace( 0, Dbg, ("Passing Irp on to Disk Driver\n") );
+
+ //
+ // See if there is an allocated run
+ //
+
+ if (NumberRuns == 1) {
+
+ //
+ // We will continously try the I/O if we get a verify required
+ // back and can verify the volume
+ //
+
+ while (TRUE) {
+
+ //
+ // Do the I/O and wait for it to finish
+ //
+
+ NtfsSingleAsync( IrpContext,
+ Vcb->TargetDeviceObject,
+ CompressionContext.IoRuns[0].StartingLbo,
+ CompressionContext.IoRuns[0].ByteCount,
+ Irp );
+
+ //
+ // If this is an asynch transfer we return STATUS_PENDING.
+ //
+
+ if (!Wait) {
+
+ DebugTrace( -1, Dbg, ("NtfsNonCachedIo -> STATUS_PENDING\n") );
+ try_return(Status = STATUS_PENDING);
+
+ } else {
+
+ NtfsWaitSync( IrpContext );
+ }
+
+ //
+ // If we didn't get a verify required back then break out of
+ // this loop
+ //
+
+ if (Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) { break; }
+
+ //
+ // Otherwise we need to verify the volume, and if it doesn't
+ // verify correctly the we dismount the volume and raise our
+ // error
+ //
+
+ if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
+
+ //**** NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_INVALID, NULL, NULL );
+ }
+
+ //
+ // The volume verified correctly so now clear the verify bit
+ // and try and I/O again
+ //
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+ }
+
+ //
+ // See if we need to do a hot fix.
+ //
+
+ if (!FT_SUCCESS(Irp->IoStatus.Status) ||
+ (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT) &&
+ (IrpContext->MajorFunction == IRP_MJ_READ) &&
+ !NtfsVerifyAndRevertUsaBlock( IrpContext,
+ Scb,
+ NtfsMapUserBuffer( Irp ),
+ OriginalByteCount,
+ StartingVbo ))) {
+
+ //
+ // Try to fix the problem
+ //
+
+ NtfsFixDataError( IrpContext,
+ Scb,
+ Vcb->TargetDeviceObject,
+ Irp,
+ 1,
+ CompressionContext.IoRuns );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsNonCachedIo -> %08lx\n", Irp->IoStatus.Status) );
+ try_return( Status = Irp->IoStatus.Status );
+ }
+
+ //
+ // If there are bytes remaining and we cannot wait, then we must
+ // post this request unless we are doing paging io.
+ //
+
+ if (!Wait && (RemainingByteCount != 0)) {
+
+ if (!FlagOn( Irp->Flags, IRP_PAGING_IO )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ Wait = TRUE;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ //
+ // Store whether we allocated this context structure in the structure
+ // itself.
+ //
+
+ IrpContext->Union.NtfsIoContext->AllocatedContext =
+ BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+ }
+
+ //
+ // Now set up the Irp->IoStatus. It will be modified by the
+ // multi-completion routine in case of error or verify required.
+ //
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Loop while there are still byte writes to satisfy.
+ //
+
+ while (TRUE) {
+
+ //
+ // We will continously try the I/O if we get a verify required
+ // back and can verify the volume. Note that we could have ended
+ // on a hole, and have no runs left.
+ //
+
+ if (NumberRuns != 0) {
+
+ while (TRUE) {
+
+ //
+ // Do the I/O and wait for it to finish
+ //
+
+ NtfsMultipleAsync( IrpContext,
+ Vcb->TargetDeviceObject,
+ Irp,
+ NumberRuns,
+ CompressionContext.IoRuns );
+
+ //
+ // If this is an asynchronous transfer, then return STATUS_PENDING.
+ //
+
+ if (!Wait) {
+
+ DebugTrace( -1, Dbg, ("NtfsNonCachedIo -> STATUS_PENDING\n") );
+ try_return( Status = STATUS_PENDING );
+ }
+
+ NtfsWaitSync( IrpContext );
+
+ //
+ // If we didn't get a verify required back then break out of
+ // this loop
+ //
+
+ if (Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) { break; }
+
+ //
+ // Otherwise we need to verify the volume, and if it doesn't
+ // verify correctly the we dismount the volume and raise our
+ // error
+ //
+
+ if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
+
+ //**** NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_INVALID, NULL, NULL );
+ }
+
+ //
+ // The volume verified correctly so now clear the verify bit
+ // and try and I/O again
+ //
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+ }
+
+ //
+ // See if we need to do a hot fix.
+ //
+
+ if (!FT_SUCCESS(Irp->IoStatus.Status) ||
+ (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT) &&
+ (IrpContext->MajorFunction == IRP_MJ_READ) &&
+ !NtfsVerifyAndRevertUsaBlock( IrpContext,
+ Scb,
+ (PCHAR)NtfsMapUserBuffer( Irp ) +
+ CompressionContext.IoRuns[0].BufferOffset,
+ OriginalByteCount -
+ CompressionContext.IoRuns[0].BufferOffset -
+ RemainingByteCount,
+ StartingVbo ))) {
+
+ //
+ // Try to fix the problem
+ //
+
+ NtfsFixDataError( IrpContext,
+ Scb,
+ Vcb->TargetDeviceObject,
+ Irp,
+ NumberRuns,
+ CompressionContext.IoRuns );
+ }
+ }
+
+ if (!NT_SUCCESS(Irp->IoStatus.Status) || (RemainingByteCount == 0)) { break; }
+
+ if (Scb->CompressionUnit != 0) {
+
+ Irp->IoStatus.Status =
+ NtfsFinishBuffers( IrpContext,
+ Irp,
+ Scb,
+ &StartingVbo,
+ ByteCount - RemainingByteCount,
+ &CompressionContext,
+ CompressedStream );
+
+ if (!NT_SUCCESS(Irp->IoStatus.Status)) { break; }
+ }
+
+ StartingVbo = StartingVbo + (ByteCount - RemainingByteCount);
+ CompressionContext.SystemBufferOffset += ByteCount - RemainingByteCount;
+
+ ByteCount = RemainingByteCount;
+
+ RemainingByteCount = NtfsPrepareBuffers( IrpContext,
+ Irp,
+ Scb,
+ &StartingVbo,
+ ByteCount,
+ &NumberRuns,
+ &CompressionContext,
+ CompressedStream );
+
+ ASSERT( RemainingByteCount < ByteCount );
+
+ if (FlagOn(Irp->Flags, IRP_PAGING_IO)) {
+ CollectDiskIoStats(Vcb, Scb, IrpContext->MajorFunction, NumberRuns);
+ }
+ }
+
+ Status = Irp->IoStatus.Status;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ //
+ // If this is a compressed file and we got success, go do our normal
+ // post processing.
+ //
+
+ if ((Scb->CompressionUnit != 0) && NT_SUCCESS(Status) && !AbnormalTermination()) {
+
+ Irp->IoStatus.Status =
+ Status =
+ NtfsFinishBuffers( IrpContext,
+ Irp,
+ Scb,
+ &StartingVbo,
+ ByteCount - RemainingByteCount,
+ &CompressionContext,
+ CompressedStream );
+ }
+
+ //
+ // For writes, free any Mdls which may have been used.
+ //
+
+ if (Mdl1 != NULL) {
+ MmUnlockPages( Mdl1 );
+ IoFreeMdl( Mdl1 );
+ }
+
+ if (Mdl2 != NULL) {
+ MmUnlockPages( Mdl2 );
+ IoFreeMdl( Mdl2 );
+ }
+
+ //
+ // Cleanup the compression context.
+ //
+
+ NtfsDeallocateCompressionBuffer( Irp, &CompressionContext );
+ }
+
+ //
+ // Now set up the final byte count if we got success
+ //
+
+ if (Wait && NT_SUCCESS(Status)) {
+
+ Irp->IoStatus.Information = OriginalByteCount;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsNonCachedIo -> %08lx\n", Status) );
+ return Status;
+}
+
+
+VOID
+NtfsNonCachedNonAlignedIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the non-cached disk io described in its parameters.
+ This routine differs from the above in that the range does not have to be
+ sector aligned. This accomplished with the use of intermediate buffers.
+
+ Currently only read is supported.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Irp - Supplies the requesting Irp.
+
+ Scb - Provides the stream to act on.
+
+ StartingVbo - The starting point for the operation.
+
+ ByteCount - The lengh of the operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // Declare some local variables for enumeration through the
+ // runs of the file, and an array to store parameters for
+ // parallel I/Os
+ //
+
+ LBO NextLbo;
+ LCN NextLcn;
+ ULONG NextLcnOffset;
+
+ VCN StartingVcn;
+
+ LONGLONG NextClusterCount;
+ BOOLEAN NextIsAllocated;
+
+ ULONG SectorSize;
+ ULONG BytesToCopy;
+ ULONG OriginalByteCount;
+ VBO OriginalStartingVbo;
+
+ PUCHAR UserBuffer;
+ PUCHAR DiskBuffer = NULL;
+
+ PMDL Mdl;
+ PMDL SavedMdl;
+ PVOID SavedUserBuffer;
+
+ PVCB Vcb = Scb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsNonCachedNonAlignedRead\n") );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("StartingVbo = %016I64x\n", StartingVbo) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", ByteCount) );
+
+ //
+ // ***Temp***
+ //
+
+ ASSERT(Scb->CompressionUnit == 0);
+
+ //
+ // Initialize some locals.
+ //
+
+ OriginalByteCount = ByteCount;
+ OriginalStartingVbo = StartingVbo;
+ SectorSize = Vcb->BytesPerSector;
+
+ //
+ // For nonbuffered I/O, we need the buffer locked in all
+ // cases.
+ //
+ // This call may raise. If this call succeeds and a subsequent
+ // condition is raised, the buffers are unlocked automatically
+ // by the I/O system when the request is completed, via the
+ // Irp->MdlAddress field.
+ //
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ IoWriteAccess,
+ IoGetCurrentIrpStackLocation(Irp)->Parameters.Read.Length );
+
+ UserBuffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Allocate the local buffer. Round to pages to avoid any device alignment
+ // problems.
+ //
+
+ DiskBuffer = NtfsAllocatePool( NonPagedPool,
+ ROUND_TO_PAGES( SectorSize ));
+
+ //
+ // We use a try block here to ensure the buffer is freed, and to
+ // fill in the correct byte count in the Iosb.Information field.
+ //
+
+ try {
+
+ //
+ // If the beginning of the request was not aligned correctly, read in
+ // the first part first.
+ //
+
+ if (((ULONG)StartingVbo) & (SectorSize - 1)) {
+
+ ULONG SectorOffset;
+
+ //
+ // Try to lookup the first run.
+ //
+
+ StartingVcn = Int64ShraMod32(StartingVbo, Vcb->ClusterShift);
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ NULL,
+ NULL );
+
+ //
+ // We just added the allocation, thus there must be at least
+ // one entry in the mcb corresponding to our write, ie.
+ // NextIsAllocated must be true. If not, the pre-existing file
+ // must have an allocation error.
+ //
+
+ if (!NextIsAllocated) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Adjust for any Lcn offset to the start of the sector we want.
+ //
+
+ NextLcnOffset = ((ULONG)StartingVbo) & ~(SectorSize - 1);
+ NextLcnOffset &= Vcb->ClusterMask;
+ NextLbo = Int64ShllMod32(NextLcn, Vcb->ClusterShift);
+ NextLbo = NextLbo + NextLcnOffset;
+
+ NtfsSingleNonAlignedSync( IrpContext,
+ Vcb,
+ Scb,
+ DiskBuffer,
+ StartingVbo + NextLcnOffset,
+ NextLbo,
+ SectorSize,
+ Irp );
+
+ if (!NT_SUCCESS( Irp->IoStatus.Status )) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Now copy the part of the first sector that we want to the user
+ // buffer.
+ //
+
+ SectorOffset = ((ULONG)StartingVbo) & (SectorSize - 1);
+
+ BytesToCopy = (ByteCount >= SectorSize - SectorOffset
+ ? SectorSize - SectorOffset
+ : ByteCount);
+
+ RtlCopyMemory( UserBuffer,
+ DiskBuffer + SectorOffset,
+ BytesToCopy );
+
+ StartingVbo = StartingVbo + BytesToCopy;
+
+ ByteCount -= BytesToCopy;
+
+ if (ByteCount == 0) {
+
+ try_return( NOTHING );
+ }
+ }
+
+ ASSERT( (((ULONG)StartingVbo) & (SectorSize - 1)) == 0 );
+
+ //
+ // If there is a tail part that is not sector aligned, read it.
+ //
+
+ if (ByteCount & (SectorSize - 1)) {
+
+ VBO LastSectorVbo;
+
+ LastSectorVbo = StartingVbo + (ByteCount & ~(SectorSize - 1));
+
+ //
+ // Try to lookup the last part of the requested range.
+ //
+
+ StartingVcn = Int64ShraMod32(LastSectorVbo, Vcb->ClusterShift);
+
+ NextIsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ &NextLcn,
+ &NextClusterCount,
+ NULL,
+ NULL );
+
+ //
+ // We just added the allocation, thus there must be at least
+ // one entry in the mcb corresponding to our write, ie.
+ // NextIsAllocated must be true. If not, the pre-existing file
+ // must have an allocation error.
+ //
+
+ if (!NextIsAllocated) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Adjust for any Lcn offset.
+ //
+
+ NextLcnOffset = ((ULONG)LastSectorVbo) & Vcb->ClusterMask;
+ NextLbo = Int64ShllMod32(NextLcn, Vcb->ClusterShift);
+ NextLbo = NextLbo + NextLcnOffset;
+
+ NtfsSingleNonAlignedSync( IrpContext,
+ Vcb,
+ Scb,
+ DiskBuffer,
+ LastSectorVbo + NextLcnOffset,
+ NextLbo,
+ SectorSize,
+ Irp );
+
+ if (!NT_SUCCESS( Irp->IoStatus.Status )) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Now copy over the part of this last sector that we need.
+ //
+
+ BytesToCopy = ByteCount & (SectorSize - 1);
+
+ UserBuffer += (ULONG)(LastSectorVbo - OriginalStartingVbo);
+
+ RtlCopyMemory( UserBuffer, DiskBuffer, BytesToCopy );
+
+ ByteCount -= BytesToCopy;
+
+ if (ByteCount == 0) {
+
+ try_return( NOTHING );
+ }
+ }
+
+ ASSERT( ((((ULONG)StartingVbo) | ByteCount) & (SectorSize - 1)) == 0 );
+
+ //
+ // Now build a Mdl describing the sector aligned balance of the transfer,
+ // and put it in the Irp, and read that part.
+ //
+
+ SavedMdl = Irp->MdlAddress;
+ Irp->MdlAddress = NULL;
+
+ SavedUserBuffer = Irp->UserBuffer;
+
+ Irp->UserBuffer = (PUCHAR)MmGetMdlVirtualAddress( SavedMdl ) +
+ (ULONG)(StartingVbo - OriginalStartingVbo);
+
+
+ Mdl = IoAllocateMdl(Irp->UserBuffer,
+ ByteCount,
+ FALSE,
+ FALSE,
+ Irp);
+
+ if (Mdl == NULL) {
+
+ Irp->MdlAddress = SavedMdl;
+ Irp->UserBuffer = SavedUserBuffer;
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ IoBuildPartialMdl(SavedMdl,
+ Mdl,
+ Irp->UserBuffer,
+ ByteCount);
+
+ //
+ // Try to read in the pages.
+ //
+
+ try {
+
+ NtfsNonCachedIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ ByteCount,
+ FALSE );
+
+ } finally {
+
+ IoFreeMdl( Irp->MdlAddress );
+
+ Irp->MdlAddress = SavedMdl;
+ Irp->UserBuffer = SavedUserBuffer;
+ }
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsFreePool( DiskBuffer );
+
+ if ( !AbnormalTermination() && NT_SUCCESS(Irp->IoStatus.Status) ) {
+
+ Irp->IoStatus.Information = OriginalByteCount;
+
+ //
+ // We now flush the user's buffer to memory.
+ //
+
+ KeFlushIoBuffers( Irp->MdlAddress, TRUE, FALSE );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsNonCachedNonAlignedRead -> VOID\n") );
+ return;
+}
+
+
+BOOLEAN
+NtfsVerifyAndRevertUsaBlock (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PVOID Buffer,
+ IN ULONG Length,
+ IN LONGLONG FileOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will revert the bytes in all of the structures protected by
+ update sequence arrays. It copies the bytes from each Usa to the
+ separate blocks protected.
+
+ If a structure does not verify correctly, then it's signature is set
+ to BaadSignature.
+
+Arguments:
+
+ Buffer - This is the pointer to the start of the buffer to recover.
+
+Return Value:
+
+ FALSE - if at least one block did not verify correctly and received a BaadSignature
+ TRUE - if no blocks received a BaadSignature
+
+--*/
+
+{
+ PMULTI_SECTOR_HEADER MultiSectorHeader;
+ PUSHORT SequenceArray;
+ PUSHORT SequenceNumber;
+ ULONG StructureSize;
+ USHORT CountBlocks;
+ PUSHORT ProtectedUshort;
+ BOOLEAN Result = TRUE;
+ PVCB Vcb = Scb->Vcb;
+ ULONG BytesLeft = Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsVerifyAndRevertUsaBlock: Entered\n") );
+
+ //
+ // Cast the buffer pointer to a Multi-Sector-Header and verify that this
+ // block has been initialized.
+ //
+
+ MultiSectorHeader = (PMULTI_SECTOR_HEADER) Buffer;
+
+ //
+ // Get the the number of blocks, based on what type of stream it is.
+ // First check for Mft or Log file.
+ //
+
+ if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ ASSERT((Scb == Vcb->MftScb) || (Scb == Vcb->Mft2Scb));
+
+ StructureSize = Vcb->BytesPerFileRecordSegment;
+
+ } else if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) {
+
+ ASSERT( Scb == Vcb->LogFileScb );
+
+ //
+ // On the first pass through the log file, we see all -1,
+ // and we just want to let it go.
+ //
+
+ if (*(PULONG)&MultiSectorHeader->Signature == MAXULONG) {
+ DebugTrace( -1, Dbg, ("NtfsVerifyAndRevertUsaBlock: (Virgin Log)\n") );
+ return TRUE;
+ }
+
+ CountBlocks = (USHORT)(MultiSectorHeader->UpdateSequenceArraySize - 1);
+ StructureSize = CountBlocks * SEQUENCE_NUMBER_STRIDE;
+
+ //
+ // Check for plausibility and otherwise use page size.
+ //
+
+ if ((StructureSize != 0x1000) && (StructureSize != 0x2000) && (StructureSize != PAGE_SIZE)) {
+
+ StructureSize = PAGE_SIZE;
+ }
+
+ //
+ // Otherwise it is an index, so we can get the count out of the Scb.
+ //
+
+ } else {
+
+ StructureSize = Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ ASSERT((StructureSize == 0x800) || (StructureSize == 0x1000) || (StructureSize == 0x400));
+ ASSERT((Length & (StructureSize - 1)) == 0);
+ }
+
+ CountBlocks = (USHORT)(StructureSize / SEQUENCE_NUMBER_STRIDE);
+
+ //
+ // Loop through all of the multi-sector blocks in this transfer.
+ //
+
+ do {
+
+ //
+ // Uninitialized log file pages always must contain MAXULONG, which is
+ // not a valid signature. Do not do the check if we see MAXULONG. Also
+ // since we may have read random uninitialized data, we must check every
+ // possible field that could cause us to fault or go outside of the block,
+ // and also not check in this case.
+ //
+
+ //
+ // For 0 or MAXULONG we assume the value is "expected", and we do not
+ // want to replace with the BaadSignature, just move on.
+ //
+
+ if ((*(PULONG)&MultiSectorHeader->Signature == MAXULONG) ||
+ (*(PULONG)&MultiSectorHeader->Signature == 0)) {
+
+ NOTHING;
+
+ } else if ((CountBlocks == (USHORT)(MultiSectorHeader->UpdateSequenceArraySize - 1)) &&
+ !FlagOn(MultiSectorHeader->UpdateSequenceArrayOffset, 1) &&
+ ((ULONG)MultiSectorHeader->UpdateSequenceArrayOffset <
+ (StructureSize - (CountBlocks + 1) * sizeof(USHORT))) &&
+ (StructureSize <= BytesLeft)) {
+
+ ULONG CountToGo = CountBlocks;
+
+ //
+ // Compute the array offset and recover the current sequence number.
+ //
+
+ SequenceNumber = (PUSHORT)Add2Ptr( MultiSectorHeader,
+ MultiSectorHeader->UpdateSequenceArrayOffset );
+
+ SequenceArray = SequenceNumber + 1;
+
+ //
+ // We now walk through each block, and insure that the last byte in each
+ // block matches the sequence number.
+ //
+
+ ProtectedUshort = (PUSHORT) (Add2Ptr( MultiSectorHeader,
+ SEQUENCE_NUMBER_STRIDE - sizeof( USHORT )));
+
+ //
+ // Loop to test for the correct sequence numbers and restore the
+ // sequence numbers.
+ //
+
+ do {
+
+ //
+ // If the sequence number does not check, then raise if the record
+ // is not allocated. If we do not raise, i.e. the routine returns,
+ // then smash the signature so we can easily tell the record is not
+ // allocated.
+ //
+
+ if (*ProtectedUshort != *SequenceNumber) {
+
+ //
+ // We do nothing except exit if this is the log file and
+ // the signature is the chkdsk signature.
+ //
+
+ if ((Scb != Vcb->LogFileScb) ||
+ (*(PULONG)MultiSectorHeader->Signature != *(PULONG)ChkdskSignature)) {
+
+ *(PULONG)MultiSectorHeader->Signature = *(PULONG)BaadSignature;
+ Result = FALSE;
+ }
+
+ break;
+
+ } else {
+
+ *ProtectedUshort = *SequenceArray++;
+ }
+
+ ProtectedUshort += (SEQUENCE_NUMBER_STRIDE / sizeof( USHORT ));
+
+ } while (--CountToGo != 0);
+
+ //
+ // If this is the log file, we report an error unless the current
+ // signature is the chkdsk signature.
+ //
+
+ } else if (Scb == Vcb->LogFileScb) {
+
+ if (*(PULONG)MultiSectorHeader->Signature != *(PULONG)ChkdskSignature) {
+
+ *(PULONG)MultiSectorHeader->Signature = *(PULONG)BaadSignature;
+ Result = FALSE;
+ }
+
+ break;
+
+ } else {
+
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG ClusterCount;
+ BOOLEAN IsAllocated;
+
+ Vcn = LlClustersFromBytesTruncate( Vcb, FileOffset );
+
+ IsAllocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ Vcn,
+ &Lcn,
+ &ClusterCount,
+ NULL,
+ NULL );
+
+ if (!IsAllocated &&
+ ( ClusterCount >= LlClustersFromBytes( Vcb, StructureSize))) {
+
+ *(PULONG)MultiSectorHeader->Signature = *(PULONG)HoleSignature;
+ } else {
+ *(PULONG)MultiSectorHeader->Signature = *(PULONG)BaadSignature;
+ Result = FALSE;
+ }
+ }
+
+ //
+ // Now adjust all pointers and counts before looping back.
+ //
+
+ MultiSectorHeader = (PMULTI_SECTOR_HEADER)Add2Ptr( MultiSectorHeader,
+ StructureSize );
+
+ if (BytesLeft > StructureSize) {
+ BytesLeft -= StructureSize;
+ } else {
+ BytesLeft = 0;
+ }
+ FileOffset = FileOffset + StructureSize;
+
+ } while (BytesLeft != 0);
+
+ DebugTrace( -1, Dbg, ("NtfsVerifyAndRevertUsaBlock: Exit\n") );
+ return Result;
+}
+
+
+VOID
+NtfsTransformUsaBlock (
+ IN PSCB Scb,
+ IN OUT PVOID SystemBuffer,
+ IN OUT PVOID Buffer,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will implement Usa protection for all structures of the
+ transfer passed described by the caller. It does so by copying the last
+ short in each block of each Usa-protected structure to the
+ Usa and storing the current sequence number into each of these bytes.
+
+ It also increments the sequence number in the Usa.
+
+Arguments:
+
+ Buffer - This is the pointer to the start of the structure to transform.
+
+ Length - This is the maximum size for the structure.
+
+Return Value:
+
+ ULONG - This is the length of the transformed structure.
+
+--*/
+
+{
+ PMULTI_SECTOR_HEADER MultiSectorHeader;
+ PUSHORT SequenceArray;
+ PUSHORT SequenceNumber;
+ ULONG StructureSize;
+ USHORT CountBlocks;
+ PUSHORT ProtectedUshort;
+ PVCB Vcb = Scb->Vcb;
+ ULONG BytesLeft = Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsTransformUsaBlock: Entered\n") );
+
+ //
+ // Cast the buffer pointer to a Multi-Sector-Header and verify that this
+ // block has been initialized.
+ //
+
+ MultiSectorHeader = (PMULTI_SECTOR_HEADER) Buffer;
+
+ //
+ // Get the the number of blocks, based on what type of stream it is.
+ // First check for Mft or Log file.
+ //
+
+ if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ ASSERT((Scb == Vcb->MftScb) || (Scb == Vcb->Mft2Scb));
+
+ StructureSize = Vcb->BytesPerFileRecordSegment;
+
+ } else if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) {
+
+ //
+ // For the log file, assume it is right in the record, if the
+ // signature is not MAXULONG below.
+ //
+
+ ASSERT( Scb == Vcb->LogFileScb );
+
+ StructureSize = PAGE_SIZE;
+
+ //
+ // Otherwise it is an index, so we can get the count out of the Scb.
+ //
+
+ } else {
+
+ StructureSize = Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ ASSERT((StructureSize == 0x800) || (StructureSize == 0x1000) || (StructureSize == 0x400));
+ ASSERT((Length & (StructureSize - 1)) == 0);
+ }
+
+ CountBlocks = (USHORT)(StructureSize / SEQUENCE_NUMBER_STRIDE);
+
+ //
+ // Loop through all of the multi-sector blocks in this transfer.
+ //
+
+ do {
+
+ //
+ // Any uninitialized structures will begin with BaadSignature or
+ // MAXULONG, as guaranteed by the Revert routine above.
+ //
+
+ if ((*(PULONG)&MultiSectorHeader->Signature != *(PULONG)BaadSignature) &&
+ (*(PULONG)&MultiSectorHeader->Signature != *(PULONG)HoleSignature) &&
+ (*(PULONG)&MultiSectorHeader->Signature != MAXULONG) &&
+ ((MultiSectorHeader->UpdateSequenceArrayOffset & 1) == 0) &&
+ (MultiSectorHeader->UpdateSequenceArrayOffset < (StructureSize - CountBlocks - CountBlocks))) {
+
+ ULONG CountToGo = CountBlocks;
+
+ //
+ // Compute the array offset and recover the current sequence number.
+ //
+
+ SequenceNumber = (PUSHORT)Add2Ptr( MultiSectorHeader,
+ MultiSectorHeader->UpdateSequenceArrayOffset );
+
+ //
+ // Increment sequence number before the write, both in the buffer
+ // going out and in the original buffer pointed to by SystemBuffer.
+ // Skip numbers with all 0's and all 1's because 0's are produced by
+ // by common failure cases and -1 is used by hot fix.
+ //
+
+ do {
+
+ *SequenceNumber += 1;
+
+ *(PUSHORT)Add2Ptr( SystemBuffer,
+ MultiSectorHeader->UpdateSequenceArrayOffset ) += 1;
+
+ } while ((*SequenceNumber == 0) || (*SequenceNumber == 0xFFFF));
+
+ SequenceArray = SequenceNumber + 1;
+
+ //
+ // We now walk through each block to copy each protected short
+ // to the sequence array, and replacing it by the incremented
+ // sequence number.
+ //
+
+ ProtectedUshort = (PUSHORT) (Add2Ptr( MultiSectorHeader,
+ SEQUENCE_NUMBER_STRIDE - sizeof( USHORT )));
+
+ //
+ // Loop to test for the correct sequence numbers and restore the
+ // sequence numbers.
+ //
+
+ do {
+
+ *SequenceArray++ = *ProtectedUshort;
+ *ProtectedUshort = *SequenceNumber;
+
+ ProtectedUshort += (SEQUENCE_NUMBER_STRIDE / sizeof( USHORT ));
+
+ } while (--CountToGo != 0);
+ }
+
+ //
+ // Now adjust all pointers and counts before looping back.
+ //
+
+ MultiSectorHeader = (PMULTI_SECTOR_HEADER)Add2Ptr( MultiSectorHeader,
+ StructureSize );
+ SystemBuffer = Add2Ptr( SystemBuffer, StructureSize );
+ BytesLeft -= StructureSize;
+
+ } while (BytesLeft != 0);
+
+ DebugTrace( -1, Dbg, ("NtfsTransformUsaBlock: Exit -> %08lx\n", StructureSize) );
+ return;
+}
+
+
+VOID
+NtfsCreateMdlAndBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ThisScb,
+ IN UCHAR NeedTwoBuffers,
+ IN OUT PULONG Length,
+ OUT PMDL *Mdl OPTIONAL,
+ OUT PVOID *Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will allocate a buffer and create an Mdl which describes
+ it. This buffer and Mdl can then be used for an I/O operation, the
+ pages will be locked in memory.
+
+ This routine is intended to be used for cases where large I/Os are
+ required. It attempts to avoid allocations errors and bugchecks by
+ using a reserved buffer scheme. In order for this scheme to work without
+ deadlocks, the calling thread must have all resources acquired that it
+ will need prior to doing the I/O. I.e., this routine itself may acquire
+ a resource which must work as an end resource.
+
+ Examples of callers to this routine are noncached writes to USA streams,
+ and noncached reads and writes to compressed streams. One case to be
+ aware of is the case where a noncached compressed write needs to fault
+ in the rest of a compression unit, in order to write the entire unit.
+ In an extreme case the noncached writer will allocated one reserved buffer,
+ and the noncached read of the rest of the compression unit may need to
+ recursively acquire the resource in this routine and allocate the other
+ reserved buffer.
+
+Arguments:
+
+ ThisScb - Scb for the file where the IO is occurring.
+
+ NeedTwoBuffers - Indicates that this is the request for the a buffer for
+ a transaction which may need two buffers. A value of
+ 0 means only 1 buffer is needed. A value of 1 or 2 indicates
+ that we need two buffers and either buffer 1 or buffer 2
+ should be acquired.
+
+ Length - This is the length needed for this buffer, returns (possibly larger)
+ length allocated.
+
+ Mdl - This is the address to store the address of the Mdl created.
+
+ Buffer - This is the address to store the address of the buffer allocated.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVOID TempBuffer;
+ PMDL TempMdl;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateMdlAndBuffer: Entered\n") );
+
+ ASSERT(*Length <= LARGE_BUFFER_SIZE);
+
+ TempBuffer = NULL;
+ TempMdl = NULL;
+
+ //
+ // If this thread already owns a buffer then call to get the second.
+ //
+ // If there have been no allocation failures recently, and
+ // we can use at least half of a big buffer, then go for
+ // one of our preallocated buffers first.
+ //
+
+ if ((NtfsReservedBufferThread == (PVOID) PsGetCurrentThread()) ||
+ ((*Length >= LARGE_BUFFER_SIZE / 2) && !NtfsBufferAllocationFailure)) {
+
+ //
+ // If we didn't get one then try from pool.
+ //
+
+ if (!NtfsGetReservedBuffer( ThisScb->Fcb, &TempBuffer, Length, NeedTwoBuffers )) {
+
+ TempBuffer = ExAllocatePoolWithTag( NonPagedPoolCacheAligned, *Length, '9ftN' );
+ }
+
+ //
+ // Otherwise try to allocate from pool and then get a reserved buffer if
+ // there have been no allocation errors recently.
+ //
+
+ } else {
+
+ TempBuffer = ExAllocatePoolWithTag( NonPagedPoolCacheAligned, *Length, '9ftN' );
+
+ if ((TempBuffer == NULL) && !NtfsBufferAllocationFailure) {
+
+ NtfsGetReservedBuffer( ThisScb->Fcb, &TempBuffer, Length, NeedTwoBuffers );
+ }
+ }
+
+ //
+ // If we could not allocate a buffer from pool, then
+ // we must stake our claim to a reserved buffer.
+ //
+ // We would like to queue the requests which need a single buffer because
+ // they won't be completely blocked by the owner of multiple buffers.
+ // But if this thread wants multiple buffers and there is already a
+ // thread with multiple buffers then fail this request with LOCK_CONFLICT
+ // in case the current thread is holding some resource needed by the
+ // existing owner.
+ //
+
+ if (TempBuffer == NULL) {
+
+ ExAcquireResourceExclusive( &NtfsReservedBufferResource, TRUE );
+
+ //
+ // Show that we have gotten an allocation failure
+ //
+
+ NtfsBufferAllocationFailure = TRUE;
+
+ //
+ // Loop here until we get a buffer or abort the current request.
+ //
+
+ while (TRUE) {
+
+ KeDelayExecutionThread( KernelMode, FALSE, &NtfsShortDelay );
+
+ if (NtfsGetReservedBuffer( ThisScb->Fcb, &TempBuffer, Length, NeedTwoBuffers )) {
+
+ if (ExGetExclusiveWaiterCount( &NtfsReservedBufferResource ) == 0) {
+
+ NtfsBufferAllocationFailure = FALSE;
+ }
+
+ ExReleaseResource( &NtfsReservedBufferResource );
+ break;
+ }
+
+ //
+ // We will perform some deadlock detection here and raise
+ // STATUS_FILE_LOCK conflict in order to retry this request if
+ // anyone is queued behind the resource. Deadlocks can occur
+ // under the following circumstances when another thread is
+ // blocked behind this resource:
+ //
+ // - Current thread needs two buffers. We can't block the
+ // Needs1 guy which may need to complete before the
+ // current Needs2 guy can proceed. Exception is case
+ // where current thread already has a buffer and we
+ // have a recursive 2 buffer case. In this case we
+ // are only waiting for the third buffer to become
+ // available.
+ //
+ // - Current thread is the lazy writer. Lazy writer will
+ // need buffer for USA transform. He also can own
+ // the BCB resource that might be needed by the current
+ // owner of a buffer.
+ //
+ // - Current thread is operating on the same Fcb as the owner
+ // of any of the buffers.
+ //
+
+ //
+ // If the current thread already owns one of the two buffers then
+ // always allow him to loop. Otherwise perform deadlock detection
+ // if we need 2 buffers or this this is the lazy writer or we
+ // are trying to get the same Fcb already owned by the 2 buffer guy.
+ //
+
+ if ((PsGetCurrentThread() != NtfsReservedBufferThread) &&
+
+ (NeedTwoBuffers ||
+
+ (ThisScb->LazyWriteThread[0] == PsGetCurrentThread()) ||
+ (ThisScb->Fcb == NtfsReserved12Fcb))) {
+
+ //
+ // If no one is waiting then see if we can continue waiting.
+ //
+
+ if (ExGetExclusiveWaiterCount( &NtfsReservedBufferResource ) == 0) {
+
+ //
+ // If there is no one waiting behind us and there is no current
+ // multi-buffer owner, then try again here.
+ //
+
+ if (NtfsReservedBufferThread == NULL) {
+
+ continue;
+ }
+
+ NtfsBufferAllocationFailure = FALSE;
+ }
+
+ ExReleaseResource( &NtfsReservedBufferResource );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_LOCK_CONFLICT, NULL, NULL );
+ }
+ }
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ if (ARGUMENT_PRESENT(Mdl)) {
+
+ //
+ // Allocate an Mdl for this buffer.
+ //
+
+ TempMdl = IoAllocateMdl( TempBuffer,
+ *Length,
+ FALSE,
+ FALSE,
+ NULL );
+
+ if (TempMdl == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Lock the new Mdl in memory.
+ //
+
+ MmBuildMdlForNonPagedPool( TempMdl );
+ *Mdl = TempMdl;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateMdlAndBuffer );
+
+ //
+ // If abnormal termination, back out anything we've done.
+ //
+
+ if (AbnormalTermination()) {
+
+ NtfsDeleteMdlAndBuffer( TempMdl, TempBuffer );
+
+ //
+ // Otherwise, give the Mdl and buffer to the caller.
+ //
+
+ } else {
+
+ *Buffer = TempBuffer;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateMdlAndBuffer: Exit\n") );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsDeleteMdlAndBuffer (
+ IN PMDL Mdl OPTIONAL,
+ IN PVOID Buffer OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will allocate a buffer and create an Mdl which describes
+ it. This buffer and Mdl can then be used for an I/O operation, the
+ pages will be locked in memory.
+
+Arguments:
+
+ Mdl - Address of Mdl to free
+
+ Buffer - This is the address to store the address of the buffer allocated.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // Free Mdl if there is one
+ //
+
+ if (Mdl != NULL) {
+ IoFreeMdl( Mdl );
+ }
+
+ //
+ // Free reserved buffer or pool
+ //
+
+ if (Buffer != NULL) {
+
+ if (!NtfsFreeReservedBuffer( Buffer )) {
+
+ NtfsFreePool( Buffer );
+ }
+ }
+}
+
+
+VOID
+NtfsWriteClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN PVOID Buffer,
+ IN ULONG ClusterCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to write clusters directly to a file. It is
+ needed when converting a resident attribute to non-resident when
+ we can't initialize through the cache manager. This happens when
+ we receive a SetEndOfFile from MM when creating a section for
+ a resident file.
+
+Arguments:
+
+ Vcb - Vcb for this device.
+
+ StartingVbo - This is the starting offset to write to.
+
+ Buffer - Buffer containing the data to write.
+
+ ClusterCount - This is the number of clusters to write.
+
+Return Value:
+
+ None. This routine will raise if the operation is unsuccessful.
+
+--*/
+
+{
+ PIRP NewIrp;
+ IO_STATUS_BLOCK IoStatusBlock;
+ UCHAR MajorFunction;
+ BOOLEAN LockedUserBuffer;
+ PNTFS_IO_CONTEXT PreviousContext;
+ ULONG Flags;
+
+ NTFS_IO_CONTEXT LocalContext;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsWriteClusters: Entered\n") );
+ DebugTrace( 0, Dbg, ("StartingVbo -> %016I64x\n", StartingVbo) );
+ DebugTrace( 0, Dbg, ("Buffer -> %08lx\n", Buffer) );
+ DebugTrace( 0, Dbg, ("ClusterCount -> %08lx\n", ClusterCount) );
+
+ //
+ // Initialize the local variables.
+ //
+
+ NewIrp = NULL;
+
+ MajorFunction = IrpContext->MajorFunction;
+
+ LockedUserBuffer = FALSE;
+
+ //
+ // Force this operation to be synchronous.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // Get an Io context block.
+ //
+
+ PreviousContext = IrpContext->Union.NtfsIoContext;
+
+ IrpContext->Union.NtfsIoContext = &LocalContext;
+ Flags = IrpContext->Flags;
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ //
+ // Use a try-finally so we can clean up properly.
+ //
+
+ try {
+
+ PIO_STACK_LOCATION IrpSp;
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+
+ NewIrp = IoBuildAsynchronousFsdRequest( IRP_MJ_WRITE,
+ Vcb->Vpb->DeviceObject,
+ Buffer,
+ BytesFromClusters( Vcb, ClusterCount ),
+ (PLARGE_INTEGER)&StartingVbo,
+ &IoStatusBlock );
+
+ if (NewIrp == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // We now have an Irp, we want to make it look as though it is part of
+ // the current call. We need to adjust the Irp stack to update this.
+ //
+
+ NewIrp->CurrentLocation--;
+
+ IrpSp = IoGetNextIrpStackLocation( NewIrp );
+
+ NewIrp->Tail.Overlay.CurrentStackLocation = IrpSp;
+
+ IrpSp->DeviceObject = Vcb->Vpb->DeviceObject;
+
+ //
+ // Put our buffer in the Irp and lock it as well.
+ //
+
+ NewIrp->UserBuffer = Buffer;
+
+ NtfsLockUserBuffer( IrpContext,
+ NewIrp,
+ IoReadAccess,
+ BytesFromClusters( Vcb, ClusterCount ));
+
+ LockedUserBuffer = TRUE;
+
+ //
+ // Put the write code into the IrpContext.
+ //
+
+ IrpContext->MajorFunction = IRP_MJ_WRITE;
+
+ //
+ // Write the data to the disk.
+ //
+
+ NtfsNonCachedIo( IrpContext,
+ NewIrp,
+ Scb,
+ StartingVbo,
+ BytesFromClusters(Vcb, ClusterCount),
+ FALSE );
+
+ //
+ // If we encountered an error or didn't write all the bytes, then
+ // raise the error code. We use the IoStatus in the Irp instead of
+ // our structure since this Irp will not be completed.
+ //
+
+ if (!NT_SUCCESS( NewIrp->IoStatus.Status )) {
+
+ DebugTrace( 0, Dbg, ("Couldn't write clusters to disk -> %08lx\n", NewIrp->IoStatus.Status) );
+
+ NtfsRaiseStatus( IrpContext, NewIrp->IoStatus.Status, NULL, NULL );
+
+ } else if (NewIrp->IoStatus.Information != BytesFromClusters( Vcb, ClusterCount )) {
+
+ DebugTrace( 0, Dbg, ("Couldn't write all byes to disk\n") );
+ NtfsRaiseStatus( IrpContext, STATUS_UNEXPECTED_IO_ERROR, NULL, NULL );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsWriteClusters );
+
+ //
+ // Recover the Io Context and remember if it is from pool.
+ //
+
+ IrpContext->Union.NtfsIoContext = PreviousContext;
+
+ SetFlag( IrpContext->Flags, FlagOn( Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT ));
+
+ IrpContext->MajorFunction = MajorFunction;
+
+ //
+ // If we allocated an Irp, we need to deallocate it. We also
+ // have to return the correct function code to the Irp Context.
+ //
+
+ if (NewIrp != NULL) {
+
+ //
+ // If there is an Mdl we free that first.
+ //
+
+ if (NewIrp->MdlAddress != NULL) {
+
+ if (LockedUserBuffer) {
+
+ MmUnlockPages( NewIrp->MdlAddress );
+ }
+
+ IoFreeMdl( NewIrp->MdlAddress );
+ }
+
+ IoFreeIrp( NewIrp );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsWriteClusters: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsMultipleAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP MasterIrp,
+ IN ULONG MultipleIrpCount,
+ IN PIO_RUN IoRuns
+ )
+
+/*++
+
+Routine Description:
+
+ This routine first does the initial setup required of a Master IRP that is
+ going to be completed using associated IRPs. This routine should not
+ be used if only one async request is needed, instead the single read/write
+ async routines should be called.
+
+ A context parameter is initialized, to serve as a communications area
+ between here and the common completion routine. This initialization
+ includes allocation of a spinlock. The spinlock is deallocated in the
+ NtfsWaitSync routine, so it is essential that the caller insure that
+ this routine is always called under all circumstances following a call
+ to this routine.
+
+ Next this routine reads or writes one or more contiguous sectors from
+ a device asynchronously, and is used if there are multiple reads for a
+ master IRP. A completion routine is used to synchronize with the
+ completion of all of the I/O requests started by calls to this routine.
+
+ Also, prior to calling this routine the caller must initialize the
+ IoStatus field in the Context, with the correct success status and byte
+ count which are expected if all of the parallel transfers complete
+ successfully. After return this status will be unchanged if all requests
+ were, in fact, successful. However, if one or more errors occur, the
+ IoStatus will be modified to reflect the error status and byte count
+ from the first run (by Vbo) which encountered an error. I/O status
+ from all subsequent runs will not be indicated.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ DeviceObject - Supplies the device to be read
+
+ MasterIrp - Supplies the master Irp.
+
+ MulitpleIrpCount - Supplies the number of multiple async requests
+ that will be issued against the master irp.
+
+ IoRuns - Supplies an array containing the Vbo, Lbo, BufferOffset, and
+ ByteCount for all the runs to executed in parallel.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIRP Irp;
+ PIO_STACK_LOCATION IrpSp;
+ PMDL Mdl;
+ BOOLEAN Wait;
+ PNTFS_IO_CONTEXT Context;
+
+ ULONG UnwindRunCount = 0;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMultipleAsync\n") );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("DeviceObject = %08lx\n", DeviceObject) );
+ DebugTrace( 0, Dbg, ("MasterIrp = %08lx\n", MasterIrp) );
+ DebugTrace( 0, Dbg, ("MultipleIrpCount = %08lx\n", MultipleIrpCount) );
+ DebugTrace( 0, Dbg, ("IoRuns = %08lx\n", IoRuns) );
+
+ //
+ // Set up things according to whether this is truely async.
+ //
+
+ Wait = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ Context = IrpContext->Union.NtfsIoContext;
+
+ try {
+
+ //
+ // Initialize Context, for use in Read/Write Multiple Asynch.
+ //
+
+ Context->MasterIrp = MasterIrp;
+
+ //
+ // Itterate through the runs, doing everything that can fail
+ //
+
+ for ( UnwindRunCount = 0;
+ UnwindRunCount < MultipleIrpCount;
+ UnwindRunCount++ ) {
+
+ //
+ // Create an associated IRP, making sure there is one stack entry for
+ // us, as well.
+ //
+
+ IoRuns[UnwindRunCount].SavedIrp = NULL;
+
+ Irp = IoMakeAssociatedIrp( MasterIrp, (CCHAR)(DeviceObject->StackSize + 1) );
+
+ if (Irp == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ IoRuns[UnwindRunCount].SavedIrp = Irp;
+
+ //
+ // Allocate and build a partial Mdl for the request.
+ //
+
+ Mdl = IoAllocateMdl( (PCHAR)MasterIrp->UserBuffer +
+ IoRuns[UnwindRunCount].BufferOffset,
+ IoRuns[UnwindRunCount].ByteCount,
+ FALSE,
+ FALSE,
+ Irp );
+
+ if (Mdl == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Sanity Check
+ //
+
+ ASSERT( Mdl == Irp->MdlAddress );
+
+ IoBuildPartialMdl( MasterIrp->MdlAddress,
+ Mdl,
+ (PCHAR)MasterIrp->UserBuffer +
+ IoRuns[UnwindRunCount].BufferOffset,
+ IoRuns[UnwindRunCount].ByteCount );
+
+ //
+ // Get the first IRP stack location in the associated Irp
+ //
+
+ IoSetNextIrpStackLocation( Irp );
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to describe our read.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = IoRuns[UnwindRunCount].ByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = IoRuns[UnwindRunCount].StartingVbo;
+
+ //
+ // If this Irp is the result of a WriteThough operation,
+ // tell the device to write it through.
+ //
+
+ if (FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH)) {
+
+ SetFlag( IrpSp->Flags, SL_WRITE_THROUGH );
+ }
+
+ //
+ // Set up the completion routine address in our stack frame.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ (Wait
+ ? &NtfsMultiSyncCompletionRoutine
+ : &NtfsMultiAsyncCompletionRoutine),
+ Context,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location in the associated Irp for the disk
+ // driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to do a read from the disk driver.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Flags = Context->IrpSpFlags;
+ IrpSp->Parameters.Read.Length = IoRuns[UnwindRunCount].ByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = IoRuns[UnwindRunCount].StartingLbo;
+ }
+
+ //
+ // We only need to set the associated IRP count in the master irp to
+ // make it a master IRP. But we set the count to one more than our
+ // caller requested, because we do not want the I/O system to complete
+ // the I/O. We also set our own count.
+ //
+
+ Context->IrpCount = MultipleIrpCount;
+ MasterIrp->AssociatedIrp.IrpCount = MultipleIrpCount;
+
+ if (Wait) {
+
+ MasterIrp->AssociatedIrp.IrpCount += 1;
+ }
+
+ //
+ // Now that all the dangerous work is done, issue the Io requests
+ //
+
+ for (UnwindRunCount = 0;
+ UnwindRunCount < MultipleIrpCount;
+ UnwindRunCount++) {
+
+ Irp = IoRuns[UnwindRunCount].SavedIrp;
+
+ //
+ // If IoCallDriver returns an error, it has completed the Irp
+ // and the error will be caught by our completion routines
+ // and dealt with as a normal IO error.
+ //
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+ }
+
+ } finally {
+
+ ULONG i;
+
+ DebugUnwind( NtfsMultipleAsync );
+
+ //
+ // Only allocating the spinlock, making the associated Irps
+ // and allocating the Mdls can fail.
+ //
+
+ if (AbnormalTermination()) {
+
+ //
+ // Unwind
+ //
+
+ for (i = 0; i <= UnwindRunCount; i++) {
+
+ if ((Irp = IoRuns[i].SavedIrp) != NULL) {
+
+ if (Irp->MdlAddress != NULL) {
+
+ IoFreeMdl( Irp->MdlAddress );
+ }
+
+ IoFreeIrp( Irp );
+ }
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsMultipleAsync -> VOID\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsSingleAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN LBO Lbo,
+ IN ULONG ByteCount,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads or writes one or more contiguous sectors from a device
+ asynchronously, and is used if there is only one read necessary to
+ complete the IRP. It implements the read by simply filling
+ in the next stack frame in the Irp, and passing it on. The transfer
+ occurs to the single buffer originally specified in the user request.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ DeviceObject - Supplies the device to read
+
+ Lbo - Supplies the starting Logical Byte Offset to begin reading from
+
+ ByteCount - Supplies the number of bytes to read from the device
+
+ Irp - Supplies the master Irp to associated with the async
+ request.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSingleAsync\n") );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("DeviceObject = %08lx\n", DeviceObject) );
+ DebugTrace( 0, Dbg, ("Lbo = %016I64x\n", Lbo) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", ByteCount) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Set up the completion routine address in our stack frame.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )
+ ? &NtfsSingleSyncCompletionRoutine
+ : &NtfsSingleAsyncCompletionRoutine),
+ IrpContext->Union.NtfsIoContext,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location in the associated Irp for the disk
+ // driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to do a read from the disk driver.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = ByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = Lbo;
+ IrpSp->Flags = IrpContext->Union.NtfsIoContext->IrpSpFlags;
+
+ //
+ // If this Irp is the result of a WriteThough operation,
+ // tell the device to write it through.
+ //
+
+ if (FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH)) {
+
+ SetFlag( IrpSp->Flags, SL_WRITE_THROUGH );
+ }
+
+ //
+ // Issue the Io request
+ //
+
+ //
+ // If IoCallDriver returns an error, it has completed the Irp
+ // and the error will be caught by our completion routines
+ // and dealt with as a normal IO error.
+ //
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSingleAsync -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsWaitSync (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine waits for one or more previously started I/O requests
+ from the above routines, by simply waiting on the event.
+
+Arguments:
+
+ Context - Pointer to Context used in previous call(s) to be waited on.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsWaitSync: Entered\n") );
+
+ KeWaitForSingleObject( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL );
+
+ KeClearEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent );
+
+ DebugTrace( -1, Dbg, ("NtfsWaitSync -> VOID\n") );
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsMultiAsyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+/*++
+
+Routine Description:
+
+ This is the completion routine for all asynchronous reads and writes
+ started via NtfsMultipleAsynch. It must synchronize its operation for
+ multiprocessor environments with itself on all other processors, via
+ a spin lock found via the Context parameter.
+
+ The completion routine has has the following responsibilities:
+
+ If the individual request was completed with an error, then
+ this completion routine must see if this is the first error
+ (essentially by Vbo), and if so it must correctly reduce the
+ byte count and remember the error status in the Context.
+
+ If the IrpCount goes to 1, then it sets the event in the Context
+ parameter to signal the caller that all of the asynch requests
+ are done.
+
+Arguments:
+
+ DeviceObject - Pointer to the file system device object.
+
+ Irp - Pointer to the associated Irp which is being completed. (This
+ Irp will no longer be accessible after this routine returns.)
+
+ Contxt - The context parameter which was specified for all of
+ the multiple asynch I/O requests for this MasterIrp.
+
+Return Value:
+
+ Currently always returns STATUS_SUCCESS.
+
+--*/
+
+{
+
+ PNTFS_IO_CONTEXT Context = Contxt;
+ PIRP MasterIrp = Context->MasterIrp;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ BOOLEAN CompleteRequest = TRUE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsMultiAsyncCompletionRoutine, Context = %08lx\n", Context) );
+
+ //
+ // If we got an error (or verify required), remember it in the Irp
+ //
+
+ MasterIrp = Context->MasterIrp;
+
+ if (!NT_SUCCESS( Irp->IoStatus.Status )) {
+
+ MasterIrp->IoStatus = Irp->IoStatus;
+ }
+
+ //
+ // Decrement IrpCount and see if it goes to zero.
+ //
+
+ if (InterlockedDecrement( &Context->IrpCount ) == 0) {
+
+ PERESOURCE Resource;
+ ERESOURCE_THREAD ResourceThreadId;
+
+ //
+ // Capture the resource values out of the context to prevent
+ // colliding with the Fsp thread if we post this.
+ //
+
+ Resource = Context->Wait.Async.Resource;
+ ResourceThreadId = Context->Wait.Async.ResourceThreadId;
+
+ //
+ // Mark the master Irp pending
+ //
+
+ IoMarkIrpPending( MasterIrp );
+
+ //
+ // If this request was successful or we posted an async paging io
+ // request then complete this irp.
+ //
+
+ if (FT_SUCCESS( MasterIrp->IoStatus.Status )) {
+
+ MasterIrp->IoStatus.Information =
+ Context->Wait.Async.RequestedByteCount;
+
+ //
+ // Go ahead an mark the File object to indicate that we performed
+ // either a read or write if this is not a paging io operation.
+ //
+
+ if (!Context->PagingIo &&
+ (IrpSp->FileObject != NULL)) {
+
+ if (IrpSp->MajorFunction == IRP_MJ_READ) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
+
+ } else {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_MODIFIED );
+ }
+ }
+
+ //
+ // If we had an error and will hot fix, we simply post the entire
+ // request.
+ //
+
+ } else if (!Context->PagingIo) {
+
+ PIRP_CONTEXT IrpContext = NULL;
+
+ //
+ // We need an IrpContext and then have to post the request.
+ // Use a try_except in case we fail the request for an IrpContext.
+ //
+
+ CompleteRequest = FALSE;
+
+ try {
+
+ IrpContext = NtfsCreateIrpContext( MasterIrp, TRUE );
+ IrpContext->Union.NtfsIoContext = Context;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ NtfsPostRequest( IrpContext, MasterIrp );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // Just give up.
+ //
+
+ CompleteRequest = TRUE;
+
+ if (IrpContext) {
+
+ //
+ // We cleanup the context below.
+ //
+
+ IrpContext->Union.NtfsIoContext = NULL;
+ NtfsDeleteIrpContext( &IrpContext );
+ }
+ }
+ }
+
+ //
+ // Now release the resource
+ //
+
+ if (Resource != NULL) {
+
+ ExReleaseResourceForThread( Resource,
+ ResourceThreadId );
+ }
+
+ if (CompleteRequest) {
+
+ //
+ // and finally, free the context record.
+ //
+
+ ExFreeToNPagedLookasideList( &NtfsIoContextLookasideList, Context );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMultiAsyncCompletionRoutine\n") );
+
+ //
+ // Return more processing required if we don't want the Irp to go away.
+ //
+
+ if (CompleteRequest) {
+
+ return STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // We need to cleanup the associated Irp and its Mdl.
+ //
+
+ IoFreeMdl( Irp->MdlAddress );
+ IoFreeIrp( Irp );
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+ }
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsMultiSyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+/*++
+
+Routine Description:
+
+ This is the completion routine for all synchronous reads and writes
+ started via NtfsMultipleAsynch. It must synchronize its operation for
+ multiprocessor environments with itself on all other processors, via
+ a spin lock found via the Context parameter.
+
+ The completion routine has has the following responsibilities:
+
+ If the individual request was completed with an error, then
+ this completion routine must see if this is the first error
+ (essentially by Vbo), and if so it must correctly reduce the
+ byte count and remember the error status in the Context.
+
+ If the IrpCount goes to 1, then it sets the event in the Context
+ parameter to signal the caller that all of the asynch requests
+ are done.
+
+Arguments:
+
+ DeviceObject - Pointer to the file system device object.
+
+ Irp - Pointer to the associated Irp which is being completed. (This
+ Irp will no longer be accessible after this routine returns.)
+
+ Contxt - The context parameter which was specified for all of
+ the multiple asynch I/O requests for this MasterIrp.
+
+Return Value:
+
+ The routine returns STATUS_MORE_PROCESSING_REQUIRED so that we can
+ immediately complete the Master Irp without being in a race condition
+ with the IoCompleteRequest thread trying to decrement the IrpCount in
+ the Master Irp.
+
+--*/
+
+{
+
+ PNTFS_IO_CONTEXT Context = Contxt;
+ PIRP MasterIrp = Context->MasterIrp;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsMultiSyncCompletionRoutine, Context = %08lx\n", Context) );
+
+ //
+ // If we got an error (or verify required), remember it in the Irp
+ //
+
+ MasterIrp = Context->MasterIrp;
+
+ if (!NT_SUCCESS( Irp->IoStatus.Status )) {
+
+ MasterIrp->IoStatus = Irp->IoStatus;
+ }
+
+ //
+ // We must do this here since IoCompleteRequest won't get a chance
+ // on this associated Irp.
+ //
+
+ IoFreeMdl( Irp->MdlAddress );
+ IoFreeIrp( Irp );
+
+ if (InterlockedDecrement(&Context->IrpCount) == 0) {
+
+ KeSetEvent( &Context->Wait.SyncEvent, 0, FALSE );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMultiSyncCompletionRoutine -> STATUS_MORE_PROCESSING_REQUIRED\n") );
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsSingleAsyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+/*++
+
+Routine Description:
+
+ This is the completion routine for all asynchronous reads and writes
+ started via NtfsSingleAsynch.
+
+ The completion routine has has the following responsibilities:
+
+ Copy the I/O status from the Irp to the Context, since the Irp
+ will no longer be accessible.
+
+ It sets the event in the Context parameter to signal the caller
+ that all of the asynch requests are done.
+
+Arguments:
+
+ DeviceObject - Pointer to the file system device object.
+
+ Irp - Pointer to the Irp for this request. (This Irp will no longer
+ be accessible after this routine returns.)
+
+ Contxt - The context parameter which was specified in the call to
+ NtfsSingleAsynch.
+
+Return Value:
+
+ Currently always returns STATUS_SUCCESS.
+
+--*/
+
+{
+ PNTFS_IO_CONTEXT Context = Contxt;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ BOOLEAN CompleteRequest = TRUE;
+
+ PERESOURCE Resource;
+ ERESOURCE_THREAD ResourceThreadId;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsSingleAsyncCompletionRoutine, Context = %08lx\n", Context) );
+
+ //
+ // Capture the resource values out of the context to prevent
+ // colliding with the Fsp thread if we post this.
+ //
+
+ Resource = Context->Wait.Async.Resource;
+ ResourceThreadId = Context->Wait.Async.ResourceThreadId;
+
+ //
+ // Mark the Irp pending
+ //
+
+ IoMarkIrpPending( Irp );
+
+ //
+ // Fill in the information field correctedly if this worked.
+ //
+
+ if (FT_SUCCESS( Irp->IoStatus.Status )) {
+
+ Irp->IoStatus.Information = Context->Wait.Async.RequestedByteCount;
+
+ //
+ // Go ahead an mark the File object to indicate that we performed
+ // either a read or write.
+ //
+
+ if (!Context->PagingIo &&
+ (IrpSp->FileObject != NULL)) {
+
+ if (IrpSp->MajorFunction == IRP_MJ_READ) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
+
+ } else {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_MODIFIED );
+ }
+ }
+
+ //
+ // If we had an error and will hot fix, we simply post the entire
+ // request.
+ //
+
+ } else if (!Context->PagingIo) {
+
+ PIRP_CONTEXT IrpContext = NULL;
+
+ //
+ // We need an IrpContext and then have to post the request.
+ // Use a try_except in case we fail the request for an IrpContext.
+ //
+
+ CompleteRequest = FALSE;
+
+ try {
+
+ IrpContext = NtfsCreateIrpContext( Irp, TRUE );
+ IrpContext->Union.NtfsIoContext = Context;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ NtfsPostRequest( IrpContext, Irp );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // Just give up.
+ //
+
+ CompleteRequest = TRUE;
+
+ if (IrpContext) {
+
+ //
+ // We cleanup the context below.
+ //
+
+ IrpContext->Union.NtfsIoContext = NULL;
+ NtfsDeleteIrpContext( &IrpContext );
+ }
+ }
+ }
+
+ //
+ // Now release the resource
+ //
+
+ if (Resource != NULL) {
+
+ ExReleaseResourceForThread( Resource,
+ ResourceThreadId );
+ }
+
+ //
+ // and finally, free the context record.
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSingleAsyncCompletionRoutine -> STATUS_SUCCESS\n") );
+
+ if (CompleteRequest) {
+
+ ExFreeToNPagedLookasideList( &NtfsIoContextLookasideList, Context );
+ return STATUS_SUCCESS;
+
+ } else {
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsSingleSyncCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+/*++
+
+Routine Description:
+
+ This is the completion routine for all reads and writes started via
+ NtfsSingleAsynch.
+
+ The completion routine has has the following responsibilities:
+
+ Copy the I/O status from the Irp to the Context, since the Irp
+ will no longer be accessible.
+
+ It sets the event in the Context parameter to signal the caller
+ that all of the asynch requests are done.
+
+Arguments:
+
+ DeviceObject - Pointer to the file system device object.
+
+ Irp - Pointer to the Irp for this request. (This Irp will no longer
+ be accessible after this routine returns.)
+
+ Contxt - The context parameter which was specified in the call to
+ NtfsSingleAsynch.
+
+Return Value:
+
+ The routine returns STATUS_MORE_PROCESSING_REQUIRED so that we can
+ immediately complete the Master Irp without being in a race condition
+ with the IoCompleteRequest thread trying to decrement the IrpCount in
+ the Master Irp.
+
+--*/
+
+{
+ PNTFS_IO_CONTEXT Context = Contxt;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsSingleCompletionRoutine, Context = %08lx\n", Context) );
+
+ KeSetEvent( &Context->Wait.SyncEvent, 0, FALSE );
+
+ DebugTrace( -1, Dbg, ("NtfsSingleCompletionRoutine -> STATUS_MORE_PROCESSING_REQUIRED\n") );
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+
+//
+// Local support routine.
+//
+
+NTSTATUS
+NtfsPagingFileCompletionRoutine (
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID MasterIrp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the completion routine for all reads and writes started via
+ NtfsPagingFileIo.
+
+ The completion routine has has the following responsibility:
+
+ Since the individual request was completed with an error,
+ this completion routine must stuff it into the master irp.
+
+Arguments:
+
+ DeviceObject - Pointer to the file system device object.
+
+ Irp - Pointer to the associated Irp which is being completed. (This
+ Irp will no longer be accessible after this routine returns.)
+
+ MasterIrp - Pointer to the master Irp. The low order bit in this value will
+ be set if a higher level call is performing a hot-fix.
+
+Return Value:
+
+ Always returns STATUS_SUCCESS.
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsPagingFileCompletionRoutine, MasterIrp = %08lx\n", MasterIrp) );
+
+ if (!FT_SUCCESS(Irp->IoStatus.Status)) {
+
+ VBO BadVbo;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+
+ if (!FsRtlIsTotalDeviceFailure(Irp->IoStatus.Status) &&
+ (Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) &&
+ !FlagOn( ((ULONG) MasterIrp), 0x1 )) {
+
+ BadVbo = IrpSp->Parameters.Read.Key;
+
+ if ((Irp->IoStatus.Status == STATUS_FT_READ_RECOVERY_FROM_BACKUP) ||
+ (Irp->IoStatus.Status == STATUS_FT_WRITE_RECOVERY)) {
+
+ NtfsPostHotFix( Irp,
+ &BadVbo,
+ IrpSp->Parameters.Read.ByteOffset.QuadPart,
+ IrpSp->Parameters.Read.Length,
+ FALSE );
+
+ } else {
+
+ NtfsPostHotFix( Irp,
+ &BadVbo,
+ IrpSp->Parameters.Read.ByteOffset.QuadPart,
+ IrpSp->Parameters.Read.Length,
+ TRUE );
+
+ if (IoGetCurrentIrpStackLocation(Irp)->MajorFunction != IRP_MJ_WRITE) {
+
+ //
+ // Assume the write will eventually succeed, otherwise we are
+ // stuck with this status.
+ //
+
+ ((PIRP)MasterIrp)->IoStatus = Irp->IoStatus;
+ }
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+ }
+ }
+
+ //
+ // If we got an error (or verify required), remember it in the Irp
+ //
+
+ ClearFlag( ((ULONG) MasterIrp), 0x1 );
+ ((PIRP)MasterIrp)->IoStatus = Irp->IoStatus;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsPagingFileCompletionRoutine => (STATUS_SUCCESS)\n") );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsSingleNonAlignedSync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PUCHAR Buffer,
+ IN VBO Vbo,
+ IN LBO Lbo,
+ IN ULONG ByteCount,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads or writes one or more contiguous sectors from a device
+ Synchronously, and does so to a buffer that must come from non paged
+ pool. It saves a pointer to the Irp's original Mdl, and creates a new
+ one describing the given buffer. It implements the read by simply filling
+ in the next stack frame in the Irp, and passing it on. The transfer
+ occurs to the single buffer originally specified in the user request.
+
+ Currently, only reads are supported.
+
+Arguments:
+
+ IrpContext->MajorFunction - Supplies either IRP_MJ_READ or IRP_MJ_WRITE.
+
+ Vcb - Supplies the device to read
+
+ Scb - Supplies the Scb to read
+
+ Buffer - Supplies a buffer from non-paged pool.
+
+ Vbo - Supplies the starting Virtual Block Offset to begin reading from
+
+ Lbo - Supplies the starting Logical Block Offset to begin reading from
+
+ ByteCount - Supplies the number of bytes to read from the device
+
+ Irp - Supplies the master Irp to associated with the async
+ request.
+
+ Context - Asynchronous I/O context structure
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ PMDL Mdl;
+ PMDL SavedMdl;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSingleNonAlignedSync\n") );
+ DebugTrace( 0, Dbg, ("MajorFunction = %08lx\n", IrpContext->MajorFunction) );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Buffer) );
+ DebugTrace( 0, Dbg, ("Lbo = %016I64x\n", Lbo) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", ByteCount) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Create a new Mdl describing the buffer, saving the current one in the
+ // Irp
+ //
+
+ SavedMdl = Irp->MdlAddress;
+
+ Irp->MdlAddress = 0;
+
+ Mdl = IoAllocateMdl( Buffer,
+ ByteCount,
+ FALSE,
+ FALSE,
+ Irp );
+
+ if (Mdl == NULL) {
+
+ Irp->MdlAddress = SavedMdl;
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Lock the new Mdl in memory.
+ //
+
+ try {
+
+ MmProbeAndLockPages( Mdl, KernelMode, IoWriteAccess );
+
+ } finally {
+
+ if ( AbnormalTermination() ) {
+
+ IoFreeMdl( Mdl );
+ }
+ }
+
+ //
+ // Set up the completion routine address in our stack frame.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ &NtfsSingleSyncCompletionRoutine,
+ IrpContext->Union.NtfsIoContext,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location in the associated Irp for the disk
+ // driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to do a read from the disk driver.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = ByteCount;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = Lbo;
+
+ //
+ // Initialize the Kernel Event in the context structure so that the
+ // caller can wait on it. Set remaining pointers to NULL.
+ //
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+
+ //
+ // Issue the read request
+ //
+ // If IoCallDriver returns an error, it has completed the Irp
+ // and the error will be caught by our completion routines
+ // and dealt with as a normal IO error.
+ //
+
+ try {
+
+ (VOID)IoCallDriver( Vcb->TargetDeviceObject, Irp );
+
+ NtfsWaitSync( IrpContext );
+
+ //
+ // See if we need to do a hot fix.
+ //
+
+ if (!FT_SUCCESS(Irp->IoStatus.Status)) {
+
+ IO_RUN IoRun;
+
+ IoRun.StartingVbo = Vbo;
+ IoRun.StartingLbo = Lbo;
+ IoRun.BufferOffset = 0;
+ IoRun.ByteCount = ByteCount;
+ IoRun.SavedIrp = NULL;
+
+ //
+ // Try to fix the problem
+ //
+
+ NtfsFixDataError( IrpContext,
+ Scb,
+ Vcb->TargetDeviceObject,
+ Irp,
+ 1,
+ &IoRun );
+ }
+
+ } finally {
+
+ MmUnlockPages( Mdl );
+
+ IoFreeMdl( Mdl );
+
+ Irp->MdlAddress = SavedMdl;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSingleNonAlignedSync -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsIsReadAheadThread (
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns whether the current thread is doing read ahead.
+
+Arguments:
+
+ None
+
+Return Value:
+
+ FALSE - if the thread is not doing read ahead
+ TRUE - if the thread is doing read ahead
+
+--*/
+
+{
+ PREAD_AHEAD_THREAD ReadAheadThread;
+ PVOID CurrentThread;
+ KIRQL OldIrql;
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &OldIrql );
+
+ CurrentThread = (PVOID)PsGetCurrentThread();
+ ReadAheadThread = (PREAD_AHEAD_THREAD)NtfsData.ReadAheadThreads.Flink;
+
+ //
+ // Scan for our thread, stopping at the end of the list or on the first
+ // NULL. We can stop on the first NULL, since when we free an entry
+ // we move it to the end of the list.
+ //
+
+ while ((ReadAheadThread != (PREAD_AHEAD_THREAD)&NtfsData.ReadAheadThreads) &&
+ (ReadAheadThread->Thread != NULL)) {
+
+ //
+ // Get out if we see our thread.
+ //
+
+ if (ReadAheadThread->Thread == CurrentThread) {
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, OldIrql );
+ return TRUE;
+ }
+ ReadAheadThread = (PREAD_AHEAD_THREAD)ReadAheadThread->Links.Flink;
+ }
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, OldIrql );
+ return FALSE;
+}
+
+
+VOID
+NtfsFixDataError (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP MasterIrp,
+ IN ULONG MultipleIrpCount,
+ IN PIO_RUN IoRuns
+ )
+
+/*
+
+Routine Description:
+
+ This routine is called when a read error, write error, or Usa error
+ is received when doing noncached I/O on a stream. It attempts to
+ recover from Usa errors if FT is present. For bad clusters it attempts
+ to isolate the error to one or more bad clusters, for which hot fix
+ requests are posted.
+
+Arguments:
+
+ Scb - Supplies the Scb for the stream which got the error
+
+ DeviceObject - Supplies the Device Object for the stream
+
+ MasterIrp - Supplies the original master Irp for the failing read or write
+
+ MultipleIrpCount - Supplies the number of runs in which the current
+ was broken into at the time the error occured.
+
+ IoRuns - Supplies an array describing the runs being accessed at the
+ time of the error
+
+Return Value:
+
+ None
+
+-*/
+
+{
+ PVOID SystemBuffer;
+ ULONG RunNumber, ByteOffset, FtCase;
+ BOOLEAN SecondaryAvailable;
+ BOOLEAN FixingUsaError;
+ BOOLEAN FinalPass;
+ ULONG ClusterMask;
+ ULONG ClustersToRecover;
+ ULONG UsaBlockSize;
+ PIO_STACK_LOCATION IrpSp;
+ PVCB Vcb = Scb->Vcb;
+ ULONG BytesPerCluster = Vcb->BytesPerCluster;
+ NTSTATUS FinalStatus = STATUS_SUCCESS;
+ ULONG AlignedRunNumber = 0;
+ ULONG AlignedByteOffset = 0;
+ NTSTATUS IrpStatus = MasterIrp->IoStatus.Status;
+
+ PNTFS_IO_CONTEXT Context = IrpContext->Union.NtfsIoContext;
+
+ LONGLONG LlTemp1;
+ LONGLONG LlTemp2;
+
+ PAGED_CODE();
+
+ //
+ // First, if the error we got indicates a total device failure, then we
+ // just report it rather than trying to hot fix every sector on the volume!
+ // Also, do not do hot fix for the read ahead thread, because that is a
+ // good way to conceal errors from the App.
+ //
+
+ if (FsRtlIsTotalDeviceFailure(MasterIrp->IoStatus.Status) ||
+ (Scb->CompressionUnit != 0)) {
+
+ return;
+ }
+
+ //
+ // Get out if we got an error and the current thread is doing read ahead.
+ //
+
+ if (!NT_SUCCESS(MasterIrp->IoStatus.Status) && NtfsIsReadAheadThread()) {
+
+ return;
+ }
+
+ //
+ // Determine whether a secondary device is available
+ //
+
+ SecondaryAvailable = (BOOLEAN)!FlagOn(Vcb->VcbState, VCB_STATE_NO_SECONDARY_AVAILABLE);
+
+ //
+ // Assume that we are recovering from a Usa error, if the MasterIrp has
+ // the success status.
+ //
+
+ FixingUsaError = FT_SUCCESS(MasterIrp->IoStatus.Status);
+
+ //
+ // We cannot fix any Usa errors if there is no secondary. Even if there is
+ // a secondary, Usa errors should only occur during restart. If it is not
+ // restart we are probably looking at uninitialized data, so don't try to
+ // "fix" it.
+ //
+
+ if (FixingUsaError &&
+ (!SecondaryAvailable || !FlagOn(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS))) {
+ return;
+ }
+
+ //
+ // Initialize Context, for use in Read/Write Multiple Asynch.
+ //
+
+ ASSERT( Context != NULL );
+
+ Context->MasterIrp = MasterIrp;
+ KeInitializeEvent( &Context->Wait.SyncEvent, NotificationEvent, FALSE );
+
+ HotFixTrace(("NtfsFixDataError, MasterIrp: %08lx, MultipleIrpCount: %08lx\n", MasterIrp, MultipleIrpCount));
+ HotFixTrace((" IoRuns: %08lx, UsaError: %02lx\n", IoRuns, FixingUsaError));
+ HotFixTrace((" Thread: %08lx\n", PsGetCurrentThread()));
+ HotFixTrace((" Scb: %08lx BadClusterScb: %08lx\n", Scb, Vcb->BadClusterFileScb));
+
+ //
+ // In most cases we will need to access the buffer for this transfer directly,
+ // so map it here.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( MasterIrp );
+
+ //
+ // If this is a Usa-protected structure, get the block size now.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT)) {
+
+ //
+ // Get the the number of blocks, based on what type of stream it is.
+ // First check for Mft or Log file.
+ //
+
+ if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ ASSERT((Scb == Vcb->MftScb) || (Scb == Vcb->Mft2Scb));
+
+ UsaBlockSize = Vcb->BytesPerFileRecordSegment;
+
+ } else if (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) {
+
+ //
+ // For the log file, we will just go a page at a time, which
+ // is generally what the log file does. Any USA errors would
+ // tend to be only at the logical end of the log file anyway.
+ //
+
+ ASSERT( Scb == Vcb->LogFileScb );
+
+ //
+ // For the log file, assume it is right in the record, use that
+ // if we get a plausible number, else use page size.
+ //
+
+ RunNumber = (USHORT)(((PMULTI_SECTOR_HEADER)SystemBuffer)->UpdateSequenceArraySize - 1);
+ UsaBlockSize = RunNumber * SEQUENCE_NUMBER_STRIDE;
+
+ if ((UsaBlockSize != 0x1000) && (UsaBlockSize != 0x2000) && (UsaBlockSize != PAGE_SIZE)) {
+
+ UsaBlockSize = PAGE_SIZE;
+ }
+
+ //
+ // Otherwise it is an index, so we can get the count out of the Scb.
+ //
+
+ } else {
+
+ UsaBlockSize = Scb->ScbType.Index.BytesPerIndexBuffer;
+ }
+ }
+
+ //
+ // Verify the maximum of UsaBlockSize and cluster size.
+ //
+
+ if (BytesPerCluster > UsaBlockSize) {
+
+ //
+ // Determine which is smaller the cluster size or the
+ // size of the buffer being read.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( MasterIrp );
+
+ UsaBlockSize = IrpSp->Parameters.Read.Length;
+ if (UsaBlockSize > BytesPerCluster) {
+
+ UsaBlockSize = BytesPerCluster;
+ }
+ }
+
+
+ //
+ // We know we got a failure in the given transfer, which could be any size.
+ // We first want to localize the error to the failing cluster(s).
+ //
+ // We do this in the following nested loops:
+ //
+ // do (for the entire transfer, 32 clusters at a time)
+ //
+ // for (primary, secondary if available, primary again if necessary)
+ //
+ // for (each run)
+ //
+ // for (each cluster)
+ //
+ // The inner-most two loops above have the ability to restart on successive
+ // 32-cluster boundaries, relative to the first cluster in the transfer.
+ // For the Ft case, where there is a secondary device available, clusters
+ // are blocked out of a mask as errors are found and corrected, so they
+ // do not have to be read in successive passes; Usa errors are blocked out
+ // of the mask immediately, while for I/O errors we force ourselves to read
+ // both copies to locate the error, only reading the primary again if the
+ // secondary contained the error.
+ //
+
+ //
+ // Loop through the entire transfer, 32 clusters at a time. The innermost
+ // loops will terminate on 32 cluster boundaries, so the outermost loop
+ // will simply keep looping until we exhaust the IoRuns array.
+ //
+
+ do {
+
+ //
+ // Initialize the clusters to recover to "all".
+ //
+
+ ClustersToRecover = MAXULONG;
+ FinalPass = FALSE;
+
+ //
+ // For these 32 clusters, loop through primary, secondary (if available),
+ // and primary again (only reading when necessary).
+ //
+
+ for (FtCase = 0; !FinalPass; FtCase++) {
+
+ //
+ // Calculate whether this is the final pass or not.
+ //
+
+ FinalPass = !SecondaryAvailable || (FtCase == 2) ||
+ (IrpContext->MajorFunction == IRP_MJ_WRITE);
+
+ //
+ // Initialize the current cluster mask for cluster 0
+ //
+
+ ClusterMask = 1;
+
+ //
+ // Loop through all of the runs in the IoRuns array, or until the
+ // ClusterMask indicates that we hit a 32 cluster boundary.
+ //
+
+ for (RunNumber = AlignedRunNumber;
+ (RunNumber < MultipleIrpCount) && (ClusterMask != 0);
+ (ClusterMask != 0) ? RunNumber++ : 0) {
+
+ //
+ // Loop through all of the clusters within this run, or until
+ // the ClusterMask indicates that we hit a 32 cluster boundary.
+ //
+
+ for (ByteOffset = (RunNumber == AlignedRunNumber) ? AlignedByteOffset : 0;
+ (ByteOffset < IoRuns[RunNumber].ByteCount) && (ClusterMask != 0);
+ ByteOffset += BytesPerCluster, ClusterMask <<= 1) {
+
+ LONGLONG StartingVbo, StartingLbo;
+ PIRP Irp;
+ PMDL Mdl;
+ BOOLEAN LowFileRecord;
+ FT_SPECIAL_READ SpecialRead;
+ ULONG Length;
+
+ HotFixTrace(("Doing ByteOffset: %08lx for FtCase: %02lx\n",
+ (((ULONG)IoRuns[RunNumber].StartingVbo) + ByteOffset),
+ FtCase));
+
+ //
+ // If this cluster no longer needs to be recovered, we can
+ // skip it.
+ //
+
+ if ((ClustersToRecover & ClusterMask) == 0) {
+ continue;
+ }
+
+ //
+ // Temporarily get the 64-bit byte offset into StartingVbo, then
+ // calculate the actual StartingLbo and StartingVbo.
+ //
+
+ StartingVbo = ByteOffset;
+
+ StartingLbo = IoRuns[RunNumber].StartingLbo + StartingVbo;
+ StartingVbo = IoRuns[RunNumber].StartingVbo + StartingVbo;
+
+ //
+ // If the file is compressed, then NtfsPrepareBuffers builds
+ // an IoRuns array where it compresses contiguous Lcns, and
+ // the Vcns do not always line up correctly. But we know there
+ // must be a corresponding Vcn for every Lcn in the stream,
+ // and that that Vcn can only be >= to the Vcn we have just
+ // calculated from the IoRuns array. Therefore, since performance
+ // of hotfix is not the issue here, we use the following simple
+ // loop to sequentially scan the Mcb for a matching Vcn for
+ // the current Lcn.
+ //
+
+ if (Scb->CompressionUnit != 0) {
+
+ VCN TempVcn;
+ LCN TempLcn, LcnOut;
+
+ TempLcn = LlClustersFromBytes( Vcb, StartingLbo );
+ TempVcn = LlClustersFromBytes( Vcb, StartingVbo );
+
+ //
+ // Scan to the end of the Mcb (we assert below this
+ // did not happen) or until we find a Vcn with the
+ // Lcn we currently want to read.
+ //
+
+ while (NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ TempVcn,
+ &LcnOut,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL )
+
+ &&
+
+ (LcnOut != TempLcn)) {
+
+ TempVcn = TempVcn + 1;
+ }
+
+ ASSERT(LcnOut == TempLcn);
+
+ StartingVbo = LlBytesFromClusters( Vcb, TempVcn );
+ }
+
+ LowFileRecord = (Scb == Vcb->MftScb) && (((PLARGE_INTEGER)&StartingVbo)->HighPart == 0);
+
+ //
+ // Calculate the amount to actually read.
+ //
+
+
+ Length = IoRuns[RunNumber].ByteCount - ByteOffset;
+
+ if (Length > BytesPerCluster) {
+
+ Length = BytesPerCluster;
+ }
+
+ //
+ // Loop while verify required, or we find we really
+ // do not have an FT device.
+ //
+
+ while (TRUE) {
+
+ //
+ // Create an associated IRP, making sure there is one stack entry for
+ // us, as well.
+ //
+
+ Irp = IoMakeAssociatedIrp( MasterIrp, (CCHAR)(DeviceObject->StackSize + 1) );
+
+ if (Irp == NULL) {
+
+ //
+ // We return the error status in the Master irp when
+ // we were called.
+ //
+
+ MasterIrp->IoStatus.Status = IrpStatus;
+ return;
+ }
+
+ //
+ // Allocate and build a partial Mdl for the request.
+ //
+
+ Mdl = IoAllocateMdl( (PCHAR)MasterIrp->UserBuffer +
+ IoRuns[RunNumber].BufferOffset + ByteOffset,
+ Length,
+ FALSE,
+ FALSE,
+ Irp );
+
+ if (Mdl == NULL) {
+
+ IoFreeIrp(Irp);
+
+ //
+ // We return the error status in the Master irp when
+ // we were called.
+ //
+
+ MasterIrp->IoStatus.Status = IrpStatus;
+ return;
+ }
+
+ //
+ // Sanity Check
+ //
+
+ ASSERT( Mdl == Irp->MdlAddress );
+
+ IoBuildPartialMdl( MasterIrp->MdlAddress,
+ Mdl,
+ (PCHAR)MasterIrp->UserBuffer +
+ IoRuns[RunNumber].BufferOffset + ByteOffset,
+ Length );
+
+ //
+ // Get the first IRP stack location in the associated Irp
+ //
+
+ IoSetNextIrpStackLocation( Irp );
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to describe our read.
+ //
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Parameters.Read.Length = Length;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = StartingVbo;
+
+ //
+ // Set up the completion routine address in our stack frame.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ &NtfsMultiSyncCompletionRoutine,
+ Context,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Setup the next IRP stack location in the associated Irp for the disk
+ // driver beneath us.
+ //
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+
+ //
+ // Setup the Stack location to do a normal read or write.
+ //
+
+ if ((IrpContext->MajorFunction == IRP_MJ_WRITE) || !SecondaryAvailable) {
+
+ IrpSp->MajorFunction = IrpContext->MajorFunction;
+ IrpSp->Flags = Context->IrpSpFlags;
+ IrpSp->Parameters.Read.ByteOffset.QuadPart = StartingLbo;
+ IrpSp->Parameters.Read.Length = Length;
+
+ //
+ // Otherwise we are supposed to read from the primary or secondary
+ // on an FT drive.
+ //
+
+ } else {
+
+ IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
+
+ if (FtCase != 1) {
+ IrpSp->Parameters.DeviceIoControl.IoControlCode = FT_PRIMARY_READ;
+ } else {
+ IrpSp->Parameters.DeviceIoControl.IoControlCode = FT_SECONDARY_READ;
+ }
+
+ Irp->AssociatedIrp.SystemBuffer = &SpecialRead;
+ SpecialRead.ByteOffset.QuadPart = StartingLbo;
+ SpecialRead.Length = Length;
+ }
+
+ //
+ // We only need to set the associated IRP count in the master irp to
+ // make it a master IRP. But we set the count to one more than our
+ // caller requested, because we do not want the I/O system to complete
+ // the I/O. We also set our own count.
+ //
+
+ Context->IrpCount = 1;
+ MasterIrp->AssociatedIrp.IrpCount = 2;
+
+ //
+ // MtfsMultiCompletionRoutine only modifies the status on errors,
+ // so we have to reset to success before each call.
+ //
+
+ MasterIrp->IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // If IoCallDriver returns an error, it has completed the Irp
+ // and the error will be caught by our completion routines
+ // and dealt with as a normal IO error.
+ //
+
+ HotFixTrace(("Calling driver with Irp: %08lx\n", Irp));
+
+ KeClearEvent( &Context->Wait.SyncEvent );
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+
+ //
+ // Now wait for it.
+ //
+
+ NtfsWaitSync( IrpContext );
+
+ HotFixTrace(("Request completion status: %08lx\n", MasterIrp->IoStatus.Status));
+
+ //
+ // If we were so lucky to get a verify required, then
+ // spin our wheels here a while.
+ //
+
+ if (MasterIrp->IoStatus.Status == STATUS_VERIFY_REQUIRED) {
+
+ //
+ // Otherwise we need to verify the volume, and if it doesn't
+ // verify correctly then we dismount the volume and report
+ // our error.
+ //
+
+ if (!NtfsPerformVerifyOperation( IrpContext, Vcb )) {
+
+ //**** NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
+
+ MasterIrp->IoStatus.Status = STATUS_FILE_INVALID;
+ return;
+ }
+
+ //
+ // The volume verified correctly so now clear the verify bit
+ // and try and I/O again
+ //
+
+ ClearFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+
+ //
+ // We may have assumed that there was a secondary available
+ // and there is not. We can only tell from getting this code.
+ // Indicate there is no secondary and that we will be only
+ // making one pass.
+ //
+
+ } else if (MasterIrp->IoStatus.Status == STATUS_INVALID_DEVICE_REQUEST) {
+
+ ASSERT((IrpContext->MajorFunction != IRP_MJ_WRITE) && SecondaryAvailable);
+
+ SetFlag(Vcb->VcbState, VCB_STATE_NO_SECONDARY_AVAILABLE);
+ SecondaryAvailable = FALSE;
+ FinalPass = TRUE;
+
+ //
+ // Otherwise we got success or another error and we should proceed.
+ //
+
+ } else {
+ break;
+ }
+ }
+
+ if (!FT_SUCCESS(MasterIrp->IoStatus.Status)) {
+
+ BOOLEAN IsHotFixPage;
+
+ //
+ // Calculate whether or not this is the hot fix thread itself
+ // (i.e., executing NtfsPerformHotFix).
+ //
+
+ IsHotFixPage = NtfsIsTopLevelHotFixScb( Scb );
+
+ LlTemp1 = StartingVbo >> PAGE_SHIFT; //**** crock for x86 compiler bug
+ LlTemp2 = NtfsGetTopLevelHotFixVcn() >> PAGE_SHIFT; //**** crock for x86 compiler bug
+
+ if (!IsHotFixPage ||
+ LlTemp1 != LlTemp2) {
+
+
+
+
+ IsHotFixPage = FALSE;
+ }
+
+ //
+ // If the entire device manages to fail in the middle of this,
+ // get out.
+ //
+
+ if (FsRtlIsTotalDeviceFailure(MasterIrp->IoStatus.Status)) {
+
+ MasterIrp->IoStatus.Status = IrpStatus;
+ return;
+ }
+
+ //
+ // If this is not a write, fill the cluster with -1 for the
+ // event that we ultimately never find good data. This is
+ // for security reasons (cannot show anyone the data that
+ // happens to be in the buffer now), signature reasons (let
+ // -1 designate read errors, as opposed to 0's which occur
+ // on ValidDataLength cases), and finally if we fail to read
+ // a bitmap, we must consider all clusters allocated if we
+ // wish to continue to use the volume before chkdsk sees it.
+ //
+
+ if (IrpContext->MajorFunction == IRP_MJ_READ) {
+
+ RtlFillMemory( (PCHAR)SystemBuffer +
+ IoRuns[RunNumber].BufferOffset + ByteOffset,
+ Length,
+ 0xFF );
+
+ //
+ // If this is file system metadata, then we better mark the
+ // volume corrupt.
+ //
+
+ if (FinalPass &&
+ FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE) &&
+ (!LowFileRecord || (((ULONG)StartingVbo >= PAGE_SIZE) &&
+ ((ULONG)StartingVbo >= (ULONG)((VOLUME_DASD_NUMBER + 1) << Vcb->MftShift))))) {
+
+ NtfsPostVcbIsCorrupt( IrpContext, 0, NULL, NULL );
+ }
+
+ //
+ // If this is a Usa-protected file, or the bitmap,
+ // then we will try to procede with our 0xFF pattern
+ // above rather than returning an error to our caller.
+ // The Usa guy will get a Usa error, and the bitmap
+ // will safely say that everything is allocated until
+ // chkdsk can fix it up.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT) ||
+ (Scb == Vcb->BitmapScb)) {
+
+ MasterIrp->IoStatus.Status = STATUS_SUCCESS;
+ }
+ }
+
+ //
+ // If we are not the page being hot fixed, we want to post the
+ // hot fix and possibly remember the final status.
+ //
+
+ if (!IsHotFixPage) {
+
+ //
+ // If we got a media error, post the hot fix now. We expect
+ // to post at most one hot fix in this routine. When we post
+ // it it will serialize on the current stream. Do not attempt
+ // hot fixes during restart, or if we do not have the bad
+ // cluster file yet.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) &&
+ (Vcb->BadClusterFileScb != NULL) &&
+ (!LowFileRecord ||
+ ((ULONG)StartingVbo >= Vcb->Mft2Scb->Header.FileSize.LowPart))) {
+
+ NtfsPostHotFix( MasterIrp,
+ &StartingVbo,
+ StartingLbo,
+ BytesPerCluster,
+ FALSE );
+ }
+
+ //
+ // Now see if we ended up with an error on this cluster, and handle
+ // it accordingly.
+ //
+ // If we are the one actually trying to fix this error,
+ // then we need to get success so that we can make the page
+ // valid with whatever good data we have and flush data
+ // to its new location.
+ //
+ // Currently we will not try to figure out if the error
+ // is actually on the Scb (not to mention the sector) that
+ // we are hot fixing, assuming that the best thing is to
+ // just try to charge on.
+ //
+
+
+ if (FinalPass) {
+
+ //
+ // Make sure he gets the error (if we still have an
+ // error (see above).
+ //
+
+ if (!FT_SUCCESS(MasterIrp->IoStatus.Status)) {
+ FinalStatus = MasterIrp->IoStatus.Status;
+ }
+ }
+ }
+ }
+
+ //
+ // If this is a Usa-protected stream, we now perform end of
+ // Usa processing. (Otherwise do end of cluster processing
+ // below.)
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_USA_PRESENT)) {
+
+ ULONG NextOffset = IoRuns[RunNumber].BufferOffset + ByteOffset + Length;
+
+ //
+ // If we are not at the end of a Usa block, there is no work
+ // to do now.
+ //
+
+ if ((NextOffset & (UsaBlockSize - 1)) == 0) {
+
+ HotFixTrace(("May be verifying UsaBlock\n"));
+
+ //
+ // If the Usa block is ok, we may be able to knock the
+ // corresponding sectors out of the ClustersToRecover mask.
+ //
+
+ if ((IrpContext->MajorFunction != IRP_MJ_READ) ||
+ NtfsVerifyAndRevertUsaBlock( IrpContext,
+ Scb,
+ (PCHAR)SystemBuffer + NextOffset -
+ UsaBlockSize,
+ UsaBlockSize,
+ StartingVbo - (UsaBlockSize - Length) )) {
+
+ //
+ // If we are only fixing a Usa error anyway, or this is
+ // the final pass or at least not the first pass, then
+ // we can remove these clusters from the recover mask.
+ //
+
+ if (FixingUsaError || FinalPass || (FtCase != 0)) {
+
+ ULONG ShiftCount = UsaBlockSize >> Vcb->ClusterShift;
+
+ ClustersToRecover -= (ClusterMask * 2) -
+ (ClusterMask >> (ShiftCount - 1));
+ }
+
+ //
+ // Note, that even if we get a Usa error, we want to
+ // update the byte count on the final pass, because
+ // our reader expects that.
+ //
+
+ } else if (FinalPass) {
+
+ HotFixTrace(("Verify may have failed\n"));
+ }
+ }
+
+ //
+ // Perform end of cluster processing if not a Usa-protected stream.
+ //
+
+ } else {
+
+ //
+ // If the read succeeded and this is the final pass or at least
+ // not the first pass, we can take this cluster out of the cluster
+ // to recover mask.
+ //
+
+ if (FT_SUCCESS(MasterIrp->IoStatus.Status) && (FinalPass || (FtCase != 0))) {
+
+ ClustersToRecover -= ClusterMask;
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // Assume we terminated the inner loops because we hit a 32 cluster boundary,
+ // and advance our alignment points.
+ //
+
+ AlignedRunNumber = RunNumber;
+ AlignedByteOffset = ByteOffset;
+
+ } while (RunNumber < MultipleIrpCount);
+
+ //
+ // Now put the final status in the MasterIrp and return
+ //
+
+ MasterIrp->IoStatus.Status = FinalStatus;
+ if (!NT_SUCCESS(FinalStatus)) {
+ MasterIrp->IoStatus.Information = 0;
+ }
+
+ HotFixTrace(("NtfsFixDataError returning IoStatus = %08lx, %08lx\n",
+ MasterIrp->IoStatus.Status,
+ MasterIrp->IoStatus.Information));
+
+}
+
+
+VOID
+NtfsPostHotFix (
+ IN PIRP Irp,
+ IN PLONGLONG BadVbo,
+ IN LONGLONG BadLbo,
+ IN ULONG ByteLength,
+ IN BOOLEAN DelayIrpCompletion
+ )
+
+/*
+
+Routine Description:
+
+ This routine posts a hot fix request to a worker thread. It has to be posted,
+ because we cannot expect to be able to acquire the resources we need exclusive
+ when the bad cluster is discovered.
+
+Arguments:
+
+ Irp - The Irp for a read or write request which got the error
+
+ BadVbo - The Vbo of the bad cluster for the read or write request
+
+ BadLbo - The Lbo of the bad cluster
+
+ ByteLength - Length to hot fix
+
+ DelayIrpCompletion - TRUE if the Irp should not be completed until the hot
+ fix is done.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PIRP_CONTEXT HotFixIrpContext;
+ PVOLUME_DEVICE_OBJECT VolumeDeviceObject;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+
+ HotFixTrace(("NTFS: Posting hotfix on file object: %08lx\n", FileObject));
+
+ //
+ // Allocate an IrpContext to post the hot fix to a worker thread.
+ //
+
+ HotFixIrpContext = NtfsCreateIrpContext( Irp, FALSE );
+
+ //
+ // First reference the file object so that it will not go away
+ // until the hot fix is done. (We cannot increment the CloseCount
+ // in the Scb, since we are not properly synchronized.)
+ //
+
+ ObReferenceObject( FileObject );
+
+ HotFixIrpContext->OriginatingIrp = (PIRP)FileObject;
+ HotFixIrpContext->ScbSnapshot.AllocationSize = *BadVbo;
+ HotFixIrpContext->ScbSnapshot.FileSize = BadLbo;
+ ((ULONG)HotFixIrpContext->ScbSnapshot.ValidDataLength) = ByteLength;
+ if (DelayIrpCompletion) {
+ ((PLARGE_INTEGER)&HotFixIrpContext->ScbSnapshot.ValidDataLength)->HighPart = (ULONG)Irp;
+ } else {
+ ((PLARGE_INTEGER)&HotFixIrpContext->ScbSnapshot.ValidDataLength)->HighPart = 0;
+ }
+
+ //
+ // Make sure these fields are filled in correctly, no matter
+ // what kind of request we are servicing (even a mount).
+ //
+
+
+ HotFixIrpContext->RealDevice = FileObject->DeviceObject;
+
+ //
+ // Locate the volume device object and Vcb that we are trying to access
+ //
+
+ VolumeDeviceObject = (PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject;
+ HotFixIrpContext->Vcb = &VolumeDeviceObject->Vcb;
+
+ //
+ // Send it off.....
+ //
+
+ ExInitializeWorkItem( &HotFixIrpContext->WorkQueueItem,
+ (PWORKER_THREAD_ROUTINE)NtfsPerformHotFix,
+ (PVOID)HotFixIrpContext );
+
+ ExQueueWorkItem( &HotFixIrpContext->WorkQueueItem, CriticalWorkQueue );
+}
+
+
+VOID
+NtfsPerformHotFix (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements implements a hot fix that was scheduled
+ above, extracting its parameters from the IrpContext initialized
+ above. The hot fix must be for a contiguous range of Lcns (usually 1).
+
+Arguments:
+
+ IrpContext - Supplies the IrpContext with the hot fix information
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ PSCB BadClusterScb;
+ VCN BadVcn;
+ LCN LcnTemp, BadLcn;
+ LONGLONG ClusterCount;
+ NTSTATUS Status;
+ PVOID Buffer;
+ PIRP IrpToComplete;
+ ULONG ClustersToFix;
+ PBCB Bcb = NULL;
+ BOOLEAN PerformFullCleanup = TRUE;
+
+ //
+ // Extract a description of the cluster to be fixed.
+ //
+
+ PFILE_OBJECT FileObject = (PFILE_OBJECT)IrpContext->OriginatingIrp;
+ VBO BadVbo = *(PVBO)&IrpContext->ScbSnapshot.AllocationSize;
+
+ PAGED_CODE();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ //
+ // Initialize our local variables
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+ BadClusterScb = Vcb->BadClusterFileScb;
+ BadVcn = LlClustersFromBytesTruncate( Vcb, BadVbo );
+ BadLcn = LlClustersFromBytesTruncate( Vcb, IrpContext->ScbSnapshot.FileSize );
+ ClustersToFix = ClustersFromBytes( Vcb, ((ULONG)IrpContext->ScbSnapshot.ValidDataLength) );
+ IrpToComplete = (PIRP)(((PLARGE_INTEGER)&IrpContext->ScbSnapshot.ValidDataLength)->HighPart);
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Set up for synchronous operation
+ //
+
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ //
+ // Show that we are performing a HotFix. Note we are not processing
+ // an Irp now.
+ //
+
+ IrpContext->OriginatingIrp = NULL;
+
+ TopLevelContext.VboBeingHotFixed = BadVbo;
+ TopLevelContext.ScbBeingHotFixed = Scb;
+
+ //
+ // Acquire the Vcb before acquiring the paging Io resource.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ //
+ // Acquire the paging io resource for this Fcb if it exists.
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ }
+
+ //
+ // Just because we are hot fixing one file, it is possible that someone
+ // will log to another file and try to lookup Lcns. So we will acquire
+ // all files. Example: Hot fix is in Mft, and SetFileInfo has only the
+ // file acquired, and will log something to the Mft, and cause Lcns to be
+ // looked up.
+ //
+
+ NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE );
+
+ //
+ // Catch all exceptions. Note, we should not get any I/O error exceptions
+ // on our device.
+ //
+
+ try {
+
+ for (; ClustersToFix != 0; ClustersToFix--) {
+
+ //
+ // Lookup the bad cluster to see if it is already in the bad cluster
+ // file, and do nothing if it is.
+ //
+
+ if (!NtfsLookupAllocation( IrpContext,
+ BadClusterScb,
+ BadLcn,
+ &LcnTemp,
+ &ClusterCount,
+ NULL,
+ NULL ) &&
+
+ NtfsLookupAllocation( IrpContext,
+ Scb,
+ BadVcn,
+ &LcnTemp,
+ &ClusterCount,
+ NULL,
+ NULL ) &&
+
+ (LcnTemp == BadLcn)) {
+
+ //
+ // Pin the bad cluster in memory, so that we will not lose whatever data
+ // we have for it. (This data will be the correct data if we are talking
+ // to the FT driver or got a write error, otherwise it may be all -1's.)
+ //
+ // Do not try to do this if we are holding on to the original Irp, as that
+ // will cause a collided page wait deadlock.
+ //
+
+ if (IrpToComplete == NULL) {
+
+ ULONG Count = 100;
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ //
+ // We loop as long as we get an data error. We want our
+ // thread to read from the disk because we will recognize
+ // an I/O request started in PerformHotFix and ignore the
+ // data error. The cases where we do get an error will
+ // probably be from Mm intercepting this request because
+ // of a collided read with another thread.
+ //
+
+
+ do {
+
+ Status = STATUS_SUCCESS;
+
+ try {
+
+ NtfsPinStream( IrpContext, Scb, BadVbo, Vcb->BytesPerCluster, &Bcb, &Buffer );
+
+ } except ((!FsRtlIsNtstatusExpected( Status = GetExceptionCode())
+ || FsRtlIsTotalDeviceFailure( Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER) {
+
+ NOTHING;
+ }
+
+ } while (Count-- && (Status != STATUS_SUCCESS));
+
+ if (Status != STATUS_SUCCESS) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ }
+
+ //
+ // Now deallocate the bad cluster in this stream in the bitmap only,
+ // since in general we do not support sparse deallocation in the file
+ // record. We will update the allocation below.
+ //
+
+#if DBG
+ KdPrint(("NTFS: Freeing Bad Vcn: %08lx, %08lx\n", ((ULONG)BadVcn), ((PLARGE_INTEGER)&BadVcn)->HighPart));
+#endif
+
+ NtfsDeallocateClusters( IrpContext,
+ Vcb,
+ &Scb->Mcb,
+ BadVcn,
+ BadVcn,
+ &Scb->TotalAllocated );
+
+
+ //
+ // Look up the bad cluster attribute.
+ //
+
+ NtfsLookupAttributeForScb( IrpContext, BadClusterScb, NULL, &Context );
+
+ //
+ // Now append this cluster to the bad cluster file
+ //
+
+#if DBG
+ KdPrint(("NTFS: Retiring Bad Lcn: %08lx, %08lx\n", ((ULONG)BadLcn), ((PLARGE_INTEGER)&BadLcn)->HighPart));
+#endif
+
+ NtfsAddBadCluster( IrpContext, Vcb, BadLcn );
+
+ //
+ // Now update the file record for the bad cluster file to
+ // show the new cluster.
+ //
+
+ NtfsAddAttributeAllocation( IrpContext,
+ BadClusterScb,
+ &Context,
+ &BadLcn,
+ (PVCN)&Li1 );
+
+ //
+ // Now reallocate a cluster to the original stream to replace the bad cluster.
+ //
+
+ HotFixTrace(("NTFS: Reallocating Bad Vcn\n"));
+ NtfsAddAllocation( IrpContext, NULL, Scb, BadVcn, (LONGLONG)1, FALSE );
+
+ //
+ // Now that there is a new home for the data, mark the page dirty, unpin
+ // it and flush it out to its new home.
+ //
+
+ if (IrpToComplete == NULL) {
+
+ CcSetDirtyPinnedData( Bcb, NULL );
+
+ NtfsUnpinBcb( &Bcb );
+
+ //
+ // Flush the stream. Ignore the status - if we get something like
+ // a log file full, the Lazy Writer will eventually write the page.
+ //
+
+ (VOID)NtfsFlushUserStream( IrpContext, Scb, &BadVbo, 1 );
+ }
+
+ //
+ // Commit all of these updates.
+ //
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ //
+ // Now that the data is flushed to its new location, we will write the
+ // hot fix record. We don't write the log record if we are
+ // fixing the logfile. Instead we explicitly flush the Mft record
+ // for the log file. The log file is one file where we expect
+ // to be able to read the mapping pairs on restart.
+ //
+
+ if (Scb == Vcb->LogFileScb) {
+
+ if (Vcb->MftScb->FileObject != NULL) {
+
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject,
+ &NtfsLarge0,
+ Vcb->BytesPerFileRecordSegment * ATTRIBUTE_DEF_TABLE_NUMBER,
+ NULL );
+ }
+
+ } else {
+
+ (VOID) NtfsWriteLog( IrpContext,
+ Scb,
+ NULL,
+ HotFix,
+ NULL,
+ 0,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromClusters( Vcb, BadVcn ),
+ 0,
+ 0,
+ Vcb->BytesPerCluster );
+
+ //
+ // And we have to commit that one, too.
+ //
+
+ NtfsCommitCurrentTransaction( IrpContext );
+ }
+
+ //
+ // Now flush the log to insure that the hot fix gets remembered,
+ // especially important if this is the paging file.
+ //
+
+ LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn(Vcb->LogHandle) );
+
+ HotFixTrace(("NTFS: Bad Cluster replaced\n"));
+ }
+
+ //
+ // Get ready for another possible pass through the loop
+ //
+
+ BadVcn = BadVcn + 1;
+ BadLcn = BadLcn + 1;
+ NtfsCleanupAttributeContext( &Context );
+ NtfsUnpinBcb( &Bcb );
+ }
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode = GetExceptionCode();
+
+ //
+ // We are not prepared to have our IrpContext requeued, so just
+ // consider these cases to be bad luck. We will put a status of
+ // data error in the irp context and pass that code to the process
+ // exception routine.
+ //
+
+ if ((ExceptionCode == STATUS_LOG_FILE_FULL) ||
+ (ExceptionCode == STATUS_CANT_WAIT)) {
+
+ ExceptionCode = IrpContext->ExceptionStatus = STATUS_DATA_ERROR;
+ }
+
+ NtfsProcessException( IrpContext, NULL, ExceptionCode );
+
+ PerformFullCleanup = FALSE;
+ }
+
+ //
+ // Let any errors be handled in the except clause above, however we
+ // cleanup on the way out, because for example we need the IrpContext
+ // still in the except clause.
+ //
+
+ try {
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+
+ NtfsCleanupAttributeContext( &Context );
+
+ NtfsUnpinBcb( &Bcb );
+
+ //
+ // If we aborted this operation then all of the file resources have
+ // already been released.
+ //
+
+ if (PerformFullCleanup) {
+
+ NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ //
+ // The files have been released but not the Vcb or the volume bitmap.
+ //
+
+ } else {
+
+ if (Vcb->BitmapScb != NULL
+ && NtfsIsExclusiveScb( Vcb->BitmapScb )) {
+
+ ExReleaseResource( Vcb->BitmapScb->Header.Resource );
+ }
+
+ //
+ // We need to release the Vcb twice since we specifically acquire
+ // it once and then again with all the files.
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ ObDereferenceObject( FileObject );
+
+ if (IrpToComplete != NULL) {
+
+ NtfsCompleteRequest( NULL, &IrpToComplete, IrpToComplete->IoStatus.Status );
+ }
+
+ if (PerformFullCleanup) {
+
+ NtfsDeleteIrpContext( &IrpContext );
+ }
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+ NOTHING;
+ }
+}
+
+
+BOOLEAN
+NtfsGetReservedBuffer (
+ IN PFCB ThisFcb,
+ OUT PVOID *Buffer,
+ OUT PULONG Length,
+ IN UCHAR Need2
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates the reserved buffers depending on the needs of
+ the caller. If the caller might require two buffers then we will allocate
+ buffers 1 or 2. Otherwise we can allocate any of the three.
+
+Arguments:
+
+ ThisFcb - This is the Fcb where the io is occurring.
+
+ Buffer - Address to store the address of the allocated buffer.
+
+ Length - Address to store the length of the returned buffer.
+
+ Need2 - Zero if only one buffer needed. Either 1 or 2 if two buffers
+ might be needed. Buffer 2 can be acquired recursively. If buffer
+ 1 is needed and the current thread already owns buffer 1 then
+ grant buffer three instead.
+
+Return Value:
+
+ BOOLEAN - Indicates whether the buffer was acquired.
+
+--*/
+
+{
+ BOOLEAN Allocated = FALSE;
+ PVOID CurrentThread;
+
+ //
+ // Capture the current thread and the Fcb for the file we are acquiring
+ // the buffer for.
+ //
+
+ CurrentThread = (PVOID) PsGetCurrentThread();
+
+ ExAcquireFastMutexUnsafe(&NtfsReservedBufferMutex);
+
+ //
+ // If we need two buffers then allocate either buffer 1 or buffer 2.
+ // We allow this caller to get a buffer if
+ //
+ // - He already owns one of these buffers (or)
+ //
+ // - Neither of the 2 buffers are allocated (and)
+ // - No other thread has a buffer on behalf of this file
+ //
+
+ if (Need2) {
+
+ if ((NtfsReservedBufferThread == CurrentThread) ||
+
+ (!FlagOn( NtfsReservedInUse, 3 ) &&
+ ((NtfsReserved3Fcb != ThisFcb) ||
+ (NtfsReserved3Thread == CurrentThread)))) {
+
+ NtfsReservedBufferThread = CurrentThread;
+ NtfsReserved12Fcb = ThisFcb;
+
+ //
+ // Check whether the caller wants buffer 1 or buffer 2.
+ //
+
+ if (Need2 == 1) {
+
+ //
+ // If we don't own buffer 1 then reserve it now.
+ //
+
+ if (!FlagOn( NtfsReservedInUse, 1 )) {
+
+ NtfsReserved1Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 1 );
+ *Buffer = NtfsReserved1;
+ *Length = LARGE_BUFFER_SIZE;
+ Allocated = TRUE;
+
+ } else if (!FlagOn( NtfsReservedInUse, 4 )) {
+
+ NtfsReserved3Fcb = ThisFcb;
+
+ NtfsReserved3Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 4 );
+ *Buffer = NtfsReserved3;
+ *Length = LARGE_BUFFER_SIZE;
+ Allocated = TRUE;
+ }
+
+ } else {
+
+ ASSERT( Need2 == 2 );
+
+ NtfsReserved2Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 2 );
+ *Buffer = NtfsReserved2;
+ *Length = LARGE_BUFFER_SIZE;
+ NtfsReserved2Count += 1;
+ Allocated = TRUE;
+ }
+ }
+
+ //
+ // We only need 1 buffer. If this thread is the exclusive owner then
+ // we know it is safe to use buffer 2. The data in this buffer doesn't
+ // need to be preserved across a recursive call.
+ //
+
+ } else if (NtfsReservedBufferThread == CurrentThread) {
+
+ NtfsReserved2Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 2 );
+ *Buffer = NtfsReserved2;
+ *Length = LARGE_BUFFER_SIZE;
+ NtfsReserved2Count += 1;
+ Allocated = TRUE;
+
+ //
+ // We only need 1 buffer. Try for buffer 3 first.
+ //
+
+ } else if (!FlagOn( NtfsReservedInUse, 4)) {
+
+ //
+ // Check if the owner of the first two buffers is operating in the
+ // same file but is a different thread. We can't grant another buffer
+ // for a different stream in the same file.
+ //
+
+ if (ThisFcb != NtfsReserved12Fcb) {
+
+ NtfsReserved3Fcb = ThisFcb;
+
+ NtfsReserved3Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 4 );
+ *Buffer = NtfsReserved3;
+ *Length = LARGE_BUFFER_SIZE;
+ Allocated = TRUE;
+ }
+
+ //
+ // If there is no exclusive owner then we can use either of the first
+ // two buffers. Note that getting one of the first two buffers will
+ // lock out the guy who needs two buffers.
+ //
+
+ } else if (NtfsReservedBufferThread == NULL) {
+
+ if (!FlagOn( NtfsReservedInUse, 2 )) {
+
+ NtfsReserved2Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 2 );
+ *Buffer = NtfsReserved2;
+ *Length = LARGE_BUFFER_SIZE;
+ NtfsReserved2Count += 1;
+ Allocated = TRUE;
+
+ } else if (!FlagOn( NtfsReservedInUse, 1 )) {
+
+ NtfsReserved1Thread = CurrentThread;
+ SetFlag( NtfsReservedInUse, 1 );
+ *Buffer = NtfsReserved1;
+ *Length = LARGE_BUFFER_SIZE;
+ Allocated = TRUE;
+ }
+ }
+
+ ExReleaseFastMutexUnsafe(&NtfsReservedBufferMutex);
+ return Allocated;
+}
+
+BOOLEAN
+NtfsFreeReservedBuffer (
+ IN PVOID Buffer
+ )
+{
+ BOOLEAN Deallocated = FALSE;
+
+ ExAcquireFastMutexUnsafe(&NtfsReservedBufferMutex);
+
+ if (Buffer == NtfsReserved1) {
+ ASSERT( FlagOn( NtfsReservedInUse, 1 ));
+
+ ClearFlag( NtfsReservedInUse, 1 );
+ NtfsReserved1Thread = NULL;
+ if (!FlagOn( NtfsReservedInUse, 2)) {
+ NtfsReservedBufferThread = NULL;
+ NtfsReserved12Fcb = NULL;
+ }
+
+ Deallocated = TRUE;
+
+ } else if (Buffer == NtfsReserved2) {
+ ASSERT( FlagOn( NtfsReservedInUse, 2 ));
+
+ NtfsReserved2Count -= 1;
+
+ if (NtfsReserved2Count == 0) {
+
+ ClearFlag( NtfsReservedInUse, 2 );
+ NtfsReserved2Thread = NULL;
+ if (!FlagOn( NtfsReservedInUse, 1)) {
+ NtfsReservedBufferThread = NULL;
+ NtfsReserved12Fcb = NULL;
+ }
+ }
+
+ Deallocated = TRUE;
+
+ } else if (Buffer == NtfsReserved3) {
+ ASSERT( FlagOn( NtfsReservedInUse, 4 ));
+ ClearFlag( NtfsReservedInUse, 4 );
+ Deallocated = TRUE;
+ NtfsReserved3Thread = NULL;
+ NtfsReserved3Fcb = NULL;
+ }
+
+ ExReleaseFastMutexUnsafe(&NtfsReservedBufferMutex);
+ return Deallocated;
+}
diff --git a/private/ntos/cntfs/dirctrl.c b/private/ntos/cntfs/dirctrl.c
new file mode 100644
index 000000000..a8ce0f588
--- /dev/null
+++ b/private/ntos/cntfs/dirctrl.c
@@ -0,0 +1,1532 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ DirCtrl.c
+
+Abstract:
+
+ This module implements the File Directory Control routine for Ntfs called
+ by the dispatch driver.
+
+Author:
+
+ Tom Miller [TomM] 1-Jan-1992
+
+ (Based heavily on GaryKi's dirctrl.c for pinball.)
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_DIRCTRL)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('dFtN')
+
+NTSTATUS
+NtfsQueryDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsNotifyChangeDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonDirectoryControl)
+#pragma alloc_text(PAGE, NtfsFsdDirectoryControl)
+#pragma alloc_text(PAGE, NtfsNotifyChangeDirectory)
+#pragma alloc_text(PAGE, NtfsQueryDirectory)
+#endif
+
+
+NTSTATUS
+NtfsFsdDirectoryControl (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Directory Control.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdDirectoryControl\n") );
+
+ //
+ // Call the common Directory Control routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Always make these requests look top level.
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, TRUE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonDirectoryControl( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdDirectoryControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonDirectoryControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Directory Control called by both the fsd
+ and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PSCB Scb;
+ PCCB Ccb;
+ PFCB Fcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonDirectoryControl\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // The only type of opens we accept are user directory opens
+ //
+
+ if (TypeOfOpen != UserDirectoryOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // We know this is a directory control so we'll case on the
+ // minor function, and call a internal worker routine to complete
+ // the irp.
+ //
+
+ switch ( IrpSp->MinorFunction ) {
+
+ case IRP_MN_QUERY_DIRECTORY:
+
+ Status = NtfsQueryDirectory( IrpContext, Irp, Vcb, Scb, Ccb );
+ break;
+
+ case IRP_MN_NOTIFY_CHANGE_DIRECTORY:
+
+ //
+ // We can't perform this operation on open by Id or if the caller has
+ // closed his handle.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) ||
+ FlagOn( FileObject->Flags, FO_CLEANUP_COMPLETE )) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ Status = NtfsNotifyChangeDirectory( IrpContext, Irp, Vcb, Scb, Ccb );
+ break;
+
+ default:
+
+ DebugTrace( 0, Dbg, ("Invalid Minor Function %08lx\n", IrpSp->MinorFunction) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+
+ Status = STATUS_INVALID_DEVICE_REQUEST;
+ break;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCommonDirectoryControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsQueryDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query directory operation. It is responsible
+ for either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+ Vcb - Supplies its Vcb
+
+ Scb - Supplies its Scb
+
+ Ccb - Supplies its Ccb
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp;
+
+ PUCHAR Buffer;
+ CLONG UserBufferLength;
+
+ ULONG BaseLength;
+
+ PUNICODE_STRING UniFileName;
+ FILE_INFORMATION_CLASS FileInformationClass;
+ ULONG FileIndex;
+ BOOLEAN RestartScan;
+ BOOLEAN ReturnSingleEntry;
+ BOOLEAN IndexSpecified;
+
+ BOOLEAN IgnoreCase;
+
+ BOOLEAN NextFlag;
+
+ BOOLEAN GotEntry;
+
+ BOOLEAN CallRestart;
+
+ ULONG NextEntry;
+ ULONG LastEntry;
+
+ PFILE_DIRECTORY_INFORMATION DirInfo;
+ PFILE_FULL_DIR_INFORMATION FullDirInfo;
+ PFILE_BOTH_DIR_INFORMATION BothDirInfo;
+ PFILE_NAMES_INFORMATION NamesInfo;
+
+ PFILE_NAME FileNameBuffer;
+ PVOID UnwindFileNameBuffer = NULL;
+ ULONG FileNameLength;
+
+ ULONG SizeOfFileName = FIELD_OFFSET( FILE_NAME, FileName );
+
+ INDEX_CONTEXT OtherContext;
+
+ PFCB AcquiredFcb = NULL;
+
+ BOOLEAN VcbAcquired = FALSE;
+
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN FirstQuery = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+ ASSERT_VCB( Vcb );
+ ASSERT_CCB( Ccb );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsQueryDirectory...\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, (" ->Length = %08lx\n", IrpSp->Parameters.QueryDirectory.Length) );
+ DebugTrace( 0, Dbg, (" ->FileName = %08lx\n", IrpSp->Parameters.QueryDirectory.FileName) );
+ DebugTrace( 0, Dbg, (" ->FileInformationClass = %08lx\n", IrpSp->Parameters.QueryDirectory.FileInformationClass) );
+ DebugTrace( 0, Dbg, (" ->FileIndex = %08lx\n", IrpSp->Parameters.QueryDirectory.FileIndex) );
+ DebugTrace( 0, Dbg, (" ->SystemBuffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+ DebugTrace( 0, Dbg, (" ->RestartScan = %08lx\n", FlagOn(IrpSp->Flags, SL_RESTART_SCAN)) );
+ DebugTrace( 0, Dbg, (" ->ReturnSingleEntry = %08lx\n", FlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY)) );
+ DebugTrace( 0, Dbg, (" ->IndexSpecified = %08lx\n", FlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED)) );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
+
+ //
+ // Because we probably need to do the I/O anyway we'll reject any request
+ // right now that cannot wait for I/O. We do not want to abort after
+ // processing a few index entries.
+ //
+
+ if (!FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT)) {
+
+ DebugTrace( 0, Dbg, ("Automatically enqueue Irp to Fsp\n") );
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ UserBufferLength = IrpSp->Parameters.QueryDirectory.Length;
+
+ FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass;
+ FileIndex = IrpSp->Parameters.QueryDirectory.FileIndex;
+
+ //
+ // Look in the Ccb to see the type of search.
+ //
+
+ IgnoreCase = BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE );
+
+ RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN);
+ ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY);
+ IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);
+
+ //
+ // Determine the size of the constant part of the structure.
+ //
+
+ switch (FileInformationClass) {
+
+ case FileDirectoryInformation:
+
+ BaseLength = FIELD_OFFSET( FILE_DIRECTORY_INFORMATION,
+ FileName[0] );
+ break;
+
+ case FileFullDirectoryInformation:
+
+ BaseLength = FIELD_OFFSET( FILE_FULL_DIR_INFORMATION,
+ FileName[0] );
+ break;
+
+ case FileNamesInformation:
+
+ BaseLength = FIELD_OFFSET( FILE_NAMES_INFORMATION,
+ FileName[0] );
+ break;
+
+ case FileBothDirectoryInformation:
+
+ BaseLength = FIELD_OFFSET( FILE_BOTH_DIR_INFORMATION,
+ FileName[0] );
+ break;
+
+ default:
+
+ Status = STATUS_INVALID_INFO_CLASS;
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // We have to create a File Name string for querying if there is either
+ // one specified in this request, or we do not already have a value
+ // in the Ccb. If we already have one then we will ignore the input
+ // name in this case unless the INDEX_SPECIFIED bit is set.
+ //
+
+ if ((Ccb->QueryBuffer == NULL) ||
+ ((IrpSp->Parameters.QueryDirectory.FileName != NULL) && IndexSpecified)) {
+
+ //
+ // Now, if the input string is NULL, we have to create the default
+ // string "*".
+ //
+
+ if (IrpSp->Parameters.QueryDirectory.FileName == NULL) {
+
+ FileNameLength = SizeOfFileName + sizeof(WCHAR);
+ FileNameBuffer = NtfsAllocatePool(PagedPool, FileNameLength );
+
+ //
+ // Initialize it.
+ //
+
+ FileNameBuffer->ParentDirectory = Scb->Fcb->FileReference;
+ FileNameBuffer->FileNameLength = 1;
+ FileNameBuffer->Flags = 0;
+ FileNameBuffer->FileName[0] = '*';
+
+ //
+ // We know we have an input file name, and we may or may not already
+ // have one in the Ccb. Allocate space for it, initialize it, and
+ // set up to deallocate on the way out if we already have a pattern
+ // in the Ccb.
+ //
+
+ } else {
+
+ UniFileName = (PUNICODE_STRING) IrpSp->Parameters.QueryDirectory.FileName;
+
+ if (!NtfsIsFileNameValid(UniFileName, TRUE)) {
+
+ if (Ccb->QueryBuffer == NULL
+ || UniFileName->Length > 4
+ || UniFileName->Length == 0
+ || UniFileName->Buffer[0] != L'.'
+ || (UniFileName->Length == 4
+ && UniFileName->Buffer[1] != L'.')) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+
+ DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ return Status;
+ }
+ }
+
+ FileNameLength = (USHORT)IrpSp->Parameters.QueryDirectory.FileName->Length;
+
+ FileNameBuffer = NtfsAllocatePool(PagedPool, SizeOfFileName + FileNameLength );
+
+ RtlCopyMemory( FileNameBuffer->FileName,
+ UniFileName->Buffer,
+ FileNameLength );
+
+ FileNameLength += SizeOfFileName;
+
+ FileNameBuffer->ParentDirectory = Scb->Fcb->FileReference;
+ FileNameBuffer->FileNameLength = (UCHAR)((FileNameLength - SizeOfFileName) / 2);
+ FileNameBuffer->Flags = 0;
+ }
+
+ //
+ // If we already have a query buffer, deallocate this on the way
+ // out.
+ //
+
+ if (Ccb->QueryBuffer != NULL) {
+
+ //
+ // If we have a name to resume from then override the restart
+ // scan boolean.
+ //
+
+ if ((UnwindFileNameBuffer = FileNameBuffer) != NULL) {
+
+ RestartScan = FALSE;
+ }
+
+ //
+ // Otherwise, store this one in the Ccb.
+ //
+
+ } else {
+
+ UNICODE_STRING Expression;
+
+ Ccb->QueryBuffer = (PVOID)FileNameBuffer;
+ Ccb->QueryLength = (USHORT)FileNameLength;
+ FirstQuery = TRUE;
+
+ //
+ // If the search expression contains a wild card then remember this in
+ // the Ccb.
+ //
+
+ Expression.MaximumLength =
+ Expression.Length = FileNameBuffer->FileNameLength * sizeof( WCHAR );
+ Expression.Buffer = FileNameBuffer->FileName;
+
+ //
+ // When we establish the search pattern, we must also establish
+ // whether the user wants to see "." and "..". This code does
+ // not necessarily have to be perfect (he said), but should be
+ // good enough to catch the common cases. Dos does not have
+ // perfect semantics for these cases, and the following determination
+ // will mimic what FastFat does exactly.
+ //
+
+ if (Scb != Vcb->RootIndexScb) {
+ static UNICODE_STRING DotString = CONSTANT_UNICODE_STRING( L"." );
+
+ if (FsRtlDoesNameContainWildCards(&Expression)) {
+
+ if (FsRtlIsNameInExpression( &Expression,
+ &DotString,
+ FALSE,
+ NULL )) {
+
+
+ SetFlag( Ccb->Flags, CCB_FLAG_RETURN_DOT | CCB_FLAG_RETURN_DOTDOT );
+ }
+ } else {
+ if (NtfsAreNamesEqual( Vcb->UpcaseTable, &Expression, &DotString, FALSE )) {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_RETURN_DOT | CCB_FLAG_RETURN_DOTDOT );
+ }
+ }
+ }
+ }
+
+ //
+ // Otherwise we are just restarting the query from the Ccb.
+ //
+
+ } else {
+
+ FileNameBuffer = (PFILE_NAME)Ccb->QueryBuffer;
+ FileNameLength = Ccb->QueryLength;
+ }
+
+ Irp->IoStatus.Information = 0;
+
+ NtfsInitializeIndexContext( &OtherContext );
+
+ try {
+
+ ULONG BytesToCopy;
+
+ FCB_TABLE_ELEMENT Key;
+ PFCB_TABLE_ELEMENT Entry;
+
+ BOOLEAN MatchAll = FALSE;
+
+ //
+ // See if we are supposed to try to acquire an Fcb on this
+ // resume.
+ //
+
+ if (Ccb->FcbToAcquire.LongValue != 0) {
+
+ //
+ // First we need to acquire the Vcb shared, since we will
+ // acquire two Fcbs.
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE );
+ VcbAcquired = TRUE;
+
+ //
+ // Now look up the Fcb, and if it is there, reference it
+ // and remember it.
+ //
+
+ Key.FileReference = Ccb->FcbToAcquire.FileReference;
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ Entry = RtlLookupElementGenericTable( &Vcb->FcbTable, &Key );
+ if (Entry != NULL) {
+ AcquiredFcb = Entry->Fcb;
+ AcquiredFcb->ReferenceCount += 1;
+ }
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ //
+ // Now that it cannot go anywhere, acquire it.
+ //
+
+ if (AcquiredFcb != NULL) {
+ NtfsAcquireSharedFcb( IrpContext, AcquiredFcb, NULL, TRUE );
+ }
+
+ //
+ // Now that we actually acquired it, we may as well clear this
+ // field.
+ //
+
+ Ccb->FcbToAcquire.LongValue = 0;
+ }
+
+ //
+ // Acquire shared access to the Scb.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ //
+ // Now that we have both files acquired, we can free the Vcb.
+ //
+
+ if (VcbAcquired) {
+ NtfsReleaseVcb( IrpContext, Vcb );
+ VcbAcquired = FALSE;
+ }
+
+ //
+ // If we are in the Fsp now because we had to wait earlier,
+ // we must map the user buffer, otherwise we can use the
+ // user's buffer directly.
+ //
+
+ Buffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Check if this is the first call to query directory for this file
+ // object. It is the first call if the enumeration context field of
+ // the ccb is null. Also check if we are to restart the scan.
+ //
+
+ if (FirstQuery || RestartScan) {
+
+ CallRestart = TRUE;
+ NextFlag = FALSE;
+
+ //
+ // On first/restarted scan, note that we have not returned either
+ // of these guys.
+ //
+
+ ClearFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED );
+
+ //
+ // Otherwise check to see if we were given a file name to restart from
+ //
+
+ } else if (UnwindFileNameBuffer != NULL) {
+
+ CallRestart = TRUE;
+ NextFlag = TRUE;
+
+ //
+ // The guy could actually be asking to return to one of the dot
+ // file positions, so we must handle that correctly.
+ //
+
+ if ((FileNameBuffer->FileNameLength <= 2) &&
+ (FileNameBuffer->FileName[0] == L'.')) {
+
+ if (FileNameBuffer->FileNameLength == 1) {
+
+ //
+ // He wants to resume after ".", so we set to return
+ // ".." again, and change the temporary pattern to
+ // rewind our context to the front.
+ //
+
+ ClearFlag( Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED );
+ SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED );
+
+ FileNameBuffer->FileName[0] = L'*';
+ NextFlag = FALSE;
+
+ } else if (FileNameBuffer->FileName[1] == L'.') {
+
+ //
+ // He wants to resume after "..", so we the change
+ // the temporary pattern to rewind our context to the
+ // front.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED );
+ FileNameBuffer->FileName[0] =
+ FileNameBuffer->FileName[1] = L'*';
+ NextFlag = FALSE;
+ }
+
+ //
+ // Always return the entry after the user's file name.
+ //
+
+ } else {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED | CCB_FLAG_DOTDOT_RETURNED );
+ }
+
+ //
+ // Otherwise we're simply continuing a previous enumeration from
+ // where we last left off. And we always leave off one beyond the
+ // last entry we returned.
+ //
+
+ } else {
+
+ CallRestart = FALSE;
+ NextFlag = FALSE;
+ }
+
+ //
+ // At this point we are about to enter our query loop. We have
+ // already decided if we need to call restart or continue when we
+ // go after an index entry. The variables LastEntry and NextEntry are
+ // used to index into the user buffer. LastEntry is the last entry
+ // we added to the user buffer, and NextEntry is the current
+ // one we're working on.
+ //
+
+ LastEntry = 0;
+ NextEntry = 0;
+
+ //
+ // Remember if we are matching everything, by checking these to common
+ // cases.
+ //
+
+ MatchAll = (FileNameBuffer->FileName[0] == L'*')
+
+ &&
+
+ ((FileNameBuffer->FileNameLength == 1) ||
+
+ ((FileNameBuffer->FileNameLength == 3) &&
+ (FileNameBuffer->FileName[1] == L'.') &&
+ (FileNameBuffer->FileName[2] == L'*')));
+
+ while (TRUE) {
+
+ PINDEX_ENTRY IndexEntry;
+ PFILE_NAME NtfsFileName;
+ PDUPLICATED_INFORMATION DupInfo;
+ PFILE_NAME DosFileName;
+
+ ULONG BytesRemainingInBuffer;
+ ULONG FoundFileNameLength;
+
+ struct {
+
+ FILE_NAME FileName;
+ WCHAR LastChar;
+ } DotDotName;
+
+ BOOLEAN SynchronizationError;
+
+ DebugTrace( 0, Dbg, ("Top of Loop\n") );
+ DebugTrace( 0, Dbg, ("LastEntry = %08lx\n", LastEntry) );
+ DebugTrace( 0, Dbg, ("NextEntry = %08lx\n", NextEntry) );
+
+ DosFileName = NULL;
+
+ //
+ // Lookup the next index entry. Check if we need to do the lookup
+ // by calling restart or continue. If we do need to call restart
+ // check to see if we have a real AnsiFileName. And set ourselves
+ // up for subsequent iternations through the loop
+ //
+
+ if (CallRestart) {
+
+ GotEntry = NtfsRestartIndexEnumeration( IrpContext,
+ Ccb,
+ Scb,
+ (PVOID)FileNameBuffer,
+ IgnoreCase,
+ NextFlag,
+ &IndexEntry,
+ AcquiredFcb );
+
+ CallRestart = FALSE;
+
+ } else {
+
+ GotEntry = NtfsContinueIndexEnumeration( IrpContext,
+ Ccb,
+ Scb,
+ NextFlag,
+ &IndexEntry );
+
+ }
+
+ //
+ // Check to see if we should quit the loop because we are only
+ // returning a single entry. We actually want to spin around
+ // the loop top twice so that our enumeration has has us left off
+ // at the last entry we didn't return. We know this is now our
+ // second time though the loop if NextEntry is not zero.
+ //
+
+ if ((ReturnSingleEntry) && (NextEntry != 0)) {
+
+ break;
+ }
+
+ //
+ // Assume we are to return one of the names "." or "..".
+ // We should not search farther in the index so we set
+ // NextFlag to FALSE.
+ //
+
+ RtlZeroMemory( &DotDotName, sizeof(DotDotName) );
+ NtfsFileName = &DotDotName.FileName;
+ NtfsFileName->Flags = FILE_NAME_NTFS | FILE_NAME_DOS;
+ NtfsFileName->FileName[0] =
+ NtfsFileName->FileName[1] = L'.';
+ DupInfo = &Scb->Fcb->Info;
+ NextFlag = FALSE;
+
+ //
+ // Handle "." first.
+ //
+
+ if (!FlagOn(Ccb->Flags, CCB_FLAG_DOT_RETURNED) &&
+ FlagOn(Ccb->Flags, CCB_FLAG_RETURN_DOT)) {
+
+ FoundFileNameLength = 2;
+ GotEntry = TRUE;
+ SetFlag( Ccb->Flags, CCB_FLAG_DOT_RETURNED );
+
+ //
+ // Handle ".." next.
+ //
+
+ } else if (!FlagOn(Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED) &&
+ FlagOn(Ccb->Flags, CCB_FLAG_RETURN_DOTDOT)) {
+
+ FoundFileNameLength = 4;
+ GotEntry = TRUE;
+ SetFlag( Ccb->Flags, CCB_FLAG_DOTDOT_RETURNED );
+
+ } else {
+
+ //
+ // Compute the length of the name we found.
+ //
+
+ if (GotEntry) {
+
+ FoundFileNameLength = IndexEntry->AttributeLength - SizeOfFileName;
+
+ NtfsFileName = (PFILE_NAME)(IndexEntry + 1);
+ DupInfo = &NtfsFileName->Info;
+ NextFlag = TRUE;
+ }
+ }
+
+ //
+ // Now check to see if we actually got another index entry. If
+ // we didn't then we also need to check if we never got any
+ // or if we just ran out. If we just ran out then we break out
+ // of the main loop and finish the Irp after the loop
+ //
+
+ if (!GotEntry) {
+
+ DebugTrace( 0, Dbg, ("GotEntry is FALSE\n") );
+
+ if (NextEntry == 0) {
+
+ if (FirstQuery) {
+
+ try_return( Status = STATUS_NO_SUCH_FILE );
+ }
+
+ try_return( Status = STATUS_NO_MORE_FILES );
+ }
+
+ break;
+ }
+
+ //
+ // Cleanup and reinitialize context from previous loop.
+ //
+
+ NtfsReinitializeIndexContext( IrpContext, &OtherContext );
+
+ //
+ // We may have matched a Dos-Only name. If so we will save
+ // it and go get the Ntfs name.
+ //
+
+ if (!FlagOn(NtfsFileName->Flags, FILE_NAME_NTFS) &&
+ FlagOn(NtfsFileName->Flags, FILE_NAME_DOS)) {
+
+ //
+ // If we are returning everything, then we can skip
+ // the Dos-Only names and save some cycles.
+ //
+
+ if (MatchAll) {
+ continue;
+ }
+
+ DosFileName = NtfsFileName;
+
+ NtfsFileName = NtfsRetrieveOtherFileName( IrpContext,
+ Ccb,
+ Scb,
+ IndexEntry,
+ &OtherContext,
+ AcquiredFcb,
+ &SynchronizationError );
+
+ //
+ // If we got an Ntfs name, then we need to list this entry now
+ // iff the Ntfs name is not in the expression. If the Ntfs
+ // name is in the expression, we can just continue and print
+ // this name when we encounter it by the Ntfs name.
+ //
+
+ if (NtfsFileName != NULL) {
+
+ if (FlagOn(Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION)) {
+
+ if (NtfsFileNameIsInExpression( Vcb->UpcaseTable,
+ (PFILE_NAME)Ccb->QueryBuffer,
+ NtfsFileName,
+ IgnoreCase )) {
+
+ continue;
+ }
+
+ } else {
+
+ if (NtfsFileNameIsEqual( Vcb->UpcaseTable,
+ (PFILE_NAME)Ccb->QueryBuffer,
+ NtfsFileName,
+ IgnoreCase )) {
+
+ continue;
+ }
+ }
+
+ FoundFileNameLength = NtfsFileName->FileNameLength * 2;
+
+ } else if (SynchronizationError) {
+
+ if (Irp->IoStatus.Information != 0) {
+ try_return( Status = STATUS_SUCCESS );
+ } else {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ } else {
+
+ continue;
+ }
+ }
+
+ //
+ // Here are the rules concerning filling up the buffer:
+ //
+ // 1. The Io system garentees that there will always be
+ // enough room for at least one base record.
+ //
+ // 2. If the full first record (including file name) cannot
+ // fit, as much of the name as possible is copied and
+ // STATUS_BUFFER_OVERFLOW is returned.
+ //
+ // 3. If a subsequent record cannot completely fit into the
+ // buffer, none of it (as in 0 bytes) is copied, and
+ // STATUS_SUCCESS is returned. A subsequent query will
+ // pick up with this record.
+ //
+
+ BytesRemainingInBuffer = UserBufferLength - NextEntry;
+
+ if ( (NextEntry != 0) &&
+ ( (BaseLength + FoundFileNameLength > BytesRemainingInBuffer) ||
+ (UserBufferLength < NextEntry) ) ) {
+
+ DebugTrace( 0, Dbg, ("Next entry won't fit\n") );
+
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ ASSERT( BytesRemainingInBuffer >= BaseLength );
+
+ //
+ // Zero the base part of the structure.
+ //
+
+ RtlZeroMemory( &Buffer[NextEntry], BaseLength );
+
+ //
+ // Now we have an entry to return to our caller. we'll
+ // case on the type of information requested and fill up the
+ // user buffer if everything fits
+ //
+
+ switch (FileInformationClass) {
+
+ case FileBothDirectoryInformation:
+
+ BothDirInfo = (PFILE_BOTH_DIR_INFORMATION)&Buffer[NextEntry];
+
+ //
+ // If this is not also a Dos name, and the Ntfs flag is set
+ // (meaning there is a separate Dos name), then call the
+ // routine to get the short name, if we do not already have
+ // it from above.
+ //
+
+ if (!FlagOn(NtfsFileName->Flags, FILE_NAME_DOS) &&
+ FlagOn(NtfsFileName->Flags, FILE_NAME_NTFS)) {
+
+ if (DosFileName == NULL) {
+
+ DosFileName = NtfsRetrieveOtherFileName( IrpContext,
+ Ccb,
+ Scb,
+ IndexEntry,
+ &OtherContext,
+ AcquiredFcb,
+ &SynchronizationError );
+ }
+
+ if (DosFileName != NULL) {
+
+ BothDirInfo->ShortNameLength = DosFileName->FileNameLength * 2;
+ RtlCopyMemory( BothDirInfo->ShortName,
+ DosFileName->FileName,
+ BothDirInfo->ShortNameLength );
+ } else if (SynchronizationError) {
+
+ if (Irp->IoStatus.Information != 0) {
+ try_return( Status = STATUS_SUCCESS );
+ } else {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ }
+ }
+
+ case FileFullDirectoryInformation:
+
+ DebugTrace( 0, Dbg, ("Getting file full Unicode directory information\n") );
+
+ FullDirInfo = (PFILE_FULL_DIR_INFORMATION)&Buffer[NextEntry];
+
+ FullDirInfo->EaSize = DupInfo->PackedEaSize;
+
+ //
+ // Add 4 bytes for the CbListHeader.
+ //
+
+ if (DupInfo->PackedEaSize != 0) {
+
+ FullDirInfo->EaSize += 4;
+ }
+
+ case FileDirectoryInformation:
+
+ DebugTrace( 0, Dbg, ("Getting file Unicode directory information\n") );
+
+ DirInfo = (PFILE_DIRECTORY_INFORMATION)&Buffer[NextEntry];
+
+ DirInfo->CreationTime.QuadPart = DupInfo->CreationTime;
+ DirInfo->LastAccessTime.QuadPart = DupInfo->LastAccessTime;
+ DirInfo->LastWriteTime.QuadPart = DupInfo->LastModificationTime;
+ DirInfo->ChangeTime.QuadPart = DupInfo->LastChangeTime;
+
+ DirInfo->FileAttributes = DupInfo->FileAttributes & FILE_ATTRIBUTE_VALID_FLAGS;
+
+ if (FlagOn( DupInfo->FileAttributes, DUP_FILE_NAME_INDEX_PRESENT)) {
+ DirInfo->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+ if (DirInfo->FileAttributes == 0) {
+ DirInfo->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ DirInfo->FileNameLength = FoundFileNameLength;
+
+ DirInfo->EndOfFile.QuadPart = DupInfo->FileSize;
+ DirInfo->AllocationSize.QuadPart = DupInfo->AllocatedLength;
+
+ break;
+
+ case FileNamesInformation:
+
+ DebugTrace( 0, Dbg, ("Getting file Unicode names information\n") );
+
+ NamesInfo = (PFILE_NAMES_INFORMATION)&Buffer[NextEntry];
+
+ NamesInfo->FileNameLength = FoundFileNameLength;
+
+ break;
+
+ default:
+
+ try_return( Status = STATUS_INVALID_INFO_CLASS );
+ }
+
+ //
+ // Compute how many bytes we can copy. This should only be less
+ // than the file name length if we are only returning a single
+ // entry.
+ //
+
+ if (BytesRemainingInBuffer >= BaseLength + FoundFileNameLength) {
+
+ BytesToCopy = FoundFileNameLength;
+
+ } else {
+
+ BytesToCopy = BytesRemainingInBuffer - BaseLength;
+
+ Status = STATUS_BUFFER_OVERFLOW;
+ }
+
+ RtlCopyMemory( &Buffer[NextEntry + BaseLength],
+ NtfsFileName->FileName,
+ BytesToCopy );
+
+ //
+ // If/when we actually emit a record for the Fcb acquired,
+ // then we can release that file now. Note we do not just
+ // do it on the first time through the loop, because some of
+ // our callers back up a bit when they give us the resume point.
+ //
+
+ if ((AcquiredFcb != NULL) &&
+ (DupInfo != &Scb->Fcb->Info) &&
+ NtfsEqualMftRef(&IndexEntry->FileReference, &Ccb->FcbToAcquire.FileReference)) {
+
+ //
+ // Now look up the Fcb, and if it is there, reference it
+ // and remember it.
+ //
+ // It is pretty inconvenient here to see if the ReferenceCount
+ // goes to zero and try to do a TearDown, we do not have the
+ // right resources. Note that the window is small, and the Fcb
+ // will go away if either someone opens the file again, someone
+ // tries to delete the directory, or someone tries to lock the
+ // volume.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsReleaseFcb( IrpContext, AcquiredFcb );
+ AcquiredFcb = NULL;
+ }
+
+ //
+ // Set up the previous next entry offset
+ //
+
+ *((PULONG)(&Buffer[LastEntry])) = NextEntry - LastEntry;
+
+ //
+ // And indicate how much of the user buffer we have currently
+ // used up. We must compute this value before we long align
+ // ourselves for the next entry. This is the point where we
+ // quad-align the length of the previous entry.
+ //
+
+ Irp->IoStatus.Information = QuadAlign( Irp->IoStatus.Information) +
+ BaseLength + BytesToCopy;
+
+ //
+ // If we weren't able to copy the whole name, then we bail here.
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ try_return( Status );
+ }
+
+ //
+ // Set ourselves up for the next iteration
+ //
+
+ LastEntry = NextEntry;
+ NextEntry += (ULONG)QuadAlign( BaseLength + BytesToCopy );
+ }
+
+ //
+ // At this point we've successfully filled up some of the buffer so
+ // now is the time to set our status to success.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ try_exit:
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ //
+ // Set the last access flag in the Fcb if the caller
+ // didn't set it explicitly.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME ) &&
+ !FlagOn( NtfsData.Flags, NTFS_FLAGS_DISABLE_LAST_ACCESS )) {
+
+ NtfsGetCurrentTime( IrpContext, Scb->Fcb->CurrentLastAccess );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsQueryDirectory );
+
+ if (VcbAcquired) {
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ NtfsCleanupIndexContext( IrpContext, &OtherContext );
+
+ if (AcquiredFcb != NULL) {
+
+ //
+ // Now look up the Fcb, and if it is there, reference it
+ // and remember it.
+ //
+ // It is pretty inconvenient here to see if the ReferenceCount
+ // goes to zero and try to do a TearDown, we do not have the
+ // right resources. Note that the window is small, and the Fcb
+ // will go away if either someone opens the file again, someone
+ // tries to delete the directory, or someone tries to lock the
+ // volume.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ NtfsReleaseFcb( IrpContext, AcquiredFcb );
+ }
+
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ NtfsCleanupAfterEnumeration( IrpContext, Ccb );
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ if (UnwindFileNameBuffer != NULL) {
+
+ NtfsFreePool(UnwindFileNameBuffer);
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsQueryDirectory -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsNotifyChangeDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the notify change directory operation. It is
+ responsible for either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+ Vcb - Supplies its Vcb
+
+ Scb - Supplies its Scb
+
+ Ccb - Supplies its Ccb
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ ULONG CompletionFilter;
+ BOOLEAN WatchTree;
+
+ PSECURITY_SUBJECT_CONTEXT SubjectContext;
+ BOOLEAN FreeSubjectContext = FALSE;
+
+ PCHECK_FOR_TRAVERSE_ACCESS CallBack;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+ ASSERT_VCB( Vcb );
+ ASSERT_CCB( Ccb );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsNotifyChangeDirectory...\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, (" ->CompletionFilter = %08lx\n", IrpSp->Parameters.NotifyDirectory.CompletionFilter) );
+ DebugTrace( 0, Dbg, (" ->WatchTree = %08lx\n", FlagOn( IrpSp->Flags, SL_WATCH_TREE )) );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+
+ //
+ // Reference our input parameter to make things easier
+ //
+
+ CompletionFilter = IrpSp->Parameters.NotifyDirectory.CompletionFilter;
+ WatchTree = BooleanFlagOn( IrpSp->Flags, SL_WATCH_TREE );
+
+ //
+ // Always set the wait bit in the IrpContext so the initial wait can't fail.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // We will only acquire the Vcb to perform the dirnotify task. The dirnotify
+ // package will provide synchronization between this operation and cleanup.
+ // We need the Vcb to synchronize with any rename or link operations underway.
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ //
+ // If the Link count is zero on this Fcb then complete this request
+ // with STATUS_DELETE_PENDING.
+ //
+
+ if (Scb->Fcb->LinkCount == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DELETE_PENDING, NULL, NULL );
+ }
+
+ //
+ // If we need to verify traverse access for this caller then allocate and
+ // capture the subject context to pass to the dir notify package. That
+ // package will be responsible for deallocating it.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_TRAVERSE_CHECK )) {
+
+ SubjectContext = NtfsAllocatePool( PagedPool,
+ sizeof( SECURITY_SUBJECT_CONTEXT ));
+
+ FreeSubjectContext = TRUE;
+ SeCaptureSubjectContext( SubjectContext );
+
+ FreeSubjectContext = FALSE;
+ CallBack = NtfsNotifyTraverseCheck;
+
+ } else {
+
+ SubjectContext = NULL;
+ CallBack = NULL;
+ }
+
+ //
+ // Call the Fsrtl package to process the request. We cast the
+ // unicode strings to ansi strings as the dir notify package
+ // only deals with memory matching.
+ //
+
+ FsRtlNotifyFullChangeDirectory( Vcb->NotifySync,
+ &Vcb->DirNotifyList,
+ Ccb,
+ (PSTRING) &Scb->ScbType.Index.NormalizedName,
+ WatchTree,
+ FALSE,
+ CompletionFilter,
+ Irp,
+ CallBack,
+ SubjectContext );
+
+ Status = STATUS_PENDING;
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_DIR_NOTIFY )) {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_DIR_NOTIFY );
+ InterlockedIncrement( &Vcb->NotifyCount );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsNotifyChangeDirectory );
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ //
+ // Since the dir notify package is holding the Irp, we discard the
+ // the IrpContext.
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, NULL, 0 );
+
+ } else if (FreeSubjectContext) {
+
+ NtfsFreePool( SubjectContext );
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsNotifyChangeDirectory -> %08lx\n", Status) );
+
+ return Status;
+}
diff --git a/private/ntos/cntfs/dirs b/private/ntos/cntfs/dirs
new file mode 100644
index 000000000..857c09b2c
--- /dev/null
+++ b/private/ntos/cntfs/dirs
@@ -0,0 +1,26 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ dirs.
+
+Abstract:
+
+ This file specifies the subdirectories of the current directory that
+ contain component makefiles.
+
+
+Author:
+
+
+NOTE: Commented description of this file is in \nt\bak\bin\dirs.tpl
+
+!ENDIF
+
+DIRS=mp
+
+OPTIONAL_DIRS= \
+ tests \
+ Views
diff --git a/private/ntos/cntfs/ea.c b/private/ntos/cntfs/ea.c
new file mode 100644
index 000000000..7ddc18c4c
--- /dev/null
+++ b/private/ntos/cntfs/ea.c
@@ -0,0 +1,2748 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Ea.c
+
+Abstract:
+
+ This module implements the File set and query Ea routines for Ntfs called
+ by the dispatch driver.
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_EA)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('EFtN')
+
+//
+// Local definitions
+//
+
+//
+// The following gives us an empty name string.
+//
+
+UNICODE_STRING AttrNoName = CONSTANT_UNICODE_STRING( L"" );
+
+#define MAXIMUM_EA_SIZE 0x0000ffff
+
+//
+// The following macros compute the packed and unpacked size of the EAs.
+// We use the 1 char defined in the structure for the NULL terminator of
+// the name.
+//
+
+#define SizeOfEaInformation \
+ (sizeof( ULONG ) + sizeof( USHORT ) + 3 * sizeof( UCHAR ))
+
+#define PackedEaSize(EA) \
+ ((SizeOfEaInformation - 4) \
+ + ((PFILE_FULL_EA_INFORMATION) EA)->EaNameLength \
+ + ((PFILE_FULL_EA_INFORMATION) EA)->EaValueLength)
+
+#define RawUnpackedEaSize(EA) \
+ (SizeOfEaInformation \
+ + ((PFILE_FULL_EA_INFORMATION) EA)->EaNameLength \
+ + ((PFILE_FULL_EA_INFORMATION) EA)->EaValueLength) \
+
+#define AlignedUnpackedEaSize(EA) \
+ (((PFILE_FULL_EA_INFORMATION) EA)->NextEntryOffset != 0 \
+ ? ((PFILE_FULL_EA_INFORMATION) EA)->NextEntryOffset \
+ : (LongAlign( RawUnpackedEaSize( EA )))) \
+
+//
+// BOOLEAN
+// NtfsAreEaNamesEqual (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSTRING NameA,
+// IN PSTRING NameB
+// );
+//
+
+#define NtfsAreEaNamesEqual(NAMEA, NAMEB ) ((BOOLEAN) \
+ ((NAMEA)->Length == (NAMEB)->Length \
+ && RtlEqualMemory( (NAMEA)->Buffer, \
+ (NAMEB)->Buffer, \
+ (NAMEA)->Length ) ) \
+)
+
+//
+// VOID
+// NtfsUpcaseEaName (
+// IN PSTRING EaName,
+// OUT PSTRING UpcasedEaName
+// );
+//
+
+#define NtfsUpcaseEaName( NAME, UPCASEDNAME ) \
+ RtlUpperString( UPCASEDNAME, NAME )
+
+BOOLEAN
+NtfsIsEaNameValid (
+ IN STRING Name
+ );
+
+//
+// Local procedure prototypes
+//
+
+VOID
+NtfsAppendEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN PFILE_FULL_EA_INFORMATION FullEa,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsDeleteEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN ULONG Offset
+ );
+
+BOOLEAN
+NtfsLocateEaByName (
+ IN PFILE_FULL_EA_INFORMATION FullEa,
+ IN ULONG EaBufferLength,
+ IN PSTRING EaName,
+ OUT PULONG Offset
+ );
+
+IO_STATUS_BLOCK
+NtfsQueryEaUserEaList (
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN PFILE_GET_EA_INFORMATION UserEaList,
+ IN BOOLEAN ReturnSingleEntry
+ );
+
+IO_STATUS_BLOCK
+NtfsQueryEaIndexSpecified (
+ OUT PCCB Ccb,
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN ULONG UserEaIndex,
+ IN BOOLEAN ReturnSingleEntry
+ );
+
+IO_STATUS_BLOCK
+NtfsQueryEaSimpleScan (
+ OUT PCCB Ccb,
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN BOOLEAN ReturnSingleEntry,
+ IN ULONG StartingOffset
+ );
+
+BOOLEAN
+NtfsIsDuplicateGeaName (
+ IN PFILE_GET_EA_INFORMATION CurrentGea,
+ IN PFILE_GET_EA_INFORMATION UserGeaBuffer
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAppendEa)
+#pragma alloc_text(PAGE, NtfsBuildEaList)
+#pragma alloc_text(PAGE, NtfsCommonQueryEa)
+#pragma alloc_text(PAGE, NtfsCommonSetEa)
+#pragma alloc_text(PAGE, NtfsDeleteEa)
+#pragma alloc_text(PAGE, NtfsFsdQueryEa)
+#pragma alloc_text(PAGE, NtfsFsdSetEa)
+#pragma alloc_text(PAGE, NtfsIsDuplicateGeaName)
+#pragma alloc_text(PAGE, NtfsIsEaNameValid)
+#pragma alloc_text(PAGE, NtfsLocateEaByName)
+#pragma alloc_text(PAGE, NtfsMapExistingEas)
+#pragma alloc_text(PAGE, NtfsQueryEaIndexSpecified)
+#pragma alloc_text(PAGE, NtfsQueryEaSimpleScan)
+#pragma alloc_text(PAGE, NtfsQueryEaUserEaList)
+#pragma alloc_text(PAGE, NtfsReplaceFileEas)
+#endif
+
+
+NTSTATUS
+NtfsFsdQueryEa (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of query Ea.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdQueryEa\n") );
+
+ //
+ // Call the common query Ea routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonQueryEa( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdQueryEa -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFsdSetEa (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of set Ea.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdSetEa\n") );
+
+ //
+ // Call the common set Ea routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonSetEa( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdSetEa -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonQueryEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for query Ea called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PFILE_FULL_EA_INFORMATION EaBuffer;
+ ULONG UserBufferLength;
+ PFILE_GET_EA_INFORMATION UserEaList;
+ ULONG UserEaListLength;
+ ULONG UserEaIndex;
+ ULONG EaLength;
+ BOOLEAN RestartScan;
+ BOOLEAN ReturnSingleEntry;
+ BOOLEAN IndexSpecified;
+
+ PFILE_FULL_EA_INFORMATION CurrentEas;
+ PBCB EaBcb;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT EaInfoAttr;
+ BOOLEAN CleanupEaInfoAttr;
+ PEA_INFORMATION EaInformation;
+ EA_INFORMATION DummyEaInformation;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonQueryEa\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("SystemBuffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.QueryEa.Length) );
+ DebugTrace( 0, Dbg, ("EaList = %08lx\n", IrpSp->Parameters.QueryEa.EaList) );
+ DebugTrace( 0, Dbg, ("EaListLength = %08lx\n", IrpSp->Parameters.QueryEa.EaListLength) );
+ DebugTrace( 0, Dbg, ("EaIndex = %08lx\n", IrpSp->Parameters.QueryEa.EaIndex) );
+ DebugTrace( 0, Dbg, ("RestartScan = %08lx\n", FlagOn(IrpSp->Flags, SL_RESTART_SCAN)) );
+ DebugTrace( 0, Dbg, ("ReturnSingleEntry = %08lx\n", FlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY)) );
+ DebugTrace( 0, Dbg, ("IndexSpecified = %08lx\n", FlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED)) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // This must be a user file or directory and the Ccb must indicate that
+ // the caller opened the entire file.
+ //
+
+ if (Ccb == NULL
+ || !FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )
+ || (TypeOfOpen != UserFileOpen
+ && TypeOfOpen != UserDirectoryOpen)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQueryEa -> %08lx\n", STATUS_INVALID_PARAMETER) );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire the Fcb exclusively.
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, FALSE, FALSE );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ UserBufferLength = IrpSp->Parameters.QueryEa.Length;
+ UserEaList = (PFILE_GET_EA_INFORMATION) IrpSp->Parameters.QueryEa.EaList;
+ UserEaListLength = IrpSp->Parameters.QueryEa.EaListLength;
+ UserEaIndex = IrpSp->Parameters.QueryEa.EaIndex;
+ RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN);
+ ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY);
+ IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);
+
+ //
+ // Initialize our local variables.
+ //
+
+ Status = STATUS_SUCCESS;
+ CleanupEaInfoAttr = FALSE;
+ EaBcb = NULL;
+
+ //
+ // Map the user's buffer.
+ //
+
+ EaBuffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Verify that the Ea file is in a consistant state. If the
+ // Ea modification count in the Fcb doesn't match that in
+ // the CCB, then the Ea file has been changed from under
+ // us. If we are not starting the search from the beginning
+ // of the Ea set, we return an error.
+ //
+
+ if (UserEaList == NULL
+ && Ccb->NextEaOffset != 0
+ && !IndexSpecified
+ && !RestartScan
+ && Fcb->EaModificationCount != Ccb->EaModificationCount) {
+
+ DebugTrace( 0, Dbg, ("NtfsCommonQueryEa: Ea file in unknown state\n") );
+
+ Status = STATUS_EA_CORRUPT_ERROR;
+
+ try_return( Status );
+ }
+
+ //
+ // Show that the Ea's for this file are consistant for this
+ // file handle.
+ //
+
+ Ccb->EaModificationCount = Fcb->EaModificationCount;
+
+ //
+ // We need to look up the attribute for the Ea information.
+ // If we don't find the attribute, then there are no EA's for
+ // this file. In that case we dummy up an ea list to use below.
+ //
+
+ NtfsInitializeAttributeContext( &EaInfoAttr );
+
+ CleanupEaInfoAttr = TRUE;
+
+ {
+ BOOLEAN EasOnFile;
+
+ EasOnFile = FALSE;
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $EA_INFORMATION,
+ &EaInfoAttr)) {
+
+ //
+ // As a sanity check we will check that the unpacked length is
+ // non-zero. It should always be so.
+ //
+
+ EaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &EaInfoAttr ));
+
+ if (EaInformation->UnpackedEaSize != 0) {
+
+ EasOnFile = TRUE;
+ }
+ }
+
+ if (EasOnFile) {
+
+ //
+ // We obtain a pointer to the start of the existing Ea's for the file.
+ //
+
+ CurrentEas = NtfsMapExistingEas( IrpContext,
+ Fcb,
+ &EaBcb,
+ &EaLength );
+
+ } else {
+
+ CurrentEas = NULL;
+ EaLength = 0;
+
+ DummyEaInformation.PackedEaSize = 0;
+ DummyEaInformation.NeedEaCount = 0;
+ DummyEaInformation.UnpackedEaSize = 0;
+
+ EaInformation = &DummyEaInformation;
+ }
+ }
+
+ //
+ // Let's clear the output buffer.
+ //
+
+ RtlZeroMemory( EaBuffer, UserBufferLength );
+
+ //
+ // We now satisfy the user's request depending on whether he
+ // specified an Ea name list, an Ea index or restarting the
+ // search.
+ //
+
+ //
+ // The user has supplied a list of Ea names.
+ //
+
+ if (UserEaList != NULL) {
+
+ Irp->IoStatus = NtfsQueryEaUserEaList( CurrentEas,
+ EaInformation,
+ EaBuffer,
+ UserBufferLength,
+ UserEaList,
+ ReturnSingleEntry );
+
+ //
+ // The user supplied an index into the Ea list.
+ //
+
+ } else if (IndexSpecified) {
+
+ Irp->IoStatus = NtfsQueryEaIndexSpecified( Ccb,
+ CurrentEas,
+ EaInformation,
+ EaBuffer,
+ UserBufferLength,
+ UserEaIndex,
+ ReturnSingleEntry );
+
+ //
+ // Else perform a simple scan, taking into account the restart
+ // flag and the position of the next Ea stored in the Ccb.
+ //
+
+ } else {
+
+ Irp->IoStatus = NtfsQueryEaSimpleScan( Ccb,
+ CurrentEas,
+ EaInformation,
+ EaBuffer,
+ UserBufferLength,
+ ReturnSingleEntry,
+ RestartScan
+ ? 0
+ : Ccb->NextEaOffset );
+ }
+
+ Status = Irp->IoStatus.Status;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsCommonQueryEa );
+
+ //
+ // We cleanup any attribute contexts.
+ //
+
+ if (CleanupEaInfoAttr) {
+
+ NtfsCleanupAttributeContext( &EaInfoAttr );
+ }
+
+ //
+ // Unpin the stream file if pinned.
+ //
+
+ NtfsUnpinBcb( &EaBcb );
+
+ //
+ // Release the Fcb.
+ //
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQueryEa -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonSetEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for set Ea called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ULONG Offset;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT EaInfoAttr;
+ PEA_INFORMATION EaInformation;
+
+ BOOLEAN PreviousEas;
+
+ EA_LIST_HEADER EaList;
+
+ PBCB EaBcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ NtfsInitializeAttributeContext( &EaInfoAttr );
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonSetEa\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // Initialize the IoStatus values.
+ //
+
+ Irp->IoStatus.Information = 0;
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Check that the file object is associated with either a user file or
+ // user directory open or an open by file ID.
+ //
+
+ if ((Ccb == NULL) ||
+ !FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE ) ||
+ ((TypeOfOpen != UserFileOpen) && (TypeOfOpen != UserDirectoryOpen))) {
+
+ DebugTrace( 0, Dbg, ("Invalid file object\n") );
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQueryEa -> %08lx\n", STATUS_INVALID_PARAMETER) );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // We must be waitable.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetEa -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Acquire exclusive access to the Fcb.
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, FALSE, FALSE );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ ULONG UserBufferLength;
+ PFILE_FULL_EA_INFORMATION Buffer;
+
+ PFILE_FULL_EA_INFORMATION CurrentEas;
+
+ //
+ // Reference the input parameters and initialize our local variables.
+ //
+
+ UserBufferLength = IrpSp->Parameters.SetEa.Length;
+
+ EaBcb = NULL;
+ Offset = 0;
+
+ EaList.FullEa = NULL;
+
+ //
+ // Map the user's Ea buffer.
+ //
+
+ Buffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Check the user's buffer for validity.
+ //
+
+ {
+ ULONG ErrorOffset;
+
+ Status = IoCheckEaBufferValidity( Buffer,
+ UserBufferLength,
+ &ErrorOffset );
+
+ if (!NT_SUCCESS( Status )) {
+
+ Irp->IoStatus.Information = ErrorOffset;
+ try_return( Status );
+ }
+ }
+
+ //
+ // Check if the file has existing Ea's.
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $EA_INFORMATION,
+ &EaInfoAttr)) {
+
+ PreviousEas = TRUE;
+
+ EaInformation = (PEA_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &EaInfoAttr ));
+
+ } else {
+
+ PreviousEas = FALSE;
+ }
+
+ //
+ // Sanity check.
+ //
+
+ ASSERT( !PreviousEas || EaInformation->UnpackedEaSize != 0 );
+
+ //
+ // Initialize our Ea list structure depending on whether there
+ // were previous Ea's or not.
+ //
+
+ if (PreviousEas) {
+
+ //
+ // Copy the information out of the Ea information attribute.
+ //
+
+ EaList.PackedEaSize = (ULONG) EaInformation->PackedEaSize;
+ EaList.NeedEaCount = EaInformation->NeedEaCount;
+ EaList.UnpackedEaSize = EaInformation->UnpackedEaSize;
+
+ CurrentEas = NtfsMapExistingEas( IrpContext,
+ Fcb,
+ &EaBcb,
+ &EaList.BufferSize );
+
+ //
+ // The allocated size of the Ea buffer is the Unpacked length.
+ //
+
+ EaList.FullEa = NtfsAllocatePool(PagedPool, EaList.BufferSize );
+
+ //
+ // Now copy the mapped Eas.
+ //
+
+ RtlCopyMemory( EaList.FullEa,
+ CurrentEas,
+ EaList.BufferSize );
+
+ //
+ // Upin the stream file.
+ //
+
+ NtfsUnpinBcb( &EaBcb );
+
+ } else {
+
+ //
+ // Set this up as an empty list.
+ //
+
+ EaList.PackedEaSize = 0;
+ EaList.NeedEaCount = 0;
+ EaList.UnpackedEaSize = 0;
+ EaList.BufferSize = 0;
+ EaList.FullEa = NULL;
+ }
+
+ //
+ // Build the new ea list.
+ //
+
+ Status = NtfsBuildEaList( IrpContext,
+ Vcb,
+ &EaList,
+ Buffer,
+ &Irp->IoStatus.Information );
+
+ if (!NT_SUCCESS( Status )) {
+
+ try_return( Status );
+ }
+
+ //
+ // Replace the existing Eas.
+ //
+
+ NtfsReplaceFileEas( IrpContext, Fcb, &EaList );
+
+ //
+ // Increment the Modification count for the Eas.
+ //
+
+ Fcb->EaModificationCount++;
+
+ //
+ // Update the information in the duplicate information and mark
+ // the Fcb as info modified.
+ //
+
+ if (EaList.UnpackedEaSize == 0) {
+
+ Fcb->Info.PackedEaSize = 0;
+
+ } else {
+
+ Fcb->Info.PackedEaSize = (USHORT) EaList.PackedEaSize;
+ }
+
+ //
+ // Update the caller's Iosb.
+ //
+
+ Irp->IoStatus.Information = 0;
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+
+ //
+ // Check if there are transactions to cleanup.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ //
+ // Show that we changed the Ea's and also set the Ccb flag so we will
+ // update the time stamps.
+ //
+
+ SetFlag( Ccb->Flags,
+ CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonSetEa );
+
+ //
+ // Free the in-memory copy of the Eas.
+ //
+
+ if (EaList.FullEa != NULL) {
+
+ NtfsFreePool( EaList.FullEa );
+ }
+
+ //
+ // Unpin the Bcb.
+ //
+
+ NtfsUnpinBcb( &EaBcb );
+
+ //
+ // Cleanup any attribute contexts used.
+ //
+
+ NtfsCleanupAttributeContext( &EaInfoAttr );
+
+ //
+ // Release the Fcb.
+ //
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+
+ //
+ // Complete the Irp.
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetEa -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsAppendEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN PFILE_FULL_EA_INFORMATION FullEa,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine appends a new packed ea onto an existing ea list,
+ it also will allocate/dealloate pool as necessary to hold the ea list.
+
+Arguments:
+
+ EaListHeader - Supplies a pointer the Ea list header structure.
+
+ FullEa - Supplies a pointer to the new full ea that is to be appended
+ to the ea list.
+
+ Vcb - Vcb for this volume.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG UnpackedEaLength;
+ STRING EaName;
+ PFILE_FULL_EA_INFORMATION ThisEa;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAppendEa...\n") );
+
+ UnpackedEaLength = AlignedUnpackedEaSize( FullEa );
+
+ //
+ // As a quick check see if the computed packed ea size plus the
+ // current ea list size will overflow the buffer.
+ //
+
+ if (UnpackedEaLength + EaListHeader->UnpackedEaSize > EaListHeader->BufferSize) {
+
+ //
+ // We will overflow our current work buffer so allocate a larger
+ // one and copy over the current buffer
+ //
+
+ PVOID Temp;
+ ULONG NewAllocationSize;
+
+ DebugTrace( 0, Dbg, ("Allocate a new ea list buffer\n") );
+
+ //
+ // Compute a new size and allocate space. Always increase the
+ // allocation in cluster increments.
+ //
+
+ NewAllocationSize = ClusterAlign( Vcb,
+ UnpackedEaLength
+ + EaListHeader->UnpackedEaSize );
+
+ Temp = NtfsAllocatePool(PagedPool, NewAllocationSize );
+
+ //
+ // Move over the existing ea list and zero the remaining space.
+ //
+
+ RtlCopyMemory( Temp,
+ EaListHeader->FullEa,
+ EaListHeader->BufferSize );
+
+ RtlZeroMemory( Add2Ptr( Temp, EaListHeader->BufferSize ),
+ NewAllocationSize - EaListHeader->BufferSize );
+
+ //
+ // Deallocate the current Ea list and use the freshly allocated list.
+ //
+
+ if (EaListHeader->FullEa != NULL) {
+
+ NtfsFreePool( EaListHeader->FullEa );
+ }
+
+ EaListHeader->FullEa = Temp;
+
+ EaListHeader->BufferSize = NewAllocationSize;
+ }
+
+ //
+ // Determine if we need to increment our need ea changes count
+ //
+
+ if (FlagOn( FullEa->Flags, FILE_NEED_EA )) {
+
+ EaListHeader->NeedEaCount += 1;
+ }
+
+ //
+ // Now copy over the ea.
+ //
+ // Before:
+ // UsedSize Allocated
+ // | |
+ // V V
+ // +xxxxxxxx+-----------------------------+
+ //
+ // After:
+ // UsedSize Allocated
+ // | |
+ // V V
+ // +xxxxxxxx+yyyyyyyyyyyyyyyy+------------+
+ //
+
+ ThisEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( EaListHeader->FullEa,
+ EaListHeader->UnpackedEaSize );
+
+ RtlCopyMemory( ThisEa,
+ FullEa,
+ UnpackedEaLength );
+
+ //
+ // We always store the offset of this Ea in the next entry offset field.
+ //
+
+ ThisEa->NextEntryOffset = UnpackedEaLength;
+
+ //
+ // Upcase the name.
+ //
+
+ EaName.MaximumLength = EaName.Length = ThisEa->EaNameLength;
+ EaName.Buffer = &ThisEa->EaName[0];
+
+ NtfsUpcaseEaName( &EaName, &EaName );
+
+ //
+ // Increment the used size in the ea list structure
+ //
+
+ EaListHeader->UnpackedEaSize += UnpackedEaLength;
+ EaListHeader->PackedEaSize += PackedEaSize( FullEa );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsAppendEa -> VOID\n") );
+
+ return;
+
+ UNREFERENCED_PARAMETER( IrpContext );
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsDeleteEa (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN ULONG Offset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes an individual packed ea from the supplied
+ ea list.
+
+Arguments:
+
+ EaListHeader - Supplies a pointer to the Ea list header structure.
+
+ Offset - Supplies the offset to the individual ea in the list to delete
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFILE_FULL_EA_INFORMATION ThisEa;
+ ULONG UnpackedEaLength;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeletePackedEa, Offset = %08lx\n", Offset) );
+
+ //
+ // Get a reference to the Ea to delete.
+ //
+
+ ThisEa = Add2Ptr( EaListHeader->FullEa, Offset );
+
+ //
+ // Determine if we need to decrement our need ea changes count
+ //
+
+ if (FlagOn( ThisEa->Flags, FILE_NEED_EA )) {
+
+ EaListHeader->NeedEaCount--;
+ }
+
+ //
+ // Decrement the Ea size values.
+ //
+
+ EaListHeader->PackedEaSize -= PackedEaSize( ThisEa );
+
+ UnpackedEaLength = AlignedUnpackedEaSize( ThisEa );
+ EaListHeader->UnpackedEaSize -= UnpackedEaLength;
+
+ //
+ // Shrink the ea list over the deleted ea. The amount to copy is the
+ // total size of the ea list minus the offset to the end of the ea
+ // we're deleting.
+ //
+ // Before:
+ // Offset Offset+UnpackedEaLength UsedSize Allocated
+ // | | | |
+ // V V V V
+ // +xxxxxxxx+yyyyyyyyyyyyyyyy+zzzzzzzzzzzzzzzzzz+------------+
+ //
+ // After
+ // Offset UsedSize Allocated
+ // | | |
+ // V V V
+ // +xxxxxxxx+zzzzzzzzzzzzzzzzzz+-----------------------------+
+ //
+
+ RtlMoveMemory( ThisEa,
+ Add2Ptr( ThisEa, ThisEa->NextEntryOffset ),
+ EaListHeader->UnpackedEaSize - Offset );
+
+ //
+ // And zero out the remaing part of the ea list, to make things
+ // nice and more robust
+ //
+
+ RtlZeroMemory( Add2Ptr( EaListHeader->FullEa, EaListHeader->UnpackedEaSize ),
+ UnpackedEaLength );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteEa -> VOID\n") );
+
+ return;
+
+ UNREFERENCED_PARAMETER( IrpContext );
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsLocateEaByName (
+ IN PFILE_FULL_EA_INFORMATION FullEa,
+ IN ULONG EaBufferLength,
+ IN PSTRING EaName,
+ OUT PULONG Offset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine locates the offset for the next individual packed ea
+ inside of a ea list, given the name of the ea to locate.
+
+Arguments:
+
+ FullEa - Pointer to the first Ea to look at.
+
+ EaBufferLength - This is the ulong-aligned size of the Ea buffer.
+
+ EaName - Supplies the name of the ea search for
+
+ Offset - Receives the offset to the located individual ea in the list
+ if one exists.
+
+Return Value:
+
+ BOOLEAN - TRUE if the named ea exists in the list and FALSE
+ otherwise.
+
+--*/
+
+{
+ PFILE_FULL_EA_INFORMATION ThisEa;
+ STRING Name;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsLocateEaByName, EaName = %Z\n", EaName) );
+
+ //
+ // If the Ea list is NULL, there is nothing to do.
+ //
+
+ if (FullEa == NULL) {
+
+ DebugTrace( -1, Dbg, ("NtfsLocateEaByName: No work to do\n") );
+ return FALSE;
+ }
+
+ //
+ // For each ea in the list check its name against the
+ // ea name we're searching for
+ //
+
+ *Offset = 0;
+
+ //
+ // We assume there is at least one Ea in the list.
+ //
+
+ do {
+
+ ThisEa = Add2Ptr( FullEa, *Offset );
+
+ //
+ // Make a string out of the name in the Ea and compare it to the
+ // given string.
+ //
+
+ RtlInitString( &Name, &ThisEa->EaName[0] );
+
+ if ( RtlCompareString( EaName, &Name, TRUE ) == 0 ) {
+
+ DebugTrace( -1, Dbg, ("NtfsLocateEaByName -> TRUE, *Offset = %08lx\n", *Offset) );
+ return TRUE;
+ }
+
+ //
+ // Update the offset to get to the next Ea.
+ //
+
+ *Offset += AlignedUnpackedEaSize( ThisEa );
+
+ } while ( *Offset < EaBufferLength );
+
+ //
+ // We've exhausted the ea list without finding a match so return false
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsLocateEaByName -> FALSE\n") );
+ return FALSE;
+}
+
+
+//
+// Local support routine.
+//
+
+PFILE_FULL_EA_INFORMATION
+NtfsMapExistingEas (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PBCB *EaBcb,
+ OUT PULONG EaLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine maps the current Eas for the file, either through the
+ Mft record for the file if resident or the Scb for the non-resident
+ Eas.
+
+Arguments:
+
+ Fcb - Pointer to the Fcb for the file whose Ea's are being queried.
+
+ EaBcb - Pointer to the Bcb to use if we are mapping data in the
+ Ea attribute stream file.
+
+ EaLength - Returns the length of the packed Eas in bytes.
+
+Return Value:
+
+ PFILE_FULL_EA_INFORMATION - Pointer to the mapped attributes.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PFILE_FULL_EA_INFORMATION CurrentEas;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMapExistingEas: Entered\n") );
+
+ //
+ // We start by looking up the Ea attribute. It better be there.
+ //
+
+ NtfsInitializeAttributeContext( &Context );
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $EA,
+ &Context )) {
+
+ //
+ // This is a disk corrupt error.
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsMapExistingEas: Corrupt disk\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ try {
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *)&CurrentEas,
+ EaLength,
+ EaBcb,
+ &Context );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMapExistingEas: Exit\n") );
+
+ return CurrentEas;
+}
+
+
+//
+// Local support routine.
+//
+
+IO_STATUS_BLOCK
+NtfsQueryEaUserEaList (
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN PFILE_GET_EA_INFORMATION UserEaList,
+ IN BOOLEAN ReturnSingleEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the work routine for querying EAs given a list
+ of Ea's to search for.
+
+Arguments:
+
+ CurrentEas - This is a pointer to the current Eas for the file
+
+ EaInformation - This is a pointer to an Ea information attribute.
+
+ EaBuffer - Supplies the buffer to receive the full eas
+
+ UserBufferLength - Supplies the length, in bytes, of the user buffer
+
+ UserEaList - Supplies the user specified ea name list
+
+ ReturnSingleEntry - Indicates if we are to return a single entry or not
+
+Return Value:
+
+ IO_STATUS_BLOCK - Receives the completion status for the operation
+
+--*/
+
+{
+ IO_STATUS_BLOCK Iosb;
+
+ ULONG GeaOffset;
+ ULONG FeaOffset;
+ ULONG Offset;
+
+ PFILE_FULL_EA_INFORMATION LastFullEa;
+ PFILE_FULL_EA_INFORMATION NextFullEa;
+
+ PFILE_GET_EA_INFORMATION GetEa;
+
+ BOOLEAN Overflow;
+ ULONG PrevEaPadding;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryEaUserEaList: Entered\n") );
+
+ //
+ // Setup pointer in the output buffer so we can track the Ea being
+ // written to it and the last Ea written.
+ //
+
+ LastFullEa = NULL;
+
+ Overflow = FALSE;
+
+ //
+ // Initialize our next offset value.
+ //
+
+ GeaOffset = 0;
+ Offset = 0;
+ PrevEaPadding = 0;
+
+ //
+ // Loop through all the entries in the user's ea list.
+ //
+
+ while (TRUE) {
+
+ STRING GeaName;
+ STRING OutputEaName;
+ ULONG RawEaSize;
+
+ //
+ // Get the next entry in the user's list.
+ //
+
+ GetEa = (PFILE_GET_EA_INFORMATION) Add2Ptr( UserEaList, GeaOffset );
+
+ //
+ // Make a string reference to the name and see if we can locate
+ // the ea by name.
+ //
+
+ GeaName.MaximumLength = GeaName.Length = GetEa->EaNameLength;
+ GeaName.Buffer = &GetEa->EaName[0];
+
+ //
+ // Upcase the name so we can do a case-insensitive compare.
+ //
+
+ NtfsUpcaseEaName( &GeaName, &GeaName );
+
+ //
+ // Check for a valid name.
+ //
+
+ if (!NtfsIsEaNameValid( GeaName )) {
+
+ DebugTrace( -1, Dbg, ("NtfsQueryEaUserEaList: Invalid Ea Name\n") );
+
+ Iosb.Information = GeaOffset;
+ Iosb.Status = STATUS_INVALID_EA_NAME;
+ return Iosb;
+ }
+
+ GeaOffset += GetEa->NextEntryOffset;
+
+ //
+ // If this is a duplicate name, then step over this entry.
+ //
+
+ if (NtfsIsDuplicateGeaName( GetEa, UserEaList )) {
+
+ continue;
+ }
+
+ //
+ // Generate a pointer in the Ea buffer.
+ //
+
+ NextFullEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( EaBuffer, Offset + PrevEaPadding );
+
+ //
+ // Try to find a matching Ea.
+ // If we couldn't, let's dummy up an Ea to give to the user.
+ //
+
+ if (!NtfsLocateEaByName( CurrentEas,
+ EaInformation->UnpackedEaSize,
+ &GeaName,
+ &FeaOffset )) {
+
+ //
+ // We were not able to locate the name therefore we must
+ // dummy up a entry for the query. The needed Ea size is
+ // the size of the name + 4 (next entry offset) + 1 (flags)
+ // + 1 (name length) + 2 (value length) + the name length +
+ // 1 (null byte).
+ //
+
+ RawEaSize = 4+1+1+2+GetEa->EaNameLength+1;
+
+ if ((RawEaSize + PrevEaPadding) > UserBufferLength) {
+
+ Overflow = TRUE;
+ break;
+ }
+
+ //
+ // Everything is going to work fine, so copy over the name,
+ // set the name length and zero out the rest of the ea.
+ //
+
+ NextFullEa->NextEntryOffset = 0;
+ NextFullEa->Flags = 0;
+ NextFullEa->EaNameLength = GetEa->EaNameLength;
+ NextFullEa->EaValueLength = 0;
+ RtlCopyMemory( &NextFullEa->EaName[0],
+ &GetEa->EaName[0],
+ GetEa->EaNameLength );
+
+ //
+ // Upcase the name in the buffer.
+ //
+
+ OutputEaName.MaximumLength = OutputEaName.Length = GeaName.Length;
+ OutputEaName.Buffer = NextFullEa->EaName;
+
+ NtfsUpcaseEaName( &OutputEaName, &OutputEaName );
+
+ NextFullEa->EaName[GetEa->EaNameLength] = 0;
+
+ //
+ // Otherwise return the Ea we found back to the user.
+ //
+
+ } else {
+
+ PFILE_FULL_EA_INFORMATION ThisEa;
+
+ //
+ // Reference this ea.
+ //
+
+ ThisEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( CurrentEas, FeaOffset );
+
+ //
+ // Check if this Ea can fit in the user's buffer.
+ //
+
+ RawEaSize = RawUnpackedEaSize( ThisEa );
+
+ if (RawEaSize > (UserBufferLength - PrevEaPadding)) {
+
+ Overflow = TRUE;
+ break;
+ }
+
+ //
+ // Copy this ea to the user's buffer.
+ //
+
+ RtlCopyMemory( NextFullEa,
+ ThisEa,
+ RawEaSize);
+
+ NextFullEa->NextEntryOffset = 0;
+ }
+
+ //
+ // Compute the next offset in the user's buffer.
+ //
+
+ Offset += (RawEaSize + PrevEaPadding);
+
+ //
+ // If we were to return a single entry then break out of our loop
+ // now
+ //
+
+ if (ReturnSingleEntry) {
+
+ break;
+ }
+
+ //
+ // If we have a new Ea entry, go back and update the offset field
+ // of the previous Ea entry.
+ //
+
+ if (LastFullEa != NULL) {
+
+ LastFullEa->NextEntryOffset = PtrOffset( LastFullEa, NextFullEa );
+ }
+
+ //
+ // If we've exhausted the entries in the Get Ea list, then we are
+ // done.
+ //
+
+ if (GetEa->NextEntryOffset == 0) {
+
+ break;
+ }
+
+ //
+ // Remember this as the previous ea value. Also update the buffer
+ // length values and the buffer offset values.
+ //
+
+ LastFullEa = NextFullEa;
+ UserBufferLength -= (RawEaSize + PrevEaPadding);
+
+ //
+ // Now remember the padding bytes needed for this call.
+ //
+
+ PrevEaPadding = LongAlign( RawEaSize ) - RawEaSize;
+ }
+
+ //
+ // If the Ea information won't fit in the user's buffer, then return
+ // an overflow status.
+ //
+
+ if (Overflow) {
+
+ Iosb.Information = 0;
+ Iosb.Status = STATUS_BUFFER_OVERFLOW;
+
+ //
+ // Otherwise return the length of the data returned.
+ //
+
+ } else {
+
+ //
+ // Return the length of the buffer filled and a success
+ // status.
+ //
+
+ Iosb.Information = Offset;
+ Iosb.Status = STATUS_SUCCESS;
+ }
+
+ DebugTrace( 0, Dbg, ("Status -> %08lx\n", Iosb.Status) );
+ DebugTrace( 0, Dbg, ("Information -> %08lx\n", Iosb.Information) );
+ DebugTrace( -1, Dbg, ("NtfsQueryEaUserEaList: Exit\n") );
+
+ return Iosb;
+}
+
+
+//
+// Local support routine
+//
+
+IO_STATUS_BLOCK
+NtfsQueryEaIndexSpecified (
+ OUT PCCB Ccb,
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN ULONG UserEaIndex,
+ IN BOOLEAN ReturnSingleEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the work routine for querying EAs given an ea index
+
+Arguments:
+
+ Ccb - This is the Ccb for the caller.
+
+ CurrentEas - This is a pointer to the current Eas for the file.
+
+ EaInformation - This is a pointer to an Ea information attribute.
+
+ EaBuffer - Supplies the buffer to receive the full eas
+
+ UserBufferLength - Supplies the length, in bytes, of the user buffer
+
+ UserEaIndex - This is the Index for the first ea to return. The value
+ 1 indicates the first ea of the file.
+
+ ReturnSingleEntry - Indicates if we are to return a single entry or not
+
+Return Value:
+
+ IO_STATUS_BLOCK - Receives the completion status for the operation
+
+--*/
+
+{
+ IO_STATUS_BLOCK Iosb;
+
+ ULONG i;
+ ULONG Offset;
+ PFILE_FULL_EA_INFORMATION ThisEa;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryEaIndexSpecified: Entered\n") );
+
+ i = 1;
+ Offset = 0;
+ ThisEa = NULL;
+
+ //
+ // If the index value is zero, there are no Eas to return.
+ //
+
+ if (UserEaIndex == 0
+ || EaInformation->UnpackedEaSize == 0) {
+
+ DebugTrace( -1, Dbg, ("NtfsQueryEaIndexSpecified: Non-existant entry\n") );
+
+ Iosb.Information = 0;
+ Iosb.Status = STATUS_NONEXISTENT_EA_ENTRY;
+
+ return Iosb;
+ }
+
+ //
+ // Walk through the CurrentEas until we find the starting Ea offset.
+ //
+
+ while (i < UserEaIndex
+ && Offset < EaInformation->UnpackedEaSize) {
+
+ ThisEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( CurrentEas, Offset );
+
+ Offset += AlignedUnpackedEaSize( ThisEa );
+
+ i += 1;
+ }
+
+ if (Offset >= EaInformation->UnpackedEaSize) {
+
+ //
+ // If we just passed the last Ea, we will return STATUS_NO_MORE_EAS.
+ // This is for the caller who may be enumerating the Eas.
+ //
+
+ if (i == UserEaIndex) {
+
+ Iosb.Status = STATUS_NO_MORE_EAS;
+
+ //
+ // Otherwise we report that this is a bad ea index.
+ //
+
+ } else {
+
+ Iosb.Status = STATUS_NONEXISTENT_EA_ENTRY;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsQueryEaIndexSpecified -> %08lx\n", Iosb.Status) );
+ return Iosb;
+ }
+
+ //
+ // We now have the offset of the first Ea to return to the user.
+ // We simply call our EaSimpleScan routine to do the actual work.
+ //
+
+ Iosb = NtfsQueryEaSimpleScan( Ccb,
+ CurrentEas,
+ EaInformation,
+ EaBuffer,
+ UserBufferLength,
+ ReturnSingleEntry,
+ Offset );
+
+ DebugTrace( -1, Dbg, ("NtfsQueryEaIndexSpecified: Exit\n") );
+
+ return Iosb;
+}
+
+
+//
+// Local support routine
+//
+
+IO_STATUS_BLOCK
+NtfsQueryEaSimpleScan (
+ OUT PCCB Ccb,
+ IN PFILE_FULL_EA_INFORMATION CurrentEas,
+ IN PEA_INFORMATION EaInformation,
+ OUT PFILE_FULL_EA_INFORMATION EaBuffer,
+ IN ULONG UserBufferLength,
+ IN BOOLEAN ReturnSingleEntry,
+ IN ULONG StartingOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the work routine for querying EAs starting from a given
+ offset within the Ea attribute.
+
+Arguments:
+
+ Ccb - This is the Ccb for the caller.
+
+ CurrentEas - This is a pointer to the current Eas for the file.
+
+ EaInformation - This is a pointer to an Ea information attribute.
+
+ EaBuffer - Supplies the buffer to receive the full eas
+
+ UserBufferLength - Supplies the length, in bytes, of the user buffer
+
+ ReturnSingleEntry - Indicates if we are to return a single entry or not
+
+ StartingOffset - Supplies the offset of the first Ea to return
+
+Return Value:
+
+ IO_STATUS_BLOCK - Receives the completion status for the operation
+
+--*/
+
+{
+ IO_STATUS_BLOCK Iosb;
+
+ PFILE_FULL_EA_INFORMATION LastFullEa;
+ PFILE_FULL_EA_INFORMATION NextFullEa;
+ PFILE_FULL_EA_INFORMATION ThisEa;
+
+ BOOLEAN BufferOverflow = FALSE;
+
+ ULONG BufferOffset;
+ ULONG PrevEaPadding;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryEaSimpleScan: Entered\n") );
+
+ //
+ // Initialize our Ea pointers and the offsets into the user buffer
+ // and our Ea buffer.
+ //
+
+ LastFullEa = NULL;
+ BufferOffset = 0;
+ PrevEaPadding = 0;
+
+ //
+ // Loop until the Ea offset is beyond the valid range of Eas.
+ //
+
+ while (StartingOffset < EaInformation->UnpackedEaSize) {
+
+ ULONG EaSize;
+
+ //
+ // Reference the next EA to return.
+ //
+
+ ThisEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( CurrentEas, StartingOffset);
+
+ //
+ // If the size of this Ea is greater than the remaining buffer size,
+ // we exit the loop. We need to remember to include any padding bytes
+ // from the previous Eas.
+ //
+
+ EaSize = RawUnpackedEaSize( ThisEa );
+
+ if ((EaSize + PrevEaPadding) > UserBufferLength) {
+
+ BufferOverflow = TRUE;
+ break;
+ }
+
+ //
+ // Copy the Ea into the user's buffer.
+ //
+
+ BufferOffset += PrevEaPadding;
+
+ NextFullEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( EaBuffer, BufferOffset );
+
+ RtlCopyMemory( NextFullEa, ThisEa, EaSize );
+
+ //
+ // Move to the next Ea.
+ //
+
+ LastFullEa = NextFullEa;
+ UserBufferLength -= (EaSize + PrevEaPadding);
+ BufferOffset += EaSize;
+
+ StartingOffset += LongAlign( EaSize );
+
+ //
+ // Remember the padding needed for this entry.
+ //
+
+ PrevEaPadding = LongAlign( EaSize ) - EaSize;
+
+ //
+ // If the user only wanted one entry, exit now.
+ //
+
+ if (ReturnSingleEntry) {
+
+ break;
+ }
+ }
+
+ //
+ // If we didn't find any entries, it could be because there were no
+ // more to find or that we ran out of buffer space.
+ //
+
+ if (LastFullEa == NULL) {
+
+ Iosb.Information = 0;
+
+ //
+ // We were not able to return a single ea entry, now we need to find
+ // out if it is because we didn't have an entry to return or the
+ // buffer is too small. If the Offset variable is less than
+ // the size of the Ea attribute, then the user buffer is too small.
+ //
+
+ if (EaInformation->UnpackedEaSize == 0) {
+
+ Iosb.Status = STATUS_NO_EAS_ON_FILE;
+
+ } else if (StartingOffset >= EaInformation->UnpackedEaSize) {
+
+ Iosb.Status = STATUS_NO_MORE_EAS;
+
+ } else {
+
+ Iosb.Status = STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // Otherwise we have returned some Ea's. Update the Iosb to return.
+ //
+
+ } else {
+
+ //
+ // Update the Ccb to show where to start the next search.
+ //
+
+ Ccb->NextEaOffset = StartingOffset;
+
+ //
+ // Zero the next entry field of the last Ea.
+ //
+
+ LastFullEa->NextEntryOffset = 0;
+
+ //
+ // Now update the Iosb.
+ //
+
+ Iosb.Information = BufferOffset;
+
+ //
+ // If there are more to return, report the buffer was too small.
+ // Otherwise return STATUS_SUCCESS.
+ //
+
+ if (BufferOverflow) {
+
+ Iosb.Status = STATUS_BUFFER_OVERFLOW;
+
+ } else {
+
+ Iosb.Status = STATUS_SUCCESS;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsQueryEaSimpleScan: Exit\n") );
+
+ return Iosb;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsIsDuplicateGeaName (
+ IN PFILE_GET_EA_INFORMATION GetEa,
+ IN PFILE_GET_EA_INFORMATION UserGeaBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine walks through a list of Gea names to find a duplicate name.
+ 'GetEa' is an actual position in the list, 'UserGeaBuffer' is the beginning
+ of the list. We are only interested in
+ previous matching ea names, as the ea information for that ea name
+ would have been returned with the previous instance.
+
+Arguments:
+
+ GetEa - Supplies the Ea name structure for the ea name to match.
+
+ UserGeaBuffer - Supplies a pointer to the user buffer with the list
+ of ea names to search for.
+
+Return Value:
+
+ BOOLEAN - TRUE if a previous match is found, FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN DuplicateFound;
+ STRING GeaString;
+
+ PFILE_GET_EA_INFORMATION ThisGetEa;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsDuplicateGeaName: Entered\n") );
+
+ //
+ // Set up the string structure.
+ //
+
+ GeaString.MaximumLength = GeaString.Length = GetEa->EaNameLength;
+ GeaString.Buffer = &GetEa->EaName[0];
+
+ DuplicateFound = FALSE;
+
+ ThisGetEa = UserGeaBuffer;
+
+ //
+ // We loop until we reach the given Gea or a match is found.
+ //
+
+ while (ThisGetEa != GetEa) {
+
+ STRING ThisGea;
+
+ //
+ // Create a string structure for the current Gea.
+ //
+
+ ThisGea.MaximumLength = ThisGea.Length = ThisGetEa->EaNameLength;
+ ThisGea.Buffer = &ThisGetEa->EaName[0];
+
+ //
+ // Check if the Gea names match, exit if they do.
+ //
+
+ if (NtfsAreEaNamesEqual( &GeaString,
+ &ThisGea )) {
+
+ DuplicateFound = TRUE;
+ break;
+ }
+
+ //
+ // Move to the next Gea entry.
+ //
+
+ ThisGetEa = (PFILE_GET_EA_INFORMATION) Add2Ptr( ThisGetEa,
+ ThisGetEa->NextEntryOffset );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsIsDuplicateGeaName: Exit\n") );
+
+ return DuplicateFound;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsBuildEaList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN PFILE_FULL_EA_INFORMATION UserEaList,
+ OUT PULONG ErrorOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to build an up-to-date Ea list based on the
+ given existing Ea list and the user-specified Ea list.
+
+Arguments:
+
+ Vcb - The Vcb for the volume.
+
+ EaListHeader - This is the Ea list to modify.
+
+ UserEaList - This is the user specified Ea list.
+
+ ErrorOffset - Supplies the address to store the offset of an invalid
+ Ea in the user's list.
+
+Return Value:
+
+ NTSTATUS - The result of modifying the Ea list.
+
+--*/
+
+{
+ NTSTATUS Status;
+ BOOLEAN MoreEas;
+ ULONG Offset;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsBuildEaList: Entered\n") );
+
+ Status = STATUS_SUCCESS;
+ Offset = 0;
+
+ //
+ // Now for each full ea in the input user buffer we do the specified operation
+ // on the ea.
+ //
+
+ do {
+
+ STRING EaName;
+ ULONG EaOffset;
+
+ PFILE_FULL_EA_INFORMATION ThisEa;
+
+ ThisEa = (PFILE_FULL_EA_INFORMATION) Add2Ptr( UserEaList, Offset );
+
+ //
+ // Create a string out of the name in the user's Ea.
+ //
+
+ EaName.MaximumLength = EaName.Length = ThisEa->EaNameLength;
+ EaName.Buffer = &ThisEa->EaName[0];
+
+ //
+ // If the Ea isn't valid, return error offset to caller.
+ //
+
+ if (!NtfsIsEaNameValid( EaName )) {
+
+ *ErrorOffset = Offset;
+ Status = STATUS_INVALID_EA_NAME;
+
+ break;
+ }
+
+ //
+ // Verify that no invalid ea flags are set.
+ //
+
+ if (ThisEa->Flags != 0
+ && ThisEa->Flags != FILE_NEED_EA) {
+
+ *ErrorOffset = Offset;
+ Status = STATUS_INVALID_EA_NAME;
+
+ break;
+ }
+
+ //
+ // If we can find the name in the Ea set, we remove it.
+ //
+
+ if (NtfsLocateEaByName( EaListHeader->FullEa,
+ EaListHeader->UnpackedEaSize,
+ &EaName,
+ &EaOffset )) {
+
+ NtfsDeleteEa( IrpContext,
+ EaListHeader,
+ EaOffset );
+ }
+
+ //
+ // If the user specified a non-zero value length, we add this
+ // ea to the in memory Ea list.
+ //
+
+ if (ThisEa->EaValueLength != 0) {
+
+ NtfsAppendEa( IrpContext,
+ EaListHeader,
+ ThisEa,
+ Vcb );
+ }
+
+ //
+ // Move to the next Ea in the list.
+ //
+
+ Offset += AlignedUnpackedEaSize( ThisEa );
+
+ MoreEas = (BOOLEAN) (ThisEa->NextEntryOffset != 0);
+
+ } while( MoreEas );
+
+ //
+ // First we check that the packed size of the Eas does not exceed the
+ // maximum value. We have to reserve the 4 bytes for the OS/2 list
+ // header.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ if (EaListHeader->PackedEaSize > (MAXIMUM_EA_SIZE - 4)) {
+
+ Status = STATUS_EA_TOO_LARGE;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsBuildEaList: Exit\n") );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsReplaceFileEas (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PEA_LIST_HEADER EaList
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will replace an existing Ea list with a new Ea list. It
+ correctly handles the case where there was no previous Eas and where we
+ are removing all of the previous EAs.
+
+Arguments:
+
+ Fcb - Fcb for the file with the EAs
+
+ EaList - This contains the modified Ea list.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ EA_INFORMATION ThisEaInformation;
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PSCB EaScb;
+ BOOLEAN EaScbAcquired = FALSE;
+
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReplaceFileEas: Entered\n") );
+
+ ThisEaInformation.PackedEaSize = (USHORT) EaList->PackedEaSize;
+ ThisEaInformation.UnpackedEaSize = EaList->UnpackedEaSize;
+ ThisEaInformation.NeedEaCount = EaList->NeedEaCount;
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // First we handle $EA_INFORMATION and then the $EA attribute in the
+ // same fashion.
+ //
+
+ try {
+
+ //
+ // Lookup the $EA_INFORMATION attribute. If it does not exist then we
+ // will need to create one.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $EA_INFORMATION,
+ &Context )) {
+
+ if (EaList->UnpackedEaSize != 0) {
+
+ DebugTrace( 0, Dbg, ("Create a new $EA_INFORMATION attribute\n") );
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $EA_INFORMATION,
+ NULL, // attribute name
+ &ThisEaInformation,
+ sizeof(EA_INFORMATION),
+ 0, // attribute flags
+ NULL, // where indexed
+ TRUE, // logit
+ &Context );
+ }
+
+ } else {
+
+ //
+ // If it exists, and we are writing an EA, then we have to update it.
+ //
+
+ if (EaList->UnpackedEaSize != 0) {
+
+ DebugTrace( 0, Dbg, ("Change an existing $EA_INFORMATION attribute\n") );
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0, // Value offset
+ &ThisEaInformation,
+ sizeof(EA_INFORMATION),
+ TRUE, // SetNewLength
+ FALSE, // LogNonResidentToo
+ FALSE, // CreateSectionUnderway
+ FALSE,
+ &Context );
+
+ //
+ // If it exists, but our new length is zero, then delete it.
+ //
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("Delete existing $EA_INFORMATION attribute\n") );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &Context );
+ }
+ }
+
+ //
+ // Now we will cleanup and reinitialize the context for reuse.
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Lookup the $EA attribute. If it does not exist then we will need to create
+ // one.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $EA,
+ &Context )) {
+
+ if (EaList->UnpackedEaSize != 0) {
+
+ DebugTrace( 0, Dbg, ("Create a new $EA attribute\n") );
+
+ NtfsCleanupAttributeContext( &Context );
+ NtfsInitializeAttributeContext( &Context );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $EA,
+ NULL, // attribute name
+ EaList->FullEa,
+ EaList->UnpackedEaSize,
+ 0, // attribute flags
+ NULL, // where indexed
+ TRUE, // logit
+ &Context );
+ }
+
+ } else {
+
+ //
+ // If it exists, and we are writing an EA, then we have to update it.
+ //
+
+ if (EaList->UnpackedEaSize != 0) {
+
+ DebugTrace( 0, Dbg, ("Change an existing $EA attribute\n") );
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0, // Value offset
+ EaList->FullEa,
+ EaList->UnpackedEaSize,
+ TRUE, // SetNewLength
+ FALSE, // LogNonResidentToo
+ FALSE, // CreateSectionUnderway
+ FALSE,
+ &Context );
+
+ //
+ // If it exists, but our new length is zero, then delete it.
+ //
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("Delete existing $EA attribute\n") );
+
+ //
+ // If the stream is non-resident then get hold of an
+ // Scb for this.
+ //
+
+ if (!NtfsIsAttributeResident( NtfsFoundAttribute( &Context ))) {
+
+ EaScb = NtfsCreateScb( IrpContext,
+ Fcb,
+ $EA,
+ &NtfsEmptyString,
+ FALSE,
+ NULL );
+
+ NtfsAcquireExclusiveScb( IrpContext, EaScb );
+ EaScbAcquired = TRUE;
+ }
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &Context );
+
+ //
+ // If we have acquired the Scb then knock the sizes back
+ // to zero.
+ //
+
+ if (EaScbAcquired) {
+
+ EaScb->Header.FileSize =
+ EaScb->Header.ValidDataLength =
+ EaScb->Header.AllocationSize = Li0;
+
+ SetFlag( EaScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+ }
+
+ //
+ // Increment the Modification count for the Eas.
+ //
+
+ Fcb->EaModificationCount++;
+
+ if (EaList->UnpackedEaSize == 0) {
+
+ Fcb->Info.PackedEaSize = 0;
+
+ } else {
+
+ Fcb->Info.PackedEaSize = (USHORT) EaList->PackedEaSize;
+ }
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_EA_SIZE );
+
+ } finally {
+
+ DebugUnwind( NtfsReplaceFileEas );
+
+ if (EaScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, EaScb );
+ }
+
+ //
+ // Cleanup our attribute enumeration context
+ //
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsReplaceFileEas: Exit\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsIsEaNameValid (
+ IN STRING Name
+ )
+
+/*++
+
+Routine Description:
+
+ This routine simple returns whether the specified file names conforms
+ to the file system specific rules for legal Ea names.
+
+ For Ea names, the following rules apply:
+
+ A. An Ea name may not contain any of the following characters:
+
+ 0x0000 - 0x001F \ / : * ? " < > | , + = [ ] ;
+
+Arguments:
+
+ Name - Supllies the name to check.
+
+Return Value:
+
+ BOOLEAN - TRUE if the name is legal, FALSE otherwise.
+
+--*/
+
+{
+ ULONG Index;
+
+ UCHAR Char;
+
+ PAGED_CODE();
+
+ //
+ // Empty names are not valid.
+ //
+
+ if ( Name.Length == 0 ) { return FALSE; }
+
+ //
+ // At this point we should only have a single name, which can't have
+ // more than 254 characters
+ //
+
+ if ( Name.Length > 254 ) { return FALSE; }
+
+ for ( Index = 0; Index < (ULONG)Name.Length; Index += 1 ) {
+
+ Char = Name.Buffer[ Index ];
+
+ //
+ // Skip over and Dbcs chacters
+ //
+
+ if ( FsRtlIsLeadDbcsCharacter( Char ) ) {
+
+ ASSERT( Index != (ULONG)(Name.Length - 1) );
+
+ Index += 1;
+
+ continue;
+ }
+
+ //
+ // Make sure this character is legal, and if a wild card, that
+ // wild cards are permissible.
+ //
+
+ if ( !FsRtlIsAnsiCharacterLegalFat(Char, FALSE) ) {
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
diff --git a/private/ntos/cntfs/fieldoff.c b/private/ntos/cntfs/fieldoff.c
new file mode 100644
index 000000000..9c26d7e68
--- /dev/null
+++ b/private/ntos/cntfs/fieldoff.c
@@ -0,0 +1,630 @@
+#include "NtfsProc.h"
+#include <stdio.h>
+
+#define doit(a,b) { printf("%s %04lx %4lx %s\n", #a, FIELD_OFFSET(a,b), sizeof(d.b), #b); }
+
+void _CRTAPI1 main()
+{
+ printf("<Record> <offset> <size> <field>\n\n");
+ {
+ NTFS_DATA d;
+ doit( NTFS_DATA, NodeTypeCode );
+ doit( NTFS_DATA, NodeByteSize );
+ doit( NTFS_DATA, DriverObject );
+ doit( NTFS_DATA, VcbQueue );
+ doit( NTFS_DATA, Resource );
+ doit( NTFS_DATA, AsyncCloseList );
+ doit( NTFS_DATA, AsyncCloseActive );
+ doit( NTFS_DATA, ReduceDelayedClose );
+ doit( NTFS_DATA, AsyncCloseCount );
+ doit( NTFS_DATA, OurProcess );
+ doit( NTFS_DATA, DelayedCloseCount );
+ doit( NTFS_DATA, DelayedCloseList );
+ doit( NTFS_DATA, NtfsCloseItem );
+ doit( NTFS_DATA, StrucSupSpinLock );
+ doit( NTFS_DATA, FreeFcbTableSize );
+ doit( NTFS_DATA, UnusedUchar );
+ doit( NTFS_DATA, FreeFcbTableArray );
+ doit( NTFS_DATA, FreeEresourceSize );
+ doit( NTFS_DATA, FreeEresourceTotal );
+ doit( NTFS_DATA, FreeEresourceMiss );
+ doit( NTFS_DATA, FreeEresourceArray );
+ doit( NTFS_DATA, CacheManagerCallbacks );
+ doit( NTFS_DATA, CacheManagerVolumeCallbacks );
+ doit( NTFS_DATA, VolumeCheckpointDpc );
+ doit( NTFS_DATA, VolumeCheckpointTimer );
+ doit( NTFS_DATA, VolumeCheckpointItem );
+ doit( NTFS_DATA, Flags );
+ doit( NTFS_DATA, ReadAheadThreads );
+ }
+ printf("\n");
+ {
+ RECORD_ALLOCATION_CONTEXT d;
+ doit( RECORD_ALLOCATION_CONTEXT, DataScb );
+ doit( RECORD_ALLOCATION_CONTEXT, BitmapScb );
+ doit( RECORD_ALLOCATION_CONTEXT, CurrentBitmapSize );
+ doit( RECORD_ALLOCATION_CONTEXT, NumberOfFreeBits );
+ doit( RECORD_ALLOCATION_CONTEXT, IndexOfLastSetBit );
+ doit( RECORD_ALLOCATION_CONTEXT, BytesPerRecord );
+ doit( RECORD_ALLOCATION_CONTEXT, ExtendGranularity );
+ doit( RECORD_ALLOCATION_CONTEXT, TruncateGranularity );
+ }
+ printf("\n");
+ {
+ RESTART_POINTERS d;
+ doit( RESTART_POINTERS, Resource );
+ doit( RESTART_POINTERS, Table );
+ doit( RESTART_POINTERS, SpinLock );
+ doit( RESTART_POINTERS, ResourceInitialized );
+ doit( RESTART_POINTERS, Unused );
+ }
+ printf("\n");
+ {
+ NTFS_MCB_ENTRY d;
+ doit( NTFS_MCB_ENTRY, LruLinks );
+ doit( NTFS_MCB_ENTRY, NtfsMcb );
+ doit( NTFS_MCB_ENTRY, NtfsMcbArray );
+ doit( NTFS_MCB_ENTRY, LargeMcb );
+ }
+ printf("\n");
+ {
+ NTFS_MCB_ARRAY d;
+ doit( NTFS_MCB_ARRAY, StartingVcn );
+ doit( NTFS_MCB_ARRAY, EndingVcn );
+ doit( NTFS_MCB_ARRAY, NtfsMcbEntry );
+ doit( NTFS_MCB_ARRAY, Unused );
+ }
+ printf("\n");
+ {
+ NTFS_MCB d;
+ doit( NTFS_MCB, FcbHeader );
+ doit( NTFS_MCB, PoolType );
+ doit( NTFS_MCB, NtfsMcbArraySizeInUse );
+ doit( NTFS_MCB, NtfsMcbArraySize );
+ doit( NTFS_MCB, NtfsMcbArray );
+ doit( NTFS_MCB, FastMutex );
+ }
+ printf("\n");
+ {
+ DEALLOCATED_CLUSTERS d;
+ doit( DEALLOCATED_CLUSTERS, Mcb );
+ doit( DEALLOCATED_CLUSTERS, Lsn );
+ doit( DEALLOCATED_CLUSTERS, ClusterCount );
+ }
+ printf("\n");
+ {
+ VCB d;
+ doit( VCB, NodeTypeCode );
+ doit( VCB, NodeByteSize );
+ doit( VCB, TargetDeviceObject );
+ doit( VCB, VcbLinks );
+ doit( VCB, MftScb );
+ doit( VCB, Mft2Scb );
+ doit( VCB, LogFileScb );
+ doit( VCB, VolumeDasdScb );
+ doit( VCB, RootIndexScb );
+ doit( VCB, BitmapScb );
+ doit( VCB, AttributeDefTableScb );
+ doit( VCB, UpcaseTableScb );
+ doit( VCB, BadClusterFileScb );
+ doit( VCB, QuotaTableScb );
+ doit( VCB, MftBitmapScb );
+ doit( VCB, LogFileObject );
+ doit( VCB, MftReserveFlags );
+ doit( VCB, MftDefragState );
+ doit( VCB, VcbState );
+ doit( VCB, Statistics );
+ doit( VCB, CleanupCount );
+ doit( VCB, CloseCount );
+ doit( VCB, ReadOnlyCloseCount );
+ doit( VCB, SystemFileCloseCount );
+ doit( VCB, TotalClusters );
+ doit( VCB, FreeClusters );
+ doit( VCB, DeallocatedClusters );
+ doit( VCB, TotalReserved );
+ doit( VCB, FreeSpaceMcb );
+ doit( VCB, FreeSpaceMcbMaximumSize );
+ doit( VCB, FreeSpaceMcbTrimToSize );
+ doit( VCB, LastBitmapHint );
+ doit( VCB, RootLcb );
+ doit( VCB, Vpb );
+ doit( VCB, BigEnoughToMove );
+ doit( VCB, DefaultBlocksPerIndexAllocationBuffer );
+ doit( VCB, DefaultBytesPerIndexAllocationBuffer );
+ doit( VCB, BytesPerSector );
+ doit( VCB, BytesPerCluster );
+ doit( VCB, BytesPerFileRecordSegment );
+ doit( VCB, ClustersPerFileRecordSegment );
+ doit( VCB, FileRecordsPerCluster );
+ doit( VCB, MftStartLcn );
+ doit( VCB, Mft2StartLcn );
+ doit( VCB, NumberSectors );
+ doit( VCB, VolumeSerialNumber );
+ doit( VCB, VolumeCreationTime );
+ doit( VCB, VolumeLastModificationTime );
+ doit( VCB, VolumeLastChangeTime );
+ doit( VCB, VolumeLastAccessTime );
+ doit( VCB, ClusterMask );
+ doit( VCB, InverseClusterMask );
+ doit( VCB, ClusterShift );
+ doit( VCB, MftShift );
+ doit( VCB, MftToClusterShift );
+ doit( VCB, ClustersPerPage );
+ doit( VCB, MftReserved );
+ doit( VCB, MftCushion );
+ doit( VCB, FcbTableMutex );
+ doit( VCB, FcbSecurityMutex );
+ doit( VCB, CheckpointMutex );
+ doit( VCB, CheckpointNotifyEvent );
+ doit( VCB, CheckpointFlags );
+ doit( VCB, AttributeFlagsMask );
+ doit( VCB, UnusedUshort );
+ doit( VCB, MftHoleGranularity );
+ doit( VCB, MftFreeRecords );
+ doit( VCB, MftHoleRecords );
+ doit( VCB, LogHandle );
+ doit( VCB, MftHoleMask );
+ doit( VCB, MftHoleInverseMask );
+ doit( VCB, MftClustersPerHole );
+ doit( VCB, MftHoleClusterMask );
+ doit( VCB, MftHoleClusterInverseMask );
+ doit( VCB, LastRestartArea );
+ doit( VCB, OpenAttributeTable );
+ doit( VCB, LastBaseLsn );
+ doit( VCB, TransactionTable );
+ doit( VCB, EndOfLastCheckpoint );
+ doit( VCB, DeviceName );
+ doit( VCB, UpcaseTable );
+ doit( VCB, UpcaseTableSize );
+ doit( VCB, FcbTable );
+ doit( VCB, DirNotifyList );
+ doit( VCB, NotifySync );
+ doit( VCB, FileObjectWithVcbLocked );
+ doit( VCB, MftZoneStart );
+ doit( VCB, MftZoneEnd );
+ doit( VCB, PriorDeallocatedClusters );
+ doit( VCB, ActiveDeallocatedClusters );
+ doit( VCB, DeallocatedClusters1 );
+ doit( VCB, DeallocatedClusters2 );
+ doit( VCB, MftBitmapAllocationContext );
+ doit( VCB, Resource );
+ doit( VCB, AttributeDefinitions );
+ doit( VCB, LogHeaderReservation );
+ doit( VCB, Tunnel );
+ }
+ printf("\n");
+ {
+ VOLUME_DEVICE_OBJECT d;
+ doit( VOLUME_DEVICE_OBJECT, DeviceObject );
+ doit( VOLUME_DEVICE_OBJECT, PostedRequestCount );
+ doit( VOLUME_DEVICE_OBJECT, OverflowQueueCount );
+ doit( VOLUME_DEVICE_OBJECT, OverflowQueue );
+ doit( VOLUME_DEVICE_OBJECT, OverflowQueueSpinLock );
+ doit( VOLUME_DEVICE_OBJECT, Vcb );
+ }
+ printf("\n");
+ {
+ QUICK_INDEX d;
+ doit( QUICK_INDEX, ChangeCount );
+ doit( QUICK_INDEX, BufferOffset );
+ doit( QUICK_INDEX, CapturedLsn );
+ doit( QUICK_INDEX, IndexBlock );
+ }
+ printf("\n");
+ {
+ NAME_LINK d;
+ doit( NAME_LINK, LinkName );
+ doit( NAME_LINK, Links );
+ }
+ printf("\n");
+ {
+ LCB d;
+ doit( LCB, NodeTypeCode );
+ doit( LCB, NodeByteSize );
+ doit( LCB, LcbState );
+
+ doit( LCB, ScbLinks );
+ doit( LCB, Scb );
+ doit( LCB, CleanupCount );
+
+ doit( LCB, FcbLinks );
+ doit( LCB, Fcb );
+ doit( LCB, ReferenceCount );
+
+ doit( LCB, IgnoreCaseLink );
+ doit( LCB, InfoFlags );
+
+ doit( LCB, OverlayParentDirectory );
+ doit( LCB, CcbQueue );
+
+ doit( LCB, ExactCaseLink );
+ doit( LCB, FileNameAttr );
+
+ doit( LCB, QuickIndex );
+
+ doit( LCB, OverlayFileNameLength );
+ doit( LCB, OverlayFlags );
+ doit( LCB, OverlayFileName );
+ }
+ printf("\n");
+ {
+ FCB d;
+ doit( FCB, NodeTypeCode );
+ doit( FCB, NodeByteSize );
+ doit( FCB, Vcb );
+ doit( FCB, FileReference );
+ doit( FCB, CleanupCount );
+ doit( FCB, CloseCount );
+ doit( FCB, ReferenceCount );
+ doit( FCB, FcbState );
+ doit( FCB, FcbDenyDelete );
+ doit( FCB, FcbDeleteFile );
+ doit( FCB, LcbQueue );
+ doit( FCB, ScbQueue );
+ doit( FCB, ExclusiveFcbLinks );
+ doit( FCB, Resource );
+ doit( FCB, BaseExclusiveCount );
+ doit( FCB, EaModificationCount );
+ doit( FCB, PagingIoResource );
+ doit( FCB, InfoFlags );
+ doit( FCB, Info );
+ doit( FCB, LinkCount );
+ doit( FCB, TotalLinks );
+ doit( FCB, CurrentLastAccess );
+ doit( FCB, SharedSecurity );
+ doit( FCB, CreateSecurityCount );
+ doit( FCB, ChildSharedSecurity );
+ doit( FCB, DelayedCloseCount );
+ }
+ printf("\n");
+ {
+ SCB_DATA d;
+ doit( SCB_DATA, TotalReserved );
+ doit( SCB_DATA, Oplock );
+ doit( SCB_DATA, FileLock );
+ doit( SCB_DATA, ReservedBitMap );
+ doit( SCB_DATA, PadUlong );
+ }
+ printf("\n");
+ {
+ SCB_INDEX d;
+ doit( SCB_INDEX, RecentlyDeallocatedQueue );
+ doit( SCB_INDEX, LcbQueue );
+ doit( SCB_INDEX, RecordAllocationContext );
+ doit( SCB_INDEX, ExactCaseNode );
+ doit( SCB_INDEX, IgnoreCaseNode );
+ doit( SCB_INDEX, NormalizedName );
+ doit( SCB_INDEX, ChangeCount );
+ doit( SCB_INDEX, AttributeBeingIndexed );
+ doit( SCB_INDEX, CollationRule );
+ doit( SCB_INDEX, BytesPerIndexBuffer );
+ doit( SCB_INDEX, BlocksPerIndexBuffer );
+ doit( SCB_INDEX, IndexBlockByteShift );
+ doit( SCB_INDEX, AllocationInitialized );
+ doit( SCB_INDEX, PadUchar );
+ doit( SCB_INDEX, IndexDepthHint );
+ doit( SCB_INDEX, PadUshort );
+ }
+ printf("\n");
+ {
+ SCB_MFT d;
+ doit( SCB_MFT, RecentlyDeallocatedQueue );
+ doit( SCB_MFT, AddedClusters );
+ doit( SCB_MFT, RemovedClusters );
+ doit( SCB_MFT, FreeRecordChange );
+ doit( SCB_MFT, HoleRecordChange );
+ doit( SCB_MFT, ReservedIndex );
+ doit( SCB_MFT, PadUlong );
+ }
+ printf("\n");
+ {
+ SCB_NONPAGED d;
+ doit( SCB_NONPAGED, NodeTypeCode );
+ doit( SCB_NONPAGED, NodeByteSize );
+ doit( SCB_NONPAGED, OpenAttributeTableIndex );
+ doit( SCB_NONPAGED, SegmentObject );
+ doit( SCB_NONPAGED, Vcb );
+ }
+ printf("\n");
+ {
+ SCB d;
+ doit( SCB, Header );
+
+ doit( SCB, FcbLinks );
+ doit( SCB, Fcb );
+ doit( SCB, Vcb );
+ doit( SCB, ScbState );
+ doit( SCB, NonCachedCleanupCount );
+ doit( SCB, CleanupCount );
+ doit( SCB, CloseCount );
+ doit( SCB, ShareAccess );
+ doit( SCB, AttributeTypeCode );
+ doit( SCB, AttributeName );
+ doit( SCB, FileObject );
+ doit( SCB, LazyWriteThread );
+ doit( SCB, NonpagedScb );
+ doit( SCB, Mcb );
+ doit( SCB, McbStructs );
+ doit( SCB, CompressionUnit );
+ doit( SCB, AttributeFlags );
+ doit( SCB, CompressionUnitShift );
+ doit( SCB, PadUchar );
+ doit( SCB, ValidDataToDisk );
+ doit( SCB, ExcessFromSplitMcb );
+ doit( SCB, TotalAllocated );
+ doit( SCB, EofListHead );
+ doit( SCB, Union );
+ doit( SCB, ScbSnapshot );
+ doit( SCB, PadUlong );
+ doit( SCB, ScbType.Data );
+ doit( SCB, ScbType.Index );
+ doit( SCB, ScbType.Mft );
+ }
+ printf("\n");
+ {
+ SCB_SNAPSHOT d;
+ doit( SCB_SNAPSHOT, SnapshotLinks );
+ doit( SCB_SNAPSHOT, AllocationSize );
+ doit( SCB_SNAPSHOT, FileSize );
+ doit( SCB_SNAPSHOT, ValidDataLength );
+ doit( SCB_SNAPSHOT, ValidDataToDisk );
+ doit( SCB_SNAPSHOT, TotalAllocated );
+ doit( SCB_SNAPSHOT, LowestModifiedVcn );
+ doit( SCB_SNAPSHOT, HighestModifiedVcn );
+ doit( SCB_SNAPSHOT, Scb );
+ doit( SCB_SNAPSHOT, Unused );
+ }
+ printf("\n");
+ {
+ CCB d;
+ doit( CCB, NodeTypeCode );
+ doit( CCB, NodeByteSize );
+ doit( CCB, Flags );
+
+ doit( CCB, FullFileName );
+ doit( CCB, LastFileNameOffset );
+ doit( CCB, EaModificationCount );
+ doit( CCB, NextEaOffset );
+
+ doit( CCB, LcbLinks );
+ doit( CCB, Lcb );
+
+ doit( CCB, TypeOfOpen );
+ doit( CCB, PadBytes );
+
+ doit( CCB, IndexContext );
+
+ doit( CCB, QueryLength );
+ doit( CCB, QueryBuffer );
+ doit( CCB, IndexEntryLength );
+ doit( CCB, IndexEntry );
+
+ doit( CCB, FcbToAcquire.LongValue );
+ doit( CCB, FcbToAcquire.FileReference );
+ }
+ printf("\n");
+ {
+ CCB_DATA d;
+ doit( CCB_DATA, Opaque );
+ }
+ printf("\n");
+ {
+ FCB_DATA d;
+ doit( FCB_DATA, Fcb );
+ doit( FCB_DATA, Scb );
+ doit( FCB_DATA, Ccb );
+ doit( FCB_DATA, Lcb );
+ doit( FCB_DATA, FileName );
+ }
+ printf("\n");
+ {
+ FCB_INDEX d;
+ doit( FCB_INDEX, Fcb );
+ doit( FCB_INDEX, Scb );
+ doit( FCB_INDEX, Ccb );
+ doit( FCB_INDEX, Lcb );
+ doit( FCB_INDEX, FileName );
+ }
+ printf("\n");
+ {
+ IRP_CONTEXT d;
+ doit( IRP_CONTEXT, NodeTypeCode );
+ doit( IRP_CONTEXT, NodeByteSize );
+ doit( IRP_CONTEXT, Flags );
+ doit( IRP_CONTEXT, ExceptionStatus );
+ doit( IRP_CONTEXT, TransactionId );
+ doit( IRP_CONTEXT, Vcb );
+ doit( IRP_CONTEXT, RealDevice );
+ doit( IRP_CONTEXT, TopLevelIrpContext );
+ doit( IRP_CONTEXT, Union.NtfsIoContext );
+ doit( IRP_CONTEXT, Union.AuxiliaryBuffer );
+ doit( IRP_CONTEXT, Union.SubjectContext );
+ doit( IRP_CONTEXT, Union.OplockCleanup );
+ doit( IRP_CONTEXT, OriginatingIrp );
+ doit( IRP_CONTEXT, MajorFunction );
+ doit( IRP_CONTEXT, MinorFunction );
+ doit( IRP_CONTEXT, UnusedUshort );
+ doit( IRP_CONTEXT, ExclusiveFcbList );
+ doit( IRP_CONTEXT, RecentlyDeallocatedQueue );
+ doit( IRP_CONTEXT, DeallocatedClusters );
+ doit( IRP_CONTEXT, ScbSnapshot );
+ doit( IRP_CONTEXT, LastRestartArea );
+ doit( IRP_CONTEXT, WorkQueueItem );
+ doit( IRP_CONTEXT, FreeClusterChange );
+ doit( IRP_CONTEXT, FcbWithPagingExclusive );
+ }
+ printf("\n");
+ {
+ TOP_LEVEL_CONTEXT d;
+ doit( TOP_LEVEL_CONTEXT, TopLevelRequest );
+ doit( TOP_LEVEL_CONTEXT, ValidSavedTopLevel );
+ doit( TOP_LEVEL_CONTEXT, OverflowReadThread );
+ doit( TOP_LEVEL_CONTEXT, Ntfs );
+ doit( TOP_LEVEL_CONTEXT, VboBeingHotFixed );
+ doit( TOP_LEVEL_CONTEXT, ScbBeingHotFixed );
+ doit( TOP_LEVEL_CONTEXT, SavedTopLevelIrp );
+ doit( TOP_LEVEL_CONTEXT, TopLevelIrpContext );
+ }
+ printf("\n");
+ {
+ FOUND_ATTRIBUTE d;
+ doit( FOUND_ATTRIBUTE, MftFileOffset );
+ doit( FOUND_ATTRIBUTE, Attribute );
+ doit( FOUND_ATTRIBUTE, FileRecord );
+ doit( FOUND_ATTRIBUTE, Bcb );
+ doit( FOUND_ATTRIBUTE, AttributeDeleted );
+ doit( FOUND_ATTRIBUTE, AttributeAllocationDeleted );
+ }
+ printf("\n");
+ {
+ ATTRIBUTE_LIST_CONTEXT d;
+ doit( ATTRIBUTE_LIST_CONTEXT, Entry );
+ doit( ATTRIBUTE_LIST_CONTEXT, Bcb );
+ doit( ATTRIBUTE_LIST_CONTEXT, AttributeList );
+ doit( ATTRIBUTE_LIST_CONTEXT, FirstEntry );
+ doit( ATTRIBUTE_LIST_CONTEXT, BeyondFinalEntry );
+ doit( ATTRIBUTE_LIST_CONTEXT, NonresidentListBcb );
+ }
+ printf("\n");
+ {
+ ATTRIBUTE_ENUMERATION_CONTEXT d;
+ doit( ATTRIBUTE_ENUMERATION_CONTEXT, FoundAttribute );
+ doit( ATTRIBUTE_ENUMERATION_CONTEXT, AttributeList );
+ }
+ printf("\n");
+ {
+ INDEX_LOOKUP_STACK d;
+ doit( INDEX_LOOKUP_STACK, Bcb );
+ doit( INDEX_LOOKUP_STACK, StartOfBuffer );
+ doit( INDEX_LOOKUP_STACK, IndexHeader );
+ doit( INDEX_LOOKUP_STACK, IndexEntry );
+ doit( INDEX_LOOKUP_STACK, IndexBlock );
+ doit( INDEX_LOOKUP_STACK, CapturedLsn );
+ }
+ printf("\n");
+ {
+ INDEX_CONTEXT d;
+ doit( INDEX_CONTEXT, AttributeContext );
+ doit( INDEX_CONTEXT, Base );
+ doit( INDEX_CONTEXT, Top );
+ doit( INDEX_CONTEXT, LookupStack );
+ doit( INDEX_CONTEXT, Current );
+ doit( INDEX_CONTEXT, ScbChangeCount );
+ doit( INDEX_CONTEXT, OldAttribute );
+ doit( INDEX_CONTEXT, NumberEntries );
+ doit( INDEX_CONTEXT, Flags );
+ doit( INDEX_CONTEXT, AcquiredFcb );
+ doit( INDEX_CONTEXT, Unused );
+ }
+ printf("\n");
+ {
+ NTFS_IO_CONTEXT d;
+ doit( NTFS_IO_CONTEXT, IrpCount );
+ doit( NTFS_IO_CONTEXT, MasterIrp );
+ doit( NTFS_IO_CONTEXT, IrpSpFlags );
+ doit( NTFS_IO_CONTEXT, AllocatedContext );
+ doit( NTFS_IO_CONTEXT, PagingIo );
+ doit( NTFS_IO_CONTEXT, Wait.Async.Resource );
+ doit( NTFS_IO_CONTEXT, Wait.Async.ResourceThreadId );
+ doit( NTFS_IO_CONTEXT, Wait.Async.RequestedByteCount );
+ doit( NTFS_IO_CONTEXT, Wait.SyncEvent );
+ }
+ printf("\n");
+ {
+ IO_RUN d;
+ doit( IO_RUN, StartingVbo );
+ doit( IO_RUN, StartingLbo );
+ doit( IO_RUN, BufferOffset );
+ doit( IO_RUN, ByteCount );
+ doit( IO_RUN, SavedIrp );
+ doit( IO_RUN, Unused );
+ }
+ printf("\n");
+ {
+ NTFS_NAME_DESCRIPTOR d;
+ doit( NTFS_NAME_DESCRIPTOR, FieldsPresent );
+ doit( NTFS_NAME_DESCRIPTOR, FileName );
+ doit( NTFS_NAME_DESCRIPTOR, AttributeType );
+ doit( NTFS_NAME_DESCRIPTOR, AttributeName );
+ doit( NTFS_NAME_DESCRIPTOR, VersionNumber );
+ }
+ printf("\n");
+ {
+ EA_LIST_HEADER d;
+ doit( EA_LIST_HEADER, PackedEaSize );
+ doit( EA_LIST_HEADER, NeedEaCount );
+ doit( EA_LIST_HEADER, UnpackedEaSize );
+ doit( EA_LIST_HEADER, BufferSize );
+ doit( EA_LIST_HEADER, FullEa );
+ }
+ printf("\n");
+ {
+ DEALLOCATED_RECORDS d;
+ doit( DEALLOCATED_RECORDS, ScbLinks );
+ doit( DEALLOCATED_RECORDS, IrpContextLinks );
+ doit( DEALLOCATED_RECORDS, Scb );
+ doit( DEALLOCATED_RECORDS, NumberOfEntries );
+ doit( DEALLOCATED_RECORDS, NextFreeEntry );
+ doit( DEALLOCATED_RECORDS, Index );
+ }
+ printf("\n");
+ {
+ FCB_TABLE_ELEMENT d;
+ doit( FCB_TABLE_ELEMENT, FileReference );
+ doit( FCB_TABLE_ELEMENT, Fcb );
+ }
+ printf("\n");
+ {
+ SHARED_SECURITY d;
+ doit( SHARED_SECURITY, ParentFcb );
+ doit( SHARED_SECURITY, ReferenceCount );
+ doit( SHARED_SECURITY, SecurityDescriptor );
+ }
+ printf("\n");
+ {
+ OLD_SCB_SNAPSHOT d;
+ doit( OLD_SCB_SNAPSHOT, AllocationSize );
+ doit( OLD_SCB_SNAPSHOT, FileSize );
+ doit( OLD_SCB_SNAPSHOT, ValidDataLength );
+ doit( OLD_SCB_SNAPSHOT, TotalAllocated );
+ doit( OLD_SCB_SNAPSHOT, CompressionUnit );
+ doit( OLD_SCB_SNAPSHOT, Resident );
+ doit( OLD_SCB_SNAPSHOT, AttributeFlags );
+ }
+ printf("\n");
+ {
+ READ_AHEAD_THREAD d;
+ doit( READ_AHEAD_THREAD, Links );
+ doit( READ_AHEAD_THREAD, Thread );
+ }
+ printf("\n");
+ {
+ DEFRAG_MFT d;
+ doit( DEFRAG_MFT, WorkQueueItem );
+ doit( DEFRAG_MFT, Vcb );
+ doit( DEFRAG_MFT, DeallocateWorkItem );
+ }
+ printf("\n");
+ {
+ NUKEM d;
+ doit( NUKEM, Next );
+ doit( NUKEM, RecordNumbers );
+ }
+ printf("\n");
+ {
+ NAME_PAIR d;
+ doit( NAME_PAIR, Short );
+ doit( NAME_PAIR, Long );
+ doit( NAME_PAIR, ShortBuffer );
+ doit( NAME_PAIR, LongBuffer );
+ }
+ printf("\n");
+ {
+ OPLOCK_CLEANUP d;
+ doit( OPLOCK_CLEANUP, OriginalFileName );
+ doit( OPLOCK_CLEANUP, FullFileName );
+ doit( OPLOCK_CLEANUP, ExactCaseName );
+ doit( OPLOCK_CLEANUP, FileObject );
+ }
+}
+
diff --git a/private/ntos/cntfs/fileinfo.c b/private/ntos/cntfs/fileinfo.c
new file mode 100644
index 000000000..2f508a4fc
--- /dev/null
+++ b/private/ntos/cntfs/fileinfo.c
@@ -0,0 +1,8307 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ FileInfo.c
+
+Abstract:
+
+ This module implements the set and query file information routines for Ntfs
+ called by the dispatch driver.
+
+Author:
+
+ Brian Andrew [BrianAn] 15-Jan-1992
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_FILEINFO)
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_FILEINFO)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('FFtN')
+
+#define SIZEOF_FILE_NAME_INFORMATION (FIELD_OFFSET( FILE_NAME_INFORMATION, FileName[0]) \
+ + sizeof( WCHAR ))
+
+//
+// Local flags for rename and set link
+//
+
+#define TRAVERSE_MATCH (0x00000001)
+#define EXACT_CASE_MATCH (0x00000002)
+#define ACTIVELY_REMOVE_SOURCE_LINK (0x00000004)
+#define REMOVE_SOURCE_LINK (0x00000008)
+#define REMOVE_TARGET_LINK (0x00000010)
+#define ADD_TARGET_LINK (0x00000020)
+#define REMOVE_TRAVERSE_LINK (0x00000040)
+#define REUSE_TRAVERSE_LINK (0x00000080)
+#define MOVE_TO_NEW_DIR (0x00000100)
+#define ADD_PRIMARY_LINK (0x00000200)
+#define OVERWRITE_SOURCE_LINK (0x00000400)
+
+//
+// Additional local flags for set link
+//
+
+#define CREATE_IN_NEW_DIR (0x00000400)
+
+//
+// Local procedure prototypes
+//
+
+//
+// VOID
+// NtfsBuildLastFileName (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFILE_OBJECT FileObject,
+// IN ULONG FileNameOffset,
+// OUT PUNICODE_STRING FileName
+// );
+//
+
+#define NtfsBuildLastFileName(IC,FO,OFF,FN) { \
+ (FN)->MaximumLength = (FN)->Length = (FO)->FileName.Length - OFF; \
+ (FN)->Buffer = (PWSTR) Add2Ptr( (FO)->FileName.Buffer, OFF ); \
+}
+
+VOID
+NtfsQueryBasicInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN OUT PFILE_BASIC_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+VOID
+NtfsQueryStandardInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_STANDARD_INFORMATION Buffer,
+ IN OUT PULONG Length,
+ IN PCCB Ccb OPTIONAL
+ );
+
+VOID
+NtfsQueryInternalInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_INTERNAL_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+VOID
+NtfsQueryEaInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_EA_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+VOID
+NtfsQueryPositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_POSITION_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryNameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_NAME_INFORMATION Buffer,
+ IN OUT PULONG Length,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsQueryAlternateNameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLCB Lcb,
+ IN OUT PFILE_NAME_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryStreamsInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PFILE_STREAM_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryCompressedFileSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PFILE_COMPRESSION_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+VOID
+NtfsQueryNetworkOpenInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsSetBasicInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsSetDispositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsSetRenameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsSetLinkInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsSetPositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb
+ );
+
+NTSTATUS
+NtfsSetAllocationInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ );
+
+NTSTATUS
+NtfsSetEndOfFileInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb OPTIONAL,
+ IN BOOLEAN VcbAcquired
+ );
+
+NTSTATUS
+NtfsCheckScbForLinkRemoval (
+ IN PSCB Scb,
+ OUT PSCB *BatchOplockScb,
+ OUT PULONG BatchOplockCount
+ );
+
+VOID
+NtfsFindTargetElements (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT TargetFileObject,
+ IN PSCB ParentScb,
+ OUT PSCB *TargetParentScb,
+ OUT PUNICODE_STRING FullTargetFileName,
+ OUT PUNICODE_STRING TargetFileName
+ );
+
+BOOLEAN
+NtfsCheckLinkForNewLink (
+ IN PFCB Fcb,
+ IN PFILE_NAME FileNameAttr,
+ IN FILE_REFERENCE FileReference,
+ IN PUNICODE_STRING NewLinkName,
+ OUT PULONG LinkFlags
+ );
+
+VOID
+NtfsCheckLinkForRename (
+ IN PFCB Fcb,
+ IN PLCB Lcb,
+ IN PFILE_NAME FileNameAttr,
+ IN FILE_REFERENCE FileReference,
+ IN PUNICODE_STRING TargetFileName,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PULONG RenameFlags
+ );
+
+VOID
+NtfsCleanupLinkForRemoval (
+ IN PFCB PreviousFcb,
+ IN BOOLEAN ExistingFcb
+ );
+
+VOID
+NtfsUpdateFcbFromLinkRemoval (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN UNICODE_STRING FileName,
+ IN UCHAR FileNameFlags
+ );
+
+VOID
+NtfsReplaceLinkInDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR FileNameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ );
+
+VOID
+NtfsMoveLinkToNewDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PUNICODE_STRING NewFullLinkName,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR NewLinkNameFlags,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN OUT PLCB Lcb,
+ IN ULONG RenameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ );
+
+VOID
+NtfsRenameLinkInDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN OUT PLCB Lcb,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR FileNameFlags,
+ IN ULONG RenameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ );
+
+VOID
+NtfsUpdateFileDupInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PCCB Ccb OPTIONAL
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCheckLinkForNewLink)
+#pragma alloc_text(PAGE, NtfsCheckLinkForRename)
+#pragma alloc_text(PAGE, NtfsCheckScbForLinkRemoval)
+#pragma alloc_text(PAGE, NtfsCleanupLinkForRemoval)
+#pragma alloc_text(PAGE, NtfsCommonQueryInformation)
+#pragma alloc_text(PAGE, NtfsCommonSetInformation)
+#pragma alloc_text(PAGE, NtfsFindTargetElements)
+#pragma alloc_text(PAGE, NtfsFsdQueryInformation)
+#pragma alloc_text(PAGE, NtfsFsdSetInformation)
+#pragma alloc_text(PAGE, NtfsMoveLinkToNewDir)
+#pragma alloc_text(PAGE, NtfsQueryAlternateNameInfo)
+#pragma alloc_text(PAGE, NtfsQueryBasicInfo)
+#pragma alloc_text(PAGE, NtfsQueryEaInfo)
+#pragma alloc_text(PAGE, NtfsQueryInternalInfo)
+#pragma alloc_text(PAGE, NtfsQueryNameInfo)
+#pragma alloc_text(PAGE, NtfsQueryPositionInfo)
+#pragma alloc_text(PAGE, NtfsQueryStandardInfo)
+#pragma alloc_text(PAGE, NtfsQueryStreamsInfo)
+#pragma alloc_text(PAGE, NtfsQueryCompressedFileSize)
+#pragma alloc_text(PAGE, NtfsQueryNetworkOpenInfo)
+#pragma alloc_text(PAGE, NtfsRenameLinkInDir)
+#pragma alloc_text(PAGE, NtfsReplaceLinkInDir)
+#pragma alloc_text(PAGE, NtfsSetAllocationInfo)
+#pragma alloc_text(PAGE, NtfsSetBasicInfo)
+#pragma alloc_text(PAGE, NtfsSetDispositionInfo)
+#pragma alloc_text(PAGE, NtfsSetEndOfFileInfo)
+#pragma alloc_text(PAGE, NtfsSetLinkInfo)
+#pragma alloc_text(PAGE, NtfsSetPositionInfo)
+#pragma alloc_text(PAGE, NtfsSetRenameInfo)
+#pragma alloc_text(PAGE, NtfsUpdateFcbFromLinkRemoval)
+#pragma alloc_text(PAGE, NtfsUpdateFileDupInfo)
+#endif
+
+
+NTSTATUS
+NtfsFsdQueryInformation (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of query file information.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdQueryInformation\n") );
+
+ //
+ // Call the common query Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonQueryInformation( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdQueryInformation -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFsdSetInformation (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of set file information.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+ ULONG LogFileFullCount = 0;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdSetInformation\n") );
+
+ //
+ // Call the common set Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+
+ if (++LogFileFullCount >= 2) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
+ }
+ }
+
+ Status = NtfsCommonSetInformation( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+ PIO_STACK_LOCATION IrpSp;
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ ExceptionCode = GetExceptionCode();
+
+ if ((ExceptionCode == STATUS_FILE_DELETED) &&
+ (IrpSp->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation)) {
+
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_SUCCESS;
+ }
+
+ Status = NtfsProcessException( IrpContext, Irp, ExceptionCode );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdSetInformation -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonQueryInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for query file information called by both the
+ fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ULONG Length;
+ FILE_INFORMATION_CLASS FileInformationClass;
+ PVOID Buffer;
+
+ BOOLEAN OpenById = FALSE;
+ BOOLEAN FcbAcquired = FALSE;
+ BOOLEAN VcbAcquired = FALSE;
+ BOOLEAN FsRtlHeaderLocked = FALSE;
+ PFILE_ALL_INFORMATION AllInfo;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonQueryInformation\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.QueryFile.Length) );
+ DebugTrace( 0, Dbg, ("FileInformationClass = %08lx\n", IrpSp->Parameters.QueryFile.FileInformationClass) );
+ DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ Length = IrpSp->Parameters.QueryFile.Length;
+ FileInformationClass = IrpSp->Parameters.QueryFile.FileInformationClass;
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ try {
+
+ //
+ // Case on the type of open we're dealing with
+ //
+
+ switch (TypeOfOpen) {
+
+ case UserVolumeOpen:
+
+ //
+ // We cannot query the user volume open.
+ //
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+
+ case UserFileOpen:
+#ifdef _CAIRO_
+ case UserPropertySetOpen:
+#endif // _CAIRO_
+ case UserDirectoryOpen:
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+ OpenById = TRUE;
+ }
+
+ case StreamFileOpen:
+
+ //
+ // Acquire the Vcb if there is no Ccb. This is for the
+ // case where the cache manager is querying the name.
+ //
+
+ if (Ccb == NULL) {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE );
+ VcbAcquired = TRUE;
+ }
+
+ if ((Scb->Header.PagingIoResource != NULL) &&
+
+ ((FileInformationClass == FileAllInformation) ||
+ (FileInformationClass == FileStandardInformation) ||
+ (FileInformationClass == FileCompressionInformation))) {
+
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE );
+
+ FsRtlLockFsRtlHeader( &Scb->Header );
+ FsRtlHeaderLocked = TRUE;
+ }
+
+ NtfsAcquireSharedFcb( IrpContext, Fcb, Scb, FALSE );
+ FcbAcquired = TRUE;
+
+ //
+ // Make sure the volume is still mounted. We need to test this
+ // with the Fcb acquired.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+
+ //
+ // Based on the information class we'll do different
+ // actions. Each of hte procedures that we're calling fills
+ // up the output buffer, if possible. They will raise the
+ // status STATUS_BUFFER_OVERFLOW for an insufficient buffer.
+ // This is considered a somewhat unusual case and is handled
+ // more cleanly with the exception mechanism rather than
+ // testing a return status value for each call.
+ //
+
+ switch (FileInformationClass) {
+
+ case FileAllInformation:
+
+ //
+ // This is illegal for the open by Id case.
+ //
+
+ if (OpenById) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ //
+ // For the all information class we'll typecast a local
+ // pointer to the output buffer and then call the
+ // individual routines to fill in the buffer.
+ //
+
+ AllInfo = Buffer;
+ Length -= (sizeof(FILE_ACCESS_INFORMATION)
+ + sizeof(FILE_MODE_INFORMATION)
+ + sizeof(FILE_ALIGNMENT_INFORMATION));
+
+ NtfsQueryBasicInfo( IrpContext, FileObject, Scb, Ccb, &AllInfo->BasicInformation, &Length );
+ NtfsQueryStandardInfo( IrpContext, FileObject, Scb, &AllInfo->StandardInformation, &Length, Ccb );
+ NtfsQueryInternalInfo( IrpContext, FileObject, Scb, &AllInfo->InternalInformation, &Length );
+ NtfsQueryEaInfo( IrpContext, FileObject, Scb, &AllInfo->EaInformation, &Length );
+ NtfsQueryPositionInfo( IrpContext, FileObject, Scb, &AllInfo->PositionInformation, &Length );
+ Status =
+ NtfsQueryNameInfo( IrpContext, FileObject, Scb, &AllInfo->NameInformation, &Length, Ccb );
+ break;
+
+ case FileBasicInformation:
+
+ NtfsQueryBasicInfo( IrpContext, FileObject, Scb, Ccb, Buffer, &Length );
+ break;
+
+ case FileStandardInformation:
+
+ NtfsQueryStandardInfo( IrpContext, FileObject, Scb, Buffer, &Length, Ccb );
+ break;
+
+ case FileInternalInformation:
+
+ NtfsQueryInternalInfo( IrpContext, FileObject, Scb, Buffer, &Length );
+ break;
+
+ case FileEaInformation:
+
+ NtfsQueryEaInfo( IrpContext, FileObject, Scb, Buffer, &Length );
+ break;
+
+ case FilePositionInformation:
+
+ NtfsQueryPositionInfo( IrpContext, FileObject, Scb, Buffer, &Length );
+ break;
+
+ case FileNameInformation:
+
+ //
+ // This is illegal for the open by Id case.
+ //
+
+ if (OpenById) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ Status = NtfsQueryNameInfo( IrpContext, FileObject, Scb, Buffer, &Length, Ccb );
+ }
+
+ break;
+
+ case FileAlternateNameInformation:
+
+ //
+ // This is illegal for the open by Id case.
+ //
+
+ if (OpenById) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ Status = NtfsQueryAlternateNameInfo( IrpContext, Scb, Ccb->Lcb, Buffer, &Length );
+ }
+
+ break;
+
+ case FileStreamInformation:
+
+ Status = NtfsQueryStreamsInfo( IrpContext, Fcb, Buffer, &Length );
+ break;
+
+ case FileCompressionInformation:
+
+ Status = NtfsQueryCompressedFileSize( IrpContext, Scb, Buffer, &Length );
+ break;
+
+ case FileNetworkOpenInformation:
+
+ NtfsQueryNetworkOpenInfo( IrpContext, FileObject, Scb, Ccb, Buffer, &Length );
+ break;
+
+ default:
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ break;
+
+ default:
+
+ Status = STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Set the information field to the number of bytes actually filled in
+ // and then complete the request
+ //
+
+ Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - Length;
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonQueryInformation );
+
+ if (FsRtlHeaderLocked) {
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+
+ if (FcbAcquired) { NtfsReleaseFcb( IrpContext, Fcb ); }
+ if (VcbAcquired) { NtfsReleaseVcb( IrpContext, Vcb ); }
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQueryInformation -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonSetInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for set file information called by both the
+ fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ FILE_INFORMATION_CLASS FileInformationClass;
+ BOOLEAN VcbAcquired = FALSE;
+ BOOLEAN ReleaseScbPaging = FALSE;
+ BOOLEAN LazyWriterCallback = FALSE;
+ ULONG WaitState;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonSetInformation\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.SetFile.Length) );
+ DebugTrace( 0, Dbg, ("FileInformationClass = %08lx\n", IrpSp->Parameters.SetFile.FileInformationClass) );
+ DebugTrace( 0, Dbg, ("FileObject = %08lx\n", IrpSp->Parameters.SetFile.FileObject) );
+ DebugTrace( 0, Dbg, ("ReplaceIfExists = %08lx\n", IrpSp->Parameters.SetFile.ReplaceIfExists) );
+ DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // We can reject volume opens immediately.
+ //
+
+ if (TypeOfOpen == UserVolumeOpen ||
+ TypeOfOpen == UnopenedFileObject) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ try {
+
+ //
+ // The typical path here is for the lazy writer callback. Go ahead and
+ // remember this first.
+ //
+
+ if (FileInformationClass == FileEndOfFileInformation) {
+
+ LazyWriterCallback = IrpSp->Parameters.SetFile.AdvanceOnly;
+ }
+
+ //
+ // Perform the oplock check for changes to allocation or EOF if called
+ // by the user.
+ //
+
+ if (!LazyWriterCallback &&
+ ((FileInformationClass == FileEndOfFileInformation) ||
+ (FileInformationClass == FileAllocationInformation)) &&
+ (TypeOfOpen == UserFileOpen) &&
+ !FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ // This call might block this request.
+ //
+
+ Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NULL,
+ NULL );
+
+ if (Status != STATUS_SUCCESS) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Update the FastIoField.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ //
+ // If this call is for EOF then we need to acquire the Vcb if we may
+ // have to perform an update duplicate call. Don't block waiting for
+ // the Vcb in the Valid data callback case.
+ // We don't want to block the lazy write threads in the clean checkpoint
+ // case.
+ //
+
+ if (FileInformationClass == FileEndOfFileInformation) {
+
+ //
+ // If this is not a system file then we will need to update duplicate info.
+ //
+
+ if (!FlagOn( Fcb->FcbState, FCB_STATE_SYSTEM_FILE )) {
+
+ WaitState = FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // Only acquire the Vcb for the Lazy writer if we know the file size in the Fcb
+ // is out of date or can compare the Scb with that in the Fcb. An unsafe comparison
+ // is OK because if they are changing then someone else can do the work.
+ // We also want to update the duplicate information if the total allocated
+ // has changed and there are no user handles remaining to perform the update.
+ //
+
+ if (LazyWriterCallback) {
+
+ if ((FlagOn( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE ) ||
+ ((Scb->Header.FileSize.QuadPart != Fcb->Info.FileSize) &&
+ FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ))) ||
+ (FlagOn( Scb->ScbState, SCB_STATE_COMPRESSED ) &&
+ (Scb->CleanupCount == 0) &&
+ (Scb->ValidDataToDisk >= Scb->Header.ValidDataLength.QuadPart) &&
+ (FlagOn( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE ) ||
+ (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ) &&
+ (Scb->TotalAllocated != Fcb->Info.AllocatedLength))))) {
+
+ //
+ // Go ahead and try to acquire the Vcb without waiting.
+ //
+
+ if (NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE )) {
+
+ VcbAcquired = TRUE;
+
+ } else {
+
+ SetFlag( IrpContext->Flags, WaitState );
+
+ //
+ // If we could not get the Vcb for any reason then return. Let's
+ // not block an essential thread waiting for the Vcb. Typically
+ // we will only be blocked during a clean checkpoint. The Lazy
+ // Writer will periodically come back and retry this call.
+ //
+
+ try_return( Status = STATUS_FILE_LOCK_CONFLICT );
+ }
+ }
+
+ //
+ // Otherwise we always want to wait for the Vcb except if we were called from
+ // MM extending a section. We will try to get this without waiting and test
+ // if called from MM if unsuccessful.
+ //
+
+ } else {
+
+ if (NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE )) {
+
+ VcbAcquired = TRUE;
+
+ } else if ((Scb->Header.PagingIoResource == NULL) ||
+ !NtfsIsExclusiveResource( Scb->Header.PagingIoResource )) {
+
+ SetFlag( IrpContext->Flags, WaitState );
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+ }
+ }
+
+ SetFlag( IrpContext->Flags, WaitState );
+ }
+
+ //
+ // Acquire the Vcb shared for changes to allocation or basic
+ // information.
+ //
+
+ } else if ((FileInformationClass == FileAllocationInformation) ||
+ (FileInformationClass == FileBasicInformation) ||
+ (FileInformationClass == FileDispositionInformation)) {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ //
+ // If this is a rename or link operation then we need to make sure
+ // we have the user's context and acquire the Vcb.
+ //
+
+ } else if ((FileInformationClass == FileRenameInformation) ||
+ (FileInformationClass == FileLinkInformation)) {
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_SECURITY )) {
+
+ IrpContext->Union.SubjectContext = NtfsAllocatePool( PagedPool,
+ sizeof( SECURITY_SUBJECT_CONTEXT ));
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_SECURITY );
+
+ SeCaptureSubjectContext( IrpContext->Union.SubjectContext );
+ }
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ }
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ } else {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ }
+
+ VcbAcquired = TRUE;
+ }
+
+ //
+ // The Lazy Writer must still synchronize with Eof to keep the
+ // stream sizes from changing. This will be cleaned up when we
+ // complete.
+ //
+
+ if (LazyWriterCallback) {
+
+ //
+ // Acquire either the paging io resource shared to serialize with
+ // the flush case where the main resource is acquired before IoAtEOF
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE );
+ ReleaseScbPaging = TRUE;
+ }
+
+ FsRtlLockFsRtlHeader( &Scb->Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+
+ //
+ // Anyone potentially shrinking/deleting allocation must get the paging I/O
+ // resource first. Also acquire this in the rename path to lock the
+ // mapped page writer out of this file.
+ //
+
+ } else if ((Scb->Header.PagingIoResource != NULL) &&
+ ((FileInformationClass == FileEndOfFileInformation) ||
+ (FileInformationClass == FileAllocationInformation) ||
+ (FileInformationClass == FileRenameInformation) ||
+ (FileInformationClass == FileLinkInformation))) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ }
+
+ //
+ // Acquire exclusive access to the Fcb, We use exclusive
+ // because it is probable that one of the subroutines
+ // that we call will need to monkey with file allocation,
+ // create/delete extra fcbs. So we're willing to pay the
+ // cost of exclusive Fcb access.
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, Scb, FALSE, FALSE );
+
+ //
+ // The lazy writer callback is the only caller who can get this far if the
+ // volume has been dismounted. We know that there are no user handles or
+ // writeable file objects or dirty pages. Make one last check to see
+ // if the volume is dismounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_INVALID, NULL, NULL );
+ }
+
+ //
+ // Based on the information class we'll do different
+ // actions. We will perform checks, when appropriate
+ // to insure that the requested operation is allowed.
+ //
+
+ switch (FileInformationClass) {
+
+ case FileBasicInformation:
+
+ Status = NtfsSetBasicInfo( IrpContext, FileObject, Irp, Scb, Ccb );
+ break;
+
+ case FileDispositionInformation:
+
+ Status = NtfsSetDispositionInfo( IrpContext, FileObject, Irp, Scb, Ccb );
+ break;
+
+ case FileRenameInformation:
+
+ Status = NtfsSetRenameInfo( IrpContext, FileObject, Irp, Vcb, Scb, Ccb );
+ break;
+
+ case FilePositionInformation:
+
+ Status = NtfsSetPositionInfo( IrpContext, FileObject, Irp, Scb );
+ break;
+
+ case FileLinkInformation:
+
+ Status = NtfsSetLinkInfo( IrpContext, Irp, Vcb, Scb, Ccb );
+ break;
+
+ case FileAllocationInformation:
+
+ if (TypeOfOpen == UserDirectoryOpen) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ Status = NtfsSetAllocationInfo( IrpContext, FileObject, Irp, Scb, Ccb );
+ }
+
+ break;
+
+ case FileEndOfFileInformation:
+
+ if (TypeOfOpen == UserDirectoryOpen) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ Status = NtfsSetEndOfFileInfo( IrpContext, FileObject, Irp, Scb, Ccb, VcbAcquired );
+ }
+
+ break;
+
+ default:
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsCommonSetInformation );
+
+ //
+ // Release the paging io resource if acquired shared.
+ //
+
+ if (ReleaseScbPaging) {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+
+ if (Status != STATUS_PENDING) {
+
+ if (VcbAcquired) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ //
+ // Complete the request unless it is being done in the oplock
+ // package.
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetInformation -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryBasicInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN OUT PFILE_BASIC_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query basic information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Ccb - Supplies the Ccb for this handle
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB Fcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryBasicInfo...\n") );
+
+ Fcb = Scb->Fcb;
+
+ //
+ // Zero the output buffer and update the length.
+ //
+
+ RtlZeroMemory( Buffer, sizeof(FILE_BASIC_INFORMATION) );
+
+ *Length -= sizeof( FILE_BASIC_INFORMATION );
+
+ //
+ // Copy over the time information
+ //
+
+ Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime;
+ Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime;
+ Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime;
+
+ Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess;
+
+ //
+ // For the file attribute information if the flags in the attribute are zero then we
+ // return the file normal attribute otherwise we return the mask of the set attribute
+ // bits. Note that only the valid attribute bits are returned to the user.
+ //
+
+ Buffer->FileAttributes = Fcb->Info.FileAttributes;
+
+ ClearFlag( Buffer->FileAttributes,
+ ~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_TEMPORARY );
+
+ if (IsDirectory( &Fcb->Info )
+ && FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
+ }
+
+ //
+ // If this is not the main stream on the file then use the stream based
+ // compressed bit.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+ }
+
+ //
+ // If the temporary flag is set, then return it to the caller.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
+ }
+
+ //
+ // If there are no flags set then explicitly set the NORMAL flag.
+ //
+
+ if (Buffer->FileAttributes == 0) {
+
+ Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryBasicInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryStandardInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_STANDARD_INFORMATION Buffer,
+ IN OUT PULONG Length,
+ IN PCCB Ccb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query standard information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Ccb - Optionally supplies the ccb for the opened file object.
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryStandardInfo...\n") );
+
+ //
+ // Zero out the output buffer and update the length field.
+ //
+
+ RtlZeroMemory( Buffer, sizeof(FILE_STANDARD_INFORMATION) );
+
+ *Length -= sizeof( FILE_STANDARD_INFORMATION );
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )
+ && (Scb->AttributeTypeCode != $INDEX_ALLOCATION)) {
+
+ DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Both the allocation and file size is in the scb header
+ //
+
+ Buffer->AllocationSize.QuadPart = Scb->TotalAllocated;
+ Buffer->EndOfFile = Scb->Header.FileSize;
+ Buffer->NumberOfLinks = Scb->Fcb->LinkCount;
+
+ //
+ // Get the delete and directory flags from the Fcb/Scb state. Note that
+ // the sense of the delete pending bit refers to the file if opened as
+ // file. Otherwise it refers to the attribute only.
+ //
+ // But only do the test if the Ccb has been supplied.
+ //
+
+ if (ARGUMENT_PRESENT(Ccb)) {
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (Scb->Fcb->LinkCount == 0 ||
+ (Ccb->Lcb != NULL &&
+ FlagOn( Ccb->Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE ))) {
+
+ Buffer->DeletePending = TRUE;
+ }
+
+ Buffer->Directory = BooleanIsDirectory( &Scb->Fcb->Info );
+
+ } else {
+
+ Buffer->DeletePending = BooleanFlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ } else {
+
+ Buffer->Directory = BooleanIsDirectory( &Scb->Fcb->Info );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryStandardInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryInternalInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_INTERNAL_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query internal information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryInternalInfo...\n") );
+
+ RtlZeroMemory( Buffer, sizeof(FILE_INTERNAL_INFORMATION) );
+
+ *Length -= sizeof( FILE_INTERNAL_INFORMATION );
+
+ //
+ // Copy over the entire file reference including the sequence number
+ //
+
+ Buffer->IndexNumber = *(PLARGE_INTEGER)&Scb->Fcb->FileReference;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryInternalInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryEaInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_EA_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query EA information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryEaInfo...\n") );
+
+ RtlZeroMemory( Buffer, sizeof(FILE_EA_INFORMATION) );
+
+ *Length -= sizeof( FILE_EA_INFORMATION );
+
+ Buffer->EaSize = Scb->Fcb->Info.PackedEaSize;
+
+ //
+ // Add 4 bytes for the CbListHeader.
+ //
+
+ if (Buffer->EaSize != 0) {
+
+ Buffer->EaSize += 4;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryEaInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryPositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_POSITION_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query position information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryPositionInfo...\n") );
+
+ RtlZeroMemory( Buffer, sizeof(FILE_POSITION_INFORMATION) );
+
+ *Length -= sizeof( FILE_POSITION_INFORMATION );
+
+ //
+ // Get the current position found in the file object.
+ //
+
+ Buffer->CurrentByteOffset = FileObject->CurrentByteOffset;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryPositionInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryNameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN OUT PFILE_NAME_INFORMATION Buffer,
+ IN OUT PULONG Length,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query name information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+ Ccb - This is the Ccb for this file object. If NULL then this request
+ is from the Lazy Writer.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS if the whole name would fit into the user buffer,
+ STATUS_BUFFER_OVERFLOW otherwise.
+
+--*/
+
+{
+ ULONG BytesToCopy;
+ NTSTATUS Status;
+ UNICODE_STRING NormalizedName;
+ PUNICODE_STRING SourceName;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryNameInfo...\n") );
+
+ NormalizedName.Buffer = NULL;
+
+ //
+ // Reduce the buffer length by the size of the fixed part of the structure.
+ //
+
+ RtlZeroMemory( Buffer, SIZEOF_FILE_NAME_INFORMATION );
+
+ *Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
+
+ //
+ // If the name length in this file object is zero, then we try to
+ // construct the name with the Lcb chain. This means we have been
+ // called by the system for a lazy write that failed.
+ //
+
+ if (Ccb == NULL) {
+
+ FILE_REFERENCE FileReference;
+
+ NtfsSetSegmentNumber( &FileReference, 0, UPCASE_TABLE_NUMBER );
+
+ //
+ // If this is a system file with a known name then just use our constant names.
+ //
+
+ if (NtfsLeqMftRef( &Scb->Fcb->FileReference, &FileReference )) {
+
+ SourceName = &NtfsSystemFiles[ Scb->Fcb->FileReference.SegmentNumberLowPart ];
+
+ } else {
+
+ NtfsBuildNormalizedName( IrpContext, Scb, &NormalizedName );
+ SourceName = &NormalizedName;
+ }
+
+ } else {
+
+ SourceName = &Ccb->FullFileName;
+ }
+
+ Buffer->FileNameLength = SourceName->Length;
+
+ if ((Scb->AttributeName.Length != 0) &&
+ NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
+
+ Buffer->FileNameLength += sizeof( WCHAR ) + Scb->AttributeName.Length;
+ }
+
+ //
+ // Figure out how many bytes we can copy.
+ //
+
+ if (*Length >= Buffer->FileNameLength) {
+
+ Status = STATUS_SUCCESS;
+
+ } else {
+
+ Status = STATUS_BUFFER_OVERFLOW;
+ Buffer->FileNameLength = *Length;
+ }
+
+ //
+ // Update the Length
+ //
+
+ *Length -= Buffer->FileNameLength;
+
+ //
+ // Copy over the file name
+ //
+
+ if (SourceName->Length <= Buffer->FileNameLength) {
+
+ BytesToCopy = SourceName->Length;
+
+ } else {
+
+ BytesToCopy = Buffer->FileNameLength;
+ }
+
+ if (BytesToCopy) {
+
+ RtlCopyMemory( &Buffer->FileName[0],
+ SourceName->Buffer,
+ BytesToCopy );
+ }
+
+ BytesToCopy = Buffer->FileNameLength - BytesToCopy;
+
+ if (BytesToCopy) {
+
+ PWCHAR DestBuffer;
+
+ DestBuffer = (PWCHAR) Add2Ptr( &Buffer->FileName, SourceName->Length );
+
+ *DestBuffer = L':';
+ DestBuffer += 1;
+
+ BytesToCopy -= sizeof( WCHAR );
+
+ if (BytesToCopy) {
+
+ RtlCopyMemory( DestBuffer,
+ Scb->AttributeName.Buffer,
+ BytesToCopy );
+ }
+ }
+
+ if ((SourceName == &NormalizedName) &&
+ (SourceName->Buffer != NULL)) {
+
+ NtfsFreePool( SourceName->Buffer );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryNameInfo -> 0x%8lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryAlternateNameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLCB Lcb,
+ IN OUT PFILE_NAME_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query alternate name information function.
+ We will return the alternate name as long as this opener has opened
+ a primary link. We don't return the alternate name if the user
+ has opened a hard link because there is no reason to expect that
+ the primary link has any relationship to a hard link.
+
+Arguments:
+
+ Scb - Supplies the Scb being queried
+
+ Lcb - Supplies the link the user traversed to open this file.
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ **** We need a status code for the case where there is no alternate name
+ or the caller isn't allowed to see it.
+
+ NTSTATUS - STATUS_SUCCESS if the whole name would fit into the user buffer,
+ STATUS_OBJECT_NAME_NOT_FOUND if we can't return the name,
+ STATUS_BUFFER_OVERFLOW otherwise.
+
+ **** A code like STATUS_NAME_NOT_FOUND would be good.
+
+--*/
+
+{
+ ULONG BytesToCopy;
+ NTSTATUS Status;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN MoreToGo;
+
+ UNICODE_STRING AlternateName;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_LCB( Lcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryAlternateNameInfo...\n") );
+
+ //
+ // If the Lcb is not a primary link we can return immediately.
+ //
+
+ if (!FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
+
+ DebugTrace( -1, Dbg, ("NtfsQueryAlternateNameInfo: Lcb not a primary link\n") );
+ return STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ //
+ // Reduce the buffer length by the size of the fixed part of the structure.
+ //
+
+ if (*Length < SIZEOF_FILE_NAME_INFORMATION ) {
+
+ *Length = 0;
+ NtfsRaiseStatus( IrpContext, STATUS_BUFFER_OVERFLOW, NULL, NULL );
+ }
+
+ RtlZeroMemory( Buffer, SIZEOF_FILE_NAME_INFORMATION );
+
+ *Length -= FIELD_OFFSET(FILE_NAME_INFORMATION, FileName[0]);
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try-finally to cleanup the attribut structure if we need it.
+ //
+
+ try {
+
+ //
+ // We can special case for the case where the name is in the Lcb.
+ //
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS )) {
+
+ AlternateName = Lcb->ExactCaseLink.LinkName;
+
+ } else {
+
+ //
+ // We will walk through the file record looking for a file name
+ // attribute with the 8.3 bit set. It is not guaranteed to be
+ // present.
+ //
+
+ MoreToGo = NtfsLookupAttributeByCode( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $FILE_NAME,
+ &AttrContext );
+
+ while (MoreToGo) {
+
+ PFILE_NAME FileName;
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ //
+ // See if the 8.3 flag is set for this name.
+ //
+
+ if (FlagOn( FileName->Flags, FILE_NAME_DOS )) {
+
+ AlternateName.Length = (USHORT)(FileName->FileNameLength * sizeof( WCHAR ));
+ AlternateName.Buffer = (PWSTR) FileName->FileName;
+
+ break;
+ }
+
+ //
+ // The last one wasn't it. Let's try again.
+ //
+
+ MoreToGo = NtfsLookupNextAttributeByCode( IrpContext,
+ Scb->Fcb,
+ $FILE_NAME,
+ &AttrContext );
+ }
+
+ //
+ // If we didn't find a match, return to the caller.
+ //
+
+ if (!MoreToGo) {
+
+ DebugTrace( 0, Dbg, ("NtfsQueryAlternateNameInfo: No Dos link\n") );
+ try_return( Status = STATUS_OBJECT_NAME_NOT_FOUND );
+
+ //
+ // **** Get a better status code.
+ //
+ }
+ }
+
+ //
+ // The name is now in alternate name.
+ // Figure out how many bytes we can copy.
+ //
+
+ if ( *Length >= (ULONG)AlternateName.Length ) {
+
+ Status = STATUS_SUCCESS;
+
+ BytesToCopy = AlternateName.Length;
+
+ } else {
+
+ Status = STATUS_BUFFER_OVERFLOW;
+
+ BytesToCopy = *Length;
+ }
+
+ //
+ // Copy over the file name
+ //
+
+ RtlCopyMemory( Buffer->FileName, AlternateName.Buffer, BytesToCopy);
+
+ //
+ // Copy the number of bytes (not characters) and update the Length
+ //
+
+ Buffer->FileNameLength = BytesToCopy;
+
+ *Length -= BytesToCopy;
+
+ try_exit: NOTHING;
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryAlternateNameInfo -> 0x%8lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsQueryStreamsInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PFILE_STREAM_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will return the attribute name and code name for as
+ many attributes in the file as will fit in the user buffer. We return
+ a string which can be appended to the end of the file name to
+ open the string.
+
+ For example, for the unnamed data stream we will return the string:
+
+ "::$DATA"
+
+ For a user data stream with the name "Authors", we return the string
+
+ ":Authors:$DATA"
+
+Arguments:
+
+ Fcb - This is the Fcb for the file.
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS if all of the names would fit into the user buffer,
+ STATUS_BUFFER_OVERFLOW otherwise.
+
+ **** We need a code indicating that they didn't all fit but
+ some of them got in.
+
+--*/
+
+{
+ NTSTATUS Status;
+ BOOLEAN MoreToGo;
+
+ PUCHAR UserBuffer;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_DEFINITION_COLUMNS AttrDefinition;
+ UNICODE_STRING AttributeCodeString;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ ATTRIBUTE_TYPE_CODE TypeCode = $DATA;
+
+ ULONG NextEntry;
+ ULONG LastEntry;
+ ULONG ThisLength;
+ ULONG NameLength;
+ ULONG LastQuadAlign;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryStreamsInfo...\n") );
+
+ Status = STATUS_SUCCESS;
+
+ LastEntry = 0;
+ NextEntry = 0;
+ LastQuadAlign = 0;
+
+ //
+ // Zero the entire buffer.
+ //
+
+ UserBuffer = (PUCHAR) Buffer;
+
+ RtlZeroMemory( UserBuffer, *Length );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ while (TRUE) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // There should always be at least one attribute.
+ //
+
+ MoreToGo = NtfsLookupAttribute( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ &AttrContext );
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+
+ //
+ // Walk through all of the entries, checking if we can return this
+ // entry to the user and if it will fit in the buffer.
+ //
+
+ while (MoreToGo) {
+
+ //
+ // If we can return this entry to the user, compute it's size.
+ // We only return user defined attributes or data streams
+ // unless we are allowing access to all attributes for
+ // debugging.
+ //
+
+ if ((Attribute->TypeCode == TypeCode)
+
+ &&
+
+ (NtfsIsAttributeResident(Attribute) ||
+ (Attribute->Form.Nonresident.LowestVcn == 0))) {
+
+ PWCHAR StreamName;
+
+ //
+ // Lookup the attribute definition for this attribute code.
+ //
+
+ AttrDefinition = NtfsGetAttributeDefinition( Fcb->Vcb,
+ Attribute->TypeCode );
+
+ //
+ // Generate a unicode string for the attribute code name.
+ //
+
+ RtlInitUnicodeString( &AttributeCodeString, AttrDefinition->AttributeName );
+
+ //
+ //
+ // The size is a combination of the length of the attribute
+ // code name and the attribute name plus the separating
+ // colons plus the size of the structure. We first compute
+ // the name length.
+ //
+
+ NameLength = ((2 + Attribute->NameLength) * sizeof( WCHAR ))
+ + AttributeCodeString.Length;
+
+ ThisLength = FIELD_OFFSET( FILE_STREAM_INFORMATION, StreamName[0] ) + NameLength;
+
+ //
+ // If the entry doesn't fit, we return buffer overflow.
+ //
+ // **** This doesn't seem like a good scheme. Maybe we should
+ // let the user know how much buffer was needed.
+ //
+
+ if (ThisLength + LastQuadAlign > *Length) {
+
+ DebugTrace( 0, Dbg, ("Next entry won't fit in the buffer \n") );
+
+ try_return( Status = STATUS_BUFFER_OVERFLOW );
+ }
+
+ //
+ // Now store the stream information into the user's buffer.
+ // The name starts with a colon, following by the attribute name
+ // and another colon, followed by the attribute code name.
+ //
+
+ if (NtfsIsAttributeResident( Attribute )) {
+
+ Buffer->StreamSize.QuadPart =
+ Attribute->Form.Resident.ValueLength;
+ Buffer->StreamAllocationSize.QuadPart =
+ QuadAlign( Attribute->Form.Resident.ValueLength );
+
+ } else {
+
+ Buffer->StreamSize.QuadPart = Attribute->Form.Nonresident.FileSize;
+ Buffer->StreamAllocationSize.QuadPart = Attribute->Form.Nonresident.AllocatedLength;
+ }
+
+ Buffer->StreamNameLength = NameLength;
+
+ StreamName = (PWCHAR) Buffer->StreamName;
+
+ *StreamName = L':';
+ StreamName += 1;
+
+ RtlCopyMemory( StreamName,
+ Add2Ptr( Attribute, Attribute->NameOffset ),
+ Attribute->NameLength * sizeof( WCHAR ));
+
+ StreamName += Attribute->NameLength;
+
+ *StreamName = L':';
+ StreamName += 1;
+
+ RtlCopyMemory( StreamName,
+ AttributeCodeString.Buffer,
+ AttributeCodeString.Length );
+
+ //
+ // Set up the previous next entry offset to point to this entry.
+ //
+
+ *((PULONG)(&UserBuffer[LastEntry])) = NextEntry - LastEntry;
+
+ //
+ // Subtract the number of bytes used from the number of bytes
+ // available in the buffer.
+ //
+
+ *Length -= (ThisLength + LastQuadAlign);
+
+ //
+ // Compute the number of bytes needed to quad-align this entry
+ // and the offset of the next entry.
+ //
+
+ LastQuadAlign = QuadAlign( ThisLength ) - ThisLength;
+
+ LastEntry = NextEntry;
+ NextEntry += (ThisLength + LastQuadAlign);
+
+ //
+ // Generate a pointer at the next entry offset.
+ //
+
+ Buffer = (PFILE_STREAM_INFORMATION) Add2Ptr( UserBuffer, NextEntry );
+ }
+
+ //
+ // Look for the next attribute in the file.
+ //
+
+ MoreToGo = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &AttrContext );
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+ }
+
+ //
+ // We've finished enumerating an attribute type code. Check
+ // to see if we should advance to the next enumeration type.
+ //
+
+#ifndef _CAIRO
+ break;
+#else // _CAIRO_
+ if (TypeCode == $PROPERTY_SET) {
+ break;
+ } else {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ TypeCode = $PROPERTY_SET;
+ }
+#endif // _CAIRO_
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsQueryStreamsInfo );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryStreamInfo -> 0x%8lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsQueryCompressedFileSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PFILE_COMPRESSION_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+
+{
+ //
+ // Lookup the attribute and pin it so that we can modify it.
+ //
+
+ //
+ // Reduce the buffer length by the size of the fixed part of the structure.
+ //
+
+ if (*Length < sizeof(FILE_COMPRESSION_INFORMATION) ) {
+
+ *Length = 0;
+ NtfsRaiseStatus( IrpContext, STATUS_BUFFER_OVERFLOW, NULL, NULL );
+ }
+
+ if ((Scb->Header.NodeTypeCode == NTFS_NTC_SCB_INDEX) ||
+ (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX)) {
+
+ Buffer->CompressedFileSize = Li0;
+
+ } else {
+
+ Buffer->CompressedFileSize.QuadPart = Scb->TotalAllocated;
+ }
+
+ //
+ // Do not return more than FileSize.
+ //
+
+ if (Buffer->CompressedFileSize.QuadPart > Scb->Header.FileSize.QuadPart) {
+
+ Buffer->CompressedFileSize = Scb->Header.FileSize;
+ }
+
+ //
+ // Start off saying that the file/directory isn't comressed
+ //
+
+ Buffer->CompressionFormat = 0;
+
+ //
+ // If this is the index allocation Scb and it has not been initialized then
+ // lookup the index root and perform the initialization.
+ //
+
+ if ((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
+ (Scb->ScbType.Index.BytesPerIndexBuffer == 0)) {
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Use a try-finally to perform cleanup.
+ //
+
+ try {
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &Context )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ NtfsUpdateIndexScbFromAttribute( Scb,
+ NtfsFoundAttribute( &Context ));
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+ }
+
+ //
+ // Return the compression state and the size of the returned data.
+ //
+
+ Buffer->CompressionFormat = (USHORT)(Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
+
+ if (Buffer->CompressionFormat != 0) {
+ Buffer->CompressionFormat += 1;
+ Buffer->ClusterShift = (UCHAR)Scb->Vcb->ClusterShift;
+ Buffer->CompressionUnitShift = (UCHAR)(Scb->CompressionUnitShift + Buffer->ClusterShift);
+ Buffer->ChunkShift = NTFS_CHUNK_SHIFT;
+ }
+
+ *Length -= sizeof(FILE_COMPRESSION_INFORMATION);
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Internal Support Routine
+//
+
+VOID
+NtfsQueryNetworkOpenInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN PCCB Ccb,
+ IN OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query network open information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Scb - Supplies the Scb being queried
+
+ Ccb - Supplies the Ccb for this handle
+
+ Buffer - Supplies a pointer to the buffer where the information is to
+ be returned
+
+ Length - Supplies the length of the buffer in bytes, and receives the
+ remaining bytes free in the buffer upon return.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB Fcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQueryNetworkOpenInfo...\n") );
+
+ Fcb = Scb->Fcb;
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED ) &&
+ (Scb->AttributeTypeCode != $INDEX_ALLOCATION)) {
+
+ DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Zero the output buffer and update the length.
+ //
+
+ RtlZeroMemory( Buffer, sizeof(FILE_NETWORK_OPEN_INFORMATION) );
+
+ *Length -= sizeof( FILE_NETWORK_OPEN_INFORMATION );
+
+ //
+ // Copy over the time information
+ //
+
+ Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime;
+ Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime;
+ Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime;
+
+ Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess;
+
+ //
+ // Both the allocation and file size are in the scb header
+ //
+
+ Buffer->AllocationSize.QuadPart = Scb->TotalAllocated;
+ Buffer->EndOfFile.QuadPart = Scb->Header.FileSize.QuadPart;
+
+ //
+ // For the file attribute information if the flags in the attribute are zero then we
+ // return the file normal attribute otherwise we return the mask of the set attribute
+ // bits. Note that only the valid attribute bits are returned to the user.
+ //
+
+ Buffer->FileAttributes = Fcb->Info.FileAttributes;
+
+ ClearFlag( Buffer->FileAttributes,
+ ~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_TEMPORARY );
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
+
+ //
+ // Set the sizes back to zero for a directory.
+ //
+
+ Buffer->AllocationSize.QuadPart =
+ Buffer->EndOfFile.QuadPart = 0;
+ }
+
+ //
+ // If this is not the main stream on the file then use the stream based
+ // compressed bit.
+ //
+
+ } else {
+
+ if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+ }
+
+ //
+ // If the temporary flag is set, then return it to the caller.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
+ }
+
+ //
+ // If there are no flags set then explicitly set the NORMAL flag.
+ //
+
+ if (Buffer->FileAttributes == 0) {
+
+ Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("*Length = %08lx\n", *Length) );
+ DebugTrace( -1, Dbg, ("NtfsQueryNetworkOpenInfo -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetBasicInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set basic information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - Supplies the Ccb for this operation
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PFCB Fcb;
+
+ PFILE_BASIC_INFORMATION Buffer;
+
+ BOOLEAN LeaveChangeTime = BooleanFlagOn( Ccb->Flags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME );
+
+ LONGLONG CurrentTime;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetBasicInfo...\n") );
+
+ Fcb = Scb->Fcb;
+
+ //
+ // Reference the system buffer containing the user specified basic
+ // information record
+ //
+
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Do a quick check to see there are any illegal time stamps being set.
+ // Ntfs supports all values of Nt time as long as the uppermost bit
+ // isn't set.
+ //
+
+ if (FlagOn( Buffer->ChangeTime.HighPart, 0x80000000 ) ||
+ FlagOn( Buffer->CreationTime.HighPart, 0x80000000 ) ||
+ FlagOn( Buffer->LastAccessTime.HighPart, 0x80000000 ) ||
+ FlagOn( Buffer->LastWriteTime.HighPart, 0x80000000 )) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ NtfsGetCurrentTime( IrpContext, CurrentTime );
+
+ //
+ // Pick up any changes from the fast Io path now while we have the
+ // file exclusive.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // If the user specified a non-zero file attributes field then
+ // we need to change the file attributes. This code uses the
+ // I/O supplied system buffer to modify the file attributes field
+ // before changing its value on the disk.
+ //
+
+ if (Buffer->FileAttributes != 0) {
+
+ //
+ // Check for valid flags being passed in. We fail if this is
+ // a directory and the TEMPORARY bit is used. Also fail if this
+ // is a file and the DIRECTORY bit is used.
+ //
+
+ if (Scb->AttributeTypeCode == $DATA) {
+
+ if (FlagOn( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY )) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ } else if (IsDirectory( &Fcb->Info )) {
+
+ if (FlagOn( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY )) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", STATUS_INVALID_PARAMETER) );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ //
+ // Clear out the normal bit and the directory bit as well as any unsupported
+ // bits.
+ //
+
+ ClearFlag( Buffer->FileAttributes,
+ (~FILE_ATTRIBUTE_VALID_SET_FLAGS |
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_RESERVED0 |
+ FILE_ATTRIBUTE_RESERVED1 |
+ FILE_ATTRIBUTE_COMPRESSED) );
+
+ //
+ // Update the attributes in the Fcb if this is a change to the file.
+ //
+
+ Fcb->Info.FileAttributes = (Fcb->Info.FileAttributes &
+ (FILE_ATTRIBUTE_COMPRESSED |
+ FILE_ATTRIBUTE_DIRECTORY |
+ DUP_FILE_NAME_INDEX_PRESENT)) |
+ Buffer->FileAttributes;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
+
+ //
+ // If this is the root directory then keep the hidden and system flags.
+ //
+
+ if (Fcb == Fcb->Vcb->RootIndexScb->Fcb) {
+
+ SetFlag( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN );
+
+ //
+ // Mark the file object temporary flag correctly.
+ //
+
+ } else if (FlagOn(Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY)) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_TEMPORARY );
+ SetFlag( FileObject->Flags, FO_TEMPORARY_FILE );
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_TEMPORARY );
+ ClearFlag( FileObject->Flags, FO_TEMPORARY_FILE );
+ }
+
+ if (!LeaveChangeTime) {
+
+ Fcb->Info.LastChangeTime = CurrentTime;
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ LeaveChangeTime = TRUE;
+ }
+ }
+
+ //
+ // If the user specified a non-zero change time then change
+ // the change time on the record. Then do the exact same
+ // for the last acces time, last write time, and creation time
+ //
+
+ if (Buffer->ChangeTime.QuadPart != 0) {
+
+ Fcb->Info.LastChangeTime = Buffer->ChangeTime.QuadPart;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME );
+
+ LeaveChangeTime = TRUE;
+ }
+
+ if (Buffer->CreationTime.QuadPart != 0) {
+
+ Fcb->Info.CreationTime = Buffer->CreationTime.QuadPart;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
+
+ if (!LeaveChangeTime) {
+
+ Fcb->Info.LastChangeTime = CurrentTime;
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ LeaveChangeTime = TRUE;
+ }
+ }
+
+ if (Buffer->LastAccessTime.QuadPart != 0) {
+
+ Fcb->CurrentLastAccess = Fcb->Info.LastAccessTime = Buffer->LastAccessTime.QuadPart;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+ SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME );
+
+ if (!LeaveChangeTime) {
+
+ Fcb->Info.LastChangeTime = CurrentTime;
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ LeaveChangeTime = TRUE;
+ }
+ }
+
+ if (Buffer->LastWriteTime.QuadPart != 0) {
+
+ Fcb->Info.LastModificationTime = Buffer->LastWriteTime.QuadPart;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_MOD );
+ SetFlag( Ccb->Flags, CCB_FLAG_USER_SET_LAST_MOD_TIME );
+
+ if (!LeaveChangeTime) {
+
+ Fcb->Info.LastChangeTime = CurrentTime;
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ LeaveChangeTime = TRUE;
+ }
+ }
+
+ //
+ // Now indicate that we should not be updating the standard information attribute anymore
+ // on cleanup.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE )) {
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE
+ );
+
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
+ }
+ }
+
+ Status = STATUS_SUCCESS;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSetBasicInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetDispositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set disposition information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - Supplies the Ccb for this handle
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PLCB Lcb;
+ BOOLEAN GenerateOnClose;
+ PIO_STACK_LOCATION IrpSp;
+ HANDLE FileHandle = NULL;
+
+ PFILE_DISPOSITION_INFORMATION Buffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetDispositionInfo...\n") );
+
+ //
+ // First pull the file handle out of the irp
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FileHandle = IrpSp->Parameters.SetFile.DeleteHandle;
+
+ //
+ // We get the Lcb for this open. If there is no link then we can't
+ // set any disposition information.
+ //
+
+ Lcb = Ccb->Lcb;
+
+ if (Lcb == NULL) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetDispositionInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Reference the system buffer containing the user specified disposition
+ // information record
+ //
+
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ try {
+
+ if (Buffer->DeleteFile) {
+
+ //
+ // Check if the file is marked read only
+ //
+
+ if (IsReadOnly( &Scb->Fcb->Info )) {
+
+ DebugTrace( 0, Dbg, ("File fat flags indicates read only\n") );
+
+ try_return( Status = STATUS_CANNOT_DELETE );
+ }
+
+ //
+ // Make sure there is no process mapping this file as an image
+ //
+
+ if (!MmFlushImageSection( &Scb->NonpagedScb->SegmentObject,
+ MmFlushForDelete )) {
+
+ DebugTrace( 0, Dbg, ("Failed to flush image section\n") );
+
+ try_return( Status = STATUS_CANNOT_DELETE );
+ }
+
+ //
+ // Check that we are not trying to delete one of the special
+ // system files.
+ //
+
+ if ((Scb == Scb->Vcb->MftScb) ||
+ (Scb == Scb->Vcb->Mft2Scb) ||
+ (Scb == Scb->Vcb->LogFileScb) ||
+ (Scb == Scb->Vcb->VolumeDasdScb) ||
+ (Scb == Scb->Vcb->AttributeDefTableScb) ||
+ (Scb == Scb->Vcb->UpcaseTableScb) ||
+ (Scb == Scb->Vcb->RootIndexScb) ||
+ (Scb == Scb->Vcb->BitmapScb) ||
+ (Scb == Scb->Vcb->BadClusterFileScb) ||
+ (Scb == Scb->Vcb->QuotaTableScb) ||
+ (Scb == Scb->Vcb->MftBitmapScb)) {
+
+ DebugTrace( 0, Dbg, ("Scb is one of the special system files\n") );
+
+ try_return( Status = STATUS_CANNOT_DELETE );
+ }
+
+ //
+ // Now check that the file is really deleteable according to indexsup
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ BOOLEAN LastLink;
+ BOOLEAN NonEmptyIndex = FALSE;
+
+ //
+ // If the link is not deleted, we check if it can be deleted.
+ //
+
+ if ((BOOLEAN)!LcbLinkIsDeleted( Lcb )
+ && (BOOLEAN)NtfsIsLinkDeleteable( IrpContext, Scb->Fcb, &NonEmptyIndex, &LastLink )) {
+
+ //
+ // It is ok to get rid of this guy. All we need to do is
+ // mark this Lcb for delete and decrement the link count
+ // in the Fcb. If this is a primary link, then we
+ // indicate that the primary link has been deleted.
+ //
+
+ SetFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
+
+ ASSERTMSG( "Link count should not be 0\n", Scb->Fcb->LinkCount != 0 );
+ Scb->Fcb->LinkCount -= 1;
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
+
+ SetFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
+ }
+
+ //
+ // Call into the notify package to close any handles on
+ // a directory being deleted.
+ //
+
+ if (IsDirectory( &Scb->Fcb->Info )) {
+
+ FsRtlNotifyFullChangeDirectory( Scb->Vcb->NotifySync,
+ &Scb->Vcb->DirNotifyList,
+ FileObject->FsContext,
+ NULL,
+ FALSE,
+ FALSE,
+ 0,
+ NULL,
+ NULL,
+ NULL );
+ }
+
+ } else if (NonEmptyIndex) {
+
+ DebugTrace( 0, Dbg, ("Index attribute has entries\n") );
+
+ try_return( Status = STATUS_DIRECTORY_NOT_EMPTY );
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("File is not deleteable\n") );
+
+ try_return( Status = STATUS_CANNOT_DELETE );
+ }
+
+ //
+ // Otherwise we are simply removing the attribute.
+ //
+
+ } else {
+
+ SetFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ //
+ // Indicate in the file object that a delete is pending
+ //
+
+ FileObject->DeletePending = TRUE;
+
+ //
+ // Only do the auditing if we have a user handle.
+ //
+
+ if (FileHandle != NULL) {
+
+ Status = ObQueryObjectAuditingByHandle( FileHandle,
+ &GenerateOnClose );
+
+ //
+ // If we have a valid handle, perform the audit.
+ //
+
+ if (NT_SUCCESS( Status ) && GenerateOnClose) {
+
+ SeDeleteObjectAuditAlarm( FileObject, FileHandle );
+ }
+ }
+
+ } else {
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (LcbLinkIsDeleted( Lcb )) {
+
+ //
+ // The user doesn't want to delete the link so clear any delete bits
+ // we have laying around
+ //
+
+ DebugTrace( 0, Dbg, ("File is being marked as do not delete on close\n") );
+
+ ClearFlag( Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE );
+
+ Scb->Fcb->LinkCount += 1;
+ ASSERTMSG( "Link count should not be 0\n", Scb->Fcb->LinkCount != 0 );
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) {
+
+ ClearFlag( Scb->Fcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED );
+ }
+ }
+
+ //
+ // Otherwise we are undeleting an attribute.
+ //
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+
+ FileObject->DeletePending = FALSE;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsSetDispositionInfo );
+
+ NOTHING;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSetDispositionInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetRenameInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set rename function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Vcb - Supplies the Vcb for the Volume
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - Supplies the Ccb for this file object
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ PLCB Lcb = Ccb->Lcb;
+ PFCB Fcb = Scb->Fcb;
+ PSCB ParentScb;
+ USHORT FcbLinkCountAdj = 0;
+
+ PFCB TargetLinkFcb = NULL;
+ BOOLEAN ExistingTargetLinkFcb;
+ BOOLEAN AcquiredTargetLinkFcb = FALSE;
+ USHORT TargetLinkFcbCountAdj = 0;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+ PERESOURCE ResourceToRelease = NULL;
+
+ PFILE_OBJECT TargetFileObject;
+ PSCB TargetParentScb;
+
+ UNICODE_STRING NewLinkName;
+ UNICODE_STRING NewFullLinkName;
+ PWCHAR NewFullLinkNameBuffer = NULL;
+ UCHAR NewLinkNameFlags;
+
+ PFILE_NAME FileNameAttr = NULL;
+ USHORT FileNameAttrLength = 0;
+
+ UNICODE_STRING PrevLinkName;
+ UNICODE_STRING PrevFullLinkName;
+ UCHAR PrevLinkNameFlags;
+
+ UNICODE_STRING SourceFullLinkName;
+ USHORT SourceLinkLastNameOffset;
+
+ BOOLEAN FoundLink;
+ PINDEX_ENTRY IndexEntry;
+ PBCB IndexEntryBcb = NULL;
+ PWCHAR NextChar;
+
+ BOOLEAN ReportDirNotify = FALSE;
+
+ ULONG RenameFlags = ACTIVELY_REMOVE_SOURCE_LINK | REMOVE_SOURCE_LINK | ADD_TARGET_LINK;
+
+ PLIST_ENTRY Links;
+ PSCB ThisScb;
+
+ NAME_PAIR NamePair;
+ LONGLONG TunneledCreationTime;
+ ULONG TunneledDataSize;
+ BOOLEAN HaveTunneledInformation = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE ();
+
+ DebugTrace( +1, Dbg, ("NtfsSetRenameInfo...\n") );
+
+ //
+ // Do a quick check that the caller is allowed to do the rename.
+ // The opener must have opened the main data stream by name and this can't be
+ // a system file.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE ) ||
+ (Lcb == NULL) ||
+ (NtfsSegmentNumber( &Fcb->FileReference ) < FIRST_USER_FILE_NUMBER)) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // If this link has been deleted, then we don't allow this operation.
+ //
+
+ if (LcbLinkIsDeleted( Lcb )) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", STATUS_ACCESS_DENIED) );
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // Verify that we can wait.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Can't wait\n") );
+ return Status;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Initialize the local variables.
+ //
+
+ ParentScb = Lcb->Scb;
+ TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
+
+ NtfsInitializeNamePair( &NamePair );
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
+ (Vcb->NotifyCount != 0)) {
+
+ ReportDirNotify = TRUE;
+ }
+
+ PrevFullLinkName.Buffer = NULL;
+ SourceFullLinkName.Buffer = NULL;
+
+ //
+ // If this is a directory file, we need to examine its descendents.
+ // We may not remove a link which may be an ancestor path
+ // component of any open file.
+ //
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ PSCB BatchOplockScb;
+ ULONG BatchOplockCount;
+
+ Status = NtfsCheckScbForLinkRemoval( Scb, &BatchOplockScb, &BatchOplockCount );
+
+ //
+ // If STATUS_PENDING is returned then we need to check whether
+ // to break a batch oplock.
+ //
+
+ if (Status == STATUS_PENDING) {
+
+ //
+ // If the number of batch oplocks has grown then fail the request.
+ //
+
+ if ((Irp->IoStatus.Information != 0) &&
+ (BatchOplockCount >= Irp->IoStatus.Information)) {
+
+ Status = STATUS_ACCESS_DENIED;
+ leave;
+ }
+
+ //
+ // Remember the count of batch oplocks in the Irp and
+ // then call the oplock package.
+ //
+
+ Irp->IoStatus.Information = BatchOplockCount;
+
+ Status = FsRtlCheckOplock( &BatchOplockScb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsPrePostIrp );
+
+ //
+ // If we got back success then raise CANT_WAIT to retry otherwise
+ // clean up.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+
+ } else if (Status == STATUS_PENDING) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ leave;
+
+ } else if (!NT_SUCCESS( Status )) {
+
+ leave;
+ }
+ }
+
+ //
+ // We now assemble the names and in memory-structures for both the
+ // source and target links and check if the target link currently
+ // exists.
+ //
+
+ NtfsFindTargetElements( IrpContext,
+ TargetFileObject,
+ ParentScb,
+ &TargetParentScb,
+ &NewFullLinkName,
+ &NewLinkName );
+
+ //
+ // Check that the new name is not invalid.
+ //
+
+ if ((NewLinkName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) ||
+ !NtfsIsFileNameValid( &NewLinkName, FALSE )) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+ leave;
+ }
+
+ //
+ // Acquire the current parent in order to synchronize removing the current name.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+
+ //
+ // If this Scb does not have a normalized name then provide it with one now.
+ //
+
+ if ((ParentScb->ScbType.Index.NormalizedName.Buffer == NULL) ||
+ (ParentScb->ScbType.Index.NormalizedName.Length == 0)) {
+
+ NtfsBuildNormalizedName( IrpContext,
+ ParentScb,
+ &ParentScb->ScbType.Index.NormalizedName );
+ }
+
+ //
+ // If this is a directory then make sure it has a normalized name.
+ //
+
+ if (IsDirectory( &Fcb->Info ) &&
+ ((Scb->ScbType.Index.NormalizedName.Buffer == NULL) ||
+ (Scb->ScbType.Index.NormalizedName.Length == 0))) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ ParentScb,
+ Scb,
+ NULL,
+ FALSE );
+ }
+
+ //
+ // Check if we are renaming to the same directory with the exact same name.
+ //
+
+ if (TargetParentScb == ParentScb) {
+
+ if (NtfsAreNamesEqual( Vcb->UpcaseTable, &NewLinkName, &Lcb->ExactCaseLink.LinkName, FALSE )) {
+
+ DebugTrace( 0, Dbg, ("Renaming to same name and directory\n") );
+ leave;
+ }
+
+ //
+ // Otherwise we want to acquire the target directory.
+ //
+
+ } else {
+
+ //
+ // We need to do the acquisition carefully since we may only have the Vcb shared.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext,
+ TargetParentScb->Fcb,
+ TargetParentScb,
+ FALSE,
+ TRUE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Now snapshot the Scb.
+ //
+
+ if (FlagOn( TargetParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ NtfsSnapshotScb( IrpContext, TargetParentScb );
+ }
+
+ } else {
+
+ NtfsAcquireExclusiveScb( IrpContext, TargetParentScb );
+ }
+
+ SetFlag( RenameFlags, MOVE_TO_NEW_DIR );
+ }
+
+ //
+ // We also determine which type of link to
+ // create. We create a hard link only unless the source link is
+ // a primary link and the user is an IgnoreCase guy.
+ //
+
+ if (FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ) &&
+ FlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE )) {
+
+ SetFlag( RenameFlags, ADD_PRIMARY_LINK );
+ }
+
+ //
+ // Lookup the entry for this filename in the target directory.
+ // We look in the Ccb for the type of case match for the target
+ // name.
+ //
+
+ FoundLink = NtfsLookupEntry( IrpContext,
+ TargetParentScb,
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
+ &NewLinkName,
+ &FileNameAttr,
+ &FileNameAttrLength,
+ NULL,
+ &IndexEntry,
+ &IndexEntryBcb );
+
+ //
+ // If we found a matching link, we need to check how we want to operate
+ // on the source link and the target link. This means whether we
+ // have any work to do, whether we need to remove the target link
+ // and whether we need to remove the source link.
+ //
+
+ if (FoundLink) {
+
+ PFILE_NAME IndexFileName;
+
+ //
+ // Assume we will remove this link.
+ //
+
+ SetFlag( RenameFlags, REMOVE_TARGET_LINK );
+
+ IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
+
+ NtfsCheckLinkForRename( Fcb,
+ Lcb,
+ IndexFileName,
+ IndexEntry->FileReference,
+ &NewLinkName,
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
+ &RenameFlags );
+
+ //
+ // Assume we will use the existing name flags on the link found. This
+ // will be the case where the file was opened with the 8.3 name and
+ // the new name is exactly the long name for the same file.
+ //
+
+ PrevLinkNameFlags =
+ NewLinkNameFlags = IndexFileName->Flags;
+
+ //
+ // If we didn't have an exact match, then we need to check if we
+ // can remove the found link and then remove it from the disk.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_TARGET_LINK )) {
+
+ //
+ // We need to check that the user wanted to remove that link.
+ //
+
+ if (!FlagOn( RenameFlags, TRAVERSE_MATCH ) &&
+ !IrpSp->Parameters.SetFile.ReplaceIfExists) {
+
+ Status = STATUS_OBJECT_NAME_COLLISION;
+ leave;
+ }
+
+ //
+ // We want to preserve the case and the flags of the matching
+ // link found. We also want to preserve the case of the
+ // name being created. The following variables currently contain
+ // the exact case for the target to remove and the new name to
+ // apply.
+ //
+ // Link to remove - In 'IndexEntry'.
+ // The link's flags are also in 'IndexEntry'. We copy
+ // these flags to 'PrevLinkNameFlags'
+ //
+ // New Name - Exact case is stored in 'NewLinkName'
+ // - It is also in 'FileNameAttr
+ //
+ // We modify this so that we can use the FileName attribute
+ // structure to create the new link. We copy the linkname being
+ // removed into 'PrevLinkName'. The following is the
+ // state after the switch.
+ //
+ // 'FileNameAttr' - contains the name for the link being
+ // created.
+ //
+ // 'PrevLinkFileName' - Contains the link name for the link being
+ // removed.
+ //
+ // 'PrevLinkFileNameFlags' - Contains the name flags for the link
+ // being removed.
+ //
+
+ //
+ // Allocate a buffer for the name being removed. It should be
+ // large enough for the entire directory name.
+ //
+
+ PrevFullLinkName.MaximumLength = TargetParentScb->ScbType.Index.NormalizedName.Length +
+ sizeof( WCHAR ) +
+ (IndexFileName->FileNameLength * sizeof( WCHAR ));
+
+ PrevFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
+ PrevFullLinkName.MaximumLength );
+
+ RtlCopyMemory( PrevFullLinkName.Buffer,
+ TargetParentScb->ScbType.Index.NormalizedName.Buffer,
+ TargetParentScb->ScbType.Index.NormalizedName.Length );
+
+ NextChar = Add2Ptr( PrevFullLinkName.Buffer,
+ TargetParentScb->ScbType.Index.NormalizedName.Length );
+
+ if (TargetParentScb != Vcb->RootIndexScb) {
+
+ *NextChar = L'\\';
+ NextChar += 1;
+ }
+
+ RtlCopyMemory( NextChar,
+ IndexFileName->FileName,
+ IndexFileName->FileNameLength * sizeof( WCHAR ));
+
+ //
+ // Copy the name found in the Index Entry to 'PrevLinkName'
+ //
+
+ PrevLinkName.Buffer = NextChar;
+ PrevLinkName.MaximumLength =
+ PrevLinkName.Length = IndexFileName->FileNameLength * sizeof( WCHAR );
+
+ //
+ // Update the full name length with the final component.
+ //
+
+ PrevFullLinkName.Length = (USHORT) PtrOffset( PrevFullLinkName.Buffer, NextChar ) + PrevLinkName.Length;
+
+ //
+ // We only need this check if the link is for a different file.
+ //
+
+ if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ //
+ // We check if there is an existing Fcb for the target link.
+ // If there is, the unclean count better be 0.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ TargetLinkFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ IndexEntry->FileReference,
+ FALSE,
+ BooleanFlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ),
+ &ExistingTargetLinkFcb );
+
+ //
+ // We need to acquire this file carefully in the event that we don't hold
+ // the Vcb exclusively.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ if (TargetLinkFcb->PagingIoResource != NULL) {
+
+ if (!ExAcquireResourceExclusive( TargetLinkFcb->PagingIoResource, FALSE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ ResourceToRelease = TargetLinkFcb->PagingIoResource;
+ }
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, FALSE, TRUE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ } else {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // Acquire the paging Io resource for this file before the main
+ // resource in case we need to delete.
+ //
+
+ if (TargetLinkFcb->PagingIoResource != NULL) {
+ ResourceToRelease = TargetLinkFcb->PagingIoResource;
+ ExAcquireResourceExclusive( ResourceToRelease, TRUE );
+ }
+
+ NtfsAcquireExclusiveFcb( IrpContext, TargetLinkFcb, NULL, FALSE, FALSE );
+ }
+
+ AcquiredTargetLinkFcb = TRUE;
+
+ //
+ // If the Fcb Info field needs to be initialized, we do so now.
+ // We read this information from the disk as the duplicate information
+ // in the index entry is not guaranteed to be correct.
+ //
+
+ if (!FlagOn( TargetLinkFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ TargetLinkFcb,
+ TargetParentScb->Fcb,
+ NULL );
+
+ NtfsConditionallyFixupQuota( IrpContext, TargetLinkFcb );
+
+ }
+
+ //
+ // We are adding a link to the source file which already
+ // exists as a link to a different file in the target directory.
+ //
+ // We need to check whether we permitted to delete this
+ // link. If not then it is possible that the problem is
+ // an existing batch oplock on the file. In that case
+ // we want to delete the batch oplock and try this again.
+ //
+
+ Status = NtfsCheckFileForDelete( IrpContext,
+ TargetParentScb,
+ TargetLinkFcb,
+ ExistingTargetLinkFcb,
+ IndexEntry );
+
+ if (!NT_SUCCESS( Status )) {
+
+ PSCB NextScb = NULL;
+
+ //
+ // We are going to either fail this request or pass
+ // this on to the oplock package. Test if there is
+ // a batch oplock on any streams on this file.
+ //
+
+ while ((NextScb = NtfsGetNextChildScb( TargetLinkFcb,
+ NextScb )) != NULL) {
+
+ if ((NextScb->AttributeTypeCode == $DATA) &&
+ (NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
+ FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ Status = FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsPrePostIrp );
+
+ break;
+ }
+ }
+
+ leave;
+ }
+
+ NtfsCleanupLinkForRemoval( TargetLinkFcb, ExistingTargetLinkFcb );
+
+ if (TargetLinkFcb->LinkCount == 1) {
+
+ NtfsDeleteFile( IrpContext,
+ TargetLinkFcb,
+ TargetParentScb,
+ NULL );
+
+ TargetLinkFcbCountAdj += 1;
+
+ } else {
+
+ NtfsRemoveLink( IrpContext,
+ TargetLinkFcb,
+ TargetParentScb,
+ PrevLinkName,
+ NULL );
+
+ TargetLinkFcbCountAdj += 1;
+ NtfsUpdateFcb( TargetLinkFcb );
+ }
+
+ //
+ // The target link is for the same file as the source link. No security
+ // checks need to be done. Go ahead and remove it.
+ //
+
+ } else {
+
+ TargetLinkFcb = Fcb;
+ NtfsRemoveLink( IrpContext,
+ Fcb,
+ TargetParentScb,
+ PrevLinkName,
+ NULL );
+
+ FcbLinkCountAdj += 1;
+ }
+ }
+ }
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ //
+ // See if we need to remove the current link.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
+
+ //
+ // Now we want to remove the source link from the file. We need to
+ // remember if we deleted a two part primary link.
+ //
+
+ if (FlagOn( RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK )) {
+
+ NtfsRemoveLink( IrpContext,
+ Fcb,
+ ParentScb,
+ Lcb->ExactCaseLink.LinkName,
+ &NamePair );
+
+ //
+ // Remember the full name for the original filename and some
+ // other information to pass to the dirnotify package.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ if (!IsDirectory( &Fcb->Info ) &&
+ !FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) {
+
+ //
+ // Tunnel property information for file links
+ //
+
+ FsRtlAddToTunnelCache( &Vcb->Tunnel,
+ *(PULONGLONG)&ParentScb->Fcb->FileReference,
+ &NamePair.Short,
+ &NamePair.Long,
+ BooleanFlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS ),
+ sizeof( LONGLONG ),
+ &Fcb->Info.CreationTime );
+ }
+ }
+
+ FcbLinkCountAdj += 1;
+ }
+
+ if (ReportDirNotify) {
+
+ SourceFullLinkName.Buffer = NtfsAllocatePool( PagedPool, Ccb->FullFileName.Length );
+
+ RtlCopyMemory( SourceFullLinkName.Buffer,
+ Ccb->FullFileName.Buffer,
+ Ccb->FullFileName.Length );
+
+ SourceFullLinkName.MaximumLength = SourceFullLinkName.Length = Ccb->FullFileName.Length;
+ SourceLinkLastNameOffset = Ccb->LastFileNameOffset;
+ }
+ }
+
+ //
+ // See if we need to add the target link.
+ //
+
+ if (FlagOn( RenameFlags, ADD_TARGET_LINK )) {
+
+ //
+ // Check that we have permission to add a file to this directory.
+ //
+
+ NtfsCheckIndexForAddOrDelete( IrpContext,
+ TargetParentScb->Fcb,
+ (IsDirectory( &Fcb->Info ) ?
+ FILE_ADD_SUBDIRECTORY :
+ FILE_ADD_FILE) );
+
+ //
+ // Grunge the tunnel cache for property restoration
+ //
+
+ if (!IsDirectory( &Fcb->Info ) &&
+ !FlagOn( FileObject->Flags, FO_OPENED_CASE_SENSITIVE )) {
+
+ NtfsResetNamePair( &NamePair );
+ TunneledDataSize = sizeof( LONGLONG );
+
+ if (FsRtlFindInTunnelCache( &Vcb->Tunnel,
+ *(PULONGLONG)&TargetParentScb->Fcb->FileReference,
+ &NewLinkName,
+ &NamePair.Short,
+ &NamePair.Long,
+ &TunneledDataSize,
+ &TunneledCreationTime)) {
+
+ ASSERT( TunneledDataSize == sizeof( LONGLONG ));
+ HaveTunneledInformation = TRUE;
+ }
+ }
+
+ //
+ // We now want to add the new link into the target directory.
+ // We create a hard link only if the source name was a hard link
+ // or this is a case-sensitive open. This means that we can
+ // replace a primary link pair with a hard link only.
+ //
+
+ NtfsAddLink( IrpContext,
+ BooleanFlagOn( RenameFlags, ADD_PRIMARY_LINK ),
+ TargetParentScb,
+ Fcb,
+ FileNameAttr,
+ NULL,
+ &NewLinkNameFlags,
+ NULL,
+ HaveTunneledInformation ? &NamePair : NULL );
+
+ //
+ // Restore timestamps on tunneled files
+ //
+
+ if (HaveTunneledInformation) {
+
+ Fcb->Info.CreationTime = TunneledCreationTime;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_CREATE );
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ //
+ // If we have tunneled information then copy the correct case of the
+ // name into the new link pointer.
+ //
+
+ if (NewLinkNameFlags == FILE_NAME_DOS) {
+
+ RtlCopyMemory( NewLinkName.Buffer,
+ NamePair.Short.Buffer,
+ NewLinkName.Length );
+ }
+
+ }
+
+ //
+ // Update the flags field in the target file name. We will use this
+ // below if we are updating the normalized name.
+ //
+
+ FileNameAttr->Flags = NewLinkNameFlags;
+
+ if (ParentScb != TargetParentScb) {
+
+ NtfsUpdateFcb( TargetParentScb->Fcb );
+ }
+
+ //
+ // If we need a full buffer for the new name for notify and don't already
+ // have one then construct the full name now. This will only happen if
+ // we are renaming within the same directory.
+ //
+
+ if (ReportDirNotify &&
+ (NewFullLinkName.Buffer == NULL)) {
+
+ NewFullLinkName.MaximumLength = Ccb->LastFileNameOffset + NewLinkName.Length;
+
+ NewFullLinkNameBuffer = NtfsAllocatePool( PagedPool,
+ NewFullLinkName.MaximumLength );
+
+ RtlCopyMemory( NewFullLinkNameBuffer,
+ Ccb->FullFileName.Buffer,
+ Ccb->LastFileNameOffset );
+
+ RtlCopyMemory( Add2Ptr( NewFullLinkNameBuffer, Ccb->LastFileNameOffset ),
+ NewLinkName.Buffer,
+ NewLinkName.Length );
+
+ NewFullLinkName.Buffer = NewFullLinkNameBuffer;
+ NewFullLinkName.Length = NewFullLinkName.MaximumLength;
+ }
+
+ FcbLinkCountAdj -= 1;
+ }
+
+ //
+ // We need to update the names in the Lcb for this file as well as any subdirectories
+ // or files. We will do this in two passes. The first pass is just to reserve enough
+ // space in all of the file objects and Lcb's. We update the names in the second pass.
+ //
+
+ if (FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ if (FlagOn( RenameFlags, REMOVE_TARGET_LINK )) {
+
+ SetFlag( RenameFlags, REMOVE_TRAVERSE_LINK );
+
+ } else {
+
+ SetFlag( RenameFlags, REUSE_TRAVERSE_LINK );
+ }
+ }
+
+ //
+ // If this is a directory and we added a target link it means that the
+ // normalized name has changed. Make sure the buffer in the Scb will hold
+ // the larger name.
+ //
+
+ if (IsDirectory( &Fcb->Info ) && FlagOn( RenameFlags, ADD_TARGET_LINK )) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ TargetParentScb,
+ Scb,
+ FileNameAttr,
+ TRUE );
+ }
+
+ //
+ // We have now modified the on-disk structures. We now need to
+ // modify the in-memory structures. This includes the Fcb and Lcb's
+ // for any links we superseded, and the source Fcb and it's Lcb's.
+ //
+ // We will do this in two passes. The first pass will guarantee that all of the
+ // name buffers will be large enough for the names. The second pass will store the
+ // names into the buffers.
+ //
+
+ if (FlagOn( RenameFlags, MOVE_TO_NEW_DIR )) {
+
+ NtfsMoveLinkToNewDir( IrpContext,
+ &NewFullLinkName,
+ &NewLinkName,
+ NewLinkNameFlags,
+ TargetParentScb,
+ Fcb,
+ Lcb,
+ RenameFlags,
+ &PrevLinkName,
+ PrevLinkNameFlags );
+
+ //
+ // Otherwise we will rename in the current directory. We need to remember
+ // if we have merged with an existing link on this file.
+ //
+
+ } else {
+
+ NtfsRenameLinkInDir( IrpContext,
+ ParentScb,
+ Fcb,
+ Lcb,
+ &NewLinkName,
+ NewLinkNameFlags,
+ RenameFlags,
+ &PrevLinkName,
+ PrevLinkNameFlags );
+ }
+
+ //
+ // Nothing should fail from this point forward.
+ //
+ // Now make the change to the normalized name. The buffer should be
+ // large enough.
+ //
+
+ if (IsDirectory( &Fcb->Info ) && FlagOn( RenameFlags, ADD_TARGET_LINK )) {
+
+ NtfsUpdateNormalizedName( IrpContext,
+ TargetParentScb,
+ Scb,
+ FileNameAttr,
+ FALSE );
+ }
+
+ //
+ // Now look at the link we superseded. If we deleted the file then go through and
+ // mark everything as deleted.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_TARGET_LINK | TRAVERSE_MATCH ) == REMOVE_TARGET_LINK) {
+
+ NtfsUpdateFcbFromLinkRemoval( IrpContext,
+ TargetParentScb,
+ TargetLinkFcb,
+ PrevLinkName,
+ PrevLinkNameFlags );
+
+ //
+ // If the link count is going to 0, we need to perform the work of
+ // removing the file.
+ //
+
+ if (TargetLinkFcb->LinkCount == 1) {
+
+ SetFlag( TargetLinkFcb->FcbState, FCB_STATE_FILE_DELETED );
+
+ //
+ // Remove this from the Fcb table if in it.
+ //
+
+ if (FlagOn( TargetLinkFcb->FcbState, FCB_STATE_IN_FCB_TABLE )) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ NtfsDeleteFcbTableEntry( Vcb, TargetLinkFcb->FileReference );
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ ClearFlag( TargetLinkFcb->FcbState, FCB_STATE_IN_FCB_TABLE );
+ }
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = TargetLinkFcb->ScbQueue.Flink;
+ Links != &TargetLinkFcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links,
+ SCB,
+ FcbLinks );
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+ }
+
+ //
+ // Change the time stamps in the parent if we modified the links in this directory.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
+
+ NtfsUpdateFcb( ParentScb->Fcb );
+ }
+
+ //
+ // We always set the last change time on the file we renamed unless
+ // the caller explicitly set this.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
+
+ //
+ // Report the changes to the affected directories. We defer reporting
+ // until now so that all of the on disk changes have been made.
+ // We have already preserved the original file name for any changes
+ // associated with it.
+ //
+ // Note that we may have to make a call to notify that we are removing
+ // a target if there is only a case change. This could make for
+ // a third notify call.
+ //
+ // Now that we have the new name we need to decide whether to report
+ // this as a change in the file or adding a file to a new directory.
+ //
+
+ if (ReportDirNotify) {
+
+ ULONG FilterMatch = 0;
+ ULONG Action;
+
+ //
+ // If we are deleting a target link in order to make a case change then
+ // report that.
+ //
+
+ if ((PrevFullLinkName.Buffer != NULL) &&
+ FlagOn( RenameFlags,
+ OVERWRITE_SOURCE_LINK | REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == REMOVE_TARGET_LINK) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &PrevFullLinkName,
+ PrevFullLinkName.Length - PrevLinkName.Length,
+ NULL,
+ (TargetParentScb->ScbType.Index.NormalizedName.Buffer != NULL ?
+ &TargetParentScb->ScbType.Index.NormalizedName :
+ NULL),
+ (IsDirectory( &TargetLinkFcb->Info ) ?
+ FILE_NOTIFY_CHANGE_DIR_NAME :
+ FILE_NOTIFY_CHANGE_FILE_NAME),
+ FILE_ACTION_REMOVED,
+ TargetParentScb->Fcb );
+ }
+
+ //
+ // If we stored the original name then we report the changes
+ // associated with it.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_SOURCE_LINK )) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &SourceFullLinkName,
+ SourceLinkLastNameOffset,
+ NULL,
+ (ParentScb->ScbType.Index.NormalizedName.Buffer != NULL ?
+ &ParentScb->ScbType.Index.NormalizedName :
+ NULL),
+ (IsDirectory( &Fcb->Info ) ?
+ FILE_NOTIFY_CHANGE_DIR_NAME :
+ FILE_NOTIFY_CHANGE_FILE_NAME),
+ ((FlagOn( RenameFlags, MOVE_TO_NEW_DIR ) ||
+ !FlagOn( RenameFlags, ADD_TARGET_LINK ) ||
+ (FlagOn( RenameFlags, REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == (REMOVE_TARGET_LINK | EXACT_CASE_MATCH))) ?
+ FILE_ACTION_REMOVED :
+ FILE_ACTION_RENAMED_OLD_NAME),
+ ParentScb->Fcb );
+ }
+
+ //
+ // Check if a new name will appear in the directory.
+ //
+
+ if (!FoundLink ||
+ (FlagOn( RenameFlags, OVERWRITE_SOURCE_LINK | EXACT_CASE_MATCH) == OVERWRITE_SOURCE_LINK) ||
+ (FlagOn( RenameFlags, REMOVE_TARGET_LINK | EXACT_CASE_MATCH ) == REMOVE_TARGET_LINK)) {
+
+ FilterMatch = IsDirectory( &Fcb->Info)
+ ? FILE_NOTIFY_CHANGE_DIR_NAME
+ : FILE_NOTIFY_CHANGE_FILE_NAME;
+
+ //
+ // If we moved to a new directory, remember the
+ // action was a create operation.
+ //
+
+ if (FlagOn( RenameFlags, MOVE_TO_NEW_DIR )) {
+
+ Action = FILE_ACTION_ADDED;
+
+ } else {
+
+ Action = FILE_ACTION_RENAMED_NEW_NAME;
+ }
+
+ //
+ // There was an entry with the same case. If this isn't the
+ // same file then we report a change to all the file attributes.
+ //
+
+ } else if (FlagOn( RenameFlags, REMOVE_TARGET_LINK | TRAVERSE_MATCH ) == REMOVE_TARGET_LINK) {
+
+ FilterMatch = (FILE_NOTIFY_CHANGE_ATTRIBUTES |
+ FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE |
+ FILE_NOTIFY_CHANGE_LAST_ACCESS |
+ FILE_NOTIFY_CHANGE_CREATION |
+ FILE_NOTIFY_CHANGE_SECURITY |
+ FILE_NOTIFY_CHANGE_EA);
+
+ //
+ // The file name isn't changing, only the properties of the
+ // file.
+ //
+
+ Action = FILE_ACTION_MODIFIED;
+ }
+
+ if (FilterMatch != 0) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &NewFullLinkName,
+ NewFullLinkName.Length - NewLinkName.Length,
+ NULL,
+ (TargetParentScb->ScbType.Index.NormalizedName.Buffer != NULL ?
+ &TargetParentScb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ Action,
+ TargetParentScb->Fcb );
+ }
+ }
+
+ //
+ // Now adjust the link counts on the different files.
+ //
+
+ if (TargetLinkFcb != NULL) {
+
+ TargetLinkFcb->LinkCount -= TargetLinkFcbCountAdj;
+ TargetLinkFcb->TotalLinks -= TargetLinkFcbCountAdj;
+
+ //
+ // Now go through and mark everything as deleted.
+ //
+
+ if (TargetLinkFcb->LinkCount == 0) {
+
+ SetFlag( TargetLinkFcb->FcbState, FCB_STATE_FILE_DELETED );
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = TargetLinkFcb->ScbQueue.Flink;
+ Links != &TargetLinkFcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+
+ ThisScb->ValidDataToDisk =
+ ThisScb->Header.AllocationSize.QuadPart =
+ ThisScb->Header.FileSize.QuadPart =
+ ThisScb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+ }
+ }
+
+ Fcb->TotalLinks -= FcbLinkCountAdj;
+ Fcb->LinkCount -= FcbLinkCountAdj;
+
+ } finally {
+
+ DebugUnwind( NtfsSetRenameInfo );
+
+ if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); }
+ if (ResourceToRelease != NULL) { ExReleaseResource( ResourceToRelease ); }
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ //
+ // If we allocated any buffers for the notify operations deallocate them now.
+ //
+
+ if (NewFullLinkNameBuffer != NULL) { NtfsFreePool( NewFullLinkNameBuffer ); }
+ if (PrevFullLinkName.Buffer != NULL) { NtfsFreePool( PrevFullLinkName.Buffer ); }
+ if (SourceFullLinkName.Buffer != NULL) {
+
+ NtfsFreePool( SourceFullLinkName.Buffer );
+ }
+
+ //
+ // If we allocated a buffer for the tunneled names, deallocate them now.
+ //
+
+ if (NamePair.Long.Buffer != NamePair.LongBuffer) {
+
+ NtfsFreePool( NamePair.Long.Buffer );
+ }
+
+ //
+ // If we allocated a file name attribute, we deallocate it now.
+ //
+
+ if (FileNameAttr != NULL) { NtfsFreePool( FileNameAttr ); }
+
+ //
+ // Some cleanup only occurs if this request has not been posted to the oplock package.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ if (AcquiredTargetLinkFcb) {
+
+ NtfsTeardownStructures( IrpContext,
+ TargetLinkFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsSetRenameInfo: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetLinkInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set link function. It will create a new link for a
+ file.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+ Vcb - Supplies the Vcb for the Volume
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - Supplies the Ccb for this file object
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ PLCB Lcb = Ccb->Lcb;
+ PFCB Fcb = Scb->Fcb;
+ PSCB ParentScb = NULL;
+ SHORT LinkCountAdj = 0;
+
+ UNICODE_STRING NewLinkName;
+ UNICODE_STRING NewFullLinkName;
+ PWCHAR NewFullLinkNameBuffer = NULL;
+ PFILE_NAME NewLinkNameAttr = NULL;
+ USHORT NewLinkNameAttrLength = 0;
+ UCHAR NewLinkNameFlags;
+
+ PSCB TargetParentScb;
+ PFILE_OBJECT TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
+
+ BOOLEAN FoundPrevLink;
+ UNICODE_STRING PrevLinkName;
+ UNICODE_STRING PrevFullLinkName;
+ UCHAR PrevLinkNameFlags;
+ USHORT PrevFcbLinkCountAdj = 0;
+ BOOLEAN ExistingPrevFcb = FALSE;
+ PFCB PreviousFcb = NULL;
+
+ ULONG RenameFlags = 0;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+ PERESOURCE ResourceToRelease = NULL;
+
+ BOOLEAN ReportDirNotify = FALSE;
+ PWCHAR NextChar;
+
+ PINDEX_ENTRY IndexEntry;
+ PBCB IndexEntryBcb = NULL;
+
+ PLIST_ENTRY Links;
+ PSCB ThisScb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetLinkInfo...\n") );
+
+ PrevFullLinkName.Buffer = NULL;
+
+ //
+ // If we are not opening the entire file, we can't set link info.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // We also fail this if we are attempting to create a link on a directory.
+ // This will prevent cycles from being created.
+ //
+
+ if (FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT)) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", STATUS_FILE_IS_A_DIRECTORY) );
+ return STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ //
+ // We can't add a link without having a parent directory. Either we want to use the same
+ // parent or our caller supplied a parent.
+ //
+
+ if (Lcb == NULL) {
+
+ //
+ // If there is no target file object, then we can't add a link.
+ //
+
+ if (TargetFileObject == NULL) {
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: No target file object -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ } else {
+
+ ParentScb = Lcb->Scb;
+ }
+
+ //
+ // If this link has been deleted, then we don't allow this operation.
+ //
+
+ if ((Lcb != NULL) && LcbLinkIsDeleted( Lcb )) {
+
+ Status = STATUS_ACCESS_DENIED;
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Check if we are allowed to perform this link operation. We can't if this
+ // is a system file or the user hasn't opened the entire file. We
+ // don't need to check for the root explicitly since it is one of
+ // the system files.
+ //
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) < FIRST_USER_FILE_NUMBER) {
+
+ Status = STATUS_INVALID_PARAMETER;
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
+ return Status;
+ }
+
+ //
+ // Verify that we can wait.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ if (Status == STATUS_PENDING) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Can't wait\n") );
+ return Status;
+ }
+
+ //
+ // Check if we will want to report this via the dir notify package.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
+ (Ccb->FullFileName.Buffer[0] == L'\\') &&
+ (Vcb->NotifyCount != 0)) {
+
+ ReportDirNotify = TRUE;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // We now assemble the names and in memory-structures for both the
+ // source and target links and check if the target link currently
+ // exists.
+ //
+
+ NtfsFindTargetElements( IrpContext,
+ TargetFileObject,
+ ParentScb,
+ &TargetParentScb,
+ &NewFullLinkName,
+ &NewLinkName );
+
+ //
+ // Check that the new name is not invalid.
+ //
+
+ if ((NewLinkName.Length > (NTFS_MAX_FILE_NAME_LENGTH * sizeof( WCHAR ))) ||
+ !NtfsIsFileNameValid( &NewLinkName, FALSE )) {
+
+ Status = STATUS_OBJECT_NAME_INVALID;
+ leave;
+ }
+
+ if (TargetParentScb == ParentScb) {
+
+ //
+ // Acquire the target parent in order to synchronize adding a link.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+
+ //
+ // Check if we are creating a link to the same directory with the
+ // exact same name.
+ //
+
+ if (NtfsAreNamesEqual( Vcb->UpcaseTable,
+ &NewLinkName,
+ &Lcb->ExactCaseLink.LinkName,
+ FALSE )) {
+
+ DebugTrace( 0, Dbg, ("Creating link to same name and directory\n") );
+ Status = STATUS_SUCCESS;
+ leave;
+ }
+
+ //
+ // Make sure the normalized name is in this Scb.
+ //
+
+ if ((ParentScb->ScbType.Index.NormalizedName.Buffer == NULL) ||
+ (ParentScb->ScbType.Index.NormalizedName.Length == 0)) {
+
+ NtfsBuildNormalizedName( IrpContext,
+ ParentScb,
+ &ParentScb->ScbType.Index.NormalizedName );
+ }
+
+ //
+ // Otherwise we remember that we are creating this link in a new directory.
+ //
+
+ } else {
+
+ SetFlag( RenameFlags, CREATE_IN_NEW_DIR );
+
+ //
+ // We know that we need to acquire the target directory so we can
+ // add and remove links. We want to carefully acquire the Scb in the
+ // event we only have the Vcb shared.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext,
+ TargetParentScb->Fcb,
+ TargetParentScb,
+ FALSE,
+ TRUE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Now snapshot the Scb.
+ //
+
+ if (FlagOn( TargetParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ NtfsSnapshotScb( IrpContext, TargetParentScb );
+ }
+
+ } else {
+
+ NtfsAcquireExclusiveScb( IrpContext, TargetParentScb );
+ }
+ }
+
+ //
+ // If we are exceeding the maximum link count on this file then return
+ // an error. There isn't a descriptive error code to use at this time.
+ //
+
+ if (Fcb->TotalLinks >= NTFS_MAX_LINK_COUNT) {
+
+ Status = STATUS_TOO_MANY_LINKS;
+ leave;
+ }
+
+ //
+ // Lookup the entry for this filename in the target directory.
+ // We look in the Ccb for the type of case match for the target
+ // name.
+ //
+
+ FoundPrevLink = NtfsLookupEntry( IrpContext,
+ TargetParentScb,
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
+ &NewLinkName,
+ &NewLinkNameAttr,
+ &NewLinkNameAttrLength,
+ NULL,
+ &IndexEntry,
+ &IndexEntryBcb );
+
+ //
+ // If we found a matching link, we need to check how we want to operate
+ // on the source link and the target link. This means whether we
+ // have any work to do, whether we need to remove the target link
+ // and whether we need to remove the source link.
+ //
+
+ if (FoundPrevLink) {
+
+ PFILE_NAME IndexFileName;
+
+ IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
+
+ //
+ // If the file references match, we are trying to create a
+ // link where one already exists.
+ //
+
+ if (NtfsCheckLinkForNewLink( Fcb,
+ IndexFileName,
+ IndexEntry->FileReference,
+ &NewLinkName,
+ &RenameFlags )) {
+
+ //
+ // There is no work to do.
+ //
+
+ Status = STATUS_SUCCESS;
+ leave;
+ }
+
+ //
+ // We need to check that the user wanted to remove that link.
+ //
+
+ if (!IrpSp->Parameters.SetFile.ReplaceIfExists) {
+
+ Status = STATUS_OBJECT_NAME_COLLISION;
+ leave;
+ }
+
+ //
+ // We want to preserve the case and the flags of the matching
+ // target link. We also want to preserve the case of the
+ // name being created. The following variables currently contain
+ // the exact case for the target to remove and the new name to
+ // apply.
+ //
+ // Link to remove - In 'IndexEntry'.
+ // The links flags are also in 'IndexEntry'. We copy
+ // these flags to 'PrevLinkNameFlags'
+ //
+ // New Name - Exact case is stored in 'NewLinkName'
+ // - Exact case is also stored in 'NewLinkNameAttr'
+ //
+ // We modify this so that we can use the FileName attribute
+ // structure to create the new link. We copy the linkname being
+ // removed into 'PrevLinkName'. The following is the
+ // state after the switch.
+ //
+ // 'NewLinkNameAttr' - contains the name for the link being
+ // created.
+ //
+ // 'PrevLinkName' - Contains the link name for the link being
+ // removed.
+ //
+ // 'PrevLinkNameFlags' - Contains the name flags for the link
+ // being removed.
+ //
+
+ //
+ // Remember the file name flags for the match being made.
+ //
+
+ PrevLinkNameFlags = IndexFileName->Flags;
+
+ //
+ // If we are report this via dir notify then build the full name.
+ // Otherwise just remember the last name.
+ //
+
+ if (ReportDirNotify) {
+
+ PrevFullLinkName.MaximumLength =
+ PrevFullLinkName.Length = (ParentScb->ScbType.Index.NormalizedName.Length +
+ sizeof( WCHAR ) +
+ NewLinkName.Length);
+
+ PrevFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
+ PrevFullLinkName.MaximumLength );
+
+ RtlCopyMemory( PrevFullLinkName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Length );
+
+ NextChar = Add2Ptr( PrevFullLinkName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Length );
+
+ if (ParentScb->ScbType.Index.NormalizedName.Length != sizeof( WCHAR )) {
+
+
+ *NextChar = L'\\';
+ NextChar += 1;
+
+ } else {
+
+ PrevFullLinkName.Length -= sizeof( WCHAR );
+ }
+
+ PrevLinkName.Buffer = NextChar;
+
+ } else {
+
+ PrevLinkName.Buffer = NtfsAllocatePool( PagedPool, NewLinkName.Length );
+ }
+
+ //
+ // Copy the name found in the Index Entry to 'PrevLinkName'
+ //
+
+ PrevLinkName.Length =
+ PrevLinkName.MaximumLength = NewLinkName.Length;
+
+ RtlCopyMemory( PrevLinkName.Buffer,
+ IndexFileName->FileName,
+ NewLinkName.Length );
+
+ //
+ // We only need this check if the existing link is for a different file.
+ //
+
+ if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ //
+ // We check if there is an existing Fcb for the target link.
+ // If there is, the unclean count better be 0.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ PreviousFcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ IndexEntry->FileReference,
+ FALSE,
+ BooleanFlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ),
+ &ExistingPrevFcb );
+
+ //
+ // We need to acquire this file carefully in the event that we don't hold
+ // the Vcb exclusively.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX )) {
+
+ if (PreviousFcb->PagingIoResource != NULL) {
+
+ if (!ExAcquireResourceExclusive( PreviousFcb->PagingIoResource, FALSE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ ResourceToRelease = PreviousFcb->PagingIoResource;
+ }
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, FALSE, TRUE )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ } else {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+
+ //
+ // Acquire the paging Io resource for this file before the main
+ // resource in case we need to delete.
+ //
+
+ if (PreviousFcb->PagingIoResource != NULL) {
+ ResourceToRelease = PreviousFcb->PagingIoResource;
+ ExAcquireResourceExclusive( ResourceToRelease, TRUE );
+ }
+
+ NtfsAcquireExclusiveFcb( IrpContext, PreviousFcb, NULL, FALSE, FALSE );
+ }
+
+ //
+ // If the Fcb Info field needs to be initialized, we do so now.
+ // We read this information from the disk as the duplicate information
+ // in the index entry is not guaranteed to be correct.
+ //
+
+ if (!FlagOn( PreviousFcb->FcbState, FCB_STATE_DUP_INITIALIZED )) {
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext,
+ TRUE,
+ PreviousFcb,
+ TargetParentScb->Fcb,
+ NULL );
+
+ NtfsConditionallyFixupQuota( IrpContext, PreviousFcb );
+ }
+
+ //
+ // We are adding a link to the source file which already
+ // exists as a link to a different file in the target directory.
+ //
+ // We need to check whether we permitted to delete this
+ // link. If not then it is possible that the problem is
+ // an existing batch oplock on the file. In that case
+ // we want to delete the batch oplock and try this again.
+ //
+
+ Status = NtfsCheckFileForDelete( IrpContext,
+ TargetParentScb,
+ PreviousFcb,
+ ExistingPrevFcb,
+ IndexEntry );
+
+ if (!NT_SUCCESS( Status )) {
+
+ PSCB NextScb = NULL;
+
+ //
+ // We are going to either fail this request or pass
+ // this on to the oplock package. Test if there is
+ // a batch oplock on any streams on this file.
+ //
+
+ while ((NextScb = NtfsGetNextChildScb( PreviousFcb,
+ NextScb )) != NULL) {
+
+ if ((NextScb->AttributeTypeCode == $DATA) &&
+ (NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
+ FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
+
+ //
+ // Go ahead and perform any necessary cleanup now.
+ // Once we call the oplock package below we lose
+ // control of the IrpContext.
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ Status = FsRtlCheckOplock( &NextScb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsPrePostIrp );
+
+ break;
+ }
+ }
+
+ leave;
+ }
+
+ //
+ // We are adding a link to the source file which already
+ // exists as a link to a different file in the target directory.
+ //
+
+ NtfsCleanupLinkForRemoval( PreviousFcb,
+ ExistingPrevFcb );
+
+ //
+ // If the link count on this file is 1, then delete the file. Otherwise just
+ // delete the link.
+ //
+
+ if (PreviousFcb->LinkCount == 1) {
+
+ NtfsDeleteFile( IrpContext,
+ PreviousFcb,
+ TargetParentScb,
+ NULL );
+
+ PrevFcbLinkCountAdj += 1;
+
+ } else {
+
+ NtfsRemoveLink( IrpContext,
+ PreviousFcb,
+ TargetParentScb,
+ PrevLinkName,
+ NULL );
+
+ PrevFcbLinkCountAdj += 1;
+ NtfsUpdateFcb( PreviousFcb );
+ }
+
+ //
+ // Otherwise we need to remove this link as our caller wants to replace it
+ // with a different case.
+ //
+
+ } else {
+
+ NtfsRemoveLink( IrpContext,
+ Fcb,
+ TargetParentScb,
+ PrevLinkName,
+ NULL );
+
+ PreviousFcb = Fcb;
+ LinkCountAdj += 1;
+ }
+ }
+
+ //
+ // Make sure we have the full name of the target if we will be reporting
+ // this.
+ //
+
+ if (ReportDirNotify && (NewFullLinkName.Buffer == NULL)) {
+
+ NewFullLinkName.MaximumLength =
+ NewFullLinkName.Length = (ParentScb->ScbType.Index.NormalizedName.Length +
+ sizeof( WCHAR ) +
+ NewLinkName.Length);
+
+ NewFullLinkNameBuffer =
+ NewFullLinkName.Buffer = NtfsAllocatePool( PagedPool,
+ NewFullLinkName.MaximumLength );
+
+ RtlCopyMemory( NewFullLinkName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Length );
+
+ NextChar = Add2Ptr( NewFullLinkName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Length );
+
+ if (ParentScb->ScbType.Index.NormalizedName.Length != sizeof( WCHAR )) {
+
+ *NextChar = L'\\';
+ NextChar += 1;
+
+ } else {
+
+ NewFullLinkName.Length -= sizeof( WCHAR );
+ }
+
+ RtlCopyMemory( NextChar,
+ NewLinkName.Buffer,
+ NewLinkName.Length );
+ }
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ //
+ // Check that we have permission to add a file to this directory.
+ //
+
+ NtfsCheckIndexForAddOrDelete( IrpContext,
+ TargetParentScb->Fcb,
+ FILE_ADD_FILE );
+
+ //
+ // We always set the last change time on the file we renamed unless
+ // the caller explicitly set this.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_UPDATE_LAST_CHANGE );
+
+ //
+ // We now want to add the new link into the target directory.
+ // We never create a primary link through the link operation although
+ // we can remove one.
+ //
+
+ NtfsAddLink( IrpContext,
+ FALSE,
+ TargetParentScb,
+ Fcb,
+ NewLinkNameAttr,
+ NULL,
+ &NewLinkNameFlags,
+ NULL,
+ NULL );
+
+ LinkCountAdj -= 1;
+ NtfsUpdateFcb( TargetParentScb->Fcb );
+
+ //
+ // Now we want to update the Fcb for the link we renamed. If we moved it
+ // to a new directory we need to move all the Lcb's associated with
+ // the previous link.
+ //
+
+ if (FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ NtfsReplaceLinkInDir( IrpContext,
+ TargetParentScb,
+ Fcb,
+ &NewLinkName,
+ NewLinkNameFlags,
+ &PrevLinkName,
+ PrevLinkNameFlags );
+ }
+
+ //
+ // We have now modified the on-disk structures. We now need to
+ // modify the in-memory structures. This includes the Fcb and Lcb's
+ // for any links we superseded, and the source Fcb and it's Lcb's.
+ //
+ // We start by looking at the link we superseded. We know the
+ // the target directory, link name and flags, and the file the
+ // link was connected to.
+ //
+
+ if (FoundPrevLink && !FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ NtfsUpdateFcbFromLinkRemoval( IrpContext,
+ TargetParentScb,
+ PreviousFcb,
+ PrevLinkName,
+ PrevLinkNameFlags );
+ }
+
+ //
+ // We have three cases to report for changes in the target directory..
+ //
+ // 1. If we overwrote an existing link to a different file, we
+ // report this as a modified file.
+ //
+ // 2. If we moved a link to a new directory, then we added a file.
+ //
+ // 3. If we renamed a link in in the same directory, then we report
+ // that there is a new name.
+ //
+ // We currently combine cases 2 and 3.
+ //
+
+ if (ReportDirNotify) {
+
+ ULONG FilterMatch = 0;
+ ULONG FileAction;
+
+ //
+ // If we removed an entry and it wasn't an exact case match, then
+ // report the entry which was removed.
+ //
+
+ if (!FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
+
+ if (FoundPrevLink) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &PrevFullLinkName,
+ PrevFullLinkName.Length - PrevLinkName.Length,
+ NULL,
+ &TargetParentScb->ScbType.Index.NormalizedName,
+ (IsDirectory( &PreviousFcb->Info ) ?
+ FILE_NOTIFY_CHANGE_DIR_NAME :
+ FILE_NOTIFY_CHANGE_FILE_NAME),
+ FILE_ACTION_REMOVED,
+ TargetParentScb->Fcb );
+ }
+
+ //
+ // We will be adding an entry.
+ //
+
+ FilterMatch = FILE_NOTIFY_CHANGE_FILE_NAME;
+ FileAction = FILE_ACTION_ADDED;
+
+ //
+ // If this was not a traverse match then report that all the file
+ // properties changed.
+ //
+
+ } else if (!FlagOn( RenameFlags, TRAVERSE_MATCH )) {
+
+ FilterMatch |= (FILE_NOTIFY_CHANGE_ATTRIBUTES |
+ FILE_NOTIFY_CHANGE_SIZE |
+ FILE_NOTIFY_CHANGE_LAST_WRITE |
+ FILE_NOTIFY_CHANGE_LAST_ACCESS |
+ FILE_NOTIFY_CHANGE_CREATION |
+ FILE_NOTIFY_CHANGE_SECURITY |
+ FILE_NOTIFY_CHANGE_EA);
+
+ FileAction = FILE_ACTION_MODIFIED;
+ }
+
+ if (FilterMatch != 0) {
+
+ NtfsReportDirNotify( IrpContext,
+ Vcb,
+ &NewFullLinkName,
+ NewFullLinkName.Length - NewLinkName.Length,
+ NULL,
+ &TargetParentScb->ScbType.Index.NormalizedName,
+ FilterMatch,
+ FileAction,
+ TargetParentScb->Fcb );
+ }
+ }
+
+ //
+ // Adjust the link counts on the files.
+ //
+
+ Fcb->TotalLinks = (SHORT) Fcb->TotalLinks - LinkCountAdj;
+ Fcb->LinkCount = (SHORT) Fcb->LinkCount - LinkCountAdj;
+
+ //
+ // We can now adjust the total link count on the previous Fcb.
+ //
+
+ if (PreviousFcb != NULL) {
+
+ PreviousFcb->TotalLinks -= PrevFcbLinkCountAdj;
+ PreviousFcb->LinkCount -= PrevFcbLinkCountAdj;
+
+ //
+ // Now go through and mark everything as deleted.
+ //
+
+ if (PreviousFcb->LinkCount == 0) {
+
+ SetFlag( PreviousFcb->FcbState, FCB_STATE_FILE_DELETED );
+
+#ifdef _CAIRO_
+
+ //
+ // Release the quota control block. This does not have to be done
+ // here however, it allows us to free up the quota control block
+ // before the fcb is removed from the table. This keeps the assert
+ // about quota table empty from triggering in
+ // NtfsClearAndVerifyQuotaIndex.
+ //
+
+ if (NtfsPerformQuotaOperation(PreviousFcb)) {
+ NtfsDereferenceQuotaControlBlock( Vcb,
+ &PreviousFcb->QuotaControl );
+ }
+
+#endif // _CAIRO_
+
+ //
+ // We need to mark all of the Scbs as gone.
+ //
+
+ for (Links = PreviousFcb->ScbQueue.Flink;
+ Links != &PreviousFcb->ScbQueue;
+ Links = Links->Flink) {
+
+ ThisScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ if (!FlagOn( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsSnapshotScb( IrpContext, ThisScb );
+
+ ThisScb->ValidDataToDisk =
+ ThisScb->Header.AllocationSize.QuadPart =
+ ThisScb->Header.FileSize.QuadPart =
+ ThisScb->Header.ValidDataLength.QuadPart = 0;
+
+ SetFlag( ThisScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ }
+ }
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsSetLinkInfo );
+
+ if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Vcb ); }
+
+ //
+ // If we allocated any buffers for name storage then deallocate them now.
+ //
+
+ if (PrevFullLinkName.Buffer != NULL) { NtfsFreePool( PrevFullLinkName.Buffer ); }
+ if (NewFullLinkNameBuffer != NULL) { NtfsFreePool( NewFullLinkNameBuffer ); }
+
+ //
+ // Release any paging io resource acquired.
+ //
+
+ if (ResourceToRelease != NULL) { ExReleaseResource( ResourceToRelease ); }
+
+ //
+ // If we allocated a file name attribute, we deallocate it now.
+ //
+
+ if (NewLinkNameAttr != NULL) { NtfsFreePool( NewLinkNameAttr ); }
+
+ //
+ // If we have the Fcb for a removed link and it didn't previously
+ // exist, call our teardown routine.
+ //
+
+ if (Status != STATUS_PENDING) {
+
+ if ((PreviousFcb != NULL) &&
+ (PreviousFcb->CleanupCount == 0)) {
+
+ NtfsTeardownStructures( IrpContext,
+ PreviousFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+ }
+
+ NtfsUnpinBcb( &IndexEntryBcb );
+
+ DebugTrace( -1, Dbg, ("NtfsSetLinkInfo: Exit -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetPositionInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set position information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ PFILE_POSITION_INFORMATION Buffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetPositionInfo...\n") );
+
+ //
+ // Reference the system buffer containing the user specified position
+ // information record
+ //
+
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ try {
+
+ //
+ // Check if the file does not use intermediate buffering. If it does
+ // not use intermediate buffering then the new position we're supplied
+ // must be aligned properly for the device
+ //
+
+ if (FlagOn( FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING )) {
+
+ PDEVICE_OBJECT DeviceObject;
+
+ DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
+
+ if ((Buffer->CurrentByteOffset.LowPart & DeviceObject->AlignmentRequirement) != 0) {
+
+ DebugTrace( 0, Dbg, ("Offset missaligned %08lx %08lx\n", Buffer->CurrentByteOffset.LowPart, Buffer->CurrentByteOffset.HighPart) );
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+ }
+
+ //
+ // Set the new current byte offset in the file object
+ //
+
+ FileObject->CurrentByteOffset = Buffer->CurrentByteOffset;
+
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsSetPositionInfo );
+
+ NOTHING;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSetPositionInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetAllocationInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set allocation information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - This is the Scb for the open operation. May not be present if
+ this is a Mm call.
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PFCB Fcb = Scb->Fcb;
+ BOOLEAN NonResidentPath = FALSE;
+ BOOLEAN UpdateCacheManager = FALSE;
+ BOOLEAN ClearCheckSizeFlag = FALSE;
+
+ LONGLONG NewAllocationSize;
+ LONGLONG PrevAllocationSize;
+
+ LONGLONG CurrentTime;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN CleanupAttrContext = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetAllocationInfo...\n") );
+
+ //
+ // If this attribute has been 'deleted' then we we can return immediately
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ Status = STATUS_SUCCESS;
+
+ DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo: Attribute is already deleted\n") );
+
+ return Status;
+ }
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Save the current state of the Scb.
+ //
+
+ NtfsSnapshotScb( IrpContext, Scb );
+
+ //
+ // Get the new allocation size.
+ //
+
+ NewAllocationSize = ((PFILE_ALLOCATION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->AllocationSize.QuadPart;
+ PrevAllocationSize = Scb->Header.AllocationSize.QuadPart;
+
+ //
+ // If we will be decreasing file size, grab the paging io resource
+ // exclusive, if there is one.
+ //
+
+ if (NewAllocationSize < Scb->Header.FileSize.QuadPart) {
+
+ //
+ // Check if there is a user mapped file which could prevent truncation.
+ //
+
+ if (!MmCanFileBeTruncated( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&NewAllocationSize )) {
+
+ Status = STATUS_USER_MAPPED_FILE;
+ DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo -> %08lx\n", Status) );
+
+ return Status;
+ }
+ }
+
+ //
+ // Use a try-finally so we can update the on disk time-stamps.
+ //
+
+ try {
+
+ //
+ // If the caller is extending the allocation of resident attribute then
+ // we will force it to become non-resident. This solves the problem of
+ // trying to keep the allocation and file sizes in sync with only one
+ // number to use in the attribute header.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ //
+ // Convert if extending.
+ //
+
+ if (NewAllocationSize > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsConvertToNonresident( IrpContext,
+ Fcb,
+ NtfsFoundAttribute( &AttrContext ),
+ BooleanFlagOn(Irp->Flags, IRP_PAGING_IO),
+ &AttrContext );
+
+ NonResidentPath = TRUE;
+
+ //
+ // Otherwise the allocation is shrinking or staying the same.
+ //
+
+ } else {
+
+ NewAllocationSize = QuadAlign( (ULONG) NewAllocationSize );
+
+ //
+ // If the allocation size doesn't change, we are done.
+ //
+
+ if ((ULONG) NewAllocationSize == Scb->Header.AllocationSize.LowPart) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // We are sometimes called by MM during a create section, so
+ // for right now the best way we have of detecting a create
+ // section is IRP_PAGING_IO being set, as in FsRtlSetFileSizes.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ (ULONG) NewAllocationSize,
+ NULL,
+ 0,
+ TRUE,
+ FALSE,
+ BooleanFlagOn(Irp->Flags, IRP_PAGING_IO),
+ FALSE,
+ &AttrContext );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ CleanupAttrContext = FALSE;
+
+ //
+ // Now update the sizes in the Scb.
+ //
+
+ Scb->Header.AllocationSize.LowPart =
+ Scb->Header.FileSize.LowPart =
+ Scb->Header.ValidDataLength.LowPart = (ULONG) NewAllocationSize;
+
+ Scb->TotalAllocated = NewAllocationSize;
+
+ //
+ // Remember to update the cache manager.
+ //
+
+ UpdateCacheManager = TRUE;
+ }
+
+ } else {
+
+ NonResidentPath = TRUE;
+ }
+
+ //
+ // We now test if we need to modify the non-resident allocation. We will
+ // do this in two cases. Either we're converting from resident in
+ // two steps or the attribute was initially non-resident.
+ //
+
+ if (NonResidentPath) {
+
+ NewAllocationSize = LlClustersFromBytes( Scb->Vcb, NewAllocationSize );
+ NewAllocationSize = LlBytesFromClusters( Scb->Vcb, NewAllocationSize );
+
+
+ DebugTrace( 0, Dbg, ("NewAllocationSize -> %016I64x\n", NewAllocationSize) );
+
+ //
+ // Now if the file allocation is being increased then we need to only add allocation
+ // to the attribute
+ //
+
+ if (Scb->Header.AllocationSize.QuadPart < NewAllocationSize) {
+
+ NtfsAddAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ),
+ LlClustersFromBytes( Scb->Vcb, NewAllocationSize - Scb->Header.AllocationSize.QuadPart ),
+ FALSE );
+
+ //
+ // Set the truncate on close flag.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ //
+ // Otherwise delete the allocation as requested.
+ //
+
+ } else if (Scb->Header.AllocationSize.QuadPart > NewAllocationSize) {
+
+ NtfsDeleteAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes( Scb->Vcb, NewAllocationSize ),
+ MAXLONGLONG,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // If this is the paging file then guarantee that the Mcb is fully loaded.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ NtfsPreloadAllocation( IrpContext,
+ Scb,
+ 0,
+ LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ));
+ }
+ }
+
+ try_exit:
+
+ if (PrevAllocationSize != Scb->Header.AllocationSize.QuadPart) {
+
+ //
+ // Mark this file object as modified and with a size change in order to capture
+ // all of the changes to the Fcb.
+ //
+
+ SetFlag( FileObject->Flags, FO_FILE_SIZE_CHANGED );
+ ClearCheckSizeFlag = TRUE;
+ }
+
+ //
+ // Always set the file as modified to force a time stamp change.
+ //
+
+ if (ARGUMENT_PRESENT( Ccb )) {
+
+ SetFlag( Ccb->Flags,
+ (CCB_FLAG_UPDATE_LAST_MODIFY |
+ CCB_FLAG_UPDATE_LAST_CHANGE |
+ CCB_FLAG_SET_ARCHIVE) );
+
+ } else {
+
+ SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
+ }
+
+ //
+ // Now capture any file size changes in this file object back to the Fcb.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // Update the standard information if required.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ }
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ //
+ // We know we wrote out any changes to the file size above so clear the
+ // flag in the Scb to check the attribute size. This will save us from doing
+ // this unnecessarily at cleanup.
+ //
+
+ if (ClearCheckSizeFlag) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Update duplicated information.
+ //
+
+ NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
+
+ //
+ // Update the cache manager if needed.
+ //
+
+ if (UpdateCacheManager) {
+
+ //
+ // We want to checkpoint the transaction if there is one active.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ }
+
+ //
+ // It is extremely expensive to make this call on a file that is not
+ // cached, and Ntfs has suffered stack overflows in addition to massive
+ // time and disk I/O expense (CcZero data on user mapped files!). Therefore,
+ // if no one has the file cached, we cache it here to make this call cheaper.
+ //
+ // Don't create the stream file if called from kernel mode in case
+ // mm is in the process of creating a section.
+ //
+
+ if (!CcIsFileCached(FileObject) &&
+ !FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) &&
+ !FlagOn(Irp->Flags, IRP_PAGING_IO)) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+ }
+
+ //
+ // Only call if the file is cached now, because the other case
+ // may cause recursion in write!
+
+ if (CcIsFileCached(FileObject)) {
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+
+ //
+ // Clear out the write mask on truncates to zero.
+ //
+
+#ifdef SYSCACHE
+ if ((Scb->Header.FileSize.QuadPart == 0) && FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE) &&
+ (Scb->ScbType.Data.WriteMask != NULL)) {
+ RtlZeroMemory(Scb->ScbType.Data.WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
+ }
+#endif
+
+ //
+ // Now cleanup the stream we created if there are no more user
+ // handles.
+ //
+
+ if ((Scb->CleanupCount == 0) && (Scb->FileObject != NULL)) {
+ NtfsDeleteInternalAttributeStream( Scb, FALSE );
+ }
+ }
+
+ Status = STATUS_SUCCESS;
+
+ } finally {
+
+ DebugUnwind( NtfsSetAllocation );
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSetAllocationInfo -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetEndOfFileInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN PCCB Ccb OPTIONAL,
+ IN BOOLEAN VcbAcquired
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the set end of file information function.
+
+Arguments:
+
+ FileObject - Supplies the file object being processed
+
+ Irp - Supplies the Irp being processed
+
+ Scb - Supplies the Scb for the file/directory being modified
+
+ Ccb - Supplies the Ccb for this operation. Will always be present if the
+ Vcb is acquired. Otherwise we must test for it.
+
+ AcquiredVcb - Indicates if this request has acquired the Vcb, meaning
+ do we have duplicate information to update.
+
+Return Value:
+
+ NTSTATUS - The status of the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PFCB Fcb = Scb->Fcb;
+ BOOLEAN NonResidentPath = TRUE;
+ BOOLEAN FileSizeChanged = FALSE;
+
+ LONGLONG NewFileSize;
+ LONGLONG NewValidDataLength;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_IRP( Irp );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetEndOfFileInfo...\n") );
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Get the new file size and whether this is coming from the lazy writer.
+ //
+
+ NewFileSize = ((PFILE_END_OF_FILE_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->EndOfFile.QuadPart;
+
+ //
+ // If this attribute has been 'deleted' then return immediately.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ DebugTrace( -1, Dbg, ("NtfsEndOfFileInfo: No work to do\n") );
+
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Save the current state of the Scb.
+ //
+
+ NtfsSnapshotScb( IrpContext, Scb );
+
+ //
+ // If we are called from the cache manager then we want to update the valid data
+ // length if necessary and also perform an update duplicate call if the Vcb
+ // is held.
+ //
+
+ if (IoGetCurrentIrpStackLocation(Irp)->Parameters.SetFile.AdvanceOnly) {
+
+ //
+ // We only have work to do if the file is nonresident.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Assume this is the lazy writer and set NewValidDataLength to
+ // NewFileSize (NtfsWriteFileSizes never goes beyond what's in the
+ // Fcb).
+ //
+
+ NewValidDataLength = NewFileSize;
+
+ NewFileSize = Scb->Header.FileSize.QuadPart;
+
+ //
+ // We can always move the valid data length in the Scb up to valid data
+ // on disk for this call back. Otherwise we may lose data in a mapped
+ // file if a user does a cached write to the middle of a page.
+ // For the typical case, Scb valid data length and file size are
+ // equal so no adjustment is necessary.
+ //
+
+ if ((Scb->Header.ValidDataLength.QuadPart < NewFileSize) &&
+ (NewValidDataLength > Scb->Header.ValidDataLength.QuadPart) &&
+ (Scb->Header.ValidDataLength.QuadPart < Scb->ValidDataToDisk)) {
+
+ //
+ // Set the valid data length to the smaller of ValidDataToDisk
+ // or file size.
+ //
+
+ if (Scb->ValidDataToDisk < NewFileSize) {
+
+ NewValidDataLength = Scb->ValidDataToDisk;
+
+ } else {
+
+ NewValidDataLength = NewFileSize;
+ }
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+
+ Scb->Header.ValidDataLength.QuadPart = NewValidDataLength;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &NewValidDataLength,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // If we acquired the Vcb then do the update duplicate if necessary.
+ //
+
+ if (VcbAcquired) {
+
+ //
+ // Now capture any file size changes in this file object back to the Fcb.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // Update the standard information if required.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Update duplicated information.
+ //
+
+ NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
+ }
+
+ //
+ // We know the file size for this Scb is now correct on disk.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ } else {
+
+ //
+ // Check if we really are changing the file size.
+ //
+
+ if (Scb->Header.FileSize.QuadPart != NewFileSize) {
+
+ FileSizeChanged = TRUE;
+
+ }
+
+ //
+ // Check if we are shrinking a mapped file in the non-lazywriter case. MM
+ // will tell us if someone currently has the file mapped.
+ //
+
+ if ((NewFileSize < Scb->Header.FileSize.QuadPart) &&
+ !MmCanFileBeTruncated( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&NewFileSize )) {
+
+ Status = STATUS_USER_MAPPED_FILE;
+ DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) );
+
+ return Status;
+ }
+
+#ifdef _CAIRO_
+
+ //
+ // If this is a change file size call after the stream has been cleaned
+ // up the fix the quota now.
+ //
+
+ if (FileSizeChanged &&
+ Scb->CleanupCount == 0 &&
+ NtfsPerformQuotaOperation( Scb->Fcb)) {
+
+ LONGLONG Delta = NewFileSize - Scb->Header.FileSize.QuadPart;
+
+ ASSERT(!FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED ));
+ ASSERT(!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE ));
+ ASSERT( FALSE );
+ NtfsUpdateFileQuota( IrpContext,
+ Scb->Fcb,
+ &Delta,
+ TRUE,
+ FALSE );
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE );
+ }
+
+#endif // _CAIRO_
+
+ //
+ // If this is a resident attribute we will try to keep it resident.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ if (FileSizeChanged) {
+
+ //
+ // If the new file size is larger than a file record then convert
+ // to non-resident and use the non-resident code below. Otherwise
+ // call ChangeAttributeValue which may also convert to nonresident.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ try {
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ //
+ // Either convert or change the attribute value.
+ //
+
+ if (NewFileSize >= Scb->Vcb->BytesPerFileRecordSegment) {
+
+ NtfsConvertToNonresident( IrpContext,
+ Fcb,
+ NtfsFoundAttribute( &AttrContext ),
+ BooleanFlagOn(Irp->Flags, IRP_PAGING_IO),
+ &AttrContext );
+
+ } else {
+
+ ULONG AttributeOffset;
+
+ //
+ // We are sometimes called by MM during a create section, so
+ // for right now the best way we have of detecting a create
+ // section is IRP_PAGING_IO being set, as in FsRtlSetFileSizes.
+ //
+
+ if ((ULONG) NewFileSize > Scb->Header.FileSize.LowPart) {
+
+ AttributeOffset = Scb->Header.ValidDataLength.LowPart;
+
+ } else {
+
+ AttributeOffset = (ULONG) NewFileSize;
+ }
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ AttributeOffset,
+ NULL,
+ (ULONG) NewFileSize - AttributeOffset,
+ TRUE,
+ FALSE,
+ BooleanFlagOn(Irp->Flags, IRP_PAGING_IO),
+ FALSE,
+ &AttrContext );
+
+ Scb->Header.FileSize.QuadPart = NewFileSize;
+
+ //
+ // If the file went non-resident, then the allocation size in
+ // the Scb is correct. Otherwise we quad-align the new file size.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.FileSize.LowPart );
+ Scb->Header.ValidDataLength.QuadPart = NewFileSize;
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+ }
+
+ NonResidentPath = FALSE;
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ } else {
+
+ NonResidentPath = FALSE;
+ }
+ }
+
+ //
+ // It is extremely expensive to make this call on a file that is not
+ // cached, and Ntfs has suffered stack overflows in addition to massive
+ // time and disk I/O expense (CcZero data on user mapped files!). Therefore,
+ // if no one has the file cached, we cache it here to make this call cheaper.
+ //
+ // Don't create the stream file if called from FsRtlSetFileSize (which sets
+ // IRP_PAGING_IO) because mm is in the process of creating a section.
+ //
+
+ if (FileSizeChanged &&
+ !CcIsFileCached(FileObject) &&
+ !FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE) &&
+ !FlagOn(Irp->Flags, IRP_PAGING_IO)) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+ }
+
+ //
+ // We now test if we need to modify the non-resident Eof. We will
+ // do this in two cases. Either we're converting from resident in
+ // two steps or the attribute was initially non-resident. We can ignore
+ // this step if not changing the file size.
+ //
+
+ if (NonResidentPath) {
+
+ //
+ // Now determine where the new file size lines up with the
+ // current file layout. The two cases we need to consider are
+ // where the new file size is less than the current file size and
+ // valid data length, in which case we need to shrink them.
+ // Or we new file size is greater than the current allocation,
+ // in which case we need to extend the allocation to match the
+ // new file size.
+ //
+
+ if (NewFileSize > Scb->Header.AllocationSize.QuadPart) {
+
+ DebugTrace( 0, Dbg, ("Adding allocation to file\n") );
+
+ //
+ // Add the allocation.
+ //
+
+ NtfsAddAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ),
+ LlClustersFromBytes(Scb->Vcb, (NewFileSize - Scb->Header.AllocationSize.QuadPart)),
+ FALSE );
+ }
+
+ NewValidDataLength = Scb->Header.ValidDataLength.QuadPart;
+
+ //
+ // If this is a paging file, let the whole thing be valid
+ // so that we don't end up zeroing pages! Also, make sure
+ // we really write this into the file.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )) {
+
+ VCN AllocatedVcns;
+
+ AllocatedVcns = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Scb->Vcb->ClusterShift);
+
+ Scb->ValidDataToDisk =
+ Scb->Header.ValidDataLength.QuadPart =
+ NewValidDataLength = NewFileSize;
+
+ //
+ // If this is the paging file then guarantee that the Mcb is fully loaded.
+ //
+
+ NtfsPreloadAllocation( IrpContext, Scb, 0, AllocatedVcns );
+ }
+
+ if (NewFileSize < NewValidDataLength) {
+
+ Scb->Header.ValidDataLength.QuadPart =
+ NewValidDataLength = NewFileSize;
+ }
+
+ if (NewFileSize < Scb->ValidDataToDisk) {
+
+ Scb->ValidDataToDisk = NewFileSize;
+ }
+
+ Scb->Header.FileSize.QuadPart = NewFileSize;
+
+ //
+ // Call our common routine to modify the file sizes. We are now
+ // done with NewFileSize and NewValidDataLength, and we have
+ // PagingIo + main exclusive (so no one can be working on this Scb).
+ // NtfsWriteFileSizes uses the sizes in the Scb, and this is the
+ // one place where in Ntfs where we wish to use a different value
+ // for ValidDataLength. Therefore, we save the current ValidData
+ // and plug it with our desired value and restore on return.
+ //
+
+ ASSERT( NewFileSize == Scb->Header.FileSize.QuadPart );
+ ASSERT( NewValidDataLength == Scb->Header.ValidDataLength.QuadPart );
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ BooleanFlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ),
+ TRUE );
+ }
+
+ //
+ // If the file size changed then mark this file object as having changed the size.
+ //
+
+ if (FileSizeChanged) {
+
+ SetFlag( FileObject->Flags, FO_FILE_SIZE_CHANGED );
+ }
+
+ //
+ // Always mark the data stream as modified.
+ //
+
+ if (ARGUMENT_PRESENT( Ccb )) {
+
+ SetFlag( Ccb->Flags,
+ (CCB_FLAG_UPDATE_LAST_MODIFY |
+ CCB_FLAG_UPDATE_LAST_CHANGE |
+ CCB_FLAG_SET_ARCHIVE) );
+
+ } else {
+
+ SetFlag( FileObject->Flags, FO_FILE_MODIFIED );
+ }
+
+ //
+ // Now capture any file size changes in this file object back to the Fcb.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, VcbAcquired );
+
+ //
+ // Update the standard information if required.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ //
+ // We know we wrote out any changes to the file size above so clear the
+ // flag in the Scb to check the attribute size. This will save us from doing
+ // this unnecessarily at cleanup.
+ //
+
+ if (FileSizeChanged) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Update duplicated information.
+ //
+
+ if (VcbAcquired) {
+
+ NtfsUpdateFileDupInfo( IrpContext, Fcb, Ccb );
+ }
+
+ //
+ // Only call if the file is cached now, because the other case
+ // may cause recursion in write!
+
+ if (CcIsFileCached(FileObject)) {
+
+ //
+ // We want to checkpoint the transaction if there is one active.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ }
+
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+
+ //
+ // Clear out the write mask on truncates to zero.
+ //
+
+#ifdef SYSCACHE
+ if ((Scb->Header.FileSize.QuadPart == 0) && FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE) &&
+ (Scb->ScbType.Data.WriteMask != NULL)) {
+ RtlZeroMemory(Scb->ScbType.Data.WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
+ }
+#endif
+
+ //
+ // Now cleanup the stream we created if there are no more user
+ // handles.
+ //
+
+ if ((Scb->CleanupCount == 0) && (Scb->FileObject != NULL)) {
+ NtfsDeleteInternalAttributeStream( Scb, FALSE );
+ }
+ }
+
+ Status = STATUS_SUCCESS;
+
+ DebugTrace( -1, Dbg, ("NtfsSetEndOfFileInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsCheckScbForLinkRemoval (
+ IN PSCB Scb,
+ OUT PSCB *BatchOplockScb,
+ OUT PULONG BatchOplockCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to check if a link to an open Scb may be
+ removed for rename. We walk through all the children and
+ verify that they have no user opens.
+
+Arguments:
+
+ Scb - Scb whose children are to be examined.
+
+ BatchOplockScb - Address to store Scb which may have a batch oplock.
+
+ BatchOplockCount - Number of files which have batch oplocks on this
+ pass through the directory tree.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS if the link can be removed,
+ STATUS_ACCESS_DENIED otherwise.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PSCB NextScb;
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckScbForLinkRemoval: Entered\n") );
+
+ //
+ // Initialize the batch oplock state.
+ //
+
+ *BatchOplockCount = 0;
+ *BatchOplockScb = NULL;
+
+ //
+ // If this is a directory file and we are removing a link,
+ // we need to examine its descendents. We may not remove a link which
+ // may be an ancestor path component of any open file.
+ //
+
+ //
+ // First look for any descendents with a non-zero unclean count.
+ //
+
+ NextScb = Scb;
+
+ while ((NextScb = NtfsGetNextScb( NextScb, Scb )) != NULL) {
+
+ //
+ // Stop if there are open handles. If there is a batch oplock on
+ // this file then we will try to break the batch oplock. In this
+ // pass we will just count the number of files with batch oplocks
+ // and remember the first one we encounter.
+ //
+
+ if (NextScb->Fcb->CleanupCount != 0) {
+
+ if ((NextScb->AttributeTypeCode == $DATA) &&
+ (NextScb->Header.NodeTypeCode == NTFS_NTC_SCB_DATA) &&
+ FsRtlCurrentBatchOplock( &NextScb->ScbType.Data.Oplock )) {
+
+ *BatchOplockCount += 1;
+
+ if (*BatchOplockScb == NULL) {
+
+ *BatchOplockScb = NextScb;
+ Status = STATUS_PENDING;
+ }
+
+ } else {
+
+ Status = STATUS_ACCESS_DENIED;
+ DebugTrace( 0, Dbg, ("NtfsCheckScbForLinkRemoval: Directory to rename has open children\n") );
+
+ break;
+ }
+ }
+ }
+
+ //
+ //
+ // We know there are no opens below this point. We will remove any prefix
+ // entries later.
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCheckScbForLinkRemoval: Exit -> %08lx\n") );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsFindTargetElements (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT TargetFileObject,
+ IN PSCB ParentScb,
+ OUT PSCB *TargetParentScb,
+ OUT PUNICODE_STRING FullTargetFileName,
+ OUT PUNICODE_STRING TargetFileName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine determines the target directory for the rename and the
+ target link name. If these is a target file object, we use that to
+ find the target. Otherwise the target is the same directory as the
+ source.
+
+Arguments:
+
+ TargetFileObject - This is the file object which describes the target
+ for the link operation.
+
+ ParentScb - This is current directory for the link.
+
+ TargetParentScb - This is the location to store the parent of the target.
+
+ FullTargetFileName - This is a pointer to a unicode string which will point
+ to the name from the root. We clear this if there is no full name
+ available.
+
+ TargetFileName - This is a pointer to a unicode string which will point to
+ the target name on exit.
+
+Return Value:
+
+ BOOLEAN - TRUE if there is no work to do, FALSE otherwise.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFindTargetElements: Entered\n") );
+
+ //
+ // We need to find the target parent directory, target file and target
+ // name for the new link. These three pieces of information allow
+ // us to see if the link already exists.
+ //
+ // Check if we have a file object for the target.
+ //
+
+ if (TargetFileObject != NULL) {
+
+ PVCB TargetVcb;
+ PFCB TargetFcb;
+ PCCB TargetCcb;
+
+ USHORT PreviousLength;
+ USHORT LastFileNameOffset;
+
+ //
+ // The target directory is given by the TargetFileObject.
+ // The name for the link is contained in the TargetFileObject.
+ //
+ // The target must be a user directory and must be on the
+ // current Vcb.
+ //
+
+ if ((NtfsDecodeFileObject( IrpContext,
+ TargetFileObject,
+ &TargetVcb,
+ &TargetFcb,
+ TargetParentScb,
+ &TargetCcb,
+ TRUE ) != UserDirectoryOpen) ||
+
+ ((ParentScb != NULL) &&
+ (TargetVcb != ParentScb->Vcb))) {
+
+ DebugTrace( -1, Dbg, ("NtfsFindTargetElements: Target file object is invalid\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ //
+ // Temporarily set the file name to point to the full buffer.
+ //
+
+ LastFileNameOffset = PreviousLength = TargetFileObject->FileName.Length;
+
+ TargetFileObject->FileName.Length = TargetFileObject->FileName.MaximumLength;
+
+ *FullTargetFileName = TargetFileObject->FileName;
+
+ //
+ // If the first character at the final component is a backslash, move the
+ // offset ahead by 2.
+ //
+
+ if (TargetFileObject->FileName.Buffer[LastFileNameOffset / 2] == L'\\') {
+
+ LastFileNameOffset += sizeof( WCHAR );
+ }
+
+ NtfsBuildLastFileName( IrpContext,
+ TargetFileObject,
+ LastFileNameOffset,
+ TargetFileName );
+
+ //
+ // Restore the file object length.
+ //
+
+ TargetFileObject->FileName.Length = PreviousLength;
+
+ //
+ // Otherwise the rename occurs in the current directory. The directory
+ // is the parent of this Fcb, the name is stored in a Rename buffer.
+ //
+
+ } else {
+
+ PFILE_RENAME_INFORMATION Buffer;
+
+ Buffer = IrpContext->OriginatingIrp->AssociatedIrp.SystemBuffer;
+
+ *TargetParentScb = ParentScb;
+
+ TargetFileName->MaximumLength =
+ TargetFileName->Length = (USHORT)Buffer->FileNameLength;
+ TargetFileName->Buffer = (PWSTR) &Buffer->FileName;
+
+ FullTargetFileName->Length =
+ FullTargetFileName->MaximumLength = 0;
+ FullTargetFileName->Buffer = NULL;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFindTargetElements: Exit\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsCheckLinkForNewLink (
+ IN PFCB Fcb,
+ IN PFILE_NAME FileNameAttr,
+ IN FILE_REFERENCE FileReference,
+ IN PUNICODE_STRING NewLinkName,
+ OUT PULONG LinkFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks the source and target directories and files.
+ It determines whether the target link needs to be removed and
+ whether the target link spans the same parent and file as the
+ source link. This routine may determine that there
+ is absolutely no work remaining for this link operation. This is true
+ if the desired link already exists.
+
+Arguments:
+
+ Fcb - This is the Fcb for the link which is being renamed.
+
+ FileNameAttr - This is the file name attribute for the matching link
+ on the disk.
+
+ FileReference - This is the file reference for the matching link found.
+
+ NewLinkName - This is the name to use for the rename.
+
+ LinkFlags - Address of flags field to store whether the source link and target
+ link traverse the same directory and file.
+
+Return Value:
+
+ BOOLEAN - TRUE if there is no work to do, FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN NoWorkToDo = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckLinkForNewLink: Entered\n") );
+
+ //
+ // Check if the file references match.
+ //
+
+ if (NtfsEqualMftRef( &FileReference, &Fcb->FileReference )) {
+
+ SetFlag( *LinkFlags, TRAVERSE_MATCH );
+ }
+
+ //
+ // We need to determine if we have an exact match for the link names.
+ //
+
+ if (RtlEqualMemory( FileNameAttr->FileName,
+ NewLinkName->Buffer,
+ NewLinkName->Length )) {
+
+ SetFlag( *LinkFlags, EXACT_CASE_MATCH );
+ }
+
+ //
+ // We now have to decide whether we will be removing the target link.
+ // The following conditions must hold for us to preserve the target link.
+ //
+ // 1 - The target link connects the same directory to the same file.
+ //
+ // 2 - The names are an exact case match.
+ //
+
+ if (FlagOn( *LinkFlags, TRAVERSE_MATCH | EXACT_CASE_MATCH ) == (TRAVERSE_MATCH | EXACT_CASE_MATCH)) {
+
+ NoWorkToDo = TRUE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCheckLinkForNewLink: Exit\n") );
+
+ return NoWorkToDo;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsCheckLinkForRename (
+ IN PFCB Fcb,
+ IN PLCB Lcb,
+ IN PFILE_NAME FileNameAttr,
+ IN FILE_REFERENCE FileReference,
+ IN PUNICODE_STRING TargetFileName,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PULONG RenameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks the source and target directories and files.
+ It determines whether the target link needs to be removed and
+ whether the target link spans the same parent and file as the
+ source link. We also determine if the new link name is an exact case
+ match for the existing link name. The booleans indicating which links
+ to remove or add have already been initialized to the default values.
+
+Arguments:
+
+ Fcb - This is the Fcb for the link which is being renamed.
+
+ Lcb - This is the link being renamed.
+
+ FileNameAttr - This is the file name attribute for the matching link
+ on the disk.
+
+ FileReference - This is the file reference for the matching link found.
+
+ TargetFileName - This is the name to use for the rename.
+
+ IgnoreCase - Indicates if the user is case sensitive.
+
+ RenameFlags - Flag field which indicates which updates to perform.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckLinkForRename: Entered\n") );
+
+ //
+ // Check if the file references match.
+ //
+
+ if (NtfsEqualMftRef( &FileReference, &Fcb->FileReference )) {
+
+ SetFlag( *RenameFlags, TRAVERSE_MATCH );
+ }
+
+ //
+ // We need to determine if we have an exact match between the desired name
+ // and the current name for the link. We already know the length are the same.
+ //
+
+ if (RtlEqualMemory( FileNameAttr->FileName,
+ TargetFileName->Buffer,
+ TargetFileName->Length )) {
+
+ SetFlag( *RenameFlags, EXACT_CASE_MATCH );
+ }
+
+ //
+ // If this is a traverse match (meaning the desired link and the link
+ // being replaced connect the same directory to the same file) we check
+ // if we can leave the link on the file.
+ //
+ // At the end of the rename, there must be an Ntfs name or hard link
+ // which matches the target name exactly.
+ //
+
+ if (FlagOn( *RenameFlags, TRAVERSE_MATCH )) {
+
+ //
+ // If we are in the same directory and are renaming between Ntfs and Dos
+ // links then don't remove the link twice.
+ //
+
+ if (!FlagOn( *RenameFlags, MOVE_TO_NEW_DIR )) {
+
+ //
+ // If We are renaming from between primary links then don't remove the
+ // source. It is removed with the target.
+ //
+
+ if ((Lcb->FileNameAttr->Flags != 0) && (FileNameAttr->Flags != 0)) {
+
+ ClearFlag( *RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK );
+ SetFlag( *RenameFlags, OVERWRITE_SOURCE_LINK );
+
+ //
+ // If this is an exact case match then don't remove the source at all.
+ //
+
+ if (FlagOn( *RenameFlags, EXACT_CASE_MATCH )) {
+
+ ClearFlag( *RenameFlags, REMOVE_SOURCE_LINK );
+ }
+
+ //
+ // If we are changing the case of a link only, then don't remove the link twice.
+ //
+
+ } else if (RtlEqualMemory( Lcb->ExactCaseLink.LinkName.Buffer,
+ FileNameAttr->FileName,
+ Lcb->ExactCaseLink.LinkName.Length )) {
+
+ SetFlag( *RenameFlags, OVERWRITE_SOURCE_LINK );
+ ClearFlag( *RenameFlags, ACTIVELY_REMOVE_SOURCE_LINK );
+ }
+ }
+
+ //
+ // If the names match exactly we can reuse the links if we don't have a
+ // conflict with the name flags.
+ //
+
+ if (FlagOn( *RenameFlags, EXACT_CASE_MATCH ) &&
+ (FlagOn( *RenameFlags, OVERWRITE_SOURCE_LINK ) ||
+ !IgnoreCase ||
+ !FlagOn( Lcb->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS ))) {
+
+ //
+ // Otherwise we are renaming hard links or this is a Posix opener.
+ //
+
+ ClearFlag( *RenameFlags, REMOVE_TARGET_LINK | ADD_TARGET_LINK );
+ }
+ }
+
+ //
+ // The non-traverse case is already initialized.
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCheckLinkForRename: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsCleanupLinkForRemoval (
+ IN PFCB PreviousFcb,
+ IN BOOLEAN ExistingFcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does the all cleanup on a file/link which is the target
+ of either a rename or set link operation.
+
+Arguments:
+
+ PreviousFcb - Address to store the Fcb for the file whose link is
+ being removed.
+
+ ExistingFcb - Address to store whether this Fcb already existed.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCleanupLinkForRemoval: Entered\n") );
+
+ //
+ // If the Fcb existed, we remove all of the prefix entries for it.
+ //
+
+ if (ExistingFcb) {
+
+ PLIST_ENTRY Links;
+ PLCB ThisLcb;
+
+ for (Links = PreviousFcb->LcbQueue.Flink;
+ Links != &PreviousFcb->LcbQueue;
+ Links = Links->Flink ) {
+
+ ThisLcb = CONTAINING_RECORD( Links,
+ LCB,
+ FcbLinks );
+
+ NtfsRemovePrefix( ThisLcb );
+
+ } // End for each Lcb of Fcb
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCleanupLinkForRemoval: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsUpdateFcbFromLinkRemoval (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN UNICODE_STRING FileName,
+ IN UCHAR FileNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the in-memory part of a link which
+ has been removed from a file. We find the Lcb's for the links and
+ mark them as deleted and removed.
+
+Arguments:
+
+ ParentScb - Scb for the directory the was removed from.
+
+ ParentScb - This is the Scb for the new directory.
+
+ Fcb - The Fcb for the file whose link is being renamed.
+
+ FileName - File name for link being removed.
+
+ FileNameFlags - File name flags for link being removed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLCB Lcb;
+ PLCB SplitPrimaryLcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateFcbFromLinkRemoval: Entered\n") );
+
+ SplitPrimaryLcb = NULL;
+
+ //
+ // Find the Lcb for the link which was removed.
+ //
+
+ Lcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ FileName,
+ FileNameFlags,
+ NULL );
+
+ //
+ // If this is a split primary, we need to find the name flags for
+ // the Lcb.
+ //
+
+ if (LcbSplitPrimaryLink( Lcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( Lcb ));
+ }
+
+ //
+ // Mark any Lcb's we have as deleted and removed.
+ //
+
+ SetFlag( Lcb->LcbState, (LCB_STATE_DELETE_ON_CLOSE | LCB_STATE_LINK_IS_GONE) );
+
+ if (SplitPrimaryLcb) {
+
+ SetFlag( SplitPrimaryLcb->LcbState,
+ (LCB_STATE_DELETE_ON_CLOSE | LCB_STATE_LINK_IS_GONE) );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateFcbFromLinkRemoval: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsReplaceLinkInDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR FileNameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to create the in-memory part of a link in a new
+ directory.
+
+Arguments:
+
+ ParentScb - Scb for the directory the link is being created in.
+
+ Fcb - The Fcb for the file whose link is being created.
+
+ NewLinkName - Name for the new component.
+
+ FileNameFlags - These are the flags to use for the new link.
+
+ PrevLinkName - File name for link being removed.
+
+ PrevLinkNameFlags - File name flags for link being removed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLCB TraverseLcb;
+ PLCB SplitPrimaryLcb = NULL;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateLinkInNewDir: Entered\n") );
+
+ SplitPrimaryLcb = NULL;
+
+ //
+ // Build the name for the traverse link and call strucsup to
+ // give us an Lcb.
+ //
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ *PrevLinkName,
+ PrevLinkNameFlags,
+ NULL );
+
+ //
+ // If this is a split primary, we need to find the name flags for
+ // the Lcb.
+ //
+
+ if (LcbSplitPrimaryLink( TraverseLcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
+ }
+
+ //
+ // We now need only to rename and combine any existing Lcb's.
+ //
+
+ NtfsRenameLcb( IrpContext,
+ TraverseLcb,
+ NewLinkName,
+ FileNameFlags,
+ FALSE );
+
+ if (SplitPrimaryLcb != NULL) {
+
+ NtfsRenameLcb( IrpContext,
+ SplitPrimaryLcb,
+ NewLinkName,
+ FileNameFlags,
+ FALSE );
+
+ NtfsCombineLcbs( IrpContext,
+ TraverseLcb,
+ SplitPrimaryLcb );
+
+ NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateLinkInNewDir: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsMoveLinkToNewDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PUNICODE_STRING NewFullLinkName,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR NewLinkNameFlags,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN OUT PLCB Lcb,
+ IN ULONG RenameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to move the in-memory part of a link to a new
+ directory. We move the link involved and its primary link partner if
+ it exists.
+
+Arguments:
+
+ NewFullLinkName - This is the full name for the new link from the root.
+
+ NewLinkName - This is the last component name only.
+
+ NewLinkNameFlags - These are the flags to use for the new link.
+
+ ParentScb - This is the Scb for the new directory.
+
+ Fcb - The Fcb for the file whose link is being renamed.
+
+ Lcb - This is the Lcb which is the base of the rename.
+
+ RenameFlags - Flag field indicating the type of operations to perform
+ on file name links.
+
+ PrevLinkName - File name for link being removed. Only meaningful here
+ if this is a traverse match and there are remaining Lcbs for the
+ previous link.
+
+ PrevLinkNameFlags - File name flags for link being removed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLCB TraverseLcb = NULL;
+ PLCB SplitPrimaryLcb = NULL;
+ BOOLEAN SplitSourceLcb = FALSE;
+
+ UNICODE_STRING TargetDirectoryName;
+ UNICODE_STRING SplitLinkName;
+
+ UCHAR SplitLinkNameFlags = NewLinkNameFlags;
+ BOOLEAN Found;
+
+ PFILE_NAME FileName;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN CleanupAttrContext = FALSE;
+
+ ULONG Pass;
+ BOOLEAN CheckBufferOnly;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMoveLinkToNewDir: Entered\n") );
+
+ //
+ // Use a try-finally to perform cleanup.
+ //
+
+ try {
+
+ //
+ // Construct the unicode string for the parent directory.
+ //
+
+ TargetDirectoryName = *NewFullLinkName;
+ TargetDirectoryName.Length -= NewLinkName->Length;
+
+ if (TargetDirectoryName.Length > sizeof( WCHAR )) {
+
+ TargetDirectoryName.Length -= sizeof( WCHAR );
+ }
+
+ // If the link being moved is a split primary link, we need to find
+ // its other half.
+ //
+
+ if (LcbSplitPrimaryLink( Lcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( Lcb ));
+ SplitSourceLcb = TRUE;
+
+ //
+ // If we found an existing Lcb we have to update its name as well. We may be
+ // able to use the new name used for the Lcb passed in. However we must check
+ // that we don't overwrite a DOS name with an NTFS only name.
+ //
+
+ if (SplitPrimaryLcb &&
+ (SplitPrimaryLcb->FileNameAttr->Flags == FILE_NAME_DOS) &&
+ (NewLinkNameFlags == FILE_NAME_NTFS)) {
+
+ //
+ // Lookup the dos only name on disk.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ //
+ // Walk through the names for this entry. There better
+ // be one which is not a DOS-only name.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &AttrContext );
+
+ while (Found) {
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ if (FileName->Flags == FILE_NAME_DOS) { break; }
+
+ Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &AttrContext );
+ }
+
+ //
+ // We should have found the entry.
+ //
+
+ if (!Found) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Now build the component name.
+ //
+
+ SplitLinkName.Buffer = FileName->FileName;
+ SplitLinkName.MaximumLength =
+ SplitLinkName.Length = FileName->FileNameLength * sizeof( WCHAR );
+ SplitLinkNameFlags = FILE_NAME_DOS;
+
+ } else {
+
+ SplitLinkName = *NewLinkName;
+ }
+ }
+
+ //
+ // If we removed or reused a traverse link, we need to check if there is
+ // an Lcb for it.
+ //
+
+ if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK | REUSE_TRAVERSE_LINK )) {
+
+ //
+ // Build the name for the traverse link and call strucsup to
+ // give us an Lcb.
+ //
+
+ if (FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ *NewLinkName,
+ PrevLinkNameFlags,
+ NULL );
+
+ } else {
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ *PrevLinkName,
+ PrevLinkNameFlags,
+ NULL );
+ }
+
+ if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK )) {
+
+ //
+ // If this is a split primary, we need to find the name flags for
+ // the Lcb.
+ //
+
+ if (LcbSplitPrimaryLink( TraverseLcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
+ }
+ }
+ }
+
+ //
+ // Now move and combine the Lcbs. We will do this in two passes. One will allocate buffers
+ // of sufficient size. The other will store the names in.
+ //
+
+ Pass = 0;
+ CheckBufferOnly = TRUE;
+ do {
+
+ //
+ // Start with the Lcb used for the rename.
+ //
+
+ NtfsMoveLcb( IrpContext,
+ Lcb,
+ ParentScb,
+ Fcb,
+ &TargetDirectoryName,
+ NewLinkName,
+ NewLinkNameFlags,
+ CheckBufferOnly );
+
+ //
+ // Next do the split primary if from the source file or the target.
+ //
+
+ if (SplitPrimaryLcb && SplitSourceLcb) {
+
+ NtfsMoveLcb( IrpContext,
+ SplitPrimaryLcb,
+ ParentScb,
+ Fcb,
+ &TargetDirectoryName,
+ &SplitLinkName,
+ SplitLinkNameFlags,
+ CheckBufferOnly );
+
+ //
+ // If we are in the second pass then optionally combine these
+ // Lcb's and delete the split.
+ //
+
+ if ((SplitLinkNameFlags == NewLinkNameFlags) && !CheckBufferOnly) {
+
+ NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
+ NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
+ }
+ }
+
+ //
+ // If we have a traverse link and are in the second pass then combine
+ // with the primary Lcb.
+ //
+
+ if (!CheckBufferOnly) {
+
+ if (TraverseLcb != NULL) {
+
+ if (!FlagOn( RenameFlags, REUSE_TRAVERSE_LINK )) {
+
+ NtfsRenameLcb( IrpContext,
+ TraverseLcb,
+ NewLinkName,
+ NewLinkNameFlags,
+ CheckBufferOnly );
+
+ if (SplitPrimaryLcb && !SplitSourceLcb) {
+
+ NtfsRenameLcb( IrpContext,
+ SplitPrimaryLcb,
+ NewLinkName,
+ NewLinkNameFlags,
+ CheckBufferOnly );
+
+ //
+ // If we are in the second pass then optionally combine these
+ // Lcb's and delete the split.
+ //
+
+ if (!CheckBufferOnly) {
+
+ NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
+ NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
+ }
+ }
+ }
+
+ NtfsCombineLcbs( IrpContext,
+ Lcb,
+ TraverseLcb );
+
+ NtfsDeleteLcb( IrpContext, &TraverseLcb );
+ }
+ }
+
+ Pass += 1;
+ CheckBufferOnly = FALSE;
+
+ } while (Pass < 2);
+
+ } finally {
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMoveLinkToNewDir: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsCreateLinkInSameDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN UNICODE_STRING NewLinkName,
+ IN UCHAR NewFileNameFlags,
+ IN UNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when we are replacing a link in a single directory.
+ We need to find the link being renamed and any auxilary links and
+ then give them their new names.
+
+Arguments:
+
+ ParentScb - Scb for the directory the rename is taking place in.
+
+ Fcb - The Fcb for the file whose link is being renamed.
+
+ NewLinkName - This is the name to use for the new link.
+
+ NewFileNameFlags - These are the flags to use for the new link.
+
+ PrevLinkName - File name for link being removed.
+
+ PrevLinkNameFlags - File name flags for link being removed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLCB TraverseLcb;
+ PLCB SplitPrimaryLcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateLinkInSameDir: Entered\n") );
+
+ //
+ // Initialize our local variables.
+ //
+
+ SplitPrimaryLcb = NULL;
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ PrevLinkName,
+ PrevLinkNameFlags,
+ NULL );
+
+ //
+ // If this is a split primary, we need to find the name flags for
+ // the Lcb.
+ //
+
+ if (LcbSplitPrimaryLink( TraverseLcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
+ }
+
+ //
+ // We now need only to rename and combine any existing Lcb's.
+ //
+
+ NtfsRenameLcb( IrpContext,
+ TraverseLcb,
+ &NewLinkName,
+ NewFileNameFlags,
+ FALSE );
+
+ if (SplitPrimaryLcb != NULL) {
+
+ NtfsRenameLcb( IrpContext,
+ SplitPrimaryLcb,
+ &NewLinkName,
+ NewFileNameFlags,
+ FALSE );
+
+ NtfsCombineLcbs( IrpContext,
+ TraverseLcb,
+ SplitPrimaryLcb );
+
+ NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateLinkInSameDir: Exit\n") );
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsRenameLinkInDir (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN OUT PLCB Lcb,
+ IN PUNICODE_STRING NewLinkName,
+ IN UCHAR NewLinkNameFlags,
+ IN ULONG RenameFlags,
+ IN PUNICODE_STRING PrevLinkName,
+ IN UCHAR PrevLinkNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the in-memory work of moving renaming a link within
+ the same directory. It will rename an existing link to the
+ new name. It also merges whatever other links need to be joined with
+ this link. This includes the complement of a primary link pair or
+ an existing hard link which may be overwritten. Merging the existing
+ links has the effect of moving any of the Ccb's on the stale Links to
+ the newly modified link.
+
+Arguments:
+
+ ParentScb - Scb for the directory the rename is taking place in.
+
+ Fcb - The Fcb for the file whose link is being renamed.
+
+ Lcb - This is the Lcb which is the base of the rename.
+
+ NewLinkName - This is the name to use for the new link.
+
+ NewLinkNameFlags - These are the flags to use for the new link.
+
+ RenameFlags - Flag field indicating the type of operations to perform
+ on the file name links.
+
+ PrevLinkName - File name for link being removed. Only meaningful for a traverse link.
+
+ PrevLinkNameFlags - File name flags for link being removed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UNICODE_STRING SplitLinkName;
+ UCHAR SplitLinkNameFlags = NewLinkNameFlags;
+
+ PLCB TraverseLcb = NULL;
+ PLCB SplitPrimaryLcb = NULL;
+
+ PFILE_NAME FileName;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN CleanupAttrContext = FALSE;
+ BOOLEAN Found;
+
+ ULONG Pass;
+ BOOLEAN CheckBufferOnly;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRenameLinkInDir: Entered\n") );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // We have the Lcb which will be our primary Lcb and the name we need
+ // to perform the rename. If the current Lcb is a split primary link
+ // or we removed a split primary link, then we need to find any
+ // the other split link.
+ //
+
+ if (LcbSplitPrimaryLink( Lcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( Lcb ));
+
+ //
+ // If we found an existing Lcb we have to update its name as well. We may be
+ // able to use the new name used for the Lcb passed in. However we must check
+ // that we don't overwrite a DOS name with an NTFS only name.
+ //
+
+ if (SplitPrimaryLcb &&
+ (SplitPrimaryLcb->FileNameAttr->Flags == FILE_NAME_DOS) &&
+ (NewLinkNameFlags == FILE_NAME_NTFS)) {
+
+ //
+ // Lookup the dos only name on disk.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ //
+ // Walk through the names for this entry. There better
+ // be one which is not a DOS-only name.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &AttrContext );
+
+ while (Found) {
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ if (FileName->Flags == FILE_NAME_DOS) { break; }
+
+ Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &AttrContext );
+ }
+
+ //
+ // We should have found the entry.
+ //
+
+ if (!Found) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Now build the component name.
+ //
+
+ SplitLinkName.Buffer = FileName->FileName;
+ SplitLinkName.MaximumLength =
+ SplitLinkName.Length = FileName->FileNameLength * sizeof( WCHAR );
+ SplitLinkNameFlags = FILE_NAME_DOS;
+
+ } else {
+
+ SplitLinkName = *NewLinkName;
+ }
+ }
+
+ //
+ // If we used a traverse link, we need to check if there is
+ // an Lcb for it. Ignore this for the case where we traversed to
+ // the other half of a primary link.
+ //
+
+ if (!FlagOn( RenameFlags, OVERWRITE_SOURCE_LINK ) &&
+ FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK | REUSE_TRAVERSE_LINK )) {
+
+ if (FlagOn( RenameFlags, EXACT_CASE_MATCH )) {
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ *NewLinkName,
+ PrevLinkNameFlags,
+ NULL );
+
+ } else {
+
+ TraverseLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ Fcb,
+ *PrevLinkName,
+ PrevLinkNameFlags,
+ NULL );
+ }
+
+ if (FlagOn( RenameFlags, REMOVE_TRAVERSE_LINK )) {
+
+ //
+ // If this is a split primary, we need to find the name flags for
+ // the Lcb.
+ //
+
+ if (LcbSplitPrimaryLink( TraverseLcb )) {
+
+ SplitPrimaryLcb = NtfsLookupLcbByFlags( Fcb,
+ (UCHAR) LcbSplitPrimaryComplement( TraverseLcb ));
+
+ SplitLinkName = *NewLinkName;
+ }
+ }
+ }
+
+ //
+ // Now move and combine the Lcbs. We will do this in two passes. One will allocate buffers
+ // of sufficient size. The other will store the names in.
+ //
+
+ Pass = 0;
+ CheckBufferOnly = TRUE;
+ do {
+
+ //
+ // Start with the Lcb used for the rename.
+ //
+
+ NtfsRenameLcb( IrpContext,
+ Lcb,
+ NewLinkName,
+ NewLinkNameFlags,
+ CheckBufferOnly );
+
+ //
+ // Next do the split primary if from the source file or the target.
+ //
+
+ if (SplitPrimaryLcb) {
+
+ NtfsRenameLcb( IrpContext,
+ SplitPrimaryLcb,
+ &SplitLinkName,
+ SplitLinkNameFlags,
+ CheckBufferOnly );
+
+ //
+ // If we are in the second pass then optionally combine these
+ // Lcb's and delete the split.
+ //
+
+ if (!CheckBufferOnly && (SplitLinkNameFlags == NewLinkNameFlags)) {
+
+ NtfsCombineLcbs( IrpContext, Lcb, SplitPrimaryLcb );
+ NtfsDeleteLcb( IrpContext, &SplitPrimaryLcb );
+ }
+ }
+
+ //
+ // If we have a traverse link and are in the second pass then combine
+ // with the primary Lcb.
+ //
+
+ if (!CheckBufferOnly) {
+
+ if (TraverseLcb != NULL) {
+
+ if (!FlagOn( RenameFlags, REUSE_TRAVERSE_LINK )) {
+
+ NtfsRenameLcb( IrpContext,
+ TraverseLcb,
+ NewLinkName,
+ NewLinkNameFlags,
+ CheckBufferOnly );
+ }
+
+ NtfsCombineLcbs( IrpContext,
+ Lcb,
+ TraverseLcb );
+
+ NtfsDeleteLcb( IrpContext, &TraverseLcb );
+ }
+ }
+
+ Pass += 1;
+ CheckBufferOnly = FALSE;
+
+ } while (Pass < 2);
+
+ } finally {
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsRenameLinkInDir: Exit\n") );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsUpdateFileDupInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PCCB Ccb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine updates the duplicate information for a file for calls
+ to set allocation or EOF on the main data stream. It is in a separate routine
+ so we don't have to put a try-except in the main path.
+
+ We will overlook any expected errors in this path. If we get any errors we
+ will simply leave this update to be performed at some other time.
+
+ We are guaranteed that the current transaction has been checkpointed before this
+ routine is called. We will look to see if the MftScb is on the exclusive list
+ for this IrpContext and release it if so. This is to prevent a deadlock when
+ we attempt to acquire the parent of this file.
+
+Arguments:
+
+ Fcb - This is the Fcb to update.
+
+ Ccb - If specified, this is the Ccb for the caller making the call.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLCB Lcb = NULL;
+ PSCB ParentScb = NULL;
+ ULONG FilterMatch;
+
+ PLIST_ENTRY Links;
+ PFCB NextFcb;
+
+ PAGED_CODE();
+
+ ASSERT( IrpContext->TransactionId == 0 );
+
+ //
+ // Check if there is an Lcb in the Ccb.
+ //
+
+ if (ARGUMENT_PRESENT( Ccb )) {
+
+ Lcb = Ccb->Lcb;
+ }
+
+ //
+ // Use a try-except to catch any errors.
+ //
+
+ try {
+
+ //
+ // Check that we don't own the Mft Scb.
+ //
+
+ if (Fcb->Vcb->MftScb != NULL) {
+
+ for (Links = IrpContext->ExclusiveFcbList.Flink;
+ Links != &IrpContext->ExclusiveFcbList;
+ Links = Links->Flink) {
+
+ ULONG Count;
+
+ NextFcb = (PFCB) CONTAINING_RECORD( Links,
+ FCB,
+ ExclusiveFcbLinks );
+
+ //
+ // If this is the Fcb for the Mft then remove it from the list.
+ //
+
+ if (NextFcb == Fcb->Vcb->MftScb->Fcb) {
+
+ //
+ // Free the snapshots for the Fcb and release the Fcb enough times
+ // to remove it from the list.
+ //
+
+ NtfsFreeSnapshotsForFcb( IrpContext, NextFcb );
+
+ Count = NextFcb->BaseExclusiveCount;
+
+ while (Count--) {
+
+ NtfsReleaseFcb( IrpContext, NextFcb );
+ }
+
+ break;
+ }
+ }
+ }
+
+#ifdef _CAIRO_
+
+ //
+ // Check that we don't own the quota table Scb.
+ // CAIROBUG: Combine these two loops when cairo ifdefs removed.
+ //
+
+ if (Fcb->Vcb->QuotaTableScb != NULL) {
+
+ for (Links = IrpContext->ExclusiveFcbList.Flink;
+ Links != &IrpContext->ExclusiveFcbList;
+ Links = Links->Flink) {
+
+ ULONG Count;
+
+ NextFcb = (PFCB) CONTAINING_RECORD( Links,
+ FCB,
+ ExclusiveFcbLinks );
+
+ //
+ // If this is the Fcb for the Mft then remove it from the list.
+ //
+
+ if (NextFcb == Fcb->Vcb->QuotaTableScb->Fcb) {
+
+ //
+ // Free the snapshots for the Fcb and release the Fcb enough times
+ // to remove it from the list.
+ //
+
+ NtfsFreeSnapshotsForFcb( IrpContext, NextFcb );
+
+ Count = NextFcb->BaseExclusiveCount;
+
+ while (Count--) {
+
+ NtfsReleaseFcb( IrpContext, NextFcb );
+ }
+
+ break;
+ }
+ }
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's
+ // for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+#endif // _CAIRO_
+
+
+ NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &Lcb, &ParentScb, TRUE );
+ NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
+
+ //
+ // If there is no Ccb then look for one in the Lcb we just got.
+ //
+
+ if (!ARGUMENT_PRESENT( Ccb ) &&
+ ARGUMENT_PRESENT( Lcb )) {
+
+ PLIST_ENTRY Links;
+ PCCB NextCcb;
+
+ Links = Lcb->CcbQueue.Flink;
+
+ while (Links != &Lcb->CcbQueue) {
+
+ NextCcb = CONTAINING_RECORD( Links, CCB, LcbLinks );
+ if (!FlagOn( NextCcb->Flags,
+ CCB_FLAG_CLOSE | CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ Ccb = NextCcb;
+ break;
+ }
+
+ Links = Links->Flink;
+ }
+ }
+
+ //
+ // Now perform the dir notify call if there is a Ccb and this is not an
+ // open by FileId.
+ //
+
+ if (ARGUMENT_PRESENT( Ccb ) &&
+ (Fcb->Vcb->NotifyCount != 0) &&
+ (ParentScb != NULL) &&
+ !FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID )) {
+
+ FilterMatch = NtfsBuildDirNotifyFilter( IrpContext,
+ Fcb->InfoFlags | Lcb->InfoFlags );
+
+ if (FilterMatch != 0) {
+
+ NtfsReportDirNotify( IrpContext,
+ Fcb->Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ FILE_ACTION_MODIFIED,
+ ParentScb->Fcb );
+ }
+ }
+
+ NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
+ Fcb->InfoFlags = 0;
+
+ } except(FsRtlIsNtstatusExpected(GetExceptionCode()) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH) {
+
+ NOTHING;
+ }
+
+ return;
+}
diff --git a/private/ntos/cntfs/filobsup.c b/private/ntos/cntfs/filobsup.c
new file mode 100644
index 000000000..3b505d5ba
--- /dev/null
+++ b/private/ntos/cntfs/filobsup.c
@@ -0,0 +1,332 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ FilObSup.c
+
+Abstract:
+
+ This module implements the Ntfs File object support routines.
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_FILOBSUP)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsSetFileObject)
+#pragma alloc_text(PAGE, NtfsUpdateScbFromFileObject)
+#endif
+
+
+VOID
+NtfsSetFileObject (
+ IN PFILE_OBJECT FileObject,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN PSCB Scb,
+ IN PCCB Ccb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine sets the file system pointers within the file object
+
+Arguments:
+
+ FileObject - Supplies a pointer to the file object being modified.
+
+ TypeOfOpen - Supplies the type of open denoted by the file object.
+ This is only used by this procedure for sanity checking.
+
+ Scb - Supplies a pointer to Scb for the file object.
+
+ Ccb - Optionally supplies a pointer to a ccb
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_FILE_OBJECT( FileObject );
+ ASSERT_SCB( Scb );
+ ASSERT_OPTIONAL_CCB( Ccb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsSetFileObject, FileObject = %08lx\n", FileObject) );
+
+ //
+ // Load up the FileObject fields.
+ //
+
+ FileObject->FsContext = Scb;
+ FileObject->FsContext2 = Ccb;
+ FileObject->Vpb = Scb->Vcb->Vpb;
+
+ //
+ // Now store TypeOfOpen if there is a Ccb
+ //
+
+ ASSERT((Ccb != NULL) || (TypeOfOpen == StreamFileOpen) || (TypeOfOpen == UnopenedFileObject));
+ if (Ccb != NULL) {
+ Ccb->TypeOfOpen = (UCHAR)TypeOfOpen;
+ }
+
+ //
+ // If this file has the temporary attribute bit set, don't lazy
+ // write it unless absolutely necessary.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+ SetFlag( FileObject->Flags, FO_TEMPORARY_FILE );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsSetFileObject -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsUpdateScbFromFileObject (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN BOOLEAN CheckTimeStamps
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the Scb/Fcb to reflect the changes to
+ a file through the fast io path. It only called with a file object which
+ represents a user's handle.
+
+Arguments:
+
+ FileObject - This is the file object used in the fast io path.
+
+ Scb - This is the Scb for this stream.
+
+ CheckTimeStamps - Indicates whether we want to update the time stamps from the
+ fast io flags as well. This will be TRUE if our caller will update the standard information,
+ attribute header and duplicate info. FALSE if only the attribute header and duplicate info.
+ The latter case is the valid data length callback from the cache manager.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ PFCB Fcb = Scb->Fcb;
+ ULONG CcbFlags;
+ ULONG ScbFlags = 0;
+ LONGLONG CurrentTime;
+
+ PAGED_CODE();
+
+ //
+ // If the size of the main data stream is not part of the Fcb then update it
+ // now and set the correct Fcb flag.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ if (Fcb->Info.FileSize != Scb->Header.FileSize.QuadPart) {
+
+ Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_SIZE );
+ }
+
+ if (Fcb->Info.AllocatedLength != Scb->TotalAllocated) {
+
+ Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ if (FlagOn( FileObject->Flags, FO_FILE_SIZE_CHANGED )) {
+
+ SetFlag( ScbFlags, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ //
+ // Remember to update the size in the attribute header for named streams as well.
+ //
+
+ } else if (FlagOn( FileObject->Flags, FO_FILE_SIZE_CHANGED )) {
+
+ SetFlag( ScbFlags, SCB_STATE_NOTIFY_RESIZE_STREAM | SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ ClearFlag( FileObject->Flags, FO_FILE_SIZE_CHANGED );
+
+ //
+ // Check whether to update the time stamps if our caller requested it.
+ //
+
+ if (CheckTimeStamps && !FlagOn( FileObject->Flags, FO_CLEANUP_COMPLETE )) {
+
+ BOOLEAN UpdateLastAccess = FALSE;
+ BOOLEAN UpdateLastChange = FALSE;
+ BOOLEAN UpdateLastModify = FALSE;
+ BOOLEAN SetArchive = TRUE;
+
+ //
+ // Copy the Ccb flags to a local variable. Then we won't have to test
+ // for the existence of the Ccb each time.
+ //
+
+ CcbFlags = 0;
+
+ //
+ // Capture the real flags if present and clear them since we will update the Scb/Fcb.
+ //
+
+ if (FileObject->FsContext2 != NULL) {
+
+ CcbFlags = ((PCCB) FileObject->FsContext2)->Flags;
+ ClearFlag( ((PCCB) FileObject->FsContext2)->Flags,
+ (CCB_FLAG_UPDATE_LAST_MODIFY |
+ CCB_FLAG_UPDATE_LAST_CHANGE |
+ CCB_FLAG_SET_ARCHIVE) );
+ }
+
+ NtfsGetCurrentTime( IrpContext, CurrentTime );
+
+ //
+ // If there was a write to the file then update the last change, last access
+ // and last write and the archive bit.
+ //
+
+ if (FlagOn( FileObject->Flags, FO_FILE_MODIFIED )) {
+
+ UpdateLastModify =
+ UpdateLastAccess =
+ UpdateLastChange = TRUE;
+
+ //
+ // Otherwise test each of the individual bits in the file object and
+ // Ccb.
+ //
+
+ } else {
+
+ if (FlagOn( FileObject->Flags, FO_FILE_FAST_IO_READ )) {
+
+ UpdateLastAccess = TRUE;
+ }
+
+ if (FlagOn( CcbFlags, CCB_FLAG_UPDATE_LAST_CHANGE )) {
+
+ UpdateLastChange = TRUE;
+
+ if (FlagOn( CcbFlags, CCB_FLAG_UPDATE_LAST_MODIFY )) {
+
+ UpdateLastModify = TRUE;
+ }
+
+ if (!FlagOn( CcbFlags, CCB_FLAG_SET_ARCHIVE )) {
+
+ SetArchive = FALSE;
+ }
+ }
+ }
+
+ //
+ // Now set the correct Fcb bits.
+ //
+
+ if (UpdateLastChange) {
+
+ if (SetArchive) {
+
+ SetFlag( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_ARCHIVE );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ if (!FlagOn( CcbFlags, CCB_FLAG_USER_SET_LAST_CHANGE_TIME )) {
+
+ Fcb->Info.LastChangeTime = CurrentTime;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_CHANGE );
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ if (UpdateLastModify) {
+
+ //
+ // Remember a change to a named data stream.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA ) &&
+ (Scb->AttributeTypeCode == $DATA)) {
+
+ SetFlag( ScbFlags, SCB_STATE_NOTIFY_MODIFY_STREAM );
+ }
+
+ if (!FlagOn( CcbFlags, CCB_FLAG_USER_SET_LAST_MOD_TIME )) {
+
+ Fcb->Info.LastModificationTime = CurrentTime;
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_MOD );
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+ }
+ }
+
+ if (UpdateLastAccess &&
+ !FlagOn( CcbFlags, CCB_FLAG_USER_SET_LAST_ACCESS_TIME ) &&
+ !FlagOn( NtfsData.Flags, NTFS_FLAGS_DISABLE_LAST_ACCESS )) {
+
+ Fcb->CurrentLastAccess = CurrentTime;
+ }
+
+ //
+ // Clear all of the fast io flags in the file object.
+ //
+
+ ClearFlag( FileObject->Flags, FO_FILE_MODIFIED | FO_FILE_FAST_IO_READ );
+ }
+
+ //
+ // Now store the Scb flags into the Scb.
+ //
+
+ if (ScbFlags) {
+
+ NtfsAcquireFsrtlHeader( Scb );
+ SetFlag( Scb->ScbState, ScbFlags );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ return;
+}
+
diff --git a/private/ntos/cntfs/flush.c b/private/ntos/cntfs/flush.c
new file mode 100644
index 000000000..a1d3a8e49
--- /dev/null
+++ b/private/ntos/cntfs/flush.c
@@ -0,0 +1,1871 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Flush.c
+
+Abstract:
+
+ This module implements the flush buffers routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Tom Miller [TomM] 18-Jan-1992
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_FLUSH)
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_FLUSH)
+
+//
+// Macro to attempt to flush a stream from an Scb.
+//
+
+#define FlushScb(IRPC,SCB,IOS) { \
+ (IOS)->Status = NtfsFlushUserStream((IRPC),(SCB),NULL,0); \
+ NtfsNormalizeAndCleanupTransaction( IRPC, \
+ &(IOS)->Status, \
+ TRUE, \
+ STATUS_UNEXPECTED_IO_ERROR ); \
+ if (FlagOn((SCB)->ScbState, SCB_STATE_FILE_SIZE_LOADED)) { \
+ NtfsWriteFileSizes( (IRPC), \
+ (SCB), \
+ &(SCB)->Header.ValidDataLength.QuadPart, \
+ TRUE, \
+ TRUE ); \
+ } \
+}
+
+//
+// Local procedure prototypes
+//
+
+NTSTATUS
+NtfsFlushCompletionRoutine(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsFlushFcbFileRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonFlushBuffers)
+#pragma alloc_text(PAGE, NtfsFlushAndPurgeFcb)
+#pragma alloc_text(PAGE, NtfsFlushAndPurgeScb)
+#pragma alloc_text(PAGE, NtfsFlushFcbFileRecords)
+#pragma alloc_text(PAGE, NtfsFlushLsnStreams)
+#pragma alloc_text(PAGE, NtfsFlushVolume)
+#pragma alloc_text(PAGE, NtfsFsdFlushBuffers)
+#pragma alloc_text(PAGE, NtfsFlushUserStream)
+#endif
+
+
+NTSTATUS
+NtfsFsdFlushBuffers (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of flush buffers.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdFlushBuffers\n") );
+
+ //
+ // Call the common flush buffer routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonFlushBuffers( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdFlushBuffers -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonFlushBuffers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for flush buffers called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ PIO_STACK_LOCATION IrpSp;
+
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PLCB Lcb = NULL;
+ PSCB ParentScb = NULL;
+
+ PLIST_ENTRY Links;
+ PFCB NextFcb;
+ ULONG Count;
+
+ BOOLEAN VcbAcquired = FALSE;
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN ParentScbAcquired = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonFlushBuffers\n") );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("->FileObject = %08lx\n", IrpSp->FileObject) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ Status = STATUS_SUCCESS;
+
+ try {
+
+ //
+ // Case on the type of open that we are trying to flush
+ //
+
+ switch (TypeOfOpen) {
+
+ case UserFileOpen:
+#ifdef _CAIRO_
+ case UserPropertySetOpen:
+#endif // _CAIRO_
+
+ DebugTrace( 0, Dbg, ("Flush User File Open\n") );
+
+ //
+ // Acquire the Vcb so we can update the duplicate information as well.
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ //
+ // Make sure the data gets out to disk.
+ //
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+
+ //
+ // Acquire exclusive access to the Scb and enqueue the irp
+ // if we didn't get access
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ //
+ // Flush the stream and verify there were no errors.
+ //
+
+ FlushScb( IrpContext, Scb, &Irp->IoStatus );
+
+ //
+ // Now commit what we've done so far.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Update the time stamps and file sizes in the Fcb based on
+ // the state of the File Object.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, FileObject, Scb, TRUE );
+
+ //
+ // If we are to update standard information then do so now.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ }
+
+ //
+ // If this is the system hive there is more work to do. We want to flush
+ // all of the file records for this file as well as for the parent index
+ // stream. We also want to flush the parent index stream. Acquire the
+ // parent stream exclusively now so that the update duplicate call won't
+ // acquire it shared first.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_SYSTEM_HIVE )) {
+
+ //
+ // Start by acquiring all of the necessary files to avoid deadlocks.
+ //
+
+ if (Ccb->Lcb != NULL) {
+
+ ParentScb = Ccb->Lcb->Scb;
+
+ if (ParentScb != NULL) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ ParentScbAcquired = TRUE;
+ }
+ }
+ }
+
+ //
+ // Update the duplicate information if there are updates to apply.
+ //
+
+ if (FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
+
+ Lcb = Ccb->Lcb;
+
+ NtfsPrepareForUpdateDuplicate( IrpContext, Fcb, &Lcb, &ParentScb, TRUE );
+ NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
+ NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
+
+ if (ParentScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, ParentScb );
+ ParentScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // Now flush the file records for this stream.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_SYSTEM_HIVE )) {
+
+ //
+ // Flush the file records for this file.
+ //
+
+ Status = NtfsFlushFcbFileRecords( IrpContext, Scb->Fcb );
+
+ //
+ // Now flush the parent index stream.
+ //
+
+ if (NT_SUCCESS(Status) && (ParentScb != NULL)) {
+
+ CcFlushCache( &ParentScb->NonpagedScb->SegmentObject, NULL, 0, &Irp->IoStatus );
+ Status = Irp->IoStatus.Status;
+
+ //
+ // Finish by flushing the file records for the parent out
+ // to disk.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ Status = NtfsFlushFcbFileRecords( IrpContext, ParentScb->Fcb );
+ }
+ }
+ }
+
+ //
+ // If our status is still success then flush the log file and
+ // report any changes.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ ULONG FilterMatch;
+
+ LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ));
+
+ //
+ // We only want to do this DirNotify if we updated duplicate
+ // info and set the ParentScb.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID ) &&
+ (Vcb->NotifyCount != 0) &&
+ FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS )) {
+
+ FilterMatch = NtfsBuildDirNotifyFilter( IrpContext, Fcb->InfoFlags );
+
+ if (FilterMatch != 0) {
+
+ NtfsReportDirNotify( IrpContext,
+ Fcb->Vcb,
+ &Ccb->FullFileName,
+ Ccb->LastFileNameOffset,
+ NULL,
+ ((FlagOn( Ccb->Flags, CCB_FLAG_PARENT_HAS_DOS_COMPONENT ) &&
+ Ccb->Lcb != NULL &&
+ Ccb->Lcb->Scb->ScbType.Index.NormalizedName.Buffer != NULL) ?
+ &Ccb->Lcb->Scb->ScbType.Index.NormalizedName :
+ NULL),
+ FilterMatch,
+ FILE_ACTION_MODIFIED,
+ ParentScb->Fcb );
+ }
+ }
+
+ ClearFlag( Fcb->InfoFlags,
+ FCB_INFO_NOTIFY_FLAGS | FCB_INFO_DUPLICATE_FLAGS );
+ }
+
+ break;
+
+ case UserDirectoryOpen:
+
+ //
+ // If the user had opened the root directory then we'll
+ // oblige by flushing the volume.
+ //
+
+ if (NodeType(Scb) != NTFS_NTC_SCB_ROOT_INDEX) {
+
+ DebugTrace( 0, Dbg, ("Flush a directory does nothing\n") );
+ break;
+ }
+
+ case UserVolumeOpen:
+
+ DebugTrace( 0, Dbg, ("Flush User Volume Open\n") );
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ NtfsFlushVolume( IrpContext,
+ Vcb,
+ TRUE,
+ FALSE,
+ TRUE,
+ FALSE );
+
+ //
+ // Make sure all of the data written in the flush gets to disk.
+ //
+
+ LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ));
+ break;
+
+ case StreamFileOpen:
+
+ //
+ // Nothing to do here.
+ //
+
+ break;
+
+ default:
+
+ NtfsBugCheck( TypeOfOpen, 0, 0 );
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonFlushBuffers );
+
+ //
+ // Release any resources which were acquired.
+ //
+
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ if (ParentScbAcquired) {
+ NtfsReleaseScb( IrpContext, ParentScb );
+ }
+
+ if (VcbAcquired) {
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ //
+ // If this is a normal termination then pass the request on
+ // to the target device object.
+ //
+
+ if (!AbnormalTermination()) {
+
+ NTSTATUS DriverStatus;
+ PIO_STACK_LOCATION NextIrpSp;
+
+ //
+ // Free the IrpContext now before calling the lower driver. Do this
+ // now in case this fails so that we won't complete the Irp in our
+ // exception routine after passing it to the lower driver.
+ //
+
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ //
+ // Get the next stack location, and copy over the stack location
+ //
+
+ NextIrpSp = IoGetNextIrpStackLocation( Irp );
+
+ *NextIrpSp = *IrpSp;
+
+
+ //
+ // Set up the completion routine
+ //
+
+ IoSetCompletionRoutine( Irp,
+ NtfsFlushCompletionRoutine,
+ NULL,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Send the request.
+ //
+
+ DriverStatus = IoCallDriver(Vcb->TargetDeviceObject, Irp);
+
+ Status = (DriverStatus == STATUS_INVALID_DEVICE_REQUEST) ?
+ Status : DriverStatus;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonFlushBuffers -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFlushVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN FlushCache,
+ IN BOOLEAN PurgeFromCache,
+ IN BOOLEAN ReleaseAllFiles,
+ IN BOOLEAN MarkFilesForDismount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine non-recursively flushes a volume. This routine will always do
+ as much of the operation as possible. It will continue until getting a logfile
+ full. If any of the streams can't be flushed because of corruption then we
+ will try to flush the others. We will mark the volume dirty in this case.
+
+ We will pass the error code back to the caller because they often need to
+ proceed as best as possible (i.e. shutdown).
+
+Arguments:
+
+ Vcb - Supplies the volume to flush
+
+ FlushCache - Supplies TRUE if the caller wants to flush the data in the
+ cache to disk.
+
+ PurgeFromCache - Supplies TRUE if the caller wants the data purged from
+ the Cache (such as for autocheck!)
+
+ ReleaseAllFiles - Indicates that our caller would like to release all Fcb's
+ after TeardownStructures. This will prevent a deadlock when acquiring
+ paging io resource after a main resource which is held from a previous
+ teardown.
+
+Return Value:
+
+ STATUS_SUCCESS or else the first error status.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PFCB Fcb;
+ PFCB NextFcb;
+ PSCB Scb;
+ IO_STATUS_BLOCK IoStatus;
+
+ ULONG Pass;
+
+ BOOLEAN UserDataFile;
+ BOOLEAN RemovedFcb;
+ BOOLEAN DecrementScbCleanup = FALSE;
+ BOOLEAN DecrementNextFcbClose = FALSE;
+
+ BOOLEAN AcquiredFcb = FALSE;
+ BOOLEAN PagingIoAcquired = FALSE;
+ BOOLEAN ReleaseFiles = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFlushVolume, Vcb = %08lx\n", Vcb) );
+
+ //
+ // This operation must be able to wait.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Make sure there is nothing on the delayed close queue.
+ //
+
+ NtfsFspClose( Vcb );
+
+ //
+ // Acquire the Vcb exclusive. The Raise condition cannot happen.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ //
+ // Set the PURGE_IN_PROGRESS flag if this is a purge operation.
+ //
+
+ if (PurgeFromCache) {
+
+ SetFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS);
+ }
+
+ //
+ // Start by flushing the log file to assure Write-Ahead-Logging.
+ //
+
+ LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ) );
+
+ //
+ // There will be two passes through the Fcb's for the volume. On the
+ // first pass we just want to flush/purge the user data streams. On
+ // the second pass we want to flush the other streams. We hold off on
+ // several of the system files until after these two passes since they
+ // may be modified during the flush phases.
+ //
+
+ Pass = 0;
+
+ do {
+
+ PVOID RestartKey;
+
+ //
+ // Loop through all of the Fcb's in the Fcb table.
+ //
+
+ RestartKey = NULL;
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ NextFcb = Fcb = NtfsGetNextFcbTableEntry( Vcb, &RestartKey );
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ if (NextFcb != NULL) {
+
+ InterlockedIncrement( &NextFcb->CloseCount );
+ DecrementNextFcbClose = TRUE;
+ }
+
+ while (Fcb != NULL) {
+
+ //
+ // Acquire Paging I/O first, since we may be deleting or truncating.
+ // Testing for the PagingIoResource is not really safe without
+ // holding the main resource, so we correct for that below.
+ //
+
+ if (Fcb->PagingIoResource != NULL) {
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = TRUE;
+ }
+
+ //
+ // Let's acquire this Scb exclusively.
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, FALSE );
+ AcquiredFcb = TRUE;
+
+ //
+ // If we now do not see a paging I/O resource we are golden,
+ // othewise we can absolutely release and acquire the resources
+ // safely in the right order, since a resource in the Fcb is
+ // not going to go away.
+ //
+
+ if (!PagingIoAcquired && (Fcb->PagingIoResource != NULL)) {
+ NtfsReleaseFcb( IrpContext, Fcb );
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = TRUE;
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, FALSE );
+ }
+
+ //
+ // If this is not one of the special system files then perform
+ // the flush and purge as requested. Go ahead and test file numbers
+ // instead of walking through the Scbs in the Vcb just in case they
+ // have been deleted.
+ //
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) != MASTER_FILE_TABLE_NUMBER &&
+ NtfsSegmentNumber( &Fcb->FileReference ) != LOG_FILE_NUMBER &&
+ NtfsSegmentNumber( &Fcb->FileReference ) != VOLUME_DASD_NUMBER &&
+ NtfsSegmentNumber( &Fcb->FileReference ) != BIT_MAP_FILE_NUMBER &&
+ NtfsSegmentNumber( &Fcb->FileReference ) != BAD_CLUSTER_FILE_NUMBER) {
+
+ //
+ // We will walk through all of the Scb's for this Fcb. In
+ // the first pass we will only deal with user data streams.
+ // In the second pass we will do the others.
+ //
+
+ Scb = NULL;
+
+ while (TRUE) {
+
+ Scb = NtfsGetNextChildScb( Fcb, Scb );
+
+ if (Scb == NULL) { break; }
+
+ //
+ // Reference the Scb to keep it from going away.
+ //
+
+ InterlockedIncrement( &Scb->CleanupCount );
+ DecrementScbCleanup = TRUE;
+
+ //
+ // Check whether this is a user data file.
+ //
+
+ UserDataFile = FALSE;
+
+ if ((NodeType( Scb ) == NTFS_NTC_SCB_DATA) &&
+ (Scb->AttributeTypeCode == $DATA)) {
+
+ UserDataFile = TRUE;
+ }
+
+ //
+ // Process this Scb in the correct loop.
+ //
+
+ if ((Pass == 0) == (UserDataFile)) {
+
+ //
+ // Initialize the state of the Io to SUCCESS.
+ //
+
+ IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Don't put this Scb on the delayed close queue.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+
+ //
+ // Flush this stream if it is not already deleted.
+ // Also don't flush resident streams for system attributes.
+ //
+
+ if (FlushCache &&
+ !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) &&
+ (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT) ||
+ (Scb->AttributeTypeCode == $DATA))) {
+
+ //
+ // Enclose the flushes with try-except, so that we can
+ // react to log file full, and in any case keep on truckin.
+ //
+
+ try {
+
+ FlushScb( IrpContext, Scb, &IoStatus );
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // We will handle all errors except LOG_FILE_FULL and fatal
+ // bugcheck errors here. In the corruption case we will
+ // want to mark the volume dirty and continue.
+ //
+
+ } except( (((IoStatus.Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ !FsRtlIsNtstatusExpected( IoStatus.Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // To make sure that we can access all of our streams correctly,
+ // we first restore all of the higher sizes before aborting the
+ // transaction. Then we restore all of the lower sizes after
+ // the abort, so that all Scbs are finally restored.
+ //
+
+ NtfsRestoreScbSnapshots( IrpContext, TRUE );
+ NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
+ NtfsRestoreScbSnapshots( IrpContext, FALSE );
+
+ //
+ // Clear the top-level exception status so we won't raise
+ // later.
+ //
+
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+
+ //
+ // Remember the first error.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ Status = IoStatus.Status;
+ }
+
+ //
+ // If the current status is either DISK_CORRUPT or FILE_CORRUPT then
+ // mark the volume dirty. We clear the IoStatus to allow
+ // a corrupt file to be purged. Otherwise it will never
+ // leave memory.
+ //
+
+ if ((IoStatus.Status == STATUS_DISK_CORRUPT_ERROR) ||
+ (IoStatus.Status == STATUS_FILE_CORRUPT_ERROR)) {
+
+ NtfsMarkVolumeDirty( IrpContext, Vcb );
+ IoStatus.Status = STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // Proceed with the purge if there are no failures. We will
+ // purge if the flush revealed a corrupt file though.
+ //
+
+ if (PurgeFromCache
+ && IoStatus.Status == STATUS_SUCCESS) {
+
+ BOOLEAN DataSectionExists;
+ BOOLEAN ImageSectionExists;
+
+ DataSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL);
+ ImageSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.ImageSectionObject != NULL);
+
+ //
+ // Since purging the data section can cause the image
+ // section to go away, we will flush the image section first.
+ //
+
+ if (ImageSectionExists) {
+
+ (VOID)MmFlushImageSection( &Scb->NonpagedScb->SegmentObject, MmFlushForWrite );
+ }
+
+ if (DataSectionExists &&
+ !CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
+ NULL,
+ 0,
+ FALSE ) &&
+ (Status == STATUS_SUCCESS)) {
+
+ Status = STATUS_UNABLE_TO_DELETE_SECTION;
+ }
+ }
+
+ if (MarkFilesForDismount
+ && IoStatus.Status == STATUS_SUCCESS) {
+
+ //
+ // Set the dismounted flag for this stream so we
+ // know we have to fail reads & writes to it.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED );
+
+ // Also mark the Scb as not allowing fast io --
+ // this ensures that the file system will get a
+ // chance to see all reads & writes to this stream.
+ //
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ Scb->Header.IsFastIoPossible = FastIoIsNotPossible;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+ }
+
+ //
+ // Move to the next Scb.
+ //
+
+ InterlockedDecrement( &Scb->CleanupCount );
+ DecrementScbCleanup = FALSE;
+ }
+ }
+
+ //
+ // Remove our reference to the current Fcb.
+ //
+
+ InterlockedDecrement( &NextFcb->CloseCount );
+ DecrementNextFcbClose = FALSE;
+
+ //
+ // Get the next Fcb and reference it so it won't go away.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ NextFcb = NtfsGetNextFcbTableEntry( Vcb, &RestartKey );
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ if (NextFcb != NULL) {
+
+ InterlockedIncrement( &NextFcb->CloseCount );
+ DecrementNextFcbClose = TRUE;
+ }
+
+ //
+ // Flushing the volume can cause new file objects to be allocated.
+ // If we are in the second pass and the Fcb is for a user file
+ // or directory then try to perform a teardown on this.
+ //
+
+ RemovedFcb = FALSE;
+ if ((Pass == 1) &&
+ (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER)) {
+
+ ASSERT( IrpContext->TransactionId == 0 );
+
+ NtfsTeardownStructures( IrpContext,
+ Fcb,
+ NULL,
+ FALSE,
+ FALSE,
+ &RemovedFcb );
+
+ //
+ // TeardownStructures can create a transaction. Commit
+ // it if present.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ }
+ }
+
+ //
+ // If the Fcb is still around then free any of the the other
+ // resources we have acquired.
+ //
+
+ if (!RemovedFcb) {
+
+ //
+ // Free the snapshots for the current Fcb. This will keep us
+ // from having a snapshot for all open attributes in the
+ // system.
+ //
+
+ NtfsFreeSnapshotsForFcb( IrpContext, Fcb );
+
+ if (PagingIoAcquired) {
+ ASSERT( IrpContext->TransactionId == 0 );
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+
+ if (AcquiredFcb) {
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+ }
+
+ //
+ // If our caller wants to insure that all files are released
+ // between flushes then walk through the exclusive Fcb list
+ // and free everything.
+ //
+
+ if (ReleaseAllFiles) {
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+ }
+
+ PagingIoAcquired = FALSE;
+ AcquiredFcb = FALSE;
+
+ //
+ // Now move to the next Fcb.
+ //
+
+ Fcb = NextFcb;
+ }
+
+ } while (++Pass < 2);
+
+ //
+ // Make sure that all of the delayed or async closes for this Vcb are gone.
+ //
+
+ if (PurgeFromCache) {
+
+ NtfsFspClose( Vcb );
+ }
+
+ //
+ // Now we want to flush/purge the streams for volume bitmap and then the Mft.
+ //
+
+ Pass = 0;
+
+ if (Vcb->BitmapScb != NULL) {
+
+ Fcb = Vcb->BitmapScb->Fcb;
+
+ } else {
+
+ Fcb = NULL;
+ }
+
+ do {
+
+ PSCB ThisScb;
+
+ if (Fcb != NULL) {
+
+ //
+ // Go through each Scb for each of these Fcb's.
+ //
+
+ ThisScb = NtfsGetNextChildScb( Fcb, NULL );
+
+ while (ThisScb != NULL) {
+
+ Scb = NtfsGetNextChildScb( Fcb, ThisScb );
+
+ //
+ // Initialize the state of the Io to SUCCESS.
+ //
+
+ IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Reference the next Scb to keep it from going away if
+ // we purge the current one.
+ //
+
+ if (Scb != NULL) {
+
+ InterlockedIncrement( &Scb->CleanupCount );
+ DecrementScbCleanup = TRUE;
+ }
+
+ if (FlushCache) {
+
+ //
+ // Flush Bitmap or Mft
+ //
+
+ CcFlushCache( &ThisScb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+
+ if (!NT_SUCCESS( IoStatus.Status )) {
+
+ Status = IoStatus.Status;
+ }
+
+ //
+ // Use a try-except to commit the current transaction.
+ //
+
+ try {
+
+ NtfsCleanupTransaction( IrpContext, IoStatus.Status, TRUE );
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // We will handle all errors except LOG_FILE_FULL and fatal
+ // bugcheck errors here. In the corruption case we will
+ // want to mark the volume dirty and continue.
+ //
+
+ } except( (((IoStatus.Status = GetExceptionCode()) == STATUS_LOG_FILE_FULL) ||
+ !FsRtlIsNtstatusExpected( IoStatus.Status ))
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ //
+ // To make sure that we can access all of our streams correctly,
+ // we first restore all of the higher sizes before aborting the
+ // transaction. Then we restore all of the lower sizes after
+ // the abort, so that all Scbs are finally restored.
+ //
+
+ NtfsRestoreScbSnapshots( IrpContext, TRUE );
+ NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
+ NtfsRestoreScbSnapshots( IrpContext, FALSE );
+
+ //
+ // Clear the top-level exception status so we won't raise
+ // later.
+ //
+
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+
+ //
+ // Remember the first error.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ Status = IoStatus.Status;
+ }
+
+ //
+ // If the current status is either DISK_CORRUPT or FILE_CORRUPT then
+ // mark the volume dirty. We clear the IoStatus to allow
+ // a corrupt file to be purged. Otherwise it will never
+ // leave memory.
+ //
+
+ if ((IoStatus.Status == STATUS_DISK_CORRUPT_ERROR) ||
+ (IoStatus.Status == STATUS_FILE_CORRUPT_ERROR)) {
+
+ NtfsMarkVolumeDirty( IrpContext, Vcb );
+ IoStatus.Status = STATUS_SUCCESS;
+ }
+ }
+ }
+
+ //
+ // Purge this stream if there have been no errors.
+ //
+
+ if (PurgeFromCache
+ && IoStatus.Status == STATUS_SUCCESS) {
+
+ if (!CcPurgeCacheSection( &ThisScb->NonpagedScb->SegmentObject,
+ NULL,
+ 0,
+ FALSE ) &&
+ (Status == STATUS_SUCCESS)) {
+
+ Status = STATUS_UNABLE_TO_DELETE_SECTION;
+ }
+ }
+
+ //
+ // Remove any reference we have to the next Scb and move
+ // forward to the next Scb.
+ //
+
+ if (DecrementScbCleanup) {
+
+ InterlockedDecrement( &Scb->CleanupCount );
+ DecrementScbCleanup = FALSE;
+ }
+
+ ThisScb = Scb;
+ }
+ }
+
+ if (Vcb->MftScb != NULL) {
+
+ Fcb = Vcb->MftScb->Fcb;
+
+ //
+ // If we are purging the MFT then acquire all files to
+ // avoid a purge deadlock. If someone create an MFT mapping
+ // between the flush and purge then the purge can spin
+ // indefinitely in CC.
+ //
+
+ if (PurgeFromCache && !ReleaseFiles) {
+
+ NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE );
+ ReleaseFiles = TRUE;
+ }
+
+ } else {
+
+ Fcb = NULL;
+ }
+
+ } while (++Pass < 2);
+
+ } finally {
+
+ //
+ // If this is a purge then clear the purge flag and set flag to force
+ // rescan of volume bitmap.
+ //
+
+ if (PurgeFromCache) {
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
+ SetFlag( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS );
+ }
+
+ //
+ // Restore any counts we may have incremented to reference
+ // in-memory structures.
+ //
+
+ if (DecrementScbCleanup) {
+
+ InterlockedDecrement( &Scb->CleanupCount );
+ }
+
+ if (DecrementNextFcbClose) {
+
+ InterlockedDecrement( &NextFcb->CloseCount );
+ }
+
+ if (PagingIoAcquired) {
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+
+ if (AcquiredFcb) {
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+
+ if (ReleaseFiles) {
+
+ NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
+ }
+
+ //
+ // Release the Vcb now.
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFlushVolume -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFlushLsnStreams (
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine non-recursively flushes all of the Lsn streams in the open
+ attribute table. It assumes that the files have all been acquired
+ exclusive prior to this call. It also assumes our caller will provide the
+ synchronization for the open attribute table.
+
+Arguments:
+
+ Vcb - Supplies the volume to flush
+
+Return Value:
+
+ STATUS_SUCCESS or else the most recent error status
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ IO_STATUS_BLOCK IoStatus;
+
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+ PSCB Scb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFlushLsnStreams, Vcb = %08lx\n", Vcb) );
+
+ //
+ // Start by flushing the log file to assure Write-Ahead-Logging.
+ //
+
+ LfsFlushToLsn( Vcb->LogHandle, LfsQueryLastLsn( Vcb->LogHandle ) );
+
+ //
+ // Loop through to flush all of the streams in the open attribute table.
+ // We skip the Mft and mirror so they get flushed last.
+ //
+
+ AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ while (AttributeEntry != NULL) {
+
+ Scb = AttributeEntry->Overlay.Scb;
+
+ //
+ // Skip the Mft, its mirror and any deleted streams. If the header
+ // is uninitialized for this stream then it means that the
+ // attribute doesn't exist (INDEX_ALLOCATION where the create failed)
+ // or the attribute is now resident.
+ //
+
+ if (Scb != NULL
+ && Scb != Vcb->MftScb
+ && Scb != Vcb->Mft2Scb
+ && Scb != Vcb->BadClusterFileScb
+ && !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )
+ && FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Now flush the stream. We don't worry about file sizes because
+ // any logged stream should have the file size already in the log.
+ //
+
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+
+ if (!NT_SUCCESS( IoStatus.Status )) {
+
+ ASSERTMSG( "Failed to flush stream for clean checkpoint\n", FALSE );
+ Status = IoStatus.Status;
+ }
+
+ }
+
+ AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+ }
+
+ //
+ // Now we do the Mft. Flushing the Mft will automatically update the mirror.
+ //
+
+ if (Vcb->MftScb != NULL) {
+
+ IoStatus.Status = STATUS_SUCCESS;
+
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+
+ if (!NT_SUCCESS( IoStatus.Status )) {
+
+ ASSERTMSG( "Failed to flush Mft stream for clean checkpoint\n", FALSE );
+ Status = IoStatus.Status;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFlushLsnStreams -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+VOID
+NtfsFlushAndPurgeFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will flush and purge all of the open streams for an
+ Fcb. It is indended to prepare this Fcb such that a teardown will
+ remove this Fcb for the tree. The caller has guaranteed that the
+ Fcb can't go away.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to flush
+
+Return Value:
+
+ None. The caller calls teardown structures and checks the result.
+
+--*/
+
+{
+ IO_STATUS_BLOCK IoStatus;
+ BOOLEAN DecrementNextScbCleanup = FALSE;
+
+ PSCB Scb;
+ PSCB AttributeListScb;
+ PSCB NextScb;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Get the first Scb for the Fcb.
+ //
+
+ Scb = NtfsGetNextChildScb( Fcb, NULL );
+
+ while (Scb != NULL) {
+
+ BOOLEAN DataSectionExists;
+ BOOLEAN ImageSectionExists;
+
+ NextScb = NtfsGetNextChildScb( Fcb, Scb );
+
+ //
+ // Save the attribute list for last so we don't purge it
+ // and then bring it back for another attribute.
+ //
+
+ if ((Scb->AttributeTypeCode == $ATTRIBUTE_LIST) &&
+ (NextScb != NULL)) {
+
+ RemoveEntryList( &Scb->FcbLinks );
+ InsertTailList( &Fcb->ScbQueue, &Scb->FcbLinks );
+
+ Scb = NextScb;
+ continue;
+ }
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ FlushScb( IrpContext, Scb, &IoStatus );
+ }
+
+ //
+ // The call to purge below may generate a close call.
+ // We increment the cleanup count of the next Scb to prevent
+ // it from going away in a TearDownStructures as part of that
+ // close.
+ //
+
+ DataSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.DataSectionObject != NULL);
+ ImageSectionExists = (BOOLEAN)(Scb->NonpagedScb->SegmentObject.ImageSectionObject != NULL);
+
+ if (NextScb != NULL) {
+
+ InterlockedIncrement( &NextScb->CleanupCount );
+ DecrementNextScbCleanup = TRUE;
+ }
+
+ if (ImageSectionExists) {
+
+ (VOID)MmFlushImageSection( &Scb->NonpagedScb->SegmentObject, MmFlushForWrite );
+ }
+
+ if (DataSectionExists) {
+
+ CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
+ NULL,
+ 0,
+ FALSE );
+ }
+
+ //
+ // Decrement the cleanup count of the next Scb if we incremented
+ // it.
+ //
+
+ if (DecrementNextScbCleanup) {
+
+ InterlockedDecrement( &NextScb->CleanupCount );
+ DecrementNextScbCleanup = FALSE;
+ }
+
+ //
+ // Move to the next Scb.
+ //
+
+ Scb = NextScb;
+ }
+
+ } finally {
+
+ //
+ // Restore any counts we may have incremented to reference
+ // in-memory structures.
+ //
+
+ if (DecrementNextScbCleanup) {
+
+ InterlockedDecrement( &NextScb->CleanupCount );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsFlushAndPurgeScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PSCB ParentScb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to flush and purge a stream. It is used
+ when there are now only non-cached handles on a file and there is
+ a data section. Flushing and purging the data section will mean that
+ the user non-cached io won't have to block for the cache coherency calls.
+
+ We want to remove all of the Fcb's from the exclusive list so that the
+ lower level flush will be its own transaction. We don't want to drop
+ any of the resources however so we acquire the Scb's above explicitly
+ and then empty the exclusive list. In all cases we will reacquire the
+ Scb's before raising out of this routine.
+
+Arguments:
+
+ Scb - Scb for the stream to flush and purge. The reference count on this
+ stream will prevent it from going away.
+
+ ParentScb - If specified then this is the parent for the stream being flushed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ IO_STATUS_BLOCK Iosb;
+
+ PAGED_CODE();
+
+ //
+ // Commit the current transaction.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Acquire the Scb and ParentScb explicitly.
+ //
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+
+ if (ARGUMENT_PRESENT( ParentScb )) {
+
+ ExAcquireResourceExclusive( ParentScb->Header.Resource, TRUE );
+ }
+
+ //
+ // Walk through and release all of the Fcb's in the Fcb list.
+ //
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Use a try-finally to reacquire the Scbs.
+ //
+
+ try {
+
+ //
+ // Perform the flush, raise on error.
+ //
+
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &Iosb );
+ NtfsNormalizeAndCleanupTransaction( IrpContext, &Iosb.Status, TRUE, STATUS_UNEXPECTED_IO_ERROR );
+
+ //
+ // If no error then purge the section.
+ //
+
+ CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject, NULL, 0, FALSE );
+
+ } finally {
+
+ //
+ // Reacquire the Scbs.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ExReleaseResource( Scb->Header.Resource );
+
+ if (ARGUMENT_PRESENT( ParentScb )) {
+
+ NtfsAcquireExclusiveScb( IrpContext, ParentScb );
+ ExReleaseResource( ParentScb->Header.Resource );
+ }
+ }
+
+ //
+ // Write the file sizes to the attribute. Commit the transaction since the
+ // file sizes must get to disk.
+ //
+
+ NtfsWriteFileSizes( IrpContext, Scb, &Scb->Header.ValidDataLength.QuadPart, TRUE, TRUE );
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsFlushCompletionRoutine(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+{
+ UNREFERENCED_PARAMETER( DeviceObject );
+ UNREFERENCED_PARAMETER( Contxt );
+
+ //
+ // Add the hack-o-ramma to fix formats.
+ //
+
+ if ( Irp->PendingReturned ) {
+
+ IoMarkIrpPending( Irp );
+ }
+
+ //
+ // If the Irp got STATUS_INVALID_DEVICE_REQUEST, normalize it
+ // to STATUS_SUCCESS.
+ //
+
+ if (Irp->IoStatus.Status == STATUS_INVALID_DEVICE_REQUEST) {
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsFlushFcbFileRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to flush the file records for a given file. It is
+ intended to flush the critical file records for the system hives.
+
+Arguments:
+
+ Fcb - This is the Fcb to flush.
+
+Return Value:
+
+ NTSTATUS - The status returned from the flush operation.
+
+--*/
+
+{
+ IO_STATUS_BLOCK IoStatus;
+ BOOLEAN MoreToGo;
+
+ LONGLONG LastFileOffset = MAXLONGLONG;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ IoStatus.Status = STATUS_SUCCESS;
+
+ //
+ // Use a try-finally to cleanup the context.
+ //
+
+ try {
+
+ //
+ // Find the first. It should be there.
+ //
+
+ MoreToGo = NtfsLookupAttribute( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ &AttrContext );
+
+ if (!MoreToGo) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ while (MoreToGo) {
+
+ if (AttrContext.FoundAttribute.MftFileOffset != LastFileOffset) {
+
+ LastFileOffset = AttrContext.FoundAttribute.MftFileOffset;
+
+ CcFlushCache( &Fcb->Vcb->MftScb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER) &LastFileOffset,
+ Fcb->Vcb->BytesPerFileRecordSegment,
+ &IoStatus );
+
+ if (!NT_SUCCESS( IoStatus.Status )) {
+
+ IoStatus.Status = FsRtlNormalizeNtstatus( IoStatus.Status,
+ STATUS_UNEXPECTED_IO_ERROR );
+ break;
+ }
+ }
+
+ MoreToGo = NtfsLookupNextAttribute( IrpContext,
+ Fcb,
+ &AttrContext );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsFlushFcbFileRecords );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ return IoStatus.Status;
+}
+
+
+NTSTATUS
+NtfsFlushUserStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLONGLONG FileOffset OPTIONAL,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine flushes a user stream as a top-level action. To do so
+ it checkpoints the current transaction first and frees all of the
+ caller's snapshots. After doing the flush, it snapshots the input
+ Scb again, just in case the caller plans to do any more work on that
+ stream. If the caller needs to modify any other streams (presumably
+ metadata), it must know to snapshot them itself after calling this
+ routine.
+
+Arguments:
+
+ Scb - Stream to flush
+
+ FileOffset - FileOffset at which the flush is to start, or NULL for
+ entire stream.
+
+ Length - Number of bytes to flush. Ignored if FileOffset not specified.
+
+Return Value:
+
+ Status of the flush
+
+--*/
+
+{
+ IO_STATUS_BLOCK IoStatus;
+ BOOLEAN ScbAcquired = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Checkpoint the current transaction and free all of its snapshots,
+ // in order to treat the flush as a top-level action with his own
+ // snapshots, etc.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+ NtfsFreeSnapshotsForFcb( IrpContext, NULL );
+
+ //
+ // Set the wait flag in the IrpContext so we don't hit a case where the
+ // reacquire below fails because we can't wait. If our caller was asynchronous
+ // and we get this far we will continue synchronously.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // We must free the Scb now before calling through MM to prevent
+ // collided page deadlocks.
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ ScbAcquired = ExIsResourceAcquiredExclusive( Scb->Header.Resource );
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ // ASSERT( !ExIsResourceAcquiredExclusive(Scb->Header.Resource) );
+ }
+ }
+
+ //
+ // Now do the flush he wanted as a top-level action
+ //
+
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject, (PLARGE_INTEGER)FileOffset, Length, &IoStatus );
+
+ //
+ // Now reacquire for the caller.
+ //
+
+ if (ScbAcquired) {
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ }
+
+ return IoStatus.Status;
+}
diff --git a/private/ntos/cntfs/fsctrl.c b/private/ntos/cntfs/fsctrl.c
new file mode 100644
index 000000000..5d8a1f7ad
--- /dev/null
+++ b/private/ntos/cntfs/fsctrl.c
@@ -0,0 +1,8366 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ FsCtrl.c
+
+Abstract:
+
+ This module implements the File System Control routines for Ntfs called
+ by the dispatch driver.
+
+Author:
+
+ Gary Kimura [GaryKi] 29-Aug-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#ifdef NTFS_CHECK_BITMAP
+BOOLEAN NtfsCopyBitmap = TRUE;
+#endif
+
+#ifdef _CAIRO_
+VOID
+NtOfsIndexTest (
+ PIRP_CONTEXT IrpContext,
+ PFCB TestFcb
+ );
+#endif _CAIRO_
+
+//
+// Temporarily reference our local attribute definitions
+//
+
+extern ATTRIBUTE_DEFINITION_COLUMNS NtfsAttributeDefinitions[];
+
+//
+//**** The following variable is only temporary and is used to disable NTFS
+//**** from mounting any volumes
+//
+
+BOOLEAN NtfsDisable = FALSE;
+
+#ifdef _CAIRO_
+//
+// ***** The following is used to determine whether to update to version 2.0.
+// ***** We don't want to do this until chkdsk will check the volume.
+//
+
+BOOLEAN NtfsUpdateTo20 = FALSE;
+#endif
+
+//
+// The following is used to determine when to move to compressed files.
+//
+
+BOOLEAN NtfsDefragMftEnabled = FALSE;
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_FSCTRL)
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_FSCTRL)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('fFtN')
+
+//
+// Local procedure prototypes
+//
+
+NTSTATUS
+NtfsMountVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsVerifyVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsUserFsRequest (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsOplockRequest (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsLockVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsUnlockVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsDismountVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsDirtyVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+BOOLEAN
+NtfsGetDiskGeometry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObjectWeTalkTo,
+ IN PDISK_GEOMETRY DiskGeometry,
+ IN PPARTITION_INFORMATION PartitionInfo
+ );
+
+VOID
+NtfsReadBootSector (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PSCB *BootScb,
+ OUT PBCB *BootBcb,
+ OUT PVOID *BootSector
+ );
+
+BOOLEAN
+NtfsIsBootSectorNtfs (
+ IN PPACKED_BOOT_SECTOR BootSector,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsGetVolumeLabel (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVPB Vpb OPTIONAL,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsSetAndGetVolumeTimes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN MarkDirty
+ );
+
+VOID
+NtfsOpenSystemFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB *Scb,
+ IN PVCB Vcb,
+ IN ULONG FileNumber,
+ IN LONGLONG Size,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN BOOLEAN ModifiedNoWrite
+ );
+
+VOID
+NtfsOpenRootDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+NTSTATUS
+NtfsQueryRetrievalPointers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsGetCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+VOID
+NtfsChangeAttributeCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVCB Vcb,
+ IN PCCB Ccb,
+ IN USHORT CompressionState
+ );
+
+NTSTATUS
+NtfsSetCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsReadCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsWriteCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsMarkAsSystemHive (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsGetStatistics (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+#define NtfsMapPageInBitmap(A,B,C,D,E,F) NtfsMapOrPinPageInBitmap(A,B,C,D,E,F,FALSE)
+
+#define NtfsPinPageInBitmap(A,B,C,D,E,F) NtfsMapOrPinPageInBitmap(A,B,C,D,E,F,TRUE)
+
+VOID
+NtfsMapOrPinPageInBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ OUT PLCN StartingLcn,
+ IN OUT PRTL_BITMAP Bitmap,
+ OUT PBCB *BitmapBcb,
+ IN BOOLEAN AlsoPinData
+ );
+
+BOOLEAN
+NtfsAddRecentlyDeallocated (
+ IN PVCB Vcb,
+ IN LCN Lcn,
+ IN OUT PRTL_BITMAP Bitmap
+ );
+
+#define BYTES_PER_PAGE (PAGE_SIZE)
+#define BITS_PER_PAGE (BYTES_PER_PAGE * 8)
+
+NTSTATUS
+NtfsGetVolumeData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsGetVolumeBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsGetRetrievalPointers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsGetMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsMoveFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsIsVolumeDirty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsSetExtendedDasdIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonFileSystemControl)
+#pragma alloc_text(PAGE, NtfsDirtyVolume)
+#pragma alloc_text(PAGE, NtfsDismountVolume)
+#pragma alloc_text(PAGE, NtfsFsdFileSystemControl)
+#pragma alloc_text(PAGE, NtfsGetDiskGeometry)
+#pragma alloc_text(PAGE, NtfsGetVolumeLabel)
+#pragma alloc_text(PAGE, NtfsIsBootSectorNtfs)
+#pragma alloc_text(PAGE, NtfsIsVolumeDirty)
+#pragma alloc_text(PAGE, NtfsLockVolume)
+#pragma alloc_text(PAGE, NtfsMarkAsSystemHive)
+#pragma alloc_text(PAGE, NtfsMountVolume)
+#pragma alloc_text(PAGE, NtfsOpenRootDirectory)
+#pragma alloc_text(PAGE, NtfsOpenSystemFile)
+#pragma alloc_text(PAGE, NtfsOplockRequest)
+#pragma alloc_text(PAGE, NtfsReadBootSector)
+#pragma alloc_text(PAGE, NtfsSetAndGetVolumeTimes)
+#pragma alloc_text(PAGE, NtfsSetTotalAllocatedField)
+#pragma alloc_text(PAGE, NtfsUnlockVolume)
+#pragma alloc_text(PAGE, NtfsUserFsRequest)
+#pragma alloc_text(PAGE, NtfsVerifyVolume)
+#pragma alloc_text(PAGE, NtfsQueryRetrievalPointers)
+#pragma alloc_text(PAGE, NtfsGetCompression)
+#pragma alloc_text(PAGE, NtfsSetCompression)
+#pragma alloc_text(PAGE, NtfsReadCompression)
+#pragma alloc_text(PAGE, NtfsWriteCompression)
+#pragma alloc_text(PAGE, NtfsGetStatistics)
+#pragma alloc_text(PAGE, NtfsGetVolumeData)
+#pragma alloc_text(PAGE, NtfsGetVolumeBitmap)
+#pragma alloc_text(PAGE, NtfsGetRetrievalPointers)
+#pragma alloc_text(PAGE, NtfsGetMftRecord)
+#pragma alloc_text(PAGE, NtfsMoveFile)
+#pragma alloc_text(PAGE, NtfsSetExtendedDasdIo)
+#endif
+
+
+NTSTATUS
+NtfsFsdFileSystemControl (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of File System Control.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ BOOLEAN Wait;
+ BOOLEAN Retry = FALSE;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdFileSystemControl\n") );
+
+ //
+ // Call the common File System Control routine, with blocking allowed if
+ // synchronous. This opeation needs to special case the mount
+ // and verify suboperations because we know they are allowed to block.
+ // We identify these suboperations by looking at the file object field
+ // and seeing if its null.
+ //
+
+ if (IoGetCurrentIrpStackLocation(Irp)->FileObject == NULL) {
+
+ Wait = TRUE;
+
+ } else {
+
+ Wait = CanFsdWait( Irp );
+ }
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, Wait );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ Retry = TRUE;
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ IrpSp = IoGetCurrentIrpStackLocation(Irp);
+
+ if (IrpSp->MinorFunction == IRP_MN_MOUNT_VOLUME) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ } else {
+
+ //
+ // The SetCompression control is a long-winded function that has
+ // to rewrite the entire stream, and has to tolerate log file full
+ // conditions. If this is the first pass through we initialize some
+ // fields in the NextIrpSp to allow us to resume the set compression
+ // operation.
+ //
+ // David Goebel 1/3/96: Changed to next stack location so that we
+ // don't wipe out buffer length values. These Irps are never
+ // dispatched, so the next stack location will not be disturbed.
+ //
+
+ if ((IrpSp->MinorFunction == IRP_MN_USER_FS_REQUEST) &&
+ ((IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_SET_COMPRESSION) ||
+ (IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_MOVE_FILE))) {
+
+ if (!Retry) {
+
+ PIO_STACK_LOCATION NextIrpSp;
+ NextIrpSp = IoGetNextIrpStackLocation( Irp );
+
+ NextIrpSp->Parameters.FileSystemControl.OutputBufferLength = MAXULONG;
+ NextIrpSp->Parameters.FileSystemControl.InputBufferLength = MAXULONG;
+ }
+ }
+
+ Status = NtfsCommonFileSystemControl( IrpContext, Irp );
+ }
+
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdFileSystemControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonFileSystemControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for File System Control called by both the
+ fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonFileSystemControl\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // We know this is a file system control so we'll case on the
+ // minor function, and call a internal worker routine to complete
+ // the irp.
+ //
+
+ switch (IrpSp->MinorFunction) {
+
+ case IRP_MN_MOUNT_VOLUME:
+
+ Status = NtfsMountVolume( IrpContext, Irp );
+ break;
+
+ case IRP_MN_USER_FS_REQUEST:
+
+ Status = NtfsUserFsRequest( IrpContext, Irp );
+ break;
+
+ default:
+
+ DebugTrace( 0, Dbg, ("Invalid Minor Function %08lx\n", IrpSp->MinorFunction) );
+ NtfsCompleteRequest( &IrpContext, &Irp, Status = STATUS_INVALID_DEVICE_REQUEST );
+ break;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsCommonFileSystemControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsMountVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the mount volume operation. It is responsible for
+ either completing of enqueuing the input Irp.
+
+ Its job is to verify that the volume denoted in the IRP is an NTFS volume,
+ and create the VCB and root SCB/FCB structures. The algorithm it uses is
+ essentially as follows:
+
+ 1. Create a new Vcb Structure, and initialize it enough to do cached
+ volume file I/O.
+
+ 2. Read the disk and check if it is an NTFS volume.
+
+ 3. If it is not an NTFS volume then free the cached volume file, delete
+ the VCB, and complete the IRP with STATUS_UNRECOGNIZED_VOLUME
+
+ 4. Check if the volume was previously mounted and if it was then do a
+ remount operation. This involves freeing the cached volume file,
+ delete the VCB, hook in the old VCB, and complete the IRP.
+
+ 5. Otherwise create a root SCB, recover the volume, create Fsp threads
+ as necessary, and complete the IRP.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ PDEVICE_OBJECT DeviceObjectWeTalkTo;
+ PVPB Vpb;
+
+ PVOLUME_DEVICE_OBJECT VolDo;
+ PVCB Vcb;
+
+ PBCB BootBcb = NULL;
+ PPACKED_BOOT_SECTOR BootSector;
+ PSCB BootScb = NULL;
+#ifdef _CAIRO_
+ PSCB QuotaDataScb = NULL;
+#endif // _CAIRO_
+
+ POBJECT_NAME_INFORMATION DeviceObjectName = NULL;
+ ULONG DeviceObjectNameLength;
+
+ PBCB Bcbs[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+ ULONG FirstNonMirroredCluster;
+ ULONG MirroredMftRange;
+
+ ULONG i;
+
+ IO_STATUS_BLOCK IoStatus;
+
+ BOOLEAN UpdatesApplied;
+ BOOLEAN VcbAcquired = FALSE;
+ BOOLEAN MountFailed = TRUE;
+ BOOLEAN CloseAttributes = FALSE;
+ BOOLEAN UpdateVersion = FALSE;
+ BOOLEAN WriteProtected;
+
+ LONGLONG LlTemp1;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ //**** The following code is only temporary and is used to disable NTFS
+ //**** from mounting any volumes
+ //
+
+ if (NtfsDisable) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_UNRECOGNIZED_VOLUME );
+ return STATUS_UNRECOGNIZED_VOLUME;
+ }
+
+ //
+ // Reject floppies
+ //
+
+ if (FlagOn( IoGetCurrentIrpStackLocation(Irp)->
+ Parameters.MountVolume.Vpb->
+ RealDevice->Characteristics, FILE_FLOPPY_DISKETTE ) ) {
+
+ Irp->IoStatus.Information = 0;
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_UNRECOGNIZED_VOLUME );
+ return STATUS_UNRECOGNIZED_VOLUME;
+ }
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsMountVolume\n") );
+
+ //
+ // Save some references to make our life a little easier
+ //
+
+ DeviceObjectWeTalkTo = IrpSp->Parameters.MountVolume.DeviceObject;
+ Vpb = IrpSp->Parameters.MountVolume.Vpb;
+
+ ClearFlag( Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+
+ //
+ // Acquire exclusive global access
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+
+ //
+ // Now is a convenient time to look through the queue of Vcb's to see if there
+ // are any which can be deleted.
+ //
+
+ try {
+
+ PLIST_ENTRY Links;
+
+ for (Links = NtfsData.VcbQueue.Flink;
+ Links != &NtfsData.VcbQueue;
+ Links = Links->Flink) {
+
+ Vcb = CONTAINING_RECORD( Links, VCB, VcbLinks );
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) &&
+ (Vcb->CloseCount == 0) &&
+ FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT ) &&
+ (Vcb->LogFileObject != NULL)) {
+
+ //
+ // Now we can check to see if we should perform the teardown
+ // on this Vcb. The release Vcb routine below can do all of
+ // the checks correctly. Make this appear to from a close
+ // call since there is no special biasing for this case.
+ //
+
+ IrpContext->Vcb = Vcb;
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_DELETE_UNDERWAY )) {
+
+ NtfsReleaseGlobal( IrpContext );
+
+ NtfsReleaseVcbCheckDelete( IrpContext,
+ Vcb,
+ IRP_MJ_CLOSE,
+ NULL );
+
+ //
+ // Only do one since we have lost our place in the Vcb list.
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+
+ break;
+
+ } else {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+ }
+ }
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // Make sure we own the global resource for mount. We can only raise above
+ // in the DeleteVcb path when we don't hold the resource.
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+ }
+
+ Vcb = NULL;
+
+ try {
+
+ PFILE_RECORD_SEGMENT_HEADER MftBuffer;
+ PVOID Mft2Buffer;
+
+ //
+ // Create a new volume device object. This will have the Vcb hanging
+ // off of its end, and set its alignment requirement from the device
+ // we talk to.
+ //
+
+ if (!NT_SUCCESS(Status = IoCreateDevice( NtfsData.DriverObject,
+ sizeof(VOLUME_DEVICE_OBJECT) - sizeof(DEVICE_OBJECT),
+ NULL,
+ FILE_DEVICE_DISK_FILE_SYSTEM,
+ 0,
+ FALSE,
+ (PDEVICE_OBJECT *)&VolDo))) {
+
+ try_return( Status );
+ }
+
+ //
+ // Our alignment requirement is the larger of the processor alignment requirement
+ // already in the volume device object and that in the DeviceObjectWeTalkTo
+ //
+
+ if (DeviceObjectWeTalkTo->AlignmentRequirement > VolDo->DeviceObject.AlignmentRequirement) {
+
+ VolDo->DeviceObject.AlignmentRequirement = DeviceObjectWeTalkTo->AlignmentRequirement;
+ }
+
+ ClearFlag( VolDo->DeviceObject.Flags, DO_DEVICE_INITIALIZING );
+
+ //
+ // Add one more to the stack size requirements for our device
+ //
+
+ VolDo->DeviceObject.StackSize = DeviceObjectWeTalkTo->StackSize + 1;
+
+ //
+ // Initialize the overflow queue for the volume
+ //
+
+ VolDo->OverflowQueueCount = 0;
+ InitializeListHead( &VolDo->OverflowQueue );
+
+ //
+ // Get a reference to the Vcb hanging off the end of the volume device object
+ // we just created
+ //
+
+ IrpContext->Vcb = Vcb = &VolDo->Vcb;
+
+ //
+ // Set the device object field in the vpb to point to our new volume device
+ // object
+ //
+
+ Vpb->DeviceObject = (PDEVICE_OBJECT)VolDo;
+
+ //
+ // Initialize the Vcb. Set checkpoint
+ // in progress (to prevent a real checkpoint from occuring until we
+ // are done).
+ //
+
+ NtfsInitializeVcb( IrpContext, Vcb, DeviceObjectWeTalkTo, Vpb );
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired= TRUE;
+
+ //
+ // Query the device we talk to for this geometry and setup enough of the
+ // vcb to read in the boot sectors. This is a temporary setup until
+ // we've read in the actual boot sector and got the real cluster factor.
+ //
+
+ {
+ DISK_GEOMETRY DiskGeometry;
+ PARTITION_INFORMATION PartitionInfo;
+
+ WriteProtected = NtfsGetDiskGeometry( IrpContext,
+ DeviceObjectWeTalkTo,
+ &DiskGeometry,
+ &PartitionInfo );
+
+ //
+ // If the sector size is greater than the page size, it is probably
+ // a bogus return, but we cannot use the device.
+ //
+
+ if (DiskGeometry.BytesPerSector > PAGE_SIZE) {
+ NtfsRaiseStatus( IrpContext, STATUS_BAD_DEVICE_TYPE, NULL, NULL );
+ }
+
+ Vcb->BytesPerSector = DiskGeometry.BytesPerSector;
+ Vcb->BytesPerCluster = Vcb->BytesPerSector;
+ Vcb->NumberSectors = PartitionInfo.PartitionLength.QuadPart / DiskGeometry.BytesPerSector;
+
+ //
+ // Fail the mount if the number of sectors is less than 16. Otherwise our mount logic
+ // won't work.
+ //
+
+ if (Vcb->NumberSectors <= 0x10) {
+
+ try_return( Status = STATUS_UNRECOGNIZED_VOLUME );
+ }
+
+ Vcb->ClusterMask = Vcb->BytesPerCluster - 1;
+ Vcb->InverseClusterMask = ~Vcb->ClusterMask;
+ for (Vcb->ClusterShift = 0, i = Vcb->BytesPerCluster; i > 1; i = i / 2) {
+ Vcb->ClusterShift += 1;
+ }
+ Vcb->ClustersPerPage = PAGE_SIZE >> Vcb->ClusterShift;
+
+ //
+ // Set the sector size in our device object.
+ //
+
+ VolDo->DeviceObject.SectorSize = (USHORT) Vcb->BytesPerSector;
+ }
+
+ //
+ // Read in the Boot sector, or spare boot sector, on exit of this try
+ // body we will have set bootbcb and bootsector.
+ //
+
+ NtfsReadBootSector( IrpContext, Vcb, &BootScb, &BootBcb, (PVOID *)&BootSector );
+
+ //
+ // Check if this is an NTFS volume
+ //
+
+ if (!NtfsIsBootSectorNtfs( BootSector, Vcb )) {
+
+ DebugTrace( 0, Dbg, ("Not an NTFS volume\n") );
+ try_return( Status = STATUS_UNRECOGNIZED_VOLUME );
+ }
+
+ //
+ // Not return write protected if the drive is really Ntfs.
+ //
+
+ if (WriteProtected) {
+
+ DebugTrace( 0, Dbg, ("Write protected volume\n") );
+ try_return( Status = STATUS_MEDIA_WRITE_PROTECTED );
+ }
+
+ //
+ // Now that we have a real boot sector on a real NTFS volume we can
+ // really set the proper Vcb fields.
+ //
+
+ {
+ BIOS_PARAMETER_BLOCK Bpb;
+
+ NtfsUnpackBios( &Bpb, &BootSector->PackedBpb );
+
+ Vcb->BytesPerSector = Bpb.BytesPerSector;
+ Vcb->BytesPerCluster = Bpb.BytesPerSector * Bpb.SectorsPerCluster;
+ Vcb->NumberSectors = BootSector->NumberSectors;
+ Vcb->MftStartLcn = BootSector->MftStartLcn;
+ Vcb->Mft2StartLcn = BootSector->Mft2StartLcn;
+
+ Vcb->ClusterMask = Vcb->BytesPerCluster - 1;
+ Vcb->InverseClusterMask = ~Vcb->ClusterMask;
+ for (Vcb->ClusterShift = 0, i = Vcb->BytesPerCluster; i > 1; i = i / 2) {
+ Vcb->ClusterShift += 1;
+ }
+
+ //
+ // If the cluster size is greater than the page size then set this value to 1.
+ //
+
+ Vcb->ClustersPerPage = PAGE_SIZE >> Vcb->ClusterShift;
+
+ if (Vcb->ClustersPerPage == 0) {
+
+ Vcb->ClustersPerPage = 1;
+ }
+
+ //
+ // File records can be smaller, equal or larger than the cluster size. Initialize
+ // both ClustersPerFileRecordSegment and FileRecordsPerCluster.
+ //
+ // If the value in the boot sector is positive then it signifies the
+ // clusters/structure. If negative then it signifies the shift value
+ // to obtain the structure size.
+ //
+
+ if (BootSector->ClustersPerFileRecordSegment < 0) {
+
+ Vcb->BytesPerFileRecordSegment = 1 << (-1 * BootSector->ClustersPerFileRecordSegment);
+
+ //
+ // Initialize the other Mft/Cluster relationship numbers in the Vcb
+ // based on whether the clusters are larger or smaller than file
+ // records.
+ //
+
+ if (Vcb->BytesPerFileRecordSegment < Vcb->BytesPerCluster) {
+
+ Vcb->FileRecordsPerCluster = Vcb->BytesPerCluster / Vcb->BytesPerFileRecordSegment;
+
+ } else {
+
+ Vcb->ClustersPerFileRecordSegment = Vcb->BytesPerFileRecordSegment / Vcb->BytesPerCluster;
+ }
+
+ } else {
+
+ Vcb->BytesPerFileRecordSegment = BytesFromClusters( Vcb, BootSector->ClustersPerFileRecordSegment );
+ Vcb->ClustersPerFileRecordSegment = BootSector->ClustersPerFileRecordSegment;
+ }
+
+ for (Vcb->MftShift = 0, i = Vcb->BytesPerFileRecordSegment; i > 1; i = i / 2) {
+ Vcb->MftShift += 1;
+ }
+
+ //
+ // We want to shift between file records and clusters regardless of which is larger.
+ // Compute the shift value here. Anyone using this value will have to know which
+ // way to shift.
+ //
+
+ Vcb->MftToClusterShift = Vcb->MftShift - Vcb->ClusterShift;
+
+ if (Vcb->ClustersPerFileRecordSegment == 0) {
+
+ Vcb->MftToClusterShift = Vcb->ClusterShift - Vcb->MftShift;
+ }
+
+ //
+ // Compute the default index allocation buffer size.
+ //
+
+ if (BootSector->DefaultClustersPerIndexAllocationBuffer < 0) {
+
+ Vcb->DefaultBytesPerIndexAllocationBuffer = 1 << (-1 * BootSector->DefaultClustersPerIndexAllocationBuffer);
+
+ //
+ // Determine whether the index allocation buffer is larger/smaller
+ // than the cluster size to determine the block size.
+ //
+
+ if (Vcb->DefaultBytesPerIndexAllocationBuffer < Vcb->BytesPerCluster) {
+
+ Vcb->DefaultBlocksPerIndexAllocationBuffer = Vcb->DefaultBytesPerIndexAllocationBuffer / DEFAULT_INDEX_BLOCK_SIZE;
+
+ } else {
+
+ Vcb->DefaultBlocksPerIndexAllocationBuffer = Vcb->DefaultBytesPerIndexAllocationBuffer / Vcb->BytesPerCluster;
+ }
+
+ } else {
+
+ Vcb->DefaultBlocksPerIndexAllocationBuffer = BootSector->DefaultClustersPerIndexAllocationBuffer;
+ Vcb->DefaultBytesPerIndexAllocationBuffer = BytesFromClusters( Vcb, Vcb->DefaultBlocksPerIndexAllocationBuffer );
+ }
+
+ //
+ // Now compute our volume specific constants that are stored in
+ // the Vcb. The total number of clusters is:
+ //
+ // (NumberSectors * BytesPerSector) / BytesPerCluster
+ //
+
+ Vcb->TotalClusters = LlClustersFromBytesTruncate( Vcb,
+ Vcb->NumberSectors * Vcb->BytesPerSector );
+
+ //
+ // Compute the attribute flags mask for this volume for this volume.
+ //
+
+ Vcb->AttributeFlagsMask = 0xffff;
+
+ if (Vcb->BytesPerCluster > 0x1000) {
+
+ ClearFlag( Vcb->AttributeFlagsMask, ATTRIBUTE_FLAG_COMPRESSION_MASK );
+ }
+
+ //
+ // For now, an attribute is considered "moveable" if it is at
+ // least 5/16 of the file record. This constant should only
+ // be changed i conjunction with the MAX_MOVEABLE_ATTRIBUTES
+ // constant. (The product of the two should be a little less
+ // than or equal to 1.)
+ //
+
+ Vcb->BigEnoughToMove = Vcb->BytesPerFileRecordSegment * 5 / 16;
+
+ //
+ // Set the serial number in the Vcb
+ //
+
+ Vcb->VolumeSerialNumber = BootSector->SerialNumber;
+ Vpb->SerialNumber = ((ULONG)BootSector->SerialNumber);
+ }
+
+ //
+ // Initialize recovery state.
+ //
+
+ NtfsInitializeRestartTable( sizeof(OPEN_ATTRIBUTE_ENTRY),
+ INITIAL_NUMBER_ATTRIBUTES,
+ &Vcb->OpenAttributeTable );
+
+ NtfsInitializeRestartTable( sizeof(TRANSACTION_ENTRY),
+ INITIAL_NUMBER_TRANSACTIONS,
+ &Vcb->TransactionTable );
+
+ //
+ // Now start preparing to restart the volume.
+ //
+
+ //
+ // Create the Mft and Log File Scbs and prepare to read them.
+ // The Mft and mirror length will be the first 4 file records or
+ // the first cluster.
+ //
+
+ FirstNonMirroredCluster = ClustersFromBytes( Vcb, 4 * Vcb->BytesPerFileRecordSegment );
+ MirroredMftRange = 4 * Vcb->BytesPerFileRecordSegment;
+
+ if (MirroredMftRange < Vcb->BytesPerCluster) {
+
+ MirroredMftRange = Vcb->BytesPerCluster;
+ }
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->MftScb,
+ Vcb,
+ MASTER_FILE_TABLE_NUMBER,
+ MirroredMftRange,
+ $DATA,
+ TRUE );
+
+ CcSetAdditionalCacheAttributes( Vcb->MftScb->FileObject, TRUE, TRUE );
+
+ LlTemp1 = FirstNonMirroredCluster;
+
+ (VOID)NtfsAddNtfsMcbEntry( &Vcb->MftScb->Mcb,
+ (LONGLONG)0,
+ Vcb->MftStartLcn,
+ (LONGLONG)FirstNonMirroredCluster,
+ FALSE );
+
+ //
+ // Now the same for Mft2
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->Mft2Scb,
+ Vcb,
+ MASTER_FILE_TABLE2_NUMBER,
+ MirroredMftRange,
+ $DATA,
+ TRUE );
+
+ CcSetAdditionalCacheAttributes( Vcb->Mft2Scb->FileObject, TRUE, TRUE );
+
+
+ (VOID)NtfsAddNtfsMcbEntry( &Vcb->Mft2Scb->Mcb,
+ (LONGLONG)0,
+ Vcb->Mft2StartLcn,
+ (LONGLONG)FirstNonMirroredCluster,
+ FALSE );
+
+ //
+ // Create the dasd system file, we do it here because we need to dummy
+ // up the mcb for it, and that way everything else in NTFS won't need
+ // to know that it is a special file. We need to do this after
+ // cluster allocation initialization because that computes the total
+ // clusters on the volume. Also for verification purposes we will
+ // set and get the times off of the volume.
+ //
+ // Open it now before the Log File, because that is the first time
+ // anyone may want to mark the volume corrupt.
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->VolumeDasdScb,
+ Vcb,
+ VOLUME_DASD_NUMBER,
+ LlBytesFromClusters( Vcb, Vcb->TotalClusters ),
+ $DATA,
+ FALSE );
+
+ (VOID)NtfsAddNtfsMcbEntry( &Vcb->VolumeDasdScb->Mcb,
+ (LONGLONG)0,
+ (LONGLONG)0,
+ Vcb->TotalClusters,
+ FALSE );
+
+ SetFlag( Vcb->VolumeDasdScb->Fcb->FcbState, FCB_STATE_DUP_INITIALIZED );
+
+ Vcb->VolumeDasdScb->Fcb->LinkCount =
+ Vcb->VolumeDasdScb->Fcb->TotalLinks = 1;
+
+ //
+ // We want to read the first four record segments of each of these
+ // files. We do this so that we don't have a cache miss when we
+ // look up the real allocation below.
+ //
+
+ for (i = 0; i < 4; i++) {
+
+ FILE_REFERENCE FileReference;
+ PATTRIBUTE_RECORD_HEADER FirstAttribute;
+
+ NtfsSetSegmentNumber( &FileReference, 0, i );
+ FileReference.SequenceNumber = (USHORT)i;
+
+ NtfsReadFileRecord( IrpContext,
+ Vcb,
+ &FileReference,
+ &Bcbs[i*2],
+ &MftBuffer,
+ &FirstAttribute,
+ NULL );
+
+ //
+ // If any of these file records are bad then
+ // fail the mount.
+ //
+
+ if (!NtfsCheckFileRecord( IrpContext, Vcb, MftBuffer )) {
+
+ try_return( Status = STATUS_DISK_CORRUPT_ERROR );
+ }
+
+ NtfsMapStream( IrpContext,
+ Vcb->Mft2Scb,
+ (LONGLONG)i,
+ Vcb->BytesPerFileRecordSegment,
+ &Bcbs[i*2 + 1],
+ &Mft2Buffer );
+ }
+
+ //
+ // The last file record was the Volume Dasd, so check the version number.
+ //
+
+ Attribute = NtfsFirstAttribute(MftBuffer);
+
+ while (TRUE) {
+
+ Attribute = NtfsGetNextRecord(Attribute);
+
+ if (Attribute->TypeCode == $VOLUME_INFORMATION) {
+
+ PVOLUME_INFORMATION VolumeInformation;
+
+ VolumeInformation = (PVOLUME_INFORMATION)NtfsAttributeValue(Attribute);
+
+ if (VolumeInformation->MajorVersion != 2) {
+
+ if (VolumeInformation->MajorVersion != 1) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
+ }
+
+#ifdef _CAIRO_
+ if (NtfsUpdateTo20) {
+
+ UpdateVersion = TRUE;
+ }
+#else
+ if (VolumeInformation->MinorVersion <= 1) {
+
+ UpdateVersion = TRUE;
+
+ } else if (NtfsDefragMftEnabled) {
+
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ }
+
+#endif
+
+ } else if (NtfsDefragMftEnabled) {
+
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ }
+
+ break;
+ }
+
+ if (Attribute->TypeCode == $END) {
+ NtfsRaiseStatus( IrpContext, STATUS_WRONG_VOLUME, NULL, NULL );
+ }
+ }
+
+ //
+ // Create the log file Scb and really look up its size.
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->LogFileScb,
+ Vcb,
+ LOG_FILE_NUMBER,
+ 0,
+ $DATA,
+ TRUE );
+
+ Vcb->LogFileObject = Vcb->LogFileScb->FileObject;
+
+ CcSetAdditionalCacheAttributes( Vcb->LogFileScb->FileObject, TRUE, TRUE );
+
+ //
+ // Lookup the log file mapping now, since we will not go to the
+ // disk for allocation information any more once we set restart
+ // in progress.
+ //
+
+ (VOID)NtfsPreloadAllocation( IrpContext, Vcb->LogFileScb, 0, MAXLONGLONG );
+
+ //
+ // Now we have to unpin everything before restart, because it generally
+ // has to uninitialize everything.
+ //
+
+ NtfsUnpinBcb( &BootBcb );
+
+ for (i = 0; i < 8; i++) {
+ NtfsUnpinBcb( &Bcbs[i] );
+ }
+
+ //
+ // Purge the Mft, since we only read the first four file
+ // records, not necessarily an entire page!
+ //
+
+ CcPurgeCacheSection( &Vcb->MftScb->NonpagedScb->SegmentObject, NULL, 0, FALSE );
+
+ //
+ // Now start up the log file and perform Restart. This calls will
+ // unpin and remap the Mft Bcb's. The MftBuffer variables above
+ // may no longer point to the correct range of bytes. This is OK
+ // if they are never referenced.
+ //
+ // Put a try-except around this to catch any restart failures.
+ // This is important in order to allow us to limp along until
+ // autochk gets a chance to run.
+ //
+ // We set restart in progress first, to prevent us from looking up any
+ // more run information (now that we know where the log file is at!)
+ //
+
+ SetFlag(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS);
+
+ try {
+
+ Status = STATUS_SUCCESS;
+
+ NtfsStartLogFile( Vcb->LogFileScb,
+ Vcb );
+
+ //
+ // We call the cache manager again with the stream files for the Mft and
+ // Mft mirror as we didn't have a log handle for the first call.
+ //
+
+ CcSetLogHandleForFile( Vcb->MftScb->FileObject,
+ Vcb->LogHandle,
+ &LfsFlushToLsn );
+
+ CcSetLogHandleForFile( Vcb->Mft2Scb->FileObject,
+ Vcb->LogHandle,
+ &LfsFlushToLsn );
+
+ CloseAttributes = TRUE;
+
+ UpdatesApplied = NtfsRestartVolume( IrpContext, Vcb );
+
+ //
+ // For right now, we will charge ahead with a dirty volume, no
+ // matter what the exception was. Later we will have to be
+ // defensive and use a filter.
+ //
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ Status = GetExceptionCode();
+
+ //
+ // If the error is STATUS_LOG_FILE_FULL then it means that
+ // we couldn't complete the restart. Mark the volume dirty in
+ // this case. Don't return this error code.
+ //
+
+ if (Status == STATUS_LOG_FILE_FULL) {
+
+ Status = STATUS_DISK_CORRUPT_ERROR;
+ IrpContext->ExceptionStatus = STATUS_DISK_CORRUPT_ERROR;
+ }
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ LONGLONG VolumeDasdOffset;
+
+ NtfsSetAndGetVolumeTimes( IrpContext, Vcb, TRUE );
+
+ //
+ // Now flush it out, so chkdsk can see it with Dasd.
+ // Clear the error in the IrpContext so that this
+ // flush will succeed. Otherwise CommonWrite will
+ // return FILE_LOCK_CONFLICT.
+ //
+
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+
+ VolumeDasdOffset = VOLUME_DASD_NUMBER << Vcb->MftShift;
+
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&VolumeDasdOffset,
+ Vcb->BytesPerFileRecordSegment,
+ NULL );
+
+ try_return( Status );
+ }
+
+ //
+ // Now flush the Mft copies, because we are going to shut the real
+ // one down and reopen it for real.
+ //
+
+ CcFlushCache( &Vcb->Mft2Scb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+
+ if (NT_SUCCESS(IoStatus.Status)) {
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+ }
+
+ if (!NT_SUCCESS(IoStatus.Status)) {
+
+ NtfsNormalizeAndRaiseStatus( IrpContext,
+ IoStatus.Status,
+ STATUS_UNEXPECTED_IO_ERROR );
+ }
+
+ //
+ // Show that the restart is complete, and it is safe to go to
+ // the disk for the Mft allocation.
+ //
+
+ ClearFlag(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS);
+
+ //
+ // Set the Mft sizes back down to the part which is guaranteed to
+ // be contiguous for now. Important on large page size systems!
+ //
+
+ Vcb->MftScb->Header.AllocationSize.QuadPart =
+ Vcb->MftScb->Header.FileSize.QuadPart =
+ Vcb->MftScb->Header.ValidDataLength.QuadPart = FirstNonMirroredCluster << Vcb->ClusterShift;
+
+ //
+ // Pin the first four file records
+ //
+
+ for (i = 0; i < 4; i++) {
+
+ NtfsPinStream( IrpContext,
+ Vcb->MftScb,
+ (LONGLONG)(i << Vcb->MftShift),
+ Vcb->BytesPerFileRecordSegment,
+ &Bcbs[i*2],
+ (PVOID *)&MftBuffer );
+
+ //
+ // Implement the one-time conversion of the Sequence Number
+ // for the Mft's own file record from 0 to 1.
+ //
+
+ if (i == 0) {
+
+ if (MftBuffer->SequenceNumber != 1) {
+
+ NtfsPostVcbIsCorrupt( (PVOID)Vcb, 0, NULL, NULL );
+ }
+ }
+
+ NtfsPinStream( IrpContext,
+ Vcb->Mft2Scb,
+ (LONGLONG)(i << Vcb->MftShift),
+ Vcb->BytesPerFileRecordSegment,
+ &Bcbs[i*2 + 1],
+ &Mft2Buffer );
+ }
+
+ //
+ // Now we need to uninitialize and purge the Mft and Mft2. This is
+ // because we could have only a partially filled page at the end, and
+ // we need to do real reads of whole pages now.
+ //
+
+ //
+ // Uninitialize and reinitialize the large mcbs so that we can reload
+ // it from the File Record.
+ //
+
+ NtfsUnloadNtfsMcbRange( &Vcb->MftScb->Mcb, (LONGLONG) 0, MAXLONGLONG, TRUE, FALSE );
+ NtfsUnloadNtfsMcbRange( &Vcb->Mft2Scb->Mcb, (LONGLONG) 0, MAXLONGLONG, TRUE, FALSE );
+
+ //
+ // Mark both of them as uninitialized.
+ //
+
+ ClearFlag( Vcb->MftScb->ScbState, SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_FILE_SIZE_LOADED );
+ ClearFlag( Vcb->Mft2Scb->ScbState, SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_FILE_SIZE_LOADED );
+
+ //
+ // Now load up the real allocation from just the first file record.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ NtfsPreloadAllocation( IrpContext,
+ Vcb->MftScb,
+ 0,
+ (FIRST_USER_FILE_NUMBER - 1) << Vcb->MftToClusterShift );
+
+ } else {
+
+ NtfsPreloadAllocation( IrpContext,
+ Vcb->MftScb,
+ 0,
+ (FIRST_USER_FILE_NUMBER - 1) >> Vcb->MftToClusterShift );
+ }
+
+ NtfsPreloadAllocation( IrpContext, Vcb->Mft2Scb, 0, MAXLONGLONG );
+
+ //
+ // We update the Mft and the Mft mirror before we delete the current
+ // stream file for the Mft. We know we can read the true attributes
+ // for the Mft and the Mirror because we initialized their sizes
+ // above through the first few records in the Mft.
+ //
+
+ NtfsUpdateScbFromAttribute( IrpContext, Vcb->MftScb, NULL );
+ NtfsUpdateScbFromAttribute( IrpContext, Vcb->Mft2Scb, NULL );
+
+ //
+ // Unpin the Bcb's for the Mft files before uninitializing.
+ //
+
+ for (i = 0; i < 8; i++) {
+ NtfsUnpinBcb( &Bcbs[i] );
+ }
+
+ //
+ // Now close and purge the Mft, and recreate its stream so that
+ // the Mft is in a normal state, and we can close the rest of
+ // the attributes from restart. We need to bump the close count
+ // to keep the scb around while we do this little bit of trickery
+ //
+
+ {
+ Vcb->MftScb->CloseCount += 1;
+
+ NtfsDeleteInternalAttributeStream( Vcb->MftScb, TRUE );
+ NtfsCreateInternalAttributeStream( IrpContext, Vcb->MftScb, FALSE );
+
+ //
+ // Tell the cache manager the file sizes for the MFT. It is possible
+ // that the shared cache map did not go away on the DeleteInternalAttributeStream
+ // call above. In that case the Cache Manager has the file sizes from
+ // restart.
+ //
+
+ CcSetFileSizes( Vcb->MftScb->FileObject,
+ (PCC_FILE_SIZES) &Vcb->MftScb->Header.AllocationSize );
+
+ CcSetAdditionalCacheAttributes( Vcb->MftScb->FileObject, TRUE, FALSE );
+
+ Vcb->MftScb->CloseCount -= 1;
+ }
+
+ //
+ // We want to read all of the file records for the Mft to put
+ // its complete mapping into the Mcb.
+ //
+
+ NtfsPreloadAllocation( IrpContext, Vcb->MftScb, 0, MAXLONGLONG );
+
+ //
+ // Close the boot file (get rid of it because we do not know its proper
+ // size, and the Scb may be inconsistent).
+ //
+
+ NtfsDeleteInternalAttributeStream( BootScb, TRUE );
+ BootScb = NULL;
+
+ //
+ // Closing the attributes from restart has to occur here after
+ // the Mft is clean, because flushing these files will cause
+ // file size updates to occur, etc.
+ //
+
+ Status = NtfsCloseAttributesFromRestart( IrpContext, Vcb );
+ CloseAttributes = FALSE;
+
+ if (!NT_SUCCESS( Status )) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ //
+ // Show that it is ok to checkpoint now.
+ //
+
+ ClearFlag(Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS);
+
+ //
+ // Clear the flag indicating that we won't defrag the volume.
+ //
+
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ //
+ // We always need to write a checkpoint record so that we have
+ // a checkpoint on the disk before we modify any files.
+ //
+
+ NtfsCheckpointVolume( IrpContext,
+ Vcb,
+ FALSE,
+ UpdatesApplied,
+ UpdatesApplied,
+ Vcb->LastRestartArea );
+
+ //
+ // Now set the defrag enabled flag.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ //
+ // Open the Root Directory.
+ //
+
+ NtfsOpenRootDirectory( IrpContext, Vcb );
+
+/* Format is using wrong attribute definitions
+
+ //
+ // At this point we are ready to use the volume normally. We could
+ // open the remaining system files by name, but for now we will go
+ // ahead and open them by file number.
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->AttributeDefTableScb,
+ Vcb,
+ ATTRIBUTE_DEF_TABLE_NUMBER,
+ 0,
+ $DATA,
+ FALSE );
+
+ //
+ // Read in the attribute definitions.
+ //
+
+ {
+ IO_STATUS_BLOCK IoStatus;
+ PSCB Scb = Vcb->AttributeDefTableScb;
+
+ if ((Scb->Header.FileSize.HighPart != 0) || (Scb->Header.FileSize.LowPart == 0)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ Vcb->AttributeDefinitions = NtfsAllocatePool(PagedPool, Scb->Header.FileSize.LowPart );
+
+ CcCopyRead( Scb->FileObject,
+ &Li0,
+ Scb->Header.FileSize.LowPart,
+ TRUE,
+ Vcb->AttributeDefinitions,
+ &IoStatus );
+
+ if (!NT_SUCCESS(IoStatus.Status)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+ }
+*/
+
+ //
+ // Just point to our own attribute definitions for now.
+ //
+
+ Vcb->AttributeDefinitions = NtfsAttributeDefinitions;
+
+ //
+ // Open the upcase table.
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->UpcaseTableScb,
+ Vcb,
+ UPCASE_TABLE_NUMBER,
+ 0,
+ $DATA,
+ FALSE );
+
+ //
+ // Read in the upcase table.
+ //
+
+ {
+ IO_STATUS_BLOCK IoStatus;
+ PSCB Scb = Vcb->UpcaseTableScb;
+
+ if ((Scb->Header.FileSize.HighPart != 0) || (Scb->Header.FileSize.LowPart < 512)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ Vcb->UpcaseTable = NtfsAllocatePool(PagedPool, Scb->Header.FileSize.LowPart );
+ Vcb->UpcaseTableSize = Scb->Header.FileSize.LowPart / 2;
+
+ CcCopyRead( Scb->FileObject,
+ &Li0,
+ Scb->Header.FileSize.LowPart,
+ TRUE,
+ Vcb->UpcaseTable,
+ &IoStatus );
+
+ if (!NT_SUCCESS(IoStatus.Status)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // If we do not have a global upcase table yet then make this one the global one
+ //
+
+ if (NtfsData.UpcaseTable == NULL) {
+
+ NtfsData.UpcaseTable = Vcb->UpcaseTable;
+ NtfsData.UpcaseTableSize = Vcb->UpcaseTableSize;
+
+ //
+ // Otherwise if this one perfectly matches the global upcase table then throw
+ // this one back and use the global one
+ //
+
+ } else if ((NtfsData.UpcaseTableSize == Vcb->UpcaseTableSize)
+
+ &&
+
+ (RtlCompareMemory( NtfsData.UpcaseTable,
+ Vcb->UpcaseTable,
+ Vcb->UpcaseTableSize) == Vcb->UpcaseTableSize)) {
+
+ ExFreePool( Vcb->UpcaseTable );
+ Vcb->UpcaseTable = NtfsData.UpcaseTable;
+ }
+ }
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->BitmapScb,
+ Vcb,
+ BIT_MAP_FILE_NUMBER,
+ 0,
+ $DATA,
+ TRUE );
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->BadClusterFileScb,
+ Vcb,
+ BAD_CLUSTER_FILE_NUMBER,
+ 0,
+ $DATA,
+ TRUE );
+
+ NtfsOpenSystemFile( IrpContext,
+ &Vcb->MftBitmapScb,
+ Vcb,
+ MASTER_FILE_TABLE_NUMBER,
+ 0,
+ $BITMAP,
+ TRUE );
+
+
+ //
+ // Initialize the bitmap support
+ //
+
+ NtfsInitializeClusterAllocation( IrpContext, Vcb );
+
+ NtfsSetAndGetVolumeTimes( IrpContext, Vcb, FALSE );
+
+ //
+ // Initialize the Mft record allocation
+ //
+
+ {
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN FoundAttribute;
+ ULONG ExtendGranularity;
+
+ //
+ // Lookup the bitmap allocation for the Mft file.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // CODENOTE Is the Mft Fcb fully initialized at this point??
+ //
+
+ FoundAttribute = NtfsLookupAttributeByCode( IrpContext,
+ Vcb->MftScb->Fcb,
+ &Vcb->MftScb->Fcb->FileReference,
+ $BITMAP,
+ &AttrContext );
+ //
+ // Error if we don't find the bitmap
+ //
+
+ if (!FoundAttribute) {
+
+ DebugTrace( 0, 0, ("Couldn't find bitmap attribute for Mft\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // If there is no file object for the Mft Scb, we create it now.
+ //
+
+ if (Vcb->MftScb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Vcb->MftScb, TRUE );
+ }
+
+ //
+ // TEMPCODE We need a better way to determine the optimal
+ // truncate and extend granularity.
+ //
+
+ ExtendGranularity = MFT_EXTEND_GRANULARITY;
+
+ if ((ExtendGranularity * Vcb->BytesPerFileRecordSegment) < Vcb->BytesPerCluster) {
+
+ ExtendGranularity = Vcb->FileRecordsPerCluster;
+ }
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ Vcb->MftScb,
+ &AttrContext,
+ Vcb->BytesPerFileRecordSegment,
+ ExtendGranularity,
+ ExtendGranularity,
+ &Vcb->MftBitmapAllocationContext );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+ }
+
+ //
+ // Get the serial number and volume label for the volume
+ //
+
+ NtfsGetVolumeLabel( IrpContext, Vpb, Vcb );
+
+ //
+ // Get the Device Name for this volume.
+ //
+
+ Status = ObQueryNameString( Vpb->RealDevice,
+ NULL,
+ 0,
+ &DeviceObjectNameLength );
+
+ ASSERT( Status != STATUS_SUCCESS);
+
+ //
+ // Unlike the rest of the system, ObQueryNameString returns
+ // STATUS_INFO_LENGTH_MISMATCH instead of STATUS_BUFFER_TOO_SMALL when
+ // passed too small a buffer.
+ //
+ // We expect to get this error here. Anything else we can't handle.
+ //
+
+ if (Status == STATUS_INFO_LENGTH_MISMATCH) {
+
+ DeviceObjectName = NtfsAllocatePool( PagedPool, DeviceObjectNameLength );
+
+ Status = ObQueryNameString( Vpb->RealDevice,
+ DeviceObjectName,
+ DeviceObjectNameLength,
+ &DeviceObjectNameLength );
+ }
+
+ if (!NT_SUCCESS( Status )) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Now that we are successfully mounting, let us see if we should
+ // enable balanced reads.
+ //
+
+ if (!FlagOn(Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED_DIRTY)) {
+
+ FsRtlBalanceReads( DeviceObjectWeTalkTo );
+ }
+
+ ASSERT( DeviceObjectName->Name.Length != 0 );
+
+ Vcb->DeviceName.MaximumLength =
+ Vcb->DeviceName.Length = DeviceObjectName->Name.Length;
+
+ Vcb->DeviceName.Buffer = NtfsAllocatePool( PagedPool, DeviceObjectName->Name.Length );
+
+ RtlCopyMemory( Vcb->DeviceName.Buffer,
+ DeviceObjectName->Name.Buffer,
+ DeviceObjectName->Name.Length );
+
+ //
+ // We have now mounted this volume. At this time we will update
+ // the version number if required and check the log file size.
+ //
+
+ if (UpdateVersion) {
+
+#ifdef _CAIRO_
+
+ if ((Vpb->VolumeLabelLength == 18) &&
+ (RtlEqualMemory(Vpb->VolumeLabel, L"$DeadMeat", 18))) {
+
+ NtfsUpdateVersionNumber( IrpContext,
+ Vcb,
+ 2,
+ 0 );
+
+ //
+ // Now enable defragging.
+ //
+
+ if (NtfsDefragMftEnabled) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+ }
+#else
+ NtfsUpdateVersionNumber( IrpContext,
+ Vcb,
+ 1,
+ 2 );
+
+ //
+ // Now enable defragging.
+ //
+
+ if (NtfsDefragMftEnabled) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+#endif
+ }
+
+ //
+ // Now we want to initialize the remaining defrag status values.
+ //
+
+ Vcb->MftHoleGranularity = MFT_HOLE_GRANULARITY;
+ Vcb->MftClustersPerHole = Vcb->MftHoleGranularity << Vcb->MftToClusterShift;
+
+ if (MFT_HOLE_GRANULARITY < Vcb->FileRecordsPerCluster) {
+
+ Vcb->MftHoleGranularity = Vcb->FileRecordsPerCluster;
+ Vcb->MftClustersPerHole = 1;
+ }
+
+ Vcb->MftHoleMask = Vcb->MftHoleGranularity - 1;
+ Vcb->MftHoleInverseMask = ~(Vcb->MftHoleMask);
+
+ Vcb->MftHoleClusterMask = Vcb->MftClustersPerHole - 1;
+ Vcb->MftHoleClusterInverseMask = ~(Vcb->MftHoleClusterMask);
+
+ //
+ // Our maximum reserved Mft space is 0x140, we will try to
+ // get an extra 40 bytes if possible.
+ //
+
+ Vcb->MftReserved = Vcb->BytesPerFileRecordSegment / 8;
+
+ if (Vcb->MftReserved > 0x140) {
+
+ Vcb->MftReserved = 0x140;
+ }
+
+ Vcb->MftCushion = Vcb->MftReserved - 0x20;
+
+ NtfsScanMftBitmap( IrpContext, Vcb );
+
+#ifdef NTFS_CHECK_BITMAP
+ {
+ ULONG BitmapSize;
+ ULONG Count;
+
+ BitmapSize = Vcb->BitmapScb->Header.FileSize.LowPart;
+
+ //
+ // Allocate a buffer for the bitmap copy and each individual bitmap.
+ //
+
+ Vcb->BitmapPages = (BitmapSize + PAGE_SIZE - 1) / PAGE_SIZE;
+
+ Vcb->BitmapCopy = NtfsAllocatePool(PagedPool, Vcb->BitmapPages * sizeof( RTL_BITMAP ));
+ RtlZeroMemory( Vcb->BitmapCopy, Vcb->BitmapPages * sizeof( RTL_BITMAP ));
+
+ //
+ // Now get a buffer for each page.
+ //
+
+ for (Count = 0; Count < Vcb->BitmapPages; Count += 1) {
+
+ (Vcb->BitmapCopy + Count)->Buffer = NtfsAllocatePool(PagedPool, PAGE_SIZE );
+ RtlInitializeBitMap( Vcb->BitmapCopy + Count, (Vcb->BitmapCopy + Count)->Buffer, PAGE_SIZE * 8 );
+ }
+
+ if (NtfsCopyBitmap) {
+
+ PUCHAR NextPage;
+ PBCB BitmapBcb = NULL;
+ ULONG BytesToCopy;
+ LONGLONG FileOffset = 0;
+
+ Count = 0;
+
+ while (BitmapSize) {
+
+ BytesToCopy = PAGE_SIZE;
+
+ if (BytesToCopy > BitmapSize) {
+
+ BytesToCopy = BitmapSize;
+ }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ NtfsMapStream( IrpContext, Vcb->BitmapScb, FileOffset, BytesToCopy, &BitmapBcb, &NextPage );
+
+ RtlCopyMemory( (Vcb->BitmapCopy + Count)->Buffer,
+ NextPage,
+ BytesToCopy );
+
+ BitmapSize -= BytesToCopy;
+ FileOffset += BytesToCopy;
+ Count += 1;
+ }
+
+ NtfsUnpinBcb( &BitmapBcb );
+
+ //
+ // Otherwise we will want to scan the entire Mft and compare the mapping pairs
+ // with the current volume bitmap.
+ //
+
+ }
+ }
+#endif
+
+#ifdef _CAIRO_
+
+ if ((Vpb->VolumeLabelLength == 18) &&
+ (RtlCompareMemory(Vpb->VolumeLabel, L"$DeadMeat", 18) == 18)) {
+
+ //
+ // Open the Quota object. At present, the quota object contains:
+ //
+ // 1. Security info
+ // 2. Quota info
+ //
+ // BUGBUG: This is the default data stream on the quota table. This
+ // should be changed when we get NtOfsCreateAttribute becomes
+ // available.
+ //
+
+ NtfsOpenSystemFile( IrpContext,
+ &QuotaDataScb,
+ Vcb,
+ QUOTA_TABLE_NUMBER,
+ 0,
+ $DATA,
+ TRUE );
+
+ //
+ // Enable quota tracking.
+ //
+
+ NtfsInitializeQuotaIndex( IrpContext,
+ QuotaDataScb->Fcb,
+ Vcb );
+
+ //
+ // Enable security index
+ //
+
+ NtfsInitializeSecurity( IrpContext, Vcb, QuotaDataScb->Fcb );
+
+#ifdef TOMM
+ // NtOfsIndexTest( IrpContext, Vcb->SecurityDescriptorStream->Fcb );
+#endif TOMM
+ }
+
+#endif _CAIRO_
+
+ NtfsCleanupTransaction( IrpContext, STATUS_SUCCESS, FALSE );
+
+ //
+ //
+ // Set our return status and say that the mount succeeded
+ //
+
+ Status = STATUS_SUCCESS;
+ MountFailed = FALSE;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsMountVolume );
+
+ NtfsUnpinBcb( &BootBcb );
+
+ if (DeviceObjectName != NULL) {
+
+ NtfsFreePool( DeviceObjectName );
+ }
+
+ if (CloseAttributes) { NtfsCloseAttributesFromRestart( IrpContext, Vcb ); }
+
+ for (i = 0; i < 8; i++) { NtfsUnpinBcb( &Bcbs[i] ); }
+
+ if (BootScb != NULL) { NtfsDeleteInternalAttributeStream( BootScb, TRUE ); }
+
+ if (Vcb != NULL) {
+
+ if (Vcb->MftScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->MftScb ); }
+ if (Vcb->Mft2Scb != NULL) { NtfsReleaseScb( IrpContext, Vcb->Mft2Scb ); }
+ if (Vcb->LogFileScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->LogFileScb ); }
+ if (Vcb->VolumeDasdScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->VolumeDasdScb ); }
+ if (Vcb->AttributeDefTableScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->AttributeDefTableScb );
+ NtfsDeleteInternalAttributeStream( Vcb->AttributeDefTableScb, TRUE );
+ Vcb->AttributeDefTableScb = NULL;}
+ if (Vcb->UpcaseTableScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->UpcaseTableScb );
+ NtfsDeleteInternalAttributeStream( Vcb->UpcaseTableScb, TRUE );
+ Vcb->UpcaseTableScb = NULL;}
+ if (Vcb->RootIndexScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->RootIndexScb ); }
+ if (Vcb->BitmapScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->BitmapScb ); }
+ if (Vcb->BadClusterFileScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->BadClusterFileScb ); }
+ if (Vcb->MftBitmapScb != NULL) { NtfsReleaseScb( IrpContext, Vcb->MftBitmapScb ); }
+
+#ifdef _CAIRO_
+
+ //
+ // Drop the security data
+ //
+
+ if (Vcb->SecurityDescriptorStream != NULL) { NtfsReleaseScb( IrpContext, Vcb->SecurityDescriptorStream ); }
+ if (QuotaDataScb != NULL) {
+ NtfsReleaseScb( IrpContext, QuotaDataScb );
+ NtfsDeleteInternalAttributeStream( QuotaDataScb, TRUE );
+ }
+
+#endif _CAIRO_
+
+ if (MountFailed) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ //
+ // On abnormal termination, someone will try to abort a transaction on
+ // this Vcb if we do not clear these fields.
+ //
+
+ IrpContext->TransactionId = 0;
+ IrpContext->Vcb = NULL;
+ }
+ }
+
+ if (VcbAcquired) {
+
+ NtfsReleaseVcbCheckDelete( IrpContext, Vcb, IRP_MJ_FILE_SYSTEM_CONTROL, NULL );
+ }
+
+ NtfsReleaseGlobal( IrpContext );
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsMountVolume -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsVerifyVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the verify volume operation. It is responsible for
+ either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsVerifyVolume\n") );
+
+ //
+ // Do nothing for now
+ //
+
+ KdPrint(("NtfsVerifyVolume is not yet implemented\n")); //**** DbgBreakPoint();
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status = STATUS_NOT_IMPLEMENTED );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsVerifyVolume -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsUserFsRequest (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for implementing the user's requests made
+ through NtFsControlFile.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+ Wait - Indicates if the thread can block for a resource or I/O
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG FsControlCode;
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location, and save some references
+ // to make our life a little easier.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsUserFsCtrl, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Case on the control code.
+ //
+
+ switch ( FsControlCode ) {
+
+ case FSCTL_REQUEST_OPLOCK_LEVEL_1:
+ case FSCTL_REQUEST_OPLOCK_LEVEL_2:
+ case FSCTL_REQUEST_BATCH_OPLOCK:
+ case FSCTL_REQUEST_FILTER_OPLOCK:
+ case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE:
+ case FSCTL_OPLOCK_BREAK_NOTIFY:
+ case FSCTL_OPBATCH_ACK_CLOSE_PENDING :
+ case FSCTL_OPLOCK_BREAK_ACK_NO_2:
+
+ Status = NtfsOplockRequest( IrpContext, Irp );
+ break;
+
+ case FSCTL_LOCK_VOLUME:
+
+ Status = NtfsLockVolume( IrpContext, Irp );
+ break;
+
+ case FSCTL_UNLOCK_VOLUME:
+
+ Status = NtfsUnlockVolume( IrpContext, Irp );
+ break;
+
+ case FSCTL_DISMOUNT_VOLUME:
+
+ Status = NtfsDismountVolume( IrpContext, Irp );
+ break;
+
+ case FSCTL_MARK_VOLUME_DIRTY:
+
+ Status = NtfsDirtyVolume( IrpContext, Irp );
+ break;
+
+ case FSCTL_IS_PATHNAME_VALID:
+
+ //
+ // All names are potentially valid NTFS names
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status = STATUS_SUCCESS );
+ break;
+
+ case FSCTL_QUERY_RETRIEVAL_POINTERS:
+ Status = NtfsQueryRetrievalPointers( IrpContext, Irp );
+ break;
+
+ case FSCTL_GET_COMPRESSION:
+ Status = NtfsGetCompression( IrpContext, Irp );
+ break;
+
+ case FSCTL_SET_COMPRESSION:
+
+ //
+ // Post this request if we can't wait.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+
+ } else {
+
+ Status = NtfsSetCompression( IrpContext, Irp );
+ }
+
+ break;
+
+ case FSCTL_READ_COMPRESSION:
+ Status = NtfsReadCompression( IrpContext, Irp );
+ break;
+
+ case FSCTL_WRITE_COMPRESSION:
+ Status = NtfsWriteCompression( IrpContext, Irp );
+ break;
+
+ case FSCTL_MARK_AS_SYSTEM_HIVE:
+ Status = NtfsMarkAsSystemHive( IrpContext, Irp );
+ break;
+
+ case FSCTL_FILESYSTEM_GET_STATISTICS:
+ Status = NtfsGetStatistics( IrpContext, Irp );
+ break;
+
+ case FSCTL_GET_NTFS_VOLUME_DATA:
+ Status = NtfsGetVolumeData( IrpContext, Irp );
+ break;
+
+ case FSCTL_GET_VOLUME_BITMAP:
+ Status = NtfsGetVolumeBitmap( IrpContext, Irp );
+ break;
+
+ case FSCTL_GET_RETRIEVAL_POINTERS:
+ Status = NtfsGetRetrievalPointers( IrpContext, Irp );
+ break;
+
+ case FSCTL_GET_NTFS_FILE_RECORD:
+ Status = NtfsGetMftRecord( IrpContext, Irp );
+ break;
+
+ case FSCTL_MOVE_FILE:
+
+ //
+ // Always make this synchronous for MoveFile
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ Status = NtfsMoveFile( IrpContext, Irp );
+
+ break;
+
+ case FSCTL_IS_VOLUME_DIRTY:
+ Status = NtfsIsVolumeDirty( IrpContext, Irp );
+ break;
+
+ case FSCTL_ALLOW_EXTENDED_DASD_IO :
+ Status = NtfsSetExtendedDasdIo( IrpContext, Irp );
+ break;
+
+ default :
+
+ //
+ // Core Ntfs does not understand this FsCtl. We poll the loadable
+ // portions to see if they can process it.
+ //
+
+#ifdef _CAIRO_
+ if (NtfsData.ViewCallBackTable != NULL) {
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ ULONG OutputBufferLength;
+
+ NtfsDecodeFileObject( IrpContext,
+ IrpSp->FileObject,
+ &Vcb,
+ &Fcb,
+ &Scb,
+ &Ccb,
+ TRUE );
+
+ OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength;
+
+ Status = NtfsData.ViewCallBackTable->ViewFileSystemControl(
+ IrpContext,
+ Fcb,
+ Scb,
+ FsControlCode,
+ IrpSp->Parameters.FileSystemControl.InputBufferLength,
+ IrpSp->Parameters.FileSystemControl.Type3InputBuffer,
+ &OutputBufferLength,
+ Irp->UserBuffer );
+
+ //
+ // If the request succeeded or was recognized in some way by Views
+ // then complete the request and return the buffer length
+ //
+
+ if (NT_SUCCESS( Status )) {
+ Irp->IoStatus.Information = OutputBufferLength;
+ NtfsCompleteRequest( &IrpContext, &Irp, Status);
+ break;
+ } else if (Status != STATUS_INVALID_DEVICE_REQUEST) {
+ NtfsCompleteRequest( &IrpContext, &Irp, Status);
+ break;
+ }
+ }
+
+#endif // _CAIRO_
+
+ DebugTrace( 0, Dbg, ("Invalid control code -> %08lx\n", FsControlCode) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status = STATUS_INVALID_PARAMETER );
+ break;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsUserFsRequest -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsOplockRequest (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine to handle oplock requests made via the
+ NtFsControlFile call.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG FsControlCode;
+ ULONG OplockCount = 0;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location, and save some reference to
+ // make life easier
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsOplockRequest, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // We only permit oplock requests on files.
+ //
+
+ if (TypeOfOpen != UserFileOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsOplockRequest -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // We jam Wait to TRUE in the IrpContext. This prevents us from returning
+ // STATUS_PENDING if we can't acquire the file. The caller would
+ // interpret that as having acquired an oplock.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // Switch on the function control code. We grab the Fcb exclusively
+ // for oplock requests, shared for oplock break acknowledgement.
+ //
+
+ switch ( FsControlCode ) {
+
+ case FSCTL_REQUEST_OPLOCK_LEVEL_1:
+ case FSCTL_REQUEST_BATCH_OPLOCK:
+ case FSCTL_REQUEST_FILTER_OPLOCK:
+ case FSCTL_REQUEST_OPLOCK_LEVEL_2:
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, Scb, FALSE, FALSE );
+
+ if (FsControlCode == FSCTL_REQUEST_OPLOCK_LEVEL_2) {
+
+ if (Scb->ScbType.Data.FileLock != NULL) {
+
+ OplockCount = (ULONG) FsRtlAreThereCurrentFileLocks( Scb->ScbType.Data.FileLock );
+ }
+
+ } else {
+
+ OplockCount = Scb->CleanupCount;
+ }
+
+ break;
+
+ case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE:
+ case FSCTL_OPBATCH_ACK_CLOSE_PENDING :
+ case FSCTL_OPLOCK_BREAK_NOTIFY:
+ case FSCTL_OPLOCK_BREAK_ACK_NO_2:
+
+ NtfsAcquireSharedFcb( IrpContext, Fcb, Scb, FALSE );
+ break;
+
+ default:
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsOplockRequest -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Use a try finally to free the Fcb.
+ //
+
+ try {
+
+ //
+ // Call the FsRtl routine to grant/acknowledge oplock.
+ //
+
+ Status = FsRtlOplockFsctrl( &Scb->ScbType.Data.Oplock,
+ Irp,
+ OplockCount );
+
+ //
+ // Set the flag indicating if Fast I/O is possible
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ } finally {
+
+ DebugUnwind( NtfsOplockRequest );
+
+ //
+ // Release all of our resources
+ //
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+
+ //
+ // If this is not an abnormal termination then complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, NULL, 0 );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsOplockRequest -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsLockVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the lock volume operation. It is responsible for
+ either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ BOOLEAN VcbAcquired = FALSE;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsLockVolume...\n") );
+
+ //
+ // Extract and decode the file object, and only permit user volume opens
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsLockVolume -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire exclusive access to the Ntfs global.
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+
+ try {
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ //
+ // Check if the Vcb is already locked, or if the open file count
+ // is greater than 1 (which implies that someone else also is
+ // currently using the volume, or a file on the volume). We also fail
+ // this request if the volume has already gone through the dismount
+ // vcb process.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) ||
+ (Vcb->CleanupCount > 1)) {
+
+ DebugTrace( 0, Dbg, ("Volume is currently in use\n") );
+
+ Status = STATUS_ACCESS_DENIED;
+
+ //
+ // If the volume is already locked then it might have been the result of an
+ // exclusive DASD open. Allow that user to explictly lock the volume.
+ //
+
+ } else if (FlagOn( Vcb->VcbState, VCB_STATE_LOCKED )) {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_EXPLICIT_LOCK )) {
+
+ DebugTrace( 0, Dbg, ("User has already locked volume\n") );
+
+ Status = STATUS_ACCESS_DENIED;
+
+ } else {
+
+ SetFlag( Vcb->VcbState, VCB_STATE_EXPLICIT_LOCK );
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // We can take this path if the volume has already been locked via
+ // create but has not taken the PerformDismountOnVcb path. We checked
+ // for this above by looking at the VOLUME_MOUNTED flag in the Vcb.
+ //
+
+ } else {
+
+ //
+ // There better be system files objects only at this point.
+ //
+
+ if (!NT_SUCCESS( NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, FALSE ))
+ || Vcb->CloseCount - Vcb->SystemFileCloseCount > 1) {
+
+ DebugTrace( 0, Dbg, ("Volume has user file objects\n") );
+
+ Status = STATUS_ACCESS_DENIED;
+
+ } else {
+
+ //
+ // We don't really want to do all of the perform dismount here because
+ // that will cause us to remount a new volume before we're ready.
+ // At this time we only want to stop the log file and close up our
+ // internal attribute streams. When the user (i.e., chkdsk) does an
+ // unlock then we'll finish up with the dismount call
+ //
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, FALSE );
+
+ SetFlag( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_EXPLICIT_LOCK );
+ Vcb->FileObjectWithVcbLocked = (PFILE_OBJECT)(((ULONG)IrpSp->FileObject) + 1);
+
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsLockVolume );
+
+ if (VcbAcquired) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ //
+ // Release all of our resources
+ //
+
+ NtfsReleaseGlobal( IrpContext );
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsLockVolume -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsUnlockVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the unlock volume operation. It is responsible for
+ either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsUnlockVolume...\n") );
+
+ //
+ // Extract and decode the file object, and only permit user volume opens
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsLockVolume -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire exclusive access to the Vcb
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_EXPLICIT_LOCK )) {
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ //
+ // Unlock the volume and complete the Irp
+ //
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_LOCKED | VCB_STATE_EXPLICIT_LOCK );
+ Vcb->FileObjectWithVcbLocked = NULL;
+
+ Status = STATUS_SUCCESS;
+
+#ifdef _CAIRO_
+
+ //
+ // If the quota tracking has been requested and the quotas need to be
+ // repaired then try to repair them now.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED ) &&
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT |
+ QUOTA_FLAG_PENDING_DELETES )) {
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+
+#endif // _CAIRO_
+
+ } else {
+
+ Status = STATUS_NOT_LOCKED;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsUnlockVolume );
+
+
+ //
+ // Release all of our resources
+ //
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsUnlockVolume -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsDismountVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the dismount volume operation. It is responsible for
+ either completing of enqueuing the input Irp.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ BOOLEAN VcbAcquired = FALSE;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsDismountVolume...\n") );
+
+ //
+ // Extract and decode the file object, and only permit user volume opens
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsLockVolume -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire exclusive access to the global resource. We want to
+ // prevent checkpointing from running on this volume.
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+
+ try {
+
+ //
+ // Acquire the Vcb exclusively.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ VcbAcquired = TRUE;
+
+ //
+ // If there's a pagefile on this volume, or if this is the
+ // system volume, don't do the dismount.
+ //
+
+ if (FlagOn(Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT) &&
+ !FlagOn(Vcb->VcbState, VCB_STATE_LOCKED)) {
+
+ try_return( Status = STATUS_ACCESS_DENIED );
+ }
+
+ //
+ // We will ignore this request if this is a dismount with only readonly files
+ // opened. To decide if there are only readonly user files opened we will
+ // check if the readonly count equals the close count for user files minus the one
+ // for the handle with the volume locked
+ //
+
+ if (FlagOn(Vcb->VcbState, VCB_STATE_LOCKED) &&
+ (Vcb->ReadOnlyCloseCount == ((Vcb->CloseCount - Vcb->SystemFileCloseCount) - 1))) {
+
+ DebugTrace( 0, Dbg, ("Volume has readonly files opened\n") );
+
+ Status = STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // Get as many cached writes out to disk as we can and mark
+ // all the streams for dismount.
+ //
+
+ NtfsFlushVolume( IrpContext, Vcb, TRUE, TRUE, TRUE, TRUE );
+
+ //
+ // Actually do the dismount.
+ //
+
+ NtfsPerformDismountOnVcb( IrpContext, Vcb, TRUE );
+
+ //
+ // Set this flag to prevent the volume from being accessed
+ // via checkpointing.
+ //
+
+ SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ Status = STATUS_SUCCESS;
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ SetFlag( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsDismountVolume );
+
+ //
+ // Release all of our resources
+ //
+
+ if (VcbAcquired) {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ NtfsReleaseGlobal( IrpContext );
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDismountVolume -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsDirtyVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine marks the specified volume dirty.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsDirtyVolume...\n") );
+
+ //
+ // Extract and decode the file object, and only permit user volume opens
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsDirtyVolume -> %08lx\n", STATUS_INVALID_PARAMETER) );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Fail this request if the volume is not mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ Status = STATUS_VOLUME_DISMOUNTED;
+
+ } else {
+
+ NtfsPostVcbIsCorrupt( IrpContext, 0, NULL, NULL );
+ }
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsDirtyVolume -> STATUS_SUCCESS\n") );
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsGetDiskGeometry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT RealDevice,
+ IN PDISK_GEOMETRY DiskGeometry,
+ IN PPARTITION_INFORMATION PartitionInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This procedure gets the disk geometry of the specified device
+
+Arguments:
+
+ RealDevice - Supplies the real device that is being queried
+
+ DiskGeometry - Receives the disk geometry
+
+ PartitionInfo - Receives the partition information
+
+Return Value:
+
+ BOOLEAN - TRUE if the media is write protected, FALSE otherwise
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG i;
+ PREVENT_MEDIA_REMOVAL Prevent;
+ BOOLEAN WriteProtected = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGetDiskGeometry:\n") );
+ DebugTrace( 0, Dbg, ("RealDevice = %08lx\n", RealDevice) );
+ DebugTrace( 0, Dbg, ("DiskGeometry = %08lx\n", DiskGeometry) );
+
+ //
+ // Attempt to lock any removable media, ignoring status.
+ //
+
+ Prevent.PreventMediaRemoval = TRUE;
+ (PVOID)NtfsDeviceIoControl( IrpContext,
+ RealDevice,
+ IOCTL_DISK_MEDIA_REMOVAL,
+ &Prevent,
+ sizeof(PREVENT_MEDIA_REMOVAL),
+ NULL,
+ 0 );
+
+ //
+ // See if the media is write protected. On success or any kind
+ // of error (possibly illegal device function), assume it is
+ // writeable, and only complain if he tells us he is write protected.
+ //
+
+ Status = NtfsDeviceIoControl( IrpContext,
+ RealDevice,
+ IOCTL_DISK_IS_WRITABLE,
+ NULL,
+ 0,
+ NULL,
+ 0 );
+
+ //
+ // Remember if the media is write protected but don't raise the error now.
+ // If the volume is not Ntfs then let another filesystem try.
+ //
+ if (Status == STATUS_MEDIA_WRITE_PROTECTED) {
+
+ WriteProtected = TRUE;
+ Status = STATUS_SUCCESS;
+ }
+
+ for (i = 0; i < 2; i++) {
+
+ if (i == 0) {
+
+ Status = NtfsDeviceIoControl( IrpContext,
+ RealDevice,
+ IOCTL_DISK_GET_DRIVE_GEOMETRY,
+ NULL,
+ 0,
+ DiskGeometry,
+ sizeof(DISK_GEOMETRY) );
+
+ } else {
+
+ Status = NtfsDeviceIoControl( IrpContext,
+ RealDevice,
+ IOCTL_DISK_GET_PARTITION_INFO,
+ NULL,
+ 0,
+ PartitionInfo,
+ sizeof(PARTITION_INFORMATION) );
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsGetDiskGeometry->VOID\n") );
+
+ return WriteProtected;
+}
+
+
+NTSTATUS
+NtfsDeviceIoControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN ULONG IoCtl,
+ IN PVOID InputBuffer OPTIONAL,
+ IN ULONG InputBufferLength,
+ IN PVOID OutputBuffer OPTIONAL,
+ IN ULONG OutputBufferLength
+ )
+
+/*++
+
+Routine Description:
+
+ This procedure issues an Ioctl to the lower device, and waits
+ for the answer.
+
+Arguments:
+
+ DeviceObject - Supplies the device to issue the request to
+
+ IoCtl - Gives the IoCtl to be used
+
+ XxBuffer - Gives the buffer pointer for the ioctl, if any
+
+ XxBufferLength - Gives the length of the buffer, if any
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIRP Irp;
+ KEVENT Event;
+ IO_STATUS_BLOCK Iosb;
+ NTSTATUS Status;
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+
+ Irp = IoBuildDeviceIoControlRequest( IoCtl,
+ DeviceObject,
+ InputBuffer,
+ InputBufferLength,
+ OutputBuffer,
+ OutputBufferLength,
+ FALSE,
+ &Event,
+ &Iosb );
+
+ if (Irp == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ Status = IoCallDriver( DeviceObject, Irp );
+
+ if (Status == STATUS_PENDING) {
+
+ (VOID)KeWaitForSingleObject( &Event,
+ Executive,
+ KernelMode,
+ FALSE,
+ (PLARGE_INTEGER)NULL );
+
+ Status = Iosb.Status;
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsReadBootSector (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PSCB *BootScb,
+ OUT PBCB *BootBcb,
+ OUT PVOID *BootSector
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads and returns a pointer to the boot sector for the volume.
+
+ Volumes formatted under 3.51 and earlier will have a boot sector at sector
+ 0 and another halfway through the disk. Volumes formatted with NT 4.0
+ will have a boot sector at the end of the disk, in the sector beyond the
+ stated size of the volume in the boot sector. When this call is made the
+ Vcb has the sector count from the device driver so we subtract one to find
+ the last sector.
+
+Arguments:
+
+ Vcb - Supplies the Vcb for the operation
+
+ BootScb - Receives the Scb for the boot file
+
+ BootBcb - Receives the bcb for the boot sector
+
+ BootSector - Receives a pointer to the boot sector
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSCB Scb = NULL;
+ BOOLEAN Error = FALSE;
+
+ FILE_REFERENCE FileReference = { BOOT_FILE_NUMBER, 0, BOOT_FILE_NUMBER };
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReadBootSector:\n") );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+
+ //
+ // Create a temporary scb for reading in the boot sector and initialize the
+ // mcb for it.
+ //
+
+ Scb = NtfsCreatePrerestartScb( IrpContext,
+ Vcb,
+ &FileReference,
+ $DATA,
+ NULL,
+ 0 );
+
+ *BootScb = Scb;
+
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = (PAGE_SIZE * 2) + Vcb->BytesPerSector;
+
+ //
+ // We don't want to look up the size for this Scb.
+ //
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+
+ (VOID)NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ (LONGLONG)0,
+ (LONGLONG)0,
+ (LONGLONG)Vcb->ClustersPerPage,
+ FALSE );
+
+
+ (VOID)NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ (LONGLONG)Vcb->ClustersPerPage,
+ Vcb->NumberSectors >> 1,
+ (LONGLONG)Vcb->ClustersPerPage,
+ FALSE );
+
+ (VOID)NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ Int64ShllMod32( (LONGLONG) Vcb->ClustersPerPage, 1 ),
+ Vcb->NumberSectors - 1,
+ 1,
+ FALSE );
+
+ //
+ // Try reading in the first boot sector
+ //
+
+ try {
+
+ NtfsMapStream( IrpContext,
+ Scb,
+ (LONGLONG)0,
+ Vcb->BytesPerSector,
+ BootBcb,
+ BootSector );
+
+ //
+ // If we got an exception trying to read the first boot sector,
+ // then handle the exception by trying to read the second boot
+ // sector. If that faults too, then we just allow ourselves to
+ // unwind and return the error.
+ //
+
+ } except (FsRtlIsNtstatusExpected(GetExceptionCode()) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH) {
+
+ Error = TRUE;
+ }
+
+ //
+ // Get out if we didn't get an error. Otherwise try the middle sector.
+ // We want to read this next because we know that 4.0 format will clear
+ // this before writing the last sector. Otherwise we could see a
+ // stale boot sector in the last sector even though a 3.51 format was
+ // the last to run.
+ //
+
+ if (!Error) { return; }
+
+ Error = FALSE;
+
+ try {
+
+ NtfsMapStream( IrpContext,
+ Scb,
+ (LONGLONG)PAGE_SIZE,
+ Vcb->BytesPerSector,
+ BootBcb,
+ BootSector );
+
+ //
+ // Ignore this sector if not Ntfs. This could be the case for
+ // a bad sector 0 on a FAT volume.
+ //
+
+ if (!NtfsIsBootSectorNtfs( *BootSector, Vcb )) {
+
+ NtfsUnpinBcb( BootBcb );
+ Error = TRUE;
+ }
+
+ //
+ // If we got an exception trying to read the first boot sector,
+ // then handle the exception by trying to read the second boot
+ // sector. If that faults too, then we just allow ourselves to
+ // unwind and return the error.
+ //
+
+ } except (FsRtlIsNtstatusExpected(GetExceptionCode()) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH) {
+
+ Error = TRUE;
+ }
+
+ //
+ // Get out if we didn't get an error. Otherwise try the middle sector.
+ //
+
+ if (!Error) { return; }
+
+ NtfsMapStream( IrpContext,
+ Scb,
+ (LONGLONG) (PAGE_SIZE * 2),
+ Vcb->BytesPerSector,
+ BootBcb,
+ BootSector );
+
+ //
+ // Clear the header flag in the Scb.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( 0, Dbg, ("BootScb > %08lx\n", *BootScb) );
+ DebugTrace( 0, Dbg, ("BootBcb > %08lx\n", *BootBcb) );
+ DebugTrace( 0, Dbg, ("BootSector > %08lx\n", *BootSector) );
+ DebugTrace( -1, Dbg, ("NtfsReadBootSector->VOID\n") );
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+//
+// First define a local macro to number the tests for the debug case.
+//
+
+#ifdef NTFSDBG
+#define NextTest ++CheckNumber &&
+#else
+#define NextTest TRUE &&
+#endif
+
+BOOLEAN
+NtfsIsBootSectorNtfs (
+ IN PPACKED_BOOT_SECTOR BootSector,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks the boot sector to determine if it is an NTFS partition.
+
+ The Vcb must alread be initialized from the device object to contain the
+ parts of the device geometry we care about here: bytes per sector and
+ total number of sectors in the partition.
+
+Arguments:
+
+ BootSector - Pointer to the boot sector which has been read in.
+
+ Vcb - Pointer to a Vcb which has been initialized with sector size and
+ number of sectors on the partition.
+
+Return Value:
+
+ FALSE - If the boot sector is not for Ntfs.
+ TRUE - If the boot sector is for Ntfs.
+
+--*/
+
+{
+#ifdef NTFSDBG
+ ULONG CheckNumber = 0;
+#endif
+
+ PULONG l;
+ ULONG Checksum = 0;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsBootSectorNtfs\n") );
+ DebugTrace( 0, Dbg, ("BootSector = %08lx\n", BootSector) );
+
+ //
+ // First calculate the boot sector checksum
+ //
+
+ for (l = (PULONG)BootSector; l < (PULONG)&BootSector->Checksum; l++) {
+ Checksum += *l;
+ }
+
+ //
+ // Now perform all the checks, starting with the Name and Checksum.
+ // The remaining checks should be obvious, including some fields which
+ // must be 0 and other fields which must be a small power of 2.
+ //
+
+ if (NextTest
+ (BootSector->Oem[0] == 'N') &&
+ (BootSector->Oem[1] == 'T') &&
+ (BootSector->Oem[2] == 'F') &&
+ (BootSector->Oem[3] == 'S') &&
+ (BootSector->Oem[4] == ' ') &&
+ (BootSector->Oem[5] == ' ') &&
+ (BootSector->Oem[6] == ' ') &&
+ (BootSector->Oem[7] == ' ')
+
+ &&
+
+ // NextTest
+ // (BootSector->Checksum == Checksum)
+ //
+ // &&
+
+ //
+ // Check number of bytes per sector. The low order byte of this
+ // number must be zero (smallest sector size = 0x100) and the
+ // high order byte shifted must equal the bytes per sector gotten
+ // from the device and stored in the Vcb. And just to be sure,
+ // sector size must be less than page size.
+ //
+
+ NextTest
+ (BootSector->PackedBpb.BytesPerSector[0] == 0)
+
+ &&
+
+ NextTest
+ ((ULONG)(BootSector->PackedBpb.BytesPerSector[1] << 8) == Vcb->BytesPerSector)
+
+ &&
+
+ NextTest
+ (BootSector->PackedBpb.BytesPerSector[1] << 8 <= PAGE_SIZE)
+
+ &&
+
+ //
+ // Sectors per cluster must be a power of 2.
+ //
+
+ NextTest
+ ((BootSector->PackedBpb.SectorsPerCluster[0] == 0x1) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x2) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x4) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x8) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x10) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x20) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x40) ||
+ (BootSector->PackedBpb.SectorsPerCluster[0] == 0x80))
+
+ &&
+
+ //
+ // These fields must all be zero. For both Fat and HPFS, some of
+ // these fields must be nonzero.
+ //
+
+ NextTest
+ (BootSector->PackedBpb.ReservedSectors[0] == 0) &&
+ (BootSector->PackedBpb.ReservedSectors[1] == 0) &&
+ (BootSector->PackedBpb.Fats[0] == 0) &&
+ (BootSector->PackedBpb.RootEntries[0] == 0) &&
+ (BootSector->PackedBpb.RootEntries[1] == 0) &&
+ (BootSector->PackedBpb.Sectors[0] == 0) &&
+ (BootSector->PackedBpb.Sectors[1] == 0) &&
+ (BootSector->PackedBpb.SectorsPerFat[0] == 0) &&
+ (BootSector->PackedBpb.SectorsPerFat[1] == 0) &&
+ // (BootSector->PackedBpb.HiddenSectors[0] == 0) &&
+ // (BootSector->PackedBpb.HiddenSectors[1] == 0) &&
+ // (BootSector->PackedBpb.HiddenSectors[2] == 0) &&
+ // (BootSector->PackedBpb.HiddenSectors[3] == 0) &&
+ (BootSector->PackedBpb.LargeSectors[0] == 0) &&
+ (BootSector->PackedBpb.LargeSectors[1] == 0) &&
+ (BootSector->PackedBpb.LargeSectors[2] == 0) &&
+ (BootSector->PackedBpb.LargeSectors[3] == 0)
+
+ &&
+
+ //
+ // Number of Sectors cannot be greater than the number of sectors
+ // on the partition.
+ //
+
+ NextTest
+ (BootSector->NumberSectors <= Vcb->NumberSectors)
+
+ &&
+
+ //
+ // Check that both Lcn values are for sectors within the partition.
+ //
+
+ NextTest
+ ((BootSector->MftStartLcn * BootSector->PackedBpb.SectorsPerCluster[0]) <=
+ Vcb->NumberSectors)
+
+ &&
+
+ NextTest
+ ((BootSector->Mft2StartLcn * BootSector->PackedBpb.SectorsPerCluster[0]) <=
+ Vcb->NumberSectors)
+
+ &&
+
+ //
+ // Clusters per file record segment and default clusters for Index
+ // Allocation Buffers must be a power of 2. A zero indicates that the
+ // size of these structures is the default size.
+ //
+
+ NextTest
+ (((BootSector->ClustersPerFileRecordSegment >= -31) &&
+ (BootSector->ClustersPerFileRecordSegment <= -9)) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x1) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x2) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x4) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x8) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x10) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x20) ||
+ (BootSector->ClustersPerFileRecordSegment == 0x40))
+
+ &&
+
+ NextTest
+ (((BootSector->DefaultClustersPerIndexAllocationBuffer >= -31) &&
+ (BootSector->DefaultClustersPerIndexAllocationBuffer <= -9)) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x1) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x2) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x4) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x8) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x10) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x20) ||
+ (BootSector->DefaultClustersPerIndexAllocationBuffer == 0x40))) {
+
+ DebugTrace( -1, Dbg, ("NtfsIsBootSectorNtfs->TRUE\n") );
+
+ return TRUE;
+
+ } else {
+
+ //
+ // If a check failed, print its check number with Debug Trace.
+ //
+
+ DebugTrace( 0, Dbg, ("Boot Sector failed test number %08lx\n", CheckNumber) );
+ DebugTrace( -1, Dbg, ("NtfsIsBootSectorNtfs->FALSE\n") );
+
+ return FALSE;
+ }
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsGetVolumeLabel (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVPB Vpb OPTIONAL,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine gets the serial number and volume label for an NTFS volume
+
+Arguments:
+
+ Vpb - Supplies the Vpb for the volume. The Vpb will receive a copy of
+ the volume label and serial number, if a Vpb is specified.
+
+ Vcb - Supplies the Vcb for the operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+ PVOLUME_INFORMATION VolumeInformation;
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsGetVolumeLabel...\n") );
+
+ //
+ // We read in the volume label attribute to get the volume label.
+ //
+
+ try {
+
+ if (ARGUMENT_PRESENT(Vpb)) {
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $VOLUME_NAME,
+ &AttributeContext )) {
+
+ Vpb->VolumeLabelLength = (USHORT)
+ NtfsFoundAttribute( &AttributeContext )->Form.Resident.ValueLength;
+
+ if ( Vpb->VolumeLabelLength > MAXIMUM_VOLUME_LABEL_LENGTH) {
+
+ Vpb->VolumeLabelLength = MAXIMUM_VOLUME_LABEL_LENGTH;
+ }
+
+ RtlCopyMemory( &Vpb->VolumeLabel[0],
+ NtfsAttributeValue( NtfsFoundAttribute( &AttributeContext ) ),
+ Vpb->VolumeLabelLength );
+
+ } else {
+
+ Vpb->VolumeLabelLength = 0;
+ }
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ //
+ // Remember if the volume is dirty when we are mounting it.
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $VOLUME_INFORMATION,
+ &AttributeContext )) {
+
+ VolumeInformation =
+ (PVOLUME_INFORMATION)NtfsAttributeValue( NtfsFoundAttribute( &AttributeContext ));
+
+ if (FlagOn(VolumeInformation->VolumeFlags, VOLUME_DIRTY)) {
+ SetFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED_DIRTY );
+ } else {
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED_DIRTY );
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsGetVolumeLabel );
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsSetAndGetVolumeTimes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN MarkDirty
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads in the volume times from the standard information attribute
+ of the volume file and also updates the access time to be the current
+ time
+
+Arguments:
+
+ Vcb - Supplies the vcb for the operation.
+
+ MarkDirty - Supplies TRUE if volume is to be marked dirty
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+ PSTANDARD_INFORMATION StandardInformation;
+
+ LONGLONG MountTime;
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsSetAndGetVolumeTimes...\n") );
+
+ try {
+
+ //
+ // Lookup the standard information attribute of the dasd file
+ //
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttributeContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ StandardInformation = (PSTANDARD_INFORMATION)NtfsAttributeValue( NtfsFoundAttribute( &AttributeContext ));
+
+ //
+ // Get the current time and make sure it differs from the time stored
+ // in last access time and then store the new last access time
+ //
+
+ NtfsGetCurrentTime( IrpContext, MountTime );
+
+ if (MountTime == StandardInformation->LastAccessTime) {
+
+ MountTime = MountTime + 1;
+ }
+
+ //****
+ //**** Hold back on the update for now.
+ //****
+ //**** NtfsChangeAttributeValue( IrpContext,
+ //**** Vcb->VolumeDasdScb->Fcb,
+ //**** FIELD_OFFSET(STANDARD_INFORMATION, LastAccessTime),
+ //**** &MountTime,
+ //**** sizeof(MountTime),
+ //**** FALSE,
+ //**** FALSE,
+ //**** &AttributeContext );
+
+ //
+ // Now save all the time fields in our vcb
+ //
+
+ Vcb->VolumeCreationTime = StandardInformation->CreationTime;
+ Vcb->VolumeLastModificationTime = StandardInformation->LastModificationTime;
+ Vcb->VolumeLastChangeTime = StandardInformation->LastChangeTime;
+ Vcb->VolumeLastAccessTime = StandardInformation->LastAccessTime; //****Also hold back = MountTime;
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+
+ //
+ // If the volume was mounted dirty, then set the dirty bit here.
+ //
+
+ if (MarkDirty) {
+
+ NtfsMarkVolumeDirty( IrpContext, Vcb );
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsOpenSystemFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB *Scb,
+ IN PVCB Vcb,
+ IN ULONG FileNumber,
+ IN LONGLONG Size,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN BOOLEAN ModifiedNoWrite
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to open one of the system files by its file number
+ during the mount process. An initial allocation is looked up for the file,
+ unless the optional initial size is specified (in which case this size is
+ used).
+
+Parameters:
+
+ Scb - Pointer to where the Scb pointer is to be stored. If Scb pointer
+ pointed to is NULL, then a PreRestart Scb is created, otherwise the
+ existing Scb is used and only the stream file is set up.
+
+ FileNumber - Number of the system file to open.
+
+ Size - If nonzero, this size is used as the initial size, rather
+ than consulting the file record in the Mft.
+
+ AttributeTypeCode - Supplies the attribute to open, e.g., $DATA or $BITMAP
+
+ ModifiedNoWrite - Indicates if the Memory Manager is not to write this
+ attribute to disk. Applies to streams under transaction
+ control.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ FILE_REFERENCE FileReference;
+ UNICODE_STRING $BadName;
+ PUNICODE_STRING AttributeName = NULL;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsOpenSystemFile:\n") );
+ DebugTrace( 0, Dbg, ("*Scb = %08lx\n", *Scb) );
+ DebugTrace( 0, Dbg, ("FileNumber = %08lx\n", FileNumber) );
+ DebugTrace( 0, Dbg, ("ModifiedNoWrite = %04x\n", ModifiedNoWrite) );
+
+ //
+ // The Bad Cluster data attribute has a name.
+ //
+
+ if (FileNumber == BAD_CLUSTER_FILE_NUMBER) {
+
+ RtlInitUnicodeString( &$BadName, L"$Bad" );
+ AttributeName = &$BadName;
+ }
+
+ //
+ // If the Scb does not already exist, create it.
+ //
+
+ if (*Scb == NULL) {
+
+ NtfsSetSegmentNumber( &FileReference, 0, FileNumber );
+ FileReference.SequenceNumber = (FileNumber == 0 ? 1 : (USHORT)FileNumber);
+
+ //
+ // Create the Scb.
+ //
+
+ *Scb = NtfsCreatePrerestartScb( IrpContext,
+ Vcb,
+ &FileReference,
+ AttributeTypeCode,
+ AttributeName,
+ 0 );
+
+ NtfsAcquireExclusiveScb( IrpContext, *Scb );
+ }
+
+ //
+ // Set the modified-no-write bit in the Scb if necessary.
+ //
+
+ if (ModifiedNoWrite) {
+
+ SetFlag( (*Scb)->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
+ }
+
+ //
+ // Lookup the file sizes.
+ //
+
+ if (Size == 0) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, *Scb, NULL );
+
+ //
+ // Otherwise, just set the size we were given.
+ //
+
+ } else {
+
+ (*Scb)->Header.FileSize.QuadPart =
+ (*Scb)->Header.ValidDataLength.QuadPart = Size;
+
+ (*Scb)->Header.AllocationSize.QuadPart = LlClustersFromBytes( Vcb, Size );
+ (*Scb)->Header.AllocationSize.QuadPart = LlBytesFromClusters( Vcb,
+ (*Scb)->Header.AllocationSize.QuadPart );
+
+ SetFlag( (*Scb)->ScbState, SCB_STATE_HEADER_INITIALIZED );
+ }
+
+ //
+ // Finally, create the stream, if not already there.
+ // And check if we should increment the counters
+ // If this is the volume file or the bad cluster file, we only increment the counts.
+ //
+
+ if ((FileNumber == VOLUME_DASD_NUMBER) ||
+ (FileNumber == BAD_CLUSTER_FILE_NUMBER)) {
+
+ if ((*Scb)->FileObject == 0) {
+
+ NtfsIncrementCloseCounts( *Scb, TRUE, FALSE );
+
+ (*Scb)->FileObject = (PFILE_OBJECT) 1;
+ }
+
+ } else {
+
+ NtfsCreateInternalAttributeStream( IrpContext, *Scb, TRUE );
+ }
+
+ DebugTrace( 0, Dbg, ("*Scb > %08lx\n", *Scb) );
+ DebugTrace( -1, Dbg, ("NtfsOpenSystemFile -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsOpenRootDirectory (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens the root directory by file number, and fills in the
+ related pointers in the Vcb.
+
+Arguments:
+
+ Vcb - Pointer to the Vcb for the volume
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFCB RootFcb;
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ FILE_REFERENCE FileReference;
+ BOOLEAN MustBeFalse;
+
+ PAGED_CODE();
+
+ //
+ // Put special code here to do initial open of Root Index.
+ //
+
+ RootFcb = NtfsCreateRootFcb( IrpContext, Vcb );
+
+ NtfsSetSegmentNumber( &FileReference, 0, ROOT_FILE_NAME_INDEX_NUMBER );
+ FileReference.SequenceNumber = ROOT_FILE_NAME_INDEX_NUMBER;
+
+ //
+ // Now create its Scb and acquire it exclusive.
+ //
+
+ Vcb->RootIndexScb = NtfsCreateScb( IrpContext,
+ RootFcb,
+ $INDEX_ALLOCATION,
+ &NtfsFileNameIndex,
+ FALSE,
+ &MustBeFalse );
+
+ //
+ // Now allocate a buffer to hold the normalized name for the root.
+ //
+
+ Vcb->RootIndexScb->ScbType.Index.NormalizedName.Buffer = NtfsAllocatePool(PagedPool, 2 );
+ Vcb->RootIndexScb->ScbType.Index.NormalizedName.MaximumLength =
+ Vcb->RootIndexScb->ScbType.Index.NormalizedName.Length = 2;
+ Vcb->RootIndexScb->ScbType.Index.NormalizedName.Buffer[0] = '\\';
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->RootIndexScb );
+
+ //
+ // Lookup the attribute and it better be there
+ //
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ RootFcb,
+ &FileReference,
+ $INDEX_ROOT,
+ &Context ) ) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // We need to update the duplicated information in the
+ // Fcb.
+
+ NtfsUpdateFcbInfoFromDisk( IrpContext, TRUE, RootFcb, NULL, NULL );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+
+ return;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsQueryRetrievalPointers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the query retrieval pointers operation.
+ It returns the retrieval pointers for the specified input
+ file from the start of the file to the request map size specified
+ in the input buffer.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ PIO_STACK_LOCATION IrpSp;
+
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PLONGLONG RequestedMapSize;
+ PLONGLONG *MappingPairs;
+
+ PVOID RangePtr;
+ ULONG Index;
+ ULONG i;
+ LONGLONG SectorCount;
+ LONGLONG Lbo;
+ LONGLONG Vbo;
+ LONGLONG Vcn;
+ LONGLONG MapSize;
+
+ //
+ // Only Kernel mode clients may query retrieval pointer information about
+ // a file, and then only the paging file. Ensure that this is the case
+ // for this caller.
+ //
+
+ if (Irp->RequestorMode != KernelMode) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Get the current stack location and extract the input and output
+ // buffer information. The input contains the requested size of
+ // the mappings in terms of VBO. The output parameter will receive
+ // a pointer to nonpaged pool where the mapping pairs are stored.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ ASSERT( IrpSp->Parameters.FileSystemControl.InputBufferLength == sizeof(LARGE_INTEGER) );
+ ASSERT( IrpSp->Parameters.FileSystemControl.OutputBufferLength == sizeof(PVOID) );
+
+ RequestedMapSize = (PLONGLONG)IrpSp->Parameters.FileSystemControl.Type3InputBuffer;
+ MappingPairs = (PLONGLONG *)Irp->UserBuffer;
+
+ //
+ // Decode the file object and assert that it is the paging file
+ //
+ //
+
+ (VOID)NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (!FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE)) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire exclusive access to the Scb
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // Check if the mapping the caller requested is too large
+ //
+
+ if (*RequestedMapSize > Scb->Header.FileSize.QuadPart) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Now get the index for the mcb entry that will contain the
+ // callers request and allocate enough pool to hold the
+ // output mapping pairs.
+ //
+
+ //
+ // Compute the Vcn which contains the byte just before the offset size
+ // passed in.
+ //
+
+ MapSize = *RequestedMapSize - 1;
+
+ if (*RequestedMapSize == 0) {
+
+ Index = 0;
+
+ } else {
+
+ Vcn = Int64ShraMod32( MapSize, Vcb->ClusterShift );
+ (VOID)NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, NULL, NULL, NULL, NULL, &RangePtr, &Index );
+ }
+
+ *MappingPairs = NtfsAllocatePool( NonPagedPool, (Index + 2) * (2 * sizeof(LARGE_INTEGER)) );
+
+ //
+ // Now copy over the mapping pairs from the mcb
+ // to the output buffer. We store in [sector count, lbo]
+ // mapping pairs and end with a zero sector count.
+ //
+
+ MapSize = *RequestedMapSize;
+
+ i = 0;
+
+ if (MapSize != 0) {
+
+ for (; i <= Index; i += 1) {
+
+ (VOID)NtfsGetNextNtfsMcbEntry( &Scb->Mcb, &RangePtr, i, &Vbo, &Lbo, &SectorCount );
+
+ SectorCount = LlBytesFromClusters( Vcb, SectorCount );
+
+ if (SectorCount > MapSize) {
+ SectorCount = MapSize;
+ }
+
+ (*MappingPairs)[ i*2 + 0 ] = SectorCount;
+ (*MappingPairs)[ i*2 + 1 ] = LlBytesFromClusters( Vcb, Lbo );
+
+ MapSize = MapSize - SectorCount;
+ }
+ }
+
+ (*MappingPairs)[ i*2 + 0 ] = 0;
+
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsQueryRetrievalPointers );
+
+ //
+ // Release all of our resources
+ //
+
+ NtfsReleaseScb( IrpContext, Scb );
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the compression state of the opened file/directory
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PUSHORT CompressionState;
+
+ //
+ // Get the current stack location and extract the output
+ // buffer information. The output parameter will receive
+ // the compressed state of the file/directory.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Get a pointer to the output buffer. Look at the system buffer field in th
+ // irp first. Then the Irp Mdl.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ CompressionState = Irp->AssociatedIrp.SystemBuffer;
+
+ } else if (Irp->MdlAddress != NULL) {
+
+ CompressionState = MmGetSystemAddressForMdl( Irp->MdlAddress );
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_USER_BUFFER );
+ return STATUS_INVALID_USER_BUFFER;
+ }
+
+ //
+ // Make sure the output buffer is large enough and then initialize
+ // the answer to be that the file isn't compressed
+ //
+
+ if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(USHORT)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ *CompressionState = 0;
+
+ //
+ // Decode the file object
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if ((TypeOfOpen != UserFileOpen) &&
+#ifdef _CAIRO_
+ (TypeOfOpen != UserPropertySetOpen) &&
+#endif // _CAIRO_
+ (TypeOfOpen != UserDirectoryOpen)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire shared access to the Scb
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+
+ //
+ // If this is the index allocation Scb and it has not been initialized then
+ // lookup the index root and perform the initialization.
+ //
+
+ if ((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
+ (Scb->ScbType.Index.BytesPerIndexBuffer == 0)) {
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Use a try-finally to perform cleanup.
+ //
+
+ try {
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &Context )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ NtfsUpdateIndexScbFromAttribute( Scb,
+ NtfsFoundAttribute( &Context ));
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &Context );
+
+ if (AbnormalTermination()) { NtfsReleaseScb( IrpContext, Scb ); }
+ }
+ }
+
+ //
+ // Return the compression state and the size of the returned data.
+ //
+
+ *CompressionState = (USHORT)(Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
+ if (*CompressionState != 0) {
+ *CompressionState += 1;
+ }
+
+ Irp->IoStatus.Information = sizeof( USHORT );
+
+ //
+ // Release all of our resources
+ //
+
+ NtfsReleaseScb( IrpContext, Scb );
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local Support Routine
+//
+
+VOID
+NtfsChangeAttributeCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVCB Vcb,
+ IN PCCB Ccb,
+ IN USHORT CompressionState
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the compression state of an attribute on disk,
+ from not compressed to compressed, or visa versa.
+
+ To turn compression off, the caller must already have the Scb acquired
+ exclusive, and guarantee that the entire file is not compressed.
+
+Arguments:
+
+ Scb - Scb for affected stream
+
+ Vcb - Vcb for volume
+
+ Ccb - Ccb for the open handle
+
+ CompressionState - 0 for no compression or nonzero for Rtl compression code - 1
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ ATTRIBUTE_RECORD_HEADER NewAttribute;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ULONG AttributeSizeChange;
+ ULONG OriginalFileAttributes;
+ UCHAR OriginalHeaderFlags;
+ UCHAR OriginalCompressionUnitShift;
+ ULONG OriginalCompressionUnit;
+
+ PFCB Fcb = Scb->Fcb;
+ LONGLONG ByteCount;
+
+ ULONG NewCompressionUnit;
+ UCHAR NewCompressionUnitShift;
+
+ //
+ // Prepare to lookup and change attribute.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ ASSERT( (Scb->Header.PagingIoResource == NULL) ||
+ (IrpContext->FcbWithPagingExclusive == Fcb) ||
+ (IrpContext->FcbWithPagingExclusive == (PFCB) Scb) );
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ OriginalFileAttributes = Fcb->Info.FileAttributes;
+ OriginalHeaderFlags = Scb->Header.Flags;
+ OriginalCompressionUnitShift = Scb->CompressionUnitShift;
+ OriginalCompressionUnit = Scb->CompressionUnit;
+
+ try {
+
+ //
+ // Lookup the attribute and pin it so that we can modify it.
+ //
+
+ if ((Scb->Header.NodeTypeCode == NTFS_NTC_SCB_INDEX) ||
+ (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX)) {
+
+ //
+ // Lookup the attribute record from the Scb.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &AttrContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ } else {
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+ }
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, &AttrContext );
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+
+ if ((CompressionState != 0) && !NtfsIsAttributeResident(Attribute)) {
+
+ LONGLONG Temp;
+ ULONG CompressionUnitInClusters;
+
+ //
+ // If we are turning compression on, then we need to fill out the
+ // allocation of the compression unit containing file size, or else
+ // it will be interpreted as compressed when we fault it in. This
+ // is peanuts compared to the dual copies of clusters we keep around
+ // in the loop below when we rewrite the file.
+ //
+
+ CompressionUnitInClusters =
+ ClustersFromBytes( Vcb, Vcb->BytesPerCluster << NTFS_CLUSTERS_PER_COMPRESSION );
+
+ Temp = LlClustersFromBytes(Vcb, Scb->Header.AllocationSize.QuadPart);
+
+ //
+ // If FileSize is not already at a cluster boundary, then add
+ // allocation.
+ //
+
+ if ((ULONG)Temp & (CompressionUnitInClusters - 1)) {
+
+ NtfsAddAllocation( IrpContext,
+ NULL,
+ Scb,
+ Temp,
+ CompressionUnitInClusters - ((ULONG)Temp & (CompressionUnitInClusters - 1)),
+ FALSE );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+
+ //
+ // The attribute may have moved. We will cleanup the attribute
+ // context and look it up again.
+ //
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ NtfsLookupAttributeForScb( IrpContext, Scb, NULL, &AttrContext );
+ NtfsPinMappedAttribute( IrpContext, Vcb, &AttrContext );
+ Attribute = NtfsFoundAttribute( &AttrContext );
+ }
+ }
+
+ //
+ // If the attribute is resident, copy it here and remember its
+ // header size.
+ //
+
+ if (NtfsIsAttributeResident(Attribute)) {
+
+ RtlCopyMemory( &NewAttribute, Attribute, SIZEOF_RESIDENT_ATTRIBUTE_HEADER );
+
+ AttributeSizeChange = SIZEOF_RESIDENT_ATTRIBUTE_HEADER;
+
+ //
+ // Set the correct compression unit but only for data streams. We
+ // don't want to change this value for the Index Root.
+ //
+
+ if (NtfsIsTypeCodeCompressible( Attribute->TypeCode )) {
+
+ if (CompressionState != 0) {
+
+ NewCompressionUnit = BytesFromClusters( Scb->Vcb, 1 << NTFS_CLUSTERS_PER_COMPRESSION );
+ NewCompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
+
+ } else {
+
+ NewCompressionUnit = 0;
+ NewCompressionUnitShift = 0;
+ }
+
+ } else {
+
+ NewCompressionUnit = Scb->CompressionUnit;
+ NewCompressionUnitShift = Scb->CompressionUnitShift;
+ }
+
+ //
+ // Else if it is nonresident, copy it here, set the compression parameter,
+ // and remember its size.
+ //
+
+ } else {
+
+ AttributeSizeChange = Attribute->Form.Nonresident.MappingPairsOffset;
+
+ if (Attribute->NameOffset != 0) {
+
+ AttributeSizeChange = Attribute->NameOffset;
+ }
+
+ RtlCopyMemory( &NewAttribute, Attribute, AttributeSizeChange );
+
+ if (CompressionState != 0) {
+
+ NewAttribute.Form.Nonresident.CompressionUnit = NTFS_CLUSTERS_PER_COMPRESSION;
+ NewCompressionUnit = Vcb->BytesPerCluster << NTFS_CLUSTERS_PER_COMPRESSION;
+ NewCompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
+ } else {
+
+ NewAttribute.Form.Nonresident.CompressionUnit = 0;
+ NewCompressionUnit = 0;
+ NewCompressionUnitShift = 0;
+ }
+
+ ASSERT(NewCompressionUnit == 0
+ || Scb->AttributeTypeCode == $INDEX_ROOT
+ || NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
+ }
+
+ //
+ // Turn compression on/off
+ //
+
+ NewAttribute.Flags = CompressionState;
+
+ //
+ // Now, log the changed attribute.
+ //
+
+ (VOID)NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(&AttrContext),
+ UpdateResidentValue,
+ &NewAttribute,
+ AttributeSizeChange,
+ UpdateResidentValue,
+ Attribute,
+ AttributeSizeChange,
+ NtfsMftOffset( &AttrContext ),
+ PtrOffset(NtfsContainingFileRecord(&AttrContext), Attribute),
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Change the attribute by calling the same routine called at restart.
+ //
+
+ NtfsRestartChangeValue( IrpContext,
+ NtfsContainingFileRecord(&AttrContext),
+ PtrOffset(NtfsContainingFileRecord(&AttrContext), Attribute),
+ 0,
+ &NewAttribute,
+ AttributeSizeChange,
+ FALSE );
+
+ //
+ // If this is the main stream for a file we want to change the file attribute
+ // for this stream in both the standard information and duplicate
+ // information structure.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (CompressionState != 0) {
+
+ SetFlag( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Fcb->Info.FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_FILE_ATTR );
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ //
+ // Now lets add or remove the total allocated field in the attribute
+ // header.
+ //
+
+ NtfsSetTotalAllocatedField( IrpContext, Scb, CompressionState );
+
+ //
+ // At this point we will change the compression unit in the Scb.
+ //
+
+ Scb->CompressionUnit = NewCompressionUnit;
+ Scb->CompressionUnitShift = NewCompressionUnitShift;
+
+ //
+ // Make sure we can reserve enough clusters to start the compress
+ // operation.
+ //
+
+ if ((CompressionState != 0) && !NtfsIsAttributeResident(Attribute)) {
+
+ ByteCount = Scb->Header.FileSize.QuadPart;
+
+ if (ByteCount > 0x40000) {
+
+ ByteCount = 0x40000;
+ }
+
+ if (!NtfsReserveClusters( IrpContext, Scb, 0, (ULONG) ByteCount )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+ }
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ //
+ // Checkpoint the transaction now to secure this change.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Update the FastIoField.
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ //
+ // Cleanup on the way out.
+ //
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ //
+ // If this requests aborts then we want to back out any changes to the
+ // in-memory structures.
+ //
+
+ if (AbnormalTermination()) {
+
+ Fcb->Info.FileAttributes = OriginalFileAttributes;
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ Scb->Header.Flags = OriginalHeaderFlags;
+ Scb->CompressionUnitShift = OriginalCompressionUnitShift;
+ Scb->CompressionUnit = OriginalCompressionUnit;
+ }
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsSetCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine compresses or decompresses an entire stream in place,
+ by walking through the stream and forcing it to be written with the
+ new compression parameters. As it writes the stream it sets a flag
+ in the Scb to tell NtfsCommonWrite to delete all allocation at the
+ outset, to force the space to be reallocated.
+
+Arguments:
+
+ Irp - Irp describing the compress or decompress change.
+
+Return Value:
+
+ NSTATUS - Status of the request.
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+ PIO_STACK_LOCATION NextIrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PUSHORT CompressionStatePtr;
+
+ PFILE_OBJECT FileObject;
+ LONGLONG FileOffset;
+ LONGLONG ByteCount;
+ PVOID Buffer;
+ BOOLEAN UserMappedFile;
+ PBCB Bcb = NULL;
+ USHORT CompressionState = 0;
+ PMDL Mdl = NULL;
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN PagingIoAcquired = FALSE;
+ BOOLEAN LockedPages = FALSE;
+ BOOLEAN FsRtlHeaderLocked = FALSE;
+
+ NTSTATUS Status = STATUS_UNSUCCESSFUL;
+
+ //
+ // Get the current stack location and extract the output
+ // buffer information. The output parameter will receive
+ // the compressed state of the file/directory.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ NextIrpSp = IoGetNextIrpStackLocation( Irp );
+
+ FileObject = IrpSp->FileObject;
+ CompressionStatePtr = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Make sure the input buffer is big enough
+ //
+
+ if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(USHORT)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Decode the file object
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // See if we are compressing, and only accept the default case or
+ // lznt1.
+ //
+
+ if (*CompressionStatePtr != 0) {
+
+ if ((*CompressionStatePtr == COMPRESSION_FORMAT_DEFAULT) ||
+ (*CompressionStatePtr == COMPRESSION_FORMAT_LZNT1)) {
+
+ CompressionState = COMPRESSION_FORMAT_LZNT1 - 1;
+
+ //
+ // Check that we can compress on this volume.
+ //
+
+ if (!FlagOn( Vcb->AttributeFlagsMask, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ if (((TypeOfOpen != UserFileOpen) &&
+#ifdef _CAIRO_
+ (TypeOfOpen != UserPropertySetOpen) &&
+#endif // _CAIRO_
+ (TypeOfOpen != UserDirectoryOpen)) ||
+ FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ try {
+
+ //
+ // We now want to acquire the Scb to check if we can continue.
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = TRUE;
+ }
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+
+ //
+ // Handle the simple directory case here.
+ //
+
+ if ((Scb->Header.NodeTypeCode == NTFS_NTC_SCB_INDEX) ||
+ (Scb->Header.NodeTypeCode == NTFS_NTC_SCB_ROOT_INDEX)) {
+
+ NtfsChangeAttributeCompression( IrpContext, Scb, Vcb, Ccb, CompressionState );
+
+ if (CompressionState != 0) {
+
+ Scb->AttributeFlags = (USHORT)((Scb->AttributeFlags & ~ATTRIBUTE_FLAG_COMPRESSION_MASK) |
+ CompressionState);
+ SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+
+ } else {
+
+ Scb->AttributeFlags = (USHORT)(Scb->AttributeFlags & ~ATTRIBUTE_FLAG_COMPRESSION_MASK);
+ ClearFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+
+ try_return(Status = STATUS_SUCCESS);
+ }
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Set the WRITE_ACCESS_SEEN flag so that we will enforce the
+ // reservation strategy.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN )) {
+
+ LONGLONG ClusterCount;
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ //
+ // Does this Scb have reserved space that causes us to exceed the free
+ // space on the volume?
+ //
+
+ ClusterCount = LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved );
+
+ if ((Scb->ScbType.Data.TotalReserved != 0) &&
+ ((ClusterCount + Vcb->TotalReserved) > Vcb->FreeClusters)) {
+
+ NtfsReleaseReservedClusters( Vcb );
+
+ try_return( Status = STATUS_DISK_FULL );
+ }
+
+ //
+ // Otherwise tally in the reserved space now for this Scb, and
+ // remember that we have seen write access.
+ //
+
+ Vcb->TotalReserved += ClusterCount;
+ SetFlag( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN );
+
+ NtfsReleaseReservedClusters( Vcb );
+ }
+
+ //
+ // If this is the first pass through SetCompression we need to set this
+ // request up as the top-level change compression operation. This means
+ // setting the REALLOCATE_ON_WRITE flag, changing the attribute state
+ // and putting the SCB_STATE_COMPRESSED flag in the correct state.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength == MAXULONG) {
+
+ //
+ // If the REALLOCATE_ON_WRITE flag is set it means that someone is
+ // already changing the compression state. Return STATUS_SUCCESS in
+ // that case.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE )) {
+
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ //
+ // If we are turning off compression and the file is uncompressed then
+ // we can just get out.
+ //
+
+ if ((CompressionState == 0) && ((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) == 0)) {
+
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ //
+ // If we are compressing, change the compressed state now.
+ //
+
+ if (CompressionState != 0) {
+
+ NtfsChangeAttributeCompression( IrpContext, Scb, Vcb, Ccb, CompressionState );
+ Scb->AttributeFlags = (USHORT)((Scb->AttributeFlags & ~ATTRIBUTE_FLAG_COMPRESSION_MASK) |
+ CompressionState);
+ SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+
+ //
+ // Otherwise, we must clear the compress flag in the Scb to
+ // start writing decompressed.
+ //
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+
+ //
+ // Set ourselves up as the top level request.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ NextIrpSp->Parameters.FileSystemControl.OutputBufferLength = 0;
+ NextIrpSp->Parameters.FileSystemControl.InputBufferLength = 0;
+
+ //
+ // If we are turning off compression and the file is uncompressed then
+ // we can just get out. Even if we raised while decompressing. If
+ // the state is now uncompressed then we have committed the change.
+ //
+
+ } else if ((CompressionState == 0) &&
+ ((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) == 0)) {
+
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ //
+ // In the Fsd entry we clear the following two parameter fields in the Irp,
+ // and then we update them to our current position on all abnormal terminations.
+ // That way if we get a log file full, we only have to resume where we left
+ // off.
+ //
+
+ ((PLARGE_INTEGER)&FileOffset)->LowPart = NextIrpSp->Parameters.FileSystemControl.OutputBufferLength;
+ ((PLARGE_INTEGER)&FileOffset)->HighPart = NextIrpSp->Parameters.FileSystemControl.InputBufferLength;
+
+ //
+ // If the stream is resident there is no need rewrite any of the data.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Release all of the files held by this Irp Context. The Mft
+ // may have been grabbed to make space for the TotalAllocated field.
+ //
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's
+ // for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ ScbAcquired = FALSE;
+
+ ASSERT(IrpContext->TransactionId == 0);
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = FALSE;
+
+ while (TRUE) {
+
+ //
+ // We must throttle our writes.
+ //
+
+ CcCanIWrite( FileObject, 0x40000, TRUE, FALSE );
+
+ //
+ // Lock the FsRtl header so we can freeze FileSize.
+ // Acquire paging io exclusive if uncompressing so
+ // we can guarantee that all of the pages get written
+ // before we mark the file as uncompressed. Otherwise a
+ // a competing LazyWrite in a range may block after
+ // going through Mm and Mm will report to this routine
+ // that the flush has occurred.
+ //
+
+ if (CompressionState == 0) {
+
+ ExAcquireResourceExclusive( Scb->Header.PagingIoResource, TRUE );
+
+ } else {
+
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE );
+ }
+
+ FsRtlLockFsRtlHeader( &Scb->Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB) Scb;
+ FsRtlHeaderLocked = TRUE;
+
+ //
+ // Jump out right here if the attribute is resident.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+ break;
+ }
+
+ //
+ // Calculate the bytes left in the file to write.
+ //
+
+ ByteCount = Scb->Header.FileSize.QuadPart - FileOffset;
+
+ //
+ // This is how we exit, seeing that we have finally rewritten
+ // everything. It is possible that the file was truncated
+ // between passes through this loop so we test for 0 bytes or
+ // a negative value.
+ //
+ // Note that we exit with the Scb still acquired,
+ // so that we can reliably turn compression off.
+ //
+
+ if (ByteCount <= 0) {
+
+ break;
+ }
+
+ //
+ // If there is more than our max, then reduce the byte count for this
+ // pass to our maximum. We must also align the file offset to a 0x40000
+ // byte boundary.
+ //
+
+ if (((ULONG)FileOffset & 0x3ffff) + ByteCount > 0x40000) {
+
+ ByteCount = 0x40000 - ((ULONG)FileOffset & 0x3ffff);
+ }
+
+ //
+ // Make sure there are enough available clusters in the range
+ // we want to rewrite.
+ //
+
+ if (!NtfsReserveClusters( IrpContext, Scb, FileOffset, (ULONG) ByteCount )) {
+
+ //
+ // If this transaction has already deallocated clusters
+ // then raise log file full to allow those to become
+ // available.
+ //
+
+ if (IrpContext->DeallocatedClusters != 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+
+ //
+ // Otherwise there is insufficient space to guarantee
+ // we can perform the compression operation.
+ //
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+ }
+
+ //
+ // See if we have to create an internal attribute stream. We do
+ // it in the loop, because the Scb must be acquired.
+ //
+
+ if (Scb->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+ }
+
+ //
+ // Map the next range of the file, and make the pages dirty.
+ //
+
+ CcMapData( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, (ULONG)ByteCount, TRUE, &Bcb, &Buffer );
+
+ //
+ // Now attempt to allocate an Mdl to describe the mapped data.
+ //
+
+ Mdl = IoAllocateMdl( Buffer, (ULONG)ByteCount, FALSE, FALSE, NULL );
+
+ if (Mdl == NULL) {
+ DebugTrace( 0, 0, ("Failed to allocate Mdl\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Lock the data into memory so that we can safely reallocate the
+ // space. Don't tell Mm here that we plan to write it, as he sets
+ // dirty now and at the unlock below if we do.
+ //
+
+ MmProbeAndLockPages( Mdl, KernelMode, IoReadAccess );
+ LockedPages = TRUE;
+
+#ifdef SYSCACHE
+
+ //
+ // Clear write mask before the flush
+ //
+
+ {
+ PULONG WriteMask;
+ ULONG Len;
+ ULONG Off = (ULONG)FileOffset;
+
+ WriteMask = Scb->ScbType.Data.WriteMask;
+ if (WriteMask == NULL) {
+ WriteMask = NtfsAllocatePool( NonPagedPool, (((0x2000000) / PAGE_SIZE) / 8) );
+ Scb->ScbType.Data.WriteMask = WriteMask;
+ RtlZeroMemory(WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
+ }
+
+ if (Off < 0x2000000) {
+ Len = (ULONG)ByteCount;
+ if ((Off + Len) > 0x2000000) {
+ Len = 0x2000000 - Off;
+ }
+ while (Len != 0) {
+ WriteMask[(Off / PAGE_SIZE)/32] &= ~(1 << ((Off / PAGE_SIZE) % 32));
+
+ Off += PAGE_SIZE;
+ if (Len <= PAGE_SIZE) {
+ break;
+ }
+ Len -= PAGE_SIZE;
+ }
+ }
+#endif
+
+ //
+ // Mark the address range modified so that the flush will
+ // flush.
+ //
+
+ MmSetAddressRangeModified( Buffer, (ULONG)ByteCount );
+
+ UserMappedFile = FALSE;
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ if (FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE )) {
+
+ UserMappedFile = TRUE;
+ }
+
+ //
+ // Tell Cc there is a user-mapped file so he will really flush.
+ //
+
+ SetFlag( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // Now flush these pages.
+ //
+
+ Irp->IoStatus.Status = NtfsFlushUserStream( IrpContext,
+ Scb,
+ &FileOffset,
+ (ULONG)ByteCount );
+
+ //
+ // Restore the FsRtl flag if there is no user mapped file.
+ // This is correctly synchronized, since we have the file
+ // exclusive, and Mm always has to query the file size before
+ // creating a user-mapped section and calling Cc to set
+ // the FsRtl flag.
+ //
+
+ if (!UserMappedFile) {
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ ClearFlag( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+
+ //
+ // On error get out.
+ //
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &Irp->IoStatus.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+
+#ifdef SYSCACHE
+
+ //
+ // Verify writes occurred after the flush
+ //
+
+ Off = (ULONG)FileOffset;
+
+ WriteMask = Scb->ScbType.Data.WriteMask;
+
+ if (Off < 0x2000000) {
+ Len = (ULONG)ByteCount;
+ if ((Off + Len) > 0x2000000) {
+ Len = 0x2000000 - Off;
+ }
+ while (Len != 0) {
+ ASSERT(WriteMask[(Off / PAGE_SIZE)/32] & (1 << ((Off / PAGE_SIZE) % 32)));
+
+ Off += PAGE_SIZE;
+ if (Len <= PAGE_SIZE) {
+ break;
+ }
+ Len -= PAGE_SIZE;
+ }
+ }
+ }
+#endif
+
+ //
+ // Now we can get rid of this Mdl.
+ //
+
+ MmUnlockPages( Mdl );
+ LockedPages = FALSE;
+ IoFreeMdl( Mdl );
+ Mdl = NULL;
+
+ //
+ // Now we can safely unpin and release the Scb for a while.
+ // (Got to let those checkpoints through!)
+ //
+
+ CcUnpinData( Bcb );
+ Bcb = NULL;
+
+ //
+ // Release any remaing reserved clusters in this range.
+ //
+
+ NtfsFreeReservedClusters( Scb, FileOffset, (ULONG) ByteCount );
+
+ //
+ // Advance the FileOffset.
+ //
+
+ FileOffset += ByteCount;
+
+ //
+ // If we hit the end of the file then exit while holding the
+ // resource so we can turn compression off.
+ //
+
+ if (FileOffset == Scb->Header.FileSize.QuadPart) {
+
+ break;
+ }
+
+ //
+ // Unlock the header an let anyone else access the file before
+ // looping back.
+ //
+
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ FsRtlHeaderLocked = FALSE;
+ }
+ }
+
+ //
+ // We have finished the conversion. Now is the time to turn compression
+ // off. Note that the compression flag in the Scb is already off.
+ //
+
+ if (CompressionState == 0) {
+
+ VCN StartingCluster;
+ VCN EndingCluster;
+
+ //
+ // The paging Io resource may already be acquired.
+ //
+
+ if (!PagingIoAcquired && !FsRtlHeaderLocked) {
+ if (Scb->Header.PagingIoResource != NULL) {
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = TRUE;
+ }
+ }
+
+ if (!ScbAcquired) {
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+ }
+
+ NtfsChangeAttributeCompression( IrpContext, Scb, Vcb, Ccb, 0 );
+ Scb->AttributeFlags &= (USHORT)~ATTRIBUTE_FLAG_COMPRESSION_MASK;
+
+ //
+ // If we are decompressing then make sure to release any holes at the end of
+ // the allocation.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Truncate any extra allocation beyond file size.
+ //
+
+ StartingCluster = LlClustersFromBytes( Vcb, Scb->Header.FileSize.QuadPart );
+ EndingCluster = Int64ShraMod32(Scb->Header.AllocationSize.QuadPart, Vcb->ClusterShift);
+
+ if (StartingCluster < EndingCluster) {
+
+#ifdef _CAIRO_
+ if (NtfsPerformQuotaOperation( Fcb ) &&
+ !FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED)) {
+
+ //
+ // This can occur if the file handle gets closed
+ // while the file is being decompressed.
+ //
+
+ ASSERT( Scb->CleanupCount == 0 );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )
+ }
+#endif // _CAIRO_
+
+ NtfsDeleteAllocation( IrpContext,
+ IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->FileObject,
+ Scb,
+ StartingCluster,
+ MAXLONGLONG,
+ TRUE,
+ TRUE );
+
+ //
+ // If this is the unnamed data stream then we need to update
+ // the total allocated size.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+ }
+
+ //
+ // We don't want to leave any reserved clusters if we went
+ // to uncompressed.
+ //
+
+ if (Scb->ScbType.Data.ReservedBitMap != NULL) {
+
+ NtfsFreePool( Scb->ScbType.Data.ReservedBitMap );
+ Scb->ScbType.Data.ReservedBitMap = NULL;
+ }
+
+ NtfsFreeFinalReservedClusters( Vcb,
+ LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved ));
+
+ Scb->ScbType.Data.TotalReserved = 0;
+
+ //
+ // Make sure there are no holes in the Mcb.
+ //
+
+#ifdef SYSCACHE
+ {
+ LCN NextLcn;
+ LONGLONG ClusterCount;
+ PVOID RangePtr;
+ ULONG Index;
+
+ VCN LastStart = 0;
+ LONGLONG LastCount = 0;
+
+ //
+ // There better not be any holes in the Mcb at this point.
+ //
+
+ RangePtr = (PVOID) 1;
+ Index = 0;
+
+ while (NtfsGetSequentialMcbEntry( &Scb->Mcb,
+ &RangePtr,
+ Index,
+ &StartingCluster,
+ &NextLcn,
+ &ClusterCount )) {
+
+ LastStart = StartingCluster;
+ LastCount = ClusterCount;
+
+ Index += 1;
+ ASSERT( NextLcn != UNUSED_LCN );
+ }
+
+ NextLcn = LlBytesFromClusters( Vcb, LastStart + LastCount );
+
+ ASSERT( NextLcn == Scb->Header.AllocationSize.QuadPart );
+ }
+#endif
+ }
+
+ //
+ // Now clear the REALLOCATE_ON_WRITE flag while holding both resources.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ }
+
+ //
+ // Unlock the header if we locked it.
+ //
+
+ if (FsRtlHeaderLocked) {
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ FsRtlHeaderLocked = FALSE;
+ }
+
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+
+ //
+ // Now clear the reallocate flag in the Scb if we set it.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength != MAXULONG) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsSetCompression );
+
+ //
+ // Cleanup the Mdl if we died with one.
+ //
+
+ if (Mdl != NULL) {
+ if (LockedPages) {
+ MmUnlockPages( Mdl );
+ }
+ IoFreeMdl( Mdl );
+ }
+
+ if (Bcb != NULL) {
+ CcUnpinData( Bcb );
+ }
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ if (ScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ //
+ // Otherwise, set to restart from the current file position, assuming
+ // this may be a log file full.
+ //
+
+ } else {
+
+
+ //
+ // If we have started the transformation and are in the exception path
+ // we are either going to continue the operation after a clean
+ // checkpoint or we are done.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength != MAXULONG) {
+
+ //
+ // If we are continuing the operation, save the current file offset.
+ //
+
+ if (IrpContext->ExceptionStatus == STATUS_LOG_FILE_FULL ||
+ IrpContext->ExceptionStatus == STATUS_CANT_WAIT) {
+
+ NextIrpSp->Parameters.FileSystemControl.OutputBufferLength = (ULONG)FileOffset;
+ NextIrpSp->Parameters.FileSystemControl.InputBufferLength = ((PLARGE_INTEGER)&FileOffset)->HighPart;
+
+ //
+ // Otherwise clear the REALLOCATE_ON_WRITE flag and set the
+ // COMPRESSED flag.
+ //
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ SetFlag( Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+ }
+
+ if (ScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ //
+ // We may have one or the other of these conditions to clean up.
+ //
+
+ if (FsRtlHeaderLocked) {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+ }
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsReadCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+
+{
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_NOT_IMPLEMENTED );
+
+ return STATUS_NOT_IMPLEMENTED;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsWriteCompression (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+Arguments:
+
+Return Value:
+
+--*/
+
+{
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_NOT_IMPLEMENTED );
+
+ return STATUS_NOT_IMPLEMENTED;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsMarkAsSystemHive (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by the registry to identify the registry handles. We
+ will mark this in the Ccb and use it during FlushBuffers to know to do a
+ careful flush.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PAGED_CODE();
+
+ //
+ // Extract and decode the file object
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // We only permit this request on files and we must be called from kernel mode.
+ //
+
+ if (Irp->RequestorMode != KernelMode ||
+ TypeOfOpen != UserFileOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsOplockRequest -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Now acquire the file and mark the Ccb and return SUCCESS.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ SetFlag( Ccb->Flags, CCB_FLAG_SYSTEM_HIVE );
+
+ NtfsReleaseScb( IrpContext, Scb );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetStatistics (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the filesystem performance counters for the
+ volume referred to.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PFILESYSTEM_STATISTICS Stats;
+ ULONG StatsSize;
+
+ PAGED_CODE();
+
+ //
+ // Get the current stack location and extract the output
+ // buffer information. The output parameter will receive
+ // the performance counters.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Get a pointer to the output buffer. Look at the system buffer field
+ // in the irp first. Then the Irp Mdl.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ Stats = Irp->AssociatedIrp.SystemBuffer;
+
+ } else if (Irp->MdlAddress != NULL) {
+
+ Stats = MmGetSystemAddressForMdl( Irp->MdlAddress );
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_USER_BUFFER );
+ return STATUS_INVALID_USER_BUFFER;
+ }
+
+ //
+ // Make sure the output buffer is large enough.
+ //
+
+ StatsSize = sizeof(FILESYSTEM_STATISTICS) * **((PCHAR *)&KeNumberProcessors);
+
+ if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < StatsSize) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // Decode the file object
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb,
+ &Fcb, &Scb, &Ccb, TRUE );
+
+ RtlCopyMemory( Stats, Vcb->Statistics, StatsSize );
+
+ Irp->IoStatus.Information = StatsSize;
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetVolumeData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ Returns a filled in VOLUME_DATA structure in the user output buffer.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation.
+
+--*/
+{
+ PIO_STACK_LOCATION IrpSp;
+ ULONG FsControlCode;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PNTFS_VOLUME_DATA_BUFFER VolumeData;
+ ULONG VolumeDataLength;
+
+ //
+ // Get the current Irp stack location and save some references.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsGetVolumeData, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Extract and decode the file object and check for type of open.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Make sure the volume is still mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_VOLUME_DISMOUNTED );
+ return STATUS_VOLUME_DISMOUNTED;
+ }
+
+ //
+ // Get the output buffer length and pointer.
+ //
+
+ VolumeDataLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength;
+ VolumeData = (PNTFS_VOLUME_DATA_BUFFER)Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Check for a minimum length on the ouput buffer.
+ //
+
+ if (VolumeDataLength < sizeof(NTFS_VOLUME_DATA_BUFFER)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ try {
+
+ //
+ // Acquire the volume bitmap and fill in the volume data structure.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ VolumeData->VolumeSerialNumber.QuadPart = Vcb->VolumeSerialNumber;
+ VolumeData->NumberSectors.QuadPart = Vcb->NumberSectors;
+ VolumeData->TotalClusters.QuadPart = Vcb->TotalClusters;
+ VolumeData->FreeClusters.QuadPart = Vcb->FreeClusters;
+ VolumeData->TotalReserved.QuadPart = Vcb->TotalReserved;
+ VolumeData->BytesPerSector = Vcb->BytesPerSector;
+ VolumeData->BytesPerCluster = Vcb->BytesPerCluster;
+ VolumeData->BytesPerFileRecordSegment = Vcb->BytesPerFileRecordSegment;
+ VolumeData->ClustersPerFileRecordSegment = Vcb->ClustersPerFileRecordSegment;
+ VolumeData->MftValidDataLength = Vcb->MftScb->Header.ValidDataLength;
+ VolumeData->MftStartLcn.QuadPart = Vcb->MftStartLcn;
+ VolumeData->Mft2StartLcn.QuadPart = Vcb->Mft2StartLcn;
+ VolumeData->MftZoneStart.QuadPart = Vcb->MftZoneStart;
+ VolumeData->MftZoneEnd.QuadPart = Vcb->MftZoneEnd;
+
+ } finally {
+
+ NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
+ }
+
+ //
+ // If nothing raised then complete the irp.
+ //
+
+ Irp->IoStatus.Information = sizeof(NTFS_VOLUME_DATA_BUFFER);
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ DebugTrace( -1, Dbg, ("NtfsGetVolumeData -> VOID\n") );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetVolumeBitmap(
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine scans volume bitmap and returns the requested range.
+
+ Input = the GET_BITMAP data structure is passed in through the input buffer.
+ Output = the VOLUME_BITMAP data structure is returned through the output buffer.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation.
+
+--*/
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG FsControlCode;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PSTARTING_LCN_INPUT_BUFFER GetBitmap;
+ ULONG GetBitmapLength;
+
+ PVOLUME_BITMAP_BUFFER VolumeBitmap;
+ ULONG VolumeBitmapLength;
+
+ ULONG BitsWritten;
+
+ LCN Lcn;
+ LCN StartingLcn;
+
+ RTL_BITMAP Bitmap;
+ PBCB BitmapBcb = NULL;
+
+ BOOLEAN StuffAdded = FALSE;
+
+ //
+ // Get the current Irp stack location and save some references.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsGetVolumeBitmap, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Extract and decode the file object and check for type of open.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Make sure the volume is still mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_VOLUME_DISMOUNTED );
+ return STATUS_VOLUME_DISMOUNTED;
+ }
+
+ //
+ // Get the input & output buffer lengths and pointers.
+ //
+
+ GetBitmapLength = IrpSp->Parameters.FileSystemControl.InputBufferLength;
+ GetBitmap = (PSTARTING_LCN_INPUT_BUFFER)IrpSp->Parameters.FileSystemControl.Type3InputBuffer;
+
+ VolumeBitmapLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength;
+ VolumeBitmap = (PVOLUME_BITMAP_BUFFER)NtfsMapUserBuffer( Irp );
+
+ //
+ // Check for a minimum length on the input and ouput buffers.
+ //
+
+ if ((GetBitmapLength < sizeof(STARTING_LCN_INPUT_BUFFER)) ||
+ (VolumeBitmapLength < sizeof(VOLUME_BITMAP_BUFFER))) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // Probe the user's buffers.
+ //
+
+ try {
+
+ ProbeForRead( GetBitmap, GetBitmapLength, sizeof(UCHAR) );
+ ProbeForWrite( VolumeBitmap, VolumeBitmapLength, sizeof(UCHAR) );
+
+ StartingLcn = GetBitmap->StartingLcn.QuadPart;
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ Status = GetExceptionCode();
+
+ NtfsRaiseStatus( IrpContext,
+ FsRtlIsNtstatusExpected(Status) ?
+ Status : STATUS_INVALID_USER_BUFFER,
+ NULL, NULL);
+ }
+
+ try {
+
+ //
+ // Acquire the volume bitmap and check for a valid requested Lcn.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Vcb->BitmapScb );
+
+ if (StartingLcn >= Vcb->TotalClusters) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ //
+ // Read in the volume bitmap page by page and copy it into the UserBuffer.
+ //
+
+ VolumeBitmapLength -= FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer);
+
+ for (Lcn = StartingLcn, BitsWritten = 0;
+ Lcn < Vcb->TotalClusters;
+ Lcn = Lcn + Bitmap.SizeOfBitMap) {
+
+ ULONG BytesToCopy;
+
+ //
+ // Read in the bitmap page and make sure that we haven't messed up the math.
+ //
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); StuffAdded = FALSE; }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsMapPageInBitmap( IrpContext, Vcb, Lcn, &Lcn, &Bitmap, &BitmapBcb );
+
+ //
+ // If this is first itteration, update StartingLcn with actual
+ // starting cluster returned.
+ //
+
+ if (BitsWritten == 0) {
+ StartingLcn = Lcn;
+ }
+
+ //
+ // Check to see if we have enough user buffer. If have some but
+ // not enough, copy what we can and return STATUS_BUFFER_OVERFLOW.
+ // If we are down to 0 (i.e. previous itteration used all the
+ // buffer), break right now.
+ //
+
+ BytesToCopy = (Bitmap.SizeOfBitMap + 7) / 8;
+
+ if (BytesToCopy > VolumeBitmapLength) {
+
+ BytesToCopy = VolumeBitmapLength;
+ Status = STATUS_BUFFER_OVERFLOW;
+
+ if (BytesToCopy == 0) {
+ break;
+ }
+ }
+
+ //
+ // Now bias the bitmap with the RecentlyDeallocatedMcb and copy it into the UserBuffer.
+ //
+
+ StuffAdded = NtfsAddRecentlyDeallocated( Vcb, Lcn, &Bitmap );
+
+ try {
+
+ RtlCopyMemory(&VolumeBitmap->Buffer[BitsWritten / 8], Bitmap.Buffer, BytesToCopy);
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ Status = GetExceptionCode();
+
+ NtfsRaiseStatus( IrpContext,
+ FsRtlIsNtstatusExpected(Status) ?
+ Status : STATUS_INVALID_USER_BUFFER,
+ NULL, NULL );
+ }
+
+ //
+ // If this was an overflow, bump up bits written and continue
+ //
+
+ if (Status != STATUS_BUFFER_OVERFLOW) {
+
+ BitsWritten += Bitmap.SizeOfBitMap;
+ VolumeBitmapLength -= BytesToCopy;
+
+ } else {
+
+ BitsWritten += BytesToCopy * 8;
+ break;
+ }
+ }
+
+ try {
+
+ VolumeBitmap->StartingLcn.QuadPart = StartingLcn;
+ VolumeBitmap->BitmapSize.QuadPart = Vcb->TotalClusters - StartingLcn;
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ Status = GetExceptionCode();
+
+ NtfsRaiseStatus( IrpContext,
+ FsRtlIsNtstatusExpected(Status) ?
+ Status : STATUS_INVALID_USER_BUFFER,
+ NULL, NULL );
+ }
+
+ Irp->IoStatus.Information =
+ FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer) + (BitsWritten + 7) / 8;
+
+ } finally {
+
+ DebugUnwind( NtfsGetVolumeBitmap );
+
+ if (StuffAdded) { NtfsFreePool( Bitmap.Buffer ); }
+
+ NtfsUnpinBcb( &BitmapBcb );
+ NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
+ }
+
+ //
+ // If nothing raised then complete the irp.
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsGetVolumeBitmap -> VOID\n") );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetRetrievalPointers (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine scans the array of MCBs for the given SCB and builds an extent
+ list. The first run in the output extent list will start at the begining
+ of the contiguous run specified by the input parameter.
+
+ Input = STARTING_VCN_INPUT_BUFFER;
+ Output = RETRIEVAL_POINTERS_BUFFER.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation.
+
+--*/
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ VCN Vcn;
+ VCN LastVcnInFile;
+ LCN Lcn;
+ LONGLONG ClusterCount;
+ LONGLONG CountFromStartingVcn;
+ LONGLONG StartingVcn;
+
+ ULONG FileRunIndex;
+ ULONG RangeRunIndex;
+
+ ULONG InputBufferLength;
+ ULONG OutputBufferLength;
+
+ PVOID RangePtr;
+
+ PRETRIEVAL_POINTERS_BUFFER OutputBuffer;
+ BOOLEAN AccessingUserBuffer;
+
+ //
+ // Get the current Irp stack location and save some references.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsGetRetrievalPointers\n") );
+
+ //
+ // Extract and decode the file object and check for type of open.
+ // If we ever decide to support UserDirectoryOpen also, make sure
+ // to check for Scb->AttributeTypeCode != $INDEX_ALLOCATION when
+ // checking whether the Scb header is initialized. Otherwise we'll
+ // have trouble with phantom Scbs created for small directories.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserFileOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Get the input and output buffer lengths and pointers.
+ // Initialize some variables.
+ //
+
+ InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength;
+ OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength;
+
+ OutputBuffer = (PRETRIEVAL_POINTERS_BUFFER)NtfsMapUserBuffer( Irp );
+
+ //
+ // Check for a minimum length on the input and ouput buffers.
+ //
+
+ if ((InputBufferLength < sizeof(STARTING_VCN_INPUT_BUFFER)) ||
+ (OutputBufferLength < sizeof(RETRIEVAL_POINTERS_BUFFER))) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // Acquire shared access to the Scb. We don't want other threads
+ // to extend or move the file while we're trying to return the
+ // retrieval pointers for it.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // Check if a starting cluster was specified.
+ //
+
+ LastVcnInFile = LlClustersFromBytesTruncate( Vcb, Scb->Header.AllocationSize.QuadPart ) - 1;
+
+ //
+ // There are three separate places inside this try/except where we
+ // access the user-supplied buffer. We want to handle exceptions
+ // differently if they happen while we are trying to access the user
+ // buffer than if they happen elsewhere in the try/except. We set
+ // this boolean immediately before touching the user buffer, and
+ // clear it immediately after.
+ //
+
+ AccessingUserBuffer = FALSE;
+ try {
+
+ AccessingUserBuffer = TRUE;
+ ProbeForRead( IrpSp->Parameters.FileSystemControl.Type3InputBuffer,
+ InputBufferLength,
+ sizeof(UCHAR) );
+
+ ProbeForWrite( OutputBuffer, OutputBufferLength, sizeof(UCHAR) );
+
+ StartingVcn = ((PSTARTING_VCN_INPUT_BUFFER)IrpSp->Parameters.FileSystemControl.Type3InputBuffer)->StartingVcn.QuadPart;
+
+ //
+ // While we have AccessingUserBuffer set to TRUE, let's initialize the
+ // extentcount. We increment this for each run in the mcb, so we need
+ // to initialize it outside the main do while loop.
+ //
+
+ OutputBuffer->ExtentCount = 0;
+ AccessingUserBuffer = FALSE;
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // If the data attribute is resident (typically for a small file),
+ // it is not safe to call NtfsPreloadAllocation. There won't be
+ // any runs, and we've already set ExtentCount to 0. The best
+ // thing to do now is leave before somebody gets hurt.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ if (StartingVcn > LastVcnInFile) {
+
+ //
+ // It's possible that the Vcn we were given is past the end of the file.
+ //
+
+ try_return( Status = STATUS_END_OF_FILE );
+
+ } else {
+
+ //
+ // We need to call NtfsPreloadAllocation to make sure all the
+ // ranges in this NtfsMcb are loaded.
+ //
+
+ NtfsPreloadAllocation( IrpContext,
+ Scb,
+ StartingVcn,
+ LastVcnInFile );
+
+ //
+ // Decide which Mcb contains the starting Vcn.
+ //
+
+ (VOID)NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ StartingVcn,
+ NULL,
+ &CountFromStartingVcn,
+ &Lcn,
+ &ClusterCount,
+ &RangePtr,
+ &RangeRunIndex );
+ }
+
+ //
+ // Fill in the Vcn where the run containing StartingVcn truly starts.
+ //
+
+ OutputBuffer->StartingVcn.QuadPart = Vcn = StartingVcn - (ClusterCount - CountFromStartingVcn);
+
+ //
+ // FileRunIndex is the index of a given run within an entire
+ // file, as opposed to RangeRunIndex which is the index of a
+ // given run within its range. RangeRunIndex is reset to 0 for
+ // each range, where FileRunIndex is set to 0 once out here.
+ //
+
+ FileRunIndex = 0;
+
+ do {
+
+ //
+ // Now copy over the mapping pairs from the mcb
+ // to the output buffer. We store in [sector count, lbo]
+ // mapping pairs and end with a zero sector count.
+ //
+
+ //
+ // Check for an exhausted output buffer.
+ //
+
+ if ((ULONG)FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[FileRunIndex+1]) > OutputBufferLength) {
+
+ //
+ // We know that we're out of room in the output buffer, so we won't be looking up
+ // any more runs. ExtentCount currently reflects how many runs we stored in the
+ // user buffer, so we can safely quit. There are indeed ExtentCount extents stored
+ // in the array, and returning STATUS_BUFFER_OVERFLOW informs our caller that we
+ // didn't have enough room to return all the runs.
+ //
+
+ Irp->IoStatus.Information = FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[FileRunIndex]);
+ try_return( Status = STATUS_BUFFER_OVERFLOW );
+ }
+
+ //
+ // Here's the interesting part -- we fill in the next array element in the ouput buffer
+ // with the current run's information.
+ //
+
+ AccessingUserBuffer = TRUE;
+ OutputBuffer->Extents[FileRunIndex].NextVcn.QuadPart = Vcn + ClusterCount;
+ OutputBuffer->Extents[FileRunIndex].Lcn.QuadPart = Lcn;
+
+ OutputBuffer->ExtentCount += 1;
+ AccessingUserBuffer = FALSE;
+
+ FileRunIndex += 1;
+
+ RangeRunIndex += 1;
+
+ } while (NtfsGetSequentialMcbEntry( &Scb->Mcb, &RangePtr, RangeRunIndex, &Vcn, &Lcn, &ClusterCount));
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ Status = GetExceptionCode();
+
+ NtfsRaiseStatus( IrpContext,
+ ((FsRtlIsNtstatusExpected(Status) || !AccessingUserBuffer) ? Status : STATUS_INVALID_USER_BUFFER),
+ NULL,
+ NULL );
+ }
+
+
+ //
+ // We successfully retrieved extent info to the end of the allocation.
+ //
+
+ Irp->IoStatus.Information = FIELD_OFFSET(RETRIEVAL_POINTERS_BUFFER, Extents[FileRunIndex]);
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsGetRetrievalPointers );
+
+ //
+ // Release resources.
+ //
+
+ NtfsReleaseScb( IrpContext, Scb );
+
+ //
+ // If nothing raised then complete the irp.
+ //
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsGetRetrievalPointers -> VOID\n") );
+ }
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsGetMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns a copy of the requested File Record Segment. A
+ hint File Reference Number is passed in. If the hint File Record
+ Segment is "not in use" then the MFT bitmap is scanned backwards
+ from the hint until an "in use" File Record Segment is found. This
+ File Record Segment is then returned along with the identifying File Reference Number.
+
+ Input = the LONGLONG File Reference Number is passed in through the input buffer.
+ Output = the FILE_RECORD data structure is returned through the output buffer.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_UNSUCCESSFUL;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG FsControlCode;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PNTFS_FILE_RECORD_INPUT_BUFFER GetFileRecord;
+ ULONG GetFileRecordLength;
+
+ PNTFS_FILE_RECORD_OUTPUT_BUFFER FileRecord;
+ ULONG FileRecordLength;
+
+ ULONG FileReferenceNumber;
+
+ PFILE_RECORD_SEGMENT_HEADER MftBuffer;
+
+ PBCB Bcb = NULL;
+ PBCB BitmapBcb = NULL;
+
+ BOOLEAN AcquiredMft = FALSE;
+ RTL_BITMAP Bitmap;
+ LONG BaseIndex;
+ LONG Index;
+ ULONG BitmapSize;
+ VCN Vcn = 0;
+ LONGLONG StartingByte;
+ PUCHAR BitmapBuffer;
+ ULONG SizeToMap;
+ ULONG BytesToCopy;
+
+ extern
+ LONG
+ NtfsReadMftExceptionFilter (
+ IN PIRP_CONTEXT IrpContext OPTIONAL,
+ IN PEXCEPTION_POINTERS ExceptionPointer,
+ IN NTSTATUS Status
+ );
+
+ //
+ // Get the current Irp stack location and save some references.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsGetMftRecord, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Extract and decode the file object and check for type of open.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Make sure the volume is still mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_VOLUME_DISMOUNTED );
+ return STATUS_VOLUME_DISMOUNTED;
+ }
+
+ //
+ // Get the input & output buffer lengths and pointers.
+ //
+
+ GetFileRecordLength = IrpSp->Parameters.FileSystemControl.InputBufferLength;
+ GetFileRecord = (PNTFS_FILE_RECORD_INPUT_BUFFER)Irp->AssociatedIrp.SystemBuffer;
+
+ FileRecordLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength;
+ FileRecord = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)Irp->AssociatedIrp.SystemBuffer;;
+
+ //
+ // Check for a minimum length on the input and ouput buffers.
+ //
+
+ if ((GetFileRecordLength < sizeof(NTFS_FILE_RECORD_INPUT_BUFFER)) ||
+ (FileRecordLength < sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER))) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ FileRecordLength -= FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer);
+ FileReferenceNumber = GetFileRecord->FileReferenceNumber.LowPart;
+
+ try {
+
+ LONGLONG ValidDataLength;
+
+ //
+ // Synchronize the lookup by acquiring the Mft.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Vcb->MftScb );
+ AcquiredMft = TRUE;
+
+ //
+ // Raise if the File Reference Number is not within the MFT valid data length.
+ //
+
+ ValidDataLength = Vcb->MftScb->Header.ValidDataLength.QuadPart;
+
+ if (FileReferenceNumber > (ValidDataLength / Vcb->BytesPerFileRecordSegment)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ //
+ // Fill in the record size and determine how much of it we can copy.
+ //
+
+ FileRecord->FileRecordLength = Vcb->BytesPerFileRecordSegment;
+
+ if (FileRecordLength >= Vcb->BytesPerFileRecordSegment) {
+
+ BytesToCopy = Vcb->BytesPerFileRecordSegment;
+ Status = STATUS_SUCCESS;
+
+ } else {
+
+ BytesToCopy = FileRecordLength;
+ Status = STATUS_BUFFER_OVERFLOW;
+ }
+
+ //
+ // If it is the MFT file record then just get it and we are done.
+ //
+
+ if (FileReferenceNumber == 0) {
+
+ try {
+ NtfsMapStream( IrpContext,
+ Vcb->MftScb,
+ 0,
+ Vcb->BytesPerFileRecordSegment,
+ &Bcb,
+ (PVOID *)&MftBuffer );
+
+ } except (NtfsReadMftExceptionFilter( IrpContext,
+ GetExceptionInformation(),
+ GetExceptionCode() )) {
+ NtfsMapStream( IrpContext,
+ Vcb->Mft2Scb,
+ 0,
+ Vcb->BytesPerFileRecordSegment,
+ &Bcb,
+ (PVOID *)&MftBuffer );
+ }
+
+
+
+ //
+ // Return the File Reference Number and the File Record.
+ //
+
+ RtlCopyMemory(FileRecord->FileRecordBuffer, MftBuffer, BytesToCopy);
+ FileRecord->FileReferenceNumber.QuadPart = 0;
+
+ try_return(Status);
+ }
+
+ //
+ // Scan through the MFT Bitmap to find an "in use" file.
+ //
+
+ while (FileReferenceNumber > 0) {
+
+ //
+ // Compute some values for the bitmap, convert the index to the offset of
+ // this page and get the base index for the File Reference number.
+ //
+
+ Index = FileReferenceNumber;
+ BitmapSize = (Index + 7) / 8;
+ Index = Index & (BITS_PER_PAGE - 1);
+ BaseIndex = FileReferenceNumber - Index;
+
+ //
+ // Set the Vcn count to the full size of the bitmap and move to the beginning
+ // of this page.
+ //
+
+ ((ULONG)Vcn) = ClustersFromBytes(Vcb, ROUND_TO_PAGES(BitmapSize));
+
+ ((ULONG)Vcn) = (ULONG)Vcn - Vcb->ClustersPerPage;
+
+ //
+ // Calculate the number of bytes to map in the current page.
+ //
+
+ SizeToMap = BitmapSize - BytesFromClusters(Vcb, ((ULONG)Vcn));
+
+ if(SizeToMap > BYTES_PER_PAGE) {
+
+ SizeToMap = BYTES_PER_PAGE;
+ }
+
+ //
+ // Initialize the bitmap for this page.
+ //
+
+ StartingByte = LlBytesFromClusters(Vcb, Vcn);
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftBitmapScb,
+ StartingByte,
+ SizeToMap,
+ &BitmapBcb,
+ &BitmapBuffer );
+
+ RtlInitializeBitMap(&Bitmap, (PULONG)BitmapBuffer, SizeToMap * 8);
+
+ //
+ // Scan thru this page for an "in use" File Record.
+ //
+
+ for (; Index >= 0; Index --) {
+
+ if (RtlCheckBit(&Bitmap, Index)) {
+
+ //
+ // Found one "in use" on this page so get it and we are done.
+ //
+
+ try {
+ NtfsMapStream( IrpContext,
+ Vcb->MftScb,
+ Int64ShllMod32(BaseIndex + Index, Vcb->MftShift),
+ Vcb->BytesPerFileRecordSegment,
+ &Bcb,
+ (PVOID *)&MftBuffer );
+
+ } except (NtfsReadMftExceptionFilter( IrpContext,
+ GetExceptionInformation(),
+ GetExceptionCode() )) {
+ NtfsMapStream( IrpContext,
+ Vcb->Mft2Scb,
+ Int64ShllMod32(BaseIndex + Index, Vcb->MftShift),
+ Vcb->BytesPerFileRecordSegment,
+ &Bcb,
+ (PVOID *)&MftBuffer );
+ }
+
+ //
+ // Return the File Reference Number and the File Record.
+ //
+
+ RtlCopyMemory(FileRecord->FileRecordBuffer, MftBuffer, BytesToCopy);
+ FileRecord->FileReferenceNumber.QuadPart = BaseIndex + Index;
+
+ try_return(Status);
+ }
+ }
+
+ //
+ // Cleanup for next time through and decrement the File Reference Number.
+ //
+
+ NtfsUnpinBcb(&BitmapBcb);
+ FileReferenceNumber = BaseIndex - 1;
+ }
+
+ try_exit: NOTHING;
+
+ Irp->IoStatus.Information =
+ FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer) +
+ BytesToCopy;
+
+ } finally {
+
+ //
+ // Release resources and exit.
+ //
+
+ NtfsUnpinBcb(&BitmapBcb);
+ NtfsUnpinBcb(&Bcb);
+
+ if (AcquiredMft) {
+
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsGetMftRecord: Exit\n") );
+ }
+
+ //
+ // If nothing raised then complete the Irp.
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsGetMftRecord -> VOID\n") );
+
+ return Status;
+}
+
+
+//
+// Local Support Routine
+//
+
+NTSTATUS
+NtfsMoveFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ The major parts of the following routine were extracted from NtfsSetCompression. This
+ routine moves a file to the requested Starting Lcn from Starting Vcn for the length
+ of cluster count. These values are passed in through the the input buffer as a MOVE_DATA
+ structure. Note that the Vcn and cluster count must be a factor of 16 the compression
+ chunk.
+
+Arguments:
+
+ Irp - Supplies the Irp being processed.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_UNSUCCESSFUL;
+ PIO_STACK_LOCATION IrpSp;
+ PIO_STACK_LOCATION NextIrpSp;
+ ULONG FsControlCode;
+
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ MOVE_FILE_DATA StackMoveData;
+ PMOVE_FILE_DATA MoveData;
+
+ LONGLONG FileOffset;
+ LONGLONG ByteCount;
+ PVOID Buffer;
+ BOOLEAN UserMappedFile;
+ PBCB Bcb = NULL;
+ USHORT CompressionState = 0;
+ PMDL Mdl = NULL;
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN PagingIoAcquired = FALSE;
+ BOOLEAN LockedPages = FALSE;
+ BOOLEAN FsRtlHeaderLocked = FALSE;
+
+ ULONG ScbCompressionUnitSave;
+ USHORT ScbAttributeFlagsSave;
+ UCHAR ScbCompressionUnitShiftSave;
+
+ ULONG CompressionUnitSize;
+
+ extern POBJECT_TYPE *IoFileObjectType;
+
+ //
+ // We should never be in the FSP for this. Otherwise the user handle
+ // is invalid.
+ //
+
+ ASSERT( !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP ));
+
+ //
+ // Get the current Irp stack location and save some references.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ NextIrpSp = IoGetNextIrpStackLocation( Irp );
+ FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode;
+
+ DebugTrace( +1, Dbg, ("NtfsMoveFile, FsControlCode = %08lx\n", FsControlCode) );
+
+ //
+ // Extract and decode the file object and check for type of open.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Can't defrag on clusters larger than 4K.
+ //
+
+ if (Vcb->BytesPerCluster > 0x1000) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ //
+ // Get the input buffer pointer and check its length.
+ //
+
+ if (IrpSp->Parameters.FileSystemControl.InputBufferLength <
+ sizeof(MOVE_FILE_DATA)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_BUFFER_TOO_SMALL );
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // This routine modifies the input buffer, so just to be a good
+ // safe citizen, copy it to our stack.
+ //
+
+ RtlCopyMemory( &StackMoveData,
+ Irp->AssociatedIrp.SystemBuffer,
+ sizeof( MOVE_FILE_DATA ));
+
+ MoveData = &StackMoveData;
+
+ //
+ // Try to get a pointer to the file object from the handle passed in.
+ //
+
+ Status = ObReferenceObjectByHandle( MoveData->FileHandle,
+ 0,
+ *IoFileObjectType,
+ Irp->RequestorMode,
+ &FileObject,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ return Status;
+ }
+
+ //
+ // We only needed the pointer, not a reference.
+ //
+
+ ObDereferenceObject( FileObject );
+
+ //
+ // Check that this file object is opened on the same volume as the
+ // DASD handle used to call this routine.
+ //
+
+ if (FileObject->Vpb != Vcb->Vpb) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Now decode this FileObject.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if ((TypeOfOpen != UserFileOpen) ||
+ FlagOn(Fcb->FcbState, FCB_STATE_PAGING_FILE)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ try {
+
+ //
+ // We now want to acquire the Scb to check if we can continue.
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ NtfsAcquireExclusivePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = TRUE;
+ }
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_VOLUME_DISMOUNTED )) {
+
+ try_return( Status = STATUS_VOLUME_DISMOUNTED );
+ }
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+ }
+
+ //
+ // Set the WRITE_ACCESS_SEEN flag so that we will enforce the
+ // reservation strategy.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN )) {
+
+ LONGLONG ClusterCount;
+
+ NtfsAcquireReservedClusters( Vcb );
+
+ //
+ // Does this Scb have reserved space that causes us to exceed the free
+ // space on the volume?
+ //
+
+ ClusterCount = LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved );
+
+ if ((Scb->ScbType.Data.TotalReserved != 0) &&
+ ((ClusterCount + Vcb->TotalReserved) > Vcb->FreeClusters)) {
+
+ NtfsReleaseReservedClusters( Vcb );
+
+ try_return( Status = STATUS_DISK_FULL );
+ }
+
+ //
+ // Otherwise tally in the reserved space now for this Scb, and
+ // remember that we have seen write access.
+ //
+
+ Vcb->TotalReserved += ClusterCount;
+ SetFlag( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN );
+
+ NtfsReleaseReservedClusters( Vcb );
+ }
+
+ //
+ // Save some parameters from the Scb, we need to restore these later.
+ //
+
+ ScbCompressionUnitShiftSave = Scb->CompressionUnitShift;
+ ScbCompressionUnitSave = Scb->CompressionUnit;
+ ScbAttributeFlagsSave = Scb->AttributeFlags;
+ CompressionState = COMPRESSION_FORMAT_LZNT1 - 1;
+
+ //
+ // If this is the first pass through NtfsMoveFile we need to set this
+ // request up as the top-level operation. This means
+ // setting the REALLOCATE_ON_WRITE flag, changing the attribute state
+ // and putting the SCB_STATE_COMPRESSED flag in the correct state.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength == MAXULONG) {
+
+ //
+ // If the REALLOCATE_ON_WRITE flag is set it means that someone is
+ // already changing the compression state, so get out now.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE )) {
+
+ try_return( Status = STATUS_UNSUCCESSFUL );
+ }
+
+ //
+ // Set ourselves up as the top level request and get the requested file
+ // offset from MoveData.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+
+ FileOffset = LlBytesFromClusters( Vcb, MoveData->StartingVcn.QuadPart );
+ NextIrpSp->Parameters.FileSystemControl.OutputBufferLength = (ULONG)FileOffset;
+ NextIrpSp->Parameters.FileSystemControl.InputBufferLength = ((PLARGE_INTEGER)&FileOffset)->HighPart;
+ }
+
+ //
+ // In the Fsd entry we clear the following two parameter fields in the Irp,
+ // and then we update them to our current position on all abnormal terminations.
+ // That way if we get a log file full, we only have to resume where we left
+ // off. We do pad the defrag range to compression unit boundaries since that
+ // is the granularity we do our defragging.
+ //
+
+ CompressionUnitSize = Vcb->BytesPerCluster << NTFS_CLUSTERS_PER_COMPRESSION;
+ ((PLARGE_INTEGER)&FileOffset)->LowPart = NextIrpSp->Parameters.FileSystemControl.OutputBufferLength;
+ ((PLARGE_INTEGER)&FileOffset)->HighPart = NextIrpSp->Parameters.FileSystemControl.InputBufferLength;
+
+ MoveData->ClusterCount += ClustersFromBytes( Vcb, ((ULONG) FileOffset & (CompressionUnitSize - 1)));
+ MoveData->ClusterCount += ((1 << NTFS_CLUSTERS_PER_COMPRESSION) - 1);
+ MoveData->ClusterCount &= ~((1 << NTFS_CLUSTERS_PER_COMPRESSION) - 1);
+
+ ((PLARGE_INTEGER) &FileOffset)->LowPart &= ~(CompressionUnitSize - 1);
+
+ //
+ // If the stream is resident there is no need rewrite any of the data.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+
+#ifdef _CAIRO_
+
+ //
+ // Expand quota to the expected state.
+ //
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ ScbAcquired = FALSE;
+
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = FALSE;
+
+ if (IrpContext->TransactionId != 0) {
+
+ //
+ // Complete the request which commits the pending
+ // transaction if there is one and releases of the
+ // acquired resources. The IrpContext will not
+ // be deleted because the no delete flag is set.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ }
+
+#else
+ NtfsReleaseScb( IrpContext, Scb );
+ ScbAcquired = FALSE;
+
+ ASSERT(IrpContext->TransactionId == 0);
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ PagingIoAcquired = FALSE;
+
+#endif // _CAIRO_
+
+ while (TRUE) {
+
+ //
+ // We must throttle our writes.
+ //
+
+ CcCanIWrite( FileObject, 0x40000, TRUE, FALSE );
+
+ //
+ // Lock the FsRtl header so we can freeze FileSize.
+ //
+
+ ExAcquireResourceExclusive( Scb->Header.PagingIoResource, TRUE );
+
+ FsRtlLockFsRtlHeader( &Scb->Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB) Scb;
+ FsRtlHeaderLocked = TRUE;
+
+ //
+ // Jump out right here if the attribute is resident or we
+ // are beyond the end of the file.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT) ||
+ (FileOffset >= Scb->Header.FileSize.QuadPart)) {
+
+ break;
+ }
+
+ //
+ // Get the number of bytes left to write.
+ //
+
+ ByteCount = LlBytesFromClusters( Vcb, MoveData->ClusterCount );
+
+ //
+ // Make sure we don't go past the end of the file.
+ //
+
+ if (ByteCount + FileOffset > Scb->Header.FileSize.QuadPart) {
+
+ ByteCount = Scb->Header.FileSize.QuadPart - FileOffset;
+ }
+
+ //
+ // This is how we exit, seeing that we have finally rewritten
+ // everything. It is possible that the file was truncated
+ // between passes through this loop so we test for 0 bytes or
+ // a negative value.
+ //
+ // Note that we exit with the Scb still acquired,
+ // so that we can reliably turn compression off.
+ //
+
+ if (ByteCount <= 0) {
+
+ break;
+ }
+
+ //
+ // If there is more than our max, then reduce the byte count for this
+ // pass to our maximum. We must also align the file offset to a 0x40000
+ // byte boundary.
+ //
+
+ if (((ULONG)FileOffset & 0x3ffff) + ByteCount > 0x40000) {
+
+ ByteCount = 0x40000 - ((ULONG)FileOffset & 0x3ffff);
+ }
+
+ //
+ // Also remember if we need to add allocation to round the allocation
+ // size to a compression unit. We need to do this now so that the
+ // Scb and on-disk allocation sizes will stay in sync. This should
+ // already be true for compressed files.
+ //
+
+ if ((ScbCompressionUnitSave == 0) &&
+ ((FileOffset + ByteCount + CompressionUnitSize - 1) > Scb->Header.AllocationSize.QuadPart)) {
+
+ LONGLONG NewAllocationSize;
+
+ NewAllocationSize = FileOffset + ByteCount + CompressionUnitSize - 1;
+ ((PLARGE_INTEGER) &NewAllocationSize)->LowPart &= ~(CompressionUnitSize - 1);
+
+ //
+ // Check again now that we have the exact needed value for allocation
+ // size.
+ //
+
+ if (NewAllocationSize > Scb->Header.AllocationSize.QuadPart) {
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ NtfsAddAllocation( IrpContext,
+ NULL,
+ Scb,
+ LlClustersFromBytes( Vcb,
+ Scb->Header.AllocationSize.QuadPart ),
+ LlClustersFromBytes( Vcb,
+ NewAllocationSize -
+ Scb->Header.AllocationSize.QuadPart ),
+ FALSE );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ SetFlag( Scb->Fcb->InfoFlags, FCB_INFO_CHANGED_ALLOC_SIZE );
+ }
+
+ SetFlag( Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE );
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Release everything at this point.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ while (!IsListEmpty( &IrpContext->ExclusiveFcbList )) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD( IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+ ScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // Get the pointer to the MOVE_DATA structure in the SCB and set some flags in the Scb.
+ // These are required in order to ensure a proper path for moving the file
+ // even though we are not compressing or decompressing.
+ //
+ // Acquire and drop the file resource in order to make this change. Otherwise
+ // a user paging read which acquires the main resource could see an
+ // inconsistent picture of this data.
+ //
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ Scb->Union.MoveData = MoveData;
+ Scb->CompressionUnitShift = NTFS_CLUSTERS_PER_COMPRESSION;
+ Scb->CompressionUnit = CompressionUnitSize;
+ Scb->AttributeFlags = (USHORT)((Scb->AttributeFlags & ~ATTRIBUTE_FLAG_COMPRESSION_MASK) | CompressionState);
+ ExReleaseResource( Scb->Header.Resource );
+
+ //
+ // Make sure there are enough available clusters in the range
+ // we want to rewrite.
+ //
+
+ if (!NtfsReserveClusters( IrpContext, Scb, FileOffset, (ULONG) ByteCount )) {
+
+ //
+ // If this transaction has already deallocated clusters
+ // then raise log file full to allow those to become
+ // available.
+ //
+
+ if (IrpContext->DeallocatedClusters != 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+
+ //
+ // Otherwise there is insufficient space to guarantee
+ // we can perform the compression operation.
+ //
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+ }
+
+ //
+ // See if we have to create an internal attribute stream. We do
+ // it in the loop, because the Scb must be acquired.
+ //
+
+ if (Scb->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+ }
+
+ //
+ // Map the next range of the file, and make the pages dirty.
+ //
+
+ CcMapData( Scb->FileObject, (PLARGE_INTEGER)&FileOffset, (ULONG)ByteCount, TRUE, &Bcb, &Buffer );
+
+ //
+ // Now attempt to allocate an Mdl to describe the mapped data.
+ //
+
+ Mdl = IoAllocateMdl( Buffer, (ULONG)ByteCount, FALSE, FALSE, NULL );
+
+ if (Mdl == NULL) {
+ DebugTrace( 0, 0, ("Failed to allocate Mdl\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ //
+ // Lock the data into memory so that we can safely reallocate the
+ // space. Don't tell Mm here that we plan to write it, as he sets
+ // dirty now and at the unlock below if we do.
+ //
+
+ MmProbeAndLockPages( Mdl, KernelMode, IoReadAccess );
+ LockedPages = TRUE;
+
+ //
+ // Mark the address range modified so that the flush will
+ // flush.
+ //
+
+ MmSetAddressRangeModified( Buffer, (ULONG)ByteCount );
+
+ UserMappedFile = FALSE;
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ if (FlagOn( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE )) {
+
+ UserMappedFile = TRUE;
+ }
+
+ //
+ // Tell Cc there is a user-mapped file so he will really flush.
+ //
+
+ SetFlag( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // Now flush these pages.
+ //
+
+ Irp->IoStatus.Status = NtfsFlushUserStream( IrpContext,
+ Scb,
+ &FileOffset,
+ (ULONG)ByteCount );
+
+ //
+ // Restore the FsRtl flag if there is no user mapped file.
+ // This is correctly synchronized, since we have the file
+ // exclusive, and Mm always has to query the file size before
+ // creating a user-mapped section and calling Cc to set
+ // the FsRtl flag.
+ //
+
+ if (!UserMappedFile) {
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ ClearFlag( Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE );
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+
+ //
+ // On error get out.
+ //
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &Irp->IoStatus.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+
+ //
+ // Now we can get rid of this Mdl.
+ //
+
+ MmUnlockPages( Mdl );
+ LockedPages = FALSE;
+ IoFreeMdl( Mdl );
+ Mdl = NULL;
+
+ //
+ // Now we can safely unpin and release the Scb for a while.
+ // (Got to let those checkpoints through!)
+ //
+
+ CcUnpinData( Bcb );
+ Bcb = NULL;
+
+ //
+ // Release any remaing reserved clusters in this range.
+ //
+
+ NtfsFreeReservedClusters( Scb, FileOffset, (ULONG) ByteCount );
+
+ //
+ // Save the ClusterCount in the Scb
+ //
+
+ MoveData->ClusterCount -= ClustersFromBytes( Vcb, (ULONG) ByteCount );
+
+ //
+ // Zero the pointer to the MOVE_DATA structure in the SCB and restore
+ // the flags in the Scb. If the file is uncompressed then make
+ // sure to release any remaining reserved clusters.
+ //
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ if (ScbCompressionUnitSave == 0) {
+
+ if (Scb->ScbType.Data.ReservedBitMap != NULL) {
+
+ NtfsFreePool( Scb->ScbType.Data.ReservedBitMap );
+ Scb->ScbType.Data.ReservedBitMap = NULL;
+ }
+
+ NtfsFreeFinalReservedClusters( Vcb,
+ LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved ));
+
+ Scb->ScbType.Data.TotalReserved = 0;
+ }
+
+ Scb->Union.MoveData = NULL;
+ Scb->CompressionUnit = ScbCompressionUnitSave;
+ Scb->CompressionUnitShift = ScbCompressionUnitShiftSave;
+ Scb->AttributeFlags = ScbAttributeFlagsSave;
+ ExReleaseResource( Scb->Header.Resource );
+
+ //
+ // Unlock the header and let anyone else access the file before
+ // looping back.
+ //
+
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ FsRtlHeaderLocked = FALSE;
+
+ //
+ // If we hit the end of the file then exit while holding the
+ // resource so we can turn compression off.
+ //
+
+ if (((ULONG) ByteCount & 0x3ffff) != 0) {
+
+ break;
+ }
+
+ //
+ // Advance the FileOffset.
+ //
+
+ FileOffset += ByteCount;
+
+ //
+ // Update the user's MoveData structure for the next pass in
+ // case we get a log file full.
+ //
+
+ RtlCopyMemory( Irp->AssociatedIrp.SystemBuffer,
+ &StackMoveData,
+ sizeof( MOVE_FILE_DATA ));
+ }
+
+ //
+ // See if we broke out of the loop with the header locked.
+ //
+
+ if (FsRtlHeaderLocked) {
+
+ //
+ // Zero the pointer to the MOVE_DATA structure in the SCB and restore
+ // the flags in the Scb. If the file is uncompressed then make
+ // sure to release any remaining reserved clusters.
+ //
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ if (ScbCompressionUnitSave == 0) {
+
+ if (Scb->ScbType.Data.ReservedBitMap != NULL) {
+
+ NtfsFreePool( Scb->ScbType.Data.ReservedBitMap );
+ Scb->ScbType.Data.ReservedBitMap = NULL;
+ }
+
+ NtfsFreeFinalReservedClusters( Vcb,
+ LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved ));
+
+ Scb->ScbType.Data.TotalReserved = 0;
+ }
+
+ Scb->Union.MoveData = NULL;
+ Scb->CompressionUnit = ScbCompressionUnitSave;
+ Scb->CompressionUnitShift = ScbCompressionUnitShiftSave;
+ Scb->AttributeFlags = ScbAttributeFlagsSave;
+ ExReleaseResource( Scb->Header.Resource );
+
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ FsRtlHeaderLocked = FALSE;
+ }
+ }
+
+ Status = STATUS_SUCCESS;
+
+ try_exit: NOTHING;
+
+ //
+ // Now clear the reallocate flag in the Scb if we set it.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength != MAXULONG) {
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ ClearFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ ExReleaseResource( Scb->Header.Resource );
+ }
+
+ } finally {
+
+
+ DebugUnwind( NtfsMovesFile );
+
+ //
+ // Cleanup the Mdl if we died with one.
+ //
+
+ if (Mdl != NULL) {
+ if (LockedPages) { MmUnlockPages( Mdl ); }
+ IoFreeMdl( Mdl );
+ }
+
+ if (Bcb != NULL) {
+ CcUnpinData( Bcb );
+ }
+
+ //
+ // Restore the Scb flags if we haven't already done so.
+ //
+
+ if (FsRtlHeaderLocked) {
+
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ if (ScbCompressionUnitSave == 0) {
+
+ if (Scb->ScbType.Data.ReservedBitMap != NULL) {
+
+ NtfsFreePool( Scb->ScbType.Data.ReservedBitMap );
+ Scb->ScbType.Data.ReservedBitMap = NULL;
+ }
+
+ NtfsFreeFinalReservedClusters( Vcb,
+ LlClustersFromBytesTruncate( Vcb, Scb->ScbType.Data.TotalReserved ));
+
+ Scb->ScbType.Data.TotalReserved = 0;
+ }
+
+ Scb->Union.MoveData = NULL;
+ Scb->CompressionUnit = ScbCompressionUnitSave;
+ Scb->CompressionUnitShift = ScbCompressionUnitShiftSave;
+ Scb->AttributeFlags = ScbAttributeFlagsSave;
+ ExReleaseResource( Scb->Header.Resource );
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ }
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ if (!AbnormalTermination()) {
+
+ if (ScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ //
+ // Otherwise, set to restart from the current file position, assuming
+ // this may be a log file full.
+ //
+
+ } else {
+
+ //
+ // If we have started the transformation and are in the exception path
+ // we are either going to continue the operation after a clean
+ // checkpoint or we are done.
+ //
+
+ if (NextIrpSp->Parameters.FileSystemControl.OutputBufferLength != MAXULONG) {
+
+ //
+ // If we are continuing the operation, save the current file offset.
+ //
+
+ if (IrpContext->ExceptionStatus == STATUS_LOG_FILE_FULL ||
+ IrpContext->ExceptionStatus == STATUS_CANT_WAIT) {
+
+ NextIrpSp->Parameters.FileSystemControl.OutputBufferLength = (ULONG)FileOffset;
+ NextIrpSp->Parameters.FileSystemControl.InputBufferLength = ((PLARGE_INTEGER)&FileOffset)->HighPart;
+
+ //
+ // Otherwise clear the REALLOCATE_ON_WRITE flag.
+ //
+
+ } else {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE );
+ }
+ }
+
+ if (ScbAcquired) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ //
+ // We may have one or the other of these conditions to clean up.
+ //
+
+ if (FsRtlHeaderLocked) {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+ }
+ }
+
+ return Status;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsIsVolumeDirty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the dirty state of the volume.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PULONG VolumeState;
+ PVOLUME_INFORMATION VolumeInfo;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+
+ //
+ // Get the current stack location and extract the output
+ // buffer information. The output parameter will receive
+ // the compressed state of the file/directory.
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Get a pointer to the output buffer. Look at the system buffer field in th
+ // irp first. Then the Irp Mdl.
+ //
+
+ if (Irp->AssociatedIrp.SystemBuffer != NULL) {
+
+ VolumeState = Irp->AssociatedIrp.SystemBuffer;
+
+ } else if (Irp->MdlAddress != NULL) {
+
+ VolumeState = MmGetSystemAddressForMdl( Irp->MdlAddress );
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_USER_BUFFER );
+ return STATUS_INVALID_USER_BUFFER;
+ }
+
+ //
+ // Make sure the output buffer is large enough and then initialize
+ // the answer to be that the volume isn't corrupt.
+ //
+
+ if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(ULONG)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ *VolumeState = 0;
+
+ //
+ // Decode the file object
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire the Scb shared.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+
+ //
+ // Make sure the volume is still mounted.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_VOLUME_DISMOUNTED );
+ return STATUS_VOLUME_DISMOUNTED;
+ }
+
+ //
+ // Look up the VOLUME_INFORMATION attribute.
+ //
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Use a try-finally to perform cleanup.
+ //
+
+ try {
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $VOLUME_INFORMATION,
+ &Context )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Return the volume state and the size of the returned data.
+ //
+
+ VolumeInfo = (PVOLUME_INFORMATION) NtfsAttributeValue( NtfsFoundAttribute( &Context ));
+
+ *VolumeState = VolumeInfo->VolumeFlags & VOLUME_DIRTY;
+
+ Irp->IoStatus.Information = sizeof( ULONG );
+
+ } finally {
+
+ NtfsReleaseScb( IrpContext, Scb );
+ NtfsCleanupAttributeContext( &Context );
+ DebugUnwind( NtfsIsVolumeDirty );
+ }
+
+ //
+ // If this is an abnormal termination then undo our work, otherwise
+ // complete the irp
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsSetExtendedDasdIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will mark a Dasd handle to perform IO outside the logical bounds of
+ the partition. Any subsequent IO will be passed to the driver which can either
+ complete it or return an error.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ PAGED_CODE();
+
+ //
+ // Decode the file object
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, IrpSp->FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // Make sure this is a volume open.
+ //
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Mark the Ccb for extended Io and return.
+ //
+
+ SetFlag( Ccb->Flags, CCB_FLAG_ALLOW_XTENDED_DASD_IO );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+ return STATUS_SUCCESS;
+}
+
+
+
diff --git a/private/ntos/cntfs/fspdisp.c b/private/ntos/cntfs/fspdisp.c
new file mode 100644
index 000000000..84c2a614f
--- /dev/null
+++ b/private/ntos/cntfs/fspdisp.c
@@ -0,0 +1,790 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ FspDisp.c
+
+Abstract:
+
+ This module implements the main dispatch procedure/thread for the Ntfs
+ Fsp
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define BugCheckFileId (NTFS_BUG_CHECK_FSPDISP)
+
+#pragma alloc_text(PAGE, NtfsSpecialDispatch)
+#pragma alloc_text(PAGE, NtfsPostSpecial)
+
+//
+// Define our local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_FSP_DISPATCHER)
+
+extern PETHREAD NtfsDesignatedTimeoutThread;
+
+
+VOID
+NtfsFspDispatch (
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This is the main FSP thread routine that is executed to receive
+ and dispatch IRP requests. Each FSP thread begins its execution here.
+ There is one thread created at system initialization time and subsequent
+ threads created as needed.
+
+Arguments:
+
+
+ Context - Supplies the thread id.
+
+Return Value:
+
+ None - This routine never exits
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ PIRP Irp;
+ PIRP_CONTEXT IrpContext;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG LogFileFullCount = 0;
+
+ PVOLUME_DEVICE_OBJECT VolDo;
+
+ BOOLEAN Retry;
+
+ IrpContext = (PIRP_CONTEXT)Context;
+
+ Irp = IrpContext->OriginatingIrp;
+
+ if (Irp != NULL) {
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ }
+
+ //
+ // Now because we are the Fsp we will force the IrpContext to
+ // indicate true on Wait.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ //
+ // If this request has an associated volume device object, remember it.
+ //
+
+ if ((Irp != NULL) &&
+ (IrpSp->FileObject != NULL)) {
+
+ VolDo = CONTAINING_RECORD( IrpSp->DeviceObject,
+ VOLUME_DEVICE_OBJECT,
+ DeviceObject );
+ } else {
+
+ VolDo = NULL;
+ }
+
+ //
+ // Now case on the function code. For each major function code,
+ // either call the appropriate FSP routine or case on the minor
+ // function and then call the FSP routine. The FSP routine that
+ // we call is responsible for completing the IRP, and not us.
+ // That way the routine can complete the IRP and then continue
+ // post processing as required. For example, a read can be
+ // satisfied right away and then read can be done.
+ //
+ // We'll do all of the work within an exception handler that
+ // will be invoked if ever some underlying operation gets into
+ // trouble (e.g., if NtfsReadSectorsSync has trouble).
+ //
+
+ while (TRUE) {
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, TRUE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ Retry = FALSE;
+
+ NtfsPostRequests += 1;
+
+ do {
+
+ try {
+
+ //
+ // Always clear the exception code in the IrpContext so we respond
+ // correctly to errors encountered in the Fsp.
+ //
+
+ IrpContext->ExceptionStatus = 0;
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAGS_CLEAR_ON_POST );
+ IrpContext->DeallocatedClusters = 0;
+ IrpContext->FreeClusterChange = 0;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP );
+
+ //
+ // If this ins the initial try with this Irp Context, update the
+ // top level Irp fields.
+ //
+
+ if (!Retry) {
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else {
+
+ Retry = FALSE;
+ }
+
+ //
+ // See if we were posted due to a log file full condition, and
+ // if so, then do a clean volume checkpoint if we are the
+ // first ones to get there. If we see a different Lsn and do
+ // not do the checkpoint, the worst that can happen is that we
+ // will get posted again if the log file is still full.
+ //
+
+ if (IrpContext->LastRestartArea.QuadPart != 0) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+
+ if (++LogFileFullCount >= 2) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
+ }
+ }
+
+ //
+ // If we have an Irp then proceed with our normal processing.
+ //
+
+ if (Irp != NULL) {
+
+ switch ( IrpContext->MajorFunction ) {
+
+ //
+ // For Create Operation,
+ //
+
+ case IRP_MJ_CREATE:
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_DASD_OPEN )) {
+
+ (VOID) NtfsCommonVolumeOpen( IrpContext, Irp );
+
+ } else {
+
+ (VOID) NtfsCommonCreate( IrpContext, Irp, NULL );
+ }
+ break;
+
+ //
+ // For close operations
+ //
+
+ case IRP_MJ_CLOSE:
+
+ //
+ // We should never post closes to this workqueue.
+ //
+
+ NtfsBugCheck( 0, 0, 0 );
+
+ //
+ // For read operations
+ //
+
+ case IRP_MJ_READ:
+
+ (VOID) NtfsCommonRead( IrpContext, Irp, TRUE );
+ break;
+
+ //
+ // For write operations,
+ //
+
+ case IRP_MJ_WRITE:
+
+ (VOID) NtfsCommonWrite( IrpContext, Irp );
+ break;
+
+ //
+ // For Query Information operations,
+ //
+
+ case IRP_MJ_QUERY_INFORMATION:
+
+ (VOID) NtfsCommonQueryInformation( IrpContext, Irp );
+ break;
+
+ //
+ // For Set Information operations,
+ //
+
+ case IRP_MJ_SET_INFORMATION:
+
+ (VOID) NtfsCommonSetInformation( IrpContext, Irp );
+ break;
+
+ //
+ // For Query EA operations,
+ //
+
+ case IRP_MJ_QUERY_EA:
+
+ (VOID) NtfsCommonQueryEa( IrpContext, Irp );
+ break;
+
+ //
+ // For Set EA operations,
+ //
+
+ case IRP_MJ_SET_EA:
+
+ (VOID) NtfsCommonSetEa( IrpContext, Irp );
+ break;
+
+
+ //
+ // For Flush buffers operations,
+ //
+
+ case IRP_MJ_FLUSH_BUFFERS:
+
+ (VOID) NtfsCommonFlushBuffers( IrpContext, Irp );
+ break;
+
+ //
+ // For Query Volume Information operations,
+ //
+
+ case IRP_MJ_QUERY_VOLUME_INFORMATION:
+
+ (VOID) NtfsCommonQueryVolumeInfo( IrpContext, Irp );
+ break;
+
+ //
+ // For Set Volume Information operations,
+ //
+
+ case IRP_MJ_SET_VOLUME_INFORMATION:
+
+ (VOID) NtfsCommonSetVolumeInfo( IrpContext, Irp );
+ break;
+
+ //
+ // For File Cleanup operations,
+ //
+
+ case IRP_MJ_CLEANUP:
+
+ (VOID) NtfsCommonCleanup( IrpContext, Irp );
+ break;
+
+ //
+ // For Directory Control operations,
+ //
+
+ case IRP_MJ_DIRECTORY_CONTROL:
+
+ (VOID) NtfsCommonDirectoryControl( IrpContext, Irp );
+ break;
+
+ //
+ // For File System Control operations,
+ //
+
+ case IRP_MJ_FILE_SYSTEM_CONTROL:
+
+ (VOID) NtfsCommonFileSystemControl( IrpContext, Irp );
+ break;
+
+ //
+ // For Lock Control operations,
+ //
+
+ case IRP_MJ_LOCK_CONTROL:
+
+ (VOID) NtfsCommonLockControl( IrpContext, Irp );
+ break;
+
+ //
+ // For Device Control operations,
+ //
+
+ case IRP_MJ_DEVICE_CONTROL:
+
+ (VOID) NtfsCommonDeviceControl( IrpContext, Irp );
+ break;
+
+ //
+ // For Query Security Information operations,
+ //
+
+ case IRP_MJ_QUERY_SECURITY:
+
+ (VOID) NtfsCommonQuerySecurityInfo( IrpContext, Irp );
+ break;
+
+ //
+ // For Set Security Information operations,
+ //
+
+ case IRP_MJ_SET_SECURITY:
+
+ (VOID) NtfsCommonSetSecurityInfo( IrpContext, Irp );
+ break;
+
+ //
+ // For any other major operations, return an invalid
+ // request.
+ //
+
+ default:
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+ break;
+ }
+
+ //
+ // Otherwise complete the request to clean up this Irp Context.
+ //
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+ }
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+ PIO_STACK_LOCATION IrpSp;
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ if (Irp != NULL) {
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ ExceptionCode = GetExceptionCode();
+
+ if (ExceptionCode == STATUS_FILE_DELETED
+ && (IrpContext->MajorFunction == IRP_MJ_READ
+ || IrpContext->MajorFunction == IRP_MJ_WRITE
+ || (IrpContext->MajorFunction == IRP_MJ_SET_INFORMATION
+ && IrpSp->Parameters.SetFile.FileInformationClass == FileEndOfFileInformation))) {
+
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_SUCCESS;
+ }
+ }
+
+ ExceptionCode = NtfsProcessException( IrpContext, Irp, ExceptionCode );
+
+ if (ExceptionCode == STATUS_CANT_WAIT ||
+ ExceptionCode == STATUS_LOG_FILE_FULL) {
+
+ Retry = TRUE;
+ }
+ }
+
+ } while (Retry);
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+
+ FsRtlExitFileSystem();
+
+ //
+ // If there are any entries on this volume's overflow queue, service
+ // them.
+ //
+
+ if ( VolDo != NULL ) {
+
+ KIRQL SavedIrql;
+ PVOID Entry = NULL;
+
+ //
+ // We have a volume device object so see if there is any work
+ // left to do in its overflow queue.
+ //
+
+ KeAcquireSpinLock( &VolDo->OverflowQueueSpinLock, &SavedIrql );
+
+ if (VolDo->OverflowQueueCount > 0) {
+
+ //
+ // There is overflow work to do in this volume so we'll
+ // decrement the Overflow count, dequeue the IRP, and release
+ // the Event
+ //
+
+ VolDo->OverflowQueueCount -= 1;
+
+ Entry = RemoveHeadList( &VolDo->OverflowQueue );
+ }
+
+ KeReleaseSpinLock( &VolDo->OverflowQueueSpinLock, SavedIrql );
+
+ //
+ // There wasn't an entry, break out of the loop and return to
+ // the Ex Worker thread.
+ //
+
+ if ( Entry == NULL ) {
+
+ break;
+ }
+
+ //
+ // Extract the IrpContext, Irp, set wait to TRUE, and loop.
+ //
+
+ IrpContext = CONTAINING_RECORD( Entry,
+ IRP_CONTEXT,
+ WorkQueueItem.List );
+
+ LogFileFullCount = 0;
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ Irp = IrpContext->OriginatingIrp;
+
+ continue;
+
+ } else {
+
+ break;
+ }
+ }
+
+ //
+ // Decrement the PostedRequestCount.
+ //
+
+ if (VolDo) {
+
+ ExInterlockedAddUlong( &VolDo->PostedRequestCount,
+ 0xffffffff,
+ &VolDo->OverflowQueueSpinLock );
+ }
+
+ return;
+}
+
+#ifdef _CAIRO_
+
+VOID
+NtfsPostSpecial (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN POST_SPECIAL_CALLOUT PostSpecialCallout,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine posts a special request to a worker thread. The function
+ to be called is passed in. The Vcb is referenced to ensure it is not
+ deleted while the posted request is excuting.
+
+Arguments:
+
+ Vcb - Volume control block for volume to post to.
+
+ PostSpecialCallout - Function to be called from the worker thread.
+
+ Context - Context point to pass to the function.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PIRP_CONTEXT NewIrpContext;
+
+ UNREFERENCED_PARAMETER( IrpContext );
+
+ PAGED_CODE();
+
+ //
+ // Create an IrpContext for use to post the request.
+ //
+
+ NewIrpContext = NtfsCreateIrpContext( NULL, TRUE);
+ NewIrpContext->Vcb = Vcb;
+
+ NewIrpContext->Union.PostSpecialCallout = PostSpecialCallout;
+ NewIrpContext->OriginatingIrp = Context;
+
+ //
+ // Updating the CloseCount and SystemFileCloseCount allows the volume
+ // to be locked or dismounted, but the Vcb will not be deleted. This
+ // routine will only be called with non-zero close counts so it is ok
+ // to increment theses counts.
+ //
+
+ ASSERT( Vcb->CloseCount > 0 && Vcb->SystemFileCloseCount > 0 );
+ InterlockedIncrement( &Vcb->CloseCount );
+ InterlockedIncrement( &Vcb->SystemFileCloseCount );
+
+ ExInitializeWorkItem( &NewIrpContext->WorkQueueItem,
+ NtfsSpecialDispatch,
+ NewIrpContext );
+
+ //
+ // Determine if the scavenger is already running.
+ //
+
+ ExAcquireFastMutexUnsafe( &NtfsScavengerLock );
+
+ if (NtfsScavengerRunning) {
+
+ //
+ // Add this item to the scavanger work list.
+ //
+
+ NewIrpContext->WorkQueueItem.List.Flink = NULL;
+
+ if (NtfsScavengerWorkList == NULL) {
+
+ NtfsScavengerWorkList = NewIrpContext;
+ } else {
+ PIRP_CONTEXT WorkIrpContext;
+
+ WorkIrpContext = NtfsScavengerWorkList;
+
+ while (WorkIrpContext->WorkQueueItem.List.Flink != NULL) {
+ WorkIrpContext = (PIRP_CONTEXT)
+ WorkIrpContext->WorkQueueItem.List.Flink;
+ }
+
+ WorkIrpContext->WorkQueueItem.List.Flink = (PLIST_ENTRY)
+ NewIrpContext;
+ }
+
+ } else {
+
+ //
+ // Start a worker thread to do scavenger work.
+ //
+
+ ExQueueWorkItem( &NewIrpContext->WorkQueueItem, DelayedWorkQueue );
+ NtfsScavengerRunning = TRUE;
+ }
+
+ ExReleaseFastMutexUnsafe( &NtfsScavengerLock);
+
+}
+
+VOID
+NtfsSpecialDispatch (
+ PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when a special operation needs to be posted.
+ It is called indirectly by NtfsPostSpecial. It is assumes that the
+ Vcb is protected from going away by incrementing the volemue close
+ counts for a file. If this routine fails nothing is done except
+ to clean up the Vcb. This routine also handles issues log file full
+ and can't wait.
+
+ The function to be called is stored in the PostSpecialCallout field
+ of the Irp Context, and the context is stored int he OriginatingIrp.
+ Both fields are zeroed before the the callout function is called.
+
+Arguments:
+
+ Context - Supplies a pointer to an IrpContext.
+
+Return Value:
+
+--*/
+
+{
+ PVCB Vcb;
+ PIRP_CONTEXT IrpContext = Context;
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+ POST_SPECIAL_CALLOUT PostSpecialCallout;
+ PVOID SpecialContext;
+ ULONG LogFileFullCount;
+ BOOLEAN Retry;
+
+ PAGED_CODE();
+
+ FsRtlEnterFileSystem();
+
+ do {
+
+ Vcb = IrpContext->Vcb;
+ LogFileFullCount = 0;
+
+ //
+ // Capture the funciton pointer and context before using the IrpContext.
+ //
+
+ PostSpecialCallout = IrpContext->Union.PostSpecialCallout;
+ SpecialContext = IrpContext->OriginatingIrp;
+ IrpContext->Union.PostSpecialCallout = NULL;
+ IrpContext->OriginatingIrp = NULL;
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, TRUE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ do {
+
+ Retry = FALSE;
+
+ try {
+
+ //
+ // See if we failed due to a log file full condition, and
+ // if so, then do a clean volume checkpoint if we are the
+ // first ones to get there. If we see a different Lsn and do
+ // not do the checkpoint, the worst that can happen is that we
+ // will fail again if the log file is still full.
+ //
+
+ if (IrpContext->LastRestartArea.QuadPart != 0) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+
+ if (++LogFileFullCount >= 2) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_EXCESS_LOG_FULL );
+ }
+ }
+
+ //
+ // Call the requested function.
+ //
+
+ PostSpecialCallout( IrpContext, SpecialContext );
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE);
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE);
+ ExceptionCode = NtfsProcessException( IrpContext, NULL, ExceptionCode );
+
+ if (ExceptionCode == STATUS_CANT_WAIT ||
+ ExceptionCode == STATUS_LOG_FILE_FULL) {
+
+ Retry = TRUE;
+ }
+ }
+
+ } while (Retry);
+
+ //
+ // At this point regardless of the status the volume needs to
+ // be cleaned up and the IrpContext freed.
+ // Dereference the Vcb and check to see if it needs to be deleted.
+ // since this call might raise warp it with a try/execpt.
+ //
+
+ try {
+
+ //
+ // Acquire the volume exclusive so the counts can be
+ // updated.
+ //
+
+ ASSERT(FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT));
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ InterlockedDecrement( &Vcb->SystemFileCloseCount );
+ InterlockedDecrement( &Vcb->CloseCount );
+
+ NtfsReleaseVcbCheckDelete( IrpContext,
+ Vcb,
+ IRP_MJ_DEVICE_CONTROL,
+ NULL );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ ASSERT( FsRtlIsNtstatusExpected( GetExceptionCode() ) );
+ }
+
+ //
+ // Restore the top level context and free the irp context.
+ //
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE);
+ NtfsDeleteIrpContext( &IrpContext );
+
+ //
+ // See if there is more work on the scavenger list.
+ //
+
+ ExAcquireFastMutexUnsafe( &NtfsScavengerLock );
+
+ ASSERT( NtfsScavengerRunning );
+
+ IrpContext = NtfsScavengerWorkList;
+
+ if (IrpContext != NULL) {
+
+ //
+ // Remove the entry from the list.
+ //
+
+ NtfsScavengerWorkList = (PIRP_CONTEXT)
+ IrpContext->WorkQueueItem.List.Flink;
+ IrpContext->WorkQueueItem.List.Flink = NULL;
+
+ } else {
+
+ NtfsScavengerRunning = FALSE;
+
+ }
+
+ ExReleaseFastMutexUnsafe( &NtfsScavengerLock );
+
+ } while ( IrpContext != NULL );
+
+ FsRtlExitFileSystem();
+}
+#endif // _CAIRO_
+
diff --git a/private/ntos/cntfs/fstiosup.c b/private/ntos/cntfs/fstiosup.c
new file mode 100644
index 000000000..a9251307a
--- /dev/null
+++ b/private/ntos/cntfs/fstiosup.c
@@ -0,0 +1,1604 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ FstIoSup.c
+
+Abstract:
+
+ This module implements the fast I/O routines for Ntfs.
+
+Author:
+
+ Tom Miller [TomM] 16-May-96
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCopyReadA)
+#pragma alloc_text(PAGE, NtfsCopyWriteA)
+#pragma alloc_text(PAGE, NtfsMdlReadA)
+#pragma alloc_text(PAGE, NtfsPrepareMdlWriteA)
+#pragma alloc_text(PAGE, NtfsWaitForIoAtEof)
+#pragma alloc_text(PAGE, NtfsFinishIoAtEof)
+#endif
+
+
+BOOLEAN
+NtfsCopyReadA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ OUT PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached read bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy read
+ of a cached file object. For a complete description of the arguments
+ see CcCopyRead.
+
+Arguments:
+
+ FileObject - Pointer to the file object being read.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ Wait - FALSE if caller may not block, TRUE otherwise
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+Return Value:
+
+ FALSE - if Wait was supplied as FALSE and the data was not delivered, or
+ if there is an I/O error.
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ LARGE_INTEGER BeyondLastByte;
+ PDEVICE_OBJECT targetVdo;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ BOOLEAN Status = TRUE;
+ ULONG PageCount = COMPUTE_PAGES_SPANNED(((PVOID)FileOffset->LowPart), Length);
+ BOOLEAN DoingIoAtEof = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Special case a read of zero length
+ //
+
+ if (Length != 0) {
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ BeyondLastByte.QuadPart = FileOffset->QuadPart + (LONGLONG)Length;
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Make our best guess on whether we need the file exclusive
+ // or shared. Note that we do not check FileOffset->HighPart
+ // until below.
+ //
+
+ if ((Header->PagingIoResource == NULL) ||
+ !ExAcquireResourceShared(Header->PagingIoResource, Wait)) {
+ Status = FALSE;
+ goto Done2;
+ }
+
+ HOT_STATISTIC(CcFastReadWait) += 1;
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we are reading beyond ValidDataLength. We have to
+ // do it now so that our reads are not nooped.
+ //
+
+ if (BeyondLastByte.QuadPart > Header->ValidDataLength.QuadPart) {
+
+ //
+ // We must serialize with anyone else doing I/O at beyond
+ // ValidDataLength, and then remember if we need to declare
+ // when we are done.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are in fact beyond ValidDataLength.
+ //
+
+ if (DoingIoAtEof) {
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test if it
+ // is really cached and if we can do fast i/o and if not, then
+ // release the fcb and return.
+ //
+
+ if ((FileObject->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible)) {
+
+ HOT_STATISTIC(CcFastReadNotPossible) += 1;
+
+ Status = FALSE;
+ goto Done;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask the
+ // file system the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ PFAST_IO_DISPATCH FastIoDispatch;
+
+ ASSERT(!KeIsExecutingDpc());
+
+ targetVdo = IoGetRelatedDeviceObject( FileObject );
+ FastIoDispatch = targetVdo->DriverObject->FastIoDispatch;
+
+
+ //
+ // All file systems that set "Is Questionable" had better support
+ // fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the answer is
+ // anything other than GoForIt then we cannot take the fast I/O
+ // path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ FileOffset,
+ Length,
+ Wait,
+ LockKey,
+ TRUE, // read operation
+ IoStatus,
+ targetVdo )) {
+
+ //
+ // Fast I/O is not possible so release the Fcb and return.
+ //
+
+ HOT_STATISTIC(CcFastReadNotPossible) += 1;
+
+ Status = FALSE;
+ goto Done;
+ }
+ }
+
+ //
+ // Check for read past file size.
+ //
+
+ if ( BeyondLastByte.QuadPart > Header->FileSize.QuadPart ) {
+
+ if ( FileOffset->QuadPart >= Header->FileSize.QuadPart ) {
+ IoStatus->Status = STATUS_END_OF_FILE;
+ IoStatus->Information = 0;
+
+ goto Done;
+ }
+
+ Length = (ULONG)( Header->FileSize.QuadPart - FileOffset->QuadPart );
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work and then
+ // release the fcb when we've done. If for whatever reason the
+ // copy read fails, then return FALSE to our caller.
+ //
+ // Also mark this as the top level "Irp" so that lower file system
+ // levels will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ try {
+
+ if (Wait && ((BeyondLastByte.HighPart | Header->FileSize.HighPart) == 0)) {
+
+ CcFastCopyRead( FileObject,
+ FileOffset->LowPart,
+ Length,
+ PageCount,
+ Buffer,
+ IoStatus );
+
+ FileObject->Flags |= FO_FILE_FAST_IO_READ;
+
+ ASSERT( (IoStatus->Status == STATUS_END_OF_FILE) ||
+ ((FileOffset->LowPart + IoStatus->Information) <= Header->FileSize.LowPart));
+
+ } else {
+
+ Status = CcCopyRead( FileObject,
+ FileOffset,
+ Length,
+ Wait,
+ Buffer,
+ IoStatus );
+
+ FileObject->Flags |= FO_FILE_FAST_IO_READ;
+
+ ASSERT( !Status || (IoStatus->Status == STATUS_END_OF_FILE) ||
+ ((FileOffset->QuadPart + IoStatus->Information) <= Header->FileSize.QuadPart));
+ }
+
+ if (Status) {
+
+ FileObject->CurrentByteOffset.QuadPart = FileOffset->QuadPart + IoStatus->Information;
+ }
+
+ } except( FsRtlIsNtstatusExpected(GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ Status = FALSE;
+ }
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ Done: NOTHING;
+
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+ ExReleaseResource( Header->PagingIoResource );
+
+ Done2: NOTHING;
+
+ FsRtlExitFileSystem();
+
+ } else {
+
+ //
+ // A zero length transfer was requested.
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = 0;
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsCopyWriteA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ IN PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached write bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy write
+ of a cached file object. For a complete description of the arguments
+ see CcCopyWrite.
+
+Arguments:
+
+ FileObject - Pointer to the file object being write.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ Wait - FALSE if caller may not block, TRUE otherwise
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+Return Value:
+
+ FALSE - if Wait was supplied as FALSE and the data was not delivered, or
+ if there is an I/O error.
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ LARGE_INTEGER Offset;
+ LARGE_INTEGER NewFileSize;
+ LARGE_INTEGER OldFileSize;
+ PDEVICE_OBJECT targetVdo = IoGetRelatedDeviceObject( FileObject );
+ PFAST_IO_DISPATCH FastIoDispatch = targetVdo->DriverObject->FastIoDispatch;
+ ULONG DoingIoAtEof = FALSE;
+ BOOLEAN Status = TRUE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // Do we need to verify the volume? If so, we must go to the file
+ // system. Also return FALSE if FileObject is write through, the
+ // File System must do that.
+ //
+
+ if (CcCanIWrite( FileObject, Length, Wait, FALSE ) &&
+ !FlagOn(FileObject->Flags, FO_WRITE_THROUGH) &&
+ CcCopyWriteWontFlush(FileObject, FileOffset, Length) &&
+ (Header->PagingIoResource != NULL)) {
+
+ //
+ // Assume our transfer will work
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = Length;
+
+ //
+ // Special case the zero byte length
+ //
+
+ if (Length != 0) {
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Split into separate paths for increased performance. First
+ // we have the faster path which only supports Wait == TRUE and
+ // 32 bits. We will make an unsafe test on whether the fast path
+ // is ok, then just return FALSE later if we were wrong. This
+ // should virtually never happen.
+ //
+ // IMPORTANT NOTE: It is very important that any changes mad to
+ // this path also be applied to the 64-bit path
+ // which is the else of this test!
+ //
+
+ NewFileSize.QuadPart = FileOffset->QuadPart + Length;
+ Offset = *FileOffset;
+
+ if (Wait && (Header->AllocationSize.HighPart == 0)) {
+
+ //
+ // Prevent truncates by acquiring paging I/O
+ //
+
+ ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped.
+ //
+
+ if ((FileOffset->HighPart < 0) || (NewFileSize.LowPart > Header->ValidDataLength.LowPart)) {
+
+ //
+ // We can change FileSize and ValidDataLength if either, no one
+ // else is now, or we are still extending after waiting.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Now that we are synchronized for end of file cases,
+ // we can calculate the real offset for this transfer and
+ // the new file size (if we succeed).
+ //
+
+
+ if ((FileOffset->HighPart < 0)) {
+ Offset = Header->FileSize;
+ }
+
+ //
+ // Above we allowed any negative .HighPart for the 32-bit path,
+ // but now we are counting on the I/O system to have thrown
+ // any negative number other than write to end of file.
+ //
+
+ ASSERT(Offset.HighPart >= 0);
+
+ //
+ // Now calculate the new FileSize and see if we wrapped the
+ // 32-bit boundary.
+ //
+
+ NewFileSize.QuadPart = Offset.QuadPart + Length;
+
+ //
+ // Update Filesize now so that we do not truncate reads.
+ //
+
+ OldFileSize.QuadPart = Header->FileSize.QuadPart;
+ if (NewFileSize.QuadPart > Header->FileSize.QuadPart) {
+
+ //
+ // If we are beyond AllocationSize, make sure we will
+ // ErrOut below, and don't modify FileSize now!
+ //
+
+ if (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) {
+ NewFileSize.QuadPart = (LONGLONG)0x7FFFFFFFFFFFFFFF;
+ } else {
+ Header->FileSize.QuadPart = NewFileSize.QuadPart;
+ }
+ }
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test
+ // if it is really cached and if we can do fast i/o and we
+ // do not have to extend. If not then release the fcb and
+ // return.
+ //
+ // Get out if we have too much to zero. This case is not important
+ // for performance, and a file system supporting sparseness may have
+ // a way to do this more efficiently.
+ //
+
+ if ((FileObject->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible) ||
+/* Remove? */ (NewFileSize.LowPart > Header->AllocationSize.QuadPart) ||
+ (Offset.LowPart >= (Header->ValidDataLength.LowPart + 0x2000)) ||
+ (NewFileSize.HighPart != 0)) {
+
+ goto ErrOut;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask
+ // the file system the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ targetVdo = IoGetRelatedDeviceObject( FileObject );
+ FastIoDispatch = targetVdo->DriverObject->FastIoDispatch;
+
+ //
+ // All file system then set "Is Questionable" had better
+ // support fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the
+ // answer is anything other than GoForIt then we cannot
+ // take the fast I/O path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ &Offset,
+ Length,
+ TRUE,
+ LockKey,
+ FALSE, // write operation
+ IoStatus,
+ targetVdo )) {
+
+ //
+ // Fast I/O is not possible so cleanup and return.
+ //
+
+ goto ErrOut;
+ }
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work
+ // and then release the fcb when we've done. If for whatever
+ // reason the copy write fails, then return FALSE to our
+ // caller.
+ //
+ // Also mark this as the top level "Irp" so that lower file
+ // system levels will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ try {
+
+ //
+ // See if we have to do some zeroing
+ //
+
+ if (Offset.LowPart > Header->ValidDataLength.LowPart) {
+
+ CcZeroData( FileObject,
+ &Header->ValidDataLength,
+ &Offset,
+ TRUE );
+ }
+
+ CcFastCopyWrite( FileObject,
+ Offset.LowPart,
+ Length,
+ Buffer );
+
+ } except( FsRtlIsNtstatusExpected(GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ Status = FALSE;
+ }
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ //
+ // If we succeeded, see if we have to update FileSize or
+ // ValidDataLength.
+ //
+
+ if (Status) {
+
+ //
+ // Set this handle as having modified the file and update
+ // the current file position pointer
+ //
+
+ FileObject->Flags |= FO_FILE_MODIFIED;
+ FileObject->CurrentByteOffset.QuadPart = Offset.QuadPart + Length;
+
+ if (DoingIoAtEof) {
+
+ //
+ // Make sure Cc knows the current FileSize, as set above,
+ // (we may not have changed it).
+ //
+
+ CcGetFileSizePointer(FileObject)->LowPart = Header->FileSize.LowPart;
+
+ FileObject->Flags |= FO_FILE_SIZE_CHANGED;
+
+ ExAcquireFastMutex( Header->FastMutex );
+ Header->ValidDataLength = NewFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ goto Done1;
+ }
+
+ //
+ // Here is the 64-bit or no-wait path.
+ //
+
+ } else {
+
+ //
+ // Prevent truncates by acquiring paging I/O
+ //
+
+ Status = ExAcquireResourceShared( Header->PagingIoResource, Wait );
+ if (!Status) {
+ goto Done2;
+ }
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped.
+ //
+
+ if ((FileOffset->QuadPart < 0) || (NewFileSize.QuadPart > Header->ValidDataLength.QuadPart)) {
+
+ //
+ // We can change FileSize and ValidDataLength if either, no one
+ // else is now, or we are still extending after waiting.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Now that we are synchronized for end of file cases,
+ // we can calculate the real offset for this transfer and
+ // the new file size (if we succeed).
+ //
+
+
+ if ((FileOffset->QuadPart < 0)) {
+ Offset = Header->FileSize;
+ }
+
+ //
+ // Now calculate the new FileSize and see if we wrapped the
+ // 32-bit boundary.
+ //
+
+ NewFileSize.QuadPart = Offset.QuadPart + Length;
+
+ //
+ // Update Filesize now so that we do not truncate reads.
+ //
+
+ OldFileSize.QuadPart = Header->FileSize.QuadPart;
+ if (NewFileSize.QuadPart > Header->FileSize.QuadPart) {
+
+ //
+ // If we are beyond AllocationSize, make sure we will
+ // ErrOut below, and don't modify FileSize now!
+ //
+
+ if (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) {
+ NewFileSize.QuadPart = (LONGLONG)0x7FFFFFFFFFFFFFFF;
+ } else {
+ Header->FileSize.QuadPart = NewFileSize.QuadPart;
+ }
+ }
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test
+ // if it is really cached and if we can do fast i/o and we
+ // do not have to extend. If not then release the fcb and
+ // return.
+ //
+ // Get out if we are about to zero too much as well, as commented above.
+ //
+
+ if ((FileObject->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible) ||
+/* Remove? */ (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) ||
+ (Offset.QuadPart >= (Header->ValidDataLength.QuadPart + 0x2000))) {
+
+ goto ErrOut;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask
+ // the file system the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ targetVdo = IoGetRelatedDeviceObject( FileObject );
+ FastIoDispatch = targetVdo->DriverObject->FastIoDispatch;
+
+ //
+ // All file system then set "Is Questionable" had better
+ // support fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the
+ // answer is anything other than GoForIt then we cannot
+ // take the fast I/O path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ &Offset,
+ Length,
+ Wait,
+ LockKey,
+ FALSE, // write operation
+ IoStatus,
+ targetVdo )) {
+
+ //
+ // Fast I/O is not possible so cleanup and return.
+ //
+
+ goto ErrOut;
+ }
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work
+ // and then release the fcb when we've done. If for whatever
+ // reason the copy write fails, then return FALSE to our
+ // caller.
+ //
+ // Also mark this as the top level "Irp" so that lower file
+ // system levels will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ try {
+
+ //
+ // See if we have to do some zeroing
+ //
+
+ if ( Offset.QuadPart > Header->ValidDataLength.QuadPart ) {
+
+ Status = CcZeroData( FileObject,
+ &Header->ValidDataLength,
+ &Offset,
+ Wait );
+ }
+
+ if (Status) {
+
+ Status = CcCopyWrite( FileObject,
+ &Offset,
+ Length,
+ Wait,
+ Buffer );
+ }
+
+ } except( FsRtlIsNtstatusExpected(GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ Status = FALSE;
+ }
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ //
+ // If we succeeded, see if we have to update FileSize ValidDataLength.
+ //
+
+ if (Status) {
+
+ //
+ // Set this handle as having modified the file and update
+ // the current file position pointer
+ //
+
+ FileObject->Flags |= FO_FILE_MODIFIED;
+ FileObject->CurrentByteOffset.QuadPart = Offset.QuadPart + Length;
+
+ if (DoingIoAtEof) {
+
+ //
+ // Make sure Cc knows the current FileSize, as set above,
+ // (we may not have changed it).
+ //
+
+ CcGetFileSizePointer(FileObject)->QuadPart = Header->FileSize.QuadPart;
+
+ ExAcquireFastMutex( Header->FastMutex );
+ FileObject->Flags |= FO_FILE_SIZE_CHANGED;
+ Header->ValidDataLength = NewFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ goto Done1;
+ }
+ }
+
+ ErrOut: NOTHING;
+
+ Status = FALSE;
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ Header->FileSize = OldFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ Done1: ExReleaseResource( Header->PagingIoResource );
+
+ Done2: FsRtlExitFileSystem();
+ }
+
+ } else {
+
+ //
+ // We could not do the I/O now.
+ //
+
+ Status = FALSE;
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsMdlReadA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached mdl read bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy read
+ of a cached file object. For a complete description of the arguments
+ see CcMdlRead.
+
+Arguments:
+
+ FileObject - Pointer to the file object being read.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ MdlChain - On output it returns a pointer to an MDL chain describing
+ the desired data.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+Return Value:
+
+ FALSE - if the data was not delivered, or if there is an I/O error.
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ BOOLEAN DoingIoAtEof = FALSE;
+ BOOLEAN Status = TRUE;
+ LARGE_INTEGER BeyondLastByte;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Special case a read of zero length
+ //
+
+ if (Length == 0) {
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = 0;
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ } else {
+
+ BeyondLastByte.QuadPart = FileOffset->QuadPart + (LONGLONG)Length;
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ *(PULONG)CcFastMdlReadWait += 1;
+
+ //
+ // Acquired shared on the common fcb header
+ //
+
+ if (Header->PagingIoResource == NULL) {
+ Status = FALSE;
+ goto Done2;
+ }
+
+ (VOID)ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we are reading beyond ValidDataLength. We have to
+ // do it now so that our reads are not nooped.
+ //
+
+ if (BeyondLastByte.QuadPart > Header->ValidDataLength.QuadPart) {
+
+ //
+ // We must serialize with anyone else doing I/O at beyond
+ // ValidDataLength, and then remember if we need to declare
+ // when we are done.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are in fact beyond ValidDataLength.
+ //
+
+ if (DoingIoAtEof) {
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test if it is
+ // really cached and if we can do fast i/o and if not
+ // then release the fcb and return.
+ //
+
+ if ((FileObject->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible)) {
+
+ Status = FALSE;
+ goto Done;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask the file system
+ // the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ PFAST_IO_DISPATCH FastIoDispatch;
+
+ ASSERT(!KeIsExecutingDpc());
+
+ FastIoDispatch = IoGetRelatedDeviceObject( FileObject )->DriverObject->FastIoDispatch;
+
+
+ //
+ // All file system then set "Is Questionable" had better support fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the answer is anything
+ // other than GoForIt then we cannot take the fast I/O path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ FileOffset,
+ Length,
+ TRUE,
+ LockKey,
+ TRUE, // read operation
+ IoStatus,
+ IoGetRelatedDeviceObject( FileObject ) )) {
+
+ //
+ // Fast I/O is not possible so release the Fcb and return.
+ //
+
+ Status = FALSE;
+ goto Done;
+ }
+ }
+
+ //
+ // Check for read past file size.
+ //
+
+ if ( BeyondLastByte.QuadPart > Header->FileSize.QuadPart ) {
+
+ if ( FileOffset->QuadPart >= Header->FileSize.QuadPart ) {
+
+ IoStatus->Status = STATUS_END_OF_FILE;
+ IoStatus->Information = 0;
+
+ goto Done;
+ }
+
+ Length = (ULONG)( Header->FileSize.QuadPart - FileOffset->QuadPart );
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work and then
+ // release the fcb when we've done. If for whatever reason the
+ // mdl read fails, then return FALSE to our caller.
+ //
+ //
+ // Also mark this as the top level "Irp" so that lower file system levels
+ // will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ try {
+
+ CcMdlRead( FileObject, FileOffset, Length, MdlChain, IoStatus );
+
+ FileObject->Flags |= FO_FILE_FAST_IO_READ;
+
+ } except( FsRtlIsNtstatusExpected(GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ Status = FALSE;
+ }
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ Done: NOTHING;
+
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+ ExReleaseResource( Header->PagingIoResource );
+
+ Done2: NOTHING;
+ FsRtlExitFileSystem();
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsPrepareMdlWriteA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached mdl read bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy read
+ of a cached file object. For a complete description of the arguments
+ see CcMdlRead.
+
+Arguments:
+
+ FileObject - Pointer to the file object being read.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ MdlChain - On output it returns a pointer to an MDL chain describing
+ the desired data.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+Return Value:
+
+ FALSE - if the data was not written, or if there is an I/O error.
+
+ TRUE - if the data is being written
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ LARGE_INTEGER Offset, NewFileSize;
+ LARGE_INTEGER OldFileSize;
+ ULONG DoingIoAtEof = FALSE;
+ BOOLEAN Status = TRUE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // Do we need to verify the volume? If so, we must go to the file
+ // system. Also return FALSE if FileObject is write through, the
+ // File System must do that.
+ //
+
+ if (CcCanIWrite( FileObject, Length, TRUE, FALSE ) &&
+ !FlagOn(FileObject->Flags, FO_WRITE_THROUGH) &&
+ CcCopyWriteWontFlush(FileObject, FileOffset, Length) &&
+ (Header->PagingIoResource != NULL)) {
+
+ //
+ // Assume our transfer will work
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+
+ //
+ // Special case the zero byte length
+ //
+
+ if (Length != 0) {
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Make our best guess on whether we need the file exclusive or
+ // shared.
+ //
+
+ NewFileSize.QuadPart = FileOffset->QuadPart + (LONGLONG)Length;
+ Offset = *FileOffset;
+
+ //
+ // Prevent truncates by acquiring paging I/O
+ //
+
+ ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped.
+ //
+
+ if ((FileOffset->QuadPart < 0) || (NewFileSize.QuadPart > Header->ValidDataLength.QuadPart)) {
+
+ //
+ // We can change FileSize and ValidDataLength if either, no one
+ // else is now, or we are still extending after waiting.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Now that we are synchronized for end of file cases,
+ // we can calculate the real offset for this transfer and
+ // the new file size (if we succeed).
+ //
+
+
+ if ((FileOffset->QuadPart < 0)) {
+ Offset = Header->FileSize;
+ }
+
+ //
+ // Now calculate the new FileSize and see if we wrapped the
+ // 32-bit boundary.
+ //
+
+ NewFileSize.QuadPart = Offset.QuadPart + Length;
+
+ //
+ // Update Filesize now so that we do not truncate reads.
+ //
+
+ OldFileSize.QuadPart = Header->FileSize.QuadPart;
+ if (NewFileSize.QuadPart > Header->FileSize.QuadPart) {
+
+ //
+ // If we are beyond AllocationSize, make sure we will
+ // ErrOut below, and don't modify FileSize now!
+ //
+
+ if (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) {
+ NewFileSize.QuadPart = (LONGLONG)0x7FFFFFFFFFFFFFFF;
+ } else {
+ Header->FileSize.QuadPart = NewFileSize.QuadPart;
+ }
+ }
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test
+ // if it is really cached and if we can do fast i/o and we
+ // do not have to extend. If not then release the fcb and
+ // return.
+ //
+ // Get out if we are about to zero too much as well, as commented above.
+ //
+
+ if ((FileObject->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible) ||
+/* Remove? */ (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) ||
+ (Offset.QuadPart >= (Header->ValidDataLength.QuadPart + 0x2000))) {
+
+ goto ErrOut;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask the file system
+ // the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ PFAST_IO_DISPATCH FastIoDispatch = IoGetRelatedDeviceObject( FileObject )->DriverObject->FastIoDispatch;
+
+ //
+ // All file system then set "Is Questionable" had better support fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the answer is anything
+ // other than GoForIt then we cannot take the fast I/O path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ &Offset,
+ Length,
+ TRUE,
+ LockKey,
+ FALSE, // write operation
+ IoStatus,
+ IoGetRelatedDeviceObject( FileObject ) )) {
+
+ //
+ // Fast I/O is not possible so release the Fcb and return.
+ //
+
+ goto ErrOut;
+ }
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work and then
+ // release the fcb when we've done. If for whatever reason the
+ // copy write fails, then return FALSE to our caller.
+ //
+ //
+ // Also mark this as the top level "Irp" so that lower file system levels
+ // will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ try {
+
+ //
+ // See if we have to do some zeroing
+ //
+
+ if ( Offset.QuadPart > Header->ValidDataLength.QuadPart ) {
+
+ Status = CcZeroData( FileObject,
+ &Header->ValidDataLength,
+ &Offset,
+ TRUE );
+ }
+
+ if (Status) {
+
+ CcPrepareMdlWrite( FileObject, &Offset, Length, MdlChain, IoStatus );
+ }
+
+ } except( FsRtlIsNtstatusExpected(GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ Status = FALSE;
+ }
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ //
+ // If we succeeded, see if we have to update FileSize ValidDataLength.
+ //
+
+ if (Status) {
+
+ //
+ // Set this handle as having modified the file
+ //
+
+ FileObject->Flags |= FO_FILE_MODIFIED;
+ IoStatus->Information = Length;
+
+ if (DoingIoAtEof) {
+
+ //
+ // Make sure Cc knows the current FileSize, as set above,
+ // (we may not have changed it).
+ //
+
+ CcGetFileSizePointer(FileObject)->QuadPart = Header->FileSize.QuadPart;
+
+ ExAcquireFastMutex( Header->FastMutex );
+ FileObject->Flags |= FO_FILE_SIZE_CHANGED;
+ Header->ValidDataLength = NewFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ goto Done1;
+ }
+
+ ErrOut: NOTHING;
+
+ Status = FALSE;
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ Header->FileSize = OldFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ Done1: ExReleaseResource( Header->PagingIoResource );
+
+ FsRtlExitFileSystem();
+ }
+
+ } else {
+
+ //
+ // We could not do the I/O now.
+ //
+
+ Status = FALSE;
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsWaitForIoAtEof (
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN OUT PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PEOF_WAIT_BLOCK EofWaitBlock
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called while synchronized for cached write, to
+ test for a possible Eof update, and return with a status if Eof is
+ being updated and with the previous FileSize to restore on error.
+ All updates to Eof are serialized by waiting in this routine. If
+ this routine returns TRUE, then NtfsFinishIoAtEof must be called.
+
+ This routine must be called while synchronized with the FsRtl header.
+
+Arguments:
+
+ Header - Pointer to the FsRtl header for the file
+
+ FileOffset - Pointer to FileOffset for the intended write
+
+ Length - Length for the intended write
+
+ EofWaitBlock - Uninitialized structure used only to serialize Eof updates
+
+Return Value:
+
+ FALSE - If the write does not extend Eof (OldFileSize not returned)
+ TRUE - If the write does extend Eof OldFileSize returned and caller
+ must eventually call NtfsFinishIoAtEof
+
+--*/
+
+{
+ PAGED_CODE();
+
+ ASSERT( Header->FileSize.QuadPart >= Header->ValidDataLength.QuadPart );
+
+ //
+ // Initialize the event and queue our block
+ //
+
+ KeInitializeEvent( &EofWaitBlock->Event, NotificationEvent, FALSE );
+ InsertTailList( Header->PendingEofAdvances, &EofWaitBlock->EofWaitLinks );
+
+ //
+ // Free the mutex and wait
+ //
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ KeWaitForSingleObject( &EofWaitBlock->Event,
+ Executive,
+ KernelMode,
+ FALSE,
+ (PLARGE_INTEGER)NULL);
+
+ //
+ // Now, resynchronize and get on with it.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now we have to check again, and actually catch the case
+ // where we are no longer extending!
+ //
+
+ if ((FileOffset->QuadPart >= 0) &&
+ ((FileOffset->QuadPart + Length) <= Header->ValidDataLength.QuadPart)) {
+
+ NtfsFinishIoAtEof( Header );
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+VOID
+NtfsFinishIoAtEof (
+ IN PFSRTL_ADVANCED_FCB_HEADER Header
+ )
+
+/*++
+
+Routine Description:
+
+ This routine must be called if NtfsWaitForIoAtEof returned
+ TRUE, or we otherwise set EOF_ADVANCE_ACTIVE.
+
+ This routine must be called while synchronized with the FsRtl header.
+
+Arguments:
+
+ Header - Pointer to the FsRtl header for the file
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PEOF_WAIT_BLOCK EofWaitBlock;
+
+ PAGED_CODE();
+
+ //
+ // If anyone is waiting, just let them go.
+ //
+
+ if (!IsListEmpty(Header->PendingEofAdvances)) {
+
+ EofWaitBlock = (PEOF_WAIT_BLOCK)RemoveHeadList( Header-> PendingEofAdvances );
+ KeSetEvent( &EofWaitBlock->Event, 0, FALSE );
+
+ //
+ // Otherwise, show there is no active extender now.
+ //
+
+ } else {
+ ClearFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+ }
+}
diff --git a/private/ntos/cntfs/index.h b/private/ntos/cntfs/index.h
new file mode 100644
index 000000000..07cba35b4
--- /dev/null
+++ b/private/ntos/cntfs/index.h
@@ -0,0 +1,159 @@
+/*++
+
+Copyright (c) 1996 Microsoft Corporation
+
+Module Name:
+
+ Index.h
+
+Abstract:
+
+ This module contains definitions common to only indexsup.c and viewsup.c
+
+Author:
+
+ Tom Miller [TomM] 8-Jan-1996
+
+Revision History:
+
+--*/
+
+//
+// Define all private support routines. Documentation of routine interface
+// is with the routine itself.
+//
+
+VOID
+NtfsGrowLookupStack (
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ IN PINDEX_LOOKUP_STACK *Sp
+ );
+
+BOOLEAN
+ReadIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG IndexBlock,
+ IN BOOLEAN Reread,
+ OUT PINDEX_LOOKUP_STACK Sp
+ );
+
+PINDEX_ALLOCATION_BUFFER
+GetIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ OUT PINDEX_LOOKUP_STACK Sp,
+ OUT PLONGLONG EndOfValidData
+ );
+
+VOID
+DeleteIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ );
+
+VOID
+FindFirstIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+BOOLEAN
+FindNextIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN ValueContainsWildcards,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ IN BOOLEAN NextFlag,
+ OUT PBOOLEAN MustRestart OPTIONAL
+ );
+
+PATTRIBUTE_RECORD_HEADER
+FindMoveableIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+PINDEX_ENTRY
+BinarySearchIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_LOOKUP_STACK Sp,
+ IN PVOID Value
+ );
+
+BOOLEAN
+AddToIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ );
+
+VOID
+InsertSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+PushIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+InsertSimpleAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN PINDEX_LOOKUP_STACK Sp,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ );
+
+PINDEX_ENTRY
+InsertWithBufferSplit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ );
+
+VOID
+DeleteFromIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+DeleteSimple (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+PruneIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PINDEX_ENTRY *DeleteEntry
+ );
+
diff --git a/private/ntos/cntfs/indexsup.c b/private/ntos/cntfs/indexsup.c
new file mode 100644
index 000000000..a7a75f961
--- /dev/null
+++ b/private/ntos/cntfs/indexsup.c
@@ -0,0 +1,6765 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ IndexSup.c
+
+Abstract:
+
+ This module implements the Index management routines for Ntfs
+
+Author:
+
+ Tom Miller [TomM] 14-Jul-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+#include "Index.h"
+
+//
+// This constant affects the logic in NtfsRetrieveOtherFileName.
+// If an index is greater than this size, then we retrieve the other
+// name by reading the file record. The number is arbitrary, but the
+// below value should normally kick in for directories of around 150
+// to 200 files, or fewer if the names are quite large.
+//
+
+#define MAX_INDEX_TO_SCAN_FOR_NAMES (0x10000)
+
+#if DBG
+BOOLEAN NtfsIndexChecks = TRUE;
+#endif
+
+#if DBG
+
+#define CheckRoot() { \
+if (NtfsIndexChecks) { \
+ NtfsCheckIndexRoot(IrpContext, \
+ Scb->Vcb, \
+ (PINDEX_ROOT)NtfsAttributeValue(Attribute), \
+ Attribute->Form.Resident.ValueLength); \
+ } \
+}
+
+#define CheckBuffer(IB) { \
+if (NtfsIndexChecks) { \
+ NtfsCheckIndexBuffer(IrpContext, \
+ Scb, \
+ (IB)); \
+ } \
+}
+
+#else
+
+#define CheckRoot() {NOTHING;}
+#define CheckBuffer(IB) {NOTHING;}
+
+#endif
+
+#ifdef _CAIRO_
+#define BINARY_SEARCH_ENTRIES (128)
+#else
+#define BINARY_SEARCH_ENTRIES (50)
+#endif
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_INDEXSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('IFtN')
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsReinitializeIndexContext)
+#pragma alloc_text(PAGE, NtfsGrowLookupStack)
+#pragma alloc_text(PAGE, AddToIndex)
+#pragma alloc_text(PAGE, BinarySearchIndex)
+#pragma alloc_text(PAGE, DeleteFromIndex)
+#pragma alloc_text(PAGE, DeleteIndexBuffer)
+#pragma alloc_text(PAGE, DeleteSimple)
+#pragma alloc_text(PAGE, FindFirstIndexEntry)
+#pragma alloc_text(PAGE, FindMoveableIndexRoot)
+#pragma alloc_text(PAGE, FindNextIndexEntry)
+#pragma alloc_text(PAGE, GetIndexBuffer)
+#pragma alloc_text(PAGE, InsertSimpleAllocation)
+#pragma alloc_text(PAGE, InsertSimpleRoot)
+#pragma alloc_text(PAGE, InsertWithBufferSplit)
+#pragma alloc_text(PAGE, NtfsAddIndexEntry)
+#pragma alloc_text(PAGE, NtfsCleanupAfterEnumeration)
+#pragma alloc_text(PAGE, NtfsCleanupIndexContext)
+#pragma alloc_text(PAGE, NtfsContinueIndexEnumeration)
+#pragma alloc_text(PAGE, NtfsCreateIndex)
+#pragma alloc_text(PAGE, NtfsDeleteIndex)
+#pragma alloc_text(PAGE, NtfsDeleteIndexEntry)
+#pragma alloc_text(PAGE, NtfsFindIndexEntry)
+#pragma alloc_text(PAGE, NtfsInitializeIndexContext)
+#pragma alloc_text(PAGE, NtfsIsIndexEmpty)
+#pragma alloc_text(PAGE, NtfsPushIndexRoot)
+#pragma alloc_text(PAGE, NtfsRestartDeleteSimpleAllocation)
+#pragma alloc_text(PAGE, NtfsRestartDeleteSimpleRoot)
+#pragma alloc_text(PAGE, NtfsRestartIndexEnumeration)
+#pragma alloc_text(PAGE, NtfsRestartInsertSimpleAllocation)
+#pragma alloc_text(PAGE, NtfsRestartInsertSimpleRoot)
+#pragma alloc_text(PAGE, NtfsRestartSetIndexBlock)
+#pragma alloc_text(PAGE, NtfsRestartUpdateFileName)
+#pragma alloc_text(PAGE, NtfsRestartWriteEndOfIndex)
+#pragma alloc_text(PAGE, NtfsRetrieveOtherFileName)
+#pragma alloc_text(PAGE, NtfsUpdateFileNameInIndex)
+#pragma alloc_text(PAGE, NtfsUpdateIndexScbFromAttribute)
+#pragma alloc_text(PAGE, PruneIndex)
+#pragma alloc_text(PAGE, PushIndexRoot)
+#pragma alloc_text(PAGE, ReadIndexBuffer)
+#pragma alloc_text(PAGE, NtOfsRestartUpdateDataInIndex)
+#endif
+
+
+VOID
+NtfsCreateIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE IndexedAttributeType,
+ IN COLLATION_RULE CollationRule,
+ IN ULONG BytesPerIndexBuffer,
+ IN UCHAR BlocksPerIndexBuffer,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN NewIndex,
+ IN BOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to create (or reinitialize) an index
+ within a given file over a given attribute. For example, to create
+ a normal directory, an index over the FILE_NAME attribute is created
+ within the desired (directory) file.
+
+Arguments:
+
+ Fcb - File in which the index is to be created.
+
+ IndexedAttributeType - Type code of attribute to be indexed.
+
+ CollationRule - Collation Rule for this index.
+
+ BytesPerIndexBuffer - Number of bytes in an index buffer.
+
+ BlocksPerIndexBuffer - Number of contiguous blocks to allocate for each
+ index buffer allocated from the index allocation.
+
+ Context - If reinitializing an existing index, this context must
+ currently describe the INDEX_ROOT attribute. Must be
+ supplied if NewIndex is FALSE.
+
+ NewIndex - Supplied as FALSE to reinitialize an existing index, or
+ TRUE if creating a new index.
+
+ LogIt - May be supplied as FALSE by Create or Cleanup when already
+ logging the creation or deletion of an entire file record.
+ Otherwise must be specified as TRUE to allow logging.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ UNICODE_STRING AttributeName;
+ WCHAR NameBuffer[10];
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ ULONG idx;
+
+ struct {
+ INDEX_ROOT IndexRoot;
+ INDEX_ENTRY EndEntry;
+ } R;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT( NewIndex || ARGUMENT_PRESENT(Context) );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCreateIndex\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("CollationRule = %08lx\n", CollationRule) );
+ DebugTrace( 0, Dbg, ("BytesPerIndexBuffer = %08lx\n", BytesPerIndexBuffer) );
+ DebugTrace( 0, Dbg, ("BlocksPerIndexBuffer = %08lx\n", BlocksPerIndexBuffer) );
+ DebugTrace( 0, Dbg, ("Context = %08lx\n", Context) );
+ DebugTrace( 0, Dbg, ("NewIndex = %02lx\n", NewIndex) );
+ DebugTrace( 0, Dbg, ("LogIt = %02lx\n", LogIt) );
+
+ //
+ // First we will initialize the Index Root structure which is the value
+ // of the attribute we need to create. We initialize it with 0 free bytes,
+ // which means the first insert will have to expand the record
+ //
+
+ RtlZeroMemory( &R, sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY) );
+
+ R.IndexRoot.IndexedAttributeType = IndexedAttributeType;
+ R.IndexRoot.CollationRule = CollationRule;
+ R.IndexRoot.BytesPerIndexBuffer = BytesPerIndexBuffer;
+ R.IndexRoot.BlocksPerIndexBuffer = BlocksPerIndexBuffer;
+
+ R.IndexRoot.IndexHeader.FirstIndexEntry = QuadAlign(sizeof(INDEX_HEADER));
+ R.IndexRoot.IndexHeader.FirstFreeByte =
+ R.IndexRoot.IndexHeader.BytesAvailable = QuadAlign(sizeof(INDEX_HEADER)) +
+ QuadAlign(sizeof(INDEX_ENTRY));
+
+ //
+ // Now we need to put in the special End entry.
+ //
+
+ R.EndEntry.Length = sizeof(INDEX_ENTRY);
+ SetFlag( R.EndEntry.Flags, INDEX_ENTRY_END );
+
+ //
+ // Now calculate the name which will be used to name the Index Root and
+ // Index Allocation attributes for this index. It is $Ixxx, where "xxx"
+ // is the attribute number being indexed in hex with leading 0's suppressed.
+ //
+
+ if (NewIndex) {
+
+ //
+ // First, there are some illegal values for the attribute code being indexed.
+ //
+
+ ASSERT( IndexedAttributeType < 0x10000000 );
+ ASSERT( IndexedAttributeType != $UNUSED );
+
+ //
+ // Initialize the attribute name.
+ //
+
+ NameBuffer[0] = (WCHAR)'$';
+ NameBuffer[1] = (WCHAR)'I';
+ idx = 2;
+
+ //
+ // Now shift a "marker" into the low order nibble, so we know when to stop
+ // shifting below.
+ //
+
+ IndexedAttributeType = (IndexedAttributeType << 4) + 0xF;
+
+ //
+ // Outer loop strips leading 0's
+ //
+
+ while (TRUE) {
+
+ if ((IndexedAttributeType & 0xF0000000) == 0) {
+ IndexedAttributeType <<= 4;
+ } else {
+
+ //
+ // The inner loop forms the name until the marker is in the high
+ // nibble.
+ //
+
+ while (IndexedAttributeType != 0xF0000000) {
+ NameBuffer[idx] = (WCHAR)(IndexedAttributeType / 0x10000000 + '0');
+ idx += 1;
+ IndexedAttributeType <<= 4;
+ }
+ NameBuffer[idx] = UNICODE_NULL;
+ break;
+ }
+ }
+
+ RtlInitUnicodeString( &AttributeName, NameBuffer );
+
+ //
+ // Now, just create the Index Root Attribute.
+ //
+
+ Context = &LocalContext;
+ NtfsInitializeAttributeContext( Context );
+ }
+
+ try {
+
+ if (NewIndex) {
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $INDEX_ROOT,
+ &AttributeName,
+ &R,
+ sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY),
+ AttributeFlags,
+ NULL,
+ LogIt,
+ Context );
+ } else {
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0,
+ &R,
+ sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY),
+ TRUE,
+ FALSE,
+ FALSE,
+ TRUE,
+ Context );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateIndex );
+
+ if (NewIndex) {
+ NtfsCleanupAttributeContext( Context );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateIndex -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsUpdateIndexScbFromAttribute (
+ IN PSCB Scb,
+ IN PATTRIBUTE_RECORD_HEADER IndexRootAttr
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called when an Index Scb needs initialization. Typically
+ once in the life of the Scb. It will update the Scb out of the $INDEX_ROOT
+ attribute.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexRootAttr - Supplies the $INDEX_ROOT attribute.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_ROOT IndexRoot = (PINDEX_ROOT) NtfsAttributeValue( IndexRootAttr );
+ PAGED_CODE();
+
+ //
+ // Update the Scb out of the attribute.
+ //
+
+ if (FlagOn(IndexRootAttr->Flags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+
+ SetFlag(Scb->ScbState, SCB_STATE_COMPRESSED );
+ Scb->AttributeFlags |= IndexRootAttr->Flags & ATTRIBUTE_FLAG_COMPRESSION_MASK;
+
+ } else {
+
+ ClearFlag(Scb->ScbState, SCB_STATE_COMPRESSED );
+ }
+
+ //
+ // Capture the values out of the attribute. Note that we load the
+ // BytesPerIndexBuffer last as a flag to indicate that the Scb is
+ // loaded.
+ //
+
+#ifdef _CAIRO_
+ if (!FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX)) {
+#endif
+ Scb->ScbType.Index.AttributeBeingIndexed = IndexRoot->IndexedAttributeType;
+ Scb->ScbType.Index.CollationRule = IndexRoot->CollationRule;
+#ifdef _CAIRO_
+ }
+#endif
+
+ Scb->ScbType.Index.BlocksPerIndexBuffer = IndexRoot->BlocksPerIndexBuffer;
+
+ //
+ // Compute the shift count for this index.
+ //
+
+ if (IndexRoot->BytesPerIndexBuffer >= Scb->Vcb->BytesPerCluster) {
+
+ Scb->ScbType.Index.IndexBlockByteShift = (UCHAR) Scb->Vcb->ClusterShift;
+
+ } else {
+
+ Scb->ScbType.Index.IndexBlockByteShift = DEFAULT_INDEX_BLOCK_BYTE_SHIFT;
+ }
+
+ Scb->ScbType.Index.BytesPerIndexBuffer = IndexRoot->BytesPerIndexBuffer;
+
+ return;
+}
+
+
+BOOLEAN
+NtfsFindIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN IgnoreCase,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PINDEX_ENTRY *IndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to look up a given value in a given index
+ and return the file reference of the indexed file record.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Supplies a pointer to the value to lookup.
+
+ IgnoreCase - For indices with collation rules where character case
+ may be relevant, supplies whether character case is
+ to be ignored. For example, if supplied as TRUE, then
+ 'T' and 't' are treated as equivalent.
+
+ QuickIndex - If specified, supplies a pointer to a quick lookup structure
+ to be updated by this routine.
+
+ Bcb - Returns a Bcb pointer which must be unpinned by the caller
+
+ IndexEntry - Returns a pointer to the actual Index Entry, valid until
+ the Bcb is unpinned.
+
+Return Value:
+
+ FALSE - if no match was found.
+ TRUE - if a match was found and being returned in FileReference.
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ BOOLEAN Result = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFindIndexEntry\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ try {
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ Value,
+ &IndexContext );
+
+ //
+ // If this operation is case insensitive we upcase the attribute
+ // value.
+ //
+
+ if (IgnoreCase) {
+
+ (*NtfsUpcaseValue[Scb->ScbType.Index.CollationRule])
+ ( IrpContext->Vcb->UpcaseTable,
+ IrpContext->Vcb->UpcaseTableSize,
+ Value );
+ }
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ Value,
+ FALSE,
+ IgnoreCase,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ //
+ // Return our outputs, clearing the Bcb so it won't get
+ // unpinned.
+ //
+
+ *IndexEntry = IndexContext.Current->IndexEntry;
+
+ //
+ // Now return the correct Bcb.
+ //
+
+ if (IndexContext.Current == IndexContext.Base) {
+
+ *Bcb = NtfsFoundBcb(&IndexContext.AttributeContext);
+ NtfsFoundBcb(&IndexContext.AttributeContext) = NULL;
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->BufferOffset = 0;
+ }
+
+ } else {
+
+ PINDEX_LOOKUP_STACK Sp = IndexContext.Current;
+
+ *Bcb = Sp->Bcb;
+ Sp->Bcb = NULL;
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->ChangeCount = Scb->ScbType.Index.ChangeCount;
+ QuickIndex->BufferOffset = PtrOffset( Sp->StartOfBuffer, Sp->IndexEntry );
+ QuickIndex->CapturedLsn = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->Lsn;
+ QuickIndex->IndexBlock = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->ThisBlock;
+ }
+ }
+
+ try_return(Result = TRUE);
+
+ } else {
+
+ try_return(Result = FALSE);
+ }
+
+ try_exit: NOTHING;
+
+ } finally{
+
+ DebugUnwind( NtfsFindIndexEntry );
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ }
+
+ DebugTrace( 0, Dbg, ("Bcb < %08lx\n", *Bcb) );
+ DebugTrace( 0, Dbg, ("IndexEntry < %08lx\n", *IndexEntry) );
+ DebugTrace( -1, Dbg, ("NtfsFindIndexEntry -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+VOID
+NtfsUpdateFileNameInIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFILE_NAME FileName,
+ IN PDUPLICATED_INFORMATION Info,
+ IN OUT PQUICK_INDEX QuickIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to look up a given value in a given index
+ and pin it for modification.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ FileName - Supplies a pointer to the file name to lookup.
+
+ Info - Supplies a pointer to the information for the update
+
+ QuickIndex - If present, this is the fast lookup information for this index entry.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PINDEX_ENTRY IndexEntry;
+ PFILE_NAME FileNameInIndex;
+ PVCB Vcb = Scb->Vcb;
+ PINDEX_LOOKUP_STACK Sp;
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateFileNameInIndex\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("FileName = %08lx\n", FileName) );
+ DebugTrace( 0, Dbg, ("Info = %08lx\n", Info) );
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ try {
+
+ //
+ // If the index entry for this filename hasn't moved we can go
+ // directly to the location in the buffer. For this to be the case the
+ // following must be true.
+ //
+ // - The entry must already be in an index buffer
+ // - The index stream may not have been truncated
+ // - The Lsn in the page can't have changed
+ //
+
+ if (ARGUMENT_PRESENT( QuickIndex ) &&
+ QuickIndex->BufferOffset != 0 &&
+ QuickIndex->ChangeCount == Scb->ScbType.Index.ChangeCount) {
+
+ //
+ // Use the top location in the index context to perform the
+ // read.
+ //
+
+ Sp = IndexContext.Base;
+
+ ReadIndexBuffer( IrpContext,
+ Scb,
+ QuickIndex->IndexBlock,
+ FALSE,
+ Sp );
+
+ //
+ // If the Lsn matches then we can use this buffer directly.
+ //
+
+ if (QuickIndex->CapturedLsn.QuadPart == Sp->CapturedLsn.QuadPart) {
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer;
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( Sp->StartOfBuffer,
+ QuickIndex->BufferOffset );
+
+ FileNameInIndex = (PFILE_NAME)(IndexEntry + 1);
+
+ //
+ // Pin the index buffer
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ //
+ // Write the log record, but do not update the IndexBuffer Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // Indexbuffer->Lsn =
+ //
+
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ UpdateFileNameAllocation,
+ Info,
+ sizeof(DUPLICATED_INFORMATION),
+ UpdateFileNameAllocation,
+ &FileNameInIndex->Info,
+ sizeof(DUPLICATED_INFORMATION),
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)IndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtfsRestartUpdateFileName( IndexEntry,
+ Info );
+
+ try_return( NOTHING );
+
+ //
+ // Otherwise we need to reinitialize the index context and take
+ // the long path below.
+ //
+
+ } else {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ NtfsInitializeIndexContext( &IndexContext );
+ }
+ }
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ (PVOID)FileName,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ (PVOID)FileName,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ //
+ // Point to the index entry and the file name within it.
+ //
+
+ IndexEntry = IndexContext.Current->IndexEntry;
+ FileNameInIndex = (PFILE_NAME)(IndexEntry + 1);
+
+ //
+ // Now pin the entry.
+ //
+
+ if (IndexContext.Current == IndexContext.Base) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_ENUMERATION_CONTEXT Context = &IndexContext.AttributeContext;
+
+ //
+ // Pin the root
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Vcb,
+ Context );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ Attribute = NtfsFoundAttribute(Context);
+
+ //
+ // Write the log record, but do not update the FileRecord Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // FileRecord->Lsn =
+ //
+
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ UpdateFileNameRoot,
+ Info,
+ sizeof(DUPLICATED_INFORMATION),
+ UpdateFileNameRoot,
+ &FileNameInIndex->Info,
+ sizeof(DUPLICATED_INFORMATION),
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)IndexEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->BufferOffset = 0;
+ }
+
+ } else {
+
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ Sp = IndexContext.Current;
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ //
+ // Pin the index buffer
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ //
+ // Write the log record, but do not update the IndexBuffer Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // Indexbuffer->Lsn =
+ //
+
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ UpdateFileNameAllocation,
+ Info,
+ sizeof(DUPLICATED_INFORMATION),
+ UpdateFileNameAllocation,
+ &FileNameInIndex->Info,
+ sizeof(DUPLICATED_INFORMATION),
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)Sp->IndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->ChangeCount = Scb->ScbType.Index.ChangeCount;
+ QuickIndex->BufferOffset = PtrOffset( Sp->StartOfBuffer, Sp->IndexEntry );
+ QuickIndex->CapturedLsn = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->Lsn;
+ QuickIndex->IndexBlock = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->ThisBlock;
+ }
+ }
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtfsRestartUpdateFileName( IndexEntry,
+ Info );
+
+ //
+ // If the file name is not in the index, this is a bad file.
+ //
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ try_exit: NOTHING;
+ } finally{
+
+ DebugUnwind( NtfsUpdateFileNameInIndex );
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateFileNameInIndex -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsAddIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN ULONG ValueLength,
+ IN PFILE_REFERENCE FileReference,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to add an entry to an index. This routine
+ always allows duplicates. If duplicates are not allowed, it is the
+ caller's responsibility to detect and eliminate any duplicate before
+ calling this routine.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Supplies a pointer to the value to add to the index
+
+ ValueLength - Supplies the length of the value in bytes.
+
+ FileReference - Supplies the file reference to place with the index entry.
+
+ QuickIndex - If specified we store the location of the index added.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ struct {
+ INDEX_ENTRY IndexEntry;
+ PVOID Value;
+#ifdef _CAIRO_
+ PVOID MustBeNull;
+#endif _CAIRO_
+ } IE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_EXCLUSIVE_SCB( Scb );
+ ASSERT( (Scb->ScbType.Index.CollationRule != COLLATION_FILE_NAME) ||
+ ( *(PLONGLONG)&((PFILE_NAME)Value)->ParentDirectory ==
+ *(PLONGLONG)&Scb->Fcb->FileReference ) );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAddIndexEntry\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("ValueLength = %08lx\n", ValueLength) );
+ DebugTrace( 0, Dbg, ("FileReference = %08lx\n", FileReference) );
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ try {
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ Value,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ Value,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ ASSERTMSG( "NtfsAddIndexEntry already exists", FALSE );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Initialize the Index Entry in pointer form.
+ //
+
+ IE.IndexEntry.FileReference = *FileReference;
+ IE.IndexEntry.Length = (USHORT)(sizeof(INDEX_ENTRY) + QuadAlign(ValueLength));
+ IE.IndexEntry.AttributeLength = (USHORT)ValueLength;
+ IE.IndexEntry.Flags = INDEX_ENTRY_POINTER_FORM;
+ IE.IndexEntry.Reserved = 0;
+ IE.Value = Value;
+#ifdef _CAIRO_
+ IE.MustBeNull = NULL;
+#endif _CAIRO_
+
+ //
+ // Now add it to the index. We can only add to a leaf, so force our
+ // position back to the correct spot in a leaf first.
+ //
+
+ IndexContext.Current = IndexContext.Top;
+ AddToIndex( IrpContext, Scb, (PINDEX_ENTRY)&IE, &IndexContext, QuickIndex );
+
+ } finally{
+
+ DebugUnwind( NtfsAddIndexEntry );
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsAddIndexEntry -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsDeleteIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN PFILE_REFERENCE FileReference
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete a specified index entry. The
+ first entry is removed which matches the value exactly (including in Case,
+ if relevant) and the file reference.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Supplies a pointer to the value to delete from the index.
+
+ FileReference - Supplies the file reference of the index entry.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PINDEX_ENTRY IndexEntry;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_EXCLUSIVE_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteIndexEntry\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("FileReference = %08lx\n", FileReference) );
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ try {
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ Value,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (!FindNextIndexEntry( IrpContext,
+ Scb,
+ Value,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ ASSERTMSG( "NtfsDeleteIndexEntry does not exist", FALSE );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Extract the found index entry pointer.
+ //
+
+ IndexEntry = IndexContext.Current->IndexEntry;
+
+ //
+ // If the file reference also matches, then this is the one we
+ // are supposed to delete.
+ //
+
+ if (!NtfsEqualMftRef(&IndexEntry->FileReference, FileReference)) {
+
+ ASSERTMSG( "NtfsDeleteIndexEntry unexpected file reference", FALSE );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ DeleteFromIndex( IrpContext, Scb, &IndexContext );
+
+ } finally{
+
+ DebugUnwind( NtfsDeleteIndexEntry );
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteIndexEntry -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsPushIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to "push" the index root, i.e., add another
+ level to the BTree, to make more room in the file record.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PINDEX_LOOKUP_STACK Sp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_EXCLUSIVE_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPushIndexRoot\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ try {
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ NULL,
+ &IndexContext );
+
+ //
+ // See if the stack will have to be grown to do the push
+ //
+
+ Sp = IndexContext.Top + 1;
+
+ if (Sp >= IndexContext.Base + (ULONG)IndexContext.NumberEntries) {
+ NtfsGrowLookupStack( Scb,
+ &IndexContext,
+ &Sp );
+ }
+
+ PushIndexRoot( IrpContext, Scb, &IndexContext );
+
+ } finally{
+
+ DebugUnwind( NtfsPushIndexRoot );
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsPushIndexRoot -> VOID\n") );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsRestartIndexEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN NextFlag,
+ OUT PINDEX_ENTRY *IndexEntry,
+ IN PFCB AcquiredFcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to start or restart an index enumeration,
+ according to the parameters as described below. The first matching
+ entry, if any, is returned by this call. Subsequent entries, if any,
+ may be returned by subsequent calls to NtfsContinueIndexEnumeration.
+
+ For each entry found, a pointer is returned to a copy of the entry, in
+ dynamically allocated pool pointed to by the Ccb. Therefore, there is
+ nothing for the caller to unpin.
+
+ Note that the Value, ValueLength, and IgnoreCase parameters on the first
+ call for a given Ccb fix what will be returned for this Ccb forever. A
+ subsequent call to this routine may also specify these parameters, but
+ in this case these parameters will be used for positioning only; all
+ matches returned will continue to match the value and IgnoreCase flag
+ specified on the first call for the Ccb.
+
+ Note that all calls to this routine must be from within a try-finally,
+ and the finally clause must include a call to NtfsCleanupAfterEnumeration.
+
+Arguments:
+
+ Ccb - Pointer to the Ccb for this enumeration.
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Pointer to the value containing the pattern which is to match
+ all returns for enumerations on this Ccb.
+
+ IgnoreCase - If FALSE, all returns will match the pattern value with
+ exact case (if relevant). If TRUE, all returns will match
+ the pattern value ignoring case. On a second or subsequent
+ call for a Ccb, this flag may be specified differently just
+ for positioning. For example, an IgnoreCase TRUE enumeration
+ may be restarted at a previously returned value found by exact
+ case match.
+
+ NextFlag - FALSE if the first match of the enumeration is to be returned.
+ TRUE if the next match after the first one is to be returned.
+
+ IndexEntry - Returns a pointer to a copy of the index entry.
+
+ AcquiredFcb - Supplies a pointer to an Fcb which has been preacquired to
+ potentially aide NtfsRetrieveOtherFileName
+
+Return Value:
+
+ FALSE - If no match is being returned, and the output pointer is undefined.
+ TRUE - If a match is being returned.
+
+--*/
+
+{
+ PINDEX_ENTRY FoundIndexEntry;
+ INDEX_CONTEXT OtherContext;
+ BOOLEAN WildCardsInExpression;
+ BOOLEAN SynchronizationError;
+ PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable;
+ PINDEX_CONTEXT IndexContext = NULL;
+ BOOLEAN CleanupOtherContext = FALSE;
+ BOOLEAN Result = FALSE;
+ BOOLEAN ContextJustCreated = FALSE;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_CCB( Ccb );
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_SCB( Scb );
+ ASSERT( ARGUMENT_PRESENT(Value) || (Ccb->IndexContext != NULL) );
+
+ DebugTrace( +1, Dbg, ("NtfsRestartIndexEnumeration\n") );
+ DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+ DebugTrace( 0, Dbg, ("NextFlag = %02lx\n", NextFlag) );
+
+ try {
+
+ //
+ // If the Ccb does not yet have an index context, then we must
+ // allocate one and initialize this Context and the Ccb as well.
+ //
+
+ if (Ccb->IndexContext == NULL) {
+
+ //
+ // Allocate and initialize the index context.
+ //
+
+ Ccb->IndexContext = (PINDEX_CONTEXT)ExAllocateFromPagedLookasideList( &NtfsIndexContextLookasideList );
+
+ NtfsInitializeIndexContext( Ccb->IndexContext );
+ ContextJustCreated = TRUE;
+
+ //
+ // Capture the caller's IgnoreCase flag.
+ //
+
+ if (IgnoreCase) {
+ SetFlag( Ccb->Flags, CCB_FLAG_IGNORE_CASE );
+ }
+
+ }
+
+ //
+ // Pick up the pointer to the index context, and save the current
+ // change count from the Scb.
+ //
+
+ IndexContext = Ccb->IndexContext;
+
+ //
+ // The first step of enumeration is to position our IndexContext.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ Value,
+ IndexContext );
+
+ //
+ // Remember if there are wild cards.
+ //
+
+ if ((*NtfsContainsWildcards[Scb->ScbType.Index.CollationRule])
+ ( Value )) {
+
+ WildCardsInExpression = TRUE;
+
+ } else {
+
+ WildCardsInExpression = FALSE;
+ }
+
+ //
+ // If the operation is caseinsensitive, upcase the string.
+ //
+
+ if (IgnoreCase) {
+
+ (*NtfsUpcaseValue[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ IrpContext->Vcb->UpcaseTableSize,
+ Value );
+ }
+
+ //
+ // If this is not actually the first call, then we have to
+ // position exactly to the Value specified, and set NextFlag
+ // correctly.
+ //
+
+ if (!ContextJustCreated) {
+
+ PIS_IN_EXPRESSION MatchRoutine;
+ PFILE_NAME NameInIndex;
+ BOOLEAN ItsThere;
+
+ //
+ // See if the specified value is actually there, because
+ // we are not allowed to resume from a Dos-only name.
+ //
+
+ ItsThere = FindNextIndexEntry( IrpContext,
+ Scb,
+ Value,
+ WildCardsInExpression,
+ IgnoreCase,
+ IndexContext,
+ FALSE,
+ NULL );
+
+ //
+ // We will set up pointers from our returns, but we must
+ // be careful only to use them if we found something.
+ //
+
+ FoundIndexEntry = IndexContext->Current->IndexEntry;
+ NameInIndex = (PFILE_NAME)(FoundIndexEntry + 1);
+
+ //
+ // Figure out which match routine to use.
+ //
+
+ if (FlagOn(Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION)) {
+ MatchRoutine = NtfsIsInExpression[COLLATION_FILE_NAME];
+ } else {
+ MatchRoutine = (PIS_IN_EXPRESSION)NtfsIsEqual[COLLATION_FILE_NAME];
+ }
+
+ //
+ // If we are trying to resume from a Ntfs-only or Dos-Only name, then
+ // we take action here. Do not do this on the internal
+ // call from NtfsContinueIndexEnumeration, which is the
+ // only one who would point at the index entry in the Ccb.
+ //
+ // We can think of this code this way. No matter what our search
+ // expression is, we traverse the index only one way. For each
+ // name we find, we will only return the file name once - either
+ // from an Ntfs-only match or from a Dos-only match if the Ntfs-only
+ // name does not match. Regardless of whether resuming from the
+ // Ntfs-Only or Dos-only name, we still can determine a unique
+ // position in the directory. That unique position is the Ntfs-only
+ // name if it matches the expression, or else the Dos-only name if
+ // it only matches. In the illegal case that neither matches, we
+ // arbitrarily resume from the Ntfs-only name.
+ //
+ // This code may be read aloud to the tune
+ // "While My Heart Gently Weeps"
+ //
+
+ if (ItsThere &&
+ (Value != (PVOID)(Ccb->IndexEntry + 1)) &&
+ (Scb->ScbType.Index.CollationRule == COLLATION_FILE_NAME) &&
+
+ //
+ // Is it a Dos-only or Ntfs-only name?
+ //
+
+ (BooleanFlagOn( NameInIndex->Flags, FILE_NAME_DOS ) !=
+ BooleanFlagOn( NameInIndex->Flags, FILE_NAME_NTFS )) &&
+
+ //
+ // Try to resume from the other name if he either gave
+ // us a Dos-only name, or he gave us an Ntfs-only name
+ // that does not fit in the search expression.
+ //
+
+ (FlagOn( NameInIndex->Flags, FILE_NAME_DOS ) ||
+ !(*MatchRoutine)( UpcaseTable,
+ Ccb->QueryBuffer,
+ FoundIndexEntry,
+ IgnoreCase ))) {
+
+ PFILE_NAME FileNameBuffer;
+ ULONG FileNameLength;
+
+ NtfsInitializeIndexContext( &OtherContext );
+ CleanupOtherContext = TRUE;
+
+ FileNameBuffer = NtfsRetrieveOtherFileName( IrpContext,
+ Ccb,
+ Scb,
+ FoundIndexEntry,
+ &OtherContext,
+ AcquiredFcb,
+ &SynchronizationError );
+
+ //
+ // We have to position to the long name and actually
+ // resume from there. To do this we have to cleanup and initialize
+ // the IndexContext in the Ccb, and lookup the long name we just
+ // found.
+ //
+ // If the other index entry is not there, there is some minor
+ // corruption going on, but we will just charge on in that event.
+ // Also, if the other index entry is there, but it does not match
+ // our expression, then we are supposed to resume from the short
+ // name, so we carry on.
+ //
+
+ ItsThere = (FileNameBuffer != NULL);
+
+ if (!ItsThere && SynchronizationError) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ if (ItsThere &&
+
+ (FlagOn(Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION) ?
+
+ NtfsFileNameIsInExpression(UpcaseTable,
+ (PFILE_NAME)Ccb->QueryBuffer,
+ FileNameBuffer,
+ IgnoreCase) :
+
+
+
+ NtfsFileNameIsEqual(UpcaseTable,
+ (PFILE_NAME)Ccb->QueryBuffer,
+ FileNameBuffer,
+ IgnoreCase))) {
+
+ ULONG SizeOfFileName = FIELD_OFFSET( FILE_NAME, FileName );
+
+ NtfsReinitializeIndexContext( IrpContext, IndexContext );
+
+ //
+ // Extract a description of the file name from the found index
+ // entry.
+ //
+
+ FileNameLength = FileNameBuffer->FileNameLength * 2;
+
+ //
+ // Call FindFirst/FindNext to position our context to the corresponding
+ // long name.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ (PVOID)FileNameBuffer,
+ IndexContext );
+
+ ItsThere = FindNextIndexEntry( IrpContext,
+ Scb,
+ (PVOID)FileNameBuffer,
+ FALSE,
+ FALSE,
+ IndexContext,
+ FALSE,
+ NULL );
+
+ if (!ItsThere) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ }
+ }
+
+ //
+ // NextFlag should only remain TRUE, if the specified value
+ // is actually there, and NextFlag was specified as TRUE
+ // on input. In particular, it is important to make
+ // NextFlag FALSE if the specified value is not actually
+ // there. (Experience shows this behavior is important to
+ // implement "delnode" correctly for the Lan Manager Server!)
+ //
+
+ NextFlag = (BOOLEAN)(NextFlag && ItsThere);
+
+ //
+ // If we created the context then we need to remember if the
+ // expression has wildcards.
+ //
+
+ } else {
+
+ //
+ // We may not handle correctly an initial enumeration with
+ // NextFlag TRUE, because the context is initially sitting
+ // in the root. Dirctrl must always pass NextFlag FALSE
+ // on the initial enumeration.
+ //
+
+ ASSERT(!NextFlag);
+
+ //
+ // Remember if the value has wild cards.
+ //
+
+ if (WildCardsInExpression) {
+
+ SetFlag( Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION );
+ }
+ }
+
+ //
+ // Now we are correctly positioned. See if there is an actual
+ // match at our current position. If not, return FALSE.
+ //
+ // (Note, FindFirstIndexEntry always leaves us positioned in
+ // some leaf of the index, and it is the first FindNext that
+ // actually positions us to the first match.)
+ //
+
+ if (!FindNextIndexEntry( IrpContext,
+ Scb,
+ Ccb->QueryBuffer,
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION ),
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
+ IndexContext,
+ NextFlag,
+ NULL )) {
+
+ try_return( Result = FALSE );
+ }
+
+ //
+ // If we get here, then we have a match that we want to return.
+ // We always copy the complete IndexEntry away and pass a pointer
+ // back to the copy. See if our current buffer for the index entry
+ // is large enough.
+ //
+
+ FoundIndexEntry = IndexContext->Current->IndexEntry;
+
+ if (Ccb->IndexEntryLength < (ULONG)FoundIndexEntry->Length) {
+
+ //
+ // If there is a buffer currently allocated, deallocate it before
+ // allocating a larger one. (Clear Ccb fields in case we get an
+ // allocation error.)
+ //
+
+ if (Ccb->IndexEntry != NULL) {
+
+ NtfsFreePool( Ccb->IndexEntry );
+ Ccb->IndexEntry = NULL;
+ Ccb->IndexEntryLength = 0;
+ }
+
+ //
+ // Allocate a new buffer for the index entry we just found, with
+ // some "padding" in case the next match is larger.
+ //
+
+ Ccb->IndexEntry = (PINDEX_ENTRY)NtfsAllocatePool(PagedPool, (ULONG)FoundIndexEntry->Length + 16 );
+
+ Ccb->IndexEntryLength = (ULONG)FoundIndexEntry->Length + 16;
+ }
+
+ //
+ // Now, save away our copy of the IndexEntry, and return a pointer
+ // to it.
+ //
+
+ RtlMoveMemory( Ccb->IndexEntry,
+ FoundIndexEntry,
+ (ULONG)FoundIndexEntry->Length );
+
+ *IndexEntry = Ccb->IndexEntry;
+
+ try_return( Result = TRUE );
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ DebugUnwind( NtfsRestartIndexEnumeration );
+
+ if (CleanupOtherContext) {
+ NtfsCleanupIndexContext( IrpContext, &OtherContext );
+ }
+ //
+ // If we died during the first call, then deallocate everything
+ // that we might have allocated.
+ //
+
+ if (AbnormalTermination() && ContextJustCreated) {
+
+ if (Ccb->IndexEntry != NULL) {
+ NtfsFreePool( Ccb->IndexEntry );
+ Ccb->IndexEntry = NULL;
+ }
+
+ if (Ccb->IndexContext != NULL) {
+ NtfsCleanupIndexContext( IrpContext, Ccb->IndexContext );
+ ExFreeToPagedLookasideList( &NtfsIndexContextLookasideList, Ccb->IndexContext );
+ Ccb->IndexContext = NULL;
+ }
+ }
+
+ //
+ // Remember if we are not returning anything, to save work later.
+ //
+
+ if (!Result && (Ccb->IndexEntry != NULL)) {
+ Ccb->IndexEntry->Length = 0;
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("*IndexEntry < %08lx\n", *IndexEntry) );
+ DebugTrace( -1, Dbg, ("NtfsRestartIndexEnumeration -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+BOOLEAN
+NtfsContinueIndexEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN BOOLEAN NextFlag,
+ OUT PINDEX_ENTRY *IndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to return again the last match on an active
+ enumeration, or to return the next match. Enumerations must always be
+ started or restarted via a call to NtfsRestartIndexEnumeration.
+
+ Note that all calls to this routine must be from within a try-finally,
+ and the finally clause must include a call to NtfsCleanupAfterEnumeration.
+
+Arguments:
+
+ Ccb - Pointer to the Ccb for this enumeration.
+
+ Scb - Supplies the Scb for the index.
+
+ NextFlag - FALSE if the last returned match is to be returned again.
+ TRUE if the next match is to be returned.
+
+ IndexEntry - Returns a pointer to a copy of the index entry.
+
+Return Value:
+
+ FALSE - If no match is being returned, and the output pointer is undefined.
+ TRUE - If a match is being returned.
+
+--*/
+
+{
+ PINDEX_ENTRY FoundIndexEntry;
+ PINDEX_CONTEXT IndexContext;
+ BOOLEAN MustRestart;
+ BOOLEAN Result = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_CCB( Ccb );
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsContinueIndexEnumeration\n") );
+ DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("NextFlag = %02lx\n", NextFlag) );
+
+ //
+ // It seems many apps like to come back one more time and really get
+ // an error status, so if we did not return anything last time, we can
+ // get out now too.
+ //
+ // There also may be no index entry, in the case of an empty directory
+ // and dirctrl is cycling through with "." and "..".
+ //
+
+ if ((Ccb->IndexEntry == NULL) || (Ccb->IndexEntry->Length == 0)) {
+
+ DebugTrace( -1, Dbg, ("NtfsContinueIndexEnumeration -> FALSE\n") );
+ return FALSE;
+ }
+
+ IndexContext = Ccb->IndexContext;
+
+ try {
+
+ //
+ // Lookup the next match.
+ //
+
+ if (!FindNextIndexEntry( IrpContext,
+ Scb,
+ Ccb->QueryBuffer,
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_WILDCARD_IN_EXPRESSION ),
+ BooleanFlagOn( Ccb->Flags, CCB_FLAG_IGNORE_CASE ),
+ IndexContext,
+ NextFlag,
+ &MustRestart )) {
+
+ //
+ // If he is saying we must restart, then that means something changed
+ // in our saved enumeration context across two file system calls.
+ // Reestablish our position in the tree by looking up the last entry
+ // we returned.
+ //
+
+ if (MustRestart) {
+
+ NtfsReinitializeIndexContext( IrpContext, Ccb->IndexContext );
+
+ try_return( Result = NtfsRestartIndexEnumeration( IrpContext,
+ Ccb,
+ Scb,
+ (PVOID)(Ccb->IndexEntry + 1),
+ FALSE,
+ NextFlag,
+ IndexEntry,
+ NULL ));
+
+ //
+ // Otherwise, there is nothing left to return.
+ //
+
+ } else {
+
+ try_return( Result = FALSE );
+ }
+ }
+
+ //
+ // If we get here, then we have a match that we want to return.
+ // We always copy the complete IndexEntry away and pass a pointer
+ // back to the copy. See if our current buffer for the index entry
+ // is large enough.
+ //
+
+ FoundIndexEntry = IndexContext->Current->IndexEntry;
+
+ if (Ccb->IndexEntryLength < (ULONG)FoundIndexEntry->Length) {
+
+ //
+ // If there is a buffer currently allocated, deallocate it before
+ // allocating a larger one.
+ //
+
+ if (Ccb->IndexEntry != NULL) {
+
+ NtfsFreePool( Ccb->IndexEntry );
+ Ccb->IndexEntry = NULL;
+ Ccb->IndexEntryLength = 0;
+ }
+
+ //
+ // Allocate a new buffer for the index entry we just found, with
+ // some "padding".
+ //
+
+ Ccb->IndexEntry = (PINDEX_ENTRY)NtfsAllocatePool(PagedPool, (ULONG)FoundIndexEntry->Length + 16 );
+
+ Ccb->IndexEntryLength = (ULONG)FoundIndexEntry->Length + 16;
+ }
+
+ //
+ // Now, save away our copy of the IndexEntry, and return a pointer
+ // to it.
+ //
+
+ RtlMoveMemory( Ccb->IndexEntry,
+ FoundIndexEntry,
+ (ULONG)FoundIndexEntry->Length );
+
+ *IndexEntry = Ccb->IndexEntry;
+
+ try_return( Result = TRUE );
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ DebugUnwind( NtfsContinueIndexEnumeration );
+
+ //
+ // Remember if we are not returning anything, to save work later.
+ //
+
+ if (!Result && (Ccb->IndexEntry != NULL)) {
+ Ccb->IndexEntry->Length = 0;
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("*IndexEntry < %08lx\n", *IndexEntry) );
+ DebugTrace( -1, Dbg, ("NtfsContinueIndexEnumeration -> %08lx\n", Result) );
+
+ return Result;
+}
+
+
+PFILE_NAME
+NtfsRetrieveOtherFileName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN OUT PINDEX_CONTEXT OtherContext,
+ IN PFCB AcquiredFcb OPTIONAL,
+ OUT PBOOLEAN SynchronizationError
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to retrieve the other index entry for a given
+ index entry. I.e., for an input Ntfs-only index entry it will find the
+ Dos-only index entry for the same file referenced, or visa versa. This
+ is a routine which clearly is relevant only to file name indices, but it
+ is located here because it uses the Index Context in the Ccb.
+
+ The idea is that nearly always the other name for a given index entry will
+ be very near to the given name.
+
+ This routine first scans the leaf index buffer described by the index
+ context for the Dos name. If this fails, this routine attempts to look
+ the other name up in the index. Currently there will always be a Dos name,
+ however if one does not exist, we treat that as benign, and simply return
+ FALSE.
+
+
+Arguments:
+
+ Ccb - Pointer to the Ccb for this enumeration.
+
+ Scb - Supplies the Scb for the index.
+
+ IndexEntry - Suppliess a pointer to an index entry, for which the Dos name
+ is to be retrieved.
+
+
+ OtherContext - Must be initialized on input, and subsequently cleaned up
+ by the caller after the information has been extracted from
+ the other index entry.
+
+ AcquiredFcb - An Fcb which has been acquired so that its file record may be
+ read
+
+ SynchronizationError - Returns TRUE if no file name is being returned because
+ of an error trying to acquire an Fcb to read its file
+ record.
+
+Return Value:
+
+ Pointer to the other desired file name.
+
+--*/
+
+{
+ PINDEX_CONTEXT IndexContext;
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ENTRY IndexTemp, IndexLast;
+ PINDEX_LOOKUP_STACK Top;
+
+ struct {
+ FILE_NAME FileName;
+ WCHAR NameBuffer[2];
+ }OtherFileName;
+
+ UNICODE_STRING OtherName;
+ USHORT OtherFlag;
+ PVCB Vcb = Scb->Vcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_CCB( Ccb );
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRetrieveOtherFileName\n") );
+ DebugTrace( 0, Dbg, ("Ccb = %08lx\n", Ccb) );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+
+ *SynchronizationError = FALSE;
+
+ //
+ // Calculate the other name space flag.
+ //
+
+ OtherFlag = ((PFILE_NAME)(IndexEntry + 1))->Flags;
+ ClearFlag( OtherFlag, ~(FILE_NAME_NTFS | FILE_NAME_DOS) );
+ OtherFlag ^= FILE_NAME_NTFS | FILE_NAME_DOS;
+
+ ASSERT( (OtherFlag == FILE_NAME_NTFS) || (OtherFlag == FILE_NAME_DOS) );
+
+ //
+ // Point to the IndexContext in the Ccb.
+ //
+
+ IndexContext = Ccb->IndexContext;
+
+ //
+ // We can only scan the top of the index if it is safe
+ // to read it.
+ //
+
+ Top = IndexContext->Top;
+
+ if ((Top->Bcb != NULL) ||
+ (Top == IndexContext->Base) ||
+ ReadIndexBuffer(IrpContext, Scb, 0, TRUE, Top)) {
+
+ //
+ // Point to the first index entry in the index buffer at the bottom of
+ // the index.
+ //
+
+ IndexHeader = Top->IndexHeader;
+ IndexTemp = Add2Ptr(IndexHeader, IndexHeader->FirstIndexEntry);
+ IndexLast = Add2Ptr(IndexHeader, IndexHeader->FirstFreeByte);
+
+ //
+ // Now scan this buffer for a matching Dos name.
+ //
+
+ while (IndexTemp < IndexLast) {
+
+ //
+ // If we find an entry with the same file reference and the Dos flag
+ // set, then we can return it and get out.
+ //
+
+ if (NtfsEqualMftRef(&IndexTemp->FileReference, &IndexEntry->FileReference) &&
+ FlagOn(((PFILE_NAME)(IndexTemp + 1))->Flags, OtherFlag)) {
+
+ DebugTrace( -1, Dbg, ("NtfsRetrieveOtherFileName -> %08lx\n", IndexTemp) );
+
+ return (PFILE_NAME)(IndexTemp + 1);
+ }
+
+ IndexTemp = Add2Ptr(IndexTemp, IndexTemp->Length);
+ }
+ }
+
+ //
+ // If this is a pretty large directory, then it may be too expensive to
+ // scan for the other name in the directory. Note in the degenerate
+ // case, we have actually do a sequential scan of the entire directory,
+ // and if all the files in the directory start with the same 6 characters,
+ // which is unfortunately common, then even our "pie-wedge" scan reads
+ // the entire directory.
+ //
+ // Thus we will just try to go read the file record in this case to get
+ // the other name. This is complicated from a synchronization standpoint -
+ // if the file is open, we need to acquire it shared before we can read
+ // it to get the other name. Here is a summary of the strategy implemented
+ // primarily here and in dirctrl:
+ //
+ // 1. Read the file record, in an attempt to avoid a fault while
+ // being synchronized.
+ // 2. If the file reference we need to synchronize is the same as
+ // in the optional AcquiredFcb parameter, go ahead and find/return
+ // the other name.
+ // 3. Else, acquire the Fcb Table to try to look up the Fcb.
+ // 4. If there is no Fcb, hold the table while returning the name.
+ // 5. If there is an Fcb, try to acquire it shared with Wait = FALSE,
+ // and hold it while returning the name.
+ // 6. If we cannot get the Fcb, and AcquiredFcb was not specified, then
+ // store the File Reference we are trying to get and return
+ // *SynchronizationError = TRUE. Dirctrl must figure out how to
+ // call us back with the FcbAcquired, by forcing a resume of the
+ // enumeration.
+ // 7. If we could not get the Fcb and there *was* a different AcquiredFcb
+ // specified, then this is the only case where we give up and fall
+ // through to find the other name in the directory. This should be
+ // extremely rare, but if we try to return *SynchronizationError = TRUE,
+ // and force a resume, we could be unlucky and loop forever, essentially
+ // toggling between synchronizing on two Fcbs. Presumably this could
+ // only happen if we have some kind of dumb client who likes to back
+ // up a few files when he resumes.
+ //
+
+ if (Scb->Header.AllocationSize.QuadPart > MAX_INDEX_TO_SCAN_FOR_NAMES) {
+
+ FCB_TABLE_ELEMENT Key;
+ PFCB_TABLE_ELEMENT Entry;
+ PFCB FcbWeNeed;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ BOOLEAN Synchronized = TRUE;
+
+ //
+ // Get the base file record active and valid before synchroniziing.
+ //
+
+ NtfsReadFileRecord( IrpContext,
+ Vcb,
+ &IndexEntry->FileReference,
+ &OtherContext->AttributeContext.FoundAttribute.Bcb,
+ &FileRecord,
+ &Attribute,
+ NULL );
+
+ //
+ // If we are not synchronized with the correct Fcb, then try to
+ // synchronize.
+ //
+
+ if (!ARGUMENT_PRESENT(AcquiredFcb) ||
+ !NtfsEqualMftRef(&AcquiredFcb->FileReference, &IndexEntry->FileReference)) {
+
+ //
+ // Now look up the Fcb, and if it is there, reference it
+ // and remember it.
+ //
+
+ Key.FileReference = IndexEntry->FileReference;
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ Entry = RtlLookupElementGenericTable( &Vcb->FcbTable, &Key );
+
+ if (Entry != NULL) {
+
+ FcbWeNeed = Entry->Fcb;
+
+ //
+ // Now that it cannot go anywhere, try to acquire it.
+ //
+
+ Synchronized = ExAcquireResourceShared( FcbWeNeed->Resource, FALSE );
+
+ //
+ // If we manage to acquire it, then increment its reference count
+ // and remember it for subsequent cleanup.
+ //
+
+ if (Synchronized) {
+
+ FcbWeNeed->ReferenceCount += 1;
+ OtherContext->AcquiredFcb = FcbWeNeed;
+ }
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ } else {
+
+ SetFlag( OtherContext->Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED );
+ }
+ }
+
+ if (Synchronized) {
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)Add2Ptr(FileRecord, FileRecord->FirstAttributeOffset);
+
+ while (((PVOID)Attribute < Add2Ptr(FileRecord, FileRecord->FirstFreeByte)) &&
+ (Attribute->TypeCode <= $FILE_NAME)) {
+
+ if ((Attribute->TypeCode == $FILE_NAME) &&
+ FlagOn(((PFILE_NAME)NtfsAttributeValue(Attribute))->Flags, OtherFlag)) {
+
+ return (PFILE_NAME)NtfsAttributeValue(Attribute);
+ }
+
+ Attribute = NtfsGetNextRecord(Attribute);
+ }
+
+ } else if (!ARGUMENT_PRESENT(AcquiredFcb)) {
+
+ Ccb->FcbToAcquire.FileReference = IndexEntry->FileReference;
+ *SynchronizationError = TRUE;
+ return NULL;
+ }
+
+ //
+ // Cleanup from above before proceding.
+ //
+
+ NtfsReinitializeIndexContext( IrpContext, OtherContext );
+ }
+
+ //
+ // Well, we were unlucky, and did not find the other name yet, form
+ // a name to scan a range of the index.
+ //
+
+ RtlZeroMemory( &OtherFileName, sizeof(OtherFileName) );
+ OtherName.MaximumLength = 6;
+ OtherName.Buffer = (PWCHAR) &OtherFileName.FileName.FileName[0];
+ OtherName.Buffer[0] = ((PFILE_NAME)(IndexEntry + 1))->FileName[0];
+
+ //
+ // Name generation is complicated enough, that we are only going to
+ // guess the other name using the first two (special case is one)
+ // characters followed by *.
+ //
+
+ if (((PFILE_NAME)(IndexEntry + 1))->FileNameLength > 1) {
+
+ OtherName.Buffer[1] = ((PFILE_NAME)(IndexEntry + 1))->FileName[1];
+ OtherName.Buffer[2] = L'*';
+ OtherFileName.FileName.FileNameLength = 3;
+ OtherName.Length = 6;
+
+ } else {
+
+ OtherName.Buffer[1] = L'*';
+ OtherFileName.FileName.FileNameLength = 2;
+ OtherName.Length = 4;
+ }
+
+ //
+ // Now we think we have formed a pretty good name fairly tightly
+ // encompasses the range of possible Dos names we expect for the
+ // given Ntfs name. We will enumerate all of the files which match
+ // the pattern we have formed, and see if any of them are the Dos
+ // name for the same file. If this expression still doesn't work,
+ // (extremely unlikely), then we will substitute the pattern with
+ // "*" and make one final attempt.
+ //
+ // Note many names are the same in Dos and Ntfs, and for them this
+ // routine is never called. Of the ones that do have separate names,
+ // the pattern we formed above should really match it and will probably
+ // scan parts of the directory already in the cache. The last loop is
+ // a terrible sequential scan of the entire directory. It should never,
+ // never happen, but it is here so that in the worst case we do not
+ // break, we just take a bit longer.
+ //
+
+ while (TRUE) {
+
+ BOOLEAN NextFlag;
+ ULONG NameLength;
+
+ NameLength = sizeof(FILE_NAME) + OtherFileName.FileName.FileNameLength - 1;
+
+ //
+ // The first step of enumeration is to position our IndexContext.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ &OtherFileName,
+ OtherContext );
+
+ NextFlag = FALSE;
+
+ //
+ // Now scan through all of the case insensitive matches.
+ // Upcase our name structure.
+ //
+
+ NtfsUpcaseName( Vcb->UpcaseTable, Vcb->UpcaseTableSize, &OtherName );
+
+ while (FindNextIndexEntry( IrpContext,
+ Scb,
+ &OtherFileName,
+ TRUE,
+ TRUE,
+ OtherContext,
+ NextFlag,
+ NULL )) {
+
+ IndexTemp = OtherContext->Current->IndexEntry;
+
+ //
+ // If we find an entry with the same file reference and the Dos flag
+ // set, then we can return it and get out.
+ //
+
+ if (NtfsEqualMftRef(&IndexTemp->FileReference, &IndexEntry->FileReference) &&
+ FlagOn(((PFILE_NAME)(IndexTemp + 1))->Flags, OtherFlag)) {
+
+ DebugTrace( -1, Dbg, ("NtfsRetrieveOtherFileName -> %08lx\n", IndexTemp) );
+
+ return (PFILE_NAME)(IndexTemp + 1);
+ }
+
+ NextFlag = TRUE;
+ }
+
+ //
+ // Give up if we have already scanned everything.
+ //
+
+ if ((OtherName.Buffer[0] == '*') && (OtherName.Length == 2)) {
+ break;
+ }
+
+
+ NtfsReinitializeIndexContext( IrpContext, OtherContext );
+
+ OtherName.Buffer[0] = '*';
+ OtherName.Length = 2;
+ OtherFileName.FileName.FileNameLength = 1;
+ }
+
+ return NULL;
+}
+
+
+VOID
+NtfsCleanupAfterEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ A call to this routine must exist within the finally clause of any routine
+ which calls either NtfsRestartIndexEnumeration or NtfsContinueIndexEnumeration.
+
+Arguments:
+
+ Ccb - Pointer to the Ccb for this enumeration.
+
+Return Value:
+
+ None
+
+--*/
+
+
+{
+ PAGED_CODE();
+
+ if (Ccb->IndexContext != NULL) {
+ NtfsReinitializeIndexContext( IrpContext, Ccb->IndexContext );
+ }
+}
+
+
+BOOLEAN
+NtfsIsIndexEmpty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to see if the specified index is empty.
+
+Arguments:
+
+ Attribute - Pointer to the attribute record header of an INDEX_ROOT
+ attribute.
+
+Return Value:
+
+ FALSE - if the index is not empty.
+ TRUE - if the index is empty.
+
+--*/
+
+{
+ PINDEX_ROOT IndexRoot;
+ PINDEX_ENTRY IndexEntry;
+ BOOLEAN Result;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsIndexEmpty\n") );
+ DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) );
+
+ IndexRoot = (PINDEX_ROOT)NtfsAttributeValue( Attribute );
+ IndexEntry = NtfsFirstIndexEntry( &IndexRoot->IndexHeader );
+
+ Result = (BOOLEAN)(!FlagOn( IndexEntry->Flags, INDEX_ENTRY_NODE ) &&
+ FlagOn( IndexEntry->Flags, INDEX_ENTRY_END ));
+
+ DebugTrace( -1, Dbg, ("NtfsIsIndexEmpty -> %02lx\n", Result) );
+
+ return Result;
+}
+
+
+VOID
+NtfsDeleteIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING AttributeName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete the specified index. The index
+ must be empty.
+
+ NOTE: This routine is not required until we can separately create/delete
+ indices, therefore it is not implemented. Use NtfsDeleteFile
+ to delete a "directory" (or a normal file).
+
+Arguments:
+
+ Fcb - Supplies the Fcb for the index.
+
+ AttributeName - Name of the index attributes: $Ixxx
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ UNREFERENCED_PARAMETER( IrpContext );
+ UNREFERENCED_PARAMETER( Fcb );
+ UNREFERENCED_PARAMETER( AttributeName );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteIndex\n") );
+ DebugTrace( 0, Dbg, ("Fcb = %08lx\n", Fcb) );
+ DebugTrace( 0, Dbg, ("AttributeName = %08lx\n", AttributeName) );
+
+ DbgDoit( DbgPrint("NtfsDeleteIndex is not yet implemented\n"); DbgBreakPoint(); );
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteIndex -> VOID\n") );
+}
+
+
+VOID
+NtfsInitializeIndexContext (
+ OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to initialize the specified index context.
+
+Arguments:
+
+ IndexContext - Index context to initialize.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ RtlZeroMemory( IndexContext, sizeof(INDEX_CONTEXT) );
+ NtfsInitializeAttributeContext( &IndexContext->AttributeContext );
+
+ //
+ // Describe the resident lookup stack
+ //
+
+ IndexContext->Base = IndexContext->LookupStack;
+ IndexContext->NumberEntries = INDEX_LOOKUP_STACK_SIZE;
+}
+
+
+VOID
+NtfsCleanupIndexContext (
+ IN PIRP_CONTEXT IrpContext,
+ OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to cleanup the specified index context,
+ typically during finally processing.
+
+Arguments:
+
+ IndexContext - Index context to clean up.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG i;
+
+ PAGED_CODE();
+
+ //
+ // Release the Fcb Table and/or an acquired Fcb.
+ //
+
+ if (FlagOn(IndexContext->Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED)) {
+ NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
+ ClearFlag( IndexContext->Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED );
+ }
+
+ if (IndexContext->AcquiredFcb != NULL) {
+
+ NtfsAcquireFcbTable( IrpContext, IrpContext->Vcb );
+ ASSERT(IndexContext->AcquiredFcb->ReferenceCount > 0);
+ IndexContext->AcquiredFcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
+
+ ExReleaseResource( IndexContext->AcquiredFcb->Resource );
+
+ IndexContext->AcquiredFcb = NULL;
+ }
+
+ for (i = 0; i < IndexContext->NumberEntries; i++) {
+ NtfsUnpinBcb( &IndexContext->Base[i].Bcb );
+ }
+
+ //
+ // See if we need to deallocate a lookup stack.
+ //
+
+ if (IndexContext->Base != IndexContext->LookupStack) {
+ NtfsFreePool( IndexContext->Base );
+ }
+
+ NtfsCleanupAttributeContext( &IndexContext->AttributeContext );
+}
+
+
+VOID
+NtfsReinitializeIndexContext (
+ IN PIRP_CONTEXT IrpContext,
+ OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to cleanup the specified index context,
+ and reinitialize it, preserving fields that should not be zeroed.
+
+Arguments:
+
+ IndexContext - Index context to clean up.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG i;
+
+ PAGED_CODE();
+
+ //
+ // Release the Fcb Table and/or an acquired Fcb.
+ //
+
+ if (FlagOn(IndexContext->Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED)) {
+ NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
+ ClearFlag( IndexContext->Flags, INDX_CTX_FLAG_FCB_TABLE_ACQUIRED );
+ }
+
+ if (IndexContext->AcquiredFcb != NULL) {
+
+ NtfsAcquireFcbTable( IrpContext, IrpContext->Vcb );
+ ASSERT(IndexContext->AcquiredFcb->ReferenceCount > 0);
+ IndexContext->AcquiredFcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
+
+ ExReleaseResource( IndexContext->AcquiredFcb->Resource );
+
+ IndexContext->AcquiredFcb = NULL;
+ }
+
+ for (i = 0; i < IndexContext->NumberEntries; i++) {
+ NtfsUnpinBcb( &IndexContext->Base[i].Bcb );
+ }
+
+ NtfsCleanupAttributeContext( &IndexContext->AttributeContext );
+ NtfsInitializeAttributeContext( &IndexContext->AttributeContext );
+}
+
+
+VOID
+NtfsGrowLookupStack (
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ IN PINDEX_LOOKUP_STACK *Sp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine grows and index lookup stack to contain the specified number
+ of entries.
+
+Arguments:
+
+ Scb - Scb for index
+
+ IndexContext - Index context to grow.
+
+ Sp - Caller's local stack pointer to be updated
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_LOOKUP_STACK NewLookupStack;
+ ULONG Relocation;
+ USHORT NumberEntries;
+
+ PAGED_CODE();
+
+ //
+ // Extract current index hint, if there is one.
+ //
+
+ NumberEntries = Scb->ScbType.Index.IndexDepthHint;
+
+ //
+ // If there was no hint, or it was too small, then
+ // calculate a new hint.
+ //
+
+ if (NumberEntries <= IndexContext->NumberEntries) {
+
+ Scb->ScbType.Index.IndexDepthHint =
+ NumberEntries = IndexContext->NumberEntries + 3;
+ }
+
+ //
+ // Allocate (may fail), initialize and copy over the old one.
+ //
+
+ NewLookupStack = NtfsAllocatePool( PagedPool, NumberEntries * sizeof(INDEX_LOOKUP_STACK) );
+
+ RtlZeroMemory( NewLookupStack, NumberEntries * sizeof(INDEX_LOOKUP_STACK) );
+
+ RtlCopyMemory( NewLookupStack,
+ IndexContext->Base,
+ IndexContext->NumberEntries * sizeof(INDEX_LOOKUP_STACK) );
+
+ //
+ // Free the old one unless we were using the local stack.
+ //
+
+ if (IndexContext->Base != IndexContext->LookupStack) {
+ NtfsFreePool( IndexContext->Base );
+ }
+
+ //
+ // Now relocate all pointers to the old stack
+ //
+
+ Relocation = (ULONG)((PCHAR)NewLookupStack - (PCHAR)IndexContext->Base);
+ IndexContext->Current = (PINDEX_LOOKUP_STACK)((PCHAR)IndexContext->Current + Relocation);
+ IndexContext->Top = (PINDEX_LOOKUP_STACK)((PCHAR)IndexContext->Top + Relocation);
+ *Sp = (PINDEX_LOOKUP_STACK)((PCHAR)*Sp + Relocation);
+
+ //
+ // Finally update context to describe new stack
+ //
+
+ IndexContext->Base = NewLookupStack;
+ IndexContext->NumberEntries = NumberEntries;
+}
+
+
+BOOLEAN
+ReadIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG IndexBlock,
+ IN BOOLEAN Reread,
+ OUT PINDEX_LOOKUP_STACK Sp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads the index buffer at the specified Vcn, and initializes
+ the stack pointer to describe it.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexBlock - Supplies the index block of this index buffer, ignored if
+ Reread is TRUE.
+
+ Reread - Supplies TRUE if buffer is being reread, and the CapturedLsn
+ should be checked.
+
+ Sp - Returns a description of the index buffer read.
+
+Return Value:
+
+ FALSE - if Reread was supplied as TRUE and the Lsn changed
+ TRUE - if the buffer was read successfully (or did not change)
+
+--*/
+
+{
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("ReadIndexBuffer\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Sp = %08lx\n", Sp) );
+
+ ASSERT(Sp->Bcb == NULL);
+
+ //
+ // If the attribute stream does not already exist, create it.
+ //
+
+ if (Scb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+ }
+
+ //
+ // If Reread is TRUE, then convert the directory entry pointer in the
+ // buffer to an offset (to be relocated later) and overwrite the Lbn
+ // input parameter with the Lbn in the stack location.
+ //
+
+ if (Reread) {
+ Sp->IndexEntry = (PINDEX_ENTRY)((PCHAR)Sp->IndexEntry - (PCHAR)Sp->StartOfBuffer);
+ IndexBlock = Sp->IndexBlock;
+ }
+
+ Sp->IndexBlock = IndexBlock;
+
+ //
+ // The vcn better only have 32 bits, other wise the the test in NtfsMapStream
+ // may not catch this error.
+ //
+
+ if (((PLARGE_INTEGER) &IndexBlock)->HighPart != 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ NtfsMapStream( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb,
+ &Sp->StartOfBuffer );
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ if ((*(PULONG)IndexBuffer->MultiSectorHeader.Signature != *(PULONG)IndexSignature) ||
+ (IndexBuffer->ThisBlock != IndexBlock)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ Sp->IndexHeader = &IndexBuffer->IndexHeader;
+ if (Reread) {
+
+ if (IndexBuffer->Lsn.QuadPart != Sp->CapturedLsn.QuadPart) {
+
+ NtfsUnpinBcb( &Sp->Bcb );
+ DebugTrace( -1, Dbg, ("ReadIndexBuffer->TRUE\n") );
+ return FALSE;
+ }
+
+ Sp->IndexEntry = (PINDEX_ENTRY)((PCHAR)Sp->IndexEntry + ((PCHAR)Sp->StartOfBuffer - (PCHAR)NULL));
+
+ } else {
+
+ Sp->IndexEntry = NtfsFirstIndexEntry(Sp->IndexHeader);
+ Sp->CapturedLsn = IndexBuffer->Lsn;
+ }
+
+
+ DebugTrace( -1, Dbg, ("ReadIndexBuffer->VOID\n") );
+
+ return TRUE;
+}
+
+
+PINDEX_ALLOCATION_BUFFER
+GetIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ OUT PINDEX_LOOKUP_STACK Sp,
+ OUT PLONGLONG EndOfValidData
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and initializes an index buffer, and initializes
+ the stack pointer to describe it.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Sp - Returns a description of the index buffer allocated.
+
+ EndOfValidData - This is the file offset of the end of the allocated buffer.
+ This is used to update the valid data length of the stream when the
+ block is written.
+
+Return Value:
+
+ Pointer to the Index Buffer allocated.
+
+--*/
+
+{
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ ATTRIBUTE_ENUMERATION_CONTEXT BitMapContext;
+ ULONG RecordIndex;
+ LONGLONG BufferOffset;
+
+ PUSHORT UsaSequenceNumber;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("GetIndexBuffer\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Sp = %08lx\n", Sp) );
+
+ //
+ // Initialize the BitMap attribute context and insure cleanup on the
+ // way out.
+ //
+
+ NtfsInitializeAttributeContext( &BitMapContext );
+
+ try {
+
+ //
+ // Lookup the BITMAP attribute.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $BITMAP,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &BitMapContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // If the attribute stream does not already exist, create it.
+ //
+
+ if (Scb->FileObject == NULL) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+ }
+
+ //
+ // If the allocation for this index has not been initialized yet,
+ // then we must initialize it first.
+ //
+
+ if (!Scb->ScbType.Index.AllocationInitialized) {
+
+ ULONG ExtendGranularity = 1;
+ ULONG TruncateGranularity = 4;
+
+ if (Scb->ScbType.Index.BytesPerIndexBuffer < Scb->Vcb->BytesPerCluster) {
+
+ ExtendGranularity = Scb->Vcb->BytesPerCluster / Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ if (ExtendGranularity > 4) {
+
+ TruncateGranularity = ExtendGranularity;
+ }
+ }
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ Scb,
+ &BitMapContext,
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ ExtendGranularity,
+ TruncateGranularity,
+ &Scb->ScbType.Index.RecordAllocationContext );
+
+ Scb->ScbType.Index.AllocationInitialized = TRUE;
+ }
+
+ //
+ // Now allocate a record. We always "hint" from the front to keep the
+ // index compact.
+ //
+
+ RecordIndex = NtfsAllocateRecord( IrpContext,
+ &Scb->ScbType.Index.RecordAllocationContext,
+ 0,
+ &BitMapContext );
+
+ //
+ // Calculate the IndexBlock.
+ //
+
+ BufferOffset = Int32x32To64( RecordIndex, Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Now remember the offset of the end of the added record.
+ //
+
+ *EndOfValidData = BufferOffset + Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ //
+ // Now pin and zero the buffer, in order to initialize it.
+ //
+
+ NtfsPreparePinWriteStream( IrpContext,
+ Scb,
+ BufferOffset,
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ TRUE,
+ &Sp->Bcb,
+ (PVOID *)&IndexBuffer );
+
+
+ //
+ // Now we can fill in the lookup stack.
+ //
+
+ Sp->StartOfBuffer = (PVOID)IndexBuffer;
+ Sp->IndexHeader = &IndexBuffer->IndexHeader;
+ Sp->IndexEntry = (PINDEX_ENTRY)NULL;
+
+ //
+ // Initialize the Index Allocation Buffer and return.
+ //
+
+ *(PULONG)IndexBuffer->MultiSectorHeader.Signature = *(PULONG)IndexSignature;
+
+ IndexBuffer->MultiSectorHeader.UpdateSequenceArrayOffset =
+ (USHORT)FIELD_OFFSET( INDEX_ALLOCATION_BUFFER, UpdateSequenceArray );
+ IndexBuffer->MultiSectorHeader.UpdateSequenceArraySize =
+ (USHORT)UpdateSequenceArraySize( Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ UsaSequenceNumber = Add2Ptr( IndexBuffer,
+ IndexBuffer->MultiSectorHeader.UpdateSequenceArrayOffset );
+ *UsaSequenceNumber = 1;
+
+
+ IndexBuffer->ThisBlock = RecordIndex * Scb->ScbType.Index.BlocksPerIndexBuffer;
+
+ IndexBuffer->IndexHeader.FirstIndexEntry =
+ IndexBuffer->IndexHeader.FirstFreeByte =
+ QuadAlign((ULONG)IndexBuffer->MultiSectorHeader.UpdateSequenceArrayOffset +
+ (ULONG)IndexBuffer->MultiSectorHeader.UpdateSequenceArraySize * sizeof(USHORT)) -
+ FIELD_OFFSET(INDEX_ALLOCATION_BUFFER, IndexHeader);;
+ IndexBuffer->IndexHeader.BytesAvailable =
+ Scb->ScbType.Index.BytesPerIndexBuffer -
+ FIELD_OFFSET(INDEX_ALLOCATION_BUFFER, IndexHeader);;
+
+ } finally {
+
+ DebugUnwind( GetIndexBuffer );
+
+ NtfsCleanupAttributeContext( &BitMapContext );
+ }
+
+ DebugTrace( -1, Dbg, ("GetIndexBuffer -> %08lx\n", IndexBuffer) );
+ return IndexBuffer;
+}
+
+
+VOID
+DeleteIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes the specified index buffer.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexBuffer - Pointer to the index buffer to delete.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT BitMapContext;
+ LONGLONG RecordIndex;
+ PATTRIBUTE_RECORD_HEADER BitmapAttribute;
+ BOOLEAN AttributeWasResident = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("DeleteIndexBuffer\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("IndexBuffer = %08lx\n", IndexBuffer) );
+
+ //
+ // Initialize the BitMap attribute context and insure cleanup on the
+ // way out.
+ //
+
+ NtfsInitializeAttributeContext( &BitMapContext );
+
+ try {
+
+ //
+ // Lookup the BITMAP attribute.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $BITMAP,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &BitMapContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Remember if the bitmap attribute is currently resident,
+ // in case it changes.
+ //
+
+ BitmapAttribute = NtfsFoundAttribute(&BitMapContext);
+ AttributeWasResident = BitmapAttribute->FormCode == RESIDENT_FORM;
+
+ //
+ // If the allocation for this index has not been initialized yet,
+ // then we must initialize it first.
+ //
+
+ if (!Scb->ScbType.Index.AllocationInitialized) {
+
+ ULONG ExtendGranularity = 1;
+ ULONG TruncateGranularity = 4;
+
+ if (Scb->ScbType.Index.BytesPerIndexBuffer < Scb->Vcb->BytesPerCluster) {
+
+ ExtendGranularity = Scb->Vcb->BytesPerCluster / Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ if (ExtendGranularity > 4) {
+
+ TruncateGranularity = ExtendGranularity;
+ }
+ }
+
+ NtfsInitializeRecordAllocation( IrpContext,
+ Scb,
+ &BitMapContext,
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ ExtendGranularity,
+ TruncateGranularity,
+ &Scb->ScbType.Index.RecordAllocationContext );
+
+ Scb->ScbType.Index.AllocationInitialized = TRUE;
+ }
+
+ //
+ // Calculate the record index for this buffer.
+ //
+
+ RecordIndex = IndexBuffer->ThisBlock / Scb->ScbType.Index.BlocksPerIndexBuffer;
+
+
+ if (((PLARGE_INTEGER)&RecordIndex)->HighPart != 0) {
+ ASSERT( ((PLARGE_INTEGER)&RecordIndex)->HighPart == 0 );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Now deallocate the record.
+ //
+
+ NtfsDeallocateRecord( IrpContext,
+ &Scb->ScbType.Index.RecordAllocationContext,
+ (ULONG)RecordIndex,
+ &BitMapContext );
+
+ } finally {
+
+ DebugUnwind( DeleteIndexBuffer );
+
+ //
+ // In the extremely rare case that the bitmap attribute was resident
+ // and now became nonresident, we will uninitialize it here so that
+ // the next time we will find the bitmap Scb, etc.
+ //
+
+ if (AttributeWasResident) {
+
+ BitmapAttribute = NtfsFoundAttribute(&BitMapContext);
+
+ if (BitmapAttribute->FormCode != RESIDENT_FORM) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ &Scb->ScbType.Index.RecordAllocationContext );
+
+ Scb->ScbType.Index.AllocationInitialized = FALSE;
+ }
+ }
+
+ NtfsCleanupAttributeContext( &BitMapContext );
+ }
+ DebugTrace( -1, Dbg, ("DeleteIndexBuffer -> VOID\n") );
+}
+
+
+VOID
+FindFirstIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine finds the the first entry in a leaf buffer of an Index Btree
+ which could possibly be a match for the input value. Another way to state
+ this is that this routine establishes a position in the Btree from which a
+ tree walk should begin to find the desired value or all desired values which
+ match the input value specification.
+
+ As stated, the context on return will always describe a pointer in a leaf
+ buffer. This is occassionally inefficient in the 2% case where a specific
+ value is being looked up that happens to reside in an intermediate node buffer.
+ However, for index adds and deletes, this pointer is always very interesting.
+ For an add, this pointer always describes the exact spot at which the add should
+ occur (adds must always occur in leafs). For deletes, this pointer is either
+ to the exact index entry which is to be deleted, or else it points to the best
+ replacement for the target to delete, when the actual target is at an intermediate
+ spot in the tree.
+
+ So this routine descends from the root of the index to the correct leaf, doing
+ a binary search in each index buffer along the way (via an external routine).
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Pointer to a value or value expression which should be used to position
+ the IndexContext, or NULL to just describe the root for pushing.
+
+ IndexContext - Address of the initialized IndexContext, to return the desired
+ position.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PINDEX_LOOKUP_STACK Sp;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PINDEX_ROOT IndexRoot;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("FindFirstIndexEntry\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ //
+ // Lookup the attribute record from the Scb.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &IndexContext->AttributeContext )) {
+
+ DebugTrace( -1, 0, ("FindFirstIndexEntry - Could *not* find attribute\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_OBJECT_PATH_NOT_FOUND, NULL, NULL );
+ }
+
+ //
+ // Now Initialize some local pointers and the rest of the context
+ //
+
+ Sp = IndexContext->Base;
+ Sp->StartOfBuffer = NtfsContainingFileRecord( &IndexContext->AttributeContext );
+ Sp->CapturedLsn = ((PFILE_RECORD_SEGMENT_HEADER)Sp->StartOfBuffer)->Lsn;
+ IndexContext->ScbChangeCount = Scb->ScbType.Index.ChangeCount;
+ IndexContext->OldAttribute =
+ Attribute = NtfsFoundAttribute( &IndexContext->AttributeContext );
+ IndexRoot = (PINDEX_ROOT)NtfsAttributeValue( Attribute );
+ Sp->IndexHeader = &IndexRoot->IndexHeader;
+
+ //
+ // The Index part of the Scb may not yet be initialized. If so, do it
+ // here.
+ //
+
+ if (Scb->ScbType.Index.BytesPerIndexBuffer == 0) {
+
+ NtfsUpdateIndexScbFromAttribute( Scb, Attribute );
+ }
+
+ //
+ // If Value is not specified, this is a special call from NtfsPushIndexRoot.
+ //
+
+ if (!ARGUMENT_PRESENT(Value)) {
+
+ Sp->IndexEntry = NtfsFirstIndexEntry(Sp->IndexHeader);
+ IndexContext->Top =
+ IndexContext->Current = Sp;
+ return;
+ }
+
+ //
+ // Loop through the Lookup stack as we descend the binary tree doing an
+ // IgnoreCase lookup of the specified value.
+ //
+
+ while (TRUE) {
+
+ //
+ // Binary search in the current buffer for the first entry <= value.
+ //
+
+ Sp->IndexEntry = BinarySearchIndex( IrpContext,
+ Scb,
+ Sp,
+ Value );
+
+ //
+ // If this entry is not a node, then we are done.
+ //
+
+ if (!FlagOn( Sp->IndexEntry->Flags, INDEX_ENTRY_NODE )) {
+
+ IndexContext->Top =
+ IndexContext->Current = Sp;
+
+ //
+ // Check for and mark corrupt if we find an empty leaf.
+ //
+
+ if ((Sp != IndexContext->Base)
+
+ &&
+
+ FlagOn(NtfsFirstIndexEntry(Sp->IndexHeader)->Flags, INDEX_ENTRY_END)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ DebugTrace( -1, Dbg, ("FindFirstIndexEntry -> VOID\n") );
+
+ return;
+ }
+
+ //
+ // Check for special case where we preemptively push the root.
+ // Otherwise we can find ourselves recursively in NtfsAllocateRecord
+ // and NtfsAddAllocation, etc., on a buffer split which needs to push
+ // the root to add to the index allocation.
+ //
+ // First off, we only need to check this the first time through
+ // the loop, and only if the caller has the Scb exclusive.
+ //
+
+ if ((Sp == IndexContext->Base) &&
+ NtfsIsExclusiveScb(Scb) &&
+ !FlagOn( Scb->Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS)) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ FileRecord = NtfsContainingFileRecord(&IndexContext->AttributeContext);
+
+ //
+ // Only do the push if there are not enough bytes to allocate a
+ // record, and the root is already a node with down pointers, and
+ // the root is "big enough to move".
+ //
+ // This means this path will typically only kick in with directories
+ // with at least 200 files or so!
+ //
+
+ if (((FileRecord->BytesAvailable - FileRecord->FirstFreeByte) < 25) &&
+ FlagOn(IndexRoot->IndexHeader.Flags, INDEX_NODE) &&
+ (Attribute->RecordLength >= Scb->Vcb->BigEnoughToMove)) {
+
+ //
+ // Our insertion point will now also be pushed, so we
+ // have to increment the stack pointer.
+ //
+
+ Sp += 1;
+
+ if (Sp >= IndexContext->Base + (ULONG)IndexContext->NumberEntries) {
+ NtfsGrowLookupStack( Scb,
+ IndexContext,
+ &Sp );
+ }
+
+ PushIndexRoot( IrpContext, Scb, IndexContext );
+ }
+ }
+
+ //
+ // If the lookup stack is exhausted, grow the lookup stack.
+ //
+
+ Sp += 1;
+ if (Sp >= IndexContext->Base + (ULONG)IndexContext->NumberEntries) {
+ NtfsGrowLookupStack( Scb,
+ IndexContext,
+ &Sp );
+ }
+
+ //
+ // Otherwise, read the index buffer pointed to by the current
+ // Index Entry.
+ //
+
+ ReadIndexBuffer( IrpContext,
+ Scb,
+ NtfsIndexEntryBlock((Sp-1)->IndexEntry),
+ FALSE,
+ Sp );
+ }
+}
+
+
+BOOLEAN
+FindNextIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN ValueContainsWildcards,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ IN BOOLEAN NextFlag,
+ OUT PBOOLEAN MustRestart OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs a pre-order traversal of an index, starting from the
+ current position described by the index context, looking for the next match
+ for the input value, or the first value that indicates no further matches are
+ possible. Pre-order refers to the fact that starting at any given index entry,
+ we visit any descendents of that index entry first before visiting the index
+ entry itself, since all descendent index entries are lexigraphically less than
+ their parent index entry. A visit to an index entry is defined as either
+ detecting that the given index entry is the special end entry, or else testing
+ whether the index entry is a match for the input value expression.
+
+ The traversal either terminates successfully (returning TRUE) or unsuccessfully
+ (returning FALSE). It terminates successfully if a match is found and being
+ returned; in this case FindNextIndexEntry may be called again to look for the
+ next match. It terminates unsuccessfully if either the End entry is encountered
+ in the index root, or if an entry is found which is lexigraphically greater than
+ the input value, when compared ignoring case (if relevant).
+
+ The traversal is driven like a state machine driven by the index context, as
+ initialized from the preceding call to FindFirstIndexEntry, or as left from the
+ last call to this routine. The traversal algorithm is explained in comments
+ below.
+
+ The caller may specify whether it wants the current match to be returned (or
+ returned again), as described by the current state of the index context. Or it
+ may specify (with NextFlag TRUE) that it wishes to get the next match. Even if
+ NextFlag is FALSE, the currently described index entry will not be returned if
+ it is not a match.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Value - Pointer to a value or value expression which should be used to position
+ the IndexContext.
+
+ ValueContainsWildCards - Indicates if the value expression contains wild
+ cards. We can do a direct compare if it
+ doesn't.
+
+ IgnoreCase - Specified as TRUE if a case-insensitive match is desired (if
+ relevant for the collation rule).
+
+ IndexContext - Address of the initialized IndexContext, to return the desired
+ position.
+
+ NextFlag - Specified as FALSE if the currently described entry should be returned
+ if it is a match, or TRUE if the next entry should first be considered
+ for a match.
+
+ MustRestart - If specified and returning FALSE, returns TRUE if enumeration must
+ be restarted.
+
+Return Value:
+
+ FALSE - if no entry is being returned, and there are no more matches.
+ TRUE - if an entry is being returned, and the caller may wish to call for further
+ matches.
+
+--*/
+
+{
+ PINDEX_ENTRY IndexEntry;
+ PINDEX_LOOKUP_STACK Sp;
+ FSRTL_COMPARISON_RESULT BlindResult;
+ BOOLEAN LocalMustRestart;
+ PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable;
+ ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("FindNextIndexEntry\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+ DebugTrace( 0, Dbg, ("NextFlag = %02lx\n", NextFlag) );
+
+ if (!ARGUMENT_PRESENT(MustRestart)) {
+ MustRestart = &LocalMustRestart;
+ }
+
+ *MustRestart = FALSE;
+
+ if (IndexContext->ScbChangeCount != Scb->ScbType.Index.ChangeCount) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (must restart)\n") );
+
+ *MustRestart = TRUE;
+ return FALSE;
+ }
+
+ Sp = IndexContext->Current;
+
+ //
+ // If there is no Bcb for the current buffer, then we are continuing
+ // an enumeration.
+ //
+
+ if (Sp->Bcb == NULL) {
+
+ //
+ // Index Root case.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ //
+ // Lookup the attribute record from the Scb.
+ //
+
+ FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+
+ //
+ // Get out if someone has changed the file record.
+ //
+
+ if (Sp->CapturedLsn.QuadPart !=
+ NtfsContainingFileRecord(&IndexContext->AttributeContext)->Lsn.QuadPart) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (must restart)\n") );
+
+ *MustRestart = TRUE;
+ return FALSE;
+ }
+
+ //
+ // Index Buffer case.
+ //
+
+ } else {
+
+ //
+ // If the index buffer is unpinned, then see if we can read it and if it is
+ // unchanged.
+ //
+
+ if (!ReadIndexBuffer( IrpContext, Scb, 0, TRUE, Sp )) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (must restart)\n") );
+
+ *MustRestart = TRUE;
+ return FALSE;
+ }
+ }
+ }
+
+ //
+ // Load up some locals.
+ //
+
+ IndexEntry = Sp->IndexEntry;
+
+ //
+ // Loop until we hit a non-end record which is case-insensitive
+ // lexicgraphically greater than the target string. We also pop
+ // out the middle if we encounter the end record in the Index Root.
+ //
+
+ do {
+
+ //
+ // We last left our hero potentially somewhere in the middle of the
+ // Btree. If he is asking for the next record, we advance one entry
+ // in the current Index Buffer. If we are in an intermediate
+ // Index Buffer (there is a Btree Vcn), then we must move down
+ // through the first record in each intermediate Buffer until we hit
+ // the first leaf buffer (no Btree Vcn).
+ //
+
+ if (NextFlag) {
+
+ LONGLONG IndexBlock;
+
+ if (IndexEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ Sp->IndexEntry =
+ IndexEntry = NtfsNextIndexEntry( IndexEntry );
+
+ NtfsCheckIndexBound( IndexEntry, Sp->IndexHeader );
+
+ while (FlagOn(IndexEntry->Flags, INDEX_ENTRY_NODE)) {
+
+ IndexBlock = NtfsIndexEntryBlock(IndexEntry);
+ Sp += 1;
+
+ //
+ // If the tree is balanced we cannot go too far here.
+ //
+
+ if (Sp >= IndexContext->Base + (ULONG)IndexContext->NumberEntries) {
+
+ ASSERT(Sp < IndexContext->Base + (ULONG)IndexContext->NumberEntries);
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ NtfsUnpinBcb( &Sp->Bcb );
+
+ ReadIndexBuffer( IrpContext,
+ Scb,
+ IndexBlock,
+ FALSE,
+ Sp );
+
+ IndexEntry = Sp->IndexEntry;
+ NtfsCheckIndexBound( IndexEntry, Sp->IndexHeader );
+ }
+
+ //
+ // Check for and mark corrupt if we find an empty leaf.
+ //
+
+ if ((Sp != IndexContext->Base)
+
+ &&
+
+ FlagOn(NtfsFirstIndexEntry(Sp->IndexHeader)->Flags, INDEX_ENTRY_END)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ }
+
+ //
+ // We could be pointing to an end record, either because of
+ // FindFirstIndexEntry or because NextFlag was TRUE, and we
+ // bumped our pointer to an end record. At any rate, if so, we
+ // move up the tree, rereading as required, until we find an entry
+ // which is not an end record, or until we hit the end record in the
+ // root, which means we hit the end of the Index.
+ //
+
+ while (FlagOn(IndexEntry->Flags, INDEX_ENTRY_END)) {
+
+ if (Sp == IndexContext->Base) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (End of Index)\n") );
+
+ return FALSE;
+ }
+
+ Sp -= 1;
+
+ //
+ // If there is no Bcb for the current buffer, then we are continuing
+ // an enumeration.
+ //
+
+ if (Sp->Bcb == NULL) {
+
+ //
+ // Index Root case.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ //
+ // Lookup the attribute record from the Scb.
+ //
+
+ FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+
+ //
+ // Get out if someone has changed the file record.
+ //
+
+ if (Sp->CapturedLsn.QuadPart !=
+ NtfsContainingFileRecord(&IndexContext->AttributeContext)->Lsn.QuadPart) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (must restart)\n") );
+
+ *MustRestart = TRUE;
+ return FALSE;
+ }
+
+ //
+ // Index Buffer case.
+ //
+
+ } else {
+
+ //
+ // If the index buffer is unpinned, then see if we can read it and if it is
+ // unchanged.
+ //
+
+ if (!ReadIndexBuffer( IrpContext, Scb, 0, TRUE, Sp )) {
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (must restart)\n") );
+
+ *MustRestart = TRUE;
+ return FALSE;
+ }
+ }
+ }
+
+ IndexEntry = Sp->IndexEntry;
+ NtfsCheckIndexBound( IndexEntry, Sp->IndexHeader );
+ }
+
+#ifdef _CAIRO_
+
+ //
+ // For a view Index, we either need to call the MatchFunction in the Index
+ // Context (if ValueContainsWildCards is TRUE), or else we look for equality
+ // from the CollateFunction in the Scb.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX)) {
+
+ INDEX_ROW IndexRow;
+ NTSTATUS Status;
+
+ IndexRow.KeyPart.Key = IndexEntry + 1;
+ IndexRow.KeyPart.KeyLength = IndexEntry->AttributeLength;
+
+ //
+ // Now, if ValueContainsWildcards is TRUE, then we are doing multiple
+ // returns via the match function (for NtOfsReadRecords).
+ //
+
+ if (ValueContainsWildcards) {
+
+ IndexRow.DataPart.Data = Add2Ptr( IndexEntry, IndexEntry->DataOffset );
+ IndexRow.DataPart.DataLength = IndexEntry->DataLength;
+
+ if ((Status = IndexContext->MatchFunction(&IndexRow,
+ IndexContext->MatchData)) == STATUS_SUCCESS) {
+
+ IndexContext->Current = Sp;
+ Sp->IndexEntry = IndexEntry;
+
+ return TRUE;
+
+ //
+ // Get out if no more matches.
+ //
+
+ } else if (Status == STATUS_NO_MORE_MATCHES) {
+ return FALSE;
+ }
+ BlindResult = GreaterThan;
+
+ //
+ // Otherwise, we are looking for an exact match via the CollateFunction.
+ //
+
+ } else {
+
+ if ((BlindResult =
+ Scb->ScbType.Index.CollationFunction((PINDEX_KEY)Value,
+ &IndexRow.KeyPart,
+ Scb->ScbType.Index.CollationData)) == EqualTo) {
+
+ IndexContext->Current = Sp;
+ Sp->IndexEntry = IndexEntry;
+
+ return TRUE;
+ }
+ }
+
+ } else
+
+#endif _CAIRO_
+
+ //
+ // At this point, we have a real live entry that we have to check
+ // for a match. Describe its name, see if its a match, and return
+ // TRUE if it is.
+ //
+
+ if (ValueContainsWildcards) {
+
+ if ((*NtfsIsInExpression[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ Value,
+ IndexEntry,
+ IgnoreCase )) {
+
+ IndexContext->Current = Sp;
+ Sp->IndexEntry = IndexEntry;
+
+ DebugTrace( 0, Dbg, ("IndexEntry < %08lx\n", IndexEntry) );
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> TRUE\n") );
+
+ return TRUE;
+ }
+
+ } else {
+
+ if ((*NtfsIsEqual[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ Value,
+ IndexEntry,
+ IgnoreCase )) {
+
+ IndexContext->Current = Sp;
+ Sp->IndexEntry = IndexEntry;
+
+ DebugTrace( 0, Dbg, ("IndexEntry < %08lx\n", IndexEntry) );
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> TRUE\n") );
+
+ return TRUE;
+ }
+ }
+
+ //
+ // If we loop back up, we must set NextFlag to TRUE. We are
+ // currently on a valid non-end entry now. Before looping back,
+ // check to see if we are beyond end of Target string by doing
+ // a case blind compare (IgnoreCase == TRUE).
+ //
+
+ NextFlag = TRUE;
+
+#ifdef _CAIRO_
+
+ //
+ // For enumerations in view indices, keep going and only terminate
+ // on the MatchFunction (BlindResult was set to GreaterThan above).
+ // If it is not an enumeration (no wild cards), we already set BlindResult
+ // when we called the colation routine above.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX))
+
+
+#endif _CAIRO_
+
+ BlindResult = (*NtfsCompareValues[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ UpcaseTableSize,
+ Value,
+ IndexEntry,
+ GreaterThan,
+ TRUE );
+
+ //
+ // The following while clause is not as bad as it looks, and it will
+ // evaluate quickly for the IgnoreCase == TRUE case. We have
+ // already done an IgnoreCase compare above, and stored the result
+ // in BlindResult.
+ //
+ // If we are doing an IgnoreCase TRUE find, we should keep going if
+ // BlindResult is either GreaterThan or EqualTo.
+ //
+ // If we are doing an IgnoreCase FALSE scan, then also continue if
+ // BlindResult is GreaterThan, but if BlindResult is EqualTo, we
+ // can only proceed if we are also GreaterThan or EqualTo case
+ // sensitive (i.e. != LessThan). This means that the Compare Values
+ // call in the following expresssion will never occur in an IgnoreCase
+ // TRUE scan (Windows default).
+ //
+
+ } while ((BlindResult == GreaterThan)
+
+ ||
+
+ ((BlindResult == EqualTo)
+
+ &&
+
+ (IgnoreCase
+
+ ||
+
+ ((*NtfsCompareValues[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ UpcaseTableSize,
+ Value,
+ IndexEntry,
+ GreaterThan,
+ FALSE ) != LessThan))));
+
+ DebugTrace( -1, Dbg, ("FindNextIndexEntry -> FALSE (end of expression)\n") );
+
+ return FALSE;
+}
+
+
+//
+// Internal routine
+//
+
+PATTRIBUTE_RECORD_HEADER
+FindMoveableIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine looks up the index root attribute again after it has potentially
+ moved. As a side effect it reloads any other fields in the index context that
+ could have changed, so callers should always call this routine first before
+ accessing the other values.
+
+Arguments:
+
+ Scb - Scb for the index
+
+ IndexContext - The index context assumed to already be pointing to the
+ active search path
+
+Return Value:
+
+ The address of the Index Root attribute record header.
+
+--*/
+
+{
+ PATTRIBUTE_RECORD_HEADER OldAttribute, Attribute;
+ PINDEX_ROOT IndexRoot;
+ PBCB SavedBcb;
+ BOOLEAN Found;
+
+ PAGED_CODE();
+
+ OldAttribute = IndexContext->OldAttribute;
+
+ //
+ // Temporarily save the Bcb for the index root, and unpin at the end,
+ // so that it does not get unexpectedly unmapped and remapped when our
+ // caller knows it can't actually move.
+ //
+
+ SavedBcb = IndexContext->AttributeContext.FoundAttribute.Bcb;
+ IndexContext->AttributeContext.FoundAttribute.Bcb = NULL;
+
+ NtfsCleanupAttributeContext( &IndexContext->AttributeContext );
+ NtfsInitializeAttributeContext( &IndexContext->AttributeContext );
+
+ try {
+
+ Found =
+ NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &IndexContext->AttributeContext );
+
+ ASSERT(Found);
+
+ //
+ // Now we have to reload our attribute pointer and point to the root
+ //
+
+ IndexContext->OldAttribute =
+ Attribute = NtfsFoundAttribute(&IndexContext->AttributeContext);
+ IndexRoot = (PINDEX_ROOT)NtfsAttributeValue(Attribute);
+
+ //
+ // Reload start of buffer and index header appropriately.
+ //
+
+ IndexContext->Base->StartOfBuffer =
+ (PVOID)NtfsContainingFileRecord(&IndexContext->AttributeContext);
+
+ IndexContext->Base->IndexHeader = &IndexRoot->IndexHeader;
+
+ //
+ // Relocate the index entry on the search path by moving its pointer the
+ // same number of bytes that the attribute moved.
+ //
+
+ IndexContext->Base->IndexEntry = (PINDEX_ENTRY)((PCHAR)IndexContext->Base->IndexEntry +
+ ((PCHAR)Attribute - (PCHAR)OldAttribute));
+ } finally {
+
+ NtfsUnpinBcb( &SavedBcb );
+ }
+
+ return Attribute;
+}
+
+
+PINDEX_ENTRY
+BinarySearchIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_LOOKUP_STACK Sp,
+ IN PVOID Value
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a binary search of the current index buffer, for the first entry
+ in the buffer which is lexigraphically less than or equal to the input value, when
+ compared case-insensitive (if relevant).
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ Sp - Supplies a pointer to a lookup stack entry describing the current buffer.
+
+ Value - Pointer to a value or value expression which should be used to position
+ the IndexContext.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ENTRY IndexTemp, IndexLast;
+ ULONG LowIndex, HighIndex, TryIndex;
+ PINDEX_ENTRY LocalEntries[BINARY_SEARCH_ENTRIES];
+ PINDEX_ENTRY *Table = LocalEntries;
+ ULONG SizeOfTable = BINARY_SEARCH_ENTRIES;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("BinarySearchIndex\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Sp = %08lx\n", Sp) );
+ DebugTrace( 0, Dbg, ("Value = %08lx\n", Value) );
+
+ //
+ // Set up to fill in our binary search vector.
+ //
+
+ IndexHeader = Sp->IndexHeader;
+ IndexTemp = (PINDEX_ENTRY)((PCHAR)IndexHeader + IndexHeader->FirstIndexEntry);
+ IndexLast = (PINDEX_ENTRY)((PCHAR)IndexHeader + IndexHeader->FirstFreeByte);
+
+ //
+ // Fill in the binary search vector, first trying our local vector, and
+ // allocating a larger one if we need to.
+ //
+
+ HighIndex = 0;
+ while (TRUE) {
+
+ while (IndexTemp < IndexLast) {
+
+ //
+ // See if we are about to store off the end of the table. If
+ // so we will have to go allocate a bigger one.
+ //
+
+ if (HighIndex == SizeOfTable) {
+ break;
+ }
+
+ Table[HighIndex] = IndexTemp;
+ IndexTemp = (PINDEX_ENTRY)((PCHAR)IndexTemp + IndexTemp->Length);
+ HighIndex += 1;
+ }
+
+ //
+ // If we got them all, then get out.
+ //
+
+ if (IndexTemp >= IndexLast) {
+ break;
+ }
+
+ if (Table != LocalEntries) {
+
+ ASSERT( Table != LocalEntries );
+ NtfsFreePool( Table );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ //
+ // Otherwise we have to allocate one. Calculate the worst case
+ // and allocate it.
+ //
+
+ SizeOfTable = (IndexHeader->BytesAvailable /
+ (sizeof(INDEX_ENTRY) + sizeof(LARGE_INTEGER))) + 2;
+ Table = (PINDEX_ENTRY *)NtfsAllocatePool(PagedPool, SizeOfTable * sizeof(PINDEX_ENTRY));
+ RtlMoveMemory( Table, LocalEntries, BINARY_SEARCH_ENTRIES * sizeof(PINDEX_ENTRY) );
+ }
+
+ //
+ // Now do a binary search of the buffer to find the lowest entry
+ // (ignoring case) that is <= to the search value. During the
+ // binary search, LowIndex is always maintained as the lowest
+ // possible Index Entry that is <=, and HighIndex is maintained as
+ // the highest possible Index that could be the first <= match.
+ // Thus the loop exits when LowIndex == HighIndex.
+ //
+
+ HighIndex -= 1;
+ LowIndex = 0;
+
+#ifdef _CAIRO_
+
+ //
+ // For view indices, we collate via the CollationFunction in the Scb.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX)) {
+
+ INDEX_KEY IndexKey;
+
+ while (LowIndex != HighIndex) {
+
+ TryIndex = LowIndex + (HighIndex - LowIndex) / 2;
+
+ IndexKey.Key = Table[TryIndex] + 1;
+ IndexKey.KeyLength = Table[TryIndex]->AttributeLength;
+
+ if (!FlagOn( Table[TryIndex]->Flags, INDEX_ENTRY_END )
+
+ &&
+
+ (Scb->ScbType.Index.CollationFunction((PINDEX_KEY)Value,
+ &IndexKey,
+ Scb->ScbType.Index.CollationData) == GreaterThan)) {
+ LowIndex = TryIndex + 1;
+ }
+ else {
+ HighIndex = TryIndex;
+ }
+ }
+
+ } else
+
+#endif _CAIRO_
+
+ while (LowIndex != HighIndex) {
+
+ PWCH UpcaseTable = IrpContext->Vcb->UpcaseTable;
+ ULONG UpcaseTableSize = IrpContext->Vcb->UpcaseTableSize;
+
+ TryIndex = LowIndex + (HighIndex - LowIndex) / 2;
+
+ if (!FlagOn( Table[TryIndex]->Flags, INDEX_ENTRY_END )
+
+ &&
+
+ (*NtfsCompareValues[Scb->ScbType.Index.CollationRule])
+ ( UpcaseTable,
+ UpcaseTableSize,
+ Value,
+ Table[TryIndex],
+ LessThan,
+ TRUE ) == GreaterThan) {
+ LowIndex = TryIndex + 1;
+ }
+ else {
+ HighIndex = TryIndex;
+ }
+ }
+
+ //
+ // Capture the return pointer and free our binary search table.
+ //
+
+ IndexTemp = Table[LowIndex];
+
+ if (Table != LocalEntries) {
+ NtfsFreePool( Table );
+ }
+
+ //
+ // When we exit the loop, we have the answer.
+ //
+
+ DebugTrace( -1, Dbg, ("BinarySearchIndex -> %08lx\n", IndexTemp) );
+
+ return IndexTemp;
+}
+
+
+BOOLEAN
+AddToIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine inserts an index entry into the Btree, possibly performing
+ one or more buffer splits as required.
+
+ The caller has positioned the index context to point to the correct
+ insertion point in a leaf buffer, via calls to FindFirstIndexEntry and
+ FindNextIndexEntry. The index context thus not only points to the
+ insertion point, but it also shows where index entries will have to be
+ promoted in the event of buffer splits.
+
+ This routine employs tail-end recursion, to eliminate the cost of nested
+ calls. For the first insert and all potential subsequent inserts due to
+ bucket splits, all work is completed at the current level in the Btree,
+ and then the InsertIndexEntry input parameter is reloaded (and the lookup
+ stack pointer is adjusted), before looping back in the while loop to do
+ any necessary insert at the next level in the tree.
+
+ Each pass through the loop dispatches to one of four routines to handle
+ the following cases:
+
+ Simple insert in the root
+ Push the root down (add one level to the tree) if the root is full
+ Simple insert in an index allocation buffer
+ Buffer split of a full index allocation buffer
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ InsertIndexEntry - pointer to the index entry to insert.
+
+ IndexContext - Index context describing the path to the insertion point.
+
+ QuickIndex - If specified we store the location of the index added.
+
+Return Value:
+
+ FALSE -- if the stack did not have to be pushed
+ TRUE -- if the stack was pushed
+
+--*/
+
+{
+ PFCB Fcb = Scb->Fcb;
+ PINDEX_LOOKUP_STACK Sp = IndexContext->Current;
+ BOOLEAN DeleteIt = FALSE;
+ BOOLEAN FirstPass = TRUE;
+ BOOLEAN StackWasPushed = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("AddToIndex\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ //
+ // This routine uses "tail-end" recursion, so we just want to keep looping
+ // until we do an insert (either in the Index Root or the Index Allocation)
+ // that does not require a split.
+ //
+
+ while (TRUE) {
+
+ IndexContext->Current = Sp;
+
+ //
+ // First see if this is an insert in the root or a leaf.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ FileRecord = (PFILE_RECORD_SEGMENT_HEADER)Sp->StartOfBuffer;
+
+ //
+ // Now see if there is enough room to do a simple insert, or if
+ // there is not enough room, see if we are small enough ourselves
+ // to demand the room be made anyway.
+ //
+
+ if ((InsertIndexEntry->Length <=
+ (USHORT) (FileRecord->BytesAvailable - FileRecord->FirstFreeByte))
+
+ ||
+
+ (InsertIndexEntry->Length + Sp->IndexHeader->BytesAvailable <
+ Scb->Vcb->BigEnoughToMove)) {
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ do {
+
+ //
+ // If not the first pass, we have to lookup the attribute
+ // again.
+ //
+
+ Attribute = FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+
+ //
+ // If FALSE is returned, then the space was not allocated and
+ // we have too loop back and try again. Second time must work.
+ //
+
+ } while (!NtfsChangeAttributeSize( IrpContext,
+ Fcb,
+ Attribute->RecordLength + InsertIndexEntry->Length,
+ &IndexContext->AttributeContext ));
+
+ InsertSimpleRoot( IrpContext,
+ Scb,
+ InsertIndexEntry,
+ DeleteIt,
+ IndexContext );
+
+ //
+ // If we have a quick index then store a buffer offset of zero
+ // to indicate the root index.
+ //
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->BufferOffset = 0;
+ }
+
+ DebugTrace( -1, Dbg, ("AddToIndex -> VOID\n") );
+
+ return StackWasPushed;
+
+ //
+ // Otherwise we have to push the current root down a level into
+ // the allocation, and try our insert again by looping back.
+ //
+
+ } else {
+
+ //
+ // Our insertion point will now also be pushed, so we
+ // have to increment the stack pointer.
+ //
+
+ Sp += 1;
+
+ if (Sp >= IndexContext->Base + (ULONG)IndexContext->NumberEntries) {
+ NtfsGrowLookupStack( Scb,
+ IndexContext,
+ &Sp );
+ }
+
+ PushIndexRoot( IrpContext, Scb, IndexContext );
+ StackWasPushed = TRUE;
+ continue;
+ }
+
+ //
+ // Otherwise this insert is in the Index Allocation, not the Index
+ // Root.
+ //
+
+ } else {
+
+ //
+ // See if there is enough room to do a simple insert.
+ //
+
+ if (InsertIndexEntry->Length <=
+ (USHORT)(Sp->IndexHeader->BytesAvailable - Sp->IndexHeader->FirstFreeByte)) {
+
+ InsertSimpleAllocation( IrpContext,
+ Scb,
+ InsertIndexEntry,
+ DeleteIt,
+ Sp,
+ QuickIndex );
+
+ DebugTrace( -1, Dbg, ("AddToIndex -> VOID\n") );
+
+ return StackWasPushed;
+
+ //
+ // Otherwise, we have to do a buffer split in the allocation.
+ //
+
+ } else {
+
+ //
+ // Call this local routine to do the buffer split. It
+ // returns a pointer to a new entry to insert, which is
+ // allocated in the nonpaged pool. InsertSimpleRoot,
+ // InsertSimpleAllocation, and InsertWithBufferSplit are
+ // all required to deallocate the InsertIndexEntry they
+ // are called with under all circumstances if we pass
+ // them TRUE for the DeleteIt parameter.
+ //
+
+ InsertIndexEntry = InsertWithBufferSplit( IrpContext,
+ Scb,
+ InsertIndexEntry,
+ DeleteIt,
+ IndexContext,
+ QuickIndex );
+
+ //
+ // Clear the QuickIndex pointer so we don't corrupt the captured
+ // information.
+ //
+
+ QuickIndex = NULL;
+
+ //
+ // Now we have to delete this index entry, since it was dynamically
+ // allocated by InsertWithBufferSplit.
+ //
+
+ DeleteIt = TRUE;
+
+ //
+ // The middle entry from the old buffer must now get
+ // inserted at the insertion point in its parent.
+ //
+
+ Sp -= 1;
+ continue;
+ }
+ }
+ }
+}
+
+
+VOID
+InsertSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to do a simple insertion of a new index entry into the
+ root, when it is known that it will fit. It calls a routine common wiht
+ restart to do the insert, and then logs the insert.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ InsertIndexEntry - Pointer to the index entry to insert.
+
+ DeleteIt - TRUE if this routine should unconditionally delete InsertIndexEntry.
+
+ IndexContext - Index context describing the position in the root at which
+ the insert is to occur.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PINDEX_ENTRY BeforeIndexEntry;
+ PATTRIBUTE_ENUMERATION_CONTEXT Context = &IndexContext->AttributeContext;
+ PVCB Vcb;
+
+ PAGED_CODE();
+
+ Vcb = Scb->Vcb;
+
+ DebugTrace( +1, Dbg, ("InsertSimpleRoot\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ try {
+
+ //
+ // Extract all of the updates required by the restart routine we
+ // will call.
+ //
+
+ FileRecord = NtfsContainingFileRecord( Context );
+ Attribute = NtfsFoundAttribute( Context );
+ BeforeIndexEntry = IndexContext->Base->IndexEntry;
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Call the same routine used by restart to actually apply the
+ // update.
+ //
+
+ NtfsRestartInsertSimpleRoot( IrpContext,
+ InsertIndexEntry,
+ FileRecord,
+ Attribute,
+ BeforeIndexEntry );
+
+ CheckRoot();
+
+ //
+ // Now that the Index Entry is guaranteed to be in place, log
+ // this update. Note that the new record is now at the address
+ // we calculated in BeforeIndexEntry.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ AddIndexEntryRoot,
+ BeforeIndexEntry,
+ BeforeIndexEntry->Length,
+ DeleteIndexEntryRoot,
+ NULL,
+ 0,
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)BeforeIndexEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ } finally {
+
+ DebugUnwind( InsertSimpleRoot );
+
+ //
+ // If we failed, it must be because we failed to write the
+ // log record. If that happened, then the record will not be
+ // deleted by the transaction abort, so we have to do it here
+ // by hand.
+ //
+
+ if (AbnormalTermination()) {
+
+ NtfsRestartDeleteSimpleRoot( IrpContext,
+ BeforeIndexEntry,
+ FileRecord,
+ Attribute );
+ }
+
+ if (DeleteIt) {
+
+ NtfsFreePool(InsertIndexEntry);
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("InsertSimpleRoot -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartInsertSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN PINDEX_ENTRY BeforeIndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This is a restart routine used both in normal operation and during restart.
+ It is called to do a simple insertion of a new index entry into the
+ root, when it is known that it will fit. It does no logging.
+
+Arguments:
+
+ InsertIndexEntry - Pointer to the index entry to insert.
+
+ FileRecord - Pointer to the file record in which the insert is to occur.
+
+ Attribute - Pointer to the attribute record header for the index root.
+
+ BeforeIndexEntry - Pointer to the index entry which is currently sitting
+ at the point at which the insert is to occur.
+
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_ROOT IndexRoot;
+ PINDEX_HEADER IndexHeader;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartInsertSimpleRoot\n") );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) );
+ DebugTrace( 0, Dbg, ("BeforeIndexEntry = %08lx\n", BeforeIndexEntry) );
+
+ //
+ // Form some pointers within the attribute value.
+ //
+
+ IndexRoot = (PINDEX_ROOT)NtfsAttributeValue(Attribute);
+ IndexHeader = &IndexRoot->IndexHeader;
+
+ //
+ // Grow the space for our attribute record as required.
+ //
+
+ NtfsRestartChangeAttributeSize( IrpContext,
+ FileRecord,
+ Attribute,
+ Attribute->RecordLength +
+ InsertIndexEntry->Length );
+
+ //
+ // Now move the tail end of the index to make room for the new entry.
+ //
+
+ RtlMoveMemory( (PCHAR)BeforeIndexEntry + InsertIndexEntry->Length,
+ BeforeIndexEntry,
+ ((PCHAR)IndexHeader + IndexHeader->FirstFreeByte) -
+ (PCHAR)BeforeIndexEntry );
+
+ //
+ // Move the new Index Entry into place. The index entry may either
+ // be a complete index entry, or it may be in pointer form.
+ //
+
+ if (FlagOn(InsertIndexEntry->Flags, INDEX_ENTRY_POINTER_FORM)) {
+
+ RtlMoveMemory( BeforeIndexEntry, InsertIndexEntry, sizeof(INDEX_ENTRY) );
+#ifndef _CAIRO_
+ RtlMoveMemory( (PVOID)(BeforeIndexEntry + 1),
+ *(PVOID *)(InsertIndexEntry + 1),
+ (ULONG)InsertIndexEntry->Length - sizeof(INDEX_ENTRY) );
+#else _CAIRO_
+ RtlMoveMemory( (PVOID)(BeforeIndexEntry + 1),
+ *(PVOID *)(InsertIndexEntry + 1),
+ InsertIndexEntry->AttributeLength );
+
+ //
+ // In pointer form the Data Pointer follows the key pointer, but there is
+ // none for normal directory indices.
+ //
+
+ if (*(PVOID *)((PCHAR)InsertIndexEntry + sizeof(INDEX_ENTRY) + sizeof(ULONG)) != NULL) {
+ RtlMoveMemory( (PVOID)((PCHAR)BeforeIndexEntry + InsertIndexEntry->DataOffset),
+ *(PVOID *)((PCHAR)InsertIndexEntry + sizeof(INDEX_ENTRY) + sizeof(ULONG)),
+ InsertIndexEntry->DataLength );
+ }
+
+#endif _CAIRO_
+
+ ClearFlag( BeforeIndexEntry->Flags, INDEX_ENTRY_POINTER_FORM );
+
+ } else {
+
+ RtlMoveMemory( BeforeIndexEntry, InsertIndexEntry, InsertIndexEntry->Length );
+ }
+
+ //
+ // Update the index header by the space we grew by.
+ //
+
+ Attribute->Form.Resident.ValueLength += InsertIndexEntry->Length;
+ IndexHeader->FirstFreeByte += InsertIndexEntry->Length;
+ IndexHeader->BytesAvailable += InsertIndexEntry->Length;
+
+ DebugTrace( -1, Dbg, ("NtfsRestartInsertSimpleRoot -> VOID\n") );
+}
+
+
+VOID
+PushIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to push the index root down a level, thus adding a
+ level to the Btree. If the Index Allocation and Bitmap attributes for this
+ index do not already exist, then they are created here prior to pushing the
+ root down. This routine performs the push down and logs the changes (either
+ directly or by calling routines which log their own changes).
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexContext - Index context describing the position in the root at which
+ the insert is to occur.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AllocationContext;
+ ATTRIBUTE_ENUMERATION_CONTEXT BitMapContext;
+ LARGE_MCB Mcb;
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ PINDEX_HEADER IndexHeaderR, IndexHeaderA;
+ PINDEX_LOOKUP_STACK Sp;
+ ULONG SizeToMove;
+ USHORT AttributeFlags;
+ LONGLONG EndOfValidData;
+
+ struct {
+ INDEX_ROOT IndexRoot;
+ INDEX_ENTRY IndexEntry;
+ LONGLONG IndexBlock;
+ } R;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("PushIndexRoot\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ //
+ // Initialize everything (only Mcb can fail), then set up to cleanup
+ // on the way out.
+ //
+
+ RtlZeroMemory( &R, sizeof(R) );
+ FsRtlInitializeLargeMcb( &Mcb, NonPagedPool );
+ NtfsInitializeAttributeContext( &AllocationContext );
+ NtfsInitializeAttributeContext( &BitMapContext );
+
+ //
+ // Allocate a buffer to save away the current index root, as we will
+ // have to start by deleting it.
+ //
+
+ SizeToMove = IndexContext->Base->IndexHeader->FirstFreeByte;
+ IndexHeaderR = NtfsAllocatePool(PagedPool, SizeToMove );
+
+ try {
+
+ //
+ // Save away the current index root, then delete it. This should
+ // insure that we have enough space to create/extend the index allocation
+ // and bitmap attributes.
+ //
+
+ AttributeFlags = NtfsFoundAttribute(&IndexContext->AttributeContext)->Flags;
+ RtlMoveMemory( IndexHeaderR,
+ IndexContext->Base->IndexHeader,
+ SizeToMove );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Scb->Fcb,
+ TRUE,
+ FALSE,
+ &IndexContext->AttributeContext );
+
+ //
+ // If the IndexAllocation isn't there, then we have to create both
+ // the Index Allocation and Bitmap attributes.
+ //
+
+ if (!NtfsLookupAttributeByName( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $INDEX_ALLOCATION,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &AllocationContext )) {
+
+ //
+ // Allocate the Index Allocation attribute. Always allocate at
+ // least one cluster.
+ //
+
+ EndOfValidData = Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ if ((ULONG) EndOfValidData < Scb->Vcb->BytesPerCluster) {
+
+ EndOfValidData = Scb->Vcb->BytesPerCluster;
+ }
+
+ NtfsAllocateAttribute( IrpContext,
+ Scb,
+ $INDEX_ALLOCATION,
+ &Scb->AttributeName,
+ 0,
+ TRUE,
+ TRUE,
+ EndOfValidData,
+ NULL );
+
+ Scb->Header.AllocationSize.QuadPart = EndOfValidData;
+
+ SetFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+
+ //
+ // Now create the BitMap attribute.
+ //
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Scb->Fcb,
+ $BITMAP,
+ &Scb->AttributeName,
+ &Li0,
+ sizeof(LARGE_INTEGER),
+ 0,
+ NULL,
+ TRUE,
+ &BitMapContext );
+ }
+
+ //
+ // Take some pains here to preserve the IndexContext for the case that
+ // we are called from AddToIndex, when it is called from DeleteFromIndex,
+ // because we still need some of the stack then. The caller must have
+ // insured the stack is big enough for him. Move all but two entries,
+ // because we do not need to move the root, and we cannot move the last
+ // entry since it would go off the end of the structure!
+ //
+
+ ASSERT(IndexContext->NumberEntries > 2);
+
+ //
+ // Do an unpin on the entry that will be overwritten.
+ //
+
+ NtfsUnpinBcb( &IndexContext->Base[IndexContext->NumberEntries - 1].Bcb );
+
+ RtlMoveMemory( IndexContext->Base + 2,
+ IndexContext->Base + 1,
+ (IndexContext->NumberEntries - 2) * sizeof(INDEX_LOOKUP_STACK) );
+
+ //
+ // Now point our local pointer to where the root will be pushed to, and
+ // clear the Bcb pointer in the stack there, since it was copied above.
+ // Advance top and current because of the move.
+ //
+
+ Sp = IndexContext->Base + 1;
+ Sp->Bcb = NULL;
+ IndexContext->Top += 1;
+ IndexContext->Current += 1;
+
+ //
+ // Allocate a buffer to hold the pushed down entries.
+ //
+
+ IndexBuffer = GetIndexBuffer( IrpContext, Scb, Sp, &EndOfValidData );
+
+ //
+ // Point now to the new index header.
+ //
+
+ IndexHeaderA = Sp->IndexHeader;
+
+ //
+ // Now do the push down and fix up the IndexEntry pointer for
+ // the new buffer.
+ //
+
+ SizeToMove = IndexHeaderR->FirstFreeByte - IndexHeaderR->FirstIndexEntry;
+ RtlMoveMemory( NtfsFirstIndexEntry(IndexHeaderA),
+ NtfsFirstIndexEntry(IndexHeaderR),
+ SizeToMove );
+
+ Sp->IndexEntry = (PINDEX_ENTRY)((PCHAR)(Sp-1)->IndexEntry +
+ ((PCHAR)IndexHeaderA - (PCHAR)((Sp-1)->IndexHeader)) +
+ (IndexHeaderA->FirstIndexEntry -
+ IndexHeaderR->FirstIndexEntry));
+
+ IndexHeaderA->FirstFreeByte += SizeToMove;
+ IndexHeaderA->Flags = IndexHeaderR->Flags;
+
+ //
+ // Finally, log the pushed down buffer.
+ //
+
+ CheckBuffer(IndexBuffer);
+
+ IndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ UpdateNonresidentValue,
+ IndexBuffer,
+ FIELD_OFFSET( INDEX_ALLOCATION_BUFFER,IndexHeader ) +
+ IndexHeaderA->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ 0,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Remember if we extended the valid data for this Scb.
+ //
+
+ if (EndOfValidData > Scb->Header.ValidDataLength.QuadPart) {
+
+ Scb->Header.ValidDataLength.QuadPart = EndOfValidData;
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // Now initialize an image of the new root.
+ //
+
+#ifdef _CAIRO_
+ if (!FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX)) {
+#endif _CAIRO_
+ R.IndexRoot.IndexedAttributeType = Scb->ScbType.Index.AttributeBeingIndexed;
+ R.IndexRoot.CollationRule = Scb->ScbType.Index.CollationRule;
+#ifdef _CAIRO_
+ } else {
+ R.IndexRoot.IndexedAttributeType = $UNUSED;
+ R.IndexRoot.CollationRule = 0;
+ }
+#endif _CAIRO_
+ R.IndexRoot.BytesPerIndexBuffer = Scb->ScbType.Index.BytesPerIndexBuffer;
+ R.IndexRoot.BlocksPerIndexBuffer = Scb->ScbType.Index.BlocksPerIndexBuffer;
+ R.IndexRoot.IndexHeader.FirstIndexEntry = (PCHAR)&R.IndexEntry -
+ (PCHAR)&R.IndexRoot.IndexHeader;
+ R.IndexRoot.IndexHeader.FirstFreeByte =
+ R.IndexRoot.IndexHeader.BytesAvailable = QuadAlign(sizeof(INDEX_HEADER)) +
+ QuadAlign(sizeof(INDEX_ENTRY)) +
+ sizeof(LONGLONG);
+ SetFlag( R.IndexRoot.IndexHeader.Flags, INDEX_NODE );
+
+ R.IndexEntry.Length = sizeof(INDEX_ENTRY) + sizeof(LONGLONG);
+ R.IndexEntry.Flags = INDEX_ENTRY_NODE | INDEX_ENTRY_END;
+ R.IndexBlock = IndexBuffer->ThisBlock;
+
+ //
+ // Now recreate the index root.
+ //
+
+ NtfsCleanupAttributeContext( &IndexContext->AttributeContext );
+ NtfsCreateAttributeWithValue( IrpContext,
+ Scb->Fcb,
+ $INDEX_ROOT,
+ &Scb->AttributeName,
+ (PVOID)&R,
+ sizeof(R),
+ AttributeFlags,
+ NULL,
+ TRUE,
+ &IndexContext->AttributeContext );
+
+ //
+ // We just pushed the index root, so let's find it again and
+ // fix up the caller's context. Note that he will try to
+ // recalculate the IndexEntry pointer, but we know that it
+ // has to change to point to the single entry in the new root.
+ //
+
+ FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+ (Sp-1)->IndexEntry = NtfsFirstIndexEntry((Sp-1)->IndexHeader);
+
+ } finally {
+
+ DebugUnwind( PushIndexRoot );
+
+ NtfsFreePool( IndexHeaderR );
+ FsRtlUninitializeLargeMcb( &Mcb );
+ NtfsCleanupAttributeContext( &AllocationContext );
+ NtfsCleanupAttributeContext( &BitMapContext );
+ }
+
+ DebugTrace( -1, Dbg, ("PushIndexRoot -> VOID\n") );
+}
+
+
+VOID
+InsertSimpleAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN PINDEX_LOOKUP_STACK Sp,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a simple insert in an index buffer in the index
+ allocation. It calls a routine common with restart to do the insert,
+ and then it logs the change.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ InsertIndexEntry - Address of the index entry to be inserted.
+
+ DeleteIt - TRUE if this routine should unconditionally delete InsertIndexEntry.
+
+ Sp - Pointer to the lookup stack location describing the insertion
+ point.
+
+ QuickIndex - If specified we store the location of the index added.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ PINDEX_ENTRY BeforeIndexEntry;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("InsertSimpleAllocation\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("Sp = %08lx\n", Sp) );
+
+ try {
+
+ //
+ // Extract all of the updates required by the restart routine we
+ // will call.
+ //
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+ BeforeIndexEntry = Sp->IndexEntry;
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Call the same routine used by restart to actually apply the
+ // update.
+ //
+
+ NtfsRestartInsertSimpleAllocation( InsertIndexEntry,
+ IndexBuffer,
+ BeforeIndexEntry );
+
+ CheckBuffer(IndexBuffer);
+
+ //
+ // Now that the Index Entry is guaranteed to be in place, log
+ // this update. Note that the new record is now at the address
+ // we calculated in BeforeIndexEntry.
+ //
+
+ IndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ AddIndexEntryAllocation,
+ BeforeIndexEntry,
+ BeforeIndexEntry->Length,
+ DeleteIndexEntryAllocation,
+ NULL,
+ 0,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)BeforeIndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Update the quick index buffer if we have it.
+ //
+
+ if (ARGUMENT_PRESENT( QuickIndex )) {
+
+ QuickIndex->ChangeCount = Scb->ScbType.Index.ChangeCount;
+ QuickIndex->BufferOffset = PtrOffset( Sp->StartOfBuffer, Sp->IndexEntry );
+ QuickIndex->CapturedLsn = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->Lsn;
+ QuickIndex->IndexBlock = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->ThisBlock;
+ }
+
+ } finally {
+
+ DebugUnwind( InsertSimpleAllocation );
+
+ //
+ // If we failed, it must be because we failed to write the
+ // log record. If that happened, then the record will not be
+ // deleted by the transaction abort, so we have to do it here
+ // by hand.
+ //
+
+ if (AbnormalTermination()) {
+
+ NtfsRestartDeleteSimpleAllocation( BeforeIndexEntry,
+ IndexBuffer );
+ }
+
+ if (DeleteIt) {
+
+ NtfsFreePool(InsertIndexEntry);
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("InsertSimpleAllocation -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartInsertSimpleAllocation (
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer,
+ IN PINDEX_ENTRY BeforeIndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a simple insert in an index buffer in the index
+ allocation. It performs this work either in the running system, or
+ when called by restart. It does no logging.
+
+Arguments:
+
+ InsertIndexEntry - Address of the index entry to be inserted.
+
+ IndexBuffer - Pointer to the index buffer into which the insert is to
+ occur.
+
+ BeforeIndexEntry - Pointer to the index entry currently residing at
+ the insertion point.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_HEADER IndexHeader;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartInsertSimpleAllocation\n") );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexBuffer = %08lx\n", IndexBuffer) );
+ DebugTrace( 0, Dbg, ("BeforeIndexEntry = %08lx\n", BeforeIndexEntry) );
+
+ //
+ // Form some pointers within the attribute value.
+ //
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+
+ //
+ // Now move the tail end of the index to make room for the new entry.
+ //
+
+ RtlMoveMemory( (PCHAR)BeforeIndexEntry + InsertIndexEntry->Length,
+ BeforeIndexEntry,
+ ((PCHAR)IndexHeader + IndexHeader->FirstFreeByte) -
+ (PCHAR)BeforeIndexEntry );
+
+ //
+ // Move the new Index Entry into place. The index entry may either
+ // be a complete index entry, or it may be in pointer form.
+ //
+
+ if (FlagOn(InsertIndexEntry->Flags, INDEX_ENTRY_POINTER_FORM)) {
+
+ RtlMoveMemory( BeforeIndexEntry, InsertIndexEntry, sizeof(INDEX_ENTRY) );
+#ifndef _CAIRO_
+ RtlMoveMemory( (PVOID)(BeforeIndexEntry + 1),
+ *(PVOID *)(InsertIndexEntry + 1),
+ (ULONG)InsertIndexEntry->Length - sizeof(INDEX_ENTRY) );
+#else _CAIRO_
+ RtlMoveMemory( (PVOID)(BeforeIndexEntry + 1),
+ *(PVOID *)(InsertIndexEntry + 1),
+ InsertIndexEntry->AttributeLength );
+
+ //
+ // In pointer form the Data Pointer follows the key pointer, but there is
+ // none for normal directory indices.
+ //
+
+ if (*(PVOID *)((PCHAR)InsertIndexEntry + sizeof(INDEX_ENTRY) + sizeof(ULONG)) != NULL) {
+ RtlMoveMemory( (PVOID)((PCHAR)BeforeIndexEntry + InsertIndexEntry->DataOffset),
+ *(PVOID *)((PCHAR)InsertIndexEntry + sizeof(INDEX_ENTRY) + sizeof(ULONG)),
+ InsertIndexEntry->DataLength );
+ }
+
+#endif _CAIRO_
+
+ ClearFlag( BeforeIndexEntry->Flags, INDEX_ENTRY_POINTER_FORM );
+
+ } else {
+
+ RtlMoveMemory( BeforeIndexEntry, InsertIndexEntry, InsertIndexEntry->Length );
+ }
+
+ //
+ // Update the index header by the space we grew by.
+ //
+
+ IndexHeader->FirstFreeByte += InsertIndexEntry->Length;
+
+ DebugTrace( -1, Dbg, ("NtfsRestartInsertSimpleAllocation -> VOID\n") );
+}
+
+
+PINDEX_ENTRY
+InsertWithBufferSplit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN BOOLEAN DeleteIt,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to perform an insert in the index allocation, when
+ it is known that a buffer split is necessary. It splits the buffer in
+ half, inserts the new entry in the appropriate half, fixes the Vcn pointer
+ in the current parent, and returns a pointer to a new entry which is being
+ promoted to insert at the next level up.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ InsertIndexEntry - Address of the index entry to be inserted.
+
+ DeleteIt - TRUE if this routine should unconditionally delete InsertIndexEntry.
+
+ IndexContext - Index context describing the position in the stack at which
+ the insert with split is to occur.
+
+ QuickIndex - If specified we store the location of the index added.
+
+Return Value:
+
+ Pointer to the index entry which must now be inserted at the next level.
+
+--*/
+
+{
+ PINDEX_ALLOCATION_BUFFER IndexBuffer, IndexBuffer2;
+ PINDEX_HEADER IndexHeader, IndexHeader2;
+ PINDEX_ENTRY BeforeIndexEntry, MiddleIndexEntry, MovingIndexEntry;
+ PINDEX_ENTRY ReturnIndexEntry;
+ INDEX_LOOKUP_STACK Stack2;
+ PINDEX_LOOKUP_STACK Sp;
+ ULONG LengthToMove;
+ ULONG Buffer2Length;
+ LONGLONG EndOfValidData;
+
+ struct {
+ INDEX_ENTRY IndexEntry;
+ LONGLONG IndexBlock;
+ } NewEnd;
+
+ PVCB Vcb;
+
+ Vcb = Scb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("InsertWithBufferSplit\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("InsertIndexEntry = %08lx\n", InsertIndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ Stack2.Bcb = NULL;
+ Sp = IndexContext->Current;
+
+ try {
+
+ //
+ // Extract all of the updates required by the restart routine we
+ // will call.
+ //
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+ IndexHeader = &IndexBuffer->IndexHeader;
+ BeforeIndexEntry = Sp->IndexEntry;
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Allocate an index buffer to take the second half of the splitting
+ // one.
+ //
+
+ IndexBuffer2 = GetIndexBuffer( IrpContext,
+ Scb,
+ &Stack2,
+ &EndOfValidData );
+
+ IndexHeader2 = &IndexBuffer2->IndexHeader;
+
+ //
+ // Scan to find the middle index entry that we will promote to
+ // the next level up, and the next one after him.
+ //
+
+ MiddleIndexEntry = NtfsFirstIndexEntry(IndexHeader);
+ NtfsCheckIndexBound( MiddleIndexEntry, IndexHeader );
+
+ if (MiddleIndexEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ while (((ULONG)((PCHAR)MiddleIndexEntry - (PCHAR)IndexHeader) +
+ (ULONG)MiddleIndexEntry->Length) < IndexHeader->BytesAvailable / 2) {
+
+ MovingIndexEntry = MiddleIndexEntry;
+ MiddleIndexEntry = NtfsNextIndexEntry(MiddleIndexEntry);
+
+ NtfsCheckIndexBound( MiddleIndexEntry, IndexHeader );
+
+ if (MiddleIndexEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ }
+
+ //
+ // We found an entry to elevate but if the next entry is the end
+ // record we want to go back one entry.
+ //
+
+ if (FlagOn( NtfsNextIndexEntry(MiddleIndexEntry)->Flags, INDEX_ENTRY_END )) {
+
+ MiddleIndexEntry = MovingIndexEntry;
+ }
+ MovingIndexEntry = NtfsNextIndexEntry(MiddleIndexEntry);
+
+ NtfsCheckIndexBound( MovingIndexEntry, IndexHeader );
+
+ if (MovingIndexEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ //
+ // Allocate space to hold this middle entry, and copy it out.
+ //
+
+ ReturnIndexEntry = NtfsAllocatePool( NonPagedPool,
+ MiddleIndexEntry->Length +
+ sizeof(LONGLONG) );
+ RtlMoveMemory( ReturnIndexEntry,
+ MiddleIndexEntry,
+ MiddleIndexEntry->Length );
+
+ if (!FlagOn(ReturnIndexEntry->Flags, INDEX_ENTRY_NODE)) {
+ SetFlag( ReturnIndexEntry->Flags, INDEX_ENTRY_NODE );
+ ReturnIndexEntry->Length += sizeof(LONGLONG);
+ }
+
+ //
+ // Now move the second half of the splitting buffer over to the
+ // new one, and fix it up.
+ //
+
+ LengthToMove = IndexHeader->FirstFreeByte - ((PCHAR)MovingIndexEntry -
+ (PCHAR)IndexHeader);
+
+ RtlMoveMemory( NtfsFirstIndexEntry(IndexHeader2),
+ MovingIndexEntry,
+ LengthToMove );
+
+ IndexHeader2->FirstFreeByte += LengthToMove;
+ IndexHeader2->Flags = IndexHeader->Flags;
+
+ //
+ // Now the new Index Buffer is done, so lets log its contents.
+ //
+
+ Buffer2Length = FIELD_OFFSET( INDEX_ALLOCATION_BUFFER,IndexHeader ) +
+ IndexHeader2->FirstFreeByte;
+
+ CheckBuffer(IndexBuffer2);
+
+ IndexBuffer2->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Stack2.Bcb,
+ UpdateNonresidentValue,
+ IndexBuffer2,
+ Buffer2Length,
+ Noop,
+ NULL,
+ 0,
+ LlBytesFromIndexBlocks( IndexBuffer2->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ 0,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Remember if we extended the valid data for this Scb.
+ //
+
+ if (EndOfValidData > Scb->Header.ValidDataLength.QuadPart) {
+
+ Scb->Header.ValidDataLength.QuadPart = EndOfValidData;
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ TRUE,
+ TRUE );
+ }
+
+ //
+ // Now let's create the image of the new end record for the
+ // splitting index buffer.
+ //
+
+ RtlZeroMemory( &NewEnd.IndexEntry, sizeof(INDEX_ENTRY) );
+ NewEnd.IndexEntry.Length = sizeof(INDEX_ENTRY);
+ NewEnd.IndexEntry.Flags = INDEX_ENTRY_END;
+
+ if (FlagOn(MiddleIndexEntry->Flags, INDEX_ENTRY_NODE)) {
+ NewEnd.IndexEntry.Length += sizeof(LONGLONG);
+ SetFlag( NewEnd.IndexEntry.Flags, INDEX_ENTRY_NODE );
+ NewEnd.IndexBlock = NtfsIndexEntryBlock(MiddleIndexEntry);
+ }
+
+ //
+ // Write a log record to set the new end of the splitting buffer.
+ //
+
+ IndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ WriteEndOfIndexBuffer,
+ &NewEnd,
+ NewEnd.IndexEntry.Length,
+ WriteEndOfIndexBuffer,
+ MiddleIndexEntry,
+ MiddleIndexEntry->Length + LengthToMove,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)MiddleIndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Now call the restart routine to write the new end of the index
+ // buffer.
+ //
+
+ NtfsRestartWriteEndOfIndex( IndexHeader,
+ MiddleIndexEntry,
+ (PINDEX_ENTRY)&NewEnd,
+ NewEnd.IndexEntry.Length );
+
+ CheckBuffer(IndexBuffer);
+
+ //
+ // Now that we are done splitting IndexBuffer and IndexBuffer2, we
+ // need to figure out which one our original entry was inserting into,
+ // and do the simple insert. Going into the first half is trivial,
+ // and follows:
+ //
+
+ if (BeforeIndexEntry < MovingIndexEntry) {
+
+ InsertSimpleAllocation( IrpContext, Scb, InsertIndexEntry, FALSE, Sp, QuickIndex );
+
+ //
+ // If it is going into the second half, we just have to fix up the
+ // stack descriptor for the buffer we allocated, and do the insert
+ // there. To fix it up we just have to do a little arithmetic to
+ // find the insert position.
+ //
+
+ } else {
+
+ Stack2.IndexEntry = (PINDEX_ENTRY)((PCHAR)BeforeIndexEntry +
+ ((PCHAR)NtfsFirstIndexEntry(IndexHeader2) -
+ (PCHAR)MovingIndexEntry));
+ InsertSimpleAllocation( IrpContext,
+ Scb,
+ InsertIndexEntry,
+ FALSE,
+ &Stack2,
+ QuickIndex );
+ }
+
+ //
+ // Now we just have to set the correct Vcns in the two index entries
+ // that point to IndexBuffer and IndexBuffer2 after the split. The
+ // first one is easy; its Vcn goes into the IndexEntry we are
+ // returning with to insert in our parent. The second one we have
+ // have to fix up is the index entry pointing to the buffer that
+ // split, since it must now point to the new buffer. It should look
+ // like this:
+ //
+ // ParentIndexBuffer: ...(ReturnIndexEntry) (ParentIndexEntry)...
+ // | |
+ // | |
+ // V V
+ // IndexBuffer IndexBuffer2
+ //
+
+ NtfsSetIndexEntryBlock( ReturnIndexEntry, IndexBuffer->ThisBlock );
+
+ //
+ // Decrement our stack pointer to point to the stack entry describing
+ // our parent.
+ //
+
+ Sp -= 1;
+
+ //
+ // First handle the case where our parent is the Index Root.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_ENUMERATION_CONTEXT Context = &IndexContext->AttributeContext;
+
+ Attribute = FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ SetIndexEntryVcnRoot,
+ &IndexBuffer2->ThisBlock,
+ sizeof(LONGLONG),
+ SetIndexEntryVcnRoot,
+ &IndexBuffer->ThisBlock,
+ sizeof(LONGLONG),
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)Sp->IndexEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Otherwise, our parent is also an Index Buffer.
+ //
+
+ } else {
+
+ PINDEX_ALLOCATION_BUFFER ParentIndexBuffer;
+
+ ParentIndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( ParentIndexBuffer->ThisBlock,
+ Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ ParentIndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ SetIndexEntryVcnAllocation,
+ &IndexBuffer2->ThisBlock,
+ sizeof(LONGLONG),
+ SetIndexEntryVcnAllocation,
+ &IndexBuffer->ThisBlock,
+ sizeof(LONGLONG),
+ LlBytesFromIndexBlocks( ParentIndexBuffer->ThisBlock,
+ Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)Sp->IndexEntry - (PCHAR)ParentIndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+ }
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtfsRestartSetIndexBlock( Sp->IndexEntry,
+ IndexBuffer2->ThisBlock );
+
+ } finally {
+
+ DebugUnwind( InsertWithBufferSplit );
+
+ NtfsUnpinBcb( &Stack2.Bcb );
+
+ if (DeleteIt) {
+
+ NtfsFreePool(InsertIndexEntry);
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("InsertWithBufferSplit -> VOID\n") );
+
+ return ReturnIndexEntry;
+}
+
+
+VOID
+NtfsRestartWriteEndOfIndex (
+ IN PINDEX_HEADER IndexHeader,
+ IN PINDEX_ENTRY OverwriteIndexEntry,
+ IN PINDEX_ENTRY FirstNewIndexEntry,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used both in normal operation and at restart to
+ update the end of an index buffer, as one of the consequences of
+ a buffer split. Since it is called at restart, it does no logging.
+
+Arguments:
+
+ IndexHeader - Supplies a pointer to the IndexHeader of the buffer
+ whose end is being rewritten.
+
+ OverWriteIndexEntry - Points to the index entry in the buffer at which
+ the overwrite of the end is to occur.
+
+ FirstNewIndexEntry - Points to the first entry in the buffer which is
+ to overwrite the end of the current buffer.
+
+ Length - Supplies the length of the index entries being written to the
+ end of the buffer, in bytes.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartWriteEndOfIndex\n") );
+ DebugTrace( 0, Dbg, ("IndexHeader = %08lx\n", IndexHeader) );
+ DebugTrace( 0, Dbg, ("OverwriteIndexEntry = %08lx\n", OverwriteIndexEntry) );
+ DebugTrace( 0, Dbg, ("FirstNewIndexEntry = %08lx\n", FirstNewIndexEntry) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ IndexHeader->FirstFreeByte = ((PCHAR)OverwriteIndexEntry - (PCHAR)IndexHeader) +
+ Length;
+ RtlMoveMemory( OverwriteIndexEntry, FirstNewIndexEntry, Length );
+
+ DebugTrace( -1, Dbg, ("NtfsRestartWriteEndOfIndex -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartSetIndexBlock(
+ IN PINDEX_ENTRY IndexEntry,
+ IN LONGLONG IndexBlock
+ )
+
+/*++
+
+Routine Description:
+
+ This routine updates the IndexBlock in an index entry, for both normal operation and
+ restart. Therefore it does no logging.
+
+Arguments:
+
+ IndexEntry - Supplies a pointer to the index entry whose Vcn is to be overwritten.
+
+ IndexBlock - The index block which is to be written to the index entry.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartSetIndexBlock\n") );
+ DebugTrace( 0, Dbg, ("IndexEntry = %08lx\n", IndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexBlock = %016I64x\n", IndexBlock) );
+
+ NtfsSetIndexEntryBlock( IndexEntry, IndexBlock );
+
+ DebugTrace( -1, Dbg, ("NtfsRestartSetIndexEntryBlock -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartUpdateFileName(
+ IN PINDEX_ENTRY IndexEntry,
+ IN PDUPLICATED_INFORMATION Info
+ )
+
+/*++
+
+Routine Description:
+
+ This routine updates the duplicated information in a file name index entry,
+ for both normal operation and restart. Therefore it does no logging.
+
+Arguments:
+
+ IndexEntry - Supplies a pointer to the index entry whose Vcn is to be overwritten.
+
+ Info - Pointer to the duplicated information for the update.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartUpdateFileName\n") );
+ DebugTrace( 0, Dbg, ("IndexEntry = %08lx\n", IndexEntry) );
+ DebugTrace( 0, Dbg, ("Info = %08lx\n", Info) );
+
+ RtlMoveMemory( &((PFILE_NAME)(IndexEntry + 1))->Info,
+ Info,
+ sizeof(DUPLICATED_INFORMATION) );
+
+ DebugTrace( -1, Dbg, ("NtfsRestartUpdateFileName -> VOID\n") );
+}
+
+
+VOID
+DeleteFromIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes an entry from an index, deleting any empty index buffers
+ as required.
+
+ The steps are as follows:
+
+ 1. If the entry to be deleted is not a leaf, then find a leaf entry to
+ delete which can be used to replace the entry we want to delete.
+
+ 2. Delete the desired index entry, or the replacement we found. If we
+ delete a replacement, remember it for reinsertion later, in step 4 or
+ 6 below.
+
+ 3. Now prune empty buffers from the tree, if any, starting with the buffer
+ we just deleted an entry from.
+
+ 4. If the original target was an intermediate entry, then delete it now,
+ and replace it with the entry we deleted in its place in 2 above. As
+ a special case, if all of the descendent buffers of the target went away,
+ then we do not have to replace it, so we hang onto the replacement for
+ reinsertion.
+
+ 5. When we pruned the index back, we may have stopped on an entry which is
+ now childless. If this is the case, then we have to delete this childless
+ entry and reinsert it in the next step. (If this is the case, then we
+ are guaranteed to not still have another entry to reinsert from 2 above!)
+
+ 6. Finally, at this point we may have an entry that needs to be reinserted
+ from either step 2 or 5 above. If so, we do this reinsert now, and we
+ are done.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexContext - Describes the entry to delete, including the entire path through
+ it from root to leaf.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ //
+ // It is possible that one or two Index Entries will have to be reinserted.
+ // However, we need to remember at most one at once.
+ //
+
+ PINDEX_ENTRY ReinsertEntry = NULL;
+
+ //
+ // A pointer to keep track of where we are in the Index Lookup Stack.
+ //
+
+ PINDEX_LOOKUP_STACK Sp = IndexContext->Current;
+
+ //
+ // Some Index entry pointers to remember the next entry to delete, and
+ // the original target if it is an intermediate node.
+ //
+
+ PINDEX_ENTRY TargetEntry = NULL;
+ PINDEX_ENTRY DeleteEntry;
+
+ //
+ // Two other Lookup Stack pointers we may have to remember.
+ //
+
+ PINDEX_LOOKUP_STACK TargetSp;
+ PINDEX_LOOKUP_STACK DeleteSp;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("DeleteFromIndex\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ //
+ // Use try-finally to free pool on the way out.
+ //
+
+ try {
+
+ //
+ // Step 1.
+ //
+ // If we are supposed to delete an entry in an intermediate buffer,
+ // then we have to find in index entry lower in the tree to replace
+ // him. (In fact we will delete the lower entry first, and get around
+ // to deleting the one we really want to delete later after possibly
+ // pruning the tree back.) For right now just find the replacement
+ // to delete first, and save him away.
+ //
+
+ DeleteEntry = Sp->IndexEntry;
+ if (FlagOn(DeleteEntry->Flags, INDEX_ENTRY_NODE)) {
+
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ENTRY NextEntry;
+
+ //
+ // Remember the real target we need to delete.
+ //
+
+ TargetEntry = DeleteEntry;
+ TargetSp = Sp;
+
+ //
+ // Go to the top of the stack (bottom of the index) and find the
+ // largest index entry in that buffer to be our replacement.
+ //
+
+ Sp =
+ IndexContext->Current = IndexContext->Top;
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+ IndexHeader = &IndexBuffer->IndexHeader;
+ NextEntry = NtfsFirstIndexEntry(IndexHeader);
+ NtfsCheckIndexBound( NextEntry, IndexHeader );
+
+ do {
+
+ DeleteEntry = NextEntry;
+ NextEntry = NtfsNextIndexEntry(NextEntry);
+
+ NtfsCheckIndexBound( NextEntry, IndexHeader );
+ if (NextEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ } while (!FlagOn(NextEntry->Flags, INDEX_ENTRY_END));
+
+ //
+ // Now we have to save this guy away because we will have to
+ // reinsert him later.
+ //
+
+ ReinsertEntry = (PINDEX_ENTRY)NtfsAllocatePool( NonPagedPool,
+ DeleteEntry->Length +
+ sizeof(LONGLONG) );
+
+ RtlMoveMemory( ReinsertEntry, DeleteEntry, DeleteEntry->Length );
+ }
+
+ //
+ // Step 2.
+ //
+ // Now it's time to delete either our target or replacement entry at
+ // DeleteEntry.
+ //
+
+ DeleteSimple( IrpContext, Scb, DeleteEntry, IndexContext );
+ DeleteEntry = NULL;
+
+ //
+ // Step 3.
+ //
+ // Now we need to see if the tree has to be "pruned" back some to
+ // eliminate any empty buffers. In the extreme case this routine
+ // returns the root to the state of being an empty directory. This
+ // routine returns a pointer to DeleteEntry if it leaves an entry
+ // in the tree somewhere which is pointing to a deleted buffer, and
+ // has to be reinserted elsewhere. We only have to prune if we are
+ // not currently in the root anyway.
+ //
+ // Remember the DeleteSp, which is where PruneIndex left the stack
+ // pointer.
+ //
+
+ if (Sp != IndexContext->Base) {
+ PruneIndex( IrpContext, Scb, IndexContext, &DeleteEntry );
+ DeleteSp = IndexContext->Current;
+ }
+
+ //
+ // Step 4.
+ //
+ // Now we have deleted someone, and possibly pruned the tree back.
+ // It is time to see if our original target has not yet been deleted
+ // yet and deal with that. First we just delete it, then we see if
+ // we really need to replace it. If the whole tree under it went
+ // empty (TargetEntry == DeleteEntry), then we do not have to replace
+ // it and we will reinsert its replacement below. Otherwise, do the
+ // replace now.
+ //
+
+ if (TargetEntry != NULL) {
+
+ LONGLONG SavedBlock;
+
+ //
+ // Reload in case root moved
+ //
+
+ if (TargetSp == IndexContext->Base) {
+ TargetEntry = TargetSp->IndexEntry;
+ }
+
+ //
+ // Save the Vcn in case we need it for the reinsert.
+ //
+
+ SavedBlock = NtfsIndexEntryBlock(TargetEntry);
+
+ //
+ // Delete it.
+ //
+
+ IndexContext->Current = TargetSp;
+ DeleteSimple( IrpContext, Scb, TargetEntry, IndexContext );
+
+ //
+ // See if this is exactly the same guy who went childless anyway
+ // when we pruned the tree. If not replace him now.
+ //
+
+ if (TargetEntry != DeleteEntry) {
+
+ //
+ // We know the replacement entry was a leaf, so give him the
+ // block number now.
+ //
+
+ SetFlag( ReinsertEntry->Flags, INDEX_ENTRY_NODE );
+ ReinsertEntry->Length += sizeof(LONGLONG);
+ NtfsSetIndexEntryBlock( ReinsertEntry, SavedBlock );
+
+ //
+ // Now we are all set up to just call our local routine to
+ // go insert our replacement. If the stack gets pushed,
+ // we have to increment our DeleteSp.
+ //
+
+ if (AddToIndex( IrpContext, Scb, ReinsertEntry, IndexContext, NULL )) {
+ DeleteSp += 1;
+ }
+
+ //
+ // We may need to save someone else away below, but it could
+ // be a different size anyway, so let's just delete the
+ // current ReinsertEntry now.
+ //
+
+ NtfsFreePool( ReinsertEntry );
+ ReinsertEntry = NULL;
+
+ //
+ // Otherwise, we just deleted the same index entry who went
+ // childless during pruning, so clear our pointer to show that we
+ // so not have to deal with him later.
+ //
+
+ } else {
+
+ DeleteEntry = NULL;
+ }
+ }
+
+ //
+ // Step 5.
+ //
+ // Now there may still be a childless entry to deal with after the
+ // pruning above, if it did not turn out to be the guy we were deleting
+ // anyway. Note that if we have to do this delete, the ReinsertEntry
+ // pointer is guaranteed to be NULL. If our original target was not
+ // an intermediate node, then we never allocated one to begin with.
+ // Otherwise we passed through the preceding block of code, and either
+ // cleared ReinsertEntry or DeleteEntry.
+ //
+
+ if (DeleteEntry != NULL) {
+
+ ASSERT( ReinsertEntry == NULL );
+
+ //
+ // Now we have to save this guy away because we will have to
+ // reinsert him later.
+ //
+
+ ReinsertEntry = (PINDEX_ENTRY)NtfsAllocatePool( NonPagedPool,
+ DeleteEntry->Length );
+ RtlMoveMemory( ReinsertEntry, DeleteEntry, DeleteEntry->Length );
+
+ //
+ // We know the guy we are saving is an intermediate node, and that
+ // we no longer need his Vcn, since we deleted that buffer. Make
+ // the guy a leaf entry now. (We don't actually care about the
+ // Vcn, but we do this cleanup here in case the interface to
+ // NtfsAddIndexEntry were to change to take an initialized
+ // index entry.)
+ //
+
+ ClearFlag( ReinsertEntry->Flags, INDEX_ENTRY_NODE );
+ ReinsertEntry->Length -= sizeof(LONGLONG);
+
+ //
+ // Delete it.
+ //
+
+ IndexContext->Current = DeleteSp;
+ DeleteSimple( IrpContext, Scb, DeleteEntry, IndexContext );
+ }
+
+ //
+ // Step 6.
+ //
+ // Finally, we may have someone to reinsert now. This will either be
+ // someone we deleted as a replacement for our actual target, and then
+ // found out we did not need, or it could be just some entry that we
+ // had to delete just above, because the index buffers below him went
+ // empty and got deleted.
+ //
+ // In any case, we can no longer use the IndexContext we were called
+ // with because it no longer indicates where the replacement goes. We
+ // solve this by calling our top most external entry to do the insert.
+ //
+
+ if (ReinsertEntry != NULL) {
+
+#ifdef _CAIRO_
+ if (!FlagOn(Scb->ScbState, SCB_STATE_VIEW_INDEX)) {
+#endif _CAIRO_
+
+ NtfsAddIndexEntry( IrpContext,
+ Scb,
+ (PVOID)(ReinsertEntry + 1),
+ NtfsFileNameSize((PFILE_NAME)(ReinsertEntry + 1)),
+ &ReinsertEntry->FileReference,
+ NULL );
+
+#ifdef _CAIRO_
+ } else {
+
+ INDEX_ROW IndexRow;
+
+ IndexRow.KeyPart.Key = ReinsertEntry + 1;
+ IndexRow.KeyPart.KeyLength = ReinsertEntry->AttributeLength;
+ IndexRow.DataPart.Data = Add2Ptr(ReinsertEntry, ReinsertEntry->DataOffset);
+ IndexRow.DataPart.DataLength = ReinsertEntry->DataLength;
+
+ NtOfsAddRecords( IrpContext,
+ Scb,
+ 1,
+ &IndexRow,
+ FALSE );
+ }
+#endif _CAIRO_
+ }
+
+ //
+ // Use the finally clause to free a potential ReinsertEntry we may
+ // have allocated.
+ //
+
+ } finally {
+
+ DebugUnwind( DeleteFromIndex );
+
+ if (ReinsertEntry != NULL) {
+
+ NtfsFreePool( ReinsertEntry );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("DeleteFromIndex -> VOID\n") );
+}
+
+
+VOID
+DeleteSimple (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN OUT PINDEX_CONTEXT IndexContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a simple insertion of an index entry, from either the
+ root or from an index allocation buffer. It writes the appropriate log
+ record first and then calls a routine in common with restart.
+
+Arguments:
+
+ Scb - Supplies the Scb for the index.
+
+ IndexEntry - Points to the index entry to delete.
+
+ IndexContext - Describes the path to the index entry we are deleting.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+ PINDEX_LOOKUP_STACK Sp = IndexContext->Current;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("DeleteSimple\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("IndexEntry = %08lx\n", IndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+
+ //
+ // Our caller never checks if he is deleting in the root or in the
+ // index allocation, so the first thing we do is check that.
+ //
+ // First we will handle the root case.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_ENUMERATION_CONTEXT Context;
+
+ //
+ // Initialize pointers for the root case.
+ //
+
+ Attribute = FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+
+ Context = &IndexContext->AttributeContext;
+ FileRecord = NtfsContainingFileRecord( Context );
+
+ //
+ // Pin the page before we start to modify it.
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Write the log record first while we can still see the attribute
+ // we are going to delete.
+ //
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ DeleteIndexEntryRoot,
+ NULL,
+ 0,
+ AddIndexEntryRoot,
+ IndexEntry,
+ IndexEntry->Length,
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)IndexEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Now call the same routine as Restart to actually delete it.
+ //
+
+ NtfsRestartDeleteSimpleRoot( IrpContext, IndexEntry, FileRecord, Attribute );
+
+ CheckRoot();
+
+ //
+ // Otherwise we are deleting in the index allocation, so do that here.
+ //
+
+ } else {
+
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ //
+ // Get the Index Buffer pointer from the stack.
+ //
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ //
+ // Pin the page before we start to modify it.
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write the log record now while the entry we are deleting is still
+ // there.
+ //
+
+ IndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ DeleteIndexEntryAllocation,
+ NULL,
+ 0,
+ AddIndexEntryAllocation,
+ IndexEntry,
+ IndexEntry->Length,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)IndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Now call the same routine as Restart to delete the entry.
+ //
+
+ NtfsRestartDeleteSimpleAllocation( IndexEntry, IndexBuffer );
+
+ CheckBuffer(IndexBuffer);
+ }
+
+ DebugTrace( -1, Dbg, ("DeleteSimple -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartDeleteSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_ENTRY IndexEntry,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ )
+
+/*++
+
+Routine Description:
+
+ This is a restart routine which does a simple deletion of an index entry
+ from the Index Root, without logging. It is also used at run time.
+
+Arguments:
+
+ IndexEntry - Points to the index entry to delete.
+
+ FileRecord - Points to the file record in which the index root resides.
+
+ Attribute - Points to the attribute record header for the index root.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_ROOT IndexRoot;
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ENTRY EntryAfter;
+ ULONG SavedLength;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartDeleteSimpleRoot\n") );
+ DebugTrace( 0, Dbg, ("IndexEntry = %08lx\n", IndexEntry) );
+ DebugTrace( 0, Dbg, ("FileRecord = %08lx\n", FileRecord) );
+ DebugTrace( 0, Dbg, ("Attribute = %08lx\n", Attribute) );
+
+ //
+ // Form some pointers within the attribute value.
+ //
+
+ IndexRoot = (PINDEX_ROOT)NtfsAttributeValue(Attribute);
+ IndexHeader = &IndexRoot->IndexHeader;
+ SavedLength = (ULONG)IndexEntry->Length;
+ EntryAfter = NtfsNextIndexEntry(IndexEntry);
+
+ //
+ // Now move the tail end of the index to make room for the new entry.
+ //
+
+ RtlMoveMemory( IndexEntry,
+ EntryAfter,
+ ((PCHAR)IndexHeader + IndexHeader->FirstFreeByte) -
+ (PCHAR)EntryAfter );
+
+ //
+ // Update the index header by the space we grew by.
+ //
+
+ Attribute->Form.Resident.ValueLength -= SavedLength;
+ IndexHeader->FirstFreeByte -= SavedLength;
+ IndexHeader->BytesAvailable -= SavedLength;
+
+ //
+ // Now shrink the attribute record.
+ //
+
+ NtfsRestartChangeAttributeSize( IrpContext,
+ FileRecord,
+ Attribute,
+ Attribute->RecordLength - SavedLength );
+
+ DebugTrace( -1, Dbg, ("NtfsRestartDeleteSimpleRoot -> VOID\n") );
+}
+
+
+VOID
+NtfsRestartDeleteSimpleAllocation (
+ IN PINDEX_ENTRY IndexEntry,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This is a restart routine which does a simple deletion of an index entry
+ from an index allocation buffer, without logging. It is also used at run time.
+
+Arguments:
+
+ IndexEntry - Points to the index entry to delete.
+
+ IndexBuffer - Pointer to the index allocation buffer in which the delete is to
+ occur.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ENTRY EntryAfter;
+ ULONG SavedLength;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRestartDeleteSimpleAllocation\n") );
+ DebugTrace( 0, Dbg, ("IndexEntry = %08lx\n", IndexEntry) );
+ DebugTrace( 0, Dbg, ("IndexBuffer = %08lx\n", IndexBuffer) );
+
+ //
+ // Form some pointers within the attribute value.
+ //
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ EntryAfter = NtfsNextIndexEntry(IndexEntry);
+ SavedLength = (ULONG)IndexEntry->Length;
+
+ //
+ // Now move the tail end of the index to make room for the new entry.
+ //
+
+ RtlMoveMemory( IndexEntry,
+ EntryAfter,
+ ((PCHAR)IndexHeader + IndexHeader->FirstFreeByte) -
+ (PCHAR)EntryAfter );
+
+ //
+ // Update the index header by the space we grew by.
+ //
+
+ IndexHeader->FirstFreeByte -= SavedLength;
+
+ DebugTrace( -1, Dbg, ("NtfsRestartDeleteSimpleAllocation -> VOID\n") );
+}
+
+
+VOID
+PruneIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PINDEX_CONTEXT IndexContext,
+ OUT PINDEX_ENTRY *DeleteEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if any index buffers need to be deallocated, as the result
+ of just having deleted an entry. The logic of the main loop is described in
+ detail below. All changes are logged.
+
+Arguments:
+
+ Scb - Supplies the Scb of the index.
+
+ IndexContext - describes the path to the index buffer in which the delete just
+ occured.
+
+ DeleteEntry - Returns a pointer to an entry which must be deleted, because all
+ of the buffers below it were deleted.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ PINDEX_HEADER IndexHeader;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute = NULL;
+ PINDEX_LOOKUP_STACK Sp = IndexContext->Current;
+ PVCB Vcb = Scb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("PruneIndex\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("IndexContext = %08lx\n", IndexContext) );
+ DebugTrace( 0, Dbg, ("DeleteEntry = %08lx\n", DeleteEntry) );
+
+ //
+ // We do not allow ourselves to be called if the index has no
+ // allocation.
+ //
+
+ ASSERT( Sp != IndexContext->Base );
+
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+ IndexHeader = &IndexBuffer->IndexHeader;
+
+ //
+ // Initialize pointers for the root.
+ //
+
+ Context = &IndexContext->AttributeContext;
+
+ //
+ // Assume returning NULL.
+ //
+
+ *DeleteEntry = NULL;
+
+ //
+ // A pruning we will go...
+ //
+
+ while (TRUE) {
+
+ //
+ // The Index Buffer is empty if its first entry is the end record.
+ // If so, delete it, otherwise get out.
+ //
+
+ if (FlagOn(NtfsFirstIndexEntry(IndexHeader)->Flags, INDEX_ENTRY_END)) {
+
+ DeleteIndexBuffer( IrpContext, Scb, IndexBuffer );
+ NtfsUnpinBcb( &Sp->Bcb );
+
+ } else {
+ break;
+ }
+
+ //
+ // We just deleted an Index Buffer, so we have to go up a level
+ // in the stack and take care of the Entry that was pointing to it.
+ // There are these cases:
+ //
+ // 1. If the Entry pointing to the one we deleted is not
+ // an End Entry, then we will remember its address in
+ // *DeleteEntry to cause it to be deleted and reinserted
+ // later.
+ // 2. If the Entry pointing to the one we deleted is an
+ // End Entry, and it is not the Index Root, then we
+ // cannot delete the End Entry, so we get the Vcn
+ // from the entry before the End, store it in the End
+ // record, and make the Entry before the End record
+ // the one returned in *DeleteEntry.
+ // 3. If the current Index Buffer has gone empty, and it is
+ // the index root, then we have an Index just gone
+ // empty. We have to catch this special case and
+ // transition the Index root back to an empty leaf by
+ // by calling NtfsCreateIndex to reinitialize it.
+ // 4. If there is no Entry before the end record, then the
+ // current Index Buffer is empty. If it is not the
+ // root, we just let ourselves loop back and delete the
+ // empty buffer in the while statement above.
+ //
+
+ Sp -= 1;
+
+ //
+ // When we get back to the root, look it up again because it may
+ // have moved.
+ //
+
+ if (Sp == IndexContext->Base) {
+ Attribute = FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+ }
+
+ IndexHeader = Sp->IndexHeader;
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ //
+ // Remember potential entry to delete.
+ //
+
+ IndexContext->Current = Sp;
+ *DeleteEntry = Sp->IndexEntry;
+
+ //
+ // If the current delete entry is not an end entry, then we have
+ // Case 1 above, and we can break out.
+ //
+
+ if (!FlagOn((*DeleteEntry)->Flags, INDEX_ENTRY_END)) {
+ break;
+ }
+
+ //
+ // If we are pointing to the end record, but it is not the first in
+ // the buffer, then we have Case 2. We need to find the predecessor
+ // index entry, choose it for deletion, and copy its Vcn to the end
+ // record.
+ //
+
+ if (*DeleteEntry != NtfsFirstIndexEntry(IndexHeader)) {
+
+ PINDEX_ENTRY NextEntry;
+
+ NextEntry = NtfsFirstIndexEntry(IndexHeader);
+ NtfsCheckIndexBound( NextEntry, IndexHeader );
+ do {
+ *DeleteEntry = NextEntry;
+ NextEntry = NtfsNextIndexEntry(NextEntry);
+
+ NtfsCheckIndexBound( NextEntry, IndexHeader );
+ if (NextEntry->Length == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+
+ } while (!FlagOn(NextEntry->Flags, INDEX_ENTRY_END));
+
+ //
+ // First handle the case where our parent is the Index Root.
+ //
+
+ if (Sp == IndexContext->Base) {
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, Context );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+
+ FileRecord->Lsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ SetIndexEntryVcnRoot,
+ &NtfsIndexEntryBlock(*DeleteEntry),
+ sizeof(LONGLONG),
+ SetIndexEntryVcnRoot,
+ &NtfsIndexEntryBlock(NextEntry),
+ sizeof(LONGLONG),
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)NextEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Otherwise, our parent is also an Index Buffer.
+ //
+
+ } else {
+
+ //
+ // Pin the page
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ IndexBuffer->Lsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ SetIndexEntryVcnAllocation,
+ &NtfsIndexEntryBlock(*DeleteEntry),
+ sizeof(LONGLONG),
+ SetIndexEntryVcnAllocation,
+ &NtfsIndexEntryBlock(NextEntry),
+ sizeof(LONGLONG),
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)NextEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+ }
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtfsRestartSetIndexBlock( NextEntry,
+ NtfsIndexEntryBlock(*DeleteEntry) );
+
+ break;
+
+ //
+ // Otherwise we are looking at an empty buffer. If it is the root
+ // then we have Case 3. We are returning an IndexRoot to the
+ // empty leaf case by reinitializing it.
+ //
+
+ } else if (Sp == IndexContext->Base) {
+
+ NtfsCreateIndex( IrpContext,
+ Scb->Fcb,
+ Scb->ScbType.Index.AttributeBeingIndexed,
+ Scb->ScbType.Index.CollationRule,
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ Scb->ScbType.Index.BlocksPerIndexBuffer,
+ Context,
+ Scb->AttributeFlags,
+ FALSE,
+ TRUE );
+
+ //
+ // Nobody should use this context anymore, so set to crash
+ // if they try to use this index entry pointer.
+ //
+
+ IndexContext->OldAttribute = NtfsFoundAttribute(Context);
+ IndexContext->Base->IndexEntry = (PINDEX_ENTRY)NULL;
+
+ //
+ // In this case our caller has nothing to delete.
+ //
+
+ *DeleteEntry = NULL;
+
+ break;
+
+ //
+ // Otherwise, this is just some intermediate empty buffer, which
+ // is Case 4. Just continue back and keep on pruning.
+ //
+
+ } else {
+ continue;
+ }
+ }
+
+ //
+ // If it looks like we did some work, and did not already find the root again,
+ // then make sure the stack is correct for return.
+ //
+
+ if ((*DeleteEntry != NULL) && (Attribute == NULL)) {
+ FindMoveableIndexRoot( IrpContext, Scb, IndexContext );
+ }
+
+ DebugTrace( -1, Dbg, ("PruneIndex -> VOID\n") );
+}
+
+
+VOID
+NtOfsRestartUpdateDataInIndex(
+ IN PINDEX_ENTRY IndexEntry,
+ IN PVOID IndexData,
+ IN ULONG Length )
+
+/*++
+
+Routine Description:
+
+ This is the restart routine used to apply updates to the data in a row,
+ both in run time and at restart.
+
+Arguments:
+
+ IndexEntry - Supplies a pointer to the IndexEntry to be updated.
+
+ IndexData - Supplies the data for the update.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PAGED_CODE();
+
+ RtlMoveMemory( Add2Ptr(IndexEntry, IndexEntry->DataOffset),
+ IndexData,
+ Length );
+}
+
diff --git a/private/ntos/cntfs/lockctrl.c b/private/ntos/cntfs/lockctrl.c
new file mode 100644
index 000000000..b814895c1
--- /dev/null
+++ b/private/ntos/cntfs/lockctrl.c
@@ -0,0 +1,899 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ LockCtrl.c
+
+Abstract:
+
+ This module implements the File Lock Control routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Gary Kimura [GaryKi] 28-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_LOCKCTRL)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonLockControl)
+#pragma alloc_text(PAGE, NtfsFastLock)
+#pragma alloc_text(PAGE, NtfsFastUnlockAll)
+#pragma alloc_text(PAGE, NtfsFastUnlockAllByKey)
+#pragma alloc_text(PAGE, NtfsFastUnlockSingle)
+#pragma alloc_text(PAGE, NtfsFsdLockControl)
+#endif
+
+
+NTSTATUS
+NtfsFsdLockControl (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Lock Control.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdLockControl\n") );
+
+ //
+ // Call the common Lock Control routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonLockControl( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdLockControl -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsFastLock (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PLARGE_INTEGER Length,
+ PEPROCESS ProcessId,
+ ULONG Key,
+ BOOLEAN FailImmediately,
+ BOOLEAN ExclusiveLock,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This is a call back routine for doing the fast lock call.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ FileOffset - Supplies the file offset used in this operation
+
+ Length - Supplies the length used in this operation
+
+ ProcessId - Supplies the process ID used in this operation
+
+ Key - Supplies the key used in this operation
+
+ FailImmediately - Indicates if the request should fail immediately
+ if the lock cannot be granted.
+
+ ExclusiveLock - Indicates if this is a request for an exclusive or
+ shared lock
+
+ IoStatus - Receives the Status if this operation is successful
+
+Return Value:
+
+ BOOLEAN - TRUE if this operation completed and FALSE if caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results;
+ PSCB Scb;
+ PFCB Fcb;
+ BOOLEAN ResourceAcquired = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFastLock\n") );
+
+ //
+ // Decode the type of file object we're being asked to process and
+ // make sure that is is only a user file open.
+ //
+
+ if ((Scb = NtfsFastDecodeUserFileOpen( FileObject )) == NULL) {
+
+ IoStatus->Status = STATUS_INVALID_PARAMETER;
+ IoStatus->Information = 0;
+
+ DebugTrace( -1, Dbg, ("NtfsFastLock -> TRUE (STATUS_INVALID_PARAMETER)\n") );
+ return TRUE;
+ }
+
+ Fcb = Scb->Fcb;
+
+ //
+ // Acquire shared access to the Fcb this operation can always wait
+ //
+
+ FsRtlEnterFileSystem();
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ (VOID) ExAcquireResourceExclusive( Fcb->Resource, TRUE );
+ ResourceAcquired = TRUE;
+
+ } else {
+
+ //(VOID) ExAcquireResourceShared( Fcb->Resource, TRUE );
+ }
+
+ try {
+
+ //
+ // We check whether we can proceed
+ // based on the state of the file oplocks.
+ //
+
+ if ((Scb->ScbType.Data.Oplock != NULL) &&
+ !FsRtlOplockIsFastIoPossible( &Scb->ScbType.Data.Oplock )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // If we don't have a file lock, then get one now.
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL
+ && !NtfsCreateFileLock( Scb, FALSE )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // Now call the FsRtl routine to do the actual processing of the
+ // Lock request
+ //
+
+ if (Results = FsRtlFastLock( Scb->ScbType.Data.FileLock,
+ FileObject,
+ FileOffset,
+ Length,
+ ProcessId,
+ Key,
+ FailImmediately,
+ ExclusiveLock,
+ IoStatus,
+ NULL,
+ FALSE )) {
+
+ //
+ // Set the flag indicating if Fast I/O is questionable. We
+ // only change this flag is the current state is possible.
+ // Retest again after synchronizing on the header.
+ //
+
+ if (Scb->Header.IsFastIoPossible == FastIoIsPossible) {
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFastLock );
+
+ //
+ // Release the Fcb, and return to our caller
+ //
+
+ if (ResourceAcquired) {
+ ExReleaseResource( Fcb->Resource );
+ }
+
+ FsRtlExitFileSystem();
+
+ DebugTrace( -1, Dbg, ("NtfsFastLock -> %08lx\n", Results) );
+ }
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsFastUnlockSingle (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PLARGE_INTEGER Length,
+ PEPROCESS ProcessId,
+ ULONG Key,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This is a call back routine for doing the fast unlock single call.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ FileOffset - Supplies the file offset used in this operation
+
+ Length - Supplies the length used in this operation
+
+ ProcessId - Supplies the process ID used in this operation
+
+ Key - Supplies the key used in this operation
+
+ Status - Receives the Status if this operation is successful
+
+Return Value:
+
+ BOOLEAN - TRUE if this operation completed and FALSE if caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results;
+ PFCB Fcb;
+ PSCB Scb;
+ BOOLEAN ResourceAcquired = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFastUnlockSingle\n") );
+
+ IoStatus->Information = 0;
+
+ //
+ // Decode the type of file object we're being asked to process and
+ // make sure that is is only a user file open.
+ //
+
+ if ((Scb = NtfsFastDecodeUserFileOpen( FileObject )) == NULL) {
+
+ IoStatus->Status = STATUS_INVALID_PARAMETER;
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockSingle -> TRUE (STATUS_INVALID_PARAMETER)\n") );
+ return TRUE;
+ }
+
+ Fcb = Scb->Fcb;
+
+ //
+ // Acquire exclusive access to the Fcb this operation can always wait
+ //
+
+ FsRtlEnterFileSystem();
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ (VOID) ExAcquireResourceExclusive( Fcb->Resource, TRUE );
+ ResourceAcquired = TRUE;
+
+ } else {
+
+ //(VOID) ExAcquireResourceShared( Fcb->Resource, TRUE );
+ }
+
+ try {
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ //
+
+ if ((Scb->ScbType.Data.Oplock != NULL) &&
+ !FsRtlOplockIsFastIoPossible( &Scb->ScbType.Data.Oplock )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // If we don't have a file lock, then get one now.
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL
+ && !NtfsCreateFileLock( Scb, FALSE )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // Now call the FsRtl routine to do the actual processing of the
+ // Lock request. The call will always succeed.
+ //
+
+ Results = TRUE;
+ IoStatus->Status = FsRtlFastUnlockSingle( Scb->ScbType.Data.FileLock,
+ FileObject,
+ FileOffset,
+ Length,
+ ProcessId,
+ Key,
+ NULL,
+ FALSE );
+
+ //
+ // Set the flag indicating if Fast I/O is possible. We are
+ // only concerned if there are no longer any filelocks on this
+ // file.
+ //
+
+ if (!FsRtlAreThereCurrentFileLocks( Scb->ScbType.Data.FileLock ) &&
+ (Scb->Header.IsFastIoPossible != FastIoIsPossible)) {
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFastUnlockSingle );
+
+ //
+ // Release the Fcb, and return to our caller
+ //
+
+ if (ResourceAcquired) {
+ ExReleaseResource( Fcb->Resource );
+ }
+
+ FsRtlExitFileSystem();
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockSingle -> %08lx\n", Results) );
+ }
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsFastUnlockAll (
+ IN PFILE_OBJECT FileObject,
+ PEPROCESS ProcessId,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This is a call back routine for doing the fast unlock all call.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ ProcessId - Supplies the process ID used in this operation
+
+ Status - Receives the Status if this operation is successful
+
+Return Value:
+
+ BOOLEAN - TRUE if this operation completed and FALSE if caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results;
+ IRP_CONTEXT IrpContext;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFastUnlockAll\n") );
+
+ IoStatus->Information = 0;
+
+ //
+ // Decode the type of file object we're being asked to process and
+ // make sure that is is only a user file open.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ if (TypeOfOpen != UserFileOpen) {
+
+ IoStatus->Status = STATUS_INVALID_PARAMETER;
+ IoStatus->Information = 0;
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockAll -> TRUE (STATUS_INVALID_PARAMETER)\n") );
+ return TRUE;
+ }
+
+ //
+ // Acquire exclusive access to the Fcb this operation can always wait
+ //
+
+ FsRtlEnterFileSystem();
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ (VOID) ExAcquireResourceExclusive( Fcb->Resource, TRUE );
+
+ } else {
+
+ (VOID) ExAcquireResourceShared( Fcb->Resource, TRUE );
+ }
+
+ try {
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ //
+
+ if (!FsRtlOplockIsFastIoPossible( &Scb->ScbType.Data.Oplock )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // If we don't have a file lock, then get one now.
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL
+ && !NtfsCreateFileLock( Scb, FALSE )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // Now call the FsRtl routine to do the actual processing of the
+ // Lock request. The call will always succeed.
+ //
+
+ Results = TRUE;
+ IoStatus->Status = FsRtlFastUnlockAll( Scb->ScbType.Data.FileLock,
+ FileObject,
+ ProcessId,
+ NULL );
+
+ //
+ // Set the flag indicating if Fast I/O is possible
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFastUnlockAll );
+
+ //
+ // Release the Fcb, and return to our caller
+ //
+
+ ExReleaseResource( Fcb->Resource );
+
+ FsRtlExitFileSystem();
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockAll -> %08lx\n", Results) );
+ }
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsFastUnlockAllByKey (
+ IN PFILE_OBJECT FileObject,
+ PVOID ProcessId,
+ ULONG Key,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This is a call back routine for doing the fast unlock all by key call.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ ProcessId - Supplies the process ID used in this operation
+
+ Key - Supplies the key used in this operation
+
+ Status - Receives the Status if this operation is successful
+
+Return Value:
+
+ BOOLEAN - TRUE if this operation completed and FALSE if caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results;
+ IRP_CONTEXT IrpContext;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFastUnlockAllByKey\n") );
+
+ IoStatus->Information = 0;
+
+ //
+ // Decode the type of file object we're being asked to process and
+ // make sure that is is only a user file open.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ if (TypeOfOpen != UserFileOpen) {
+
+ IoStatus->Status = STATUS_INVALID_PARAMETER;
+ IoStatus->Information = 0;
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockAllByKey -> TRUE (STATUS_INVALID_PARAMETER)\n") );
+ return TRUE;
+ }
+
+ //
+ // Acquire exclusive access to the Fcb this operation can always wait
+ //
+
+ FsRtlEnterFileSystem();
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ (VOID) ExAcquireResourceExclusive( Fcb->Resource, TRUE );
+
+ } else {
+
+ (VOID) ExAcquireResourceShared( Fcb->Resource, TRUE );
+ }
+
+ try {
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ //
+
+ if (!FsRtlOplockIsFastIoPossible( &Scb->ScbType.Data.Oplock )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // If we don't have a file lock, then get one now.
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL
+ && !NtfsCreateFileLock( Scb, FALSE )) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // Now call the FsRtl routine to do the actual processing of the
+ // Lock request. The call will always succeed.
+ //
+
+ Results = TRUE;
+ IoStatus->Status = FsRtlFastUnlockAllByKey( Scb->ScbType.Data.FileLock,
+ FileObject,
+ ProcessId,
+ Key,
+ NULL );
+
+ //
+ // Set the flag indicating if Fast I/O is possible
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsFastUnlockAllByKey );
+
+ //
+ // Release the Fcb, and return to our caller
+ //
+
+ ExReleaseResource( Fcb->Resource );
+
+ FsRtlExitFileSystem();
+
+ DebugTrace( -1, Dbg, ("NtfsFastUnlockAllByKey -> %08lx\n", Results) );
+ }
+
+ return Results;
+}
+
+
+NTSTATUS
+NtfsCommonLockControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Lock Control called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ BOOLEAN FcbAcquired = FALSE;
+
+ BOOLEAN OplockPostIrp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get a pointer to the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonLockControl\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("MinorFunction = %08lx\n", IrpSp->MinorFunction) );
+
+ //
+ // Extract and decode the type of file object we're being asked to process
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // If the file is not a user file open then we reject the request
+ // as an invalid parameter
+ //
+
+ if (TypeOfOpen != UserFileOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_PARAMETER );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonLockControl -> STATUS_INVALID_PARAMETER\n") );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Acquire exclusive access to the Fcb
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, Scb, FALSE, FALSE );
+ FcbAcquired = TRUE;
+
+ } else {
+
+ //NtfsAcquireSharedFcb( IrpContext, Fcb, Scb );
+ }
+
+ OplockPostIrp = FALSE;
+
+ try {
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ // This call might post the irp for us.
+ //
+
+ Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NULL );
+
+ if (Status != STATUS_SUCCESS) {
+
+ OplockPostIrp = TRUE;
+ try_return( NOTHING );
+ }
+
+ //
+ // If we don't have a file lock, then get one now.
+ //
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+
+ NtfsCreateFileLock( Scb, TRUE );
+ }
+
+ //
+ // Now call the FsRtl routine to do the actual processing of the
+ // Lock request
+ //
+
+ Status = FsRtlProcessFileLock( Scb->ScbType.Data.FileLock, Irp, NULL );
+
+ //
+ // Set the flag indicating if Fast I/O is possible
+ //
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsCommonLockControl );
+
+ //
+ // Release the Fcb, and return to our caller
+ //
+
+ if (FcbAcquired) {
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+
+ //
+ // Only if this is not an abnormal termination and we did not post the irp
+ // do we delete the irp context
+ //
+
+ if (!AbnormalTermination() && !OplockPostIrp) {
+
+ NtfsCompleteRequest( &IrpContext, NULL, 0 );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonLockControl -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
diff --git a/private/ntos/cntfs/logsup.c b/private/ntos/cntfs/logsup.c
new file mode 100644
index 000000000..b448ca234
--- /dev/null
+++ b/private/ntos/cntfs/logsup.c
@@ -0,0 +1,3434 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ LogSup.c
+
+Abstract:
+
+ This module implements the Ntfs interfaces to the Log File Service (LFS).
+
+Author:
+
+ Tom Miller [TomM] 24-Jul-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_LOGSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('LFtN')
+
+#ifdef NTFSDBG
+
+#define ASSERT_RESTART_TABLE(T) { \
+ PULONG _p = (PULONG)(((PCHAR)(T)) + sizeof(RESTART_TABLE)); \
+ ULONG _Count = ((T)->EntrySize/4) * (T)->NumberEntries; \
+ ULONG _i; \
+ for (_i = 0; _i < _Count; _i += 1) { \
+ if (_p[_i] == 0xDAADF00D) { \
+ DbgPrint("DaadFood for table %08lx, At %08lx\n", (T), &_p[_i]); \
+ ASSERTMSG("ASSERT_RESTART_TABLE ", FALSE); \
+ } \
+ } \
+}
+
+#else
+
+#define ASSERT_RESTART_TABLE(T) {NOTHING;}
+
+#endif
+
+//
+// Local procedure prototypes
+//
+
+typedef LCN UNALIGNED *PLCN_UNALIGNED;
+
+VOID
+DirtyPageRoutine (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PLSN OldestLsn,
+ IN PLSN NewestLsn,
+ IN PVOID Context1,
+ IN PVOID Context2
+ );
+
+VOID
+LookupLcns (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN VCN Vcn,
+ IN ULONG ClusterCount,
+ IN BOOLEAN MustBeAllocated,
+ OUT PLCN_UNALIGNED FirstLcn
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, LookupLcns)
+#pragma alloc_text(PAGE, NtfsCheckpointCurrentTransaction)
+#pragma alloc_text(PAGE, NtfsCheckpointForLogFileFull)
+#pragma alloc_text(PAGE, NtfsCheckpointVolume)
+#pragma alloc_text(PAGE, NtfsCommitCurrentTransaction)
+#pragma alloc_text(PAGE, NtfsFreeRestartTable)
+#pragma alloc_text(PAGE, NtfsGetFirstRestartTable)
+#pragma alloc_text(PAGE, NtfsGetNextRestartTable)
+#pragma alloc_text(PAGE, NtfsInitializeLogging)
+#pragma alloc_text(PAGE, NtfsInitializeRestartTable)
+#pragma alloc_text(PAGE, NtfsStartLogFile)
+#pragma alloc_text(PAGE, NtfsStopLogFile)
+#pragma alloc_text(PAGE, NtfsWriteLog)
+#endif
+
+
+LSN
+NtfsWriteLog (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PBCB Bcb OPTIONAL,
+ IN NTFS_LOG_OPERATION RedoOperation,
+ IN PVOID RedoBuffer OPTIONAL,
+ IN ULONG RedoLength,
+ IN NTFS_LOG_OPERATION UndoOperation,
+ IN PVOID UndoBuffer OPTIONAL,
+ IN ULONG UndoLength,
+ IN LONGLONG StreamOffset,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN ULONG StructureSize
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements an Ntfs-specific interface to LFS for the
+ purpose of logging updates to file record segments and resident
+ attributes.
+
+ The caller creates one of the predefined log record formats as
+ determined by the given LogOperation, and calls this routine with
+ this log record and pointers to the respective file and attribute
+ records. The list of log operations along with the respective structure
+ expected for the Log Buffer is present in ntfslog.h.
+
+Arguments:
+
+ Scb - Pointer to the Scb for the respective file or Mft. The caller must
+ have at least shared access to this Scb.
+
+ Bcb - If specified, this Bcb will be set dirty specifying the Lsn of
+ the log record written.
+
+ RedoOperation - One of the log operation codes defined in ntfslog.h.
+
+ RedoBuffer - A pointer to the structure expected for the given Redo operation,
+ as summarized in ntfslog.h. This pointer should only be
+ omitted if and only if the table in ntfslog.h does not show
+ a log record for this log operation.
+
+ RedoLength - Length of the Redo buffer in bytes.
+
+ UndoOperation - One of the log operation codes defined in ntfslog.h.
+
+ Must be CompensationLogRecord if logging the Undo of
+ a previous operation, such as during transaction abort.
+ In this case, of course, the Redo information is from
+ the Undo information of the record being undone. See
+ next parameter.
+
+ UndoBuffer - A pointer to the structure expected for the given Undo operation,
+ as summarized in ntfslog.h. This pointer should only be
+ omitted if and only if the table in ntfslog.h does not show
+ a log record for this log operation. If this pointer is
+ identical to RedoBuffer, then UndoLength is ignored and
+ only a single copy of the RedoBuffer is made, but described
+ by both the Redo and Undo portions of the log record.
+
+ For a compensation log record (UndoOperation ==
+ CompensationLogRecord), this argument must point to the
+ UndoNextLsn of the log record being compensated.
+
+ UndoLength - Length of the Undo buffer in bytes. Ignored if RedoBuffer ==
+ UndoBuffer.
+
+ For a compensation log record, this argument must be the length
+ of the original redo record. (Used during restart).
+
+ StreamOffset - Offset within the stream for the start of the structure being
+ modified (Mft or Index), or simply the stream offset for the start
+ of the update.
+
+ RecordOffset - Byte offset from StreamOffset above to update reference
+
+ AttributeOffset - Offset within a value to which an update applies, if relevant.
+
+ StructureSize - Size of the entire structure being logged.
+
+Return Value:
+
+ The Lsn of the log record written. For most callers, this status may be ignored,
+ because the Lsn is also correctly recorded in the transaction context.
+
+ If an error occurs this procedure will raise.
+
+--*/
+
+{
+ LFS_WRITE_ENTRY WriteEntries[3];
+
+ struct {
+
+ NTFS_LOG_RECORD_HEADER LogRecordHeader;
+ LCN Runs[PAGE_SIZE/512 - 1];
+
+ } LocalHeader;
+
+ PNTFS_LOG_RECORD_HEADER MyHeader;
+ PVCB Vcb;
+
+ LSN UndoNextLsn;
+ LSN ReturnLsn;
+ PLSN DirtyLsn = NULL;
+
+ ULONG WriteIndex = 0;
+ ULONG UndoIndex = 0;
+ ULONG RedoIndex = 0;
+ LONG UndoBytes = 0;
+ LONG UndoAdjustmentForLfs = 0;
+ LONG UndoRecords = 0;
+
+ PTRANSACTION_ENTRY TransactionEntry;
+ POPEN_ATTRIBUTE_ENTRY OpenAttributeEntry = NULL;
+ ULONG OpenAttributeIndex = 0;
+ BOOLEAN AttributeTableAcquired = FALSE;
+ BOOLEAN TransactionTableAcquired = FALSE;
+
+ ULONG LogClusterCount = ClustersFromBytes( Scb->Vcb, StructureSize );
+ VCN LogVcn = LlClustersFromBytesTruncate( Scb->Vcb, StreamOffset );
+
+ PAGED_CODE();
+
+ Vcb = Scb->Vcb;
+
+ //
+ // If the log handle is gone, then we noop this call.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE )) {
+
+ return Li0; //**** LfsZeroLsn;
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsWriteLog:\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08lx\n", Scb) );
+ DebugTrace( 0, Dbg, ("Bcb = %08lx\n", Bcb) );
+ DebugTrace( 0, Dbg, ("RedoOperation = %08lx\n", RedoOperation) );
+ DebugTrace( 0, Dbg, ("RedoBuffer = %08lx\n", RedoBuffer) );
+ DebugTrace( 0, Dbg, ("RedoLength = %08lx\n", RedoLength) );
+ DebugTrace( 0, Dbg, ("UndoOperation = %08lx\n", UndoOperation) );
+ DebugTrace( 0, Dbg, ("UndoBuffer = %08lx\n", UndoBuffer) );
+ DebugTrace( 0, Dbg, ("UndoLength = %08lx\n", UndoLength) );
+ DebugTrace( 0, Dbg, ("StreamOffset = %016I64x\n", StreamOffset) );
+ DebugTrace( 0, Dbg, ("RecordOffset = %08lx\n", RecordOffset) );
+ DebugTrace( 0, Dbg, ("AttributeOffset = %08lx\n", AttributeOffset) );
+ DebugTrace( 0, Dbg, ("StructureSize = %08lx\n", StructureSize) );
+
+ //
+ // Check Redo and Undo lengths
+ //
+
+ ASSERT(((RedoOperation == UpdateNonresidentValue) && (RedoLength <= PAGE_SIZE))
+
+ ||
+
+ !ARGUMENT_PRESENT(Scb)
+
+ ||
+
+ !ARGUMENT_PRESENT(Bcb)
+
+ ||
+
+ ((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
+ (RedoLength <= Scb->ScbType.Index.BytesPerIndexBuffer))
+
+ ||
+
+ (RedoLength <= Scb->Vcb->BytesPerFileRecordSegment));
+
+ ASSERT(((UndoOperation == UpdateNonresidentValue) && (UndoLength <= PAGE_SIZE))
+
+ ||
+
+ !ARGUMENT_PRESENT(Scb)
+
+ ||
+
+ !ARGUMENT_PRESENT(Bcb)
+
+ ||
+
+ ((Scb->AttributeTypeCode == $INDEX_ALLOCATION) &&
+ (UndoLength <= Scb->ScbType.Index.BytesPerIndexBuffer))
+
+ ||
+
+ (UndoLength <= Scb->Vcb->BytesPerFileRecordSegment)
+
+ ||
+
+ (UndoOperation == CompensationLogRecord));
+
+ //
+ // Initialize local pointers.
+ //
+
+ MyHeader = (PNTFS_LOG_RECORD_HEADER)&LocalHeader;
+
+ try {
+
+ //
+ // If the structure size is nonzero, then create an open attribute table
+ // entry.
+ //
+
+ if (StructureSize != 0) {
+
+ //
+ // Allocate an entry in the open attribute table and initialize it,
+ // if it does not already exist. If we subsequently fail, we do
+ // not have to clean this up. It will go away on the next checkpoint.
+ //
+
+ if (Scb->NonpagedScb->OpenAttributeTableIndex == 0) {
+
+ OPEN_ATTRIBUTE_ENTRY LocalOpenEntry;
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
+ AttributeTableAcquired = TRUE;
+
+ //
+ // Only proceed if the OpenAttributeTableIndex is still 0.
+ // We may reach this point for the MftScb. It may not be
+ // acquired when logging changes to file records. We will
+ // use the OpenAttributeTable for final synchronization
+ // for the Mft open attribute table entry.
+ //
+
+ if (Scb->NonpagedScb->OpenAttributeTableIndex == 0) {
+
+ //
+ // Our structures require tables to stay within 64KB, since
+ // we use USHORT offsets. Things are getting out of hand
+ // at this point anyway. Raise log file full to reset the
+ // table sizes if we get to this point.
+ //
+
+ if (SizeOfRestartTable(&Vcb->OpenAttributeTable) > 0xF000) {
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+ }
+
+ Scb->NonpagedScb->OpenAttributeTableIndex =
+ OpenAttributeIndex = NtfsAllocateRestartTableIndex( &Vcb->OpenAttributeTable );
+ OpenAttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
+ OpenAttributeIndex );
+ OpenAttributeEntry->Overlay.Scb = Scb;
+ OpenAttributeEntry->FileReference = Scb->Fcb->FileReference;
+ // OpenAttributeEntry->LsnOfOpenRecord = ???
+ OpenAttributeEntry->AttributeTypeCode = Scb->AttributeTypeCode;
+ OpenAttributeEntry->AttributeName = Scb->AttributeName;
+ OpenAttributeEntry->AttributeNamePresent = FALSE;
+ if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ OpenAttributeEntry->BytesPerIndexBuffer = Scb->ScbType.Index.BytesPerIndexBuffer;
+
+ } else {
+ OpenAttributeEntry->BytesPerIndexBuffer = 0;
+ }
+
+ RtlMoveMemory( &LocalOpenEntry, OpenAttributeEntry, sizeof(OPEN_ATTRIBUTE_ENTRY) );
+
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ AttributeTableAcquired = FALSE;
+
+ //
+ // Now log the new open attribute table entry before goin on,
+ // to insure that the application of the caller's log record
+ // will have the information he needs on the attribute. We will
+ // use the Undo buffer to convey the attribute name. We will
+ // not infinitely recurse, because now this Scb already has an
+ // open attribute table index.
+ //
+
+ NtfsWriteLog( IrpContext,
+ Scb,
+ NULL,
+ OpenNonresidentAttribute,
+ &LocalOpenEntry,
+ sizeof(OPEN_ATTRIBUTE_ENTRY),
+ Noop,
+ Scb->AttributeName.Length != 0 ?
+ Scb->AttributeName.Buffer : NULL,
+ Scb->AttributeName.Length,
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+
+ } else {
+
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ AttributeTableAcquired = FALSE;
+ }
+ }
+ }
+
+ //
+ // Allocate a transaction ID and initialize it, if it does not already exist.
+ // If we subsequently fail, we clean it up when the current request is
+ // completed.
+ //
+
+ if (IrpContext->TransactionId == 0) {
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ TransactionTableAcquired = TRUE;
+
+ //
+ // Our structures require tables to stay within 64KB, since
+ // we use USHORT offsets. Things are getting out of hand
+ // at this point anyway. Raise log file full to reset the
+ // table sizes if we get to this point.
+ //
+
+ if (SizeOfRestartTable(&Vcb->TransactionTable) > 0xF000) {
+ NtfsRaiseStatus( IrpContext, STATUS_LOG_FILE_FULL, NULL, NULL );
+ }
+
+ IrpContext->TransactionId =
+ NtfsAllocateRestartTableIndex( &Vcb->TransactionTable );
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG );
+
+ TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+ TransactionEntry->TransactionState = TransactionActive;
+ TransactionEntry->FirstLsn =
+ TransactionEntry->PreviousLsn =
+ TransactionEntry->UndoNextLsn = Li0; //**** LfsZeroLsn;
+
+ //
+ // Remember that we will need a commit record even if we abort
+ // the transaction.
+ //
+
+ TransactionEntry->UndoBytes = QuadAlign( sizeof( NTFS_LOG_RECORD_HEADER ));
+ TransactionEntry->UndoRecords = 1;
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ TransactionTableAcquired = FALSE;
+
+ //
+ // Remember the space for the commit record in our Lfs adjustment.
+ //
+
+ UndoAdjustmentForLfs += QuadAlign( sizeof( NTFS_LOG_RECORD_HEADER ));
+
+ //
+ // If there is an undo operation for this log record, we reserve
+ // the space for another Lfs log record.
+ //
+
+ if (UndoOperation != Noop) {
+ UndoAdjustmentForLfs += Vcb->LogHeaderReservation;
+ }
+ }
+
+ //
+ // At least for now, assume update is contained in one physical page.
+ //
+
+ //ASSERT( (StructureSize == 0) || (StructureSize <= PAGE_SIZE) );
+
+ //
+ // If there isn't enough room for this structure on the stack, we
+ // need to allocate an auxilary buffer.
+ //
+
+ if (LogClusterCount > (PAGE_SIZE / 512)) {
+
+ MyHeader = (PNTFS_LOG_RECORD_HEADER)
+ NtfsAllocatePool(PagedPool, sizeof( NTFS_LOG_RECORD_HEADER )
+ + (LogClusterCount - 1) * sizeof( LCN ));
+
+ }
+
+ //
+ // Now fill in the WriteEntries array and the log record header.
+ //
+
+ WriteEntries[0].Buffer = (PVOID)MyHeader;
+ WriteEntries[0].ByteLength = sizeof(NTFS_LOG_RECORD_HEADER);
+ WriteIndex += 1;
+
+ //
+ // Lookup the Runs for this log record
+ //
+
+ MyHeader->LcnsToFollow = (USHORT)LogClusterCount;
+
+ if (LogClusterCount != 0) {
+ LookupLcns( IrpContext,
+ Scb,
+ LogVcn,
+ LogClusterCount,
+ TRUE,
+ &MyHeader->LcnsForPage[0] );
+
+ WriteEntries[0].ByteLength += (LogClusterCount - 1) * sizeof(LCN);
+ }
+
+ //
+ // If there is a Redo buffer, fill in its write entry.
+ //
+
+ if (RedoLength != 0) {
+
+ WriteEntries[1].Buffer = RedoBuffer;
+ WriteEntries[1].ByteLength = RedoLength;
+ UndoIndex = RedoIndex = WriteIndex;
+ WriteIndex += 1;
+ }
+
+ //
+ // If there is an undo buffer, and it is at a different address than
+ // the redo buffer, then fill in its write entry.
+ //
+
+ if ((RedoBuffer != UndoBuffer) && (UndoLength != 0) &&
+ (UndoOperation != CompensationLogRecord)) {
+
+ WriteEntries[WriteIndex].Buffer = UndoBuffer;
+ WriteEntries[WriteIndex].ByteLength = UndoLength;
+ UndoIndex = WriteIndex;
+ WriteIndex += 1;
+ }
+
+ //
+ // Now fill in the rest of the header. Assume Redo and Undo buffer is
+ // the same, then fix them up if they are not.
+ //
+
+ MyHeader->RedoOperation = (USHORT)RedoOperation;
+ MyHeader->UndoOperation = (USHORT)UndoOperation;
+ MyHeader->RedoOffset = (USHORT)WriteEntries[0].ByteLength;
+ MyHeader->RedoLength = (USHORT)RedoLength;
+ MyHeader->UndoOffset = MyHeader->RedoOffset;
+ if (RedoBuffer != UndoBuffer) {
+ MyHeader->UndoOffset += (USHORT)QuadAlign(MyHeader->RedoLength);
+ }
+ MyHeader->UndoLength = (USHORT)UndoLength;
+ MyHeader->TargetAttribute = (USHORT)Scb->NonpagedScb->OpenAttributeTableIndex;
+ MyHeader->RecordOffset = (USHORT)RecordOffset;
+ MyHeader->AttributeOffset = (USHORT)AttributeOffset;
+ MyHeader->Reserved = 0;
+
+ MyHeader->TargetVcn = LogVcn;
+ MyHeader->ClusterBlockOffset = (USHORT) LogBlocksFromBytesTruncate( ClusterOffset( Vcb, StreamOffset ));
+
+ //
+ // Finally, get our current transaction entry and call Lfs. We acquire
+ // the transaction table exclusive both to synchronize the Lsn updates
+ // on return from Lfs, and also to mark the Bcb dirty before any more
+ // log records are written.
+ //
+ // If we do not do serialize the LfsWrite and CcSetDirtyPinnedData, here is
+ // what can happen:
+ //
+ // We log an update for a page and get an Lsn back
+ //
+ // Another thread writes a start of checkpoint record
+ // This thread then collects all of the dirty pages at that time
+ // Sometime it writes the dirty page table
+ //
+ // The former thread which had been preempted, now sets the Bcb dirty
+ //
+ // If we crash at this time, the page we updated is not in the dirty page
+ // table of the checkpoint, and it its update record is also not seen since
+ // it was written before the start of the checkpoint!
+ //
+ // Note however, since the page being updated is pinned and cannot be written,
+ // updating the Lsn in the page may simply be considered part of the update.
+ // Whoever is doing this update (to the Mft or an Index buffer), must have the
+ // Mft or Index acquired exclusive anyway.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ TransactionTableAcquired = TRUE;
+
+ TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ //
+ // Set up the UndoNextLsn. If this is a normal log record, then use
+ // the UndoNextLsn stored in the transaction entry; otherwise, use
+ // the one passed in as the Undo buffer.
+ //
+
+ if (UndoOperation != CompensationLogRecord) {
+
+ UndoNextLsn = TransactionEntry->UndoNextLsn;
+
+ //
+ // If there is undo information, calculate the number to pass to Lfs
+ // for undo bytes to reserve.
+ //
+
+ if (UndoOperation != Noop) {
+
+ UndoBytes += QuadAlign(WriteEntries[0].ByteLength);
+
+ if (UndoIndex != 0) {
+
+ UndoBytes += QuadAlign(WriteEntries[UndoIndex].ByteLength);
+ }
+
+ UndoRecords += 1;
+ }
+
+ } else {
+
+ UndoNextLsn = *(PLSN)UndoBuffer;
+
+ //
+ // We can reduce our Undo requirements, by the Redo data being
+ // logged. This is either an abort record for a previous action
+ // or a commit record. If it is a commit record we accounted
+ // for it above on the first NtfsWriteLog, and NtfsCommitTransaction
+ // will adjust for the rest.
+ //
+
+ if (!FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
+
+ UndoBytes -= QuadAlign(WriteEntries[0].ByteLength);
+
+ if (RedoIndex != 0) {
+
+ UndoBytes -= QuadAlign(WriteEntries[RedoIndex].ByteLength);
+ }
+
+ UndoRecords -= 1;
+ }
+ }
+
+#ifdef NTFSDBG
+ //
+ // Perform log-file-full fail checking. We do not perform this check if
+ // we are writing an undo record (since we are guaranteed space to undo
+ // things).
+ //
+
+ if (UndoOperation != CompensationLogRecord &&
+ (IrpContext->MajorFunction != IRP_MJ_FILE_SYSTEM_CONTROL ||
+ IrpContext->MinorFunction != IRP_MN_MOUNT_VOLUME)) {
+ //
+ // There should never be any log records during clean checkpoints
+ //
+
+ ASSERT( !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE ));
+
+ LogFileFullFailCheck( IrpContext );
+ }
+#endif
+
+ //
+ // Call Lfs to write the record.
+ //
+
+ LfsWrite( Vcb->LogHandle,
+ WriteIndex,
+ &WriteEntries[0],
+ LfsClientRecord,
+ &IrpContext->TransactionId,
+ UndoNextLsn,
+ TransactionEntry->PreviousLsn,
+ UndoBytes + UndoAdjustmentForLfs,
+ &ReturnLsn );
+
+ //
+ // Now that we are successful, update the transaction entry appropriately.
+ //
+
+ TransactionEntry->UndoBytes += UndoBytes;
+ TransactionEntry->UndoRecords += UndoRecords;
+ TransactionEntry->PreviousLsn = ReturnLsn;
+
+ //
+ // The UndoNextLsn for the transaction depends on whether we are
+ // doing a compensation log record or not.
+ //
+
+ if (UndoOperation != CompensationLogRecord) {
+ TransactionEntry->UndoNextLsn = ReturnLsn;
+ } else {
+ TransactionEntry->UndoNextLsn = UndoNextLsn;
+ }
+
+ //
+ // If this is the first Lsn, then we have to update that as
+ // well.
+ //
+
+ if (TransactionEntry->FirstLsn.QuadPart == 0) {
+
+ TransactionEntry->FirstLsn = ReturnLsn;
+ }
+
+ //
+ // Set to use this Lsn when marking dirty below
+ //
+
+ DirtyLsn = &ReturnLsn;
+
+ //
+ // Set the flag in the Irp Context which indicates we wrote
+ // a log record to disk.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG );
+
+ } finally {
+
+ DebugUnwind( NtfsWriteLog );
+
+ //
+ // Now set the Bcb dirty if specified. We want to set it no matter
+ // what happens, because our caller has modified the buffer and is
+ // counting on us to call the Cache Manager.
+ //
+
+ if (ARGUMENT_PRESENT(Bcb)) {
+
+ TIMER_STATUS TimerStatus;
+
+ CcSetDirtyPinnedData( Bcb, DirtyLsn );
+
+ //
+ // Synchronize with the checkpoint timer and other instances of this routine.
+ //
+ // Perform an interlocked exchange to indicate that a timer is being set.
+ //
+ // If the previous value indicates that no timer was set, then we
+ // enable the volume checkpoint timer. This will guarantee that a checkpoint
+ // will occur to flush out the dirty Bcb data.
+ //
+ // If the timer was set previously, then it is guaranteed that a checkpoint
+ // will occur without this routine having to reenable the timer.
+ //
+ // If the timer and checkpoint occurred between the dirtying of the Bcb and
+ // the setting of the timer status, then we will be queueing a single extra
+ // checkpoint on a clean volume. This is not considered harmful.
+ //
+
+ //
+ // Atomically set the timer status to indicate a timer is being set and
+ // retrieve the previous value.
+ //
+
+ TimerStatus = InterlockedExchange( &NtfsData.TimerStatus, TIMER_SET );
+
+ //
+ // If the timer is not currently set then we must start the checkpoint timer
+ // to make sure the above dirtying is flushed out.
+ //
+
+ if (TimerStatus == TIMER_NOT_SET) {
+
+ LONGLONG FiveSecondsFromNow = -5*1000*1000*10;
+
+ KeSetTimer( &NtfsData.VolumeCheckpointTimer,
+ *(PLARGE_INTEGER)&FiveSecondsFromNow,
+ &NtfsData.VolumeCheckpointDpc );
+ }
+ }
+
+ if (TransactionTableAcquired) {
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ if (AttributeTableAcquired) {
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ }
+
+ if (MyHeader != (PNTFS_LOG_RECORD_HEADER)&LocalHeader) {
+
+ NtfsFreePool( MyHeader );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsWriteLog -> %016I64x\n", ReturnLsn ) );
+
+ return ReturnLsn;
+}
+
+
+VOID
+NtfsCheckpointVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN OwnsCheckpoint,
+ IN BOOLEAN CleanVolume,
+ IN BOOLEAN FlushVolume,
+ IN LSN LastKnownLsn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called periodically to perform a checkpoint on the volume
+ with respect to the log file. The checkpoint dumps a bunch of log file
+ state information to the log file, and finally writes a summary of the
+ dumped information in its Restart Area.
+
+ This checkpoint dumps the following:
+
+ Open Attribute Table
+ (all of the attribute names for the Attribute Table)
+ Dirty Pages Table
+ Transaction Table
+
+Arguments:
+
+ Vcb - Pointer to the Vcb on which the checkpoint is to occur.
+
+ OwnsCheckpoint - TRUE if the caller has already taken steps to insure
+ that he may proceed with the checkpointing. In this case we
+ don't do any checks for other checkpoints and don't clear the
+ checkpoint flag or notify any waiting checkpoint threads.
+
+ CleanVolume - TRUE if the caller wishes to clean the volume before doing
+ the checkpoint, or FALSE for a normal periodic checkpoint.
+
+ FlushVolume - Applies only if CleanVolume is TRUE. This indicates if we should
+ should flush the volume or only Lsn streams.
+
+ LastKnownLsn - Applies only if CleanVolume is TRUE. Only perform the
+ clean checkpoint if this value is the same as the last restart area
+ in the Vcb. This will prevent us from doing unecesary clean
+ checkpoints.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ RESTART_AREA RestartArea;
+ RESTART_POINTERS DirtyPages;
+ RESTART_POINTERS Pointers;
+ LSN BaseLsn;
+ PATTRIBUTE_NAME_ENTRY NamesBuffer = NULL;
+ PTRANSACTION_ENTRY TransactionEntry;
+ BOOLEAN DirtyPageTableInitialized = FALSE;
+ BOOLEAN OpenAttributeTableAcquired = FALSE;
+ BOOLEAN TransactionTableAcquired = FALSE;
+ LSN OldestDirtyPageLsn = Li0;
+ BOOLEAN AcquireFiles = FALSE;
+ BOOLEAN BitmapAcquired = FALSE;
+ BOOLEAN PostDefrag = FALSE;
+ KPRIORITY PreviousPriority;
+ BOOLEAN RestorePreviousPriority = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckpointVolume:\n") );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+
+ if (!OwnsCheckpoint) {
+
+ //
+ // Acquire the checkpoint event.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ //
+ // We will want to post a defrag if defragging is permitted and enabled
+ // and we have begun the defrag operation or have excess mapping.
+ // If the defrag hasn't been triggered then check the Mft free
+ // space. We can skip defragging if a defrag operation is
+ // currently active.
+ //
+
+ if (!CleanVolume
+ && FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED )
+ && FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED )
+ && !FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE )) {
+
+ if (FlagOn( Vcb->MftDefragState,
+ VCB_MFT_DEFRAG_TRIGGERED | VCB_MFT_DEFRAG_EXCESS_MAP )) {
+
+ PostDefrag = TRUE;
+
+ } else {
+
+ NtfsCheckForDefrag( Vcb );
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
+
+ PostDefrag = TRUE;
+
+ } else {
+
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ }
+ }
+ }
+
+ //
+ // If a checkpoint is already active, we either have to get out,
+ // or wait for it.
+ //
+
+ while (FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS )) {
+
+ //
+ // Release the checkpoint event because we cannot checkpoint now.
+ //
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ if (CleanVolume) {
+
+ NtfsWaitOnCheckpointNotify( IrpContext, Vcb );
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ } else {
+
+ return;
+ }
+ }
+
+ //
+ // We now have the checkpoint event. Check if there is still
+ // a need to perform the checkpoint.
+ //
+
+ if (CleanVolume &&
+ LastKnownLsn.QuadPart != Vcb->LastRestartArea.QuadPart) {
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ return;
+ }
+
+ SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
+ NtfsResetCheckpointNotify( IrpContext, Vcb );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ //
+ // If this is a clean volume checkpoint then boost the priority of
+ // this thread.
+ //
+
+ if (CleanVolume) {
+
+ PreviousPriority = KeSetPriorityThread( &PsGetCurrentThread()->Tcb,
+ LOW_REALTIME_PRIORITY );
+
+ if (PreviousPriority != LOW_REALTIME_PRIORITY) {
+
+ RestorePreviousPriority = TRUE;
+ }
+ }
+ }
+
+ RtlZeroMemory( &RestartArea, sizeof(RESTART_AREA) );
+ RtlZeroMemory( &DirtyPages, sizeof(RESTART_POINTERS) );
+
+ //
+ // Insure cleanup on the way out
+ //
+
+ try {
+
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+ ULONG NameBytes = 0;
+
+ //
+ // Now remember the current "last Lsn" value as the start of
+ // our checkpoint. We acquire the transaction table to capture
+ // this value to synchronize with threads who are writing log
+ // records and setting pages dirty as atomic actions.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ BaseLsn =
+ RestartArea.StartOfCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+
+
+ ASSERT( (RestartArea.StartOfCheckpoint.QuadPart != 0) ||
+ FlagOn(Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN) );
+
+ //
+ // If the last checkpoint was completely clean, and no one has
+ // written to the log since then, we can just return.
+ //
+
+ if (FlagOn( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN )
+
+ &&
+
+ (RestartArea.StartOfCheckpoint.QuadPart == Vcb->EndOfLastCheckpoint.QuadPart)
+
+ &&
+
+ !CleanVolume) {
+
+ //
+ // Let's take this opportunity to shrink the Open Attribute and Transaction
+ // table back if they have gotten large.
+ //
+
+ //
+ // First the Open Attribute Table
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
+ OpenAttributeTableAcquired = TRUE;
+
+ if (IsRestartTableEmpty(&Vcb->OpenAttributeTable)
+
+ &&
+
+ (Vcb->OpenAttributeTable.Table->NumberEntries >
+ HIGHWATER_ATTRIBUTE_COUNT)) {
+
+ //
+ // Initialize first in case we get an allocation failure.
+ //
+
+ InitializeNewTable( sizeof(OPEN_ATTRIBUTE_ENTRY),
+ INITIAL_NUMBER_ATTRIBUTES,
+ &Pointers );
+
+ NtfsFreePool( Vcb->OpenAttributeTable.Table );
+ Vcb->OpenAttributeTable.Table = Pointers.Table;
+ }
+
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ OpenAttributeTableAcquired = FALSE;
+
+ //
+ // Now check the transaction table (freeing in the finally clause).
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ TransactionTableAcquired = TRUE;
+
+ if (IsRestartTableEmpty(&Vcb->TransactionTable)
+
+ &&
+
+ (Vcb->TransactionTable.Table->NumberEntries >
+ HIGHWATER_TRANSACTION_COUNT)) {
+
+ //
+ // Initialize first in case we get an allocation failure.
+ //
+
+ InitializeNewTable( sizeof(TRANSACTION_ENTRY),
+ INITIAL_NUMBER_TRANSACTIONS,
+ &Pointers );
+
+ NtfsFreePool( Vcb->TransactionTable.Table );
+ Vcb->TransactionTable.Table = Pointers.Table;
+ }
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Flush any dangling dirty pages from before the last restart.
+ // Note that it is arbitrary what Lsn we flush to here, and, in fact,
+ // it is not absolutely required that we flush anywhere at all - we
+ // could actually rely on the Lazy Writer. All we are trying to do
+ // is reduce the amount of work that we will have to do at Restart,
+ // by not forcing ourselves to have to go too far back in the log.
+ // Presumably this can only happen for some reason the system is
+ // starting to produce dirty pages faster than the lazy writer is
+ // writing them.
+ //
+ // (We may wish to play with taking this call out...)
+ //
+ // This may be an appropriate place to worry about this, but, then
+ // again, the Lazy Writer is using (currently) five threads. It may
+ // not be appropriate to hold up this one thread doing the checkpoint
+ // if the Lazy Writer is getting behind. How many dirty pages we
+ // can even have is limited by the size of memory, so if the log file
+ // is large enough, this may not be an issue. It seems kind of nice
+ // to just let the Lazy Writer keep writing dirty pages as he does
+ // now.
+ //
+ // if (!FlagOn(Vcb->VcbState, VCB_STATE_LAST_CHECKPOINT_CLEAN)) {
+ // CcFlushPagesToLsn( Vcb->LogHandle, &Vcb->LastRestartArea );
+ // }
+ //
+
+ //
+ // Now we must clean the volume here if that is what the caller wants.
+ //
+
+ if (CleanVolume) {
+
+ NtfsCleanCheckpoints += 1;
+
+ //
+ // Lock down the volume if this is a clean checkpoint.
+ //
+
+ NtfsAcquireAllFiles( IrpContext, Vcb, FlushVolume, FALSE );
+
+#ifdef NTFSDBG
+ ASSERT( !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE ));
+ DebugDoit( SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE ));
+#endif // NTFSDBG
+
+
+ AcquireFiles = TRUE;
+
+ //
+ // Now we will acquire the Open Attribute Table exclusive to delete
+ // all of the entries, since we want to write a clean checkpoint.
+ // This is OK, since we have the global resource and nothing else
+ // can be going on. (Similarly we are writing an empty transaction
+ // table, while in fact we will be the only transaction, but there
+ // is no need to capture our guy, nor explicitly empty this table.)
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
+ OpenAttributeTableAcquired = TRUE;
+
+ //
+ // First reclaim the page we have reserved in the undo total, to
+ // guarantee that we can flush the log file.
+ //
+
+ LfsResetUndoTotal( Vcb->LogHandle, 1, -(LONG)(2 * PAGE_SIZE) );
+
+ if (FlushVolume) {
+
+ (VOID)NtfsFlushVolume( IrpContext, Vcb, TRUE, FALSE, FALSE, FALSE );
+
+ } else {
+
+ NtfsFlushLsnStreams( Vcb );
+ }
+
+ SetFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
+
+ //
+ // Loop through to deallocate all of the open attribute entries. Any
+ // that point to an Scb need to get the index in the Scb zeroed. If
+ // they do not point to an Scb, we have to see if there is a name to
+ // free.
+ //
+
+ AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ while (AttributeEntry != NULL) {
+
+ ULONG Index;
+
+ if (AttributeEntry->Overlay.Scb != NULL) {
+
+ AttributeEntry->Overlay.Scb->NonpagedScb->OpenAttributeTableIndex = 0;
+
+ } else {
+
+ //
+ // Delete its name, if it has one. Check that we aren't
+ // using the hardcode $I30 name.
+ //
+
+ if ((AttributeEntry->AttributeName.Buffer != NULL) &&
+ (AttributeEntry->AttributeName.Buffer != NtfsFileNameIndexName)) {
+
+ NtfsFreePool( AttributeEntry->AttributeName.Buffer );
+ }
+ }
+
+ //
+ // Get the index for the entry.
+ //
+
+ Index = GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+
+ NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable,
+ Index );
+
+ AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+ }
+
+ //
+ // Initialize first in case we get an allocation failure.
+ //
+
+ ASSERT(IsRestartTableEmpty(&Vcb->OpenAttributeTable));
+
+ InitializeNewTable( sizeof(OPEN_ATTRIBUTE_ENTRY),
+ INITIAL_NUMBER_ATTRIBUTES,
+ &Pointers );
+
+ NtfsFreePool( Vcb->OpenAttributeTable.Table );
+ Vcb->OpenAttributeTable.Table = Pointers.Table;
+
+ //
+ // Initialize first in case we get an allocation failure.
+ // Make sure we commit the current transaction.
+ //
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ ASSERT(IsRestartTableEmpty(&Vcb->TransactionTable));
+
+ InitializeNewTable( sizeof(TRANSACTION_ENTRY),
+ INITIAL_NUMBER_TRANSACTIONS,
+ &Pointers );
+
+ NtfsFreePool( Vcb->TransactionTable.Table );
+ Vcb->TransactionTable.Table = Pointers.Table;
+
+ //
+ // Make sure we do not process any log file before the restart
+ // area, because we did not dump the open attribute table.
+ //
+
+ RestartArea.StartOfCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
+
+ //
+ // Save some work if this is a clean checkpoint
+ //
+
+ } else {
+
+ PDIRTY_PAGE_ENTRY DirtyPage;
+ POPEN_ATTRIBUTE_ENTRY OpenEntry;
+ ULONG JustMe = 0;
+
+ //
+ // Now we construct the dirty page table by calling the Cache Manager.
+ // For each dirty page on files tagged with our log handle, he will
+ // call us back at our DirtyPageRoutine. We will allocate the initial
+ // Dirty Page Table, but we will let the call back routine grow it as
+ // necessary.
+ //
+
+ NtfsInitializeRestartTable( sizeof(DIRTY_PAGE_ENTRY) +
+ (Vcb->ClustersPerPage - 1) * sizeof(LCN),
+ 32,
+ &DirtyPages );
+
+ NtfsAcquireExclusiveRestartTable( &DirtyPages, TRUE );
+
+ DirtyPageTableInitialized = TRUE;
+
+ //
+ // Now we will acquire the Open Attribute Table shared to freeze changes.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
+ OpenAttributeTableAcquired = TRUE;
+
+ //
+ // Loop to see how much we will have to allocate for attribute names.
+ //
+
+ AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ while (AttributeEntry != NULL) {
+
+ //
+ // This checks for one type of aliasing.
+ //
+
+ // ASSERT( (AttributeEntry->Overlay.Scb == NULL) ||
+ // (AttributeEntry->Overlay.Scb->OpenAttributeTableIndex ==
+ // GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
+ // AttributeEntry )));
+
+ //
+ // Clear the DirtyPageSeen flag prior to collecting the dirty pages,
+ // to help us figure out which Open Attribute Entries we still need.
+ //
+
+ AttributeEntry->DirtyPagesSeen = FALSE;
+
+ if (AttributeEntry->AttributeName.Length != 0) {
+
+ //
+ // Add to our name total, the size of an Attribute Entry,
+ // which includes the size of the terminating UNICODE_NULL.
+ //
+
+ NameBytes += AttributeEntry->AttributeName.Length +
+ sizeof(ATTRIBUTE_NAME_ENTRY);
+ }
+
+ AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+ }
+
+ //
+ // Now call the Cache Manager to give us all of our dirty pages
+ // via the DirtyPageRoutine callback, and remember what the oldest
+ // Lsn is for a dirty page.
+ //
+
+ OldestDirtyPageLsn = CcGetDirtyPages( Vcb->LogHandle,
+ &DirtyPageRoutine,
+ (PVOID)IrpContext,
+ (PVOID)&DirtyPages );
+
+ if (OldestDirtyPageLsn.QuadPart != 0 &&
+ OldestDirtyPageLsn.QuadPart < Vcb->LastBaseLsn.QuadPart) {
+
+ OldestDirtyPageLsn = Vcb->LastBaseLsn;
+ }
+
+ //
+ // Now loop through the dirty page table to extract all of the Vcn/Lcn
+ // Mapping that we have, and insert it into the appropriate Scb.
+ //
+
+ DirtyPage = NtfsGetFirstRestartTable( &DirtyPages );
+
+ //
+ // The dirty page routine is called while holding spin locks,
+ // so it cannot take page faults. Thus we must scan the dirty
+ // page table we just built and fill in the Lcns here.
+ //
+
+ while (DirtyPage != NULL) {
+
+ PSCB Scb;
+
+ OpenEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
+ DirtyPage->TargetAttribute );
+
+ ASSERT(IsRestartTableEntryAllocated(OpenEntry));
+
+ ASSERT( DirtyPage->OldestLsn.QuadPart >= Vcb->LastBaseLsn.QuadPart );
+
+ Scb = OpenEntry->Overlay.Scb;
+
+ //
+ // If we have Lcn's then look them up.
+ //
+
+ if (DirtyPage->LcnsToFollow != 0) {
+
+ LookupLcns( IrpContext,
+ Scb,
+ DirtyPage->Vcn,
+ DirtyPage->LcnsToFollow,
+ FALSE,
+ &DirtyPage->LcnsForPage[0] );
+
+ //
+ // Other free this dirty page entry.
+ //
+
+ } else {
+
+ NtfsFreeRestartTableIndex( &DirtyPages,
+ GetIndexFromRestartEntry( &DirtyPages,
+ DirtyPage ));
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ DirtyPage = NtfsGetNextRestartTable( &DirtyPages,
+ DirtyPage );
+ }
+
+ //
+ // If there were any names, then allocate space for them and copy
+ // them out.
+ //
+
+ if (NameBytes != 0) {
+
+ PATTRIBUTE_NAME_ENTRY Name;
+
+ //
+ // Allocate the buffer, with space for two terminating 0's on
+ // the end.
+ //
+
+ NameBytes += 4;
+ Name =
+ NamesBuffer = NtfsAllocatePool( NonPagedPool, NameBytes );
+
+ //
+ // Now loop to copy the names.
+ //
+
+ AttributeEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ while (AttributeEntry != NULL) {
+
+ //
+ // Free the Open Attribute Entry if there were no
+ // dirty pages and the Scb is gone. This is the only
+ // place they are deleted. (Yes, I know we allocated
+ // space for its name, but I didn't want to make three
+ // passes through the open attribute table. Permeter
+ // is running as we speak, and showing 407 open files
+ // on NT/IDW5.)
+ //
+
+ if (!AttributeEntry->DirtyPagesSeen
+
+ &&
+
+ (AttributeEntry->Overlay.Scb == NULL)) {
+
+ ULONG Index;
+
+ //
+ // Get the index for the entry.
+ //
+
+ Index = GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+
+ //
+ // Delete its name and free it up.
+ //
+
+ if ((AttributeEntry->AttributeName.Buffer != NULL) &&
+ (AttributeEntry->AttributeName.Buffer != NtfsFileNameIndexName)) {
+
+ NtfsFreePool( AttributeEntry->AttributeName.Buffer );
+ }
+
+ NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable,
+ Index );
+
+ //
+ // Otherwise, if we are not deleting it, we have to
+ // copy its name into the buffer we allocated.
+ //
+
+ } else if (AttributeEntry->AttributeName.Length != 0) {
+
+ //
+ // Prefix each name in the buffer with the attribute index
+ // and name length.
+ //
+
+ Name->Index = (USHORT)GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+ Name->NameLength = AttributeEntry->AttributeName.Length;
+ RtlMoveMemory( &Name->Name[0],
+ AttributeEntry->AttributeName.Buffer,
+ AttributeEntry->AttributeName.Length );
+
+ Name->Name[Name->NameLength/2] = 0;
+
+ Name = (PATTRIBUTE_NAME_ENTRY)((PCHAR)Name +
+ sizeof(ATTRIBUTE_NAME_ENTRY) +
+ Name->NameLength);
+
+ ASSERT( (PCHAR)Name <= ((PCHAR)NamesBuffer + NameBytes - 4) );
+ }
+
+ AttributeEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ AttributeEntry );
+ }
+
+ //
+ // Terminate the Names Buffer.
+ //
+
+ Name->Index = 0;
+ Name->NameLength = 0;
+ }
+
+ //
+ // Now write all of the non-empty tables to the log.
+ //
+
+ //
+ // Write the Open Attribute Table
+ //
+
+ if (!IsRestartTableEmpty(&Vcb->OpenAttributeTable)) {
+ RestartArea.OpenAttributeTableLsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NULL,
+ OpenAttributeTableDump,
+ Vcb->OpenAttributeTable.Table,
+ SizeOfRestartTable(&Vcb->OpenAttributeTable),
+ Noop,
+ NULL,
+ 0,
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+
+ RestartArea.OpenAttributeTableLength =
+ SizeOfRestartTable(&Vcb->OpenAttributeTable);
+ JustMe = 1;
+ }
+
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ OpenAttributeTableAcquired = FALSE;
+
+ //
+ // Write the Open Attribute Names
+ //
+
+ if (NameBytes != 0) {
+ RestartArea.AttributeNamesLsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NULL,
+ AttributeNamesDump,
+ NamesBuffer,
+ NameBytes,
+ Noop,
+ NULL,
+ 0,
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+
+ RestartArea.AttributeNamesLength = NameBytes;
+ JustMe = 1;
+ }
+
+ //
+ // Write the Dirty Page Table
+ //
+
+ if (!IsRestartTableEmpty(&DirtyPages)) {
+ RestartArea.DirtyPageTableLsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NULL,
+ DirtyPageTableDump,
+ DirtyPages.Table,
+ SizeOfRestartTable(&DirtyPages),
+ Noop,
+ NULL,
+ 0,
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+
+ RestartArea.DirtyPageTableLength = SizeOfRestartTable(&DirtyPages);
+ JustMe = 1;
+ }
+
+ //
+ // Write the Transaction Table if there is more than just us. We
+ // are a transaction if we wrote any log records above.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ TransactionTableAcquired = TRUE;
+
+ //
+ // Assumee will want to do at least one more checkpoint.
+ //
+
+ ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
+
+ if ((ULONG)Vcb->TransactionTable.Table->NumberAllocated > JustMe) {
+ RestartArea.TransactionTableLsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NULL,
+ TransactionTableDump,
+ Vcb->TransactionTable.Table,
+ SizeOfRestartTable(&Vcb->TransactionTable),
+ Noop,
+ NULL,
+ 0,
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+
+ RestartArea.TransactionTableLength =
+ SizeOfRestartTable(&Vcb->TransactionTable);
+
+ //
+ // Loop to see if the oldest Lsn comes from the transaction table.
+ //
+
+ TransactionEntry = NtfsGetFirstRestartTable( &Vcb->TransactionTable );
+
+ while (TransactionEntry != NULL) {
+ if ((TransactionEntry->FirstLsn.QuadPart != 0)
+
+ &&
+
+ (TransactionEntry->FirstLsn.QuadPart < BaseLsn.QuadPart)) {
+
+ BaseLsn = TransactionEntry->FirstLsn;
+ }
+
+ TransactionEntry = NtfsGetNextRestartTable( &Vcb->TransactionTable,
+ TransactionEntry );
+ }
+
+ //
+ // If the transaction table is otherwise empty, then this is a good
+ // time to reset our totals with Lfs, in case our counts get off a bit.
+ //
+
+ } else {
+
+ //
+ // If we are a transaction, then we have to add in our counts.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->TransactionTable, IrpContext->TransactionId );
+
+ LfsResetUndoTotal( Vcb->LogHandle,
+ TransactionEntry->UndoRecords + 2,
+ TransactionEntry->UndoBytes +
+ QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
+
+ //
+ // Otherwise, we reset to our "idle" requirements.
+ //
+
+ } else {
+ LfsResetUndoTotal( Vcb->LogHandle,
+ 2,
+ QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
+ }
+
+ //
+ // If the DirtyPage table is entry then mark this as a clean checkpoint.
+ //
+
+ if (IsRestartTableEmpty( &DirtyPages )) {
+
+ SetFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
+ }
+ }
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ TransactionTableAcquired = FALSE;
+ }
+
+ //
+ // So far BaseLsn holds the minimum of the start Lsn for the checkpoint,
+ // or any of the FirstLsn fields for active transactions. Now we see
+ // if the oldest Lsn we need in the log should actually come from the
+ // oldest page in the dirty page table.
+ //
+
+ if ((OldestDirtyPageLsn.QuadPart != 0)
+
+ &&
+
+ (OldestDirtyPageLsn.QuadPart < BaseLsn.QuadPart)) {
+
+ BaseLsn = OldestDirtyPageLsn;
+ }
+
+ //
+ // Finally, write our Restart Area to describe all of the above, and
+ // give Lfs our new BaseLsn.
+ //
+
+ Vcb->LastBaseLsn = Vcb->LastRestartArea = BaseLsn;
+ LfsWriteRestartArea( Vcb->LogHandle,
+ sizeof(RESTART_AREA),
+ &RestartArea,
+ &Vcb->LastRestartArea );
+
+ //
+ // If this is a clean checkpoint then initialize our reserved area.
+ //
+
+ if (CleanVolume) {
+
+ LfsResetUndoTotal( Vcb->LogHandle, 2, QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
+ }
+
+ //
+ // Now remember where the log file is at now, so we know when to
+ // go idle above.
+ //
+
+ Vcb->EndOfLastCheckpoint = LfsQueryLastLsn( Vcb->LogHandle );
+
+ //
+ // Now we want to check if we can release any of the clusters in the
+ // deallocated cluster arrays. We know we can look at the
+ // fields in the PriorDeallocatedClusters structure because they
+ // are never modified in the running system.
+ //
+ // We compare the Lsn in the Prior structure to see if it is older
+ // than the new BaseLsn value. If so we will acquire the volume
+ // bitmap in order to swap the structures.
+ //
+
+ if ((Vcb->ActiveDeallocatedClusters != NULL) &&
+
+ ((Vcb->PriorDeallocatedClusters->ClusterCount != 0) ||
+ (Vcb->ActiveDeallocatedClusters->ClusterCount != 0))) {
+
+ if (BaseLsn.QuadPart > Vcb->PriorDeallocatedClusters->Lsn.QuadPart) {
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+ BitmapAcquired = TRUE;
+
+ //
+ // If the Prior Mcb is not empty then empty it.
+ //
+
+ if (Vcb->PriorDeallocatedClusters->ClusterCount != 0) {
+
+ //
+ // Decrement the count of deallocated clusters by the amount stored here.
+ //
+
+ Vcb->DeallocatedClusters =
+ Vcb->DeallocatedClusters - Vcb->PriorDeallocatedClusters->ClusterCount;
+
+ //
+ // Remove all of the mappings in the Mcb.
+ //
+
+ FsRtlTruncateLargeMcb( &Vcb->PriorDeallocatedClusters->Mcb, (LONGLONG)0 );
+
+ //
+ // Remember that there are no deallocated structures left in
+ // the Mcb.
+ //
+
+ Vcb->PriorDeallocatedClusters->ClusterCount = 0;
+ }
+
+ //
+ // If this is a clean checkpoint then free the active deallocated
+ // clusters. Otherwise do at least one more checkpoint.
+ //
+
+ if (Vcb->ActiveDeallocatedClusters->ClusterCount != 0) {
+
+ if (CleanVolume) {
+
+ //
+ // Decrement the count of deallocated clusters by the amount stored here.
+ //
+
+ Vcb->DeallocatedClusters =
+ Vcb->DeallocatedClusters - Vcb->ActiveDeallocatedClusters->ClusterCount;
+
+ //
+ // Remove all of the mappings in the Mcb.
+ //
+
+ FsRtlTruncateLargeMcb( &Vcb->ActiveDeallocatedClusters->Mcb, (LONGLONG)0 );
+
+ //
+ // Remember that there are no deallocated structures left in
+ // the Mcb.
+ //
+
+ Vcb->ActiveDeallocatedClusters->ClusterCount = 0;
+
+ //
+ // We know the count has gone to zero.
+ //
+
+ ASSERT( Vcb->DeallocatedClusters == 0 );
+
+ } else {
+
+ PDEALLOCATED_CLUSTERS Temp;
+
+ Temp = Vcb->PriorDeallocatedClusters;
+
+ Vcb->PriorDeallocatedClusters = Vcb->ActiveDeallocatedClusters;
+ Vcb->ActiveDeallocatedClusters = Temp;
+
+ //
+ // Remember the last Lsn for the prior Mcb.
+ //
+
+ Vcb->PriorDeallocatedClusters->Lsn = LfsQueryLastLsn( Vcb->LogHandle );
+
+ //
+ // Always do at least one more checkpoint.
+ //
+
+ ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
+ }
+ }
+
+ } else {
+
+ ClearFlag( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsCheckpointVolume );
+
+ if (BitmapAcquired) {
+
+ NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
+ }
+
+ //
+ // If the Dirty Page Table got initialized, free it up.
+ //
+
+ if (DirtyPageTableInitialized) {
+ NtfsFreeRestartTable( &DirtyPages );
+ }
+
+ //
+ // Release any resources
+ //
+
+ if (OpenAttributeTableAcquired) {
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ }
+
+ if (TransactionTableAcquired) {
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ //
+ // Release any names buffer.
+ //
+
+ if (NamesBuffer != NULL) {
+ NtfsFreePool( NamesBuffer );
+ }
+
+ //
+ // If this checkpoint created a transaction, free the index now.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+
+ IrpContext->TransactionId = 0;
+ }
+
+ if (AcquireFiles) {
+
+#ifdef NTFSDBG
+ ASSERT( FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE ));
+ DebugDoit( ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE ));
+#endif // NTFSDBG
+
+ NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
+ }
+
+ //
+ // If we didn't own the checkpoint operation then indicate
+ // that someone else is free to checkpoint.
+ //
+
+ if (!OwnsCheckpoint) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->CheckpointFlags,
+ VCB_CHECKPOINT_IN_PROGRESS | VCB_DUMMY_CHECKPOINT_POSTED);
+
+ NtfsSetCheckpointNotify( IrpContext, Vcb );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ if (RestorePreviousPriority) {
+
+ KeSetPriorityThread( &PsGetCurrentThread()->Tcb,
+ PreviousPriority );
+ }
+ }
+
+ //
+ // If we need to post a defrag request then do so now.
+ //
+
+ if (PostDefrag) {
+
+ PDEFRAG_MFT DefragMft;
+
+ //
+ // Use a try-except to ignore allocation errors.
+ //
+
+ try {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ if (!FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE )) {
+
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ DefragMft = NtfsAllocatePool( NonPagedPool, sizeof( DEFRAG_MFT ));
+
+ DefragMft->Vcb = Vcb;
+ DefragMft->DeallocateWorkItem = TRUE;
+
+ //
+ // Send it off.....
+ //
+
+ ExInitializeWorkItem( &DefragMft->WorkQueueItem,
+ (PWORKER_THREAD_ROUTINE)NtfsDefragMft,
+ (PVOID)DefragMft );
+
+ ExQueueWorkItem( &DefragMft->WorkQueueItem, CriticalWorkQueue );
+
+ } else {
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ } except( FsRtlIsNtstatusExpected( GetExceptionCode() )
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCheckpointVolume -> VOID\n") );
+}
+
+
+VOID
+NtfsCheckpointForLogFileFull (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to perform the clean checkpoint generated after
+ a log file full. This routine will call the clean checkpoint routine
+ and then release all of the resources acquired.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ IrpContext->ExceptionStatus = 0;
+
+ //
+ // Call the checkpoint routine to do the actual work.
+ //
+
+ NtfsCheckpointVolume( IrpContext,
+ IrpContext->Vcb,
+ FALSE,
+ TRUE,
+ FALSE,
+ IrpContext->LastRestartArea );
+
+ ASSERT( IrpContext->TransactionId == 0 );
+
+ while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ IrpContext->LastRestartArea = Li0;
+
+ return;
+}
+
+
+
+VOID
+NtfsCommitCurrentTransaction (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine commits the current transaction by writing a final record
+ to the log and deallocating the transaction Id.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PTRANSACTION_ENTRY TransactionEntry;
+ PVCB Vcb = IrpContext->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // If this request created a transaction, complete it now.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ LSN CommitLsn;
+
+ //
+ // It is possible to get a LOG_FILE_FULL before writing
+ // out the first log record of a transaction. In that
+ // case there is a transaction Id but we haven't reserved
+ // space in the log file. It is wrong to write the
+ // commit record in this case because we can get an
+ // unexpected LOG_FILE_FULL. We can also test the UndoRecords
+ // count in the transaction entry but don't want to acquire
+ // the restart table to make this check.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG )) {
+
+ //
+ // Write the log record to "forget" this transaction,
+ // because it should not be aborted. Until if/when we
+ // do real TP, commit and forget are atomic.
+ //
+
+ CommitLsn =
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NULL,
+ ForgetTransaction,
+ NULL,
+ 0,
+ CompensationLogRecord,
+ (PVOID)&Li0,
+ sizeof(LSN),
+ (LONGLONG)0,
+ 0,
+ 0,
+ 0 );
+ }
+
+ //
+ // We can now free the transaction table index, because we are
+ // done with it now.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ TransactionEntry = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ //
+ // Call Lfs to free our undo space.
+ //
+
+ if ((TransactionEntry->UndoRecords != 0) &&
+ (!FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ))) {
+
+ LfsResetUndoTotal( Vcb->LogHandle,
+ TransactionEntry->UndoRecords,
+ -TransactionEntry->UndoBytes );
+ }
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ IrpContext->TransactionId = 0;
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+
+ //
+ // One way we win by being recoverable, is that we do not really
+ // have to do write-through - flushing the updates to the log
+ // is enough. We don't make this call if we are in the abort
+ // transaction path. Otherwise we could get a log file full
+ // while aborting.
+ //
+
+ if (FlagOn( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH ) &&
+ (IrpContext == IrpContext->TopLevelIrpContext) &&
+ (IrpContext->TopLevelIrpContext->ExceptionStatus == STATUS_SUCCESS)) {
+
+ NtfsUpdateScbSnapshots( IrpContext );
+ LfsFlushToLsn( Vcb->LogHandle, CommitLsn );
+ }
+ }
+}
+
+
+VOID
+NtfsCheckpointCurrentTransaction (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checkpoints the current transaction by commiting it
+ to the log and deallocating the transaction Id. The current request
+ cann keep running, but changes to date are committed and will not be
+ backed out.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ NtfsUpdateScbSnapshots( IrpContext );
+
+ //
+ // Cleanup any recently deallocated record information for this transaction.
+ //
+
+ NtfsDeallocateRecordsComplete( IrpContext );
+ IrpContext->DeallocatedClusters = 0;
+ IrpContext->FreeClusterChange = 0;
+}
+
+
+VOID
+NtfsInitializeLogging (
+ )
+
+/*
+
+Routine Description:
+
+ This routine is to be called once during startup of Ntfs (not once
+ per volume), to initialize the logging support.
+
+Parameters:
+
+ None
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeLogging:\n") );
+ LfsInitializeLogFileService();
+ DebugTrace( -1, Dbg, ("NtfsInitializeLogging -> VOID\n") );
+}
+
+
+VOID
+NtfsStartLogFile (
+ IN PSCB LogFileScb,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens the log file for a volume by calling Lfs. The returned
+ LogHandle is stored in the Vcb. If the log file has not been initialized,
+ Lfs detects this and initializes it automatically.
+
+Arguments:
+
+ LogFileScb - The Scb for the log file
+
+ Vcb - Pointer to the Vcb for this volume
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ UNICODE_STRING UnicodeName;
+ LFS_INFO LfsInfo;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsStartLogFile:\n") );
+
+ RtlInitUnicodeString( &UnicodeName, L"NTFS" );
+
+ LfsInfo = LfsPackLog;
+
+ //
+ // Slam the allocation size into file size and valid data in case there
+ // is some error.
+ //
+
+ LogFileScb->Header.FileSize = LogFileScb->Header.AllocationSize;
+ LogFileScb->Header.ValidDataLength = LogFileScb->Header.AllocationSize;
+
+ Vcb->LogHeaderReservation = LfsOpenLogFile( LogFileScb->FileObject,
+ UnicodeName,
+ 1,
+ 0,
+ LogFileScb->Header.AllocationSize.QuadPart,
+ &LfsInfo,
+ &Vcb->LogHandle );
+
+ SetFlag( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE );
+ DebugTrace( -1, Dbg, ("NtfsStartLogFile -> VOID\n") );
+}
+
+
+VOID
+NtfsStopLogFile (
+ IN PVCB Vcb
+ )
+
+/*
+
+Routine Description:
+
+ This routine should be called during volume dismount to close the volume's
+ log file with the log file service.
+
+Arguments:
+
+ Vcb - Pointer to the Vcb for the volume
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ LFS_LOG_HANDLE LogHandle = Vcb->LogHandle;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsStopLogFile:\n") );
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE )) {
+
+ ASSERT( LogHandle != NULL );
+ LfsFlushToLsn( LogHandle, LfsQueryLastLsn(LogHandle) );
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_VALID_LOG_HANDLE );
+ LfsCloseLogFile( LogHandle );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsStopLogFile -> VOID\n") );
+}
+
+
+VOID
+NtfsInitializeRestartTable (
+ IN ULONG EntrySize,
+ IN ULONG NumberEntries,
+ OUT PRESTART_POINTERS TablePointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to allocate and initialize a new Restart Table,
+ and return a pointer to it.
+
+Arguments:
+
+ EntrySize - Size of the table entries, in bytes.
+
+ NumberEntries - Number of entries to allocate for the table.
+
+ TablePointer - Returns a pointer to the table.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ try {
+
+ RtlZeroMemory( TablePointer, sizeof(RESTART_POINTERS) );
+
+ //
+ // Call common routine to allocate the actual table.
+ //
+
+ InitializeNewTable( EntrySize, NumberEntries, TablePointer );
+
+ //
+ // Initialiaze the resource and spin lock.
+ //
+
+ KeInitializeSpinLock( &TablePointer->SpinLock );
+ ExInitializeResource( &TablePointer->Resource );
+ TablePointer->ResourceInitialized = TRUE;
+
+ } finally {
+
+ DebugUnwind( NtfsInitializeRestartTable );
+
+ //
+ // On error, clean up any partial work that was done.
+ //
+
+ if (AbnormalTermination()) {
+
+ NtfsFreeRestartTable( TablePointer );
+ }
+ }
+}
+
+
+VOID
+NtfsFreeRestartTable (
+ IN PRESTART_POINTERS TablePointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees a previously allocated Restart Table.
+
+Arguments:
+
+ TablePointer - Pointer to the Restart Table to delete.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ if (TablePointer->Table != NULL) {
+ NtfsFreePool( TablePointer->Table );
+ TablePointer->Table = NULL;
+ }
+
+ if (TablePointer->ResourceInitialized) {
+ ExDeleteResource( &TablePointer->Resource );
+ TablePointer->ResourceInitialized = FALSE;
+ }
+}
+
+
+VOID
+NtfsExtendRestartTable (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG NumberNewEntries,
+ IN ULONG FreeGoal
+ )
+
+/*++
+
+Routine Description:
+
+ This routine extends a previously allocated Restart Table, by
+ creating and initializing a new one, and copying over the the
+ table entries from the old one. The old table is then deallocated.
+ On return, the table pointer points to the new Restart Table.
+
+Arguments:
+
+ TablePointer - Address of the pointer to the previously created table.
+
+ NumberNewEntries - The number of addtional entries to be allocated
+ in the new table.
+
+ FreeGoal - A hint as to what point the caller would like to truncate
+ the table back to, when sufficient entries are deleted.
+ If truncation is not desired, then MAXULONG may be specified.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PRESTART_TABLE NewTable, OldTable;
+ ULONG OldSize;
+
+ OldSize = SizeOfRestartTable(TablePointer);
+
+ //
+ // Get pointer to old table.
+ //
+
+ OldTable = TablePointer->Table;
+ ASSERT_RESTART_TABLE(OldTable);
+
+ //
+ // Start by initializing a table for the new size.
+ //
+
+ InitializeNewTable( OldTable->EntrySize,
+ OldTable->NumberEntries + NumberNewEntries,
+ TablePointer );
+
+ //
+ // Copy body of old table in place to new table.
+ //
+
+ NewTable = TablePointer->Table;
+ RtlMoveMemory( (NewTable + 1),
+ (OldTable + 1),
+ OldTable->EntrySize * OldTable->NumberEntries );
+
+ //
+ // Fix up new table's header, and fix up free list.
+ //
+
+ NewTable->FreeGoal = MAXULONG;
+ if (FreeGoal != MAXULONG) {
+ NewTable->FreeGoal = sizeof(RESTART_TABLE) + FreeGoal * NewTable->EntrySize;
+ }
+
+ if (OldTable->FirstFree != 0) {
+
+ NewTable->FirstFree = OldTable->FirstFree;
+ *(PULONG)GetRestartEntryFromIndex( TablePointer, OldTable->LastFree ) =
+ OldSize;;
+ } else {
+
+ NewTable->FirstFree = OldSize;
+ }
+
+ //
+ // Copy number allocated
+ //
+
+ NewTable->NumberAllocated = OldTable->NumberAllocated;
+
+ //
+ // Free the old table and return the new one.
+ //
+
+ NtfsFreePool( OldTable );
+
+ ASSERT_RESTART_TABLE(NewTable);
+}
+
+
+ULONG
+NtfsAllocateRestartTableIndex (
+ IN PRESTART_POINTERS TablePointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates an index from within a previously initialized
+ Restart Table. If the table is empty, it is extended.
+
+ Note that the table must already be acquired either shared or exclusive,
+ and if it must be extended, then the table is released and will be
+ acquired exclusive on return.
+
+Arguments:
+
+ TablePointer - Pointer to the Restart Table in which an index is to
+ be allocated.
+
+Return Value:
+
+ The allocated index.
+
+--*/
+
+{
+ PRESTART_TABLE Table;
+ ULONG EntryIndex;
+ KIRQL OldIrql;
+ PULONG Entry;
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateRestartTableIndex:\n") );
+ DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
+
+ Table = TablePointer->Table;
+ ASSERT_RESTART_TABLE(Table);
+
+ //
+ // Acquire the spin lock to synchronize the allocation.
+ //
+
+ KeAcquireSpinLock( &TablePointer->SpinLock, &OldIrql );
+
+ //
+ // If the table is empty, then we have to extend it.
+ //
+
+ if (Table->FirstFree == 0) {
+
+ //
+ // First release the spin lock and the table resource, and get
+ // the resource exclusive.
+ //
+
+ KeReleaseSpinLock( &TablePointer->SpinLock, OldIrql );
+ NtfsReleaseRestartTable( TablePointer );
+ NtfsAcquireExclusiveRestartTable( TablePointer, TRUE );
+
+ //
+ // Now extend the table. Note that if this routine raises, we have
+ // nothing to release.
+ //
+
+ NtfsExtendRestartTable( TablePointer, 16, MAXULONG );
+
+ //
+ // And re-get our pointer to the restart table
+ //
+
+ Table = TablePointer->Table;
+
+ //
+ // Now get the spin lock again and proceed.
+ //
+
+ KeAcquireSpinLock( &TablePointer->SpinLock, &OldIrql );
+ }
+
+ //
+ // Get First Free to return it.
+ //
+
+ EntryIndex = Table->FirstFree;
+
+ ASSERT( EntryIndex != 0 );
+
+ //
+ // Dequeue this entry and zero it.
+ //
+
+ Entry = (PULONG)GetRestartEntryFromIndex( TablePointer, EntryIndex );
+
+ Table->FirstFree = *Entry;
+ ASSERT( Table->FirstFree != RESTART_ENTRY_ALLOCATED );
+
+ RtlZeroMemory( Entry, Table->EntrySize );
+
+ //
+ // Show that it's allocated.
+ //
+
+ *Entry = RESTART_ENTRY_ALLOCATED;
+
+ //
+ // If list is going empty, then we fix the LastFree as well.
+ //
+
+ if (Table->FirstFree == 0) {
+
+ Table->LastFree = 0;
+ }
+
+ Table->NumberAllocated += 1;
+
+ //
+ // Now just release the spin lock before returning.
+ //
+
+ KeReleaseSpinLock( &TablePointer->SpinLock, OldIrql );
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateRestartTableIndex -> %08lx\n", EntryIndex) );
+
+ return EntryIndex;
+}
+
+
+PVOID
+NtfsAllocateRestartTableFromIndex (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates a specific index from within a previously
+ initialized Restart Table. If the index does not exist within the
+ existing table, the table is extended.
+
+ Note that the table must already be acquired either shared or exclusive,
+ and if it must be extended, then the table is released and will be
+ acquired exclusive on return.
+
+Arguments:
+
+ TablePointer - Pointer to the Restart Table in which an index is to
+ be allocated.
+
+ Index - The index to be allocated.
+
+Return Value:
+
+ The table entry allocated.
+
+--*/
+
+{
+ PULONG Entry;
+ PULONG LastEntry;
+
+ PRESTART_TABLE Table;
+ KIRQL OldIrql;
+
+ ULONG ThisIndex;
+ ULONG LastIndex;
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateRestartTableFromIndex\n") );
+ DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
+ DebugTrace( 0, Dbg, ("Index = %08lx\n", Index) );
+
+ Table = TablePointer->Table;
+ ASSERT_RESTART_TABLE(Table);
+
+ //
+ // Acquire the spin lock to synchronize the allocation.
+ //
+
+ KeAcquireSpinLock( &TablePointer->SpinLock, &OldIrql );
+
+ //
+ // If the entry is not in the table, we will have to extend the table.
+ //
+
+ if (!IsRestartIndexWithinTable( TablePointer, Index )) {
+
+ ULONG TableSize;
+ ULONG BytesToIndex;
+ ULONG AddEntries;
+
+ //
+ // We extend the size by computing the number of entries
+ // between the existing size and the desired index and
+ // adding 1 to that.
+ //
+
+ TableSize = SizeOfRestartTable( TablePointer );;
+ BytesToIndex = Index - TableSize;
+
+ AddEntries = BytesToIndex / Table->EntrySize + 1;
+
+ //
+ // There should always be an integral number of entries being added.
+ //
+
+ ASSERT( BytesToIndex % Table->EntrySize == 0 );
+
+ //
+ // First release the spin lock and the table resource, and get
+ // the resource exclusive.
+ //
+
+ KeReleaseSpinLock( &TablePointer->SpinLock, OldIrql );
+ NtfsReleaseRestartTable( TablePointer );
+ NtfsAcquireExclusiveRestartTable( TablePointer, TRUE );
+
+ //
+ // Now extend the table. Note that if this routine raises, we have
+ // nothing to release.
+ //
+
+ NtfsExtendRestartTable( TablePointer,
+ AddEntries,
+ TableSize );
+
+ Table = TablePointer->Table;
+ ASSERT_RESTART_TABLE(Table);
+
+ //
+ // Now get the spin lock again and proceed.
+ //
+
+ KeAcquireSpinLock( &TablePointer->SpinLock, &OldIrql );
+ }
+
+ //
+ // Now see if the entry is already allocated, and just return if it is.
+ //
+
+ Entry = (PULONG)GetRestartEntryFromIndex( TablePointer, Index );
+
+ if (!IsRestartTableEntryAllocated(Entry)) {
+
+ //
+ // We now have to walk through the table, looking for the entry
+ // we're interested in and the previous entry. Start by looking at the
+ // first entry.
+ //
+
+ ThisIndex = Table->FirstFree;
+
+ //
+ // Get the Entry from the list.
+ //
+
+ Entry = (PULONG) GetRestartEntryFromIndex( TablePointer, ThisIndex );
+
+ //
+ // If this is a match, then we pull it out of the list and are done.
+ //
+
+ if (ThisIndex == Index) {
+
+ //
+ // Dequeue this entry.
+ //
+
+ Table->FirstFree = *Entry;
+ ASSERT( Table->FirstFree != RESTART_ENTRY_ALLOCATED );
+
+ //
+ // Otherwise we need to walk through the list looking for the
+ // predecessor of our entry.
+ //
+
+ } else {
+
+ while (TRUE) {
+
+ //
+ // Remember the entry just found.
+ //
+
+ LastIndex = ThisIndex;
+ LastEntry = Entry;
+
+ //
+ // We should never run out of entries.
+ //
+
+ ASSERT( *LastEntry != 0 );
+
+ //
+ // Lookup up the next entry in the list.
+ //
+
+ ThisIndex = *LastEntry;
+ Entry = (PULONG) GetRestartEntryFromIndex( TablePointer, ThisIndex );
+
+ //
+ // If this is our match we are done.
+ //
+
+ if (ThisIndex == Index) {
+
+ //
+ // Dequeue this entry.
+ //
+
+ *LastEntry = *Entry;
+
+ //
+ // If this was the last entry, we update that in the
+ // table as well.
+ //
+
+ if (Table->LastFree == ThisIndex) {
+
+ Table->LastFree = LastIndex;
+ }
+
+ break;
+ }
+ }
+ }
+
+ //
+ // If the list is now empty, we fix the LastFree as well.
+ //
+
+ if (Table->FirstFree == 0) {
+
+ Table->LastFree = 0;
+ }
+
+ //
+ // Zero this entry. Then show that this is allocated and increment the
+ // allocated count.
+ //
+
+ RtlZeroMemory( Entry, Table->EntrySize );
+ *Entry = RESTART_ENTRY_ALLOCATED;
+
+ Table->NumberAllocated += 1;
+ }
+
+
+ //
+ // Now just release the spin lock before returning.
+ //
+
+ KeReleaseSpinLock( &TablePointer->SpinLock, OldIrql );
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateRestartTableFromIndex -> %08lx\n", Entry) );
+
+ return (PVOID)Entry;
+}
+
+
+VOID
+NtfsFreeRestartTableIndex (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine frees a previously allocated index in a Restart Table.
+ If the index is before FreeGoal for the table, it is simply deallocated to
+ the front of the list for immediate reuse. If the index is beyond
+ FreeGoal, then it is deallocated to the end of the list, to facilitate
+ truncation of the list in the event that all of the entries beyond
+ FreeGoal are freed. However, this routine does not automatically
+ truncate the list, as this would cause too much overhead. The list
+ is checked during periodic checkpoint processing.
+
+Arguments:
+
+ TablePointer - Pointer to the Restart Table to which the index is to be
+ deallocated.
+
+ Index - The index being deallocated.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PRESTART_TABLE Table;
+ PULONG Entry, OldLastEntry;
+ KIRQL OldIrql;
+
+ DebugTrace( +1, Dbg, ("NtfsFreeRestartTableIndex:\n") );
+ DebugTrace( 0, Dbg, ("TablePointer = %08lx\n", TablePointer) );
+ DebugTrace( 0, Dbg, ("Index = %08lx\n", Index) );
+
+ //
+ // Get pointers to table and the entry we are freeing.
+ //
+
+ Table = TablePointer->Table;
+ ASSERT_RESTART_TABLE(Table);
+
+ ASSERT( Table->FirstFree == 0
+ || (Table->FirstFree >= 0x18)
+ && ((Table->FirstFree - 0x18) % Table->EntrySize) == 0 );
+
+ ASSERT( (Index >= 0x18)
+ && ((Index - 0x18) % Table->EntrySize) == 0 );
+
+ Entry = GetRestartEntryFromIndex( TablePointer, Index );
+
+ //
+ // Acquire the spin lock to synchronize the allocation.
+ //
+
+ KeAcquireSpinLock( &TablePointer->SpinLock, &OldIrql );
+
+ //
+ // If the index is before FreeGoal, then do a normal deallocation at
+ // the front of the list.
+ //
+
+ if (Index < Table->FreeGoal) {
+
+ *Entry = Table->FirstFree;
+ Table->FirstFree = Index;
+ if (Table->LastFree == 0) {
+ Table->LastFree = Index;
+ }
+
+ //
+ // Otherwise we will deallocate this guy to the end of the list.
+ //
+
+ } else {
+
+ if (Table->LastFree != 0) {
+ OldLastEntry = GetRestartEntryFromIndex( TablePointer,
+ Table->LastFree );
+ *OldLastEntry = Index;
+ } else {
+ Table->FirstFree = Index;
+ }
+ Table->LastFree = Index;
+ *Entry = 0;
+ }
+
+ Table->NumberAllocated -= 1;
+
+ //
+ // Now just release the spin lock before returning.
+ //
+
+ KeReleaseSpinLock( &TablePointer->SpinLock, OldIrql );
+
+ DebugTrace( -1, Dbg, ("NtfsFreeRestartTableIndex -> VOID\n") );
+}
+
+
+PVOID
+NtfsGetFirstRestartTable (
+ IN PRESTART_POINTERS TablePointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the first allocated entry from a Restart Table.
+
+Arguments:
+
+ TablePointer - Pointer to the respective Restart Table Pointers structure.
+
+Return Value:
+
+ Pointer to the first entry, or NULL if none are allocated.
+
+--*/
+
+{
+ PCHAR Entry;
+
+ PAGED_CODE();
+
+ //
+ // If we know the table is empty, we can return immediately.
+ //
+
+ if (IsRestartTableEmpty( TablePointer )) {
+
+ return NULL;
+ }
+
+ //
+ // Otherwise point to the first table entry.
+ //
+
+ Entry = (PCHAR)(TablePointer->Table + 1);
+
+ //
+ // Loop until we hit the first one allocated, or the end of the list.
+ //
+
+ while ((ULONG)(Entry - (PCHAR)TablePointer->Table) <
+ SizeOfRestartTable(TablePointer)) {
+
+ if (IsRestartTableEntryAllocated(Entry)) {
+ return (PVOID)Entry;
+ }
+
+ Entry += TablePointer->Table->EntrySize;
+ }
+
+ return NULL;
+}
+
+
+PVOID
+NtfsGetNextRestartTable (
+ IN PRESTART_POINTERS TablePointer,
+ IN PVOID Current
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the next allocated entry from a Restart Table.
+
+Arguments:
+
+ TablePointer - Pointer to the respective Restart Table Pointers structure.
+
+ Current - Current entry pointer.
+
+Return Value:
+
+ Pointer to the next entry, or NULL if none are allocated.
+
+--*/
+
+
+{
+ PCHAR Entry = (PCHAR)Current;
+
+ PAGED_CODE();
+
+ //
+ // Point to the next entry.
+ //
+
+ Entry += TablePointer->Table->EntrySize;
+
+ //
+ // Loop until we hit the first one allocated, or the end of the list.
+ //
+
+ while ((ULONG)(Entry - (PCHAR)TablePointer->Table) <
+ SizeOfRestartTable(TablePointer)) {
+
+ if (IsRestartTableEntryAllocated(Entry)) {
+ return (PVOID)Entry;
+ }
+
+ Entry += TablePointer->Table->EntrySize;
+ }
+
+ return NULL;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+DirtyPageRoutine (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PLSN OldestLsn,
+ IN PLSN NewestLsn,
+ IN PVOID Context1,
+ IN PVOID Context2
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used as the call back routine for retrieving dirty pages
+ from the Cache Manager. It adds them to the Dirty Table list whose
+ pointer is pointed to by the Context parameter.
+
+Arguments:
+
+ FileObject - Pointer to the file object which has the dirty page
+
+ FileOffset - File offset for start of dirty page
+
+ Length - Length recorded for the dirty page
+
+ OldestLsn - Oldest Lsn of an update not written through stored for that page
+
+ Context1 - IrpContext
+
+ Context2 - Pointer to the pointer to the Restart Table
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVCB Vcb;
+ VCN Vcn;
+ ULONG ClusterCount;
+ PDIRTY_PAGE_ENTRY PageEntry;
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+ ULONG PageIndex;
+ PIRP_CONTEXT IrpContext = (PIRP_CONTEXT)Context1;
+ PRESTART_POINTERS DirtyPageTable = (PRESTART_POINTERS)Context2;
+ PSCB_NONPAGED NonpagedScb;
+
+ UNREFERENCED_PARAMETER( NewestLsn );
+
+ DebugTrace( +1, Dbg, ("DirtyPageRoutine:\n") );
+ DebugTrace( 0, Dbg, ("FileObject = %08lx\n", FileObject) );
+ DebugTrace( 0, Dbg, ("FileOffset = %016I64x\n", *FileOffset) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+ DebugTrace( 0, Dbg, ("OldestLsn = %016I64x\n", *OldestLsn) );
+ DebugTrace( 0, Dbg, ("Context2 = %08lx\n", Context2) );
+
+ //
+ // Get the Vcb out of the file object.
+ //
+
+ NonpagedScb = CONTAINING_RECORD( FileObject->SectionObjectPointer,
+ SCB_NONPAGED,
+ SegmentObject );
+
+ Vcb = NonpagedScb->Vcb;
+
+ //
+ // We noop this call if the open attribute entry for this Scb is 0. We assume
+ // there was a clean volume checkpoint which cleared this field.
+ //
+
+ if (NonpagedScb->OpenAttributeTableIndex == 0 ) {
+
+ DebugTrace( -1, Dbg, ("DirtyPageRoutine -> VOID\n") );
+ return;
+ }
+
+ //
+ // First allocate an entry in the dirty page table.
+ //
+
+ PageIndex = NtfsAllocateRestartTableIndex( DirtyPageTable );
+
+ //
+ // Get a pointer to the entry we just allocated.
+ //
+
+ PageEntry = GetRestartEntryFromIndex( DirtyPageTable, PageIndex );
+
+ //
+ // Calculate the range of Vcns which are dirty.
+ //
+
+ Vcn = Int64ShraMod32(FileOffset->QuadPart, Vcb->ClusterShift);
+ ClusterCount = ClustersFromBytes( Vcb, Length );
+
+ //
+ // Now fill in the Dirty Page Entry, except for the Lcns, because
+ // we are not allowed to take page faults now.
+ //
+
+ PageEntry->TargetAttribute = NonpagedScb->OpenAttributeTableIndex;
+ PageEntry->LengthOfTransfer = Length;
+ PageEntry->LcnsToFollow = ClusterCount;
+ PageEntry->Reserved = 0;
+ PageEntry->Vcn = Vcn;
+
+ //
+ // We don't use an Lsn which is prior to our current base Lsn
+ // or the known flushed lsn for a fuzzy checkpoint.
+ //
+
+ if (OldestLsn->QuadPart < Vcb->LastBaseLsn.QuadPart) {
+
+ PageEntry->OldestLsn = Vcb->LastBaseLsn;
+
+
+ } else {
+
+ PageEntry->OldestLsn = *OldestLsn;
+ }
+
+ //
+ // Mark the Open Attribute Table Entry for this file.
+ //
+
+ AttributeEntry = (POPEN_ATTRIBUTE_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->OpenAttributeTable, PageEntry->TargetAttribute );
+
+ AttributeEntry->DirtyPagesSeen = TRUE;
+
+ DebugTrace( -1, Dbg, ("DirtyPageRoutine -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+LookupLcns (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN VCN Vcn,
+ IN ULONG ClusterCount,
+ IN BOOLEAN MustBeAllocated,
+ OUT PLCN_UNALIGNED FirstLcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine looks up the Lcns for a range of Vcns, and stores them in
+ an output array. One Lcn is stored for each Vcn in the range, even
+ if the Lcns are contiguous.
+
+Arguments:
+
+ Scb - Scb for stream on which lookup should occur.
+
+ Vcn - Start of range of Vcns to look up.
+
+ ClusterCount - Number of Vcns to look up.
+
+ MustBeAllocated - FALSE - if need not be allocated, and should check Mcb only
+ TRUE - if it must be allocated as far as caller knows (i.e.,
+ NtfsLookupAllocation also has checks)
+
+ FirstLcn - Pointer to storage for first Lcn. The caller must guarantee
+ that there is enough space to store ClusterCount Lcns.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ BOOLEAN Allocated;
+ LONGLONG Clusters;
+ LCN Lcn;
+ ULONG i;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("LookupLcns:\n") );
+ DebugTrace( 0, Dbg, ("Scb = %08l\n", Scb) );
+ DebugTrace( 0, Dbg, ("Vcn = %016I64x\n", Vcn) );
+ DebugTrace( 0, Dbg, ("ClusterCount = %08l\n", ClusterCount) );
+ DebugTrace( 0, Dbg, ("FirstLcn = %08lx\n", FirstLcn) );
+
+ //
+ // Loop until we have looked up all of the clusters
+ //
+
+ while (ClusterCount != 0) {
+
+ if (MustBeAllocated) {
+
+ //
+ // Lookup the next run.
+ //
+
+ Allocated = NtfsLookupAllocation( IrpContext,
+ Scb,
+ Vcn,
+ &Lcn,
+ &Clusters,
+ NULL,
+ NULL );
+
+ ASSERT( Allocated && (Lcn != 0) );
+
+ } else {
+
+ Allocated = NtfsLookupNtfsMcbEntry( &Scb->Mcb, Vcn, &Lcn, &Clusters, NULL, NULL, NULL, NULL );
+
+ //
+ // If we are off the end of the Mcb, then set up to just return
+ // Li0 for as many Lcns as are being looked up.
+ //
+
+ if (!Allocated ||
+ (Lcn == UNUSED_LCN)) {
+ Lcn = 0;
+ Clusters = ClusterCount;
+ Allocated = FALSE;
+ }
+ }
+
+ //
+ // If we got as many clusters as we were looking for, then just
+ // take the number we were looking for.
+ //
+
+ if (Clusters > ClusterCount) {
+
+ Clusters = ClusterCount;
+ }
+
+ //
+ // Fill in the Lcns in the header.
+ //
+
+ for (i = 0; i < (ULONG)Clusters; i++) {
+
+ *(FirstLcn++) = Lcn;
+
+ if (Allocated) {
+ Lcn = Lcn + 1;
+ }
+ }
+
+ //
+ // Adjust loop variables for the number Lcns we just received.
+ //
+
+ Vcn = Vcn + Clusters;
+ ClusterCount -= (ULONG)Clusters;
+ }
+ DebugTrace( -1, Dbg, ("LookupLcns -> VOID\n") );
+}
+
+
+VOID
+InitializeNewTable (
+ IN ULONG EntrySize,
+ IN ULONG NumberEntries,
+ OUT PRESTART_POINTERS TablePointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to allocate and initialize a new table when the
+ associated Restart Table is being allocated or extended.
+
+Arguments:
+
+ EntrySize - Size of the table entries, in bytes.
+
+ NumberEntries - Number of entries to allocate for the table.
+
+ TablePointer - Returns a pointer to the table.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PRESTART_TABLE Table;
+ PULONG Entry;
+ ULONG Size;
+ ULONG Offset;
+
+ //
+ // Calculate size of table to allocate.
+ //
+
+ Size = EntrySize * NumberEntries + sizeof(RESTART_TABLE);
+
+ //
+ // Allocate and zero out the table.
+ //
+
+ Table =
+ TablePointer->Table = NtfsAllocatePool( NonPagedPool, Size );
+
+ RtlZeroMemory( Table, Size );
+
+ //
+ // Initialize the table header.
+ //
+
+ Table->EntrySize = (USHORT)EntrySize;
+ Table->NumberEntries = (USHORT)NumberEntries;
+ Table->FreeGoal = MAXULONG;
+ Table->FirstFree = sizeof(RESTART_TABLE);
+ Table->LastFree = Table->FirstFree + (NumberEntries - 1) * EntrySize;
+
+ //
+ // Initialize the free list.
+ //
+
+ for (Entry = (PULONG)(Table + 1), Offset = sizeof(RESTART_TABLE) + EntrySize;
+ Entry < (PULONG)((PCHAR)Table + Table->LastFree);
+ Entry = (PULONG)((PCHAR)Entry + EntrySize), Offset += EntrySize) {
+
+ *Entry = Offset;
+ }
+
+ ASSERT_RESTART_TABLE(Table);
+}
+
diff --git a/private/ntos/cntfs/mcbsup.c b/private/ntos/cntfs/mcbsup.c
new file mode 100644
index 000000000..330925812
--- /dev/null
+++ b/private/ntos/cntfs/mcbsup.c
@@ -0,0 +1,2610 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ McbSup.c
+
+Abstract:
+
+ This module implements the Ntfs Mcb package.
+
+Author:
+
+ Gary Kimura [GaryKi] 10-Sep-1994
+ Tom Miller [TomM]
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define FIRST_RANGE ((PVOID)1)
+
+#ifndef NTFS_VERIFY_MCB
+#define NtfsVerifyNtfsMcb(M) NOTHING;
+#define NtfsVerifyUncompressedNtfsMcb(M,S,E) NOTHING;
+#endif
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('MFtN')
+
+//
+// Local procedure prototypes
+//
+
+ULONG
+NtfsMcbLookupArrayIndex (
+ IN PNTFS_MCB Mcb,
+ IN VCN Vcn
+ );
+
+VOID
+NtfsInsertNewRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN ULONG ArrayIndex,
+ IN BOOLEAN MakeNewRangeEmpty
+ );
+
+VOID
+NtfsCollapseRanges (
+ IN PNTFS_MCB Mcb,
+ IN ULONG StartingArrayIndex,
+ IN ULONG EndingArrayIndex
+ );
+
+VOID
+NtfsMcbCleanupLruQueue (
+ IN PVOID Parameter
+ );
+
+#ifdef NTFS_VERIFY_MCB
+VOID
+NtfsVerifyNtfsMcb (
+ IN PNTFS_MCB Mcb
+ );
+
+VOID
+NtfsVerifyUncompressedNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn
+ );
+#endif
+
+BOOLEAN
+NtfsLockNtfsMcb (
+ IN PNTFS_MCB Mcb
+ );
+
+VOID
+NtfsUnlockNtfsMcb (
+ IN PNTFS_MCB Mcb
+ );
+
+//
+// Local macros to ASSERT that caller's resource is exclusive or restart is
+// underway.
+//
+
+#define ASSERT_STREAM_EXCLUSIVE(M) { \
+ ASSERT( FlagOn( ((PSCB) (M)->FcbHeader)->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) || \
+ ExIsResourceAcquiredExclusive((M)->FcbHeader->Resource )); \
+}
+
+//
+// Local macros to enqueue and dequeue elements from the lru queue
+//
+
+#define NtfsMcbEnqueueLruEntry(M,E) { \
+ InsertTailList( &NtfsMcbLruQueue, &(E)->LruLinks ); \
+ NtfsMcbCurrentLevel += 1; \
+}
+
+#define NtfsMcbDequeueLruEntry(M,E) { \
+ if ((E)->LruLinks.Flink != NULL) { \
+ RemoveEntryList( &(E)->LruLinks ); \
+ NtfsMcbCurrentLevel -= 1; \
+ } \
+}
+
+//
+// Local macro to unload a single array entry
+//
+
+#define UnloadEntry(M,I) { \
+ PNTFS_MCB_ENTRY _Entry; \
+ _Entry = (M)->NtfsMcbArray[(I)].NtfsMcbEntry; \
+ (M)->NtfsMcbArray[(I)].NtfsMcbEntry = NULL; \
+ if (_Entry != NULL) { \
+ ExAcquireFastMutex( &NtfsMcbFastMutex ); \
+ NtfsMcbDequeueLruEntry( Mcb, _Entry ); \
+ ExReleaseFastMutex( &NtfsMcbFastMutex ); \
+ FsRtlUninitializeLargeMcb( &_Entry->LargeMcb ); \
+ if ((M)->NtfsMcbArraySize != 1) { \
+ NtfsFreePool( _Entry ); \
+ } \
+ } \
+}
+
+
+VOID
+NtfsInitializeNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN PFSRTL_ADVANCED_FCB_HEADER FcbHeader,
+ IN PNTFS_MCB_INITIAL_STRUCTS McbStructs,
+ IN POOL_TYPE PoolType
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes a new Ntfs Mcb structure.
+
+Arguments:
+
+ Mcb - Supplies the Mcb being initialized
+
+ FcbHeader - Supplies a pointer to the Fcb header containing
+ the resource to grab when accessing the Mcb
+
+ McbStructs - Initial allocation typically coresident in another
+ structure to handle initial structures for small and
+ medium files. This structure should be initially zeroed.
+
+ PoolType - Supplies the type of pool to use when
+ allocating mapping information storage
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PNTFS_MCB_ARRAY Array;
+
+ RtlZeroMemory( McbStructs, sizeof(NTFS_MCB_INITIAL_STRUCTS) );
+
+ //
+ // Initialize the fcb header field of the mcb
+ //
+
+ Mcb->FcbHeader = FcbHeader;
+
+ //
+ // Initialize the pool type
+ //
+
+ Mcb->PoolType = PoolType;
+
+ //
+ // Now initialize the initial array element
+ //
+
+ Mcb->NtfsMcbArray = Array = &McbStructs->Phase1.SingleMcbArrayEntry;
+ Mcb->NtfsMcbArraySize = 1;
+ Mcb->NtfsMcbArraySizeInUse = 1;
+ Mcb->FastMutex = FcbHeader->FastMutex;
+
+ //
+ // Initialize the first array entry.
+ //
+
+ Array[0].StartingVcn = 0;
+ Array[0].EndingVcn = -1;
+
+ //
+ // And return to our caller
+ //
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return;
+}
+
+
+VOID
+NtfsUninitializeNtfsMcb (
+ IN PNTFS_MCB Mcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine uninitializes an Ntfs Mcb structure.
+
+Arguments:
+
+ Mcb - Supplies the Mcb being decommissioned
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG i;
+ PNTFS_MCB_ENTRY Entry;
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Take out the global mutex
+ //
+
+ ExAcquireFastMutex( &NtfsMcbFastMutex );
+
+ //
+ // Deallocate the mcb array if it exists. For every entry in the array
+ // if the mcb entry is not null then remove the entry from the lru
+ // queue, uninitialize the large mcb, and free the pool.
+ //
+
+ if (Mcb->NtfsMcbArray != NULL) {
+
+ for (i = 0; i < Mcb->NtfsMcbArraySizeInUse; i += 1) {
+
+ if ((Entry = Mcb->NtfsMcbArray[i].NtfsMcbEntry) != NULL) {
+
+ //
+ // Remove the entry from the lru queue
+ //
+
+ NtfsMcbDequeueLruEntry( Mcb, Entry );
+
+ //
+ // Now release the entry
+ //
+
+ FsRtlUninitializeLargeMcb( &Entry->LargeMcb );
+
+ //
+ // We can tell from the array count whether this is
+ // the initial entry and does not need to be deallocated.
+ //
+
+ if (Mcb->NtfsMcbArraySize > 1) {
+ NtfsFreePool( Entry );
+ }
+ }
+ }
+
+ //
+ // We can tell from the array count whether this is
+ // the initial array entry(s) and do not need to be deallocated.
+ //
+
+
+ if (Mcb->NtfsMcbArraySize > 3) {
+ NtfsFreePool( Mcb->NtfsMcbArray );
+ }
+
+ Mcb->NtfsMcbArray = NULL;
+
+ //
+ // Clear the fast mutex field.
+ //
+
+ Mcb->FastMutex = NULL;
+ }
+
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+ULONG
+NtfsNumberOfRangesInNtfsMcb (
+ IN PNTFS_MCB Mcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the total number of ranges stored in
+ the mcb
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+Return Value:
+
+ ULONG - The number of ranges mapped by the input mcb
+
+--*/
+
+{
+ ASSERT_STREAM_EXCLUSIVE(Mcb);
+
+ //
+ // Our answer is the number of ranges in use in the mcb
+ //
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return Mcb->NtfsMcbArraySizeInUse;
+}
+
+
+BOOLEAN
+NtfsNumberOfRunsInRange (
+ IN PNTFS_MCB Mcb,
+ IN PVOID RangePtr,
+ OUT PULONG NumberOfRuns
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the total number of runs stored withing a range
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+ RangePtr - Supplies the range to being queried
+
+ NumberOrRuns - Returns the number of run in the specified range
+ but only if the range is loaded
+
+Return Value:
+
+ BOOLEAN - TRUE if the range is loaded and then output variable
+ is valid and FALSE if the range is not loaded.
+
+--*/
+
+{
+ VCN TempVcn;
+ LCN TempLcn;
+ PNTFS_MCB_ENTRY Entry = (PNTFS_MCB_ENTRY)RangePtr;
+
+ //
+ // Null RangePtr means first range
+ //
+
+ if (Entry == FIRST_RANGE) {
+ Entry = Mcb->NtfsMcbArray[0].NtfsMcbEntry;
+
+ //
+ // If not loaded, return FALSE
+ //
+
+ if (Entry == NULL) {
+ return FALSE;
+ }
+ }
+
+ ASSERT_STREAM_EXCLUSIVE(Mcb);
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ ASSERT( Mcb == Entry->NtfsMcb );
+
+ *NumberOfRuns = FsRtlNumberOfRunsInLargeMcb( &Entry->LargeMcb );
+
+ //
+ // Check if the current entry ends with a hole and increment the run count
+ // to reflect this. Detect the case where the range has length 0 for a
+ // file with no allocation. EndingVcn will be less than the starting Vcn
+ // in this case.
+ //
+
+ if (!FsRtlLookupLastLargeMcbEntry( &Entry->LargeMcb, &TempVcn, &TempLcn )) {
+
+ //
+ // If this is a non-zero length range then add one for the implied hole.
+ //
+
+ if (Entry->NtfsMcbArray->EndingVcn >= Entry->NtfsMcbArray->StartingVcn) {
+
+ *NumberOfRuns += 1;
+ }
+
+ //
+ // There is an entry then check if it reaches the end boundary of the range.
+ //
+
+ } else if (TempVcn != (Entry->NtfsMcbArray->EndingVcn - Entry->NtfsMcbArray->StartingVcn)) {
+
+ *NumberOfRuns += 1;
+ }
+
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsLookupLastNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ OUT PLONGLONG Vcn,
+ OUT PLONGLONG Lcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the last mapping stored in the mcb
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+ Vcn - Receives the Vcn of the last mapping
+
+ Lcn - Receives the Lcn corresponding to the Vcn
+
+Return Value:
+
+ BOOLEAN - TRUE if the mapping exist and FALSE if no mapping has been
+ defined or it is unloaded
+
+--*/
+
+{
+ PNTFS_MCB_ENTRY Entry;
+ LONGLONG StartingVcn;
+
+ ASSERT_STREAM_EXCLUSIVE(Mcb);
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Get the last entry and compute its starting vcn, and make sure
+ // the entry is valid
+ //
+
+ if ((Entry = Mcb->NtfsMcbArray[Mcb->NtfsMcbArraySizeInUse - 1].NtfsMcbEntry) == NULL) {
+
+ return FALSE;
+ }
+
+ StartingVcn = Mcb->NtfsMcbArray[Mcb->NtfsMcbArraySizeInUse - 1].StartingVcn;
+
+ //
+ // Otherwise lookup the last entry and compute the real vcn
+ //
+
+ if (FsRtlLookupLastLargeMcbEntry( &Entry->LargeMcb, Vcn, Lcn )) {
+
+ *Vcn += StartingVcn;
+
+ } else {
+
+ *Vcn = Mcb->NtfsMcbArray[Mcb->NtfsMcbArraySizeInUse - 1].EndingVcn;
+ *Lcn = UNUSED_LCN;
+ }
+
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsLookupNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ OUT PLONGLONG Lcn OPTIONAL,
+ OUT PLONGLONG CountFromLcn OPTIONAL,
+ OUT PLONGLONG StartingLcn OPTIONAL,
+ OUT PLONGLONG CountFromStartingLcn OPTIONAL,
+ OUT PVOID *RangePtr OPTIONAL,
+ OUT PULONG RunIndex OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to query mapping information
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+ Vcn - Supplies the Vcn being queried
+
+ Lcn - Optionally receives the lcn corresponding to the input vcn
+
+ CountFromLcn - Optionally receives the number of clusters following
+ the lcn in the run
+
+ StartingLcn - Optionally receives the start of the run containing the
+ input vcn
+
+ CountFromStartingLcn - Optionally receives the number of clusters in
+ the entire run
+
+ RangePtr - Optionally receives the index for the range that we're returning
+
+ RunIndex - Optionally receives the index for the run within the range that
+ we're returning
+
+Return Value:
+
+ BOOLEAN - TRUE if the mapping exists and FALSE if it doesn't exist
+ or if it is unloaded.
+
+--*/
+
+{
+ ULONG LocalRangeIndex;
+
+ PNTFS_MCB_ENTRY Entry;
+
+ NtfsAcquireNtfsMcbMutex( Mcb );
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Locate the array entry that has the hit for the input vcn, and
+ // make sure it is valid. Also set the output range index if present
+ //
+
+ LocalRangeIndex = NtfsMcbLookupArrayIndex(Mcb, Vcn);
+
+ //
+ // Now lookup the large mcb entry. The Vcn we pass in is
+ // biased by the starting vcn. If we miss then we'll just return false
+ //
+
+ if (((Entry = Mcb->NtfsMcbArray[LocalRangeIndex].NtfsMcbEntry) == NULL) ||
+ (Vcn > Entry->NtfsMcbArray->EndingVcn)) {
+
+ if (ARGUMENT_PRESENT(RangePtr)) {
+
+ *RangePtr = (PVOID)Entry;
+
+ //
+ // If this is the first range, always normalize back to the reserved pointer,
+ // since this is the only range which can move if we split out of our
+ // initial static allocation!
+ //
+
+ if (LocalRangeIndex == 0) {
+ *RangePtr = FIRST_RANGE;
+ }
+ }
+
+ NtfsReleaseNtfsMcbMutex( Mcb );
+
+ return FALSE;
+ }
+
+ if (!FsRtlLookupLargeMcbEntry( &Entry->LargeMcb,
+ Vcn - Mcb->NtfsMcbArray[LocalRangeIndex].StartingVcn,
+ Lcn,
+ CountFromLcn,
+ StartingLcn,
+ CountFromStartingLcn,
+ RunIndex )) {
+
+ //
+ // If we go off the end of the Mcb, but are in the range, then we
+ // return a hole to the end of the range.
+ //
+
+ if (ARGUMENT_PRESENT(Lcn)) {
+ *Lcn = UNUSED_LCN;
+ }
+
+ if (ARGUMENT_PRESENT(CountFromLcn)) {
+ *CountFromLcn = Mcb->NtfsMcbArray[LocalRangeIndex].EndingVcn - Vcn + 1;
+ }
+
+ if (ARGUMENT_PRESENT(StartingLcn)) {
+ *StartingLcn = UNUSED_LCN;
+ }
+
+ ASSERTMSG("Brian's bogus CountFromStartingLcn is not implemented\n",
+ !ARGUMENT_PRESENT(CountFromStartingLcn));
+
+ if (ARGUMENT_PRESENT(RunIndex)) {
+ *RunIndex = FsRtlNumberOfRunsInLargeMcb( &Entry->LargeMcb );
+ }
+ }
+
+ if (ARGUMENT_PRESENT(RangePtr)) {
+
+ *RangePtr = (PVOID)Entry;
+
+ //
+ // If this is the first range, always normalize back to the reserved pointer,
+ // since this is the only range which can move if we split out of our
+ // initial static allocation!
+ //
+
+ if (LocalRangeIndex == 0) {
+ *RangePtr = FIRST_RANGE;
+ }
+ }
+
+ //
+ // Now move this entry to the tail of the lru queue.
+ // We need to take out the global mutex to do this.
+ // Only do this if he is already in the queue - we can
+ // deadlock if we take a fault in the paging file path.
+ //
+
+ if (Entry->LruLinks.Flink != NULL) {
+
+ if (ExTryToAcquireFastMutex( &NtfsMcbFastMutex )) {
+
+ NtfsMcbDequeueLruEntry( Mcb, Entry );
+ NtfsMcbEnqueueLruEntry( Mcb, Entry );
+
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+ }
+ }
+
+ NtfsReleaseNtfsMcbMutex( Mcb );
+
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsGetNextNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN PVOID *RangePtr,
+ IN ULONG RunIndex,
+ OUT PLONGLONG Vcn,
+ OUT PLONGLONG Lcn,
+ OUT PLONGLONG Count
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the range denoted by the type index values
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+ RangePtr - Supplies the pointer to the range being queried, or NULL for the first one,
+ returns next range
+
+ RunIndex - Supplies the index within then being queried, or MAXULONG for first in next
+
+ Vcn - Receives the starting Vcn of the run being returned
+
+ Lcn - Receives the starting Lcn of the run being returned or unused
+ lbn value of -1
+
+ Count - Receives the number of clusters within this run
+
+Return Value:
+
+ BOOLEAN - TRUE if the two input indices are valid and FALSE if the
+ the index are not valid or if the range is not loaded
+
+--*/
+
+{
+ PNTFS_MCB_ENTRY Entry = (PNTFS_MCB_ENTRY)*RangePtr;
+ BOOLEAN Result = FALSE;
+
+ NtfsAcquireNtfsMcbMutex( Mcb );
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ try {
+
+ //
+ // Null RangePtr means first range
+ //
+
+ if (Entry == FIRST_RANGE) {
+ Entry = Mcb->NtfsMcbArray[0].NtfsMcbEntry;
+ }
+
+ //
+ // If there is no entry 0, get out.
+ //
+
+ if (Entry == NULL) {
+
+ try_return(Result = FALSE);
+ }
+
+ //
+ // RunIndex of MAXULONG means first of next
+ //
+
+ if (RunIndex == MAXULONG) {
+
+ //
+ // If we are already in the last range, get out.
+ //
+
+ if (Entry->NtfsMcbArray == (Mcb->NtfsMcbArray + Mcb->NtfsMcbArraySizeInUse - 1)) {
+
+ try_return(Result = FALSE);
+ }
+
+ *RangePtr = Entry = (Entry->NtfsMcbArray + 1)->NtfsMcbEntry;
+ RunIndex = 0;
+ }
+
+ //
+ // If there is no next entry, get out.
+ //
+
+ if (Entry == NULL) {
+
+ try_return(Result = FALSE);
+ }
+
+ ASSERT( Mcb == Entry->NtfsMcb );
+
+ //
+ // Lookup the large mcb entry. If we get a miss then the we're
+ // beyond the end of the ntfs mcb and should return false
+ //
+
+ if (!FsRtlGetNextLargeMcbEntry( &Entry->LargeMcb, RunIndex, Vcn, Lcn, Count )) {
+
+ //
+ // Our caller should only be off by one or two (if there is
+ // a hole) runs.
+ //
+
+ ASSERT(RunIndex <= (FsRtlNumberOfRunsInLargeMcb(&Entry->LargeMcb) + 1));
+
+ //
+ // Get the first Vcn past the last Vcn in a run. It is -1 if there
+ // are no runs.
+ //
+
+ if (!FsRtlLookupLastLargeMcbEntry( &Entry->LargeMcb, Vcn, Lcn )) {
+
+ *Vcn = -1;
+ }
+
+ *Vcn += Entry->NtfsMcbArray->StartingVcn + 1;
+
+ //
+ // If that one is beyond the ending Vcn, then get out.
+ // Otherwise there is a hole at the end of the range, and we
+ // must return that when he is reading one index beyond the
+ // last run. If we have a run index beyond that, then it is
+ // time to return FALSE as well.
+ //
+
+ if ((*Vcn > Entry->NtfsMcbArray->EndingVcn) ||
+ (RunIndex > FsRtlNumberOfRunsInLargeMcb(&Entry->LargeMcb))) {
+
+ try_return(Result = FALSE);
+ }
+
+ //
+ // If we go off the end of the Mcb, but are in the range, then we
+ // return a hole to the end of the range.
+ //
+
+ *Lcn = UNUSED_LCN;
+ *Count = Entry->NtfsMcbArray->EndingVcn - *Vcn + 1;
+
+ } else {
+
+ //
+ // Otherwise we have a hit on the large mcb and need to bias the returned
+ // vcn by the starting vcn value for this range.
+ //
+
+ *Vcn = *Vcn + Entry->NtfsMcbArray->StartingVcn;
+ }
+
+ //
+ // Make certain we aren't returning a VCN that maps over to
+ // the next range.
+ //
+
+ ASSERT(*Vcn - 1 != Entry->NtfsMcbArray->EndingVcn);
+
+ Result = TRUE;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsReleaseNtfsMcbMutex( Mcb );
+ }
+
+ return Result;
+}
+
+
+BOOLEAN
+NtfsSplitNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ IN LONGLONG Amount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine splits an mcb
+
+Arguments:
+
+ Mcb - Supplies the Mcb being maniuplated
+
+ Vcn - Supplies the Vcn to be shifted
+
+ Amount - Supplies the amount to shift by
+
+Return Value:
+
+ BOOLEAN - TRUE if worked okay and FALSE otherwise
+
+--*/
+
+{
+ ULONG RangeIndex;
+ PNTFS_MCB_ENTRY Entry;
+ ULONG i;
+
+ ASSERT_STREAM_EXCLUSIVE(Mcb);
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Locate the array entry that has the hit for the input vcn
+ //
+
+ RangeIndex = NtfsMcbLookupArrayIndex(Mcb, Vcn);
+
+ Entry = Mcb->NtfsMcbArray[RangeIndex].NtfsMcbEntry;
+
+ //
+ // Now if the entry is not null then we have to call the large
+ // mcb package to split the mcb. Bias the vcn by the starting vcn
+ //
+
+ if (Entry != NULL) {
+
+ if (!FsRtlSplitLargeMcb( &Entry->LargeMcb,
+ Vcn - Mcb->NtfsMcbArray[RangeIndex].StartingVcn,
+ Amount )) {
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return FALSE;
+ }
+ }
+
+ //
+ // Even if the entry is null we will march through the rest of our ranges
+ // updating the ending vcn and starting vcn as we go. We will update the
+ // ending vcn for the range we split and only update the starting vcn
+ // for the last entry, because its ending vcn is already max long long
+ //
+
+ for (i = RangeIndex + 1; i < Mcb->NtfsMcbArraySizeInUse; i += 1) {
+
+ Mcb->NtfsMcbArray[i - 1].EndingVcn += Amount;
+ Mcb->NtfsMcbArray[i].StartingVcn += Amount;
+ }
+
+ //
+ // And grow the last range unless it would wrap.
+ //
+
+ if ((Mcb->NtfsMcbArray[i - 1].EndingVcn + Amount) > Mcb->NtfsMcbArray[i - 1].EndingVcn) {
+ Mcb->NtfsMcbArray[i - 1].EndingVcn += Amount;
+ }
+
+ //
+ // Then return to our caller
+ //
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return TRUE;
+}
+
+
+VOID
+NtfsRemoveNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG Count
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes an range of mappings from the Mcb. After
+ the call the mapping for the range will be a hole. It is an
+ error to call this routine with the mapping range being removed
+ also being unloaded.
+
+Arguments:
+
+ Mcb - Supplies the Mcb being maniuplated
+
+ StartingVcn - Supplies the starting Vcn to remove
+
+ Count - Supplies the number of mappings to remove
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG Vcn;
+ LONGLONG RunLength;
+ LONGLONG RemainingCount;
+
+ ULONG RangeIndex;
+ PNTFS_MCB_ENTRY Entry;
+ VCN EntryStartingVcn;
+ VCN EntryEndingVcn;
+
+ ASSERT_STREAM_EXCLUSIVE(Mcb);
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Loop through the range of vcn's that we need to remove
+ //
+
+ for (Vcn = StartingVcn, RemainingCount = Count;
+ Vcn < StartingVcn + Count;
+ Vcn += RunLength, RemainingCount -= RunLength) {
+
+ //
+ // Locate the array entry that has the hit for the vcn
+ //
+
+ RangeIndex = NtfsMcbLookupArrayIndex(Mcb, Vcn);
+
+ Entry = Mcb->NtfsMcbArray[RangeIndex].NtfsMcbEntry;
+ EntryStartingVcn = Mcb->NtfsMcbArray[RangeIndex].StartingVcn;
+ EntryEndingVcn = Mcb->NtfsMcbArray[RangeIndex].EndingVcn;
+
+ //
+ // Compute how much to delete from the entry. We will delete to
+ // to end of the entry or as much as count is remaining
+ //
+
+ RunLength = EntryEndingVcn - Vcn + 1;
+
+ //
+ // If the Mcb is set up correctly, the only way we can get
+ // RunLength == 0 is if the Mcb is completely empty. Assume
+ // that this is error recovery, and that it is ok.
+ //
+
+ if ((Entry == NULL) || (RunLength == 0)) {
+ break;
+ }
+
+ //
+ // If that is too much, then just delete what we need.
+ //
+
+ if ((ULONGLONG)RunLength > (ULONGLONG)RemainingCount) { RunLength = RemainingCount; }
+
+ //
+ // Now remove the mapping from the large mcb, bias the vcn
+ // by the start of the range
+ //
+
+ FsRtlRemoveLargeMcbEntry( &Entry->LargeMcb, Vcn - EntryStartingVcn, RunLength );
+ }
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAddNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ IN LONGLONG Lcn,
+ IN LONGLONG RunCount,
+ IN BOOLEAN AlreadySynchronized
+ )
+
+/*++
+
+Routine Description:
+
+ This routine add a new entry to a Mcb
+
+Arguments:
+
+ Mcb - Supplies the Mcb being modified
+
+ Vcn - Supplies the Vcn that we are providing a mapping for
+
+ Lcn - Supplies the Lcn corresponding to the input Vcn if run count is non zero
+
+ RunCount - Supplies the size of the run following the hole
+
+ AlreadySynchronized - Indicates if the caller has already acquired the mcb mutex
+
+Return Value:
+
+ BOOLEAN - TRUE if the mapping was added successfully and FALSE otherwise
+
+--*/
+
+{
+ LONGLONG LocalVcn;
+ LONGLONG LocalLcn;
+ LONGLONG RunLength;
+ LONGLONG RemainingCount;
+
+ ULONG RangeIndex;
+ PNTFS_MCB_ENTRY Entry;
+ PNTFS_MCB_ENTRY NewEntry = NULL;
+ LONGLONG EntryStartingVcn;
+ LONGLONG EntryEndingVcn;
+
+ BOOLEAN Result = FALSE;
+
+ if (!AlreadySynchronized) { NtfsAcquireNtfsMcbMutex( Mcb ); }
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ try {
+
+ //
+ // Loop through the range of vcn's that we need to add
+ //
+
+ for (LocalVcn = Vcn, LocalLcn = Lcn, RemainingCount = RunCount;
+ LocalVcn < Vcn + RunCount;
+ LocalVcn += RunLength, LocalLcn += RunLength, RemainingCount -= RunLength) {
+
+ //
+ // Locate the array entry that has the hit for the vcn
+ //
+
+ RangeIndex = NtfsMcbLookupArrayIndex(Mcb, LocalVcn);
+
+ Entry = Mcb->NtfsMcbArray[RangeIndex].NtfsMcbEntry;
+ EntryStartingVcn = Mcb->NtfsMcbArray[RangeIndex].StartingVcn;
+
+ //
+ // Now if the entry doesn't exist then we'll need to create one
+ //
+
+ if (Entry == NULL) {
+
+ //
+ // See if we need to get the first entry in the initial structs.
+ //
+
+ if (Mcb->NtfsMcbArraySize == 1) {
+ Entry = &CONTAINING_RECORD(&Mcb->NtfsMcbArray[0],
+ NTFS_MCB_INITIAL_STRUCTS,
+ Phase1.SingleMcbArrayEntry)->Phase1.McbEntry;
+
+ //
+ // Allocate pool and initialize the fields in of the entry
+ //
+
+ } else {
+ NewEntry =
+ Entry = NtfsAllocatePoolWithTag( Mcb->PoolType, sizeof(NTFS_MCB_ENTRY), 'MftN' );
+ }
+
+ //
+ // Initialize the entry but don't put into the Mcb array until
+ // initialization is complete.
+ //
+
+ Entry->NtfsMcb = Mcb;
+ Entry->NtfsMcbArray = &Mcb->NtfsMcbArray[RangeIndex];
+ FsRtlInitializeLargeMcb( &Entry->LargeMcb, Mcb->PoolType );
+
+ //
+ // Now put the entry into the lru queue under the protection of
+ // the global mutex
+ //
+
+ ExAcquireFastMutex( &NtfsMcbFastMutex );
+
+ //
+ // Only put paged Mcb entries in the queue.
+ //
+
+ if (Mcb->PoolType == PagedPool) {
+ NtfsMcbEnqueueLruEntry( Mcb, Entry );
+ }
+
+ //
+ // Now that the initialization is complete we can store
+ // this entry in the Mcb array. This will now be cleaned
+ // up with the Scb if there is a future error.
+ //
+
+ Mcb->NtfsMcbArray[RangeIndex].NtfsMcbEntry = Entry;
+ NewEntry = NULL;
+
+ //
+ // Check if we should fire off the cleanup lru queue work item
+ //
+
+ if ((NtfsMcbCurrentLevel > NtfsMcbHighWaterMark) && !NtfsMcbCleanupInProgress) {
+
+ NtfsMcbCleanupInProgress = TRUE;
+
+ ExInitializeWorkItem( &NtfsMcbWorkItem, NtfsMcbCleanupLruQueue, NULL );
+
+ ExQueueWorkItem( &NtfsMcbWorkItem, CriticalWorkQueue );
+ }
+
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+ }
+
+ //
+ // Get out if he is trying to add a hole. At least we created the LargeMcb
+ //
+
+ if (Lcn == UNUSED_LCN) {
+ try_return( Result = TRUE );
+ }
+
+ //
+ // If this request goes beyond the end of the range,
+ // and it is the last range, and we will simply
+ // grow it.
+ //
+
+ EntryEndingVcn = LocalVcn + RemainingCount - 1;
+
+ if ((EntryEndingVcn > Mcb->NtfsMcbArray[RangeIndex].EndingVcn) &&
+ ((RangeIndex + 1) == Mcb->NtfsMcbArraySizeInUse)) {
+
+ Mcb->NtfsMcbArray[RangeIndex].EndingVcn = EntryEndingVcn;
+
+ //
+ // Otherwise, just insert enough of this run to go to the end
+ // of the range.
+ //
+
+ } else {
+ EntryEndingVcn = Mcb->NtfsMcbArray[RangeIndex].EndingVcn;
+ }
+
+ //
+ // At this point the entry exists so now compute how much to add
+ // We will add to end of the entry or as much as count allows us
+ //
+
+ RunLength = EntryEndingVcn - LocalVcn + 1;
+
+ if (((ULONGLONG)RunLength) > ((ULONGLONG)RemainingCount)) { RunLength = RemainingCount; }
+
+ //
+ // Now add the mapping from the large mcb, bias the vcn
+ // by the start of the range
+ //
+
+ ASSERT( (LocalVcn - EntryStartingVcn) >= 0 );
+
+ if (!FsRtlAddLargeMcbEntry( &Entry->LargeMcb,
+ LocalVcn - EntryStartingVcn,
+ LocalLcn,
+ RunLength )) {
+
+ try_return( Result = FALSE );
+ }
+ }
+
+ Result = TRUE;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ if (!AlreadySynchronized) { NtfsReleaseNtfsMcbMutex( Mcb ); }
+
+ if (NewEntry != NULL) { NtfsFreePool( NewEntry ); }
+ }
+
+ return Result;
+}
+
+
+VOID
+NtfsUnloadNtfsMcbRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn,
+ IN BOOLEAN TruncateOnly,
+ IN BOOLEAN AlreadySynchronized
+ )
+
+/*++
+
+Routine Description:
+
+ This routine unloads the mapping stored in the Mcb. After
+ the call everything from startingVcn and endingvcn is now unmapped and unknown.
+
+Arguments:
+
+ Mcb - Supplies the Mcb being manipulated
+
+ StartingVcn - Supplies the first Vcn which is no longer being mapped
+
+ EndingVcn - Supplies the last vcn to be unloaded
+
+ TruncateOnly - Supplies TRUE if last affected range should only be
+ truncated, or FALSE if it should be unloaded (as during
+ error recovery)
+
+ AlreadySynchronized - Supplies TRUE if our caller already owns the Mcb mutex.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG StartingRangeIndex;
+ ULONG EndingRangeIndex;
+
+ ULONG i;
+
+ if (!AlreadySynchronized) { NtfsAcquireNtfsMcbMutex( Mcb ); }
+
+ NtfsVerifyNtfsMcb(Mcb);
+ NtfsVerifyUncompressedNtfsMcb(Mcb,StartingVcn,EndingVcn);
+
+ //
+ // Get the starting and ending range indices for this call
+ //
+
+ StartingRangeIndex = NtfsMcbLookupArrayIndex( Mcb, StartingVcn );
+ EndingRangeIndex = NtfsMcbLookupArrayIndex( Mcb, EndingVcn );
+
+ //
+ // Use try finally to enforce common termination processing.
+ //
+
+ try {
+
+ //
+ // For all nonpaged Mcbs, just unload all ranges touched by the
+ // unload range, and collapse with any unloaded neighbors.
+ //
+
+ if (Mcb->PoolType == PagedPool) {
+
+ //
+ // Handle truncate case. The first test insures that we only truncate
+ // the Mcb were were initialized with (we cannot deallocate it).
+ //
+ // Also only truncate if ending is MAXLONGLONG and we are not eliminating
+ // the entire range, because that is the common truncate case, and we
+ // do not want to unload the last range every time we truncate on close.
+ //
+
+ if (((StartingRangeIndex == 0) && (Mcb->NtfsMcbArraySizeInUse == 1))
+
+ ||
+
+ (TruncateOnly && (StartingVcn != Mcb->NtfsMcbArray[StartingRangeIndex].StartingVcn))) {
+
+ //
+ // If this is not a truncate call, make sure to eliminate the
+ // entire range.
+ //
+
+ if (!TruncateOnly) {
+ StartingVcn = 0;
+ }
+
+ if (Mcb->NtfsMcbArray[StartingRangeIndex].NtfsMcbEntry != NULL) {
+
+ FsRtlTruncateLargeMcb( &Mcb->NtfsMcbArray[StartingRangeIndex].NtfsMcbEntry->LargeMcb,
+ StartingVcn - Mcb->NtfsMcbArray[StartingRangeIndex].StartingVcn );
+ }
+
+ Mcb->NtfsMcbArray[StartingRangeIndex].EndingVcn = StartingVcn - 1;
+
+ StartingRangeIndex += 1;
+ }
+
+ //
+ // Unload entries that are beyond the starting range index
+ //
+
+ for (i = StartingRangeIndex; i <= EndingRangeIndex; i += 1) {
+
+ UnloadEntry( Mcb, i );
+ }
+
+ //
+ // If there is a preceding unloaded range, we must collapse him too.
+ //
+
+ if ((StartingRangeIndex != 0) &&
+ (Mcb->NtfsMcbArray[StartingRangeIndex - 1].NtfsMcbEntry == NULL)) {
+
+ StartingRangeIndex -= 1;
+ }
+
+ //
+ // If there is a subsequent unloaded range, we must collapse him too.
+ //
+
+ if ((EndingRangeIndex != (Mcb->NtfsMcbArraySizeInUse - 1)) &&
+ (Mcb->NtfsMcbArray[EndingRangeIndex + 1].NtfsMcbEntry == NULL)) {
+
+ EndingRangeIndex += 1;
+ }
+
+ //
+ // Now collapse empty ranges.
+ //
+
+ if (StartingRangeIndex < EndingRangeIndex) {
+ NtfsCollapseRanges( Mcb, StartingRangeIndex, EndingRangeIndex );
+ }
+
+ try_return(NOTHING);
+ }
+
+ //
+ // For nonpaged Mcbs, there is only one range and we truncate it.
+ //
+
+ ASSERT((StartingRangeIndex | EndingRangeIndex) == 0);
+
+ if (Mcb->NtfsMcbArray[0].NtfsMcbEntry != NULL) {
+
+ FsRtlTruncateLargeMcb( &Mcb->NtfsMcbArray[0].NtfsMcbEntry->LargeMcb, StartingVcn );
+ }
+
+ Mcb->NtfsMcbArray[0].EndingVcn = StartingVcn - 1;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ //
+ // Truncate all unused entries from the end by dropping ArraySizeInUse
+ // to be the index of the last loaded entry + 1.
+ //
+
+ for (i = Mcb->NtfsMcbArraySizeInUse - 1;
+ (Mcb->NtfsMcbArray[i].NtfsMcbEntry == NULL);
+ i--) {
+
+ //
+ // If the first range is unloaded, set it to its initial state
+ // (empty) and break out.
+ //
+
+ if (i==0) {
+ Mcb->NtfsMcbArray[0].EndingVcn = -1;
+ break;
+ }
+ }
+ Mcb->NtfsMcbArraySizeInUse = i + 1;
+
+ //
+ // See if we broke anything.
+ //
+
+ NtfsVerifyNtfsMcb(Mcb);
+ NtfsVerifyUncompressedNtfsMcb(Mcb,StartingVcn,EndingVcn);
+
+ if (!AlreadySynchronized) { NtfsReleaseNtfsMcbMutex( Mcb ); }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsDefineNtfsMcbRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn,
+ IN BOOLEAN AlreadySynchronized
+ )
+
+/*++
+
+Routine Description:
+
+ This routine splits an existing range within the Mcb into two ranges
+
+Arguments:
+
+ Mcb - Supplies the Mcb being modified
+
+ StartingVcn - Supplies the beginning of the new range being split
+
+ EndingVcn - Supplies the ending vcn to include in this new range
+
+ AlreadySynchronized - Indicates if the caller has already acquired the mcb mutex
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG StartingRangeIndex, EndingRangeIndex;
+
+ if (!AlreadySynchronized) { NtfsAcquireNtfsMcbMutex( Mcb ); }
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Make sure we're of the right pool type
+ //
+ // If the ending vcn is less than or equal to the starting vcn then we will no op
+ // this call
+ //
+
+ if ((Mcb->PoolType != PagedPool) || (EndingVcn < StartingVcn)) {
+
+ if (!AlreadySynchronized) { NtfsReleaseNtfsMcbMutex( Mcb ); }
+
+ return;
+ }
+
+ try {
+
+ PNTFS_MCB_ARRAY Array1;
+ PNTFS_MCB_ARRAY Array2;
+ PNTFS_MCB_ENTRY Entry1;
+ PNTFS_MCB_ENTRY Entry2;
+
+ //
+ // Lookup the index for the starting Vcn and make sure it is equivalent to the ending
+ // range index
+ //
+
+ StartingRangeIndex = NtfsMcbLookupArrayIndex( Mcb, StartingVcn );
+ EndingRangeIndex = NtfsMcbLookupArrayIndex( Mcb, EndingVcn );
+ Array1 = &Mcb->NtfsMcbArray[StartingRangeIndex];
+ Array2 = &Mcb->NtfsMcbArray[EndingRangeIndex];
+ Entry1 = Array1->NtfsMcbEntry;
+ Entry2 = Array2->NtfsMcbEntry;
+
+ //
+ // We handle two cases where the specified range overlaps two
+ // existing ranges.
+ //
+
+ if (EndingRangeIndex == (StartingRangeIndex + 1)) {
+
+ //
+ // If an existing range wants to grow into the next range, and the next
+ // range is unloaded, then make it happen.
+ //
+
+ if (Array2->NtfsMcbEntry == NULL) {
+
+ //
+ // Grow the first range and shrink the second range.
+ //
+
+ Array1->EndingVcn = EndingVcn;
+ Array2->StartingVcn = EndingVcn + 1;
+
+ //
+ // If we did not empty the second range, NULL out Array2,
+ // so that we will not eliminate a range below.
+ //
+
+ if (EndingVcn < Array2->EndingVcn) {
+ Array2 = NULL;
+ }
+
+ //
+ // Otherwise, we will split the second range, and move the entries
+ // from the end of the first range into the start of the second range.
+ // (Other optimizations in Ntfs are designed to make sure we do not
+ // end up sliding too much stuff around!)
+ //
+
+ } else {
+
+ ULONG Index;
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG Count;
+ VCN StartingVcn1 = Array1->StartingVcn;
+ BOOLEAN MoreEntries;
+
+ //
+ // Both Mcbs better be there.
+ //
+
+ ASSERT((Entry1 != NULL) && (Entry2 != NULL));
+
+ //
+ // Make room in the second Mcb for the entries we will move into there.
+ //
+
+ FsRtlSplitLargeMcb( &Entry2->LargeMcb,
+ 0,
+ (ULONG)(Array2->StartingVcn - StartingVcn) );
+
+ //
+ // Now look up the first Vcn to move in the first Mcb. If this
+ // Mcb consists of one large hole then there is nothing to
+ // move.
+ //
+
+ Vcn = StartingVcn - StartingVcn1;
+ MoreEntries = FsRtlLookupLargeMcbEntry( &Entry1->LargeMcb,
+ Vcn,
+ &Lcn,
+ &Count,
+ NULL,
+ NULL,
+ &Index );
+
+ //
+ // Loop to move entries over.
+ //
+
+ while (MoreEntries) {
+
+ //
+ // If this entry is not a hole, move it.
+ //
+
+ if (Lcn != UNUSED_LCN) {
+
+ ASSERT( (Vcn - (StartingVcn - StartingVcn1)) >= 0 );
+
+ FsRtlAddLargeMcbEntry( &Entry2->LargeMcb,
+ Vcn - (StartingVcn - StartingVcn1),
+ Lcn,
+ Count );
+ }
+
+ Index += 1;
+
+ MoreEntries = FsRtlGetNextLargeMcbEntry( &Entry1->LargeMcb,
+ Index,
+ &Vcn,
+ &Lcn,
+ &Count );
+ }
+
+ //
+ // Now truncate the original Mcb.
+ //
+
+ FsRtlTruncateLargeMcb( &Entry1->LargeMcb, StartingVcn - StartingVcn1 );
+
+ //
+ // Update the range boundaries.
+ //
+
+ Array1->EndingVcn = StartingVcn - 1;
+ Array2->StartingVcn = StartingVcn;
+
+ if (EndingVcn > Array2->EndingVcn) {
+ Array2->EndingVcn = EndingVcn;
+ }
+
+ //
+ // In the unusual case that we moved the entire first range over,
+ // set to eliminate that range by setting the following variables.
+ //
+
+ if (StartingVcn == Array1->StartingVcn) {
+
+ Array2 = Array1;
+ EndingRangeIndex = StartingRangeIndex;
+
+ //
+ // Otherwise make sure we do not eliminate a range below.
+ //
+
+ } else {
+ Array2 = NULL;
+ }
+ }
+
+ //
+ // We may have emptied all of a range, and
+ // have to eliminate him if so.
+ //
+
+ if (Array2 != NULL) {
+
+ ULONG i;
+
+ //
+ // Make sure the entry is unloaded.
+ //
+
+ UnloadEntry( Mcb, EndingRangeIndex );
+
+ //
+ // We will eliminate one array entry.
+ //
+
+ Mcb->NtfsMcbArraySizeInUse -= 1;
+
+ //
+ // Check if we need to move the ending entries up the array
+ // if so then move them forward, and adjust the back pointers.
+ //
+
+ if (EndingRangeIndex < Mcb->NtfsMcbArraySizeInUse) {
+
+ RtlMoveMemory( Array2,
+ Array2 + 1,
+ sizeof(NTFS_MCB_ARRAY) * (Mcb->NtfsMcbArraySizeInUse - EndingRangeIndex));
+
+ for (i = EndingRangeIndex;
+ i < Mcb->NtfsMcbArraySizeInUse;
+ i += 1) {
+
+ if (Mcb->NtfsMcbArray[i].NtfsMcbEntry != NULL) {
+ Mcb->NtfsMcbArray[i].NtfsMcbEntry->NtfsMcbArray = &Mcb->NtfsMcbArray[i];
+ }
+ }
+ }
+ }
+
+ try_return( NOTHING );
+ }
+
+ //
+ // For all remaining cases, the indices must be the same.
+ //
+
+ ASSERT( StartingRangeIndex == EndingRangeIndex );
+
+ //
+ // First catch the case of extending the last range
+ //
+
+ if (((StartingRangeIndex + 1) == Mcb->NtfsMcbArraySizeInUse) &&
+ (StartingVcn == Array1->StartingVcn) &&
+ (EndingVcn >= Array1->EndingVcn)) {
+
+ Array1->EndingVcn = EndingVcn;
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Now handle the case of a disjoint overlap, which can only happen in the
+ // last range. (If this test fails our range is included in an existing
+ // range, and we handle that after this case.)
+ //
+
+ if (StartingVcn > Array2->EndingVcn) {
+
+ LONGLONG OldEndingVcn = Array2->EndingVcn;
+
+ //
+ // Has to be the last range.
+ //
+
+ ASSERT( (EndingRangeIndex + 1) == Mcb->NtfsMcbArraySizeInUse );
+
+ //
+ // First extend the last range to include our new range.
+ //
+
+ Array2->EndingVcn = EndingVcn;
+
+ //
+ // We will be adding a new range and inserting or growing the
+ // previous range up to the new range. If the previous range is
+ // *empty* but has an NtfsMcbEntry then we want to unload the entry.
+ // Otherwise we will grow that range to the correct value but
+ // the Mcb won't contain the clusters for the range. We want
+ // to unload that range and update the OldEndingVcn value so
+ // as not to create two empty ranges prior to this.
+ //
+
+ if ((OldEndingVcn == -1) &&
+ (Array2->NtfsMcbEntry != NULL)) {
+
+ UnloadEntry( Mcb, EndingRangeIndex );
+ }
+
+ //
+ // Now create the range the caller specified.
+ //
+
+ NtfsInsertNewRange( Mcb, StartingVcn, EndingRangeIndex, TRUE );
+
+ //
+ // Now, if this range does not abut the previous last range, *and*
+ // the previous range was not *empty*, then we have to define a
+ // range to contain the unloaded space in the middle.
+ //
+
+ if (((OldEndingVcn + 1) < StartingVcn) &&
+ ((OldEndingVcn + 1) != 0)) {
+
+ NtfsInsertNewRange( Mcb, OldEndingVcn + 1, StartingRangeIndex, TRUE );
+ }
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Check if we really need to insert a new range at the ending vcn
+ // we only need to do the work if there is not already one at that vcn
+ // and this is not the last range
+ //
+ // We do the ending range first, because its index will change if we
+ // insert in the starting range.
+ //
+
+ if (Array2->EndingVcn > EndingVcn) {
+
+ NtfsInsertNewRange( Mcb, EndingVcn + 1, EndingRangeIndex, FALSE );
+ Array1 = &Mcb->NtfsMcbArray[StartingRangeIndex];
+ }
+
+ //
+ // Check if we really need to insert a new range at the starting vcn
+ // we only need to do the work if there is not already one at that vcn
+ //
+
+ if (Array1->StartingVcn < StartingVcn) {
+
+ NtfsInsertNewRange( Mcb, StartingVcn, StartingRangeIndex, FALSE );
+
+ //
+ // In the last range, we may still need to extend.
+ //
+
+ if (EndingVcn > Mcb->NtfsMcbArray[StartingRangeIndex + 1].EndingVcn) {
+ ASSERT((StartingRangeIndex + 2) == Mcb->NtfsMcbArraySizeInUse);
+ Mcb->NtfsMcbArray[StartingRangeIndex + 1].EndingVcn = EndingVcn;
+ }
+ }
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ if (!AlreadySynchronized) { NtfsReleaseNtfsMcbMutex( Mcb ); }
+ }
+
+ return;
+}
+
+
+//
+// Local support routines
+//
+
+ULONG
+NtfsMcbLookupArrayIndex (
+ IN PNTFS_MCB Mcb,
+ IN VCN Vcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routines searches the mcb array for an entry that contains
+ the input vcn value
+
+Arguments:
+
+ Mcb - Supplies the Mcb being queried
+
+ Vcn - Supplies the Vcn to lookup
+
+Return Value:
+
+ ULONG - The index of the entry containing the input Vcn value
+
+--*/
+
+{
+ ULONG Index;
+ ULONG MinIndex;
+ ULONG MaxIndex;
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Do a quick binary search for the entry containing the vcn
+ //
+
+ MinIndex = 0;
+ MaxIndex = Mcb->NtfsMcbArraySizeInUse - 1;
+
+ while (TRUE) {
+
+ Index = (MaxIndex + MinIndex) / 2;
+
+ if (Mcb->NtfsMcbArray[Index].StartingVcn > Vcn) {
+
+ MaxIndex = Index - 1;
+
+ } else if ((Mcb->NtfsMcbArray[Index].EndingVcn < Vcn) &&
+ (Index != Mcb->NtfsMcbArraySizeInUse - 1)) {
+
+ MinIndex = Index + 1;
+
+ } else {
+
+ return Index;
+ }
+ }
+}
+
+
+//
+// Local support routines
+//
+
+VOID
+NtfsInsertNewRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN ULONG ArrayIndex,
+ IN BOOLEAN MakeNewRangeEmpty
+ )
+
+/*++
+
+ This routine is used to add a new range at the specified vcn and index location
+
+Arguments:
+
+ Mcb - Supplies the Mcb being modified
+
+ StartingVcn - Supplies the vcn for the new range
+
+ ArrayIndex - Supplies the index currently containing the starting vcn
+
+ MakeNewRangeEmpty - TRUE if the caller wants the new range unloaded regardless
+ of the state of the current range
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG i;
+ PNTFS_MCB_ENTRY Entry;
+ PNTFS_MCB_ENTRY NewEntry;
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Check if we need to grow the array
+ //
+
+ if (Mcb->NtfsMcbArraySizeInUse >= Mcb->NtfsMcbArraySize) {
+
+ PNTFS_MCB_ARRAY NewArray;
+ ULONG OldArraySize = Mcb->NtfsMcbArraySize;
+
+ //
+ // Test for initial case where we only have one array entry.
+ //
+
+ if (Mcb->NtfsMcbArraySize == 1) {
+
+ //
+ // Convince ourselves that we do not have to move the array entry.
+ //
+
+ ASSERT(FIELD_OFFSET(NTFS_MCB_INITIAL_STRUCTS, Phase1.SingleMcbArrayEntry) ==
+ FIELD_OFFSET(NTFS_MCB_INITIAL_STRUCTS, Phase2.ThreeMcbArrayEntries));
+
+ if (Mcb->NtfsMcbArray[0].NtfsMcbEntry != NULL) {
+
+ //
+ // Allocate a new Mcb Entry, copy the current one over and change the pointer.
+ //
+
+ Entry = NtfsAllocatePoolWithTag( Mcb->PoolType, sizeof(NTFS_MCB_ENTRY), 'MftN' );
+
+ //
+ // Once space is allocated, dequeue the old entry.
+ //
+
+ ExAcquireFastMutex( &NtfsMcbFastMutex );
+ NtfsMcbDequeueLruEntry( Mcb, Mcb->NtfsMcbArray[0].NtfsMcbEntry );
+
+ RtlCopyMemory( Entry, Mcb->NtfsMcbArray[0].NtfsMcbEntry, sizeof(NTFS_MCB_ENTRY) );
+
+ Mcb->NtfsMcbArray[0].NtfsMcbEntry = Entry;
+
+ NtfsMcbEnqueueLruEntry( Mcb, Entry );
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+ }
+
+ //
+ // Now change to using the three array elements
+ //
+
+ Mcb->NtfsMcbArraySize = 3;
+
+ } else {
+
+ //
+ // If we do then allocate an array that can contain 8 more entires
+ //
+
+ NewArray = NtfsAllocatePoolWithTag( Mcb->PoolType, sizeof(NTFS_MCB_ARRAY) * (Mcb->NtfsMcbArraySize + 8), 'mftN' );
+ Mcb->NtfsMcbArraySize += 8;
+
+ //
+ // Copy over the memory from the old array to the new array and then
+ // for every loaded entry we need to adjust its back pointer to the
+ // array
+ //
+
+ RtlCopyMemory( NewArray, Mcb->NtfsMcbArray, sizeof(NTFS_MCB_ARRAY) * Mcb->NtfsMcbArraySizeInUse);
+
+ for (i = 0; i < Mcb->NtfsMcbArraySizeInUse; i += 1) {
+
+ if (NewArray[i].NtfsMcbEntry != NULL) {
+
+ NewArray[i].NtfsMcbEntry->NtfsMcbArray = &NewArray[i];
+ }
+ }
+
+ //
+ // Free the old array if it was not the original array.
+ //
+
+ if (OldArraySize > 3) {
+ NtfsFreePool( Mcb->NtfsMcbArray );
+ }
+
+ Mcb->NtfsMcbArray = NewArray;
+ }
+
+ //
+ // Zero the new part of the array.
+ //
+
+ ASSERT(sizeof(NTFS_MCB_ARRAY) == ((PCHAR)&NewArray[1] - (PCHAR)&NewArray[0]));
+
+ RtlZeroMemory( &Mcb->NtfsMcbArray[OldArraySize],
+ (Mcb->NtfsMcbArraySize - OldArraySize) * sizeof(NTFS_MCB_ARRAY) );
+ }
+
+ //
+ // Now move entries that are beyond the array index over by one to make
+ // room for the new entry
+ //
+
+ if (ArrayIndex + 2 <= Mcb->NtfsMcbArraySizeInUse) {
+
+ RtlMoveMemory( &Mcb->NtfsMcbArray[ArrayIndex + 2],
+ &Mcb->NtfsMcbArray[ArrayIndex + 1],
+ sizeof(NTFS_MCB_ARRAY) * (Mcb->NtfsMcbArraySizeInUse - ArrayIndex - 1));
+
+ for (i = ArrayIndex + 2; i < Mcb->NtfsMcbArraySizeInUse + 1; i += 1) {
+
+ if (Mcb->NtfsMcbArray[i].NtfsMcbEntry != NULL) {
+
+ Mcb->NtfsMcbArray[i].NtfsMcbEntry->NtfsMcbArray = &Mcb->NtfsMcbArray[i];
+ }
+ }
+ }
+
+ //
+ // Increment our in use count by one
+ //
+
+ Mcb->NtfsMcbArraySizeInUse += 1;
+
+ //
+ // Now fix the starting and ending Vcn values for the old entry and the
+ // new entry
+ //
+
+ Mcb->NtfsMcbArray[ArrayIndex + 1].StartingVcn = StartingVcn;
+ Mcb->NtfsMcbArray[ArrayIndex + 1].EndingVcn = Mcb->NtfsMcbArray[ArrayIndex].EndingVcn;
+ Mcb->NtfsMcbArray[ArrayIndex + 1].NtfsMcbEntry = NULL;
+
+ Mcb->NtfsMcbArray[ArrayIndex].EndingVcn = StartingVcn - 1;
+
+ //
+ // Now if the entry is old entry is not null then we have a bunch of work to do
+ //
+
+ if (!MakeNewRangeEmpty && (Entry = Mcb->NtfsMcbArray[ArrayIndex].NtfsMcbEntry) != NULL) {
+
+ LONGLONG Vcn;
+ LONGLONG Lcn;
+ LONGLONG RunLength;
+ ULONG Index;
+ BOOLEAN FreeNewEntry = FALSE;
+
+ //
+ // Use a try-finally in case the Mcb initialization fails.
+ //
+
+ try {
+
+ //
+ // Allocate the new entry slot
+ //
+
+ NewEntry = NtfsAllocatePoolWithTag( Mcb->PoolType, sizeof(NTFS_MCB_ENTRY), 'MftN' );
+
+ FreeNewEntry = TRUE;
+ NewEntry->NtfsMcb = Mcb;
+ NewEntry->NtfsMcbArray = &Mcb->NtfsMcbArray[ArrayIndex + 1];
+ FsRtlInitializeLargeMcb( &NewEntry->LargeMcb, Mcb->PoolType );
+
+ ExAcquireFastMutex( &NtfsMcbFastMutex );
+ NtfsMcbEnqueueLruEntry( Mcb, NewEntry );
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+
+ //
+ // Now that the initialization is complete we can store
+ // this entry in the Mcb array. This will now be cleaned
+ // up with the Scb if there is a future error.
+ //
+
+ Mcb->NtfsMcbArray[ArrayIndex + 1].NtfsMcbEntry = NewEntry;
+ FreeNewEntry = FALSE;
+
+ //
+ // Lookup the entry containing the starting vcn in the old entry and put it
+ // in the new entry. But only if the entry exists otherwise we know that
+ // the large mcb doesn't extend into the new range
+ //
+
+ if (FsRtlLookupLargeMcbEntry( &Entry->LargeMcb,
+ StartingVcn - Mcb->NtfsMcbArray[ArrayIndex].StartingVcn,
+ &Lcn,
+ &RunLength,
+ NULL,
+ NULL,
+ &Index )) {
+
+ if (Lcn != UNUSED_LCN) {
+
+ FsRtlAddLargeMcbEntry( &NewEntry->LargeMcb,
+ 0,
+ Lcn,
+ RunLength );
+ }
+
+ //
+ // Now for every run in the old entry that is beyond the starting vcn we will
+ // copy it into the new entry. This will also copy over the dummy run at the end
+ // of the mcb if it exists
+ //
+
+ for (i = Index + 1; FsRtlGetNextLargeMcbEntry( &Entry->LargeMcb, i, &Vcn, &Lcn, &RunLength ); i += 1) {
+
+ if (Lcn != UNUSED_LCN) {
+ ASSERT( (Vcn - (StartingVcn - Mcb->NtfsMcbArray[ArrayIndex].StartingVcn)) >= 0 );
+ FsRtlAddLargeMcbEntry( &NewEntry->LargeMcb,
+ Vcn - (StartingVcn - Mcb->NtfsMcbArray[ArrayIndex].StartingVcn),
+ Lcn,
+ RunLength );
+ }
+ }
+
+ //
+ // Now modify the old mcb to be smaller and put in the dummy run
+ //
+
+ FsRtlTruncateLargeMcb( &Entry->LargeMcb,
+ StartingVcn - Mcb->NtfsMcbArray[ArrayIndex].StartingVcn );
+ }
+
+ } finally {
+
+ if (FreeNewEntry) { NtfsFreePool( NewEntry ); }
+ }
+ }
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return;
+}
+
+
+//
+// Local support routines
+//
+
+VOID
+NtfsCollapseRanges (
+ IN PNTFS_MCB Mcb,
+ IN ULONG StartingArrayIndex,
+ IN ULONG EndingArrayIndex
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will remove the indicated array entries
+
+Arguments:
+
+ Mcb - Supplies the Mcb being modified
+
+ StartingArrayIndex - Supplies the first index to remove
+
+ EndingArrayIndex - Supplies the last index to remove
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG i;
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // Make sure all the ranges are unloaded.
+ //
+
+ DebugDoit(
+
+ for (i = StartingArrayIndex; i <= EndingArrayIndex; i++) {
+ ASSERT(Mcb->NtfsMcbArray[i].NtfsMcbEntry == NULL);
+ }
+ );
+
+ //
+ // We keep the first entry by we need to copy over
+ // the ending vcn of the last entry
+ //
+
+ Mcb->NtfsMcbArray[StartingArrayIndex].EndingVcn = Mcb->NtfsMcbArray[EndingArrayIndex].EndingVcn;
+
+ //
+ // Check if we need to move the ending entries up the array
+ // if so then move them forward, and adjust the back pointers.
+ //
+
+ if (EndingArrayIndex < Mcb->NtfsMcbArraySizeInUse - 1) {
+
+ RtlMoveMemory( &Mcb->NtfsMcbArray[StartingArrayIndex + 1],
+ &Mcb->NtfsMcbArray[EndingArrayIndex + 1],
+ sizeof(NTFS_MCB_ARRAY) * (Mcb->NtfsMcbArraySizeInUse - EndingArrayIndex - 1));
+
+ for (i = StartingArrayIndex + 1;
+ i <= (StartingArrayIndex + Mcb->NtfsMcbArraySizeInUse - EndingArrayIndex - 1);
+ i += 1) {
+
+ if (Mcb->NtfsMcbArray[i].NtfsMcbEntry != NULL) {
+
+ Mcb->NtfsMcbArray[i].NtfsMcbEntry->NtfsMcbArray = &Mcb->NtfsMcbArray[i];
+ }
+ }
+ }
+
+ //
+ // Decrement the in use count and return to our caller
+ //
+
+ Mcb->NtfsMcbArraySizeInUse -= (EndingArrayIndex - StartingArrayIndex);
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsMcbCleanupLruQueue (
+ IN PVOID Parameter
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called as an ex work queue item and its job is
+ to free up the lru queue until we reach the low water mark
+
+
+Arguments:
+
+ Parameter - ignored
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLIST_ENTRY Links;
+
+ PNTFS_MCB Mcb;
+ PNTFS_MCB_ARRAY Array;
+ PNTFS_MCB_ENTRY Entry;
+
+ UNREFERENCED_PARAMETER( Parameter );
+
+ //
+ // Grab the global lock
+ //
+
+ ExAcquireFastMutex( &NtfsMcbFastMutex );
+
+ try {
+
+ //
+ // Scan through the lru queue until we either exhaust the queue
+ // or we've trimmed enough
+ //
+
+ for (Links = NtfsMcbLruQueue.Flink;
+ (Links != &NtfsMcbLruQueue) && (NtfsMcbCurrentLevel > NtfsMcbLowWaterMark);
+ Links = Links->Flink ) {
+
+ //
+ // Get the entry and the mcb it points to
+ //
+
+ Entry = CONTAINING_RECORD( Links, NTFS_MCB_ENTRY, LruLinks );
+
+ Mcb = Entry->NtfsMcb;
+
+ //
+ // Skip this entry if it is in the open attribute table.
+ //
+
+ if (((PSCB)(Mcb->FcbHeader))->NonpagedScb->OpenAttributeTableIndex != 0) {
+
+ continue;
+ }
+
+ //
+ // Try and lock the mcb
+ //
+
+ if (NtfsLockNtfsMcb( Mcb )) {
+
+ NtfsVerifyNtfsMcb(Mcb);
+
+ //
+ // The previous test was an unsafe test. Check again in case
+ // this entry has been added.
+ //
+
+ if (((PSCB)(Mcb->FcbHeader))->NonpagedScb->OpenAttributeTableIndex == 0) {
+
+ //
+ // We locked the mcb so we can remove this entry, but
+ // first backup our links pointer so we can continue with the loop
+ //
+
+ Links = Links->Blink;
+
+ //
+ // Get a point to the array entry and then remove this entry and return
+ // it to pool
+ //
+
+ Array = Entry->NtfsMcbArray;
+
+ Array->NtfsMcbEntry = NULL;
+ NtfsMcbDequeueLruEntry( Mcb, Entry );
+ FsRtlUninitializeLargeMcb( &Entry->LargeMcb );
+ if (Mcb->NtfsMcbArraySize != 1) {
+ NtfsFreePool( Entry );
+ }
+ }
+
+ NtfsUnlockNtfsMcb( Mcb );
+ }
+ }
+
+ } finally {
+
+ //
+ // Say we're done with the cleanup so that another one can be fired off when
+ // necessary
+ //
+
+ NtfsMcbCleanupInProgress = FALSE;
+
+ ExReleaseFastMutex( &NtfsMcbFastMutex );
+ }
+
+ //
+ // Return to our caller
+ //
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsLockNtfsMcb (
+ IN PNTFS_MCB Mcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to get the Fcb resource(s) exclusive so that
+ ranges may be unloaded.
+
+Arguments:
+
+ Mcb - Supplies the mcb being queried
+
+Return Value:
+
+--*/
+
+{
+ //
+ // Try to acquire paging resource exclusive.
+ //
+
+ if ((Mcb->FcbHeader->PagingIoResource == NULL) ||
+ ExAcquireResourceExclusive(Mcb->FcbHeader->PagingIoResource, FALSE)) {
+
+ //
+ // Now we can try to acquire the main resource exclusively as well.
+ //
+
+ if (ExAcquireResourceExclusive(Mcb->FcbHeader->Resource, FALSE)) {
+ return TRUE;
+ }
+
+ //
+ // We failed to acquire the paging I/O resource, so free the main one
+ // on the way out.
+ //
+
+ if (Mcb->FcbHeader->PagingIoResource != NULL) {
+ ExReleaseResource( Mcb->FcbHeader->PagingIoResource );
+ }
+ }
+
+ //
+ // Could not get this file exclusive.
+ //
+
+ return FALSE;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsUnlockNtfsMcb (
+ IN PNTFS_MCB Mcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine verifies that an mcb is properly formed
+
+Arguments:
+
+ Mcb - Supplies the mcb being queried
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // If there is a paging I/O resource, release it first.
+ //
+
+ if (Mcb->FcbHeader->PagingIoResource != NULL) {
+ ExReleaseResource(Mcb->FcbHeader->PagingIoResource);
+ }
+
+ //
+ // Now release the main resource.
+ //
+
+ ExReleaseResource(Mcb->FcbHeader->Resource);
+}
+
+#ifdef NTFS_VERIFY_MCB
+
+//
+// Local support routine
+//
+
+VOID
+NtfsVerifyNtfsMcb (
+ IN PNTFS_MCB Mcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine verifies that an mcb is properly formed
+
+Arguments:
+
+ Mcb - Supplies the mcb being queried
+
+Return Value:
+
+--*/
+
+{
+ ULONG i;
+ PNTFS_MCB_ARRAY Array;
+ PNTFS_MCB_ENTRY Entry;
+
+ LONGLONG Vbn;
+ LONGLONG Lbn;
+
+ ASSERT(Mcb->FcbHeader != NULL);
+ ASSERT(Mcb->FcbHeader->NodeTypeCode != 0);
+
+ ASSERT((Mcb->PoolType == PagedPool) || (Mcb->PoolType == NonPagedPool));
+
+ ASSERT(Mcb->NtfsMcbArraySizeInUse <= Mcb->NtfsMcbArraySize);
+
+ for (i = 0; i < Mcb->NtfsMcbArraySizeInUse; i += 1) {
+
+ Array = &Mcb->NtfsMcbArray[i];
+
+ ASSERT(((i == 0) && (Array->StartingVcn == 0)) ||
+ ((i != 0) && (Array->StartingVcn != 0)));
+
+ ASSERT(Array->StartingVcn <= (Array->EndingVcn + 1));
+
+ if ((Entry = Array->NtfsMcbEntry) != NULL) {
+
+ ASSERT(Entry->NtfsMcb == Mcb);
+ ASSERT(Entry->NtfsMcbArray == Array);
+
+ if (FsRtlLookupLastLargeMcbEntry( &Entry->LargeMcb, &Vbn, &Lbn )) {
+ ASSERT( Vbn <= (Array->EndingVcn - Array->StartingVcn) );
+ }
+ }
+ }
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsVerifyUncompressedNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn
+ )
+
+/*++
+
+Routine Description:
+
+ This routines checks if an mcb is for an uncompressed scb and then
+ checks that there are no holes in the mcb. Holes within the range being
+ removed are legal provided EndingVcn is max long long.
+
+Arguments:
+
+ Mcb - Supplies the Mcb being examined
+
+ StartingVcn - The starting Vcn being unloaded
+
+ EndingVcn - The ending Vcn being unloaded
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG i;
+ ULONG j;
+ PNTFS_MCB_ARRAY Array;
+ PNTFS_MCB_ENTRY Entry;
+
+ LONGLONG Vbn;
+ LONGLONG Lbn;
+ LONGLONG Count;
+
+ //
+ // Check if the scb is compressed
+ //
+
+ if (((PSCB)Mcb->FcbHeader)->CompressionUnit != 0) { return; }
+
+ //
+ // For each large mcb in the ntfs mcb we will make sure it doesn't
+ // have any holes.
+ //
+
+ for (i = 0; i < Mcb->NtfsMcbArraySizeInUse; i += 1) {
+
+ Array = &Mcb->NtfsMcbArray[i];
+
+ if ((Entry = Array->NtfsMcbEntry) != NULL) {
+
+ for (j = 0; FsRtlGetNextLargeMcbEntry(&Entry->LargeMcb,j,&Vbn,&Lbn,&Count); j += 1) {
+
+ ASSERT((Lbn != -1) ||
+ ((Vbn + Array->StartingVcn >= StartingVcn) && (EndingVcn == MAXLONGLONG)) ||
+ FlagOn(((PSCB)Mcb->FcbHeader)->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS));
+ }
+ }
+ }
+
+ return;
+}
+#endif
diff --git a/private/ntos/cntfs/mftsup.c b/private/ntos/cntfs/mftsup.c
new file mode 100644
index 000000000..fcbec26c5
--- /dev/null
+++ b/private/ntos/cntfs/mftsup.c
@@ -0,0 +1,2195 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ MftSup.c
+
+Abstract:
+
+ This module implements the master file table management routines for Ntfs
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_MFTSUP)
+
+//
+// Local support routines
+//
+
+BOOLEAN
+NtfsTruncateMft (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+BOOLEAN
+NtfsDefragMftPriv (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+LONG
+NtfsReadMftExceptionFilter (
+ IN PIRP_CONTEXT IrpContext,
+ IN PEXCEPTION_POINTERS ExceptionPointer,
+ IN NTSTATUS Status
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAllocateMftRecord)
+#pragma alloc_text(PAGE, NtfsCheckForDefrag)
+#pragma alloc_text(PAGE, NtfsDeallocateMftRecord)
+#pragma alloc_text(PAGE, NtfsDefragMftPriv)
+#pragma alloc_text(PAGE, NtfsFillMftHole)
+#pragma alloc_text(PAGE, NtfsInitializeMftHoleRecords)
+#pragma alloc_text(PAGE, NtfsInitializeMftRecord)
+#pragma alloc_text(PAGE, NtfsIsMftIndexInHole)
+#pragma alloc_text(PAGE, NtfsLogMftFileRecord)
+#pragma alloc_text(PAGE, NtfsPinMftRecord)
+#pragma alloc_text(PAGE, NtfsReadFileRecord)
+#pragma alloc_text(PAGE, NtfsReadMftRecord)
+#pragma alloc_text(PAGE, NtfsTruncateMft)
+#pragma alloc_text(PAGE, NtfsIterateMft)
+#endif
+
+
+VOID
+NtfsReadFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_REFERENCE FileReference,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *BaseFileRecord,
+ OUT PATTRIBUTE_RECORD_HEADER *FirstAttribute,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads the specified file record from the Mft, checking that the
+ sequence number in the caller's file reference is still the one in the file
+ record.
+
+Arguments:
+
+ Vcb - Vcb for volume on which Mft is to be read
+
+ Fcb - If specified allows us to identify the file which owns the
+ invalid file record.
+
+ FileReference - File reference, including sequence number, of the file record
+ to be read.
+
+ Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
+
+ BaseFileRecord - Returns a pointer to the requested file record.
+
+ FirstAttribute - Returns a pointer to the first attribute in the file record.
+
+ MftFileOffset - If specified, returns the file offset of the file record.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ USHORT SequenceNumber = FileReference->SequenceNumber;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReadFileRecord\n") );
+
+ NtfsReadMftRecord( IrpContext,
+ Vcb,
+ FileReference,
+ Bcb,
+ BaseFileRecord,
+ MftFileOffset );
+
+ //
+ // Invent a new status here. ***
+ //
+
+ if ((((*BaseFileRecord)->SequenceNumber != SequenceNumber) ||
+ !FlagOn((*BaseFileRecord)->Flags, FILE_RECORD_SEGMENT_IN_USE))
+
+ &&
+
+ //
+ // For now accept either sequence number 0 or 1 for the Mft.
+ //
+
+ !(NtfsSegmentNumber( FileReference ) == 0 &&
+ ((*BaseFileRecord)->SequenceNumber != SequenceNumber))) {
+
+ NtfsUnpinBcb( Bcb );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, FileReference, NULL );
+ }
+
+ *FirstAttribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)*BaseFileRecord +
+ (*BaseFileRecord)->FirstAttributeOffset);
+
+ DebugTrace( -1, Dbg, ("NtfsReadFileRecord -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsReadMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PMFT_SEGMENT_REFERENCE SegmentReference,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine reads the specified Mft record from the Mft, without checking
+ sequence numbers. This routine may be used to read records in the Mft for
+ a file other than its base file record, or it could conceivably be used for
+ extraordinary maintenance functions.
+
+Arguments:
+
+ Vcb - Vcb for volume on which Mft is to be read
+
+ SegmentReference - File reference, including sequence number, of the file
+ record to be read.
+
+ Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
+
+ FileRecord - Returns a pointer to the requested file record.
+
+ MftFileOffset - If specified, returns the file offset of the file record.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFILE_RECORD_SEGMENT_HEADER FileRecord2;
+ LONGLONG FileOffset;
+ PBCB Bcb2 = NULL;
+ NTSTATUS Status = STATUS_SUCCESS;
+ BOOLEAN ErrorPath = FALSE;
+
+ LONGLONG LlTemp1;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReadMftRecord\n") );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("SegmentReference = %08lx\n", NtfsSegmentNumber( SegmentReference )) );
+ *Bcb = NULL;
+
+ try {
+
+ //
+ // Capture the Segment Reference and make sure the Sequence Number is 0.
+ //
+
+ FileOffset = NtfsFullSegmentNumber( SegmentReference );
+
+ //
+ // Calculate the file offset in the Mft to the file record segment.
+ //
+
+ FileOffset = LlBytesFromFileRecords( Vcb, FileOffset );
+
+ //
+ // Pass back the file offset within the Mft.
+ //
+
+ if (ARGUMENT_PRESENT( MftFileOffset )) {
+
+ *MftFileOffset = FileOffset;
+ }
+
+ //
+ // Try to read it from the normal Mft.
+ //
+
+ try {
+
+ NtfsMapStream( IrpContext,
+ Vcb->MftScb,
+ FileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ Bcb,
+ (PVOID *)FileRecord );
+
+ //
+ // Raise here if we have a file record covered by the mirror,
+ // and we do not see the file signature.
+ //
+
+ if ((FileOffset < Vcb->Mft2Scb->Header.FileSize.QuadPart) &&
+ (*(PULONG)(*FileRecord)->MultiSectorHeader.Signature != *(PULONG)FileSignature)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DATA_ERROR, NULL, NULL );
+ }
+
+
+ //
+ // If we get an exception that is not expected, then we will allow
+ // the search to continue and let the crash occur in the "normal" place.
+ // Otherwise, if the read is within the part of the Mft mirrored in Mft2,
+ // then we will simply try to read the data from Mft2. If the expected
+ // status came from a read not within Mft2, then we will also continue,
+ // which cause one of our caller's try-except's to initiate an unwind.
+ //
+
+ } except (NtfsReadMftExceptionFilter( IrpContext, GetExceptionInformation(), Status = GetExceptionCode() )) {
+
+ ErrorPath = TRUE;
+ }
+
+ //
+ // If the status is expected or the Bcb is NULL or this is beyond the
+ // mirrorred portion of the Mft then raise the status
+ // and let a top level handler take care of this.
+ //
+
+ if (ErrorPath) {
+
+ if ((*Bcb == NULL) ||
+ (FileOffset >= Vcb->Mft2Scb->Header.FileSize.QuadPart )) {
+
+ NtfsRaiseStatus( IrpContext, Status, SegmentReference, NULL );
+ }
+
+ //
+ // Try to read from Mft2. If this fails with an expected status,
+ // then we are just going to have to give up and let the unwind
+ // occur from one of our caller's try-except.
+ //
+
+ NtfsMapStream( IrpContext,
+ Vcb->Mft2Scb,
+ FileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ &Bcb2,
+ (PVOID *)&FileRecord2 );
+
+ //
+ // Pin the original page because we are going to update it.
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Vcb->MftScb,
+ FileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ Bcb );
+
+ //
+ // Now copy the entire page.
+ //
+
+ RtlCopyMemory( *FileRecord,
+ FileRecord2,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Set it dirty with the largest Lsn, so that whoever is doing Restart
+ // will successfully establish the "oldest unapplied Lsn".
+ //
+
+ LlTemp1 = MAXLONGLONG;
+
+ CcSetDirtyPinnedData( *Bcb,
+ (PLARGE_INTEGER)&LlTemp1 );
+
+ NtfsUnpinBcb( &Bcb2 );
+ }
+
+ if (*(PULONG)(*FileRecord)->MultiSectorHeader.Signature != *(PULONG)FileSignature) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, SegmentReference, NULL );
+ }
+
+ } finally {
+
+ if (AbnormalTermination()) {
+
+ NtfsUnpinBcb( Bcb );
+ NtfsUnpinBcb( &Bcb2 );
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("Bcb > %08lx\n", Bcb) );
+ DebugTrace( 0, Dbg, ("FileRecord > %08lx\n", *FileRecord) );
+ DebugTrace( -1, Dbg, ("NtfsReadMftRecord -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsPinMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PMFT_SEGMENT_REFERENCE SegmentReference,
+ IN BOOLEAN PreparingToWrite,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine pins the specified Mft record from the Mft, without checking
+ sequence numbers. This routine may be used to pin records in the Mft for
+ a file other than its base file record, or it could conceivably be used for
+ extraordinary maintenance functions, such as during restart.
+
+Arguments:
+
+ Vcb - Vcb for volume on which Mft is to be read
+
+ SegmentReference - File reference, including sequence number, of the file
+ record to be read.
+
+ PreparingToWrite - TRUE if caller is preparing to write, and does not care
+ about whether the record read correctly
+
+ Bcb - Returns the Bcb for the file record. This Bcb is mapped, not pinned.
+
+ FileRecord - Returns a pointer to the requested file record.
+
+ MftFileOffset - If specified, returns the file offset of the file record.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ LONGLONG FileOffset;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsReadMftRecord\n") );
+ DebugTrace( 0, Dbg, ("Vcb = %08lx\n", Vcb) );
+ DebugTrace( 0, Dbg, ("SegmentReference = %08lx\n", NtfsSegmentNumber( SegmentReference )) );
+
+ //
+ // Capture the Segment Reference and make sure the Sequence Number is 0.
+ //
+
+ FileOffset = NtfsFullSegmentNumber( SegmentReference );
+
+ //
+ // Calculate the file offset in the Mft to the file record segment.
+ //
+
+ FileOffset = LlBytesFromFileRecords( Vcb, FileOffset );
+
+ //
+ // Pass back the file offset within the Mft.
+ //
+
+ if (ARGUMENT_PRESENT( MftFileOffset )) {
+
+ *MftFileOffset = FileOffset;
+ }
+
+ //
+ // Try to read it from the normal Mft.
+ //
+
+ try {
+
+ NtfsPinStream( IrpContext,
+ Vcb->MftScb,
+ FileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ Bcb,
+ (PVOID *)FileRecord );
+
+ //
+ // If we get an exception that is not expected, then we will allow
+ // the search to continue and let the crash occur in the "normal" place.
+ // Otherwise, if the read is within the part of the Mft mirrored in Mft2,
+ // then we will simply try to read the data from Mft2. If the expected
+ // status came from a read not within Mft2, then we will also continue,
+ // which cause one of our caller's try-except's to initiate an unwind.
+ //
+
+ } except(!FsRtlIsNtstatusExpected(GetExceptionCode()) ?
+ EXCEPTION_CONTINUE_SEARCH :
+ ( FileOffset < Vcb->Mft2Scb->Header.FileSize.QuadPart ) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH ) {
+
+ //
+ // Try to read from Mft2. If this fails with an expected status,
+ // then we are just going to have to give up and let the unwind
+ // occur from one of our caller's try-except.
+ //
+
+ NtfsPinStream( IrpContext,
+ Vcb->Mft2Scb,
+ FileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ Bcb,
+ (PVOID *)FileRecord );
+
+ }
+
+ if (!PreparingToWrite &&
+ (*(PULONG)(*FileRecord)->MultiSectorHeader.Signature != *(PULONG)FileSignature)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, SegmentReference, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Bcb > %08lx\n", Bcb) );
+ DebugTrace( 0, Dbg, ("FileRecord > %08lx\n", *FileRecord) );
+ DebugTrace( -1, Dbg, ("NtfsReadMftRecord -> VOID\n") );
+
+ return;
+}
+
+
+MFT_SEGMENT_REFERENCE
+NtfsAllocateMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN MftData
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to allocate a record in the Mft file. We need
+ to find the bitmap attribute for the Mft file and call into the bitmap
+ package to allocate us a record.
+
+Arguments:
+
+ Vcb - Vcb for volume on which Mft is to be read
+
+ MftData - TRUE if the file record is being allocated to describe the
+ $DATA attribute for the Mft.
+
+Return Value:
+
+ MFT_SEGMENT_REFERENCE - The is the segment reference for the allocated
+ record. It contains the file reference number but without
+ the previous sequence number.
+
+--*/
+
+{
+ MFT_SEGMENT_REFERENCE NewMftRecord;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ BOOLEAN FoundAttribute;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAllocateMftRecord: Entered\n") );
+
+ //
+ // Synchronize the lookup by acquiring the Mft.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
+
+ //
+ // Lookup the bitmap allocation for the Mft file. This is the
+ // bitmap attribute for the Mft file.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // Lookup the bitmap attribute for the Mft.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByCode( IrpContext,
+ Vcb->MftScb->Fcb,
+ &Vcb->MftScb->Fcb->FileReference,
+ $BITMAP,
+ &AttrContext );
+ //
+ // Error if we don't find the bitmap
+ //
+
+ if (!FoundAttribute) {
+
+ DebugTrace( 0, Dbg, ("Should find bitmap attribute\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ if (!FlagOn(Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED)) {
+
+ (VOID)NtfsReserveMftRecord( IrpContext,
+ Vcb,
+ &AttrContext );
+ }
+
+ //
+ // If we need this record for the Mft Data attribute, then we need to
+ // use the one we have already reserved, and then remember there is'nt
+ // one reserved anymore.
+ //
+
+ if (MftData) {
+
+ NtfsSetSegmentNumber( &NewMftRecord,
+ 0,
+ NtfsAllocateMftReservedRecord( IrpContext,
+ Vcb,
+ &AttrContext ) );
+
+ //
+ // Never let use get file record zero for this or we could lose a
+ // disk.
+ //
+
+ ASSERT( NtfsUnsafeSegmentNumber( &NewMftRecord ) != 0 );
+
+ if (NtfsUnsafeSegmentNumber( &NewMftRecord ) == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Allocate the record.
+ //
+
+ } else {
+
+ NtfsSetSegmentNumber( &NewMftRecord,
+ 0,
+ NtfsAllocateRecord( IrpContext,
+ &Vcb->MftBitmapAllocationContext,
+ FIRST_USER_FILE_NUMBER,
+ &AttrContext ) );
+ }
+
+
+ } finally {
+
+ DebugUnwind( NtfsAllocateMftRecord );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+
+ DebugTrace( -1, Dbg, ("NtfsAllocateMftRecord: Exit\n") );
+ }
+
+ return NewMftRecord;
+}
+
+
+VOID
+NtfsInitializeMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PMFT_SEGMENT_REFERENCE MftSegment,
+ IN OUT PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PBCB Bcb,
+ IN BOOLEAN Directory
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes a Mft record for use. We need to initialize the
+ sequence number for this usage of the the record. We also initialize the
+ update sequence array and the field which indicates the first usable
+ attribute offset in the record.
+
+Arguments:
+
+ Vcb - Vcb for volume for the Mft.
+
+ MftSegment - This is a pointer to the file reference for this
+ segment. We store the sequence number in it to make this
+ a fully valid file reference.
+
+ FileRecord - Pointer to the file record to initialize.
+
+ Bcb - Bcb to use to set this page dirty via NtfsWriteLog.
+
+ Directory - Boolean indicating if this file is a directory containing
+ an index over the filename attribute.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG FileRecordOffset;
+
+ PUSHORT UsaSequenceNumber;
+
+ PATTRIBUTE_RECORD_HEADER AttributeHeader;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeMftRecord: Entered\n") );
+
+ //
+ // Write a log record to uninitialize the structure in case we abort.
+ // We need to do this prior to setting the IN_USE bit.
+ // We don't store the Lsn for this operation in the page because there
+ // is no redo operation.
+ //
+
+ //
+ // Capture the Segment Reference and make sure the Sequence Number is 0.
+ //
+
+ FileRecordOffset = NtfsFullSegmentNumber(MftSegment);
+
+ FileRecordOffset = LlBytesFromFileRecords( Vcb, FileRecordOffset );
+
+ //
+ // We now log the new Mft record.
+ //
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ Bcb,
+ Noop,
+ NULL,
+ 0,
+ DeallocateFileRecordSegment,
+ NULL,
+ 0,
+ FileRecordOffset,
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ RtlZeroMemory( &FileRecord->ReferenceCount,
+ Vcb->BytesPerFileRecordSegment - FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER, ReferenceCount ));
+
+ //
+ // First we update the sequence count in the file record and our
+ // Mft segment. We avoid using 0 as a sequence number.
+ //
+
+ if (FileRecord->SequenceNumber == 0) {
+
+ FileRecord->SequenceNumber = 1;
+ }
+
+ //
+ // Store the new sequence number in the Mft segment given us by the
+ // caller.
+ //
+
+ MftSegment->SequenceNumber = FileRecord->SequenceNumber;
+
+ //
+ // Fill in the header for the Update sequence array.
+ //
+
+ *(PULONG)FileRecord->MultiSectorHeader.Signature = *(PULONG)FileSignature;
+
+ FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset = FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER, UpdateArrayForCreateOnly );
+ FileRecord->MultiSectorHeader.UpdateSequenceArraySize = (USHORT)UpdateSequenceArraySize( Vcb->BytesPerFileRecordSegment );
+
+ //
+ // We initialize the update sequence array sequence number to one.
+ //
+
+ UsaSequenceNumber = Add2Ptr( FileRecord, FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset );
+ *UsaSequenceNumber = 1;
+
+ //
+ // The first attribute offset begins on a quad-align boundary
+ // after the update sequence array.
+ //
+
+ FileRecord->FirstAttributeOffset = (USHORT)(FileRecord->MultiSectorHeader.UpdateSequenceArrayOffset
+ + (FileRecord->MultiSectorHeader.UpdateSequenceArraySize
+ * sizeof( UPDATE_SEQUENCE_NUMBER )));
+
+ FileRecord->FirstAttributeOffset = (USHORT)QuadAlign( FileRecord->FirstAttributeOffset );
+
+ //
+ // This is also the first free byte in this file record.
+ //
+
+ FileRecord->FirstFreeByte = FileRecord->FirstAttributeOffset;
+
+ //
+ // We set the flags to show the segment is in use and look at
+ // the directory parameter to indicate whether to show
+ // the name index present.
+ //
+
+ FileRecord->Flags = (USHORT)(FILE_RECORD_SEGMENT_IN_USE |
+ (Directory ? FILE_FILE_NAME_INDEX_PRESENT : 0));
+
+ //
+ // The size is given in the Vcb.
+ //
+
+ FileRecord->BytesAvailable = Vcb->BytesPerFileRecordSegment;
+
+ //
+ // Now we put an $END attribute in the File record.
+ //
+
+ AttributeHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord,
+ FileRecord->FirstFreeByte );
+
+ FileRecord->FirstFreeByte += QuadAlign( sizeof(ATTRIBUTE_TYPE_CODE) );
+
+ //
+ // Fill in the fields in the attribute.
+ //
+
+ AttributeHeader->TypeCode = $END;
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeMftRecord: Exit\n") );
+
+ return;
+}
+
+
+VOID
+NtfsDeallocateMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG FileNumber
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will cause an Mft record to go into the NOT_USED state.
+ We pin the record and modify the sequence count and IN USE bit.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+ FileNumber - This is the low 32 bits for the file number.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ LONGLONG FileOffset;
+ MFT_SEGMENT_REFERENCE Reference;
+ PBCB MftBcb = NULL;
+
+ BOOLEAN FoundAttribute;
+ BOOLEAN AcquiredMft = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeallocateMftRecord: Entered\n") );
+
+ NtfsSetSegmentNumber( &Reference, 0, FileNumber );
+ Reference.SequenceNumber = 0;
+
+ //
+ // Lookup the bitmap allocation for the Mft file.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Use a try finally to cleanup the attribute context.
+ //
+
+ try {
+
+ NtfsPinMftRecord( IrpContext,
+ Vcb,
+ &Reference,
+ TRUE,
+ &MftBcb,
+ &FileRecord,
+ &FileOffset );
+
+ if (FlagOn(FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE)) {
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ MftBcb,
+ DeallocateFileRecordSegment,
+ NULL,
+ 0,
+ InitializeFileRecordSegment,
+ FileRecord,
+ PtrOffset(FileRecord, &FileRecord->Flags) + 4,
+ FileOffset,
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ //
+ // We increment the sequence count in the file record and clear
+ // the In-Use flag.
+ //
+
+ ClearFlag( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE );
+
+ FileRecord->SequenceNumber += 1;
+
+ NtfsUnpinBcb( &MftBcb );
+
+ //
+ // Synchronize the lookup by acquiring the Mft.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
+ AcquiredMft = TRUE;
+
+ //
+ // Lookup the bitmap attribute for the Mft.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByCode( IrpContext,
+ Vcb->MftScb->Fcb,
+ &Vcb->MftScb->Fcb->FileReference,
+ $BITMAP,
+ &AttrContext );
+ //
+ // Error if we don't find the bitmap
+ //
+
+ if (!FoundAttribute) {
+
+ DebugTrace( 0, Dbg, ("Should find bitmap attribute\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ NtfsDeallocateRecord( IrpContext,
+ &Vcb->MftBitmapAllocationContext,
+ FileNumber,
+ &AttrContext );
+
+ //
+ // If this file number is less than our reserved index then clear
+ // the reserved index.
+ //
+
+ if (FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED )
+ && FileNumber < Vcb->MftScb->ScbType.Mft.ReservedIndex) {
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED );
+ ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
+
+ Vcb->MftScb->ScbType.Mft.ReservedIndex = 0;
+ }
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ Vcb->MftFreeRecords += 1;
+ Vcb->MftScb->ScbType.Mft.FreeRecordChange += 1;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsDeallocateMftRecord );
+
+ NtfsUnpinBcb( &MftBcb );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ if (AcquiredMft) {
+
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsDeallocateMftRecord: Exit\n") );
+ }
+}
+
+
+BOOLEAN
+NtfsIsMftIndexInHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG Index,
+ OUT PULONG HoleLength OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to check if an Mft index lies within a hole in
+ the Mft.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+ Index - This is the index to test. It is the lower 32 bits of an
+ Mft segment.
+
+ HoleLength - This is the length of the hole starting at this index.
+
+Return Value:
+
+ BOOLEAN - TRUE if the index is within the Mft and there is no allocation
+ for it.
+
+--*/
+
+{
+ BOOLEAN InHole = FALSE;
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG Clusters;
+
+ PAGED_CODE();
+
+ //
+ // If the index is past the last file record then it is not considered
+ // to be in a hole.
+ //
+
+ if (Index < (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart )) {
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ Vcn = Index << Vcb->MftToClusterShift;
+
+ } else {
+
+ Vcn = Index >> Vcb->MftToClusterShift;
+ }
+
+ //
+ // Now look this up the Mcb for the Mft. This Vcn had better be
+ // in the Mcb or there is some problem.
+ //
+
+ if (!NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
+ Vcn,
+ &Lcn,
+ &Clusters,
+ NULL,
+ NULL,
+ NULL,
+ NULL )) {
+
+ ASSERT( FALSE );
+ NtfsRaiseStatus( IrpContext,
+ STATUS_FILE_CORRUPT_ERROR,
+ NULL,
+ Vcb->MftScb->Fcb );
+ }
+
+ if (Lcn == UNUSED_LCN) {
+
+ InHole = TRUE;
+
+ //
+ // We know the number of clusters beginning from
+ // this point in the Mcb. Convert to file records
+ // and return to the user.
+ //
+
+ if (ARGUMENT_PRESENT( HoleLength )) {
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ *HoleLength = ((ULONG)Clusters) >> Vcb->MftToClusterShift;
+
+ } else {
+
+ *HoleLength = ((ULONG)Clusters) << Vcb->MftToClusterShift;
+ }
+ }
+ }
+ }
+
+ return InHole;
+}
+
+
+VOID
+NtfsFillMftHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG Index
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to fill in a hole within the Mft. We will find
+ the beginning of the hole and then allocate the clusters to fill the
+ hole. We will try to fill a hole with the HoleGranularity in the Vcb.
+ If the hole containing this index is not that large we will truncate
+ the size being added. We always guarantee to allocate the clusters on
+ file record boundaries.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+ Index - This is the index to test. It is the lower 32 bits of an
+ Mft segment.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG FileRecords;
+ ULONG BaseIndex;
+
+ VCN IndexVcn;
+ VCN HoleStartVcn;
+ VCN StartingVcn;
+
+ LCN Lcn;
+ LONGLONG ClusterCount;
+ LONGLONG RunClusterCount;
+
+ PAGED_CODE();
+
+ //
+ // Convert the Index to a Vcn in the file. Find the cluster that would
+ // be the start of this hole if the hole is fully deallocated.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ IndexVcn = Index << Vcb->MftToClusterShift;
+ HoleStartVcn = (Index & Vcb->MftHoleInverseMask) << Vcb->MftToClusterShift;
+
+ } else {
+
+ IndexVcn = Index >> Vcb->MftToClusterShift;
+ HoleStartVcn = (Index & Vcb->MftHoleInverseMask) >> Vcb->MftToClusterShift;
+ }
+
+ //
+ // Lookup the run containing this index.
+ //
+
+ NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
+ IndexVcn,
+ &Lcn,
+ &ClusterCount,
+ NULL,
+ &RunClusterCount,
+ NULL,
+ NULL );
+
+ //
+ // This had better be a hole.
+ //
+
+ if (Lcn != UNUSED_LCN) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
+ }
+
+ //
+ // Take the start of the deallocated space and round up to a hole boundary.
+ //
+
+ StartingVcn = IndexVcn - (RunClusterCount - ClusterCount);
+
+ if (StartingVcn <= HoleStartVcn) {
+
+ StartingVcn = HoleStartVcn;
+ RunClusterCount -= (HoleStartVcn - StartingVcn);
+ StartingVcn = HoleStartVcn;
+
+ //
+ // We can go to the beginning of a hole. Just use the Vcn for the file
+ // record we want to reallocate.
+ //
+
+ } else {
+
+ RunClusterCount = ClusterCount;
+ StartingVcn = IndexVcn;
+ }
+
+ //
+ // Trim the cluster count back to a hole if necessary.
+ //
+
+ if ((ULONG) RunClusterCount >= Vcb->MftClustersPerHole) {
+
+ RunClusterCount = Vcb->MftClustersPerHole;
+
+ //
+ // We don't have enough clusters for a full hole. Make sure
+ // we end on a file record boundary however. We must end up
+ // with enough clusters for the file record we are reallocating.
+ //
+
+ } else if (Vcb->FileRecordsPerCluster == 0) {
+
+ ((PLARGE_INTEGER) &ClusterCount)->LowPart &= (Vcb->ClustersPerFileRecordSegment - 1);
+
+ if (StartingVcn + ClusterCount < IndexVcn + Vcb->ClustersPerFileRecordSegment) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Vcb->MftScb->Fcb );
+ }
+ }
+
+ //
+ // Now attempt to allocate the space.
+ //
+
+ NtfsAddAllocation( IrpContext,
+ Vcb->MftScb->FileObject,
+ Vcb->MftScb,
+ StartingVcn,
+ ClusterCount,
+ FALSE );
+
+ //
+ // Compute the number of file records reallocated and then
+ // initialize and deallocate each file record.
+ //
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ FileRecords = (ULONG) ClusterCount >> Vcb->MftToClusterShift;
+ BaseIndex = (ULONG) StartingVcn >> Vcb->MftToClusterShift;
+
+ } else {
+
+ FileRecords = (ULONG) ClusterCount << Vcb->MftToClusterShift;
+ BaseIndex = (ULONG) StartingVcn << Vcb->MftToClusterShift;
+ }
+
+ NtfsInitializeMftHoleRecords( IrpContext,
+ Vcb,
+ BaseIndex,
+ FileRecords );
+
+ Vcb->MftHoleRecords -= FileRecords;
+ Vcb->MftScb->ScbType.Mft.HoleRecordChange -= FileRecords;
+
+ return;
+}
+
+
+VOID
+NtfsLogMftFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN LONGLONG MftOffset,
+ IN PBCB Bcb,
+ IN BOOLEAN Redo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to log changes to the file record for the Mft
+ file. We log the entire record instead of individual changes so
+ that we can recover the data even if there is a USA error. The entire
+ data will be sitting in the Log file.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume being logged.
+
+ FileRecord - This is the file record being logged.
+
+ MftOffset - This is the offset of this file record in the Mft stream.
+
+ Bcb - This is the Bcb for the pinned file record.
+
+ RedoOperation - Boolean indicating if we are logging
+ a redo or undo operation.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ VCN Vcn;
+
+ PVOID RedoBuffer;
+ NTFS_LOG_OPERATION RedoOperation;
+ ULONG RedoLength;
+
+ PVOID UndoBuffer;
+ NTFS_LOG_OPERATION UndoOperation;
+ ULONG UndoLength;
+
+ PAGED_CODE();
+
+ //
+ // Find the logging values based on whether this is an
+ // undo or redo.
+ //
+
+ if (Redo) {
+
+ RedoBuffer = FileRecord;
+ RedoOperation = InitializeFileRecordSegment;
+ RedoLength = FileRecord->FirstFreeByte;
+
+ UndoBuffer = NULL;
+ UndoOperation = Noop;
+ UndoLength = 0;
+
+ } else {
+
+ UndoBuffer = FileRecord;
+ UndoOperation = InitializeFileRecordSegment;
+ UndoLength = FileRecord->FirstFreeByte;
+
+ RedoBuffer = NULL;
+ RedoOperation = Noop;
+ RedoLength = 0;
+ }
+
+ //
+ // Now that we have calculated all the values, call the logging
+ // routine.
+ //
+
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ Bcb,
+ RedoOperation,
+ RedoBuffer,
+ RedoLength,
+ UndoOperation,
+ UndoBuffer,
+ UndoLength,
+ MftOffset,
+ 0,
+ 0,
+ Vcb->BytesPerFileRecordSegment );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsDefragMft (
+ IN PDEFRAG_MFT DefragMft
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called whenever we have detected that the Mft is in a state
+ where defragging is desired.
+
+Arguments:
+
+ DefragMft - This is the defrag structure.
+
+Return Value:
+
+ BOOLEAN - TRUE if we took some defrag step, FALSE otherwise.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ PVCB Vcb;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ BOOLEAN DefragStepTaken = FALSE;
+
+ DebugTrace( +1, Dbg, ("NtfsDefragMft: Entered\n") );
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ Vcb = DefragMft->Vcb;
+
+ //
+ // Use a try-except to catch errors here.
+ //
+
+ try {
+
+ //
+ // Deallocate the defrag structure we were called with.
+ //
+
+ if (DefragMft->DeallocateWorkItem) {
+
+ NtfsFreePool( DefragMft );
+ }
+
+ //
+ // Create the Irp context. We will use all of the transaction support
+ // contained in a normal IrpContext.
+ //
+
+ IrpContext = NtfsCreateIrpContext( NULL, TRUE );
+ IrpContext->Vcb = Vcb;
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED )
+ && FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ DefragStepTaken = NtfsDefragMftPriv( IrpContext,
+ Vcb );
+ } else {
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ } except( NtfsExceptionFilter( IrpContext, GetExceptionInformation())) {
+
+ NtfsProcessException( IrpContext, NULL, GetExceptionCode() );
+
+ //
+ // If the exception code was not LOG_FILE_FULL then
+ // disable defragging.
+ //
+
+ if (GetExceptionCode() != STATUS_LOG_FILE_FULL) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ DefragStepTaken = FALSE;
+ }
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ACTIVE );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+
+ FsRtlExitFileSystem();
+
+ DebugTrace( -1, Dbg, ("NtfsDefragMft: Exit\n") );
+
+ return DefragStepTaken;
+}
+
+
+VOID
+NtfsCheckForDefrag (
+ IN OUT PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to check whether there is any defrag work to do
+ involving freeing file records and creating holes in the Mft. It
+ will modify the TRIGGERED flag in the Vcb if there is still work to
+ do.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume to defrag.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG RecordsToClusters;
+ LONGLONG AdjClusters;
+
+ PAGED_CODE();
+
+ //
+ // Convert the available Mft records to clusters.
+ //
+
+ if (Vcb->FileRecordsPerCluster) {
+
+ RecordsToClusters = Int64ShllMod32(((LONGLONG)(Vcb->MftFreeRecords - Vcb->MftHoleRecords)),
+ Vcb->MftToClusterShift);
+
+ } else {
+
+ RecordsToClusters = Int64ShraMod32(((LONGLONG)(Vcb->MftFreeRecords - Vcb->MftHoleRecords)),
+ Vcb->MftToClusterShift);
+ }
+
+ //
+ // If we have already triggered the defrag then check if we are below
+ // the lower threshold.
+ //
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
+
+ AdjClusters = Vcb->FreeClusters >> MFT_DEFRAG_LOWER_THRESHOLD;
+
+ if (AdjClusters >= RecordsToClusters) {
+
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED );
+ }
+
+ //
+ // Otherwise check if we have exceeded the upper threshold.
+ //
+
+ } else {
+
+ AdjClusters = Vcb->FreeClusters >> MFT_DEFRAG_UPPER_THRESHOLD;
+
+ if (AdjClusters < RecordsToClusters) {
+
+ SetFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsInitializeMftHoleRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG FirstIndex,
+ IN ULONG RecordCount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to initialize the file records created when filling
+ a hole in the Mft.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+ FirstIndex - Index for the start of the hole to fill.
+
+ RecordCount - Count of file records in the hole.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PBCB Bcb = NULL;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Loop to initialize each file record.
+ //
+
+ while (RecordCount--) {
+
+ PUSHORT UsaSequenceNumber;
+ PMULTI_SECTOR_HEADER UsaHeader;
+
+ MFT_SEGMENT_REFERENCE ThisMftSegment;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ PATTRIBUTE_RECORD_HEADER AttributeHeader;
+
+ //
+ // Convert the index to a segment reference.
+ //
+
+ *((PLONGLONG)&ThisMftSegment) = FirstIndex;
+
+ //
+ // Pin the file record to initialize.
+ //
+
+ NtfsPinMftRecord( IrpContext,
+ Vcb,
+ &ThisMftSegment,
+ TRUE,
+ &Bcb,
+ &FileRecord,
+ NULL );
+
+ //
+ // Initialize the file record including clearing the in-use
+ // bit.
+ //
+
+ RtlZeroMemory( FileRecord, Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Fill in the header for the Update sequence array.
+ //
+
+ UsaHeader = (PMULTI_SECTOR_HEADER) FileRecord;
+
+ *(PULONG)UsaHeader->Signature = *(PULONG)FileSignature;
+
+ UsaHeader->UpdateSequenceArrayOffset = FIELD_OFFSET( FILE_RECORD_SEGMENT_HEADER,
+ UpdateArrayForCreateOnly );
+ UsaHeader->UpdateSequenceArraySize = (USHORT)UpdateSequenceArraySize( Vcb->BytesPerFileRecordSegment );
+
+ //
+ // We initialize the update sequence array sequence number to one.
+ //
+
+ UsaSequenceNumber = Add2Ptr( FileRecord, UsaHeader->UpdateSequenceArrayOffset );
+ *UsaSequenceNumber = 1;
+
+ //
+ // The first attribute offset begins on a quad-align boundary
+ // after the update sequence array.
+ //
+
+ FileRecord->FirstAttributeOffset = (USHORT)(UsaHeader->UpdateSequenceArrayOffset
+ + (UsaHeader->UpdateSequenceArraySize
+ * sizeof( UPDATE_SEQUENCE_NUMBER )));
+
+ FileRecord->FirstAttributeOffset = (USHORT)QuadAlign( FileRecord->FirstAttributeOffset );
+
+ //
+ // The size is given in the Vcb.
+ //
+
+ FileRecord->BytesAvailable = Vcb->BytesPerFileRecordSegment;
+
+ //
+ // Now we put an $END attribute in the File record.
+ //
+
+ AttributeHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord,
+ FileRecord->FirstAttributeOffset );
+
+ //
+ // The first free byte is after this location.
+ //
+
+ FileRecord->FirstFreeByte = QuadAlign( FileRecord->FirstAttributeOffset
+ + sizeof( ATTRIBUTE_TYPE_CODE ));
+
+ //
+ // Fill in the fields in the attribute.
+ //
+
+ AttributeHeader->TypeCode = $END;
+
+ //
+ // Log the entire file record.
+ //
+
+ NtfsLogMftFileRecord( IrpContext,
+ Vcb,
+ FileRecord,
+ LlBytesFromFileRecords( Vcb, FirstIndex ),
+ Bcb,
+ TRUE );
+
+ NtfsUnpinBcb( &Bcb );
+
+ //
+ // Move to the next record.
+ //
+
+ FirstIndex += 1;
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsInitializeMftHoleRecords );
+
+ NtfsUnpinBcb( &Bcb );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsTruncateMft (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to perform the work of truncating the Mft. If will
+ truncate the Mft and adjust the sizes of the Mft and Mft bitmap.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume to defrag.
+
+Return Value:
+
+ BOOLEAN - TRUE if we could deallocate any disk space, FALSE otherwise.
+
+--*/
+
+{
+ PVOID RangePtr;
+ ULONG Index;
+ VCN StartingVcn;
+ VCN NextVcn;
+ LCN NextLcn;
+ LONGLONG ClusterCount;
+ LONGLONG FileOffset;
+
+ ULONG FreeRecordChange;
+ IO_STATUS_BLOCK IoStatus;
+
+ PAGED_CODE();
+
+ //
+ // Try to find a range of file records at the end of the file which can
+ // be deallocated.
+ //
+
+ if (!NtfsFindMftFreeTail( IrpContext, Vcb, &FileOffset )) {
+
+ return FALSE;
+ }
+
+ FreeRecordChange = (ULONG) LlFileRecordsFromBytes( Vcb, Vcb->MftScb->Header.FileSize.QuadPart - FileOffset );
+
+ Vcb->MftFreeRecords -= FreeRecordChange;
+ Vcb->MftScb->ScbType.Mft.FreeRecordChange -= FreeRecordChange;
+
+ //
+ // Now we want to figure out how many holes we may be removing from the Mft.
+ // Walk through the Mcb and count the holes.
+ //
+
+ StartingVcn = LlClustersFromBytes( Vcb, FileOffset );
+
+ NtfsLookupNtfsMcbEntry( &Vcb->MftScb->Mcb,
+ StartingVcn,
+ &NextLcn,
+ &ClusterCount,
+ NULL,
+ NULL,
+ &RangePtr,
+ &Index );
+
+ do {
+
+ //
+ // If this is a hole then update the hole count in the Vcb and
+ // hole change count in the MftScb.
+ //
+
+ if (NextLcn == UNUSED_LCN) {
+
+ ULONG HoleChange;
+
+ if (Vcb->FileRecordsPerCluster == 0) {
+
+ HoleChange = ((ULONG)ClusterCount) >> Vcb->MftToClusterShift;
+
+ } else {
+
+ HoleChange = ((ULONG)ClusterCount) << Vcb->MftToClusterShift;
+ }
+
+ Vcb->MftHoleRecords -= HoleChange;
+ Vcb->MftScb->ScbType.Mft.HoleRecordChange -= HoleChange;
+ }
+
+ Index += 1;
+
+ } while (NtfsGetSequentialMcbEntry( &Vcb->MftScb->Mcb,
+ &RangePtr,
+ Index,
+ &NextVcn,
+ &NextLcn,
+ &ClusterCount ));
+
+ //
+ // We want to flush the data in the Mft out to disk in
+ // case a lazywrite comes in during a window where we have
+ // removed the allocation but before a possible abort.
+ //
+
+ CcFlushCache( &Vcb->MftScb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&FileOffset,
+ BytesFromFileRecords( Vcb, FreeRecordChange ),
+ &IoStatus );
+
+ ASSERT( IoStatus.Status == STATUS_SUCCESS );
+
+ //
+ // Now do the truncation.
+ //
+
+ NtfsDeleteAllocation( IrpContext,
+ Vcb->MftScb->FileObject,
+ Vcb->MftScb,
+ StartingVcn,
+ MAXLONGLONG,
+ TRUE,
+ FALSE );
+
+ return TRUE;
+}
+
+#ifdef _CAIRO_
+
+NTSTATUS
+NtfsIterateMft (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PFILE_REFERENCE FileReference,
+ IN FILE_RECORD_WALK FileRecordFunction,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine interates over the MFT. It calls the FileRecordFunction
+ with an Fcb for each existing file on the volume. The Fcb is owned
+ exclusive and Vcb is owned shared. The starting FileReference number
+ is passed in so that iterate can be restarted where is left off.
+
+Arguments:
+
+ Vcb - Pointer to the volume to control for the MFT
+
+ FileReference - Suplies a pointer to the starting file reference number
+ This value is updated as the interator progresses.
+
+ FileRecordFunction - Suplies a pointer to function to be called with
+ each file found in the MFT.
+
+ Context - Passed along to the FileRecordFunction.
+
+Return Value:
+
+ Returns back status of the entire operation.
+
+--*/
+
+{
+
+ ULONG LogFileFullCount = 0;
+ NTSTATUS Status = STATUS_SUCCESS;
+ PFCB CurrentFcb = NULL;
+ BOOLEAN DecrementReferenceCount = FALSE;
+
+ PAGED_CODE();
+
+ while (NT_SUCCESS(Status)) {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ Status = STATUS_VOLUME_DISMOUNTED;
+ leave;
+ }
+
+ //
+ // Set the irp context flags to indicate that we are in the
+ // fsp and that the irp context should not be deleted when
+ // complete request or process exception are called. The in
+ // fsp flag keeps us from raising in a few places. These
+ // flags must be set inside the loop since they are cleared
+ // under certain conditions.
+ //
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_IN_FSP);
+
+ DecrementReferenceCount = TRUE;
+
+ Status = NtfsTryOpenFcb( IrpContext,
+ Vcb,
+ &CurrentFcb,
+ *FileReference );
+
+ if (!NT_SUCCESS( Status )) {
+ leave;
+ }
+
+ //
+ // Call the worker function.
+ //
+
+ FileRecordFunction( IrpContext,
+ CurrentFcb,
+ Context );
+
+ //
+ // Complete the request which commits the pending
+ // transaction if there is one and releases of the
+ // acquired resources. The IrpContext will not
+ // be deleted because the no delete flag is set.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ ASSERT(CurrentFcb->ReferenceCount > 0);
+ CurrentFcb->ReferenceCount--;
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ DecrementReferenceCount = FALSE;
+ NtfsTeardownStructures( IrpContext,
+ CurrentFcb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+
+ NtfsCompleteRequest( &IrpContext, NULL, Status );
+
+ } finally {
+
+ if (CurrentFcb != NULL) {
+
+ if (DecrementReferenceCount) {
+
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ ASSERT(CurrentFcb->ReferenceCount > 0);
+ CurrentFcb->ReferenceCount--;
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ DecrementReferenceCount = FALSE;
+ }
+
+ CurrentFcb = NULL;
+ }
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ }
+
+ //
+ // If a status of not found was return then just continue to
+ // the next file record.
+ //
+
+ if (Status == STATUS_NOT_FOUND) {
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Advance to the next file record.
+ //
+
+ (*((LONGLONG UNALIGNED *) FileReference))++;
+ }
+
+ return Status;
+}
+#endif // _CAIRO_
+
+
+//
+// Local support routine.
+//
+
+BOOLEAN
+NtfsDefragMftPriv (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This is the main worker routine which performs the Mft defragging. This routine
+ will defrag according to the following priorities. First try to deallocate the
+ tail of the file. Second rewrite the mapping for the file if necessary. Finally
+ try to find a range of the Mft that we can turn into a hole. We will only do
+ the first and third if we are trying to reclaim disk space. The second we will
+ do to try and keep us from getting into trouble while modify Mft records which
+ describe the Mft itself.
+
+Arguments:
+
+ Vcb - This is the Vcb for the volume being defragged.
+
+Return Value:
+
+ BOOLEAN - TRUE if a defrag operation was successfully done, FALSE otherwise.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ BOOLEAN CleanupAttributeContext = FALSE;
+ BOOLEAN DefragStepTaken = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // We will acquire the Scb for the Mft for this operation.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->MftScb );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If we don't have a reserved record then reserve one now.
+ //
+
+ if (!FlagOn( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED )) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttributeContext = TRUE;
+
+ //
+ // Lookup the bitmap. There is an error if we can't find
+ // it.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Vcb->MftScb->Fcb,
+ &Vcb->MftScb->Fcb->FileReference,
+ $BITMAP,
+ &AttrContext )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ (VOID)NtfsReserveMftRecord( IrpContext,
+ Vcb,
+ &AttrContext );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ CleanupAttributeContext = FALSE;
+ }
+
+ //
+ // We now want to test for the three defrag operation we
+ // do. Start by checking if we are still trying to
+ // recover Mft space for the disk. This is true if
+ // have begun defragging and are above the lower threshold
+ // or have not begun defragging and are above the upper
+ // threshold.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ NtfsCheckForDefrag( Vcb );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ //
+ // If we are actively defragging and can deallocate space
+ // from the tail of the file then do that. We won't synchronize
+ // testing the flag for the defrag state below since making
+ // the calls is benign in any case.
+ //
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
+
+ if (NtfsTruncateMft( IrpContext, Vcb )) {
+
+ try_return( DefragStepTaken = TRUE );
+ }
+ }
+
+ //
+ // Else if we need to rewrite the mapping for the file do
+ // so now.
+ //
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_EXCESS_MAP )) {
+
+ if (NtfsRewriteMftMapping( IrpContext,
+ Vcb )) {
+
+ try_return( DefragStepTaken = TRUE );
+ }
+ }
+
+ //
+ // The last choice is to try to find a candidate for a hole in
+ // the file. We will walk backwards from the end of the file.
+ //
+
+ if (FlagOn( Vcb->MftDefragState, VCB_MFT_DEFRAG_TRIGGERED )) {
+
+ if (NtfsCreateMftHole( IrpContext, Vcb )) {
+
+ try_return( DefragStepTaken = TRUE );
+ }
+ }
+
+ //
+ // We couldn't do any work to defrag. This means that we can't
+ // even try to defrag unless a file record is freed at some
+ // point.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsDefragMftPriv );
+
+ if (CleanupAttributeContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ NtfsReleaseScb( IrpContext, Vcb->MftScb );
+ }
+
+ return DefragStepTaken;
+}
+
+
+//
+// Local support routine
+//
+
+LONG
+NtfsReadMftExceptionFilter (
+ IN PIRP_CONTEXT IrpContext,
+ IN PEXCEPTION_POINTERS ExceptionPointer,
+ IN NTSTATUS Status
+ )
+{
+ UNREFERENCED_PARAMETER( ExceptionPointer );
+
+ //
+ // Check if we support this error.
+ //
+
+ if (!FsRtlIsNtstatusExpected( Status )) {
+
+ return EXCEPTION_CONTINUE_SEARCH;
+ }
+
+ //
+ // Clear the status field in the IrpContext.
+ //
+
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
diff --git a/private/ntos/cntfs/mp/makefile b/private/ntos/cntfs/mp/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/ntos/cntfs/mp/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/ntos/cntfs/mp/ntfs.prf b/private/ntos/cntfs/mp/ntfs.prf
new file mode 100644
index 000000000..80099c9b7
--- /dev/null
+++ b/private/ntos/cntfs/mp/ntfs.prf
@@ -0,0 +1,407 @@
+NtfsFsdCreate@8
+NtfsFreeSnapshotsForFcb@8
+NtfsCompleteRequest@12
+NtfsDeleteIrpContext@4
+NtfsSetTopLevelIrp@12
+NtfsCreateIrpContext@8
+NtfsCommonCleanup@8
+NtfsSnapshotScb@8
+NtfsReleaseFcb@8
+NtfsIncrementCloseCounts@12
+_abnormal_termination
+NtfsAcquireSharedVcb@12
+NtfsCreateScb@24
+NtfsFsdCleanup@8
+NtfsAcquireExclusiveFcb@20
+NtfsSetFileObject@16
+NtfsCommonCreate@12
+NtfsPingVolume@8
+NtfsMapStream@28
+NtfsReadMftRecord@24
+NtfsMapUserBuffer@4
+NtfsCollateNames@24
+NtfsReadFileRecord@28
+NtfsFindInFileRecord@32
+NtfsCleanupAttributeContext@4
+NtfsLookupInFileRecord@40
+NtfsDecrementCleanupCounts@12
+NtfsFsdClose@8
+NtfsAcquireExclusiveScb@8
+NtfsDeleteCcb@12
+NtfsAccessCheck@24
+NtfsCreateCcb@32
+NtfsDecrementCloseCounts@24
+NtfsIncrementCleanupCounts@12
+NtfsCommonClose@40
+NtfsReleaseVcbCheckDelete@16
+NtfsOpenAttribute@60
+NtfsFindPrefix@36
+NtfsUpcaseName@12
+NtfsAcquireFcbWithPaging@12
+ReadIndexBuffer@24
+NtfsFileUpcaseValue@12
+NtfsInitializeIndexContext@4
+NtfsIsFileNameValid@8
+NtfsCleanupIndexContext@8
+NtfsFileCompareValues@24
+NtfsFileIsEqual@16
+FindFirstIndexEntry@16
+BinarySearchIndex@16
+FindNextIndexEntry@32
+NtfsFindNameLink@8
+NtfsTeardownStructures@24
+NtfsRemoveScb@16
+NtfsPrepareFcbForRemoval@16
+NtfsCheckValidAttributeAccess@44
+NtfsOpenExistingAttr@60
+NtfsUpdateScbFromFileObject@16
+NtfsCheckExistingFile@20
+NtfsOpenExistingPrefixFcb@60
+NtfsOpenAttributeInExistingFile@56
+NtfsFinishIoAtEof@4
+NtfsFileContainsWildcards@4
+NtfsAcquireSharedFcb@16
+NtfsReinitializeIndexContext@8
+NtfsFastQueryNetworkOpenInfo@20
+NtfsQueryDirectory@20
+NtfsReleaseScbWithPaging@8
+NtfsFullCompareNames@8
+NtfsCommonDirectoryControl@8
+NtfsCleanupAfterEnumeration@8
+NtfsMdlReadA@28
+NtfsFsdDirectoryControl@8
+NtfsRestartIndexEnumeration@32
+NtfsRemoveClose@8
+NtfsQueueClose@8
+NtfsFspClose@4
+NtfsFindIndexEntry@28
+NtfsLookupEntry@36
+NtfsCreateNewFile@84
+FindMoveableIndexRoot@12
+NtfsFileIsInExpression@16
+NtfsNetworkOpenCreate@12
+NtfsContinueIndexEnumeration@20
+NtfsCopyReadA@32
+NtfsWaitSync@4
+NtfsMcbLookupArrayIndex@12
+NtfsLookupNtfsMcbEntry@36
+_allshr
+NtfsSingleAsync@24
+NtfsSingleSyncCompletionRoutine@12
+NtfsLookupAllocation@32
+NtfsNonCachedIo@28
+NtfsPrepareBuffers@32
+NtfsDeallocateCompressionBuffer@8
+NtfsLockUserBuffer@16
+NtfsDeleteMdlAndBuffer@8
+NtfsCommitCurrentTransaction@4
+NtfsFreeRestartTable@4
+LfsResetUndoTotal@12
+LfsQueryLastLsn@4
+InitializeNewTable@12
+NtfsInitializeRestartTable@12
+NtfsGetFirstRestartTable@4
+LfsAllocateLbcb@8
+LfsWriteLfsRestart@12
+LfsPinOrMapData@40
+NtfsCheckpointVolume@28
+LfsWriteLogRecordIntoLogPage@52
+NtfsFreeRestartTableIndex@8
+LfsPrepareLfcbForLogRecord@8
+LfsWrite@44
+LfsCurrentAvailSpace@12
+NtfsWriteLog@56
+LfsTransferLogBytes@28
+NtfsAllocateRestartTableIndex@4
+LfsVerifyLogSpaceAvail@20
+NtfsVolumeCheckpointDpc@16
+NtfsCheckpointAllVolumes@4
+LfsWriteRestartArea@16
+NtfsFreeReservedBuffer@4
+NtfsFsdWrite@8
+NtfsCreateMdlAndBuffer@24
+NtfsReleaseFileForCcFlush@8
+NtfsCommonWrite@8
+LfsFlushLfcb@8
+LfsFlushLbcb@8
+LfsSetBaseLsnPriv@16
+NtfsGetNextRestartTable@8
+NtfsTransformUsaBlock@16
+LfsFindFirstIo@40
+NtfsAcquireFileForCcFlush@8
+LfsDeallocateLbcb@8
+NtfsReleaseScbFromLazyWrite@4
+NtfsAcquireScbForLazyWrite@8
+LookupLcns@28
+DirtyPageRoutine@28
+LfsNextLogPageOffset@20
+LfsGetLbcb@4
+NtfsFsdRead@8
+NtfsCommonRead@12
+NtfsFsdFileSystemControl@8
+NtfsCommonFileSystemControl@8
+NtfsUserFsRequest@8
+NtfsDeallocateRecordsComplete@4
+NtfsPinMappedData@24
+NtfsCheckpointCurrentTransaction@4
+NtfsUpdateScbSnapshots@4
+NtfsRestartChangeValue@28
+NtfsUpdateLcbDuplicateInfo@8
+NtfsRestartUpdateFileName@8
+NtfsOplockRequest@8
+NtfsUpdateFileNameInIndex@20
+NtfsBreakBatchOplock@32
+NtfsChangeAttributeValue@40
+NtfsUpdateStandardInformation@8
+NtfsOpenAttributeCheck@20
+NtfsAcquireSharedScbForTransaction@8
+NtfsReleaseSharedResources@4
+NtfsPrepareForUpdateDuplicate@20
+NtfsUpdateDuplicateInfo@16
+NtfsFsdSetInformation@8
+NtfsCommonSetInformation@8
+NtfsWriteFileSizes@20
+NtfsSetEndOfFileInfo@24
+NtfsSetBasicInfo@20
+NtfsUpdateFileDupInfo@12
+NtfsCopyWriteA@32
+_allmul
+LfsFindOldestClientLsn@12
+_aullshr
+LfsFindCurrentAvail@4
+_allshl
+NtfsFsdQueryVolumeInformation@8
+NtfsCommonQueryVolumeInfo@8
+NtfsFastQueryStdInfo@20
+NtfsAddNtfsMcbEntry@32
+NtfsDefineNtfsMcbRange@24
+NtfsVerifyAndRevertUsaBlock@24
+NtfsUpdateScbFromAttribute@12
+NtfsCreateInternalStreamCommon@16
+NtfsUpdateFcbInfoFromDisk@20
+NtfsFcbTableCompare@12
+NtfsAllocateFcbTableEntry@8
+NtfsUpdateFcbSecurity@20
+NtfsAllocateEresource@0
+SeValidSecurityDescriptor@8
+NtfsCreateFcb@28
+NtfsInitializeNtfsMcb@16
+NtfsCheckScbForCache@4
+NtfsMapAttributeValue@24
+NtfsDeleteInternalAttributeStream@8
+NtfsNumberOfRunsInRange@12
+NtfsPreloadAllocation@24
+NtfsMapOrPinPageInBitmap@32
+NtfsGetNextNtfsMcbEntry@24
+NtfsPinStream@28
+NtfsUnloadNtfsMcbRange@28
+NtfsInsertPrefix@8
+NtfsOpenFile@92
+NtfsInsertNameLink@8
+NtfsCreateLcb@28
+NtfsFspDispatch@4
+NtfsAddToWorkque@8
+NtfsFreeEresource@4
+NtfsFreeFcbTableEntry@8
+NtfsDeleteScb@8
+NtfsDeleteFcb@12
+NtfsUninitializeNtfsMcb@4
+NtfsTeardownFromLcb@32
+NtfsGetNextCachedFreeRun@24
+NtfsAddCachedRun@28
+NtfsDeleteIndexEntry@16
+DeleteFromIndex@12
+NtfsPinMftRecord@28
+NtfsRemovePrefix@4
+PruneIndex@16
+NtfsFreeBitmapRun@24
+NtfsDeallocateClusters@32
+NtfsDeleteAllocationInternal@32
+NtfsDeleteAllocation@36
+DeleteSimple@16
+NtfsRemoveNtfsMcbEntry@20
+NtfsUpdateFcb@4
+NtfsRestartClearBitsInBitMap@16
+NtfsRestartDeleteSimpleAllocation@8
+NtfsUpdateIndexScbFromAttribute@8
+NtfsAddScbToFspClose@12
+NtfsUpdateScbFromMemory@8
+AddToIndex@20
+NtfsScanMcbForRealClusterCount@24
+NtfsRestartChangeMapping@28
+NtfsAllocateBitmapRun@24
+NtfsGetSpaceForAttribute@16
+NtfsGetNextScb@8
+NtfsRenameLinkInDir@36
+NtfsDeleteAttributeAllocation@24
+NtfsAddIndexEntry@24
+NtfsIsMftIndexInHole@16
+NtfsChangeAttributeSize@16
+NtfsRenameLcb@20
+NtfsBuildMappingPairs@20
+NtfsAddAllocation@32
+NtfsCreateAttributeWithValue@40
+NtfsCreateNonresidentWithValue@44
+NtfsInitializeMftRecord@24
+NtfsAllocateAttribute@40
+NtfsFindTargetElements@24
+NtfsOpenTargetDirectory@44
+NtfsAddLink@36
+NtfsAddNameToParent@36
+NtfsAllocateClusters@36
+NtfsRestartRemoveAttribute@12
+NtfsGetHighestVcn@16
+NtfsConvertToNonresident@20
+NtfsInitializeFcbAndStdInfo@24
+NtfsRestartSetBitsInBitMap@16
+NtfsAllocateRecord@16
+NtfsSetRenameInfo@24
+NtfsIsFatNameValid@8
+InsertSimpleAllocation@24
+NtfsGetNextHoleToFill@36
+NtfsCreateAttributeWithAllocation@32
+NtfsCreateAttribute@32
+NtfsRestartInsertAttribute@28
+NtfsAllocateMftRecord@12
+NtfsOpenNewAttr@56
+NtfsRestartInsertSimpleAllocation@12
+MmCanFileBeTruncated@8
+NtfsStoreSecurityDescriptor@12
+NtfsLookupLastNtfsMcbEntry@12
+NtfsDeleteAttributeRecord@20
+NtfsGetSizeForMappingPairs@24
+NtfsAssignSecurity@36
+NtfsAddAttributeAllocation@20
+NtfsReadAheadCachedBitmap@16
+NtfsCheckIndexForAddOrDelete@12
+NtfsRemoveLink@24
+NtfsAddDeallocatedRecords@16
+RtlFindLastBackwardRunClear@12
+NtfsUpdateNormalizedName@20
+NtfsOplockPrePostIrp@8
+NtfsDeallocateMftRecord@12
+NtfsDeleteLcb@8
+NtfsDeallocateRecord@16
+NtfsDeleteFile@16
+NtfsOplockComplete@8
+NtfsDereferenceSharedSecurity@4
+NtfsDeleteAllocationFromRecord@16
+NtfsPrepareMdlWriteA@28
+NtfsAcquireScbForReadAhead@8
+NtfsReleaseScbFromReadAhead@4
+NtfsQueryFsSizeInfo@16
+NtfsOpenSubdirectory@32
+NtfsReleaseFcbWithPaging@8
+NtfsFastUnlockSingle@28
+InsertSimpleRoot@20
+NtfsCreateFileLock@8
+NtfsRestartDeleteSimpleRoot@16
+NtfsRestartInsertSimpleRoot@20
+NtfsFastLock@36
+NtfsRestartChangeAttributeSize@16
+NtfsFastIoCheckIfPossible@32
+MmSetAddressRangeModified@8
+LfsFlushToLsn@12
+LfsFlushToLsnPriv@12
+NtfsExtendRestartTable@12
+NtfsLoadSecurityDescriptor@12
+NtfsAcquireForCreateSection@4
+NtfsReleaseForCreateSection@4
+NtfsBackoutFailedOpens@20
+NtfsGetReservedBuffer@16
+NtfsAddRecentlyDeallocated@16
+NtfsFindFreeBitmapRun@32
+NtfsIsLcnInCachedFreeRun@24
+NtfsCreateIndex@40
+NtfsMultipleAsync@20
+NtfsMultiSyncCompletionRoutine@12
+NtfsInitializeRecordAllocation@28
+NtfsPreparePinWriteStream@32
+GetIndexBuffer@16
+PushIndexRoot@12
+NtfsWaitForIoAtEof@16
+NtfsLockNtfsMcb@4
+NtfsMcbCleanupLruQueue@4
+NtfsUnlockNtfsMcb@4
+RtlFindNextForwardRunClear@12
+NtfsQueryFsAttributeInfo@16
+NtfsCheckRecordStackUsage@4
+NtfsScanEntireBitmap@12
+NtfsAcquireExclusiveGlobal@4
+NtfsGetNextFcbTableEntry@8
+NtfsAcquireExclusiveVcb@12
+NtfsPrePostIrp@8
+NtfsGetDiskGeometry@16
+NtfsRaiseStatus@16
+RtlUnwind@16
+NtfsProcessException@12
+NtfsExceptionFilter@8
+_except_handler3
+NtfsInitializeVcb@16
+_local_unwind2
+_global_unwind2
+NtfsMountVolume@8
+NtfsPostRequest@8
+NtfsDeviceIoControl@28
+NtfsCommonVolumeOpen@8
+NtfsRestartVolume@8
+NtfsStartLogFile@8
+LfsReadRestart@48
+LfsUpdateRestartAreaFromLfcb@8
+ReleaseRestartState@16
+LfsUpdateLfcbFromNoRestart@32
+NtfsScanMftBitmap@8
+NtfsOpenRootDirectory@8
+NtfsFlushUserStream@16
+NtfsCheckFileRecord@12
+NtfsFsdDeviceControl@8
+NtfsSetAndGetVolumeTimes@12
+InitializeRestartState@20
+LfsRemoveClientFromList@12
+NtfsCreateRootFcb@8
+LfsIsRestartPageHeaderValid@16
+NtfsInitializeCachedBitmap@8
+LfsRestartLogFile@24
+LfsUpdateLfcbFromPgHeader@24
+NtfsCreatePrerestartScb@24
+LfsAddClientToList@12
+NtfsRestoreScbSnapshots@8
+LfsNormalizeBasicLogFile@12
+LfsInitializeLogFilePriv@24
+NtfsDismountVolume@8
+NtfsCommonDeviceControl@8
+NtfsGetVolumeLabel@12
+LfsIsRestartAreaValid@8
+LfsInitializeLogFileService@0
+LfsAllocateLfcb@0
+NtfsInitializeClusterAllocation@8
+NtfsFlushVolume@20
+NtfsIsBootSectorNtfs@8
+NtfsCheckAttributeRecord@16
+NtfsSetExtendedDasdIo@8
+NtfsCloseAttributesFromRestart@8
+NtfsOpenSystemFile@32
+NtfsVolumeDasdIo@24
+NtfsGet8dot3NameStatus@12
+DriverEntry@8
+NtfsInitializeNtfsData@4
+_alldiv
+LfsOpenLogFile@36
+NtfsReadBootSector@20
+LfsReadRestartArea@16
+NtfsAbortTransaction@12
+NtfsReplaceFileEas@12
+NtfsReplaceAttribute@28
+NtfsOverwriteAttr@56
+NtfsRemoveDataAttributes@24
+NtfsAddEa@24
+NtfsQueryNameInfo@24
+NtfsCommonQueryInformation@8
+NtfsFsdQueryInformation@8
+NtfsReserveMftRecord@12
+NtfsDeleteVcb@8
+LfsDeleteLogHandle@4
+NtfsReleaseAllFiles@12
+NtfsAcquireAllFiles@16
+NtfsPerformDismountOnVcb@12
+NtfsStopLogFile@4
diff --git a/private/ntos/cntfs/mp/sources b/private/ntos/cntfs/mp/sources
new file mode 100644
index 000000000..944376fc4
--- /dev/null
+++ b/private/ntos/cntfs/mp/sources
@@ -0,0 +1,30 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+NT_UP=0
+
+TARGETPATH=$(BASEDIR)\public\sdk\lib
+TARGETLIBS=..\..\lfs\mp\obj\*\lfs.lib
+
+!include ..\sources.inc
diff --git a/private/ntos/cntfs/namesup.c b/private/ntos/cntfs/namesup.c
new file mode 100644
index 000000000..466f1b283
--- /dev/null
+++ b/private/ntos/cntfs/namesup.c
@@ -0,0 +1,1114 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NameSup.c
+
+Abstract:
+
+ This module implements the Ntfs Name support routines
+
+Author:
+
+ Gary Kimura [GaryKi] & Tom Miller [TomM] 20-Feb-1990
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define Dbg (DEBUG_TRACE_NAMESUP)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCollateNames)
+#pragma alloc_text(PAGE, NtfsIsFatNameValid)
+#pragma alloc_text(PAGE, NtfsIsFileNameValid)
+#pragma alloc_text(PAGE, NtfsParseName)
+#pragma alloc_text(PAGE, NtfsParsePath)
+#pragma alloc_text(PAGE, NtfsUpcaseName)
+#endif
+
+#define MAX_CHARS_IN_8_DOT_3 (12)
+
+
+PARSE_TERMINATION_REASON
+NtfsParsePath (
+ IN UNICODE_STRING Path,
+ IN BOOLEAN WildCardsPermissible,
+ OUT PUNICODE_STRING FirstPart,
+ OUT PNTFS_NAME_DESCRIPTOR Name,
+ OUT PUNICODE_STRING RemainingPart
+ )
+
+/*++
+
+Routine Description:
+
+ This routine takes as input a path. Each component of the path is
+ checked until either:
+
+ - The end of the path has been reached, or
+
+ - A well formed complex name is excountered, or
+
+ - An illegal character is encountered, or
+
+ - A complex name component is malformed
+
+ At this point the return value is set to one of the three reasons
+ above, and the arguments are set as follows:
+
+ FirstPart: All the components up to one containing an illegal
+ character or colon character. May be the whole path.
+
+ Name: The "pieces" of a component containing an illegal
+ character or colon character. This name is actually
+ a struncture containing the four pieces of a name,
+ "file name, attribute type, attribute name, version
+ number." In the example below, they are shown
+ separated by plus signs.
+
+ RemainingPart: All the remaining components.
+
+ A preceding or trailing backslash is ignored during processing and
+ stripped in either FirstPart or RemainingPart. Following are some
+ examples of this routine's actions.
+
+ Path FirstPart Name Remaining
+ ================ ========= ============ =========
+
+ \nt\pri\os \nt\pri\os <empty>
+
+ \nt\pri\os\ \nt\pri\os <empty>
+
+ nt\pri\os \nt\pri\os <empty>
+
+ \nt\pr"\os \nt pr" os
+
+ \nt\pri\os:contr::3\ntfs \nt\pri os + contr + + 3 ntfs
+
+ \nt\pri\os\circle:pict:circ \nt\pri\os circle + pict + circ <empty>
+
+Arguments:
+
+ Path - This unicode string descibes the path to parse. Note that path
+ here may only describe a single component.
+
+ WildCardsPermissible - This parameter tells us if wild card characters
+ should be considered legal.
+
+ FirstPart - This unicode string will receive portion of the path, up to
+ a component boundry, successfully parsed before the parse terminated.
+ Note that store for this string comes from the Path parameter.
+
+ Name - This is the name we were parsing when we reached our termination
+ condition. It is a srtucture of strings that receive the file name,
+ attribute type, attribute name, and version number respectively.
+ It wil be filled in only to the extent that the parse succeeded. For
+ example, in the case we encounter an illegal character in the
+ attribute type field, only the file name field will be filled in.
+ This may signal a special control file, and this possibility must be
+ investigated by the file system.
+
+ RemainingPart - This string will receive any portion of the path, starting
+ at the first component boundry after the termination name, not parsed.
+ It will often be an empty string.
+
+ReturnValue:
+
+ An enumerated type with one of the following values:
+
+ EndOfPathReached - The path was fully parsed. Only first part
+ is filled in.
+ NonSimpleName - A component of the path containing a legal,
+ well formed non-simple name was encountered.
+ IllegalCharacterInName - An illegal character was encountered. Parsing
+ stops immediately.
+ MalFormedName - A non-simple name did not conform to the
+ correct format. This may be a result of too
+ many fields, or a malformed version number.
+ AttributeOnly - A component of the path containing a legal
+ well formed non-simple name was encountered
+ which does not have a file name.
+ VersionNumberPresent - A component of the path containing a legal
+ well formed non-simple name was encountered
+ which contains a version number.
+
+--*/
+
+{
+ UNICODE_STRING FirstName;
+
+ BOOLEAN WellFormed;
+ BOOLEAN MoreNamesInPath;
+ BOOLEAN FirstIteration;
+ BOOLEAN FoundIllegalCharacter;
+
+ PARSE_TERMINATION_REASON TerminationReason;
+
+ PAGED_CODE();
+
+ //
+ // Initialize some loacal variables and OUT parameters.
+ //
+
+ FirstIteration = TRUE;
+ MoreNamesInPath = TRUE;
+
+ //
+ // By default, set the returned first part to start at the beginning of
+ // the input buffer and include a leading backslash.
+ //
+
+ FirstPart->Buffer = Path.Buffer;
+
+ if (Path.Buffer[0] == L'\\') {
+
+ FirstPart->Length = 2;
+ FirstPart->MaximumLength = 2;
+
+ } else {
+
+ FirstPart->Length = 0;
+ FirstPart->MaximumLength = 0;
+ }
+
+ //
+ // Do the first check outside the loop in case we are given a backslash
+ // by itself.
+ //
+
+ if (FirstPart->Length == Path.Length) {
+
+ RemainingPart->Length = 0;
+ RemainingPart->Buffer = &Path.Buffer[Path.Length >> 1];
+
+ return EndOfPathReached;
+ }
+
+ //
+ // Crack the path, checking each componant
+ //
+
+ while (MoreNamesInPath) {
+
+ //
+ // Clear the flags field in the name descriptor.
+ //
+
+ Name->FieldsPresent = 0;
+
+ FsRtlDissectName( Path, &FirstName, RemainingPart );
+
+ MoreNamesInPath = (BOOLEAN)(RemainingPart->Length != 0);
+
+ //
+ // If this is not the last name in the path, then attributes
+ // and version numbers are not allowed. If this is the last
+ // name then propagate the callers arguments.
+ //
+
+ WellFormed = NtfsParseName( FirstName,
+ WildCardsPermissible,
+ &FoundIllegalCharacter,
+ Name );
+
+ //
+ // Check the cases when we will break out of this loop, ie. if the
+ // the name was not well formed or it was non-simple.
+ //
+
+ if ( !WellFormed ||
+ (Name->FieldsPresent != FILE_NAME_PRESENT_FLAG)
+
+ //
+ // TEMPCODE TRAILING_DOT
+ //
+
+ || (Name->FileName.Length != Name->FileName.MaximumLength)
+
+ ) {
+
+ break;
+ }
+
+ //
+ // We will continue parsing this string, so consider the current
+ // FirstName to be parsed and add it to FirstPart. Also reset
+ // the Name->FieldsPresent variable.
+ //
+
+ if ( FirstIteration ) {
+
+ FirstPart->Length += FirstName.Length;
+ FirstIteration = FALSE;
+
+ } else {
+
+ FirstPart->Length += (sizeof(WCHAR) + FirstName.Length);
+ }
+
+ FirstPart->MaximumLength = FirstPart->Length;
+
+ Path = *RemainingPart;
+ }
+
+ //
+ // At this point FirstPart, Name, and RemainingPart should all be set
+ // correctly. It remains, only to generate the correct return value.
+ //
+
+ if ( !WellFormed ) {
+
+ if ( FoundIllegalCharacter ) {
+
+ TerminationReason = IllegalCharacterInName;
+
+ } else {
+
+ TerminationReason = MalFormedName;
+ }
+
+ } else {
+
+ if ( Name->FieldsPresent == FILE_NAME_PRESENT_FLAG ) {
+
+ //
+ // TEMPCODE TRAILING_DOT
+ //
+
+ if (Name->FileName.Length != Name->FileName.MaximumLength) {
+
+ TerminationReason = NonSimpleName;
+
+ } else {
+
+ TerminationReason = EndOfPathReached;
+ }
+
+ } else if (FlagOn( Name->FieldsPresent, VERSION_NUMBER_PRESENT_FLAG )) {
+
+ TerminationReason = VersionNumberPresent;
+
+ } else if (!FlagOn( Name->FieldsPresent, FILE_NAME_PRESENT_FLAG )) {
+
+ TerminationReason = AttributeOnly;
+
+ } else {
+
+ TerminationReason = NonSimpleName;
+ }
+
+ }
+
+ return TerminationReason;
+}
+
+
+BOOLEAN
+NtfsParseName (
+ IN UNICODE_STRING Name,
+ IN BOOLEAN WildCardsPermissible,
+ OUT PBOOLEAN FoundIllegalCharacter,
+ OUT PNTFS_NAME_DESCRIPTOR ParsedName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine takes as input a single name component. It is processed into
+ file name, attribute type, attribute name, and version number fields.
+
+ If the name is well formed according to the following rules:
+
+ A. An NTFS name may not contain any of the following characters:
+
+ 0x0000-0x001F " / < > ? | *
+
+ B. An Ntfs name can take any of the following forms:
+
+ ::T
+ :A
+ :A:T
+ N
+ N:::V
+ N::T
+ N::T:V
+ N:A
+ N:A::V
+ N:A:T
+ N:A:T:V
+
+ If a version number is present, there must be a file name.
+ We specifically note the legal names without a filename
+ component (AttributeOnly) and any name with a version number
+ (VersionNumberPresent).
+
+ Incidently, N corresponds to file name, T to attribute type, A to
+ attribute name, and V to version number.
+
+ TRUE is returned. If FALSE is returned, then the OUT parameter
+ FoundIllegalCharacter will be set appropriatly. Note that the buffer
+ space for ParsedName comes from Name.
+
+Arguments:
+
+ Name - This is the single path element input name.
+
+ WildCardsPermissible - This determines if wild cards characters should be
+ considered legal
+
+ FoundIllegalCharacter - This parameter will receive a TRUE if the the
+ function returns FALSE because of encountering an illegal character.
+
+ ParsedName - Recieves the pieces of the processed name. Note that the
+ storage for all the string from the input Name.
+
+ReturnValue:
+
+ TRUE if the Name is well formed, and FALSE otherwise.
+
+
+--*/
+
+{
+ ULONG Index;
+ ULONG NameLength;
+ ULONG FieldCount;
+ ULONG FieldIndexes[5];
+ UCHAR ValidCharFlags = FSRTL_NTFS_LEGAL;
+
+ PULONG Fields;
+
+ BOOLEAN IsNameValid = TRUE;
+
+ PAGED_CODE();
+
+ //
+ // Initialize some OUT parameters and local variables.
+ //
+
+ *FoundIllegalCharacter = FALSE;
+
+ Fields = &ParsedName->FieldsPresent;
+
+ *Fields = 0;
+
+ FieldCount = 1;
+
+ FieldIndexes[0] = 0xFFFFFFFF; // We add on to this later...
+
+ //
+ // For starters, zero length names are invalid.
+ //
+
+ NameLength = Name.Length / sizeof(WCHAR);
+
+ if ( NameLength == 0 ) {
+
+ return FALSE;
+ }
+
+ //
+ // Now name must correspond to a legal single Ntfs Name.
+ //
+
+ for (Index = 0; Index < NameLength; Index += 1) {
+
+ WCHAR Char;
+
+ Char = Name.Buffer[Index];
+
+ //
+ // First check that file names are well formed in terms of colons.
+ //
+
+ if ( Char == L':' ) {
+
+ //
+ // A colon can't be the last character, and we can't have
+ // more than three colons.
+ //
+
+ if ( (Index == NameLength - 1) ||
+ (FieldCount >= 4) ) {
+
+ IsNameValid = FALSE;
+ break;
+ }
+
+ FieldIndexes[FieldCount] = Index;
+
+ FieldCount += 1;
+
+ ValidCharFlags |= FSRTL_OLE_LEGAL;
+
+ continue;
+ }
+
+ //
+ // Now check for wild card characters if they weren't allowed,
+ // and other illegal characters.
+ //
+
+ if ( (Char <= 0xff) &&
+ !FsRtlTestAnsiCharacter( Char, TRUE, WildCardsPermissible, ValidCharFlags )) {
+
+ IsNameValid = FALSE;
+ *FoundIllegalCharacter = TRUE;
+ break;
+ }
+ }
+
+ //
+ // If we ran into a problem with one of the fields, don't try to load
+ // up that field into the out parameter.
+ //
+
+ if ( !IsNameValid ) {
+
+ FieldCount -= 1;
+
+ //
+ // Set the end of the last field to the current Index.
+ //
+
+ } else {
+
+ FieldIndexes[FieldCount] = Index;
+ }
+
+ //
+ // Now we load up the OUT parmeters
+ //
+
+ while ( FieldCount != 0 ) {
+
+ ULONG StartIndex;
+ ULONG EndIndex;
+ USHORT Length;
+
+ //
+ // Add one here since this is actually the position of the colon.
+ //
+
+ StartIndex = FieldIndexes[FieldCount - 1] + 1;
+
+ EndIndex = FieldIndexes[FieldCount];
+
+ Length = (USHORT)((EndIndex - StartIndex) * sizeof(WCHAR));
+
+ //
+ // If this field is empty, skip it
+ //
+
+ if ( Length == 0 ) {
+
+ FieldCount -= 1;
+ continue;
+ }
+
+ //
+ // Now depending of the field, extract the appropriate information.
+ //
+
+ if ( FieldCount == 1 ) {
+
+ UNICODE_STRING TempName;
+
+ TempName.Buffer = &Name.Buffer[StartIndex];
+ TempName.Length = Length;
+ TempName.MaximumLength = Length;
+
+ //
+ // If the resulting length is 0, forget this entry.
+ //
+
+ if (TempName.Length == 0) {
+
+ FieldCount -= 1;
+ continue;
+ }
+
+ SetFlag(*Fields, FILE_NAME_PRESENT_FLAG);
+
+ ParsedName->FileName = TempName;
+
+ } else if ( FieldCount == 2) {
+
+ SetFlag(*Fields, ATTRIBUTE_NAME_PRESENT_FLAG);
+
+ ParsedName->AttributeName.Buffer = &Name.Buffer[StartIndex];
+ ParsedName->AttributeName.Length = Length;
+ ParsedName->AttributeName.MaximumLength = Length;
+
+ } else if ( FieldCount == 3) {
+
+ SetFlag(*Fields, ATTRIBUTE_TYPE_PRESENT_FLAG);
+
+ ParsedName->AttributeType.Buffer = &Name.Buffer[StartIndex];
+ ParsedName->AttributeType.Length = Length;
+ ParsedName->AttributeType.MaximumLength = Length;
+
+ } else if ( FieldCount == 4) {
+
+ ULONG VersionNumber;
+ STRING VersionNumberA;
+ UNICODE_STRING VersionNumberU;
+
+ NTSTATUS Status;
+ UCHAR *endp = NULL;
+
+ VersionNumberU.Buffer = &Name.Buffer[StartIndex];
+ VersionNumberU.Length = Length;
+ VersionNumberU.MaximumLength = Length;
+
+ //
+ // Note that the resulting Ansi string is null terminated.
+ //
+
+ Status = RtlUnicodeStringToCountedOemString( &VersionNumberA,
+ &VersionNumberU,
+ TRUE );
+
+ //
+ // If something went wrong (most likely ran out of pool), raise.
+ //
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ ExRaiseStatus( Status );
+ }
+
+ VersionNumber = 0; //**** strtoul( VersionNumberA.Buffer, &endp, 0 );
+
+ RtlFreeOemString( &VersionNumberA );
+
+ if ( (VersionNumber == MAXULONG) || (endp != NULL) ) {
+
+ IsNameValid = FALSE;
+
+ } else {
+
+ SetFlag( *Fields, VERSION_NUMBER_PRESENT_FLAG );
+ ParsedName->VersionNumber = VersionNumber;
+ }
+ }
+
+ FieldCount -= 1;
+ }
+
+ //
+ // Check for special malformed cases.
+ //
+
+ if (FlagOn( *Fields, VERSION_NUMBER_PRESENT_FLAG )
+ && !FlagOn( *Fields, FILE_NAME_PRESENT_FLAG )) {
+
+ IsNameValid = FALSE;
+ }
+
+ return IsNameValid;
+}
+
+
+VOID
+NtfsUpcaseName (
+ IN PWCH UpcaseTable,
+ IN ULONG UpcaseTableSize,
+ IN OUT PUNICODE_STRING Name
+ )
+
+/*++
+
+Routine Description:
+
+ This routine upcases a string.
+
+Arguments:
+
+ UpcaseTable - Pointer to an array of Unicode upcased characters indexed by
+ the Unicode character to be upcased.
+
+ UpcaseTableSize - Size of the Upcase table in unicode characters
+
+ Name - Supplies the string to upcase
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG i;
+ ULONG Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpcaseName\n") );
+ DebugTrace( 0, Dbg, ("Name = %Z\n", Name) );
+
+ Length = Name->Length / sizeof(WCHAR);
+
+ for (i=0; i < Length; i += 1) {
+
+ if ((ULONG)Name->Buffer[i] < UpcaseTableSize) {
+ Name->Buffer[i] = UpcaseTable[ (ULONG)Name->Buffer[i] ];
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("Upcased Name = %Z\n", Name) );
+ DebugTrace( -1, Dbg, ("NtfsUpcaseName -> VOID\n") );
+
+ return;
+}
+
+
+FSRTL_COMPARISON_RESULT
+NtfsCollateNames (
+ IN PWCH UpcaseTable,
+ IN ULONG UpcaseTableSize,
+ IN PUNICODE_STRING Expression,
+ IN PUNICODE_STRING Name,
+ IN FSRTL_COMPARISON_RESULT WildIs,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+Routine Description:
+
+ This routine compares an expression with a name lexigraphically for
+ LessThan, EqualTo, or GreaterThan. If the expression does not contain
+ any wildcards, this procedure does a complete comparison. If the
+ expression does contain wild cards, then the comparison is only done up
+ to the first wildcard character. Name may not contain wild cards.
+ The wildcard character compares as less then all other characters. So
+ the wildcard name "*.*" will always compare less than all all strings.
+
+Arguments:
+
+ UpcaseTable - Pointer to an array of Unicode upcased characters indexed by
+ the Unicode character to be upcased.
+
+ UpcaseTableSize - Size of the Upcase table in unicode characters
+
+ Expression - Supplies the first name expression to compare, optionally with
+ wild cards. Note that caller must have already upcased
+ the name (this will make lookup faster).
+
+ Name - Supplies the second name to compare - no wild cards allowed.
+ The caller must have already upcased the name.
+
+ WildIs - Determines what Result is returned if a wild card is encountered
+ in the Expression String. For example, to find the start of
+ an expression in the Btree, LessThan should be supplied; then
+ GreaterThan should be supplied to find the end of the expression
+ in the tree.
+
+ IgnoreCase - TRUE if case should be ignored for the comparison
+
+Return Value:
+
+ FSRTL_COMPARISON_RESULT - LessThan if Expression < Name
+ EqualTo if Expression == Name
+ GreaterThan if Expression > Name
+
+--*/
+
+{
+ WCHAR ConstantChar;
+ WCHAR ExpressionChar;
+
+ ULONG i;
+ ULONG Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCollateNames\n") );
+ DebugTrace( 0, Dbg, ("Expression = %Z\n", Expression) );
+ DebugTrace( 0, Dbg, ("Name = %Z\n", Name) );
+ DebugTrace( 0, Dbg, ("WildIs = %08lx\n", WildIs) );
+ DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) );
+
+ //
+ // Calculate the length in wchars that we need to compare. This will
+ // be the smallest length of the two strings.
+ //
+
+ if (Expression->Length < Name->Length) {
+
+ Length = Expression->Length / sizeof(WCHAR);
+
+ } else {
+
+ Length = Name->Length / sizeof(WCHAR);
+ }
+
+ //
+ // Now we'll just compare the elements in the names until we can decide
+ // their lexicagrahical ordering, checking for wild cards in
+ // LocalExpression (from Expression).
+ //
+ // If an upcase table was specified, the compare is done case insensitive.
+ //
+
+ for (i = 0; i < Length; i += 1) {
+
+ ConstantChar = Name->Buffer[i];
+ ExpressionChar = Expression->Buffer[i];
+
+ if ( IgnoreCase ) {
+
+ if (ConstantChar < UpcaseTableSize) {
+ ConstantChar = UpcaseTable[(ULONG)ConstantChar];
+ }
+ if (ExpressionChar < UpcaseTableSize) {
+ ExpressionChar = UpcaseTable[(ULONG)ExpressionChar];
+ }
+ }
+
+ if ( FsRtlIsUnicodeCharacterWild(ExpressionChar) ) {
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> %08lx (Wild)\n", WildIs) );
+ return WildIs;
+ }
+
+ if ( ExpressionChar < ConstantChar ) {
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> LessThan\n") );
+ return LessThan;
+ }
+
+ if ( ExpressionChar > ConstantChar ) {
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> GreaterThan\n") );
+ return GreaterThan;
+ }
+ }
+
+ //
+ // We've gone through the entire short match and they're equal
+ // so we need to now check which one is shorter, or, if
+ // LocalExpression is longer, we need to see if the next character is
+ // wild! (For example, an enumeration of "ABC*", must return
+ // "ABC".
+ //
+
+ if (Expression->Length < Name->Length) {
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> LessThan (length)\n") );
+ return LessThan;
+ }
+
+ if (Expression->Length > Name->Length) {
+
+ if (FsRtlIsUnicodeCharacterWild(Expression->Buffer[i])) {
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> %08lx (trailing wild)\n", WildIs) );
+ return WildIs;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> GreaterThan (length)\n") );
+ return GreaterThan;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCollateNames -> EqualTo\n") );
+ return EqualTo;
+}
+
+BOOLEAN
+NtfsIsFileNameValid (
+ IN PUNICODE_STRING FileName,
+ IN BOOLEAN WildCardsPermissible
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if the specified file name is valid. Note that
+ only the file name part of the name is allowed, ie. no colons are
+ permitted.
+
+Arguments:
+
+ FileName - Supplies the name to check.
+
+ WildCardsPermissible - Tells us if wild card characters are ok.
+
+Return Value:
+
+ BOOLEAN - TRUE if the name is valid, FALSE otherwise.
+
+--*/
+
+{
+ ULONG Index;
+ ULONG NameLength;
+ BOOLEAN AllDots = TRUE;
+ BOOLEAN IsNameValid = TRUE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsIsFileNameValid\n") );
+ DebugTrace( 0, Dbg, ("FileName = %Z\n", FileName) );
+ DebugTrace( 0, Dbg, ("WildCardsPermissible = %s\n",
+ WildCardsPermissible ? "TRUE" : "FALSE") );
+
+ //
+ // Check if corresponds to a legal single Ntfs Name.
+ //
+
+ NameLength = FileName->Length / sizeof(WCHAR);
+
+ for (Index = 0; Index < NameLength; Index += 1) {
+
+ WCHAR Char;
+
+ Char = FileName->Buffer[Index];
+
+ //
+ // Check for wild card characters if they weren't allowed, and
+ // check for the other illegal characters including the colon and
+ // backslash characters since this can only be a single component.
+ //
+
+ if ( ((Char <= 0xff) &&
+ !FsRtlIsAnsiCharacterLegalNtfs(Char, WildCardsPermissible)) ||
+ (Char == L':') ||
+ (Char == L'\\') ) {
+
+ IsNameValid = FALSE;
+ break;
+ }
+
+ //
+ // Remember if this is not a '.' character.
+ //
+
+ if (Char != L'.') {
+
+ AllDots = FALSE;
+ }
+ }
+
+ //
+ // The names '.' and '..' are also invalid.
+ //
+
+ if (AllDots
+ && (NameLength == 1
+ || NameLength == 2)) {
+
+ IsNameValid = FALSE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsIsFileNameValid -> %s\n", IsNameValid ? "TRUE" : "FALSE") );
+
+ return IsNameValid;
+}
+
+
+BOOLEAN
+NtfsIsFatNameValid (
+ IN PUNICODE_STRING FileName,
+ IN BOOLEAN WildCardsPermissible
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if the specified file name is conformant to the
+ Fat 8.3 file naming rules.
+
+Arguments:
+
+ FileName - Supplies the name to check.
+
+ WildCardsPermissible - Tells us if wild card characters are ok.
+
+Return Value:
+
+ BOOLEAN - TRUE if the name is valid, FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN Results;
+ STRING DbcsName;
+ USHORT i;
+ CHAR Buffer[24];
+ WCHAR wc;
+
+ PAGED_CODE();
+
+ //
+ // If the name is more than 24 bytes then it can't be a valid Fat name.
+ //
+
+ if (FileName->Length > 24) {
+
+ return FALSE;
+ }
+
+ //
+ // We will do some extra checking ourselves because we really want to be
+ // fairly restrictive of what an 8.3 name contains. That way
+ // we will then generate an 8.3 name for some nomially valid 8.3
+ // names (e.g., names that contain DBCS characters). The extra characters
+ // we'll filter off are those characters less than and equal to the space
+ // character and those beyond lowercase z.
+ //
+
+ if (FlagOn(NtfsData.Flags,NTFS_FLAGS_ALLOW_EXTENDED_CHAR)) {
+
+ for (i = 0; i < FileName->Length / 2; i += 1) {
+
+ wc = FileName->Buffer[i];
+
+ if ((wc <= 0x0020) || (wc == 0x007c)) { return FALSE; }
+ }
+
+ } else {
+
+ for (i = 0; i < FileName->Length / 2; i += 1) {
+
+ wc = FileName->Buffer[i];
+
+ if ((wc <= 0x0020) || (wc >= 0x007f) || (wc == 0x007c)) { return FALSE; }
+ }
+ }
+
+ //
+ // The characters match up okay so now build up the dbcs string to call
+ // the fsrtl routine to check for legal 8.3 formation
+ //
+
+ Results = FALSE;
+
+ DbcsName.MaximumLength = 24;
+ DbcsName.Buffer = Buffer;
+
+ if (NT_SUCCESS(RtlUnicodeStringToCountedOemString( &DbcsName, FileName, FALSE))) {
+
+ if (FsRtlIsFatDbcsLegal( DbcsName, WildCardsPermissible, FALSE, FALSE )) {
+
+ Results = TRUE;
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsIsDosNameInCurrentCodePage(
+ IN PUNICODE_STRING FileName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks that the given file name is composed only of OEM
+ characters and that it conforms to the DOS 8.3 standard. In other
+ words it answers the question: "Can I copy this file to a FAT
+ partition and then back again and have the same name that I
+ started out with?"
+
+Arguments:
+
+ FileName - Supplies the file name to check.
+
+Return Value:
+
+ FALSE - The supplied file name is not a DOS file name.
+ TRUE - The supplied file name is a DOS file name.
+
+--*/
+{
+ WCHAR upcase_buffer[MAX_CHARS_IN_8_DOT_3 + 1];
+ UNICODE_STRING upcase_file_name;
+ CHAR oem_buffer[MAX_CHARS_IN_8_DOT_3 + 1];
+ STRING oem_string;
+ WCHAR unicode_buffer[MAX_CHARS_IN_8_DOT_3 + 1];
+ UNICODE_STRING unicode_string;
+ ULONG i;
+ USHORT Length;
+
+ //
+ // This ain't 8.3 if unicode version has more than 12 characters.
+ //
+
+ if (FileName->Length > MAX_CHARS_IN_8_DOT_3*sizeof(WCHAR)) {
+ return FALSE;
+ }
+
+ upcase_file_name.Buffer = upcase_buffer;
+ upcase_file_name.Length = 0;
+ upcase_file_name.MaximumLength = (MAX_CHARS_IN_8_DOT_3 + 1) * sizeof(WCHAR);
+
+ oem_string.Buffer = oem_buffer;
+ oem_string.Length = 0;
+ oem_string.MaximumLength = MAX_CHARS_IN_8_DOT_3 + 1;
+
+ unicode_string.Buffer = unicode_buffer;
+ unicode_string.Length = 0;
+ unicode_string.MaximumLength = (MAX_CHARS_IN_8_DOT_3 + 1) * sizeof(WCHAR);
+
+ //
+ // Return false if there is a space in the name.
+ //
+
+ for (i = 0, Length = FileName->Length / 2;
+ i < Length;
+ i += 1) {
+
+ WCHAR wc;
+
+ wc = FileName->Buffer[i];
+ if (wc == 0x0020) {
+
+ return FALSE;
+ }
+ }
+
+ //
+ // Upcase the original file name.
+ //
+
+ if (!NT_SUCCESS(RtlUpcaseUnicodeString(&upcase_file_name, FileName, FALSE)) ||
+
+ //
+ // Convert the upcased unicode string to OEM and check out the OEM string
+ // to see if it's 8.3.
+ //
+
+ !NT_SUCCESS(RtlUnicodeStringToOemString(&oem_string, &upcase_file_name, FALSE)) ||
+ !FsRtlIsFatDbcsLegal(oem_string, FALSE, FALSE, FALSE) ||
+
+ //
+ // Convert the OEM back to UNICODE and make sure that it's the same
+ // as the original upcased version of the file name.
+ //
+
+ !NT_SUCCESS(RtlOemStringToUnicodeString(&unicode_string, &oem_string, FALSE)) ||
+ !RtlEqualUnicodeString(&upcase_file_name, &unicode_string, FALSE)) {
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
diff --git a/private/ntos/cntfs/nodetype.h b/private/ntos/cntfs/nodetype.h
new file mode 100644
index 000000000..ec2c777bd
--- /dev/null
+++ b/private/ntos/cntfs/nodetype.h
@@ -0,0 +1,122 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NodeType.h
+
+Abstract:
+
+ This module defines all of the node type codes used in this development
+ shell. Every major data structure in the file system is assigned a node
+ type code that is. This code is the first CSHORT in the structure and is
+ followed by a CSHORT containing the size, in bytes, of the structure.
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#ifndef _NODETYPE_
+#define _NODETYPE_
+
+typedef CSHORT NODE_TYPE_CODE;
+typedef NODE_TYPE_CODE *PNODE_TYPE_CODE;
+
+#define NTC_UNDEFINED ((NODE_TYPE_CODE)0x0000)
+
+#define NTFS_NTC_DATA_HEADER ((NODE_TYPE_CODE)0x0700)
+#define NTFS_NTC_VCB ((NODE_TYPE_CODE)0x0701)
+#define NTFS_NTC_FCB ((NODE_TYPE_CODE)0x0702)
+#define NTFS_NTC_SCB_INDEX ((NODE_TYPE_CODE)0x0703)
+#define NTFS_NTC_SCB_ROOT_INDEX ((NODE_TYPE_CODE)0x0704)
+#define NTFS_NTC_SCB_DATA ((NODE_TYPE_CODE)0x0705)
+#define NTFS_NTC_SCB_MFT ((NODE_TYPE_CODE)0x0706)
+#define NTFS_NTC_SCB_NONPAGED ((NODE_TYPE_CODE)0x0707)
+#define NTFS_NTC_CCB_INDEX ((NODE_TYPE_CODE)0x0708)
+#define NTFS_NTC_CCB_DATA ((NODE_TYPE_CODE)0x0709)
+#define NTFS_NTC_IRP_CONTEXT ((NODE_TYPE_CODE)0x070A)
+#define NTFS_NTC_LCB ((NODE_TYPE_CODE)0x070B)
+#define NTFS_NTC_PREFIX_ENTRY ((NODE_TYPE_CODE)0x070C)
+#define NTFS_NTC_QUOTA_CONTROL ((NODE_TYPE_CODE)0x070D)
+
+
+
+typedef CSHORT NODE_BYTE_SIZE;
+
+//
+// So all records start with
+//
+// typedef struct _RECORD_NAME {
+// NODE_TYPE_CODE NodeTypeCode;
+// NODE_BYTE_SIZE NodeByteSize;
+// :
+// } RECORD_NAME;
+// typedef RECORD_NAME *PRECORD_NAME;
+//
+
+#define NodeType(P) ((P) != NULL ? (*((PNODE_TYPE_CODE)(P))) : NTC_UNDEFINED)
+#define SafeNodeType(P) (*((PNODE_TYPE_CODE)(P)))
+
+
+//
+// The following definitions are used to generate meaningful blue bugcheck
+// screens. On a bugcheck the file system can output 4 ulongs of useful
+// information. The first ulong will have encoded in it a source file id
+// (in the high word) and the line number of the bugcheck (in the low word).
+// The other values can be whatever the caller of the bugcheck routine deems
+// necessary.
+//
+// Each individual file that calls bugcheck needs to have defined at the
+// start of the file a constant called BugCheckFileId with one of the
+// NTFS_BUG_CHECK_ values defined below and then use NtfsBugCheck to bugcheck
+// the system.
+//
+
+#define NTFS_BUG_CHECK_ALLOCSUP (0x00010000)
+#define NTFS_BUG_CHECK_ATTRDATA (0x00020000)
+#define NTFS_BUG_CHECK_ATTRSUP (0x00030000)
+#define NTFS_BUG_CHECK_BITMPSUP (0x00040000)
+#define NTFS_BUG_CHECK_CACHESUP (0x00050000)
+#define NTFS_BUG_CHECK_CHECKSUP (0x00060000)
+#define NTFS_BUG_CHECK_CLEANUP (0x00070000)
+#define NTFS_BUG_CHECK_CLOST (0x00080000)
+#define NTFS_BUG_CHECK_COLATSUP (0x00090000)
+#define NTFS_BUG_CHECK_CREATE (0x000a0000)
+#define NTFS_BUG_CHECK_DEVCTRL (0x000b0000)
+#define NTFS_BUG_CHECK_DEVIOSUP (0x000c0000)
+#define NTFS_BUG_CHECK_DIRCTRL (0x000d0000)
+#define NTFS_BUG_CHECK_EA (0x000e0000)
+#define NTFS_BUG_CHECK_FILEINFO (0x000f0000)
+#define NTFS_BUG_CHECK_FILOBSUP (0x00100000)
+#define NTFS_BUG_CHECK_FLUSH (0x00110000)
+#define NTFS_BUG_CHECK_FSCTRL (0x00120000)
+#define NTFS_BUG_CHECK_FSPDISP (0x00130000)
+#define NTFS_BUG_CHECK_INDEXSUP (0x00140000)
+#define NTFS_BUG_CHECK_LOCKCTRL (0x00150000)
+#define NTFS_BUG_CHECK_LOGSUP (0x00160000)
+#define NTFS_BUG_CHECK_MFTSUP (0x00170000)
+#define NTFS_BUG_CHECK_NAMESUP (0x00180000)
+#define NTFS_BUG_CHECK_NTFSDATA (0x00190000)
+#define NTFS_BUG_CHECK_NTFSINIT (0x001a0000)
+#define NTFS_BUG_CHECK_PREFXSUP (0x001b0000)
+#define NTFS_BUG_CHECK_READ (0x001c0000)
+#define NTFS_BUG_CHECK_RESRCSUP (0x001d0000)
+#define NTFS_BUG_CHECK_RESTRSUP (0x001e0000)
+#define NTFS_BUG_CHECK_SECURSUP (0x001f0000)
+#define NTFS_BUG_CHECK_SEINFO (0x00200000)
+#define NTFS_BUG_CHECK_SHUTDOWN (0x00210000)
+#define NTFS_BUG_CHECK_STRUCSUP (0x00220000)
+#define NTFS_BUG_CHECK_VERFYSUP (0x00230000)
+#define NTFS_BUG_CHECK_VOLINFO (0x00240000)
+#define NTFS_BUG_CHECK_WORKQUE (0x00250000)
+#define NTFS_BUG_CHECK_WRITE (0x00260000)
+
+#define NtfsBugCheck(A,B,C) { KeBugCheckEx(NTFS_FILE_SYSTEM, BugCheckFileId | __LINE__, A, B, C ); }
+
+#endif // _NODETYPE_
+
diff --git a/private/ntos/cntfs/ntfs.def b/private/ntos/cntfs/ntfs.def
new file mode 100644
index 000000000..fc5868907
--- /dev/null
+++ b/private/ntos/cntfs/ntfs.def
@@ -0,0 +1,16 @@
+LIBRARY NTFS.SYS
+
+DESCRIPTION 'Microsoft (R) NT File System Driver'
+
+EXPORTS
+ NtOfsRegisterCallBacks
+ NtfsReleaseFcb
+ NtfsMapStream
+ NtfsAcquireSharedFcb
+ NtfsAcquireExclusiveScb
+ NtfsRaiseStatus
+ NtfsCreateInternalStreamCommon
+ NtOfsSetLength
+ NtOfsQueryLength
+ NtOfsPutData
+
diff --git a/private/ntos/cntfs/ntfs.h b/private/ntos/cntfs/ntfs.h
new file mode 100644
index 000000000..f98b02376
--- /dev/null
+++ b/private/ntos/cntfs/ntfs.h
@@ -0,0 +1,2334 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Ntfs.h
+
+Current Version Numbers:
+
+ Major.Minor Version: 1.2
+
+Abstract:
+
+ This module defines the on-disk structure of the Ntfs file system.
+
+ An Ntfs volume consists of sectors of data allocated on a granularity
+ called a cluster. The cluster factor is the number of sectors per
+ cluster. Valid cluster factors are 1, 2, 4, 8, etc.
+
+ The Ntfs volume starts with a boot sector at LBN=0, and a duplicate
+ boot sector at LBN=(number of sectors on the partition div 2). So
+ a disk with N sectors start with two boot sectors as illustrated.
+
+ 0 ... N/2 ... N
+ +-----------+-------+------------+-------+------------+
+ |BootSector | ... | BootSector | ... | |
+ +-----------+-------+------------+-------+------------+
+
+ The boot sector gives you the standard Bios Parameter Block, and
+ tells you how many sectors are in the volume, and gives you the starting
+ LCNs of the master file table (mft) and the duplicate master file table
+ (mft2).
+
+ The master file table contains the file record segments for all of
+ the volume. The first 16 or so file record segments are reserved for
+ special files. Mft2 only mirrors the first three record segments.
+
+ 0 1 2 3 4 5 6 7 8 9 ...
+ +---+---+---+---+---+---+---+---+---+---+-----+
+ | M | M | L | V | A | R | B | B | B | Q | |
+ | f | f | o | o | t | o | i | o | a | u | |
+ | t | t | g | l | t | o | t | o | d | o | |
+ | | 2 | F | D | r | t | M | t | C | t | ... |
+ | | | i | a | D | D | a | | l | a | |
+ | | | l | s | e | i | p | | u | | |
+ | | | e | d | f | r | | | s | | |
+ +---+---+---+---+---+---+---+---+---+---+-----+
+
+
+ Each file record segment starts with a file record segment header, and
+ is followed by one or more attributes. Each attribute starts with an
+ attribute record header. The attribute record denotes the attribute type,
+ optional name, and value for the attribute. If the attribute is resident
+ the value is contained in the file record and immediately follows the
+ attribute record header. If the attribute is non-resident the value
+ value is off in some other sectors on the disk.
+
+ +---------+-----------------+-------+
+ | File | Attrib : Name | |
+ | Record | Record : and/or | ... |
+ | Segment | Header : Attrib | |
+ | Header | : Data | |
+ +---------+-----------------+-------+
+
+ Now if we run out of space for storing attributes in the file record
+ segment we allocate additional file record segments and insert in the
+ first (or base) file record segment an attribute called the Attribute
+ List. The attribute list indicates for every attribute associated
+ with the file where the attribute can be found. This includes
+ those in the base file record.
+
+ The value part of the attribute we're calling the attribute list is
+ a list of sorted attribute list entries. Though illustrated
+ here as resident the attribute list can be nonresident.
+
+ +---------+---------------------------+-----------------+-------+
+ | File | Attrib : Attrib : | Attrib : Name | |
+ | Record | Record : List : ... | Record : and/or | ... |
+ | Segment | Header : Entry : | Header : Attrib | |
+ | Header | : : | : Data | |
+ +---------+---------------------------+-----------------+-------+
+
+ |
+ V
+
+ +---------+-----------------+-------+
+ | File | Attrib : Name | |
+ | Record | Record : and/or | ... |
+ | Segment | Header : Attrib | |
+ | Header | : Data | |
+ +---------+-----------------+-------+
+
+ This file defines all of the above structures and also lays out the
+ structures for some predefined attributes values (e.g., standard
+ information, etc).
+
+ Attributes are stored in ascending order in the file record and
+ attribute list. The sorting is done by first sorting according to
+ attribute type code, then attribute name, and lastly attribute value.
+ NTFS guarantees that if two attributes of the same type code and
+ name exist on a file then they must have different values, and the
+ values must be resident.
+
+ The indexing attributes are the last interesting attribute. The data
+ component of an index root attribute must always be resident and contains
+ an index header followed by a list of index list entries.
+
+ +--------+------------------------+
+ | Attrib | : Index : |
+ | Record | Index : List : ... |
+ | Header | Header : Entry : |
+ | | : : |
+ +--------+------------------------+
+
+ Each index list entry contains the key for the index and a reference
+ to the file record segment for the index. If ever we need to spill
+ out of the file record segment we allocate an additional cluster from
+ the disk (not file record segments). The storage for the additional
+ clusters is the data part of an index allocation attribute. So what
+ we wind up with is an index allocation attribute (non-resident) consisting
+ of Index Allocation Buffers which are referenced by the b-tree used in in
+ the index.
+
+ +--------+------------------------+-----+---------------------+
+ | Attrib | Index : Index : | | Attrib : Index |
+ | Record | List : List : ... | ... | Record : Allocation |
+ | Header | Header : Entry : | | Header : |
+ | | : : | | : |
+ +--------+------------------------+-----+---------------------+
+
+ |
+ | (VCN within index allocation)
+ V
+
+ +------------+------------------------------+
+ | Index | : Index : Index : |
+ | Allocation | Index : List : List : ... |
+ | Buffer | Header: Entry : Entry : |
+ | | : : : |
+ +------------+------------------------------+
+
+ Resident attributes are straight forward. Non-resident attributes require
+ a little more work. If the attribute is non-resident then following
+ the attribute record header is a list of retrieval information giving a
+ VCN to LCN mapping for the attribute. In the figure above the
+ Index allocation attribute is a a non-resident attribute
+
+ +---------+----------------------+-------+
+ | File | Attrib : Retrieval | |
+ | Record | Record : Information | ... |
+ | Segment | Header : | |
+ | Header | : | |
+ +---------+----------------------+-------+
+
+ If the retrieval information does not fit in the base file segment then
+ it can be stored in an external file record segment all by itself, and
+ if in the still doesn't fit in one external file record segment then
+ there is a provision in the attribute list to contain multiple
+ entries for an attribute that needs additional retrieval information.
+
+Author:
+
+ Brian Andrew [BrianAn] 21-May-1991
+ David Goebel [DavidGoe]
+ Gary Kimura [GaryKi]
+ Tom Miller [TomM]
+
+Revision History:
+
+
+IMPORTANT NOTE:
+
+ The NTFS on-disk structure must guarantee natural alignment of all
+ arithmetic quantities on disk up to and including quad-word (64-bit)
+ numbers. Therefore, all attribute records are quad-word aligned, etc.
+
+--*/
+
+#ifndef _NTFS_
+#define _NTFS_
+
+#pragma pack(4)
+
+
+//
+// The fundamental unit of allocation on an Ntfs volume is the
+// cluster. Format guarantees that the cluster size is an integral
+// power of two times the physical sector size of the device. Ntfs
+// reserves 64-bits to describe a cluster, in order to support
+// large disks. The LCN represents a physical cluster number on
+// the disk, and the VCN represents a virtual cluster number within
+// an attribute.
+//
+
+typedef LONGLONG LCN;
+typedef LCN *PLCN;
+
+typedef LONGLONG VCN;
+typedef VCN *PVCN;
+
+typedef LONGLONG LBO;
+typedef LBO *PLBO;
+
+typedef LONGLONG VBO;
+typedef VBO *PVBO;
+
+//
+// Temporary definitions ****
+//
+
+typedef ULONG COLLATION_RULE;
+typedef ULONG DISPLAY_RULE;
+
+//
+// The compression chunk size is constant for now, at 4KB.
+//
+
+#define NTFS_CHUNK_SIZE (0x1000)
+#define NTFS_CHUNK_SHIFT (12)
+
+//
+// This number is actually the log of the number of clusters per compression
+// unit to be stored in a nonresident attribute record header.
+//
+
+#define NTFS_CLUSTERS_PER_COMPRESSION (4)
+
+//
+// Collation Rules
+//
+
+//
+// For binary collation, values are collated by a binary compare of
+// their bytes, with the first byte being most significant.
+//
+
+#define COLLATION_BINARY (0)
+
+//
+// For collation of Ntfs file names, file names are collated as
+// Unicode strings. See below.
+//
+
+#define COLLATION_FILE_NAME (1)
+
+//
+// For collation of Unicode strings, the strings are collated by
+// their binary Unicode value, with the exception that for
+// characters which may be upcased, the lower case value for that
+// character collates immediately after the upcased value.
+//
+
+#define COLLATION_UNICODE_STRING (2)
+
+//
+// Total number of collation rules
+//
+
+#define COLLATION_NUMBER_RULES (3)
+
+//
+// The following macros are used to set and query with respect to
+// the update sequence arrays.
+//
+
+#define UpdateSequenceStructureSize(MSH) ( \
+ ((((PMULTI_SECTOR_HEADER)(MSH))->UpdateSequenceArraySize-1) * \
+ SEQUENCE_NUMBER_STRIDE) \
+)
+
+#define UpdateSequenceArraySize(STRUCT_SIZE) ( \
+ ((STRUCT_SIZE) / SEQUENCE_NUMBER_STRIDE + 1) \
+)
+
+
+//
+// The MFT Segment Reference is an address in the MFT tagged with
+// a circularly reused sequence number set at the time that the MFT
+// Segment Reference was valid. Note that this format limits the
+// size of the Master File Table to 2**48 segments. So, for
+// example, with a 1KB segment size the maximum size of the master
+// file would be 2**58 bytes, or 2**28 gigabytes.
+//
+
+typedef struct _MFT_SEGMENT_REFERENCE {
+
+ //
+ // First a 48 bit segment number.
+ //
+
+ ULONG SegmentNumberLowPart; // offset = 0x000
+ USHORT SegmentNumberHighPart; // offset = 0x004
+
+ //
+ // Now a 16 bit nonzero sequence number. A value of 0 is
+ // reserved to allow the possibility of a routine accepting
+ // 0 as a sign that the sequence number check should be
+ // repressed.
+ //
+
+ USHORT SequenceNumber; // offset = 0x006
+
+} MFT_SEGMENT_REFERENCE, *PMFT_SEGMENT_REFERENCE; // sizeof = 0x008
+
+//
+// A file reference in NTFS is simply the MFT Segment Reference of
+// the Base file record.
+//
+
+typedef MFT_SEGMENT_REFERENCE FILE_REFERENCE, *PFILE_REFERENCE;
+
+//
+// While the format allows 48 bits worth of segment number, the current
+// implementation restricts this to 32 bits. Using NtfsUnsafeSegmentNumber
+// results in a performance win. When the implementation changes, the
+// unsafe segment numbers must be cleaned up. NtfsFullSegmentNumber is
+// used in a few spots to guarantee integrity of the disk.
+//
+
+#define NtfsSegmentNumber(fr) NtfsUnsafeSegmentNumber( fr )
+#define NtfsFullSegmentNumber(fr) ( (*(ULONGLONG UNALIGNED *)(fr)) & 0xFFFFFFFFFFFF )
+#define NtfsUnsafeSegmentNumber(fr) ((fr)->SegmentNumberLowPart)
+
+#define NtfsSetSegmentNumber(fr,high,low) \
+ ((fr)->SegmentNumberHighPart = (high), (fr)->SegmentNumberLowPart = (low))
+
+#define NtfsEqualMftRef(X,Y) ( NtfsSegmentNumber( X ) == NtfsSegmentNumber( Y ) )
+
+#define NtfsLtrMftRef(X,Y) ( NtfsSegmentNumber( X ) < NtfsSegmentNumber( Y ) )
+
+#define NtfsGtrMftRef(X,Y) ( NtfsSegmentNumber( X ) > NtfsSegmentNumber( Y ) ) \
+
+#define NtfsLeqMftRef(X,Y) ( NtfsSegmentNumber( X ) <= NtfsSegmentNumber( Y ) )
+
+#define NtfsGeqMftRef(X,Y) ( NtfsSegmentNumber( X ) >= NtfsSegmentNumber( Y ) )
+
+//
+// System File Numbers. The following file numbers are a fixed
+// part of the volume number. For the system files, the
+// SequenceNumber is always equal to the File Number. So to form a
+// File Reference for a given System File, set LowPart and
+// SequenceNumber to the File Number, and set HighPart to 0. Any
+// unused file numbers prior to FIRST_USER_FILE_NUMBER should not
+// be used. They are reserved to allow the potential for easy
+// upgrade of existing volumes from future versions of the file
+// system.
+//
+// Each definition below is followed by a comment containing
+// the file name for the file:
+//
+// Number Name
+// ------ ----
+
+#define MASTER_FILE_TABLE_NUMBER (0) // $Mft
+
+#define MASTER_FILE_TABLE2_NUMBER (1) // $MftMirr
+
+#define LOG_FILE_NUMBER (2) // $LogFile
+
+#define VOLUME_DASD_NUMBER (3) // $Volume
+
+#define ATTRIBUTE_DEF_TABLE_NUMBER (4) // $AttrDef
+
+#define ROOT_FILE_NAME_INDEX_NUMBER (5) // .
+
+#define BIT_MAP_FILE_NUMBER (6) // $BitMap
+
+#define BOOT_FILE_NUMBER (7) // $Boot
+
+#define BAD_CLUSTER_FILE_NUMBER (8) // $BadClus
+
+#define QUOTA_TABLE_NUMBER (9) // $Quota
+
+#define UPCASE_TABLE_NUMBER (10) // $UpCase
+
+#define CAIRO_NUMBER (11) // $Cairo
+
+#define FIRST_USER_FILE_NUMBER (16)
+
+//
+// The number of bits to extend the Mft and bitmap. We round these up to a
+// cluster boundary for a large cluster volume
+//
+
+#define BITMAP_EXTEND_GRANULARITY (64)
+#define MFT_HOLE_GRANULARITY (32)
+#define MFT_EXTEND_GRANULARITY (16)
+
+//
+// The shift values for determining the threshold for the Mft defragging.
+//
+
+#define MFT_DEFRAG_UPPER_THRESHOLD (3) // Defrag if 1/8 of free space
+#define MFT_DEFRAG_LOWER_THRESHOLD (4) // Stop at 1/16 of free space
+
+
+//
+// Attribute Type Code. Attribute Types also have a Unicode Name,
+// and the correspondence between the Unicode Name and the
+// Attribute Type Code is stored in the Attribute Definition File.
+//
+
+typedef ULONG ATTRIBUTE_TYPE_CODE;
+typedef ATTRIBUTE_TYPE_CODE *PATTRIBUTE_TYPE_CODE;
+
+//
+// System-defined Attribute Type Codes. For the System-defined
+// attributes, the Unicode Name is exactly equal to the name of the
+// following symbols. For this reason, all of the system-defined
+// attribute names start with "$", to always distinguish them when
+// attribute names are listed, and to reserve a namespace for
+// attributes defined in the future. I.e., a User-Defined
+// attribute name will never collide with a current or future
+// system-defined attribute name if it does not start with "$".
+// User attribute numbers should not start until
+// $FIRST_USER_DEFINED_ATTRIBUTE, too allow the potential for
+// upgrading existing volumes with new user-defined attributes in
+// future versions of NTFS. The tagged attribute list is
+// terminated with a lone-standing 0 ($END) - the rest of the
+// attribute record does not exist.
+//
+// The type code value of 0 is reserved for convenience of the
+// implementation.
+//
+
+#define $UNUSED (0X0)
+
+#define $STANDARD_INFORMATION (0x10)
+#define $ATTRIBUTE_LIST (0x20)
+#define $FILE_NAME (0x30)
+#define $OBJECT_ID (0x40)
+#define $SECURITY_DESCRIPTOR (0x50)
+#define $VOLUME_NAME (0x60)
+#define $VOLUME_INFORMATION (0x70)
+#define $DATA (0x80)
+#define $INDEX_ROOT (0x90)
+#define $INDEX_ALLOCATION (0xA0)
+#define $BITMAP (0xB0)
+#define $SYMBOLIC_LINK (0xC0)
+#define $EA_INFORMATION (0xD0)
+#define $EA (0xE0)
+#ifdef _CAIRO_
+#define $PROPERTY_SET (0xF0)
+#endif // _CAIRO_
+#define $FIRST_USER_DEFINED_ATTRIBUTE (0x100)
+#define $END (0xFFFFFFFF)
+
+
+//
+// The boot sector is duplicated on the partition. The first copy
+// is on the first physical sector (LBN == 0) of the partition, and
+// the second copy is at <number sectors on partition> / 2. If the
+// first copy can not be read when trying to mount the disk, the
+// second copy may be read and has the identical contents. Format
+// must figure out which cluster the second boot record belongs in,
+// and it must zero all of the other sectors that happen to be in
+// the same cluster. The boot file minimally contains with two
+// clusters, which are the two clusters which contain the copies of
+// the boot record. If format knows that some system likes to put
+// code somewhere, then it should also align this requirement to
+// even clusters, and add that to the boot file as well.
+//
+// Part of the sector contains a BIOS Parameter Block. The BIOS in
+// the sector is packed (i.e., unaligned) so we'll supply an
+// unpacking macro to translate a packed BIOS into its unpacked
+// equivalent. The unpacked BIOS structure is already defined in
+// ntioapi.h so we only need to define the packed BIOS.
+//
+
+//
+// Define the Packed and Unpacked BIOS Parameter Block
+//
+
+typedef struct _PACKED_BIOS_PARAMETER_BLOCK {
+
+ UCHAR BytesPerSector[2]; // offset = 0x000
+ UCHAR SectorsPerCluster[1]; // offset = 0x002
+ UCHAR ReservedSectors[2]; // offset = 0x003 (zero)
+ UCHAR Fats[1]; // offset = 0x005 (zero)
+ UCHAR RootEntries[2]; // offset = 0x006 (zero)
+ UCHAR Sectors[2]; // offset = 0x008 (zero)
+ UCHAR Media[1]; // offset = 0x00A
+ UCHAR SectorsPerFat[2]; // offset = 0x00B (zero)
+ UCHAR SectorsPerTrack[2]; // offset = 0x00D
+ UCHAR Heads[2]; // offset = 0x00F
+ UCHAR HiddenSectors[4]; // offset = 0x011 (zero)
+ UCHAR LargeSectors[4]; // offset = 0x015 (zero)
+
+} PACKED_BIOS_PARAMETER_BLOCK; // sizeof = 0x019
+
+typedef PACKED_BIOS_PARAMETER_BLOCK *PPACKED_BIOS_PARAMETER_BLOCK;
+
+typedef struct BIOS_PARAMETER_BLOCK {
+
+ USHORT BytesPerSector;
+ UCHAR SectorsPerCluster;
+ USHORT ReservedSectors;
+ UCHAR Fats;
+ USHORT RootEntries;
+ USHORT Sectors;
+ UCHAR Media;
+ USHORT SectorsPerFat;
+ USHORT SectorsPerTrack;
+ USHORT Heads;
+ ULONG HiddenSectors;
+ ULONG LargeSectors;
+
+} BIOS_PARAMETER_BLOCK;
+
+typedef BIOS_PARAMETER_BLOCK *PBIOS_PARAMETER_BLOCK;
+
+//
+// This macro takes a Packed BIOS and fills in its Unpacked
+// equivalent
+//
+
+#define NtfsUnpackBios(Bios,Pbios) { \
+ CopyUchar2(&((Bios)->BytesPerSector), &(Pbios)->BytesPerSector ); \
+ CopyUchar1(&((Bios)->SectorsPerCluster), &(Pbios)->SectorsPerCluster); \
+ CopyUchar2(&((Bios)->ReservedSectors), &(Pbios)->ReservedSectors ); \
+ CopyUchar1(&((Bios)->Fats), &(Pbios)->Fats ); \
+ CopyUchar2(&((Bios)->RootEntries), &(Pbios)->RootEntries ); \
+ CopyUchar2(&((Bios)->Sectors), &(Pbios)->Sectors ); \
+ CopyUchar1(&((Bios)->Media), &(Pbios)->Media ); \
+ CopyUchar2(&((Bios)->SectorsPerFat), &(Pbios)->SectorsPerFat ); \
+ CopyUchar2(&((Bios)->SectorsPerTrack), &(Pbios)->SectorsPerTrack ); \
+ CopyUchar2(&((Bios)->Heads), &(Pbios)->Heads ); \
+ CopyUchar4(&((Bios)->HiddenSectors), &(Pbios)->HiddenSectors ); \
+ CopyUchar4(&((Bios)->LargeSectors), &(Pbios)->LargeSectors ); \
+}
+
+//
+// Define the boot sector. Note that MFT2 is exactly three file
+// record segments long, and it mirrors the first three file record
+// segments from the MFT, which are MFT, MFT2 and the Log File.
+//
+// The Oem field contains the ASCII characters "NTFS ".
+//
+// The Checksum field is a simple additive checksum of all of the
+// ULONGs which precede the Checksum ULONG. The rest of the sector
+// is not included in this Checksum.
+//
+
+typedef struct _PACKED_BOOT_SECTOR {
+
+ UCHAR Jump[3]; // offset = 0x000
+ UCHAR Oem[8]; // offset = 0x003
+ PACKED_BIOS_PARAMETER_BLOCK PackedBpb; // offset = 0x00B
+ UCHAR Unused[4]; // offset = 0x024
+ LONGLONG NumberSectors; // offset = 0x028
+ LCN MftStartLcn; // offset = 0x030
+ LCN Mft2StartLcn; // offset = 0x038
+ CHAR ClustersPerFileRecordSegment; // offset = 0x040
+ UCHAR Reserved0[3];
+ CHAR DefaultClustersPerIndexAllocationBuffer; // offset = 0x044
+ UCHAR Reserved1[3];
+ LONGLONG SerialNumber; // offset = 0x048
+ ULONG Checksum; // offset = 0x050
+ UCHAR BootStrap[0x200-0x044]; // offset = 0x054
+
+} PACKED_BOOT_SECTOR; // sizeof = 0x200
+
+typedef PACKED_BOOT_SECTOR *PPACKED_BOOT_SECTOR;
+
+
+//
+// File Record Segment. This is the header that begins every File
+// Record Segment in the Master File Table.
+//
+
+typedef struct _FILE_RECORD_SEGMENT_HEADER {
+
+ //
+ // Multi-Sector Header as defined by the Cache Manager. This
+ // structure will always contain the signature "FILE" and a
+ // description of the location and size of the Update Sequence
+ // Array.
+ //
+
+ MULTI_SECTOR_HEADER MultiSectorHeader; // offset = 0x000
+
+ //
+ // Log File Sequence Number of last logged update to this File
+ // Record Segment.
+ //
+
+ LSN Lsn; // offset = 0x008
+
+ //
+ // Sequence Number. This is incremented each time that a File
+ // Record segment is freed, and 0 is not used. The
+ // SequenceNumber field of a File Reference must match the
+ // contents of this field, or else the File Reference is
+ // incorrect (presumably stale).
+ //
+
+ USHORT SequenceNumber; // offset = 0x010
+
+ //
+ // This is the count of the number of references which exist
+ // for this segment, from an INDEX_xxx attribute. In File
+ // Records Segments other than the Base File Record Segment,
+ // this field is 0.
+ //
+
+ USHORT ReferenceCount; // offset = 0x012
+
+ //
+ // Offset to the first Attribute record in bytes.
+ //
+
+ USHORT FirstAttributeOffset; // offset = 0x014
+
+ //
+ // FILE_xxx flags.
+ //
+
+ USHORT Flags; // offset = 0x016
+
+ //
+ // First free byte available for attribute storage, from start
+ // of this header. This value should always be aligned to a
+ // quad-word boundary, since attributes are quad-word aligned.
+ //
+
+ ULONG FirstFreeByte; // offset = x0018
+
+ //
+ // Total bytes available in this file record segment, from the
+ // start of this header. This is essentially the file record
+ // segment size.
+ //
+
+ ULONG BytesAvailable; // offset = 0x01C
+
+ //
+ // This is a File Reference to the Base file record segment for
+ // this file. If this is the Base, then the value of this
+ // field is all 0's.
+ //
+
+ FILE_REFERENCE BaseFileRecordSegment; // offset = 0x020
+
+ //
+ // This is the attribute instance number to be used when
+ // creating an attribute. It is zeroed when the base file
+ // record is created, and captured for each new attribute as it
+ // is created and incremented afterwards for the next
+ // attribute. Instance numbering must also occur for the
+ // initial attributes. Zero is a valid attribute instance
+ // number, and typically used for standard information.
+ //
+
+ USHORT NextAttributeInstance; // offset = 0x028
+
+ //
+ // Update Sequence Array to protect multi-sector transfers of
+ // the File Record Segment. Accesses to already initialized
+ // File Record Segments should go through the offset above, for
+ // upwards compatibility.
+ //
+
+ UPDATE_SEQUENCE_ARRAY UpdateArrayForCreateOnly; // offset = 0x02A
+
+} FILE_RECORD_SEGMENT_HEADER;
+typedef FILE_RECORD_SEGMENT_HEADER *PFILE_RECORD_SEGMENT_HEADER;
+
+//
+// FILE_xxx flags.
+//
+
+#define FILE_RECORD_SEGMENT_IN_USE (0x0001)
+#define FILE_FILE_NAME_INDEX_PRESENT (0x0002)
+
+//
+// Define a macro to determine the maximum space available for a
+// single attribute. For example, this is required when a
+// nonresident attribute has to split into multiple file records -
+// we need to know how much we can squeeze into a single file
+// record. If this macro has any inaccurracy, it must be in the
+// direction of returning a slightly smaller number than actually
+// required.
+//
+// ULONG
+// NtfsMaximumAttributeSize (
+// IN ULONG FileRecordSegmentSize
+// );
+//
+
+#define NtfsMaximumAttributeSize(FRSS) ( \
+ (FRSS) - QuadAlign(sizeof(FILE_RECORD_SEGMENT_HEADER)) - \
+ QuadAlign((((FRSS) / SEQUENCE_NUMBER_STRIDE) * sizeof(UPDATE_SEQUENCE_NUMBER))) - \
+ QuadAlign(sizeof(ATTRIBUTE_TYPE_CODE)) \
+)
+
+
+//
+// Attribute Record. Logically an attribute has a type, an
+// optional name, and a value, however the storage details make it
+// a little more complicated. For starters, an attribute's value
+// may either be resident in the file record segment itself, on
+// nonresident in a separate data stream. If it is nonresident, it
+// may actually exist multiple times in multiple file record
+// segments to describe different ranges of VCNs.
+//
+// Attribute Records are always aligned on a quad word (64-bit)
+// boundary.
+//
+
+typedef struct _ATTRIBUTE_RECORD_HEADER {
+
+ //
+ // Attribute Type Code.
+ //
+
+ ATTRIBUTE_TYPE_CODE TypeCode; // offset = 0x000
+
+ //
+ // Length of this Attribute Record in bytes. The length is
+ // always rounded to a quad word boundary, if necessary. Also
+ // the length only reflects the size necessary to store the
+ // given record variant.
+ //
+
+ ULONG RecordLength; // offset = 0x004
+
+ //
+ // Attribute Form Code (see below)
+ //
+
+ UCHAR FormCode; // offset = 0x008
+
+ //
+ // Length of the optional attribute name in characters, or 0 if
+ // there is none.
+ //
+
+ UCHAR NameLength; // offset = 0x009
+
+ //
+ // Offset to the attribute name from start of attribute record,
+ // in bytes, if it exists. This field is undefined if
+ // NameLength is 0.
+ //
+
+ USHORT NameOffset; // offset = 0x00A
+
+ //
+ // ATTRIBUTE_xxx flags.
+ //
+
+ USHORT Flags; // offset = 0x00C
+
+ //
+ // The file-record-unique attribute instance number for this
+ // attribute.
+ //
+
+ USHORT Instance; // offset = 0x00E
+
+ //
+ // The following union handles the cases distinguished by the
+ // Form Code.
+ //
+
+ union {
+
+ //
+ // Resident Form. Attribute resides in file record segment.
+ //
+
+ struct {
+
+ //
+ // Length of attribute value in bytes.
+ //
+
+ ULONG ValueLength; // offset = 0x010
+
+ //
+ // Offset to value from start of attribute record, in
+ // bytes.
+ //
+
+ USHORT ValueOffset; // offset = 0x014
+
+ //
+ // RESIDENT_FORM_xxx Flags.
+ //
+
+ UCHAR ResidentFlags; // offset = 0x016
+
+ //
+ // Reserved.
+ //
+
+ UCHAR Reserved; // offset = 0x017
+
+ } Resident;
+
+ //
+ // Nonresident Form. Attribute resides in separate stream.
+ //
+
+ struct {
+
+ //
+ // Lowest VCN covered by this attribute record.
+ //
+
+ VCN LowestVcn; // offset = 0x010
+
+ //
+ // Highest VCN covered by this attribute record.
+ //
+
+ VCN HighestVcn; // offset = 0x018
+
+ //
+ // Offset to the Mapping Pairs Array (defined below),
+ // in bytes, from the start of the attribute record.
+ //
+
+ USHORT MappingPairsOffset; // offset = 0x020
+
+ //
+ // Unit of Compression size for this stream, expressed
+ // as a log of the cluster size.
+ //
+ // 0 means file is not compressed
+ // 1, 2, 3, and 4 are potentially legal values if the
+ // stream is compressed, however the implementation
+ // may only choose to use 4, or possibly 3. Note
+ // that 4 means cluster size time 16. If convenient
+ // the implementation may wish to accept a
+ // reasonable range of legal values here (1-5?),
+ // even if the implementation only generates
+ // a smaller set of values itself.
+ //
+
+ UCHAR CompressionUnit; // offset = 0x022
+
+ //
+ // Reserved to get to quad word boundary.
+ //
+
+ UCHAR Reserved[5]; // offset = 0x023
+
+ //
+ // Allocated Length of the file in bytes. This is
+ // obviously an even multiple of the cluster size.
+ // (Not present if LowestVcn != 0.)
+ //
+
+ LONGLONG AllocatedLength; // offset = 0x028
+
+ //
+ // File Size in bytes (highest byte which may be read +
+ // 1). (Not present if LowestVcn != 0.)
+ //
+
+ LONGLONG FileSize; // offset = 0x030
+
+ //
+ // Valid Data Length (highest initialized byte + 1).
+ // This field must also be rounded to a cluster
+ // boundary, and the data must always be initialized to
+ // a cluster boundary. (Not present if LowestVcn != 0.)
+ //
+
+ LONGLONG ValidDataLength; // offset = 0x038
+
+ //
+ // Totally allocated. This field is only present for the first
+ // file record of a compressed stream. It represents the sum of
+ // the allocated clusters for a file.
+ //
+
+ LONGLONG TotalAllocated; // offset = 0x040
+
+ //
+ //
+ // Mapping Pairs Array, starting at the offset stored
+ // above.
+ //
+ // The Mapping Pairs Array is stored in a compressed
+ // form, and assumes that this information is
+ // decompressed and cached by the system. The reason
+ // for compressing this information is clear, it is
+ // done in the hopes that all of the retrieval
+ // information always fits in a single file record
+ // segment.
+ //
+ // Logically, the MappingPairs Array stores a series of
+ // NextVcn/CurrentLcn pairs. So, for example, given
+ // that we know the first Vcn (from LowestVcn above),
+ // the first Mapping Pair tells us what the next Vcn is
+ // (for the next Mapping Pair), and what Lcn the
+ // current Vcn is mapped to, or 0 if the Current Vcn is
+ // not allocated. (This is exactly the FsRtl MCB
+ // structure).
+ //
+ // For example, if a file has a single run of 8
+ // clusters, starting at Lcn 128, and the file starts
+ // at LowestVcn=0, then the Mapping Pairs array has
+ // just one entry, which is:
+ //
+ // NextVcn = 8
+ // CurrentLcn = 128
+ //
+ // The compression is implemented with the following
+ // algorithm. Assume that you initialize two "working"
+ // variables as follows:
+ //
+ // NextVcn = LowestVcn (from above)
+ // CurrentLcn = 0
+ //
+ // The MappingPairs array is byte stream, which simply
+ // store the changes to the working variables above,
+ // when processed sequentially. The byte stream is to
+ // be interpreted as a zero-terminated stream of
+ // triples, as follows:
+ //
+ // count byte = v + (l * 16)
+ //
+ // where v = number of changed low-order Vcn bytes
+ // l = number of changed low-order Lcn bytes
+ //
+ // v Vcn change bytes
+ // l Lcn change bytes
+ //
+ // The byte stream terminates when a count byte of 0 is
+ // encountered.
+ //
+ // The decompression algorithm goes as follows,
+ // assuming that Attribute is a pointer to the
+ // attribute record.
+ //
+ // 1. Initialize:
+ // NextVcn = Attribute->LowestVcn;
+ // CurrentLcn = 0;
+ //
+ // 2. Initialize byte stream pointer to: (PCHAR)Attribute +
+ // Attribute->AttributeForm->Nonresident->MappingPairsOffset
+ //
+ // 3. CurrentVcn = NextVcn;
+ //
+ // 4. Read next byte from stream. If it is 0, then
+ // break, else extract v and l (see above).
+ //
+ // 5. Interpret the next v bytes as a signed quantity,
+ // with the low-order byte coming first. Unpack it
+ // sign-extended into 64 bits and add it to NextVcn.
+ // (It can really only be positive, but the Lcn
+ // change can be positive or negative.)
+ //
+ // 6. Interpret the next l bytes as a signed quantity,
+ // with the low-order byte coming first. Unpack it
+ // sign-extended into 64 bits and add it to
+ // CurrentLcn. Remember, if this produces a
+ // CurrentLcn of 0, then the Vcns from the
+ // CurrentVcn to NextVcn-1 are unallocated.
+ //
+ // 7. Update cached mapping information from
+ // CurrentVcn, NextVcn and CurrentLcn.
+ //
+ // 8. Loop back to 3.
+ //
+ // The compression algorithm should now be obvious, as
+ // it is the reverse of the above. The compression and
+ // decompression algorithms will be available as common
+ // RTL routines, available to NTFS and file utilities.
+ //
+ // In defense of this algorithm, not only does it
+ // provide compression of the on-disk storage
+ // requirements, but it results in a single
+ // representation, independent of disk size and file
+ // size. Contrast this with solutions which are in use
+ // which define multiple sizes for virtual and logical
+ // cluster sizes, depending on the size of the disk,
+ // etc. For example, two byte cluster numbers might
+ // suffice for a floppy, while four bytes would be
+ // required for most hard disks today, and five or six
+ // bytes are required after a certain number of
+ // gigabytes, etc. This eventually results in more
+ // complex code than above (because of the cases) and
+ // worse yet - untested cases. So, more important than
+ // the compression, the above algorithm provides one
+ // case which efficiently handles any size disk.
+ //
+
+ } Nonresident;
+
+ } Form;
+
+} ATTRIBUTE_RECORD_HEADER;
+typedef ATTRIBUTE_RECORD_HEADER *PATTRIBUTE_RECORD_HEADER;
+
+//
+// Attribute Form Codes
+//
+
+#define RESIDENT_FORM (0x00)
+#define NONRESIDENT_FORM (0x01)
+
+//
+// Define Attribute Flags
+//
+
+//
+// The first range of flag bits is reserved for
+// storing the compression method. This constant
+// defines the mask of the bits reserved for
+// compression method. It is also the first
+// illegal value, since we increment it to calculate
+// the code to pass to the Rtl routines. Thus it is
+// impossible for us to store COMPRESSION_FORMAT_DEFAULT.
+//
+
+#define ATTRIBUTE_FLAG_COMPRESSION_MASK (0x00FF)
+#define ATTRIBUTE_FLAG_SPARSE (0x8000)
+
+//
+// RESIDENT_FORM_xxx flags
+//
+
+//
+// This attribute is indexed.
+//
+
+#define RESIDENT_FORM_INDEXED (0x01)
+
+//
+// The maximum attribute name length is 255 (in chars)
+//
+
+#define NTFS_MAX_ATTR_NAME_LEN (255)
+
+//
+// Define macros for the size of resident and nonresident headers.
+//
+
+#define SIZEOF_RESIDENT_ATTRIBUTE_HEADER ( \
+ FIELD_OFFSET(ATTRIBUTE_RECORD_HEADER,Form.Resident.Reserved)+1 \
+)
+
+#define SIZEOF_FULL_NONRES_ATTR_HEADER ( \
+ sizeof(ATTRIBUTE_RECORD_HEADER) \
+)
+
+#define SIZEOF_PARTIAL_NONRES_ATTR_HEADER ( \
+ FIELD_OFFSET(ATTRIBUTE_RECORD_HEADER,Form.Nonresident.TotalAllocated) \
+)
+
+
+//
+// Standard Information Attribute. This attribute is present in
+// every base file record, and must be resident.
+//
+
+typedef struct _STANDARD_INFORMATION {
+
+ //
+ // File creation time.
+ //
+
+ LONGLONG CreationTime; // offset = 0x000
+
+ //
+ // Last time the DATA attribute was modified.
+ //
+
+ LONGLONG LastModificationTime; // offset = 0x008
+
+ //
+ // Last time any attribute was modified.
+ //
+
+ LONGLONG LastChangeTime; // offset = 0x010
+
+ //
+ // Last time the file was accessed. This field may not always
+ // be updated (write-protected media), and even when it is
+ // updated, it may only be updated if the time would change by
+ // a certain delta. It is meant to tell someone approximately
+ // when the file was last accessed, for purposes of possible
+ // file migration.
+ //
+
+ LONGLONG LastAccessTime; // offset = 0x018
+
+ //
+ // File attributes. The first byte is the standard "Fat"
+ // flags for this file.
+ //
+
+ ULONG FileAttributes; // offset = 0x020
+
+ //
+ // Maximum file versions allowed for this file. If this field
+ // is 0, then versioning is not enabled for this file. If
+ // there are multiple files with the same version, then the
+ // value of Maximum file versions in the file with the highest
+ // version is the correct one.
+ //
+
+ ULONG MaximumVersions; // offset = 0x024
+
+ //
+ // Version number for this file.
+ //
+
+ ULONG VersionNumber; // offset = 0x028
+
+#ifdef _CAIRO_
+
+ //
+ // Class Id from the bidirectional Class Id index
+ //
+
+ ULONG ClassId; // offset = 0x02c
+
+ //
+ // Id for file owner, from bidir security index
+ //
+
+ ULONG OwnerId; // offset = 0x030
+
+ //
+ // SecurityId for the file - translates via bidir index to
+ // granted access Acl.
+ //
+
+ ULONG SecurityId; // offset = 0x034
+
+ //
+ // Current amount of quota that has been charged for all the
+ // streams of this file. Changed in same transaction with the
+ // quota file itself.
+ //
+
+ ULONGLONG QuotaCharged; // offset = 0x038
+
+ //
+ // Update sequence number for this file.
+ //
+
+ ULONGLONG Usn; // offset = 0x040
+
+#else _CAIRO_
+
+ ULONG Reserved; // offset = 0x02c
+
+#endif _CAIRO_
+
+
+} STANDARD_INFORMATION; // sizeof = 0x048
+typedef STANDARD_INFORMATION *PSTANDARD_INFORMATION;
+
+//
+// Large Standard Information Attribute. We use this to find the
+// security ID field.
+//
+
+typedef struct LARGE_STANDARD_INFORMATION {
+
+ //
+ // File creation time.
+ //
+
+ LONGLONG CreationTime; // offset = 0x000
+
+ //
+ // Last time the DATA attribute was modified.
+ //
+
+ LONGLONG LastModificationTime; // offset = 0x008
+
+ //
+ // Last time any attribute was modified.
+ //
+
+ LONGLONG LastChangeTime; // offset = 0x010
+
+ //
+ // Last time the file was accessed. This field may not always
+ // be updated (write-protected media), and even when it is
+ // updated, it may only be updated if the time would change by
+ // a certain delta. It is meant to tell someone approximately
+ // when the file was last accessed, for purposes of possible
+ // file migration.
+ //
+
+ LONGLONG LastAccessTime; // offset = 0x018
+
+ //
+ // File attributes. The first byte is the standard "Fat"
+ // flags for this file.
+ //
+
+ ULONG FileAttributes; // offset = 0x020
+
+ //
+ // Maximum file versions allowed for this file. If this field
+ // is 0, then versioning is not enabled for this file. If
+ // there are multiple files with the same version, then the
+ // value of Maximum file versions in the file with the highest
+ // version is the correct one.
+ //
+
+ ULONG MaximumVersions; // offset = 0x024
+
+ //
+ // Version number for this file.
+ //
+
+ ULONG VersionNumber; // offset = 0x028
+
+ ULONG UnusedUlong;
+
+ //
+ // Id for file owner, from bidir security index
+ //
+
+ ULONG OwnerId; // offset = 0x030
+
+ //
+ // SecurityId for the file - translates via bidir index to
+ // granted access Acl.
+ //
+
+ ULONG SecurityId; // offset = 0x034
+
+} LARGE_STANDARD_INFORMATION;
+typedef LARGE_STANDARD_INFORMATION *PLARGE_STANDARD_INFORMATION;
+
+//
+// This was the size of standard information prior to NT4.0
+//
+
+#define SIZEOF_OLD_STANDARD_INFORMATION (0x30)
+
+//
+// Define the file attributes, starting with the Fat attributes.
+//
+
+#define FAT_DIRENT_ATTR_READ_ONLY (0x01)
+#define FAT_DIRENT_ATTR_HIDDEN (0x02)
+#define FAT_DIRENT_ATTR_SYSTEM (0x04)
+#define FAT_DIRENT_ATTR_VOLUME_ID (0x08)
+#define FAT_DIRENT_ATTR_ARCHIVE (0x20)
+#define FAT_DIRENT_ATTR_DEVICE (0x40)
+
+
+//
+// Attribute List. Because there is not a special header that goes
+// before the list of attribute list entries we do not need to
+// declare an attribute list header
+//
+
+//
+// The Attributes List attribute is an ordered-list of quad-word
+// aligned ATTRIBUTE_LIST_ENTRY records. It is ordered first by
+// Attribute Type Code, and then by Attribute Name (if present).
+// No two attributes may exist with the same type code, name and
+// LowestVcn. This also means that at most one occurrence of a
+// given Attribute Type Code without a name may exist.
+//
+// To binary search this attribute, it is first necessary to make a
+// quick pass through it and form a list of pointers, since the
+// optional name makes it variable-length.
+//
+
+typedef struct _ATTRIBUTE_LIST_ENTRY {
+
+ //
+ // Attribute Type Code, the first key on which this list is
+ // ordered.
+ //
+
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode; // offset = 0x000
+
+ //
+ // Size of this record in bytes, including the optional name
+ // appended to this structure.
+ //
+
+ USHORT RecordLength; // offset = 0x004
+
+ //
+ // Length of attribute name, if there is one. If a name exists
+ // (AttributeNameLength != 0), then it is a Unicode string of
+ // the specified number of characters immediately following
+ // this record. This is the second key on which this list is
+ // ordered.
+ //
+
+ UCHAR AttributeNameLength; // offset = 0x006
+
+ //
+ // Reserved to get to quad-word boundary
+ //
+
+ UCHAR AttributeNameOffset; // offset = 0x007
+
+ //
+ // Lowest Vcn for this attribute. This field is always zero
+ // unless the attribute requires multiple file record segments
+ // to describe all of its runs, and this is a reference to a
+ // segment other than the first one. The field says what the
+ // lowest Vcn is that is described by the referenced segment.
+ //
+
+ VCN LowestVcn; // offset = 0x008
+
+ //
+ // Reference to the MFT segment in which the attribute resides.
+ //
+
+ MFT_SEGMENT_REFERENCE SegmentReference; // offset = 0x010
+
+ //
+ // The file-record-unique attribute instance number for this
+ // attribute.
+ //
+
+ USHORT Instance; // offset = 0x018
+
+ //
+ // When creating an attribute list entry, start the name here.
+ // (When reading one, use the AttributeNameOffset field.)
+ //
+
+ WCHAR AttributeName[1]; // offset = 0x01A
+
+} ATTRIBUTE_LIST_ENTRY;
+typedef ATTRIBUTE_LIST_ENTRY *PATTRIBUTE_LIST_ENTRY;
+
+
+typedef struct _DUPLICATED_INFORMATION {
+
+ //
+ // File creation time.
+ //
+
+ LONGLONG CreationTime; // offset = 0x000
+
+ //
+ // Last time the DATA attribute was modified.
+ //
+
+ LONGLONG LastModificationTime; // offset = 0x008
+
+ //
+ // Last time any attribute was modified.
+ //
+
+ LONGLONG LastChangeTime; // offset = 0x010
+
+ //
+ // Last time the file was accessed. This field may not always
+ // be updated (write-protected media), and even when it is
+ // updated, it may only be updated if the time would change by
+ // a certain delta. It is meant to tell someone approximately
+ // when the file was last accessed, for purposes of possible
+ // file migration.
+ //
+
+ LONGLONG LastAccessTime; // offset = 0x018
+
+ //
+ // Allocated Length of the file in bytes. This is obviously
+ // an even multiple of the cluster size. (Not present if
+ // LowestVcn != 0.)
+ //
+
+ LONGLONG AllocatedLength; // offset = 0x020
+
+ //
+ // File Size in bytes (highest byte which may be read + 1).
+ // (Not present if LowestVcn != 0.)
+ //
+
+ LONGLONG FileSize; // offset = 0x028
+
+ //
+ // File attributes. The first byte is the standard "Fat"
+ // flags for this file.
+ //
+
+ ULONG FileAttributes; // offset = 0x030
+
+ //
+ // The size of buffer needed to pack these Ea's
+ //
+
+ USHORT PackedEaSize; // offset = 0x034
+
+ //
+ // Reserved for quad word alignment
+ //
+
+ USHORT Reserved; // offset = 0x036
+
+} DUPLICATED_INFORMATION; // sizeof = 0x038
+typedef DUPLICATED_INFORMATION *PDUPLICATED_INFORMATION;
+
+//
+// This bit is duplicated from the file record, to indicate that
+// this file has a file name index present (is a "directory").
+//
+
+#define DUP_FILE_NAME_INDEX_PRESENT (0x10000000)
+
+//
+// The following macros examine fields of the duplicated structure.
+//
+
+#define IsDirectory( DUPLICATE ) \
+ (FlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ DUP_FILE_NAME_INDEX_PRESENT ))
+
+#define IsReadOnly( DUPLICATE ) \
+ (FlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_READONLY ))
+
+#define IsHidden( DUPLICATE ) \
+ (FlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_HIDDEN ))
+
+#define IsSystem( DUPLICATE ) \
+ (FlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_SYSTEM ))
+
+#define BooleanIsDirectory( DUPLICATE ) \
+ (BooleanFlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ DUP_FILE_NAME_INDEX_PRESENT ))
+
+#define BooleanIsReadOnly( DUPLICATE ) \
+ (BooleanFlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_READONLY ))
+
+#define BooleanIsHidden( DUPLICATE ) \
+ (BooleanFlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_HIDDEN ))
+
+#define BooleanIsSystem( DUPLICATE ) \
+ (BooleanFlagOn( ((PDUPLICATED_INFORMATION) (DUPLICATE))->FileAttributes, \
+ FILE_ATTRIBUTE_SYSTEM ))
+
+
+//
+// File Name attribute. A file has one File Name attribute for
+// every directory it is entered into (hard links).
+//
+
+typedef struct _FILE_NAME {
+
+ //
+ // This is a File Reference to the directory file which indexes
+ // to this name.
+ //
+
+ FILE_REFERENCE ParentDirectory; // offset = 0x000
+
+ //
+ // Information for faster directory operations.
+ //
+
+ DUPLICATED_INFORMATION Info; // offset = 0x008
+
+ //
+ // Length of the name to follow, in (Unicode) characters.
+ //
+
+ UCHAR FileNameLength; // offset = 0x040
+
+ //
+ // FILE_NAME_xxx flags
+ //
+
+ UCHAR Flags; // offset = 0x041
+
+ //
+ // First character of Unicode File Name
+ //
+
+ WCHAR FileName[1]; // offset = 0x042
+
+} FILE_NAME;
+typedef FILE_NAME *PFILE_NAME;
+
+//
+// File Name flags
+//
+
+#define FILE_NAME_NTFS (0x01)
+#define FILE_NAME_DOS (0x02)
+
+//
+// The maximum file name length is 255 (in chars)
+//
+
+#define NTFS_MAX_FILE_NAME_LENGTH (255)
+
+//
+// The maximum number of links on a file is 1024
+//
+
+#define NTFS_MAX_LINK_COUNT (1024)
+
+//
+// This flag is not part of the disk structure, but is defined here
+// to explain its use and avoid possible future collisions. For
+// enumerations of "directories" this bit may be set to convey to
+// the collating routine that it should not match file names that
+// only have the FILE_NAME_DOS bit set.
+//
+
+#define FILE_NAME_IGNORE_DOS_ONLY (0x80)
+
+#define NtfsFileNameSizeFromLength(LEN) ( \
+ (sizeof( FILE_NAME) + LEN - 2) \
+)
+
+#define NtfsFileNameSize(PFN) ( \
+ (sizeof( FILE_NAME ) + ((PFN)->FileNameLength - 1) * 2) \
+)
+
+
+//
+// Security Descriptor attribute. This is just a normal attribute
+// stream containing a security descriptor as defined by NT
+// security and is really treated pretty opaque by NTFS.
+//
+
+
+//
+// Volume Name attribute. This attribute is just a normal
+// attribute stream containing the unicode characters that make up
+// the volume label. It is an attribute of the Mft File.
+//
+
+
+//
+// Volume Information attribute. This attribute is only intended
+// to be used on the Volume DASD file.
+//
+
+typedef struct _VOLUME_INFORMATION {
+
+ LONGLONG Reserved;
+
+ //
+ // Major and minor version number of NTFS on this volume,
+ // starting with 1.0. The major and minor version numbers are
+ // set from the major and minor version of the Format and NTFS
+ // implementation for which they are initialized. The policy
+ // for incementing major and minor versions will always be
+ // decided on a case by case basis, however, the following two
+ // paragraphs attempt to suggest an approximate strategy.
+ //
+ // The major version number is incremented if/when a volume
+ // format change is made which requires major structure changes
+ // (hopefully never?). If an implementation of NTFS sees a
+ // volume with a higher major version number, it should refuse
+ // to mount the volume. If a newer implementation of NTFS sees
+ // an older major version number, it knows the volume cannot be
+ // accessed without performing a one-time conversion.
+ //
+ // The minor version number is incremented if/when minor
+ // enhancements are made to a major version, which potentially
+ // support enhanced functionality through additional file or
+ // attribute record fields, or new system-defined files or
+ // attributes. If an older implementation of NTFS sees a newer
+ // minor version number on a volume, it may issue some kind of
+ // warning, but it will proceed to access the volume - with
+ // presumably some degradation in functionality compared to the
+ // version of NTFS which initialized the volume. If a newer
+ // implementation of NTFS sees a volume with an older minor
+ // version number, it may issue a warning and proceed. In this
+ // case, it may choose to increment the minor version number on
+ // the volume and begin full or incremental upgrade of the
+ // volume on an as-needed basis. It may also leave the minor
+ // version number unchanged, until some sort of explicit
+ // directive from the user specifies that the minor version
+ // should be updated.
+ //
+
+ UCHAR MajorVersion; // offset = 0x000
+
+ UCHAR MinorVersion; // offset = 0x001
+
+ //
+ // VOLUME_xxx flags.
+ //
+
+ USHORT VolumeFlags; // offset = 0x002
+
+} VOLUME_INFORMATION; // sizeof = 0x004
+typedef VOLUME_INFORMATION *PVOLUME_INFORMATION;
+
+//
+// Volume is Dirty
+//
+
+#define VOLUME_DIRTY (0x0001)
+#define VOLUME_RESIZE_LOG_FILE (0x0002)
+
+
+//
+// Common Index Header for Index Root and Index Allocation Buffers.
+// This structure is used to locate the Index Entries and describe
+// the free space in either of the two structures above.
+//
+
+typedef struct _INDEX_HEADER {
+
+ //
+ // Offset from the start of this structure to the first Index
+ // Entry.
+ //
+
+ ULONG FirstIndexEntry; // offset = 0x000
+
+ //
+ // Offset from the start of the first index entry to the first
+ // (quad-word aligned) free byte.
+ //
+
+ ULONG FirstFreeByte; // offset = 0x004
+
+ //
+ // Total number of bytes available, from the start of the first
+ // index entry. In the Index Root, this number must always be
+ // equal to FirstFreeByte, as the total attribute record will
+ // be grown and shrunk as required.
+ //
+
+ ULONG BytesAvailable; // offset = 0x008
+
+ //
+ // INDEX_xxx flags.
+ //
+
+ UCHAR Flags; // offset = 0x00C
+
+ //
+ // Reserved to round up to quad word boundary.
+ //
+
+ UCHAR Reserved[3]; // offset = 0x00D
+
+} INDEX_HEADER; // sizeof = 0x010
+typedef INDEX_HEADER *PINDEX_HEADER;
+
+//
+// INDEX_xxx flags
+//
+
+//
+// This Index or Index Allocation buffer is an intermediate node,
+// as opposed to a leaf in the Btree. All Index Entries will have
+// a block down pointer.
+//
+
+#define INDEX_NODE (0x01)
+
+//
+// Index Root attribute. The index attribute consists of an index
+// header record followed by one or more index entries.
+//
+
+typedef struct _INDEX_ROOT {
+
+ //
+ // Attribute Type Code of the attribute being indexed.
+ //
+
+ ATTRIBUTE_TYPE_CODE IndexedAttributeType; // offset = 0x000
+
+ //
+ // Collation rule for this index.
+ //
+
+ COLLATION_RULE CollationRule; // offset = 0x004
+
+ //
+ // Size of Index Allocation Buffer in bytes.
+ //
+
+ ULONG BytesPerIndexBuffer; // offset = 0x008
+
+ //
+ // Size of Index Allocation Buffers in units of blocks.
+ // Blocks will be clusters when index buffer is equal or
+ // larger than clusters and log blocks for large
+ // cluster systems.
+ //
+
+ UCHAR BlocksPerIndexBuffer; // offset = 0x00C
+
+ //
+ // Reserved to round to quad word boundary.
+ //
+
+ UCHAR Reserved[3]; // offset = 0x00D
+
+ //
+ // Index Header to describe the Index Entries which follow
+ //
+
+ INDEX_HEADER IndexHeader; // offset = 0x010
+
+} INDEX_ROOT; // sizeof = 0x020
+typedef INDEX_ROOT *PINDEX_ROOT;
+
+//
+// Index Allocation record is used for non-root clusters of the
+// b-tree. Each non root cluster is contained in the data part of
+// the index allocation attribute. Each cluster starts with an
+// index allocation list header and is followed by one or more
+// index entries.
+//
+
+typedef struct _INDEX_ALLOCATION_BUFFER {
+
+ //
+ // Multi-Sector Header as defined by the Cache Manager. This
+ // structure will always contain the signature "INDX" and a
+ // description of the location and size of the Update Sequence
+ // Array.
+ //
+
+ MULTI_SECTOR_HEADER MultiSectorHeader; // offset = 0x000
+
+ //
+ // Log File Sequence Number of last logged update to this Index
+ // Allocation Buffer.
+ //
+
+ LSN Lsn; // offset = 0x008
+
+ //
+ // We store the index block of this Index Allocation buffer for
+ // convenience and possible consistency checking.
+ //
+
+ VCN ThisBlock; // offset = 0x010
+
+ //
+ // Index Header to describe the Index Entries which follow
+ //
+
+ INDEX_HEADER IndexHeader; // offset = 0x018
+
+ //
+ // Update Sequence Array to protect multi-sector transfers of
+ // the Index Allocation Buffer.
+ //
+
+ UPDATE_SEQUENCE_ARRAY UpdateSequenceArray; // offset = 0x028
+
+} INDEX_ALLOCATION_BUFFER;
+typedef INDEX_ALLOCATION_BUFFER *PINDEX_ALLOCATION_BUFFER;
+
+//
+// Default size of index buffer and index blocks.
+//
+
+#define DEFAULT_INDEX_BLOCK_SIZE (0x200)
+#define DEFAULT_INDEX_BLOCK_BYTE_SHIFT (9)
+
+//
+// Index Entry. This structure is common to both the resident
+// index list attribute and the Index Allocation records
+//
+
+typedef struct _INDEX_ENTRY {
+
+ //
+ // Define a union to distinguish directory indices from view indices
+ //
+
+ union {
+
+ //
+ // Reference to file containing the attribute with this
+ // attribute value.
+ //
+
+ FILE_REFERENCE FileReference; // offset = 0x000
+
+ //
+ // For views, describe the Data Offset and Length in bytes
+ //
+
+ struct {
+
+ USHORT DataOffset; // offset = 0x000
+ USHORT DataLength; // offset = 0x001
+ ULONG ReservedForZero; // offset = 0x002
+ };
+ };
+
+ //
+ // Length of this index entry, in bytes.
+ //
+
+ USHORT Length; // offset = 0x008
+
+ //
+ // Length of attribute value, in bytes. The attribute value
+ // immediately follows this record.
+ //
+
+ USHORT AttributeLength; // offset = 0x00A
+
+ //
+ // INDEX_ENTRY_xxx Flags.
+ //
+
+ USHORT Flags; // offset = 0x00C
+
+ //
+ // Reserved to round to quad-word boundary.
+ //
+
+ USHORT Reserved; // offset = 0x00E
+
+ //
+ // If this Index Entry is an intermediate node in the tree, as
+ // determined by the INDEX_xxx flags, then a VCN is stored at
+ // the end of this entry at Length - sizeof(VCN).
+ //
+
+} INDEX_ENTRY; // sizeof = 0x010
+typedef INDEX_ENTRY *PINDEX_ENTRY;
+
+//
+// INDEX_ENTRY_xxx flags
+//
+
+//
+// This entry is currently in the intermediate node form, i.e., it
+// has a Vcn at the end.
+//
+
+#define INDEX_ENTRY_NODE (0x0001)
+
+//
+// This entry is the special END record for the Index or Index
+// Allocation buffer.
+//
+
+#define INDEX_ENTRY_END (0x0002)
+
+//
+// This flag is *not* part of the on-disk structure. It is defined
+// and reserved here for the convenience of the implementation to
+// help avoid allocating buffers from the pool and copying.
+//
+
+#define INDEX_ENTRY_POINTER_FORM (0x8000)
+
+#define NtfsIndexEntryBlock(IE) ( \
+ *(PLONGLONG)((PCHAR)(IE) + (ULONG)(IE)->Length - sizeof(LONGLONG)) \
+ )
+
+#define NtfsSetIndexEntryBlock(IE,IB) { \
+ *(PLONGLONG)((PCHAR)(IE) + (ULONG)(IE)->Length - sizeof(LONGLONG)) = (IB); \
+ }
+
+#define NtfsFirstIndexEntry(IH) ( \
+ (PINDEX_ENTRY)((PCHAR)(IH) + (IH)->FirstIndexEntry) \
+ )
+
+#define NtfsNextIndexEntry(IE) ( \
+ (PINDEX_ENTRY)((PCHAR)(IE) + (ULONG)(IE)->Length) \
+ )
+
+#define NtfsCheckIndexBound(IE, IH) { \
+ if (((PCHAR)(IE) < (PCHAR)(IH)) || \
+ ((PCHAR)(IE) >= ((PCHAR)Add2Ptr((IH), (IH)->BytesAvailable)))) { \
+ NtfsRaiseStatus(IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL ); \
+ } \
+}
+
+
+//
+// MFT Bitmap attribute
+//
+// The MFT Bitmap is simply a normal attribute stream in which
+// there is one bit to represent the allocation state of each File
+// Record Segment in the MFT. Bit clear means free, and bit set
+// means allocated.
+//
+// Whenever the MFT Data attribute is extended, the MFT Bitmap
+// attribute must also be extended. If the bitmap is still in a
+// file record segment for the MFT, then it must be extended and
+// the new bits cleared. When the MFT Bitmap is in the Nonresident
+// form, then the allocation should always be sufficient to store
+// enough bits to describe the MFT, however ValidDataLength insures
+// that newly allocated space to the MFT Bitmap has an initial
+// value of all 0's. This means that if the MFT Bitmap is extended,
+// the newly represented file record segments are automatically in
+// the free state.
+//
+// No structure definition is required; the positional offset of
+// the file record segment is exactly equal to the bit offset of
+// its corresponding bit in the Bitmap.
+//
+
+
+//
+// Symbolic Link attribute ****TBS
+//
+
+typedef struct _SYMBOLIC_LINK {
+
+ LONGLONG Tbs; // offset = 0x000
+
+} SYMBOLIC_LINK; // sizeof = 0x008
+typedef SYMBOLIC_LINK *PSYMBOLIC_LINK;
+
+
+//
+// Ea Information attribute
+//
+// This attribute is only present if the file/directory also has an
+// EA attribute. It is used to store common EA query information.
+//
+
+typedef struct _EA_INFORMATION {
+
+ //
+ // The size of buffer needed to pack these Ea's
+ //
+
+ USHORT PackedEaSize; // offset = 0x000
+
+ //
+ // This is the count of Ea's with their NEED_EA
+ // bit set.
+ //
+
+ USHORT NeedEaCount; // offset = 0x002
+
+ //
+ // The size of the buffer needed to return all Ea's
+ // in their unpacked form.
+ //
+
+ ULONG UnpackedEaSize; // offset = 0x004
+
+} EA_INFORMATION; // sizeof = 0x008
+typedef EA_INFORMATION *PEA_INFORMATION;
+
+
+//
+// Attribute Definition Table
+//
+// The following struct defines the columns of this table.
+// Initially they will be stored as simple records, and ordered by
+// Attribute Type Code.
+//
+
+typedef struct _ATTRIBUTE_DEFINITION_COLUMNS {
+
+ //
+ // Unicode attribute name.
+ //
+
+ WCHAR AttributeName[64]; // offset = 0x000
+
+ //
+ // Attribute Type Code.
+ //
+
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode; // offset = 0x080
+
+ //
+ // Default Display Rule for this attribute
+ //
+
+ DISPLAY_RULE DisplayRule; // offset = 0x084
+
+ //
+ // Default Collation rule
+ //
+
+ COLLATION_RULE CollationRule; // offset = 0x088
+
+ //
+ // ATTRIBUTE_DEF_xxx flags
+ //
+
+ ULONG Flags; // offset = 0x08C
+
+ //
+ // Minimum Length for attribute, if present.
+ //
+
+ LONGLONG MinimumLength; // offset = 0x090
+
+ //
+ // Maximum Length for attribute.
+ //
+
+ LONGLONG MaximumLength; // offset = 0x098
+
+} ATTRIBUTE_DEFINITION_COLUMNS; // sizeof = 0x0A0
+typedef ATTRIBUTE_DEFINITION_COLUMNS *PATTRIBUTE_DEFINITION_COLUMNS;
+
+//
+// ATTRIBUTE_DEF_xxx flags
+//
+
+//
+// This flag is set if the attribute may be indexed.
+//
+
+#define ATTRIBUTE_DEF_INDEXABLE (0x00000002)
+
+//
+// This flag is set if the attribute may occur more than once, such
+// as is allowed for the File Name attribute.
+//
+
+#define ATTRIBUTE_DEF_DUPLICATES_ALLOWED (0x00000004)
+
+//
+// This flag is set if the value of the attribute may not be
+// entirely null, i.e., all binary 0's.
+//
+
+#define ATTRIBUTE_DEF_MAY_NOT_BE_NULL (0x00000008)
+
+//
+// This attribute must be indexed, and no two attributes may exist
+// with the same value in the same file record segment.
+//
+
+#define ATTRIBUTE_DEF_MUST_BE_INDEXED (0x00000010)
+
+//
+// This attribute must be named, and no two attributes may exist
+// with the same name in the same file record segment.
+//
+
+#define ATTRIBUTE_DEF_MUST_BE_NAMED (0x00000020)
+
+//
+// This attribute must be in the Resident Form.
+//
+
+#define ATTRIBUTE_DEF_MUST_BE_RESIDENT (0x00000040)
+
+//
+// Modifications to this attribute should be logged even if the
+// attribute is nonresident.
+//
+
+#define ATTRIBUTE_DEF_LOG_NONRESIDENT (0X00000080)
+
+
+#ifdef _CAIRO_
+
+//
+// Define the struture of the quota data in the quota index. The key for
+// the quota index is the 32 bit owner id.
+//
+
+typedef struct _QUOTA_USER_DATA {
+ ULONG QuotaVersion;
+ ULONG QuotaFlags;
+ ULONGLONG QuotaThreshold;
+ ULONGLONG QuotaLimit;
+ ULONGLONG QuotaUsed;
+ ULONGLONG QuotaChangeTime;
+ ULONGLONG QuotaExceededTime;
+ SID QuotaSid;
+} QUOTA_USER_DATA, *PQUOTA_USER_DATA;
+
+//
+// Define the size of the quota user data structure without the quota SID.
+//
+
+#define SIZEOF_QUOTA_USER_DATA FIELD_OFFSET(QUOTA_USER_DATA, QuotaSid)
+
+//
+// Define the current version of the quote user data.
+//
+
+#define QUOTA_USER_VERSION 1
+
+//
+// Define the quota flags.
+//
+
+#define QUOTA_FLAG_DEFAULT_LIMITS (0x00000001)
+#define QUOTA_FLAG_LIMIT_REACHED (0x00000002)
+#define QUOTA_FLAG_ID_DELETED (0x00000004)
+#define QUOTA_FLAG_USER_MASK (0x00000007)
+
+//
+// The following flags are only stored in the quota defaults index entry.
+//
+
+#define QUOTA_FLAG_TRACKING_ENABLED (0x00000010)
+#define QUOTA_FLAG_ENFORCEMENT_ENABLED (0x00000020)
+#define QUOTA_FLAG_TRACKING_REQUESTED (0x00000040)
+#define QUOTA_FLAG_LOG_THRESHOLD (0x00000080)
+#define QUOTA_FLAG_LOG_LIMIT (0x00000100)
+#define QUOTA_FLAG_OUT_OF_DATE (0x00000200)
+#define QUOTA_FLAG_CORRUPT (0x00000400)
+#define QUOTA_FLAG_PENDING_DELETES (0x00000800)
+
+//
+// Define special quota owner ids.
+//
+
+#define QUOTA_INVALID_ID 0x00000000
+#define QUOTA_DEFAULTS_ID 0x00000001
+#define QUOTA_FISRT_USER_ID 0x00000100
+
+#else
+
+//
+// The following typedef is used in a function prototype.
+//
+
+typedef struct _QUOTA_USER_DATA *PQUOTA_USER_DATA;
+
+#endif // _CAIRO_
+
+//
+// Security definitions
+//
+// Security descriptors are stored only once on a volume since there may be
+// many files that share the same descriptor bits. Typically each principal
+// will create files with a single descriptor.
+//
+// The descriptors themselves are stored in a stream, packed on DWORD boundaries.
+// No descriptor will span a 256K cache boundary. The descriptors are assigned
+// a ULONG Id each time a unique descriptor is stored. Prefixing each descriptor
+// in the stream is the hash of the descriptor, the assigned security ID, the
+// length, and the offset within the stream to the beginning of the structure.
+//
+// An index is used to map from a security Id to offset within the stream. This
+// is used to retrieve the security descriptor bits for access validation. The key
+// format is simply the ULONG security Id. The data portion of the index record
+// is the header of the security descriptor in the stream (see above paragraph).
+//
+// Another index is used to map from a hash to offset within the stream. To
+// simplify the job of the indexing package, the key used in this index is the
+// hash followed by the assigned Id. When a security descriptor is stored,
+// a hash is computed and an approximate seek is made on this index. As entries
+// are enumerated in the index, the descriptor stream is mapped and the security
+// descriptor bits are compared. The key format is a structure that contains
+// the hash and then the Id. The collation routine tests the hash before the Id.
+// The data portion of the index record is the header of the security descriptor.
+//
+
+#if defined (_CAIRO_)
+
+//
+// Key structure for Security Hash index
+//
+
+typedef struct _SECURITY_HASH_KEY
+{
+ ULONG Hash; // Hash value for descriptor
+ ULONG SecurityId; // Security Id (guaranteed unique)
+} SECURITY_HASH_KEY, *PSECURITY_HASH_KEY;
+
+//
+// Key structure for Security Id index is simply the SECURITY_ID itself
+//
+
+//
+// Header for security descriptors in the security descriptor stream. This
+// is the data format for all indexes and is part of SharedSecurity
+//
+
+typedef struct _SECURITY_DESCRIPTOR_HEADER
+{
+ SECURITY_HASH_KEY HashKey; // Hash value for the descriptor
+ ULONGLONG Offset; // offset to beginning of header
+ ULONG Length; // Length in bytes
+} SECURITY_DESCRIPTOR_HEADER, *PSECURITY_DESCRIPTOR_HEADER;
+
+#define GETSECURITYDESCRIPTORLENGTH(HEADER) \
+ ((HEADER)->Length - sizeof( SECURITY_DESCRIPTOR_HEADER ))
+
+#define SetSecurityDescriptorLength(HEADER,LENGTH) \
+ ((HEADER)->Length = (LENGTH) + sizeof( SECURITY_DESCRIPTOR_HEADER ))
+
+#endif // defined (_CAIRO_)
+
+//
+// Define standard values for well-known security IDs
+//
+
+#define SECURITY_ID_INVALID (0x00000000)
+#define SECURITY_ID_FIRST (0x00000100)
+
+
+//
+// MACROS
+//
+// Define some macros that are helpful for manipulating NTFS on
+// disk structures.
+//
+
+//
+// The following macro returns the first attribute record in a file
+// record segment.
+//
+// PATTRIBUTE_RECORD_HEADER
+// NtfsFirstAttribute (
+// IN PFILE_RECORD_SEGMENT_HEADER FileRecord
+// );
+//
+// The following macro takes a pointer to an attribute record (or
+// attribute list entry) and returns a pointer to the next
+// attribute record (or attribute list entry) in the list
+//
+// PVOID
+// NtfsGetNextRecord (
+// IN PATTRIB_RECORD or PATTRIB_LIST_ENTRY Struct
+// );
+//
+//
+// The following macro takes as input a attribute record or
+// attribute list entry and initializes a string variable to the
+// name found in the record or entry. The memory used for the
+// string buffer is the memory found in the attribute.
+//
+// VOID
+// NtfsInitializeStringFromAttribute (
+// IN OUT PUNICODE_STRING Name,
+// IN PATTRIBUTE_RECORD_HEADER Attribute
+// );
+//
+// VOID
+// NtfsInitializeStringFromEntry (
+// IN OUT PUNICODE_STRING Name,
+// IN PATTRIBUTE_LIST_ENTRY Entry
+// );
+//
+//
+// The following two macros assume resident form and should only be
+// used when that state is known. They return a pointer to the
+// value a resident attribute or a pointer to the byte one beyond
+// the value.
+//
+// PVOID
+// NtfsGetValue (
+// IN PATTRIBUTE_RECORD_HEADER Attribute
+// );
+//
+// PVOID
+// NtfsGetBeyondValue (
+// IN PATTRIBUTE_RECORD_HEADER Attribute
+// );
+//
+// The following two macros return a boolean value indicating if
+// the input attribute record is of the specified type code, or the
+// indicated value. The equivalent routine to comparing attribute
+// names cannot be defined as a macro and is declared in AttrSup.c
+//
+// BOOLEAN
+// NtfsEqualAttributeTypeCode (
+// IN PATTRIBUTE_RECORD_HEADER Attribute,
+// IN ATTRIBUTE_TYPE_CODE Code
+// );
+//
+// BOOLEAN
+// NtfsEqualAttributeValue (
+// IN PATTRIBUTE_RECORD_HEADER Attribute,
+// IN PVOID Value,
+// IN ULONG Length
+// );
+//
+
+#define NtfsFirstAttribute(FRS) ( \
+ (PATTRIBUTE_RECORD_HEADER)((PCHAR)(FRS) + (FRS)->FirstAttributeOffset) \
+)
+
+#define NtfsGetNextRecord(STRUCT) ( \
+ (PVOID)((PUCHAR)(STRUCT) + (STRUCT)->RecordLength) \
+)
+
+#define NtfsCheckRecordBound(PTR, SPTR, SIZ) { \
+ if (((PCHAR)(PTR) < (PCHAR)(SPTR)) || ((PCHAR)(PTR) >= ((PCHAR)(SPTR) + (SIZ)))) { \
+ NtfsRaiseStatus(IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NULL ); \
+ } \
+}
+
+#define NtfsInitializeStringFromAttribute(NAME,ATTRIBUTE) { \
+ (NAME)->Length = (USHORT)(ATTRIBUTE)->NameLength << 1; \
+ (NAME)->MaximumLength = (NAME)->Length; \
+ (NAME)->Buffer = (PWSTR)Add2Ptr((ATTRIBUTE), (ATTRIBUTE)->NameOffset); \
+}
+
+#define NtfsInitializeStringFromEntry(NAME,ENTRY) { \
+ (NAME)->Length = (USHORT)(ENTRY)->AttributeNameLength << 1; \
+ (NAME)->MaximumLength = (NAME)->Length; \
+ (NAME)->Buffer = (PWSTR)((ENTRY) + 1); \
+}
+
+#define NtfsGetValue(ATTRIBUTE) ( \
+ Add2Ptr((ATTRIBUTE), (ATTRIBUTE)->Form.Resident.ValueOffset) \
+)
+
+#define NtfsGetBeyondValue(ATTRIBUTE) ( \
+ Add2Ptr(NtfsGetValue(ATTRIBUTE), (ATTRIBUTE)->Form.Resident.ValueLength) \
+)
+
+#define NtfsEqualAttributeTypeCode(A,C) ( \
+ (C) == (A)->TypeCode \
+)
+
+#define NtfsEqualAttributeValue(A,V,L) ( \
+ NtfsIsAttributeResident(A) && \
+ (A)->Form.Resident.ValueLength == (L) && \
+ RtlEqualMemory(NtfsGetValue(A),(V),(L)) \
+)
+
+#pragma pack()
+
+#endif // _NTFS_
diff --git a/private/ntos/cntfs/ntfs.rc b/private/ntos/cntfs/ntfs.rc
new file mode 100644
index 000000000..6e82ede4d
--- /dev/null
+++ b/private/ntos/cntfs/ntfs.rc
@@ -0,0 +1,11 @@
+#include <windows.h>
+
+#include <ntverp.h>
+
+#define VER_FILETYPE VFT_DRV
+#define VER_FILESUBTYPE VFT2_DRV_SYSTEM
+#define VER_FILEDESCRIPTION_STR "NT File System Driver"
+#define VER_INTERNALNAME_STR "ntfs.sys"
+
+#include "common.ver"
+
diff --git a/private/ntos/cntfs/ntfsdata.c b/private/ntos/cntfs/ntfsdata.c
new file mode 100644
index 000000000..fb53a0fa0
--- /dev/null
+++ b/private/ntos/cntfs/ntfsdata.c
@@ -0,0 +1,2607 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsData.c
+
+Abstract:
+
+ This module declares the global data used by the Ntfs file system.
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_NTFSDATA)
+
+//
+// The debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CATCH_EXCEPTIONS)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('NFtN')
+
+#define CollectExceptionStats(VCB,EXCEPTION_CODE) { \
+ if ((VCB) != NULL) { \
+ PFILESYSTEM_STATISTICS FsStat = &(VCB)->Statistics[KeGetCurrentProcessorNumber()]; \
+ if ((EXCEPTION_CODE) == STATUS_LOG_FILE_FULL) { \
+ FsStat->Ntfs.LogFileFullExceptions += 1; \
+ } else { \
+ FsStat->Ntfs.OtherExceptions += 1; \
+ } \
+ } \
+}
+
+//
+// The global fsd data record
+//
+
+NTFS_DATA NtfsData;
+
+//
+// Semaphore to synchronize creation of stream files.
+//
+
+FAST_MUTEX StreamFileCreationFastMutex;
+
+//
+// A mutex and queue of NTFS MCBS that will be freed
+// if we reach over a certain threshold
+//
+
+FAST_MUTEX NtfsMcbFastMutex;
+LIST_ENTRY NtfsMcbLruQueue;
+
+ULONG NtfsMcbHighWaterMark;
+ULONG NtfsMcbLowWaterMark;
+ULONG NtfsMcbCurrentLevel;
+
+BOOLEAN NtfsMcbCleanupInProgress;
+WORK_QUEUE_ITEM NtfsMcbWorkItem;
+
+//
+// The global large integer constants
+//
+
+LARGE_INTEGER NtfsLarge0 = {0x00000000,0x00000000};
+LARGE_INTEGER NtfsLarge1 = {0x00000001,0x00000000};
+
+LONGLONG NtfsLastAccess;
+
+//
+// The following fields are used to allocate nonpaged structures
+// using a lookaside list, and other fixed sized structures from a
+// small cache.
+//
+
+NPAGED_LOOKASIDE_LIST NtfsFileLockLookasideList;
+NPAGED_LOOKASIDE_LIST NtfsIoContextLookasideList;
+NPAGED_LOOKASIDE_LIST NtfsIrpContextLookasideList;
+NPAGED_LOOKASIDE_LIST NtfsKeventLookasideList;
+NPAGED_LOOKASIDE_LIST NtfsScbNonpagedLookasideList;
+NPAGED_LOOKASIDE_LIST NtfsScbSnapshotLookasideList;
+
+PAGED_LOOKASIDE_LIST NtfsCcbLookasideList;
+PAGED_LOOKASIDE_LIST NtfsCcbDataLookasideList;
+PAGED_LOOKASIDE_LIST NtfsDeallocatedRecordsLookasideList;
+PAGED_LOOKASIDE_LIST NtfsFcbDataLookasideList;
+PAGED_LOOKASIDE_LIST NtfsFcbIndexLookasideList;
+PAGED_LOOKASIDE_LIST NtfsIndexContextLookasideList;
+PAGED_LOOKASIDE_LIST NtfsLcbLookasideList;
+PAGED_LOOKASIDE_LIST NtfsNukemLookasideList;
+PAGED_LOOKASIDE_LIST NtfsScbDataLookasideList;
+
+//
+// Useful constant Unicode strings.
+//
+
+//
+// This is the string for the name of the index allocation attributes.
+//
+
+UNICODE_STRING NtfsFileNameIndex;
+WCHAR NtfsFileNameIndexName[] = { '$', 'I','0' + $FILE_NAME/0x10, '0' + $FILE_NAME%0x10, '\0' };
+
+//
+// This is the string for the attribute code for index allocation.
+// $INDEX_ALLOCATION.
+//
+
+UNICODE_STRING NtfsIndexAllocation =
+ CONSTANT_UNICODE_STRING( L"$INDEX_ALLOCATION" );
+
+//
+// This is the string for the data attribute, $DATA.
+//
+
+UNICODE_STRING NtfsDataString =
+ CONSTANT_UNICODE_STRING( L"$DATA" );
+
+//
+// This strings are used for informational popups.
+//
+
+UNICODE_STRING NtfsSystemFiles[] = {
+
+ CONSTANT_UNICODE_STRING( L"\\$Mft" ),
+ CONSTANT_UNICODE_STRING( L"\\$MftMirr" ),
+ CONSTANT_UNICODE_STRING( L"\\$LogFile" ),
+ CONSTANT_UNICODE_STRING( L"\\$Volume" ),
+ CONSTANT_UNICODE_STRING( L"\\$AttrDef" ),
+ CONSTANT_UNICODE_STRING( L"\\" ),
+ CONSTANT_UNICODE_STRING( L"\\$BitMap" ),
+ CONSTANT_UNICODE_STRING( L"\\$Boot" ),
+ CONSTANT_UNICODE_STRING( L"\\$BadClus" ),
+ CONSTANT_UNICODE_STRING( L"\\$Quota" ),
+ CONSTANT_UNICODE_STRING( L"\\$UpCase" ),
+};
+
+UNICODE_STRING NtfsUnknownFile =
+ CONSTANT_UNICODE_STRING( L"\\????" );
+
+UNICODE_STRING NtfsRootIndexString =
+ CONSTANT_UNICODE_STRING( L"." );
+
+//
+// This is the empty string. This can be used to pass a string with
+// no length.
+//
+
+UNICODE_STRING NtfsEmptyString = { 0, 0, NULL };
+
+//
+// The following file references are used to identify system files.
+//
+
+FILE_REFERENCE MftFileReference = { MASTER_FILE_TABLE_NUMBER, 0, MASTER_FILE_TABLE_NUMBER };
+FILE_REFERENCE Mft2FileReference = { MASTER_FILE_TABLE2_NUMBER, 0, MASTER_FILE_TABLE2_NUMBER };
+FILE_REFERENCE LogFileReference = { LOG_FILE_NUMBER, 0, LOG_FILE_NUMBER };
+FILE_REFERENCE VolumeFileReference = { VOLUME_DASD_NUMBER, 0, VOLUME_DASD_NUMBER };
+FILE_REFERENCE RootIndexFileReference = { ROOT_FILE_NAME_INDEX_NUMBER, 0, ROOT_FILE_NAME_INDEX_NUMBER };
+FILE_REFERENCE BitmapFileReference = { BIT_MAP_FILE_NUMBER, 0, BIT_MAP_FILE_NUMBER };
+FILE_REFERENCE FirstUserFileReference = { FIRST_USER_FILE_NUMBER, 0, 0 };
+FILE_REFERENCE BootFileReference = { BOOT_FILE_NUMBER, 0, BOOT_FILE_NUMBER };
+
+//
+// The following are used to determine what level of protection to attach
+// to system files and attributes.
+//
+
+BOOLEAN NtfsProtectSystemFiles = TRUE;
+BOOLEAN NtfsProtectSystemAttributes = TRUE;
+
+//
+// FsRtl fast I/O call backs
+//
+
+FAST_IO_DISPATCH NtfsFastIoDispatch;
+
+#ifdef NTFSDBG
+
+LONG NtfsDebugTraceLevel = DEBUG_TRACE_ERROR;
+LONG NtfsDebugTraceIndent = 0;
+LONG NtfsFailCheck = 0;
+
+ULONG NtfsFsdEntryCount = 0;
+ULONG NtfsFspEntryCount = 0;
+ULONG NtfsIoCallDriverCount = 0;
+
+#endif // NTFSDBG
+
+//
+// Performance statistics
+//
+
+ULONG NtfsMaxDelayedCloseCount;
+ULONG NtfsMinDelayedCloseCount;
+
+ULONG NtfsCleanCheckpoints = 0;
+ULONG NtfsPostRequests = 0;
+
+UCHAR BaadSignature[4] = {'B', 'A', 'A', 'D'};
+UCHAR IndexSignature[4] = {'I', 'N', 'D', 'X'};
+UCHAR FileSignature[4] = {'F', 'I', 'L', 'E'};
+UCHAR HoleSignature[4] = {'H', 'O', 'L', 'E'};
+UCHAR ChkdskSignature[4] = {'C', 'H', 'K', 'D'};
+
+//
+// Large Reserved Buffer Context
+//
+
+ULONG NtfsReservedInUse = 0;
+PVOID NtfsReserved1 = NULL;
+PVOID NtfsReserved2 = NULL;
+ULONG NtfsReserved2Count = 0;
+PVOID NtfsReserved3 = NULL;
+PVOID NtfsReserved1Thread = NULL;
+PVOID NtfsReserved2Thread = NULL;
+PVOID NtfsReserved3Thread = NULL;
+PFCB NtfsReserved12Fcb = NULL;
+PFCB NtfsReserved3Fcb = NULL;
+PVOID NtfsReservedBufferThread = NULL;
+BOOLEAN NtfsBufferAllocationFailure = FALSE;
+FAST_MUTEX NtfsReservedBufferMutex;
+ERESOURCE NtfsReservedBufferResource;
+LARGE_INTEGER NtfsShortDelay = {(ULONG)-100000, -1}; // 10 milliseconds
+
+#ifdef _CAIRO_
+FAST_MUTEX NtfsScavengerLock;
+PIRP_CONTEXT NtfsScavengerWorkList;
+BOOLEAN NtfsScavengerRunning;
+#endif // _CAIRO_
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsFastIoCheckIfPossible)
+#pragma alloc_text(PAGE, NtfsFastQueryBasicInfo)
+#pragma alloc_text(PAGE, NtfsFastQueryStdInfo)
+#pragma alloc_text(PAGE, NtfsFastQueryNetworkOpenInfo)
+#ifdef _CAIRO_
+#pragma alloc_text(PAGE, NtfsFastIoQueryCompressionInfo)
+#pragma alloc_text(PAGE, NtfsFastIoQueryCompressedSize)
+#endif _CAIRO_
+#endif
+
+//
+// Internal support routines
+//
+
+LONG
+NtfsProcessExceptionFilter (
+ IN PEXCEPTION_POINTERS ExceptionPointer
+ )
+{
+ UNREFERENCED_PARAMETER( ExceptionPointer );
+
+ ASSERT( NT_SUCCESS( ExceptionPointer->ExceptionRecord->ExceptionCode ));
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+ULONG
+NtfsRaiseStatusFunction (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is only required by the NtfsDecodeFileObject macro. It is
+ a function wrapper around NtfsRaiseStatus.
+
+Arguments:
+
+ Status - Status to raise
+
+Return Value:
+
+ 0 - but no one will see it!
+
+--*/
+
+{
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ return 0;
+}
+
+
+
+VOID
+NtfsRaiseStatus (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ )
+
+{
+ //
+ // If the caller is declaring corruption, then let's mark the
+ // the volume corrupt appropriately, and maybe generate a popup.
+ //
+
+ if (Status == STATUS_DISK_CORRUPT_ERROR) {
+
+ NtfsPostVcbIsCorrupt( IrpContext, Status, FileReference, Fcb );
+
+ } else if ((Status == STATUS_FILE_CORRUPT_ERROR) ||
+ (Status == STATUS_EA_CORRUPT_ERROR)) {
+
+ NtfsPostVcbIsCorrupt( IrpContext, Status, FileReference, Fcb );
+ }
+
+ //
+ // Set a flag to indicate that we raised this status code and store
+ // it in the IrpContext.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RAISED_STATUS );
+
+ if (NT_SUCCESS( IrpContext->ExceptionStatus )) {
+
+ //
+ // If this is a paging io request and we got a Quota Exceeded error
+ // then translate the status to FILE_LOCK_CONFLICT so that this
+ // is a retryable condition.
+ //
+
+ if ((Status == STATUS_QUOTA_EXCEEDED) &&
+ (IrpContext->OriginatingIrp != NULL) &&
+ (FlagOn( IrpContext->OriginatingIrp->Flags, IRP_PAGING_IO ))) {
+
+ Status = STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ IrpContext->ExceptionStatus = Status;
+ }
+
+ //
+ // Now finally raise the status, and make sure we do not come back.
+ //
+
+ ExRaiseStatus( IrpContext->ExceptionStatus );
+}
+
+
+LONG
+NtfsExceptionFilter (
+ IN PIRP_CONTEXT IrpContext OPTIONAL,
+ IN PEXCEPTION_POINTERS ExceptionPointer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to decide if we should or should not handle
+ an exception status that is being raised. It inserts the status
+ into the IrpContext and either indicates that we should handle
+ the exception or bug check the system.
+
+Arguments:
+
+ ExceptionPointer - Supplies the exception record to being checked.
+
+Return Value:
+
+ ULONG - returns EXCEPTION_EXECUTE_HANDLER or bugchecks
+
+--*/
+
+{
+ NTSTATUS ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
+
+ ASSERT_OPTIONAL_IRP_CONTEXT( IrpContext );
+
+ DebugTrace( 0, DEBUG_TRACE_UNWIND, ("NtfsExceptionFilter %X\n", ExceptionCode) );
+
+ //
+ // If the exception is an in page error, then get the real I/O error code
+ // from the exception record
+ //
+
+ if ((ExceptionCode == STATUS_IN_PAGE_ERROR) &&
+ (ExceptionPointer->ExceptionRecord->NumberParameters >= 3)) {
+
+ ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionInformation[2];
+
+ //
+ // If we got FILE_LOCK_CONFLICT from a paging request then change it
+ // to STATUS_CANT_WAIT. This means that we couldn't wait for a
+ // reserved buffer or some other retryable condition. In the write
+ // case the correct error is already in the IrpContext. The read
+ // case doesn't pass the error back via the top-level irp context
+ // however.
+ //
+
+ if (ExceptionCode == STATUS_FILE_LOCK_CONFLICT) {
+
+ ExceptionCode = STATUS_CANT_WAIT;
+ }
+ }
+
+ //
+ // If there is not an irp context, we must have had insufficient resources
+ //
+
+ if (!ARGUMENT_PRESENT(IrpContext)) {
+
+ //
+ // Check whether this is a fatal error and bug check if so.
+ // Typically the only error is insufficient resources but
+ // it is possible that pool has been corrupted.
+ //
+
+ if (!FsRtlIsNtstatusExpected( ExceptionCode )) {
+
+ NtfsBugCheck( (ULONG)ExceptionPointer->ExceptionRecord,
+ (ULONG)ExceptionPointer->ContextRecord,
+ (ULONG)ExceptionPointer->ExceptionRecord->ExceptionAddress );
+ }
+
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+
+#ifdef NTFS_RESTART
+ ASSERT( (ExceptionCode != STATUS_FILE_CORRUPT_ERROR) &&
+ (ExceptionCode != STATUS_DISK_CORRUPT_ERROR) );
+#endif
+
+ //
+ // When processing any exceptions we always can wait. Remember the
+ // current state of the wait flag so we can restore while processing
+ // the exception.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST );
+ }
+
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ //
+ // If someone got STATUS_LOG_FILE_FULL or STATUS_CANT_WAIT, let's
+ // handle that. Note any other error that also happens will
+ // probably not go away and will just reoccur. If it does go
+ // away, that's ok too.
+ //
+
+ if (IrpContext->TopLevelIrpContext == IrpContext) {
+
+ if ((IrpContext->ExceptionStatus == STATUS_LOG_FILE_FULL) ||
+ (IrpContext->ExceptionStatus == STATUS_CANT_WAIT)) {
+
+ ExceptionCode = IrpContext->ExceptionStatus;
+ }
+ }
+
+ //
+ // If we didn't raise this status code then we need to check if
+ // we should handle this exception.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_RAISED_STATUS )) {
+
+ if (FsRtlIsNtstatusExpected( ExceptionCode )) {
+
+ //
+ // If we got an allocation failure doing paging Io then convert
+ // this to FILE_LOCK_CONFLICT.
+ //
+
+ if ((ExceptionCode == STATUS_QUOTA_EXCEEDED) &&
+ (IrpContext->OriginatingIrp != NULL) &&
+ (FlagOn( IrpContext->OriginatingIrp->Flags, IRP_PAGING_IO ))) {
+
+ ExceptionCode = STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ IrpContext->ExceptionStatus = ExceptionCode;
+
+ } else {
+
+ NtfsBugCheck( (ULONG)ExceptionPointer->ExceptionRecord,
+ (ULONG)ExceptionPointer->ContextRecord,
+ (ULONG)ExceptionPointer->ExceptionRecord->ExceptionAddress );
+ }
+
+ } else {
+
+ //
+ // We raised this code explicitly ourselves, so it had better be
+ // expected.
+ //
+
+ ASSERT( ExceptionCode == IrpContext->ExceptionStatus );
+ ASSERT( FsRtlIsNtstatusExpected( ExceptionCode ) );
+ }
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_RAISED_STATUS );
+
+ //
+ // If the exception code is log file full, then remember the current
+ // RestartAreaLsn in the Vcb, so we can see if we are the ones to flush
+ // the log file later. Note, this does not have to be synchronized,
+ // because we are just using it to arbitrate who must do the flush, but
+ // eventually someone will anyway.
+ //
+
+ if (ExceptionCode == STATUS_LOG_FILE_FULL) {
+
+ IrpContext->TopLevelIrpContext->LastRestartArea = IrpContext->Vcb->LastRestartArea;
+ }
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+
+NTSTATUS
+NtfsProcessException (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL,
+ IN NTSTATUS ExceptionCode
+ )
+
+/*++
+
+Routine Description:
+
+ This routine process an exception. It either completes the request
+ with the saved exception status or it sends the request off to the Fsp
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+ ExceptionCode - Supplies the normalized exception status being handled
+
+Return Value:
+
+ NTSTATUS - Returns the results of either posting the Irp or the
+ saved completion status.
+
+--*/
+
+{
+ BOOLEAN TopLevelRequest;
+ PIRP_CONTEXT PostIrpContext = NULL;
+ BOOLEAN Retry = FALSE;
+
+ BOOLEAN ReleaseBitmap = FALSE;
+
+ ASSERT_OPTIONAL_IRP_CONTEXT( IrpContext );
+ ASSERT_OPTIONAL_IRP( Irp );
+
+ DebugTrace( 0, Dbg, ("NtfsProcessException\n") );
+
+ //
+ // If there is not an irp context, we must have had insufficient resources
+ //
+
+ if (IrpContext == NULL) {
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ NtfsCompleteRequest( NULL, &Irp, ExceptionCode );
+ }
+
+ return ExceptionCode;
+ }
+
+ //
+ // Get the real exception status from the Irp Context.
+ //
+
+ ExceptionCode = IrpContext->ExceptionStatus;
+
+ //
+ // All errors which could possibly have started a transaction must go
+ // through here. Abort the transaction.
+ //
+
+ //
+ // Increment the appropriate performance counters.
+ //
+
+ CollectExceptionStats( IrpContext->Vcb, ExceptionCode );
+
+ try {
+
+ //
+ // If this is an Mdl write request, then take care of the Mdl
+ // here so that things get cleaned up properly, and in the
+ // case of log file full we will just create a new Mdl. By
+ // getting rid of this Mdl now, the pages will not be locked
+ // if we try to truncate the file while restoring snapshots.
+ //
+
+ if ((IrpContext->MajorFunction == IRP_MJ_WRITE) &&
+ FlagOn(IrpContext->MinorFunction, IRP_MN_MDL) &&
+ (Irp->MdlAddress != NULL)) {
+
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
+
+ CcMdlWriteComplete( IrpSp->FileObject,
+ &IrpSp->Parameters.Write.ByteOffset,
+ Irp->MdlAddress );
+
+ Irp->MdlAddress = NULL;
+ }
+
+ //
+ // On a failed mount this value will be NULL. Don't perform the
+ // abort in that case or we will fail when looking at the Vcb
+ // in the Irp COntext.
+ //
+
+ if (IrpContext->Vcb != NULL) {
+
+ //
+ // To make sure that we can access all of our streams correctly,
+ // we first restore all of the higher sizes before aborting the
+ // transaction. Then we restore all of the lower sizes after
+ // the abort, so that all Scbs are finally restored.
+ //
+
+ NtfsRestoreScbSnapshots( IrpContext, TRUE );
+
+ //
+ // If we modified the volume bitmap during this transaction we
+ // want to acquire it and hold it throughout the abort process.
+ // Otherwise this abort could constantly be setting the rescan
+ // bitmap flag at the same time as some interleaved transaction
+ // is performing bitmap operations and we will thrash performing
+ // bitmap scans.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_MODIFIED_BITMAP ) &&
+ (IrpContext->TransactionId != 0)) {
+
+ //
+ // Acquire the resource and remember we need to release it.
+ //
+
+ ExAcquireResourceExclusive( IrpContext->Vcb->BitmapScb->Header.Resource,
+ TRUE );
+
+ //
+ // Restore the free cluster count in the Vcb.
+ //
+
+ IrpContext->Vcb->FreeClusters -= IrpContext->FreeClusterChange;
+
+ ReleaseBitmap = TRUE;
+ }
+
+ NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
+
+ if (ReleaseBitmap) {
+
+ ExReleaseResource( IrpContext->Vcb->BitmapScb->Header.Resource );
+ ReleaseBitmap = FALSE;
+ }
+
+ NtfsRestoreScbSnapshots( IrpContext, FALSE );
+
+ NtfsAcquireCheckpoint( IrpContext, IrpContext->Vcb );
+ SetFlag( IrpContext->Vcb->MftDefragState, VCB_MFT_DEFRAG_ENABLED );
+ NtfsReleaseCheckpoint( IrpContext, IrpContext->Vcb );
+ }
+
+ //
+ // Exceptions at this point are pretty bad, we failed to undo everything.
+ //
+
+ } except(NtfsProcessExceptionFilter( GetExceptionInformation() )) {
+
+ PSCB_SNAPSHOT ScbSnapshot;
+ PSCB NextScb;
+
+ //
+ // If we get an exception doing this then things are in really bad
+ // shape but we still don't want to bugcheck the system so we
+ // need to protect ourselves
+ //
+
+ try {
+
+ NtfsPostVcbIsCorrupt( IrpContext, 0, NULL, NULL );
+
+ } except(NtfsProcessExceptionFilter( GetExceptionInformation() )) {
+
+ NOTHING;
+ }
+
+ if (ReleaseBitmap) {
+
+ //
+ // Since we had an unexpected failure and we know that
+ // we have modified the bitmap we need to do a complete
+ // scan to accurately know the free cluster count.
+ //
+
+ SetFlag( IrpContext->Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS );
+ ExReleaseResource( IrpContext->Vcb->BitmapScb->Header.Resource );
+ ReleaseBitmap = FALSE;
+ }
+
+ //
+ // We have taken all the steps possible to cleanup the current
+ // transaction and it has failed. Any of the Scb's involved in
+ // this transaction could now be out of ssync with the on-disk
+ // structures. We can't go to disk to restore this so we will
+ // clean up the in-memory structures as best we can so that the
+ // system won't crash.
+ //
+ // We will go through the Scb snapshot list and knock down the
+ // sizes to the lower of the two values. We will also truncate
+ // the Mcb to that allocation. If this is a normal data stream
+ // we will actually empty the Mcb.
+ //
+
+ ScbSnapshot = &IrpContext->ScbSnapshot;
+
+ //
+ // There is no snapshot data to restore if the Flink is still NULL.
+ //
+
+ if (ScbSnapshot->SnapshotLinks.Flink != NULL) {
+
+ //
+ // Loop to retore first the Scb data from the snapshot in the
+ // IrpContext, and then 0 or more additional snapshots linked
+ // to the IrpContext.
+ //
+
+ do {
+
+ NextScb = ScbSnapshot->Scb;
+
+ if (NextScb == NULL) {
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+ continue;
+ }
+
+ //
+ // Go through each of the sizes and use the lower value.
+ //
+
+ if (ScbSnapshot->AllocationSize < NextScb->Header.AllocationSize.QuadPart) {
+
+ NextScb->Header.AllocationSize.QuadPart = ScbSnapshot->AllocationSize;
+ }
+
+ if (FlagOn(NextScb->Header.AllocationSize.LowPart, 1)) {
+
+ NextScb->Header.AllocationSize.LowPart -= 1;
+ SetFlag(NextScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT);
+
+ } else {
+
+ ClearFlag(NextScb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT);
+ }
+
+ //
+ // Update the FastIoField.
+ //
+
+ NtfsAcquireFsrtlHeader( NextScb );
+ NextScb->Header.IsFastIoPossible = NtfsIsFastIoPossible( NextScb );
+ NtfsReleaseFsrtlHeader( NextScb );
+
+ if (ScbSnapshot->FileSize < NextScb->Header.FileSize.QuadPart) {
+
+ NextScb->Header.FileSize.QuadPart = ScbSnapshot->FileSize;
+ }
+
+ if (ScbSnapshot->ValidDataLength < NextScb->Header.ValidDataLength.QuadPart) {
+
+ NextScb->Header.ValidDataLength.QuadPart = ScbSnapshot->ValidDataLength;
+ }
+
+ //
+ // Truncate the Mcb to 0 for user data streams and to the
+ // allocation size for other streams.
+ //
+
+ if (NtfsIsTypeCodeUserData( NextScb->AttributeTypeCode ) &&
+ !FlagOn( NextScb->Fcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ (NtfsSegmentNumber( &NextScb->Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER)) {
+
+ NtfsUnloadNtfsMcbRange( &NextScb->Mcb, (LONGLONG) 0, MAXLONGLONG, FALSE, FALSE );
+
+ } else {
+
+ NtfsUnloadNtfsMcbRange( &NextScb->Mcb,
+ Int64ShraMod32(NextScb->Header.AllocationSize.QuadPart, NextScb->Vcb->ClusterShift),
+ MAXLONGLONG,
+ FALSE,
+ FALSE );
+ }
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+
+ } while (ScbSnapshot != &IrpContext->ScbSnapshot);
+ }
+
+ //ASSERTMSG( "***Failed to abort transaction, volume is corrupt", FALSE );
+
+ //
+ // Clear the transaction Id in the IrpContext to make sure we don't
+ // try to write any log records in the complete request.
+ //
+
+ IrpContext->TransactionId = 0;
+ }
+
+ //
+ // If this isn't the top-level request then make sure to pass the real
+ // error back to the top level.
+ //
+
+ if (IrpContext != IrpContext->TopLevelIrpContext) {
+
+ //
+ // Make sure this error is returned to the top level guy.
+ // If the status is FILE_LOCK_CONFLICT then we are using this
+ // value to stop some lower level request. Convert it to
+ // STATUS_CANT_WAIT so the top-level request will retry.
+ //
+
+ if (NT_SUCCESS( IrpContext->TopLevelIrpContext->ExceptionStatus )) {
+
+ if (ExceptionCode == STATUS_FILE_LOCK_CONFLICT) {
+
+ IrpContext->TopLevelIrpContext->ExceptionStatus = STATUS_CANT_WAIT;
+
+ } else {
+
+ IrpContext->TopLevelIrpContext->ExceptionStatus = ExceptionCode;
+ }
+ }
+ }
+
+ //
+ // If the status is cant wait then send the request off to the fsp.
+ //
+
+ TopLevelRequest = NtfsIsTopLevelRequest( IrpContext );
+
+ //
+ // We want to look at the LOG_FILE_FULL or CANT_WAIT cases and consider
+ // if we want to post the request. We only post requests at the top
+ // level.
+ //
+
+ if (ExceptionCode == STATUS_LOG_FILE_FULL ||
+ ExceptionCode == STATUS_CANT_WAIT) {
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ //
+ // If we are top level, we will either post it or retry.
+ //
+
+ if (TopLevelRequest) {
+
+ //
+ // See if we are supposed to post the request.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_FORCE_POST )) {
+
+ PostIrpContext = IrpContext;
+
+ //
+ // Otherwise we will retry this request in the original thread.
+ //
+
+ } else {
+
+ Retry = TRUE;
+ }
+
+ //
+ // Otherwise we will complete the request, see if there is any
+ // related processing to do.
+ //
+
+ } else {
+
+ //
+ // We are the top level Ntfs call. If we are processing a
+ // LOG_FILE_FULL condition then there may be no one above us
+ // who can do the checkpoint. Go ahead and fire off a dummy
+ // request. Do an unsafe test on the flag since it won't hurt
+ // to generate an occasional additional request.
+ //
+
+ if ((ExceptionCode == STATUS_LOG_FILE_FULL) &&
+ (IrpContext->TopLevelIrpContext == IrpContext) &&
+ !FlagOn( IrpContext->Vcb->CheckpointFlags, VCB_DUMMY_CHECKPOINT_POSTED )) {
+
+ //
+ // Create a dummy IrpContext but protect this request with
+ // a try-except to catch any allocation failures.
+ //
+
+ try {
+
+ PostIrpContext = NtfsCreateIrpContext( NULL, TRUE );
+ PostIrpContext->Vcb = IrpContext->Vcb;
+ PostIrpContext->LastRestartArea = PostIrpContext->Vcb->LastRestartArea;
+
+ NtfsAcquireCheckpoint( IrpContext, IrpContext->Vcb );
+ SetFlag( IrpContext->Vcb->CheckpointFlags, VCB_DUMMY_CHECKPOINT_POSTED );
+ NtfsReleaseCheckpoint( IrpContext, IrpContext->Vcb );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+ }
+
+ //
+ // If this is a paging write and we are not the top level
+ // request then we need to return STATUS_FILE_LOCk_CONFLICT
+ // to make MM happy (and keep the pages dirty) and to
+ // prevent this request from retrying the request.
+ //
+
+ ExceptionCode = STATUS_FILE_LOCK_CONFLICT;
+ }
+ }
+ }
+
+ if (PostIrpContext) {
+
+ NTSTATUS PostStatus;
+
+ //
+ // Clear the current error code.
+ //
+
+ PostIrpContext->ExceptionStatus = 0;
+
+ //
+ // We need a try-except in case the Lock buffer call fails.
+ //
+
+ try {
+
+ PostStatus = NtfsPostRequest( PostIrpContext, PostIrpContext->OriginatingIrp );
+
+ //
+ // If we posted the original request we don't have any
+ // completion work to do.
+ //
+
+ if (PostIrpContext == IrpContext) {
+
+ Irp = NULL;
+ IrpContext = NULL;
+ ExceptionCode = PostStatus;
+ }
+
+ } except (EXCEPTION_EXECUTE_HANDLER) {
+
+ //
+ // If we don't have an error in the IrpContext then
+ // generate a generic IO error. We can't use the
+ // original status code if either LOG_FILE_FULL or
+ // CANT_WAIT. We would complete the Irp yet retry the
+ // request.
+ //
+
+ if (IrpContext == PostIrpContext) {
+
+ if (PostIrpContext->ExceptionStatus == 0) {
+
+ if ((ExceptionCode == STATUS_LOG_FILE_FULL) ||
+ (ExceptionCode == STATUS_CANT_WAIT)) {
+
+ ExceptionCode = STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ } else {
+
+ ExceptionCode = PostIrpContext->ExceptionStatus;
+ }
+ }
+ }
+ }
+
+ //
+ // We have the Irp. We either need to complete this request or allow
+ // the top level thread to retry.
+ //
+
+ if (ARGUMENT_PRESENT(Irp)) {
+
+ //
+ // If this is a top level Ntfs request and we still have the Irp
+ // it means we will be retrying the request. In that case
+ // mark the Irp Context so it doesn't go away.
+ //
+
+ if (Retry) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
+ NtfsCompleteRequest( &IrpContext, NULL, ExceptionCode );
+
+ //
+ // Clear the status code in the Irp Context.
+ //
+
+ IrpContext->ExceptionStatus = 0;
+
+ } else {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, ExceptionCode );
+ }
+
+ } else if (IrpContext != NULL) {
+
+ NtfsCompleteRequest( &IrpContext, NULL, ExceptionCode );
+ }
+
+ return ExceptionCode;
+}
+
+
+VOID
+NtfsCompleteRequest (
+ IN OUT PIRP_CONTEXT *IrpContext OPTIONAL,
+ IN OUT PIRP *Irp OPTIONAL,
+ IN NTSTATUS Status
+ )
+
+/*++
+
+Routine Description:
+
+ This routine completes an IRP and deallocates the IrpContext
+
+Arguments:
+
+ Irp - Supplies the Irp being processed
+
+ Status - Supplies the status to complete the Irp with
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // If we have an Irp Context then unpin all of the repinned bcbs
+ // we might have collected, and delete the Irp context. Delete Irp
+ // Context will zero out our pointer for us.
+ //
+
+ if (ARGUMENT_PRESENT(IrpContext)) {
+
+ ASSERT_IRP_CONTEXT( *IrpContext );
+
+ if ((*IrpContext)->TransactionId != 0) {
+ NtfsCommitCurrentTransaction( *IrpContext );
+ }
+
+ (*IrpContext)->ExceptionStatus = Status;
+
+ //
+ // Always store the status in the top level Irp Context unless
+ // there is already an error code.
+ //
+
+ if (NT_SUCCESS( (*IrpContext)->TopLevelIrpContext->ExceptionStatus )) {
+ (*IrpContext)->TopLevelIrpContext->ExceptionStatus = Status;
+ }
+
+ NtfsDeleteIrpContext( IrpContext );
+ }
+
+ //
+ // If we have an Irp then complete the irp.
+ //
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP( *Irp );
+
+ if (NT_ERROR( Status ) &&
+ FlagOn( (*Irp)->Flags, IRP_INPUT_OPERATION )) {
+
+ (*Irp)->IoStatus.Information = 0;
+ }
+
+ IrpSp = IoGetCurrentIrpStackLocation( *Irp );
+
+ (*Irp)->IoStatus.Status = Status;
+
+ IoCompleteRequest( *Irp, IO_DISK_INCREMENT );
+
+ //
+ // Zero out our input pointer
+ //
+
+ *Irp = NULL;
+ }
+
+ return;
+}
+
+
+BOOLEAN
+NtfsFastIoCheckIfPossible (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ IN BOOLEAN CheckForReadOperation,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if fast i/o is possible for a read/write operation
+
+Arguments:
+
+ FileObject - Supplies the file object used in the query
+
+ FileOffset - Supplies the starting byte offset for the read/write operation
+
+ Length - Supplies the length, in bytes, of the read/write operation
+
+ Wait - Indicates if we can wait
+
+ LockKey - Supplies the lock key
+
+ CheckForReadOperation - Indicates if this is a check for a read or write
+ operation
+
+ IoStatus - Receives the status of the operation if our return value is
+ FastIoReturnError
+
+Return Value:
+
+ BOOLEAN - TRUE if fast I/O is possible and FALSE if the caller needs
+ to take the long route
+
+--*/
+
+{
+ PSCB Scb;
+
+ LARGE_INTEGER LargeLength;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+ UNREFERENCED_PARAMETER( IoStatus );
+ UNREFERENCED_PARAMETER( Wait );
+
+ PAGED_CODE();
+
+ //
+ // Decode the file object to get our fcb, the only one we want
+ // to deal with is a UserFileOpen
+ //
+
+ if ((Scb = NtfsFastDecodeUserFileOpen( FileObject )) == NULL) {
+
+ return FALSE;
+ }
+
+ LargeLength = RtlConvertUlongToLargeInteger( Length );
+
+ //
+ // Based on whether this is a read or write operation we call
+ // fsrtl check for read/write
+ //
+
+ if (CheckForReadOperation) {
+
+ if (Scb->ScbType.Data.FileLock == NULL
+ || FsRtlFastCheckLockForRead( Scb->ScbType.Data.FileLock,
+ FileOffset,
+ &LargeLength,
+ LockKey,
+ FileObject,
+ PsGetCurrentProcess() )) {
+
+ return TRUE;
+ }
+
+ } else {
+
+ if ((Scb->ScbType.Data.FileLock == NULL
+ || FsRtlFastCheckLockForWrite( Scb->ScbType.Data.FileLock,
+ FileOffset,
+ &LargeLength,
+ LockKey,
+ FileObject,
+ PsGetCurrentProcess() ))
+
+ &&
+
+ (!FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK ) ||
+ NtfsReserveClusters( NULL, Scb, FileOffset->QuadPart, Length))) {
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+BOOLEAN
+NtfsFastQueryBasicInfo (
+ IN PFILE_OBJECT FileObject,
+ IN BOOLEAN Wait,
+ IN OUT PFILE_BASIC_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is for the fast query call for basic file information.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ Wait - Indicates if we are allowed to wait for the information
+
+ Buffer - Supplies the output buffer to receive the basic information
+
+ IoStatus - Receives the final status of the operation
+
+Return Value:
+
+ BOOLEAN _ TRUE if the operation is successful and FALSE if the caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results = FALSE;
+ IRP_CONTEXT IrpContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ BOOLEAN FcbAcquired = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Prepare the dummy irp context
+ //
+
+ RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) );
+ IrpContext.NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext.NodeByteSize = sizeof(IRP_CONTEXT);
+ if (Wait) {
+ SetFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ } else {
+ ClearFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ }
+
+ //
+ // Determine the type of open for the input file object. The callee really
+ // ignores the irp context for us.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ FsRtlEnterFileSystem();
+
+ try {
+
+ if (ExAcquireResourceShared( Fcb->Resource, Wait )) {
+
+ FcbAcquired = TRUE;
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) ||
+ !FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ try_return( NOTHING );
+ }
+
+ } else {
+
+ try_return( NOTHING );
+ }
+
+ switch (TypeOfOpen) {
+
+ case UserFileOpen:
+#ifdef _CAIRO_
+ case UserPropertySetOpen:
+#endif // _CAIRO_
+ case UserDirectoryOpen:
+ case StreamFileOpen:
+
+ //
+ // Fill in the basic information fields
+ //
+
+ Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime;
+ Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime;
+ Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime;
+
+ Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess;
+
+ Buffer->FileAttributes = Fcb->Info.FileAttributes;
+
+ ClearFlag( Buffer->FileAttributes,
+ ~FILE_ATTRIBUTE_VALID_FLAGS
+ | FILE_ATTRIBUTE_TEMPORARY );
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
+ }
+
+ //
+ // If this is not the main stream on the file then use the stream based
+ // compressed bit.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ if (Scb->CompressionUnit != 0) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+ }
+
+ //
+ // Set the temporary flag if set in the Scb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
+ }
+
+ //
+ // If there are no flags set then explicitly set the NORMAL flag.
+ //
+
+ if (Buffer->FileAttributes == 0) {
+
+ Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ Results = TRUE;
+
+ IoStatus->Information = sizeof(FILE_BASIC_INFORMATION);
+
+ IoStatus->Status = STATUS_SUCCESS;
+
+ break;
+
+ default:
+
+ NOTHING;
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ if (FcbAcquired) { ExReleaseResource( Fcb->Resource ); }
+
+ FsRtlExitFileSystem();
+ }
+
+ //
+ // Return to our caller
+ //
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsFastQueryStdInfo (
+ IN PFILE_OBJECT FileObject,
+ IN BOOLEAN Wait,
+ IN OUT PFILE_STANDARD_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is for the fast query call for standard file information.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ Wait - Indicates if we are allowed to wait for the information
+
+ Buffer - Supplies the output buffer to receive the basic information
+
+ IoStatus - Receives the final status of the operation
+
+Return Value:
+
+ BOOLEAN _ TRUE if the operation is successful and FALSE if the caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results = FALSE;
+ IRP_CONTEXT IrpContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ BOOLEAN FcbAcquired = FALSE;
+ BOOLEAN FsRtlHeaderLocked = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Prepare the dummy irp context
+ //
+
+ RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) );
+ IrpContext.NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext.NodeByteSize = sizeof(IRP_CONTEXT);
+ if (Wait) {
+ SetFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ } else {
+ ClearFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ }
+
+ //
+ // Determine the type of open for the input file object. The callee really
+ // ignores the irp context for us.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ FsRtlEnterFileSystem();
+
+ try {
+
+ switch (TypeOfOpen) {
+
+ case UserFileOpen:
+#ifdef _CAIRO_
+ case UserPropertySetOpen:
+#endif // _CAIRO_
+ case UserDirectoryOpen:
+ case StreamFileOpen:
+
+ if (Scb->Header.PagingIoResource != NULL) {
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE );
+ }
+
+ FsRtlLockFsRtlHeader( &Scb->Header );
+ FsRtlHeaderLocked = TRUE;
+
+ if (ExAcquireResourceShared( Fcb->Resource, Wait )) {
+
+ FcbAcquired = TRUE;
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) ||
+ !FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ try_return( NOTHING );
+ }
+
+ } else {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Fill in the standard information fields. If the
+ // Scb is not initialized then take the long route
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED) &&
+ (Scb->AttributeTypeCode != $INDEX_ALLOCATION)) {
+
+ NOTHING;
+
+ } else {
+
+ Buffer->AllocationSize.QuadPart = Scb->TotalAllocated;
+ Buffer->EndOfFile = Scb->Header.FileSize;
+ Buffer->NumberOfLinks = Fcb->LinkCount;
+
+ if (Ccb != NULL) {
+
+ if (FlagOn(Ccb->Flags, CCB_FLAG_OPEN_AS_FILE)) {
+
+ if (Scb->Fcb->LinkCount == 0 ||
+ (Ccb->Lcb != NULL &&
+ FlagOn( Ccb->Lcb->LcbState, LCB_STATE_DELETE_ON_CLOSE ))) {
+
+ Buffer->DeletePending = TRUE;
+ }
+
+ } else {
+
+ Buffer->DeletePending = BooleanFlagOn( Scb->ScbState, SCB_STATE_DELETE_ON_CLOSE );
+ }
+ }
+
+ Buffer->Directory = BooleanIsDirectory( &Fcb->Info );
+
+ IoStatus->Information = sizeof(FILE_STANDARD_INFORMATION);
+
+ IoStatus->Status = STATUS_SUCCESS;
+
+ Results = TRUE;
+ }
+
+ break;
+
+ default:
+
+ NOTHING;
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ if (FcbAcquired) { ExReleaseResource( Fcb->Resource ); }
+
+ if (FsRtlHeaderLocked) {
+ FsRtlUnlockFsRtlHeader( &Scb->Header );
+ if (Scb->Header.PagingIoResource != NULL) {
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+ }
+
+ FsRtlExitFileSystem();
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return Results;
+}
+
+
+BOOLEAN
+NtfsFastQueryNetworkOpenInfo (
+ IN PFILE_OBJECT FileObject,
+ IN BOOLEAN Wait,
+ OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is for the fast query network open call.
+
+Arguments:
+
+ FileObject - Supplies the file object used in this operation
+
+ Wait - Indicates if we are allowed to wait for the information
+
+ Buffer - Supplies the output buffer to receive the information
+
+ IoStatus - Receives the final status of the operation
+
+Return Value:
+
+ BOOLEAN _ TRUE if the operation is successful and FALSE if the caller
+ needs to take the long route.
+
+--*/
+
+{
+ BOOLEAN Results = FALSE;
+ IRP_CONTEXT IrpContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ BOOLEAN FcbAcquired = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Prepare the dummy irp context
+ //
+
+ RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) );
+ IrpContext.NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext.NodeByteSize = sizeof(IRP_CONTEXT);
+ if (Wait) {
+ SetFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ } else {
+ ClearFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+ }
+
+ //
+ // Determine the type of open for the input file object. The callee really
+ // ignores the irp context for us.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE );
+
+ FsRtlEnterFileSystem();
+
+ try {
+
+ if (ExAcquireResourceShared( Fcb->Resource, Wait )) {
+
+ FcbAcquired = TRUE;
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) ||
+ !FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED ) ||
+ (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED) &&
+ (Scb->AttributeTypeCode != $INDEX_ALLOCATION))) {
+
+ try_return( NOTHING );
+ }
+
+ } else {
+
+ try_return( NOTHING );
+ }
+
+ switch (TypeOfOpen) {
+
+ case UserFileOpen:
+#ifdef _CAIRO_
+ case UserPropertySetOpen:
+#endif // _CAIRO_
+ case UserDirectoryOpen:
+ case StreamFileOpen:
+
+ //
+ // Fill in the basic information fields
+ //
+
+ Buffer->CreationTime.QuadPart = Fcb->Info.CreationTime;
+ Buffer->LastWriteTime.QuadPart = Fcb->Info.LastModificationTime;
+ Buffer->ChangeTime.QuadPart = Fcb->Info.LastChangeTime;
+
+ Buffer->LastAccessTime.QuadPart = Fcb->CurrentLastAccess;
+
+ Buffer->FileAttributes = Fcb->Info.FileAttributes;
+
+ ClearFlag( Buffer->FileAttributes,
+ ~FILE_ATTRIBUTE_VALID_FLAGS | FILE_ATTRIBUTE_TEMPORARY );
+
+ if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ if (IsDirectory( &Fcb->Info )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_DIRECTORY );
+
+ //
+ // If this is not the main stream then copy the compression
+ // value from this Scb.
+ //
+
+ } else if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+
+ Buffer->AllocationSize.QuadPart =
+ Buffer->EndOfFile.QuadPart = 0;
+
+ //
+ // Return a non-zero size only for data streams.
+ //
+
+ } else {
+
+ Buffer->AllocationSize.QuadPart = Scb->TotalAllocated;
+ Buffer->EndOfFile = Scb->Header.FileSize;
+
+ //
+ // If not the unnamed data stream then use the Scb
+ // compression value.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ if (FlagOn( Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+
+ } else {
+
+ ClearFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_COMPRESSED );
+ }
+ }
+ }
+
+ //
+ // Set the temporary flag if set in the Scb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+
+ SetFlag( Buffer->FileAttributes, FILE_ATTRIBUTE_TEMPORARY );
+ }
+
+ //
+ // If there are no flags set then explicitly set the NORMAL flag.
+ //
+
+ if (Buffer->FileAttributes == 0) {
+
+ Buffer->FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ IoStatus->Information = sizeof(FILE_NETWORK_OPEN_INFORMATION);
+
+ IoStatus->Status = STATUS_SUCCESS;
+
+ Results = TRUE;
+
+ break;
+
+ default:
+
+ NOTHING;
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ if (FcbAcquired) { ExReleaseResource( Fcb->Resource ); }
+
+ FsRtlExitFileSystem();
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return Results;
+}
+
+
+#ifdef _CAIRO_
+VOID
+NtfsFastIoQueryCompressionInfo (
+ IN PFILE_OBJECT FileObject,
+ OUT PFILE_COMPRESSION_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is a fast call for returning the comprssion information
+ for a file. It assumes that the caller has an exception handler and
+ will treat exceptions as an error. Therefore, this routine only uses
+ a finally clause to cleanup any resources, and it does not worry about
+ returning errors in the IoStatus.
+
+Arguments:
+
+ FileObject - FileObject for the file on which the compressed information
+ is desired.
+
+ Buffer - Buffer to receive the compressed data information (as defined
+ in ntioapi.h)
+
+ IoStatus - Returns STATUS_SUCCESS and the size of the information being
+ returned.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ IRP_CONTEXT IrpContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ BOOLEAN ScbAcquired = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Prepare the dummy irp context
+ //
+
+ RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) );
+ IrpContext.NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext.NodeByteSize = sizeof(IRP_CONTEXT);
+ SetFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ //
+ // Assume success (otherwise caller should see the exception)
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = sizeof(FILE_COMPRESSION_INFORMATION);
+
+ //
+ // Determine the type of open for the input file object. The callee really
+ // ignores the irp context for us.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE);
+
+ FsRtlEnterFileSystem();
+
+ try {
+
+ NtfsAcquireSharedScb( &IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ //
+ // Now return the compressed data information.
+ //
+
+ Buffer->CompressedFileSize.QuadPart = Scb->TotalAllocated;
+ Buffer->CompressionFormat = (USHORT)(Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK);
+ if (Buffer->CompressionFormat != 0) {
+ Buffer->CompressionFormat += 1;
+ }
+ Buffer->CompressionUnitShift = (UCHAR)(Scb->CompressionUnitShift + Vcb->ClusterShift);
+ Buffer->ChunkShift = NTFS_CHUNK_SHIFT;
+ Buffer->ClusterShift = (UCHAR)Vcb->ClusterShift;
+ Buffer->Reserved[0] = Buffer->Reserved[1] = Buffer->Reserved[2] = 0;
+
+ } finally {
+
+ if (ScbAcquired) {NtfsReleaseScb( &IrpContext, Scb );}
+ FsRtlExitFileSystem();
+ }
+}
+
+
+VOID
+NtfsFastIoQueryCompressedSize (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ OUT PULONG CompressedSize
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is a fast call for returning the the size of a specified
+ compression unit. It assumes that the caller has an exception handler and
+ will treat exceptions as an error. Therefore, this routine does not even
+ have a finally clause, since it does not acquire any resources directly.
+
+Arguments:
+
+ FileObject - FileObject for the file on which the compressed information
+ is desired.
+
+ FileOffset - FileOffset for a compression unit for which the allocated size
+ is desired.
+
+ CompressedSize - Returns the allocated size of the compression unit.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ IRP_CONTEXT IrpContext;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG SizeInBytes;
+ LONGLONG ClusterCount = 0;
+
+ PAGED_CODE();
+
+ //
+ // Prepare the dummy irp context
+ //
+
+ RtlZeroMemory( &IrpContext, sizeof(IRP_CONTEXT) );
+ IrpContext.NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext.NodeByteSize = sizeof(IRP_CONTEXT);
+ SetFlag(IrpContext.Flags, IRP_CONTEXT_FLAG_WAIT);
+
+ //
+ // Determine the type of open for the input file object. The callee really
+ // ignores the irp context for us.
+ //
+
+ TypeOfOpen = NtfsDecodeFileObject( &IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, FALSE);
+ IrpContext.Vcb = Vcb;
+
+ ASSERT(Scb->CompressionUnit != 0);
+ ASSERT((FileOffset->QuadPart & (Scb->CompressionUnit - 1)) == 0);
+
+ //
+ // Calculate the Vcn the caller wants, and initialize our output.
+ //
+
+ Vcn = LlClustersFromBytes( Vcb, FileOffset->QuadPart );
+ *CompressedSize = 0;
+
+ //
+ // Loop as long as we are looking up allocated Vcns.
+ //
+
+ while (NtfsLookupAllocation(&IrpContext, Scb, Vcn, &Lcn, &ClusterCount, NULL, NULL)) {
+
+ SizeInBytes = LlBytesFromClusters( Vcb, ClusterCount );
+
+ //
+ // If this allocated run goes beyond the end of the compresion unit, then
+ // we know it is fully allocated.
+ //
+
+ if ((SizeInBytes + *CompressedSize) > Scb->CompressionUnit) {
+ *CompressedSize = Scb->CompressionUnit;
+ break;
+ }
+
+ *CompressedSize += (ULONG)SizeInBytes;
+ Vcn += ClusterCount;
+ }
+}
+#endif _CAIRO_
+
+
+VOID
+NtfsRaiseInformationHardError (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to generate a popup in the event a corrupt file
+ or disk is encountered. The main purpose of the routine is to find
+ a name to pass to the popup package. If there is no Fcb we will take
+ the volume name out of the Vcb. If the Fcb has an Lcb in its Lcb list,
+ we will construct the name by walking backwards through the Lcb's.
+ If the Fcb has no Lcb but represents a system file, we will return
+ a default system string. If the Fcb represents a user file, but we
+ have no Lcb, we will use the name in the file object for the current
+ request.
+
+Arguments:
+
+ Status - Error status.
+
+ FileReference - File reference being accessed in Mft when error occurred.
+
+ Fcb - If specified, this is the Fcb being used when the error was encountered.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ FCB_TABLE_ELEMENT Key;
+ PFCB_TABLE_ELEMENT Entry = NULL;
+
+ PKTHREAD Thread;
+ UNICODE_STRING Name;
+ ULONG NameLength;
+
+ PFILE_OBJECT FileObject;
+
+ WCHAR *NewBuffer;
+
+ PIRP Irp = NULL;
+ PIO_STACK_LOCATION IrpSp;
+
+ //
+ // Return if there is no originating Irp, for example when originating
+ // from NtfsPerformHotFix.
+ //
+
+ if (IrpContext->OriginatingIrp == NULL) {
+ return;
+ }
+
+ if (IrpContext->OriginatingIrp->Type == IO_TYPE_IRP) {
+
+ Irp = IrpContext->OriginatingIrp;
+ IrpSp = IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp );
+ FileObject = IrpSp->FileObject;
+
+ } else {
+
+ return;
+ }
+
+ NewBuffer = NULL;
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // If the Fcb isn't specified and the file reference is, then
+ // try to get the Fcb from the Fcb table.
+ //
+
+ if (!ARGUMENT_PRESENT( Fcb )
+ && ARGUMENT_PRESENT( FileReference )) {
+
+ Key.FileReference = *FileReference;
+
+ NtfsAcquireFcbTable( IrpContext, IrpContext->Vcb );
+ Entry = RtlLookupElementGenericTable( &IrpContext->Vcb->FcbTable,
+ &Key );
+ NtfsReleaseFcbTable( IrpContext, IrpContext->Vcb );
+
+ if (Entry != NULL) {
+
+ Fcb = Entry->Fcb;
+ }
+ }
+
+ if (Irp == NULL ||
+ IoIsSystemThread( IrpContext->OriginatingIrp->Tail.Overlay.Thread )) {
+
+ Thread = NULL;
+
+ } else {
+
+ Thread = (PKTHREAD)IrpContext->OriginatingIrp->Tail.Overlay.Thread;
+ }
+
+ //
+ // If there is no Fcb and no file reference we use the name in the
+ // Vpb for the volume. If there is a file reference then assume
+ // the error occurred in a system file.
+ //
+
+ if (!ARGUMENT_PRESENT( Fcb )) {
+
+ if (!ARGUMENT_PRESENT( FileReference )) {
+
+ Name.MaximumLength = Name.Length = IrpContext->Vcb->Vpb->VolumeLabelLength;
+ Name.Buffer = (PWCHAR) IrpContext->Vcb->Vpb->VolumeLabel;
+
+ } else if (NtfsSegmentNumber( FileReference ) <= UPCASE_TABLE_NUMBER) {
+
+ Name = NtfsSystemFiles[NtfsSegmentNumber( FileReference )];
+
+ } else {
+
+ Name = NtfsSystemFiles[0];
+ }
+
+ //
+ // If the name has an Lcb, we contruct a name with a chain of Lcb's.
+ //
+
+ } else if (!IsListEmpty( &Fcb->LcbQueue )) {
+
+ BOOLEAN LeadingBackslash;
+
+ //
+ // Get the length of the list.
+ //
+
+ NameLength = NtfsLookupNameLengthViaLcb( Fcb, &LeadingBackslash );
+
+ //
+ // We now know the length of the name. Allocate and fill this buffer.
+ //
+
+ NewBuffer = NtfsAllocatePool(PagedPool, NameLength );
+
+ Name.MaximumLength = Name.Length = (USHORT) NameLength;
+ Name.Buffer = NewBuffer;
+
+ //
+ // Now insert the name.
+ //
+
+ NtfsFileNameViaLcb( Fcb, NewBuffer, NameLength, NameLength );
+
+ //
+ // Check if this is a system file.
+ //
+
+ } else if (NtfsSegmentNumber( &Fcb->FileReference ) < FIRST_USER_FILE_NUMBER) {
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) <= UPCASE_TABLE_NUMBER) {
+
+ Name = NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )];
+
+ } else {
+
+ Name = NtfsSystemFiles[0];
+ }
+
+ //
+ // In this case we contruct a name out of the file objects in the
+ // Originating Irp. If there is no file object or file object buffer
+ // we generate an unknown file message.
+ //
+
+ } else if (FileObject == NULL
+ || (IrpContext->MajorFunction == IRP_MJ_CREATE
+ && BooleanFlagOn( IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID ))
+ || (FileObject->FileName.Length == 0
+ && (FileObject->RelatedFileObject == NULL
+ || IrpContext->MajorFunction != IRP_MJ_CREATE))) {
+
+ Name = NtfsUnknownFile;
+
+ //
+ // If there is a valid name in the file object we use that.
+ //
+
+ } else if ((FileObject->FileName.MaximumLength != 0) &&
+ (FileObject->FileName.Length != 0) &&
+ (FileObject->FileName.Buffer[0] == L'\\')) {
+
+ Name = FileObject->FileName;
+
+ //
+ // We have to construct the name.
+ //
+
+ } else {
+
+ if ((FileObject->FileName.MaximumLength != 0) &&
+ (FileObject->FileName.Length != 0)) {
+
+ NameLength = FileObject->FileName.Length;
+
+ if (((PFILE_OBJECT) FileObject->RelatedFileObject)->FileName.Length != 2) {
+
+ NameLength += 2;
+ }
+
+ } else {
+
+ NameLength = 0;
+ }
+
+ NameLength += ((PFILE_OBJECT) FileObject->RelatedFileObject)->FileName.Length;
+
+ NewBuffer = NtfsAllocatePool(PagedPool, NameLength );
+
+ Name.MaximumLength = Name.Length = (USHORT) NameLength;
+ Name.Buffer = NewBuffer;
+
+ if (FileObject->FileName.Length != 0) {
+
+ NameLength -= FileObject->FileName.Length;
+
+ RtlCopyMemory( Add2Ptr( NewBuffer, NameLength ),
+ FileObject->FileName.Buffer,
+ FileObject->FileName.Length );
+
+ NameLength -= sizeof( WCHAR );
+
+ *((PWCHAR) Add2Ptr( NewBuffer, NameLength )) = L'\\';
+ }
+
+ if (NameLength != 0) {
+
+ FileObject = (PFILE_OBJECT) FileObject->RelatedFileObject;
+
+ RtlCopyMemory( NewBuffer,
+ FileObject->FileName.Buffer,
+ FileObject->FileName.Length );
+ }
+ }
+
+ //
+ // Now generate a popup.
+ //
+
+ IoRaiseInformationalHardError( Status, &Name, Thread );
+
+ } finally {
+
+ if (NewBuffer != NULL) {
+
+ NtfsFreePool( NewBuffer );
+ }
+ }
+
+ return;
+}
+
+
+PTOP_LEVEL_CONTEXT
+NtfsSetTopLevelIrp (
+ IN PTOP_LEVEL_CONTEXT TopLevelContext,
+ IN BOOLEAN ForceTopLevel,
+ IN BOOLEAN SetTopLevel
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to set up the top level context in the thread local
+ storage. Ntfs always puts its own context in this location and restores
+ the previous value on exit. This routine will determine if this request is
+ top level and top level ntfs. It will return a pointer to the top level ntfs
+ context stored in the thread local storage on return.
+
+Arguments:
+
+ TopLevelContext - This is the local top level context for our caller.
+
+ ForceTopLevel - Always use the input top level context.
+
+ SetTopLevel - Only applies if the ForceTopLevel value is TRUE. Indicates
+ if we should make this look like the top level request.
+
+Return Value:
+
+ PTOP_LEVEL_CONTEXT - Pointer to the top level ntfs context for this thread.
+ It may be the same as passed in by the caller. In that case the fields
+ will be initialized.
+
+--*/
+
+{
+ PTOP_LEVEL_CONTEXT CurrentTopLevelContext;
+ ULONG StackBottom;
+ ULONG StackTop;
+ BOOLEAN TopLevelRequest = TRUE;
+ BOOLEAN TopLevelNtfs = TRUE;
+
+ BOOLEAN ValidCurrentTopLevel = FALSE;
+
+ //
+ // Get the current value out of the thread local storage. If it is a zero
+ // value or not a pointer to a valid ntfs top level context or a valid
+ // Fsrtl value then we are the top level request.
+ //
+
+ CurrentTopLevelContext = NtfsGetTopLevelContext();
+
+ //
+ // Check if this is a valid Ntfs top level context.
+ //
+
+ IoGetStackLimits( &StackTop, &StackBottom);
+
+ if (((ULONG) CurrentTopLevelContext <= StackBottom - sizeof( TOP_LEVEL_CONTEXT )) &&
+ ((ULONG) CurrentTopLevelContext >= StackTop) &&
+ !FlagOn( (ULONG) CurrentTopLevelContext, 0x3 ) &&
+ (CurrentTopLevelContext->Ntfs == 0x5346544e)) {
+
+ ValidCurrentTopLevel = TRUE;
+ }
+
+ //
+ // If we are to force this request to be top level then set the
+ // TopLevelRequest flag according to the SetTopLevel input.
+ //
+
+ if (ForceTopLevel) {
+
+ TopLevelRequest = SetTopLevel;
+
+ //
+ // If the value is NULL then we are top level everything.
+ //
+
+ } else if (CurrentTopLevelContext == NULL) {
+
+ NOTHING;
+
+ //
+ // If this has one of the Fsrtl magic numbers then we were called from
+ // either the fast io path or the mm paging io path.
+ //
+
+ } else if ((ULONG) CurrentTopLevelContext <= FSRTL_MAX_TOP_LEVEL_IRP_FLAG) {
+
+ TopLevelRequest = FALSE;
+
+ } else if (ValidCurrentTopLevel &&
+ !FlagOn( CurrentTopLevelContext->TopLevelIrpContext->Flags,
+ IRP_CONTEXT_FLAG_CALL_SELF )) {
+
+ TopLevelRequest = FALSE;
+ TopLevelNtfs = FALSE;
+ }
+
+ //
+ // If we are the top level ntfs then initialize the caller's structure
+ // and store it in the thread local storage.
+ //
+
+ if (TopLevelNtfs) {
+
+ TopLevelContext->Ntfs = 0x5346544e;
+ TopLevelContext->SavedTopLevelIrp = (PIRP) CurrentTopLevelContext;
+ TopLevelContext->TopLevelIrpContext = NULL;
+ TopLevelContext->TopLevelRequest = TopLevelRequest;
+
+ if (ValidCurrentTopLevel) {
+
+ TopLevelContext->VboBeingHotFixed = CurrentTopLevelContext->VboBeingHotFixed;
+ TopLevelContext->ScbBeingHotFixed = CurrentTopLevelContext->ScbBeingHotFixed;
+ TopLevelContext->ValidSavedTopLevel = TRUE;
+ TopLevelContext->OverflowReadThread = CurrentTopLevelContext->OverflowReadThread;
+
+ } else {
+
+ TopLevelContext->VboBeingHotFixed = 0;
+ TopLevelContext->ScbBeingHotFixed = NULL;
+ TopLevelContext->ValidSavedTopLevel = FALSE;
+ TopLevelContext->OverflowReadThread = FALSE;
+ }
+
+ IoSetTopLevelIrp( (PIRP) TopLevelContext );
+ return TopLevelContext;
+ }
+
+ return CurrentTopLevelContext;
+}
+
+#ifdef NTFS_CHECK_BITMAP
+BOOLEAN NtfsForceBitmapBugcheck = FALSE;
+BOOLEAN NtfsDisableBitmapCheck = FALSE;
+
+VOID
+NtfsBadBitmapCopy (
+ IN PIRP_CONTEXT IrpContext,
+ IN ULONG BadBit,
+ IN ULONG Length
+ )
+{
+ if (!NtfsDisableBitmapCheck) {
+
+ DbgPrint("%s:%d %s\n",__FILE__,__LINE__,"Invalid bitmap");
+ DbgBreakPoint();
+
+ if (!NtfsDisableBitmapCheck && NtfsForceBitmapBugcheck) {
+
+ KeBugCheckEx( NTFS_FILE_SYSTEM, (ULONG) IrpContext, BadBit, Length, 0 );
+ }
+ }
+ return;
+}
+
+BOOLEAN
+NtfsCheckBitmap (
+ IN PVCB Vcb,
+ IN ULONG Lcn,
+ IN ULONG Count,
+ IN BOOLEAN Set
+ )
+{
+ ULONG BitmapPage;
+ ULONG LastBitmapPage;
+ ULONG BitOffset;
+ ULONG BitsThisPage;
+ BOOLEAN Valid = FALSE;
+
+ BitmapPage = Lcn / (PAGE_SIZE * 8);
+ LastBitmapPage = (Lcn + Count + (PAGE_SIZE * 8) - 1) / (PAGE_SIZE * 8);
+ BitOffset = Lcn & ((PAGE_SIZE * 8) - 1);
+
+ if (LastBitmapPage > Vcb->BitmapPages) {
+
+ return Valid;
+ }
+
+ do {
+
+ BitsThisPage = Count;
+
+ if (BitOffset + Count > (PAGE_SIZE * 8)) {
+
+ BitsThisPage = (PAGE_SIZE * 8) - BitOffset;
+ }
+
+ if (Set) {
+
+ Valid = RtlAreBitsSet( Vcb->BitmapCopy + BitmapPage,
+ BitOffset,
+ BitsThisPage );
+
+ } else {
+
+ Valid = RtlAreBitsClear( Vcb->BitmapCopy + BitmapPage,
+ BitOffset,
+ BitsThisPage );
+ }
+
+ BitOffset = 0;
+ Count -= BitsThisPage;
+ BitmapPage += 1;
+
+ } while (Valid && (BitmapPage < LastBitmapPage));
+
+ if (Count != 0) {
+
+ Valid = FALSE;
+ }
+
+ return Valid;
+}
+#endif
+
+//
+// Debugging support routines used for pool verification. Alas, this works only
+// on checked X86.
+//
+
+#if DBG && i386 && defined (NTFSPOOLCHECK)
+//
+// Number of backtrace items retrieved on X86
+
+
+#define BACKTRACE_DEPTH 8
+
+typedef struct _BACKTRACE
+{
+ ULONG State;
+ ULONG Pad;
+ PVOID Allocate[BACKTRACE_DEPTH];
+ PVOID Free[BACKTRACE_DEPTH];
+} BACKTRACE, *PBACKTRACE;
+
+
+#define STATE_ALLOCATED 'M'
+#define STATE_LOOKASIDE 'J'
+#define STATE_FREE 'Z'
+
+//
+// WARNING! The following depends on pool allocations being either
+// 0 mod PAGE_SIZE (for large blocks)
+// or 8 mod 0x20 (for all other requests)
+//
+
+#define PAGE_ALIGNED(pv) (((ULONG)(pv) & (PAGE_SIZE - 1)) == 0)
+#define IsKernelPoolBlock(pv) (PAGE_ALIGNED(pv) || (((ULONG)(pv) % 0x20) == 8))
+
+PVOID
+NtfsDebugAllocatePoolWithTagNoRaise (
+ POOL_TYPE Pool,
+ ULONG Length,
+ ULONG Tag)
+{
+ ULONG Ignore;
+ PBACKTRACE BackTrace =
+ ExAllocatePoolWithTag( Pool, Length + sizeof (BACKTRACE), Tag );
+
+ if (PAGE_ALIGNED(BackTrace))
+ {
+ return BackTrace;
+ }
+
+ RtlZeroMemory( BackTrace, sizeof (BACKTRACE) );
+ RtlCaptureStackBackTrace( 0, BACKTRACE_DEPTH, BackTrace->Allocate, &Ignore );
+
+ BackTrace->State = STATE_ALLOCATED;
+
+ return BackTrace + 1;
+}
+
+PVOID
+NtfsDebugAllocatePoolWithTag (
+ POOL_TYPE Pool,
+ ULONG Length,
+ ULONG Tag)
+{
+ ULONG Ignore;
+ PBACKTRACE BackTrace =
+ FsRtlAllocatePoolWithTag( Pool, Length + sizeof (BACKTRACE), Tag );
+
+ if (PAGE_ALIGNED(BackTrace))
+ {
+ return BackTrace;
+ }
+
+ RtlZeroMemory( BackTrace, sizeof (BACKTRACE) );
+ RtlCaptureStackBackTrace( 0, BACKTRACE_DEPTH, BackTrace->Allocate, &Ignore );
+
+ BackTrace->State = STATE_ALLOCATED;
+
+ return BackTrace + 1;
+}
+
+VOID
+NtfsDebugFreePool (
+ PVOID pv)
+{
+ if (IsKernelPoolBlock( pv ))
+ {
+ ExFreePool( pv );
+ }
+ else
+ {
+ ULONG Ignore;
+ PBACKTRACE BackTrace = (PBACKTRACE)pv - 1;
+
+ if (BackTrace->State != STATE_ALLOCATED)
+ {
+ DbgBreakPoint( );
+ }
+
+ RtlCaptureStackBackTrace( 0, BACKTRACE_DEPTH, BackTrace->Free, &Ignore );
+
+ BackTrace->State = STATE_FREE;
+
+ ExFreePool( BackTrace );
+ }
+}
+
+#endif
diff --git a/private/ntos/cntfs/ntfsdata.h b/private/ntos/cntfs/ntfsdata.h
new file mode 100644
index 000000000..d4acb2b5f
--- /dev/null
+++ b/private/ntos/cntfs/ntfsdata.h
@@ -0,0 +1,604 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsData.h
+
+Abstract:
+
+ This module declares the global data used by the Ntfs file system.
+
+Author:
+
+ Gary Kimura [GaryKi] 28-Dec-1989
+
+Revision History:
+
+--*/
+
+#ifndef _NTFSDATA_
+#define _NTFSDATA_
+
+//
+// The following are used to determine what level of protection to attach
+// to system files and attributes.
+//
+
+extern BOOLEAN NtfsProtectSystemFiles;
+extern BOOLEAN NtfsProtectSystemAttributes;
+
+//
+// Performance statistics
+//
+
+extern ULONG NtfsMaxDelayedCloseCount;
+extern ULONG NtfsMinDelayedCloseCount;
+
+extern ULONG NtfsCleanCheckpoints;
+extern ULONG NtfsPostRequests;
+
+//
+// The global fsd data record
+//
+
+extern NTFS_DATA NtfsData;
+
+//
+// Semaphore to synchronize creation of stream files.
+//
+
+extern FAST_MUTEX StreamFileCreationFastMutex;
+
+//
+// A mutex and queue of NTFS MCBS that will be freed
+// if we reach over a certain threshold
+//
+
+extern FAST_MUTEX NtfsMcbFastMutex;
+extern LIST_ENTRY NtfsMcbLruQueue;
+
+extern ULONG NtfsMcbHighWaterMark;
+extern ULONG NtfsMcbLowWaterMark;
+extern ULONG NtfsMcbCurrentLevel;
+
+extern BOOLEAN NtfsMcbCleanupInProgress;
+extern WORK_QUEUE_ITEM NtfsMcbWorkItem;
+
+//
+// The following are global large integer constants used throughout ntfs
+// We declare the actual name using Ntfs prefixes to avoid any linking
+// conflicts but internally in the file system we'll use smaller Li prefixes
+// to denote the values.
+//
+
+extern LARGE_INTEGER NtfsLarge0;
+extern LARGE_INTEGER NtfsLarge1;
+
+extern LONGLONG NtfsLastAccess;
+
+#define Li0 (NtfsLarge0)
+#define Li1 (NtfsLarge1)
+
+#define MAXULONGLONG (0xffffffffffffffff)
+#define UNUSED_LCN ((LONGLONG)(-1))
+
+//
+// The following fields are used to allocate nonpaged structures
+// using a lookaside list, and other fixed sized structures from a
+// small cache.
+//
+
+extern NPAGED_LOOKASIDE_LIST NtfsFileLockLookasideList;
+extern NPAGED_LOOKASIDE_LIST NtfsIoContextLookasideList;
+extern NPAGED_LOOKASIDE_LIST NtfsIrpContextLookasideList;
+extern NPAGED_LOOKASIDE_LIST NtfsKeventLookasideList;
+extern NPAGED_LOOKASIDE_LIST NtfsScbNonpagedLookasideList;
+extern NPAGED_LOOKASIDE_LIST NtfsScbSnapshotLookasideList;
+
+extern PAGED_LOOKASIDE_LIST NtfsCcbLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsCcbDataLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsDeallocatedRecordsLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsFcbDataLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsFcbIndexLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsIndexContextLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsLcbLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsNukemLookasideList;
+extern PAGED_LOOKASIDE_LIST NtfsScbDataLookasideList;
+
+//
+// This is the string for the name of the index allocation attributes.
+//
+
+extern UNICODE_STRING NtfsFileNameIndex;
+extern WCHAR NtfsFileNameIndexName[];
+
+//
+// This is the string for the attribute code for index allocation.
+// $INDEX_ALLOCATION.
+//
+
+extern UNICODE_STRING NtfsIndexAllocation;
+
+//
+// This is the string for the data attribute, $DATA.
+//
+
+extern UNICODE_STRING NtfsDataString;
+
+//
+// This strings are used for informational popups.
+//
+
+extern UNICODE_STRING NtfsSystemFiles[];
+
+//
+// This is the '.' string to use to lookup the parent entry.
+//
+
+extern UNICODE_STRING NtfsRootIndexString;
+
+//
+// This is the empty string. This can be used to pass a string with
+// no length.
+//
+
+extern UNICODE_STRING NtfsEmptyString;
+
+//
+// The following file references are used to identify system files.
+//
+
+extern FILE_REFERENCE MftFileReference;
+extern FILE_REFERENCE Mft2FileReference;
+extern FILE_REFERENCE LogFileReference;
+extern FILE_REFERENCE VolumeFileReference;
+extern FILE_REFERENCE RootIndexFileReference;
+extern FILE_REFERENCE BitmapFileReference;
+extern FILE_REFERENCE FirstUserFileReference;
+extern FILE_REFERENCE BootFileReference;
+
+//
+// The global structure used to contain our fast I/O callbacks
+//
+
+extern FAST_IO_DISPATCH NtfsFastIoDispatch;
+
+extern UCHAR BaadSignature[4];
+extern UCHAR IndexSignature[4];
+extern UCHAR FileSignature[4];
+extern UCHAR HoleSignature[4];
+extern UCHAR ChkdskSignature[4];
+
+//
+// Large Reserved Buffer Context
+//
+
+extern ULONG NtfsReservedInUse;
+extern PVOID NtfsReserved1;
+extern PVOID NtfsReserved2;
+extern ULONG NtfsReserved2Count;
+extern PVOID NtfsReserved3;
+extern PVOID NtfsReserved1Thread;
+extern PVOID NtfsReserved2Thread;
+extern PVOID NtfsReserved3Thread;
+extern PFCB NtfsReserved12Fcb;
+extern PFCB NtfsReserved3Fcb;
+extern PVOID NtfsReservedBufferThread;
+extern BOOLEAN NtfsBufferAllocationFailure;
+extern FAST_MUTEX NtfsReservedBufferMutex;
+extern ERESOURCE NtfsReservedBufferResource;
+extern LARGE_INTEGER NtfsShortDelay;
+
+#ifdef _CAIRO_
+extern FAST_MUTEX NtfsScavengerLock;
+extern PIRP_CONTEXT NtfsScavengerWorkList;
+extern BOOLEAN NtfsScavengerRunning;
+#endif // _CAIRO_
+
+#define LARGE_BUFFER_SIZE (0x10000)
+
+//
+// The following is the number of minutes for the last access increment
+//
+
+#define LAST_ACCESS_INCREMENT_MINUTES (60)
+
+//
+// Read ahead amount used for normal data files
+//
+
+#define READ_AHEAD_GRANULARITY (0x10000)
+
+//
+// Define maximum number of parallel Reads or Writes that will be generated
+// per one request.
+//
+
+#define NTFS_MAX_PARALLEL_IOS ((ULONG)8)
+
+//
+// Define a symbol which states the maximum number of runs that will be
+// added or deleted in one transaction per attribute. Note that the per-run
+// cost of deleting a run is 8-bytes in the BITMAP_RANGE, an average of
+// four bytes in the mapping array, and 16 bytes in the LCN_RANGE - for a total
+// of 28-bytes. Allocations do not log an LCN_RANGE, so their per-run cost is
+// 12 bytes. The worst problem is deleteing large fragmented files, where you
+// must add the cost of the rest of the log records for deleting all the attributes.
+//
+
+#define MAXIMUM_RUNS_AT_ONCE (128)
+
+
+
+//
+// Turn on pseudo-asserts if NTFS_FREE_ASSERTS is defined.
+//
+
+#if (!DBG && defined( NTFS_FREE_ASSERTS )) || defined( NTFSDBG )
+#undef ASSERT
+#undef ASSERTMSG
+#define ASSERT(exp) \
+ ((exp) ? TRUE : \
+ (DbgPrint( "%s:%d %s\n",__FILE__,__LINE__,#exp ), \
+ DbgBreakPoint(), \
+ TRUE))
+#define ASSERTMSG(msg,exp) \
+ ((exp) ? TRUE : \
+ (DbgPrint( "%s:%d %s %s\n",__FILE__,__LINE__,msg,#exp ), \
+ DbgBreakPoint(), \
+ TRUE))
+#endif
+
+//
+// The following debug macros are used in ntfs and defined in this module
+//
+// DebugTrace( Indent, Level, (DbgPrint list) );
+//
+// DebugUnwind( NonquotedString );
+//
+// DebugDoit( Statement );
+//
+// DbgDoit( Statement );
+//
+// The following assertion macros ensure that the indicated structure
+// is valid
+//
+// ASSERT_VCB( IN PVCB Vcb );
+// ASSERT_OPTIONAL_VCB( IN PVCB Vcb OPTIONAL );
+//
+// ASSERT_FCB( IN PFCB Fcb );
+// ASSERT_OPTIONAL_FCB( IN PFCB Fcb OPTIONAL );
+//
+// ASSERT_SCB( IN PSCB Scb );
+// ASSERT_OPTIONAL_SCB( IN PSCB Scb OPTIONAL );
+//
+// ASSERT_CCB( IN PSCB Ccb );
+// ASSERT_OPTIONAL_CCB( IN PSCB Ccb OPTIONAL );
+//
+// ASSERT_LCB( IN PLCB Lcb );
+// ASSERT_OPTIONAL_LCB( IN PLCB Lcb OPTIONAL );
+//
+// ASSERT_PREFIX_ENTRY( IN PPREFIX_ENTRY PrefixEntry );
+// ASSERT_OPTIONAL_PREFIX_ENTRY( IN PPREFIX_ENTRY PrefixEntry OPTIONAL );
+//
+// ASSERT_IRP_CONTEXT( IN PIRP_CONTEXT IrpContext );
+// ASSERT_OPTIONAL_IRP_CONTEXT( IN PIRP_CONTEXT IrpContext OPTIONAL );
+//
+// ASSERT_IRP( IN PIRP Irp );
+// ASSERT_OPTIONAL_IRP( IN PIRP Irp OPTIONAL );
+//
+// ASSERT_FILE_OBJECT( IN PFILE_OBJECT FileObject );
+// ASSERT_OPTIONAL_FILE_OBJECT( IN PFILE_OBJECT FileObject OPTIONAL );
+//
+// The following macros are used to check the current thread owns
+// the indicated resource
+//
+// ASSERT_EXCLUSIVE_RESOURCE( IN PERESOURCE Resource );
+//
+// ASSERT_SHARED_RESOURCE( IN PERESOURCE Resource );
+//
+// ASSERT_RESOURCE_NOT_MINE( IN PERESOURCE Resource );
+//
+// The following macros are used to check whether the current thread
+// owns the resoures in the given structures.
+//
+// ASSERT_EXCLUSIVE_FCB( IN PFCB Fcb );
+//
+// ASSERT_SHARED_FCB( IN PFCB Fcb );
+//
+// ASSERT_EXCLUSIVE_SCB( IN PSCB Scb );
+//
+// ASSERT_SHARED_SCB( IN PSCB Scb );
+//
+// The following macro is used to check that we are not trying to
+// manipulate an lcn that does not exist
+//
+// ASSERT_LCN_RANGE( IN PVCB Vcb, IN LCN Lcn );
+//
+
+#ifdef NTFSDBG
+
+extern LONG NtfsDebugTraceLevel;
+extern LONG NtfsDebugTraceIndent;
+extern LONG NtfsFailCheck;
+
+#define DEBUG_TRACE_ERROR (0x00000001)
+#define DEBUG_TRACE_QUOTA (0x00000002)
+#define DEBUG_TRACE_CATCH_EXCEPTIONS (0x00000004)
+#define DEBUG_TRACE_UNWIND (0x00000008)
+
+#define DEBUG_TRACE_CLEANUP (0x00000010)
+#define DEBUG_TRACE_CLOSE (0x00000020)
+#define DEBUG_TRACE_CREATE (0x00000040)
+#define DEBUG_TRACE_DIRCTRL (0x00000080)
+
+#define DEBUG_TRACE_EA (0x00000100)
+#define DEBUG_TRACE_FILEINFO (0x00000200)
+#define DEBUG_TRACE_SEINFO (0x00000200) // shared with FileInfo
+#define DEBUG_TRACE_FSCTRL (0x00000400)
+#define DEBUG_TRACE_SHUTDOWN (0x00000400) // shared with FsCtrl
+#define DEBUG_TRACE_LOCKCTRL (0x00000800)
+
+#define DEBUG_TRACE_READ (0x00001000)
+#define DEBUG_TRACE_VOLINFO (0x00002000)
+#define DEBUG_TRACE_WRITE (0x00004000)
+#define DEBUG_TRACE_FLUSH (0x00008000)
+
+#define DEBUG_TRACE_DEVCTRL (0x00010000)
+#define DEBUG_TRACE_LOGSUP (0x00020000)
+#define DEBUG_TRACE_BITMPSUP (0x00040000)
+#define DEBUG_TRACE_ALLOCSUP (0x00080000)
+
+#define DEBUG_TRACE_MFTSUP (0x00100000)
+#define DEBUG_TRACE_INDEXSUP (0x00200000)
+#define DEBUG_TRACE_ATTRSUP (0x00400000)
+#define DEBUG_TRACE_FILOBSUP (0x00800000)
+
+#define DEBUG_TRACE_NAMESUP (0x01000000)
+#define DEBUG_TRACE_SECURSUP (0x01000000) // shared with NameSup
+#define DEBUG_TRACE_VERFYSUP (0x02000000)
+#define DEBUG_TRACE_CACHESUP (0x04000000)
+#define DEBUG_TRACE_PREFXSUP (0x08000000)
+
+#define DEBUG_TRACE_DEVIOSUP (0x10000000)
+#define DEBUG_TRACE_STRUCSUP (0x20000000)
+#define DEBUG_TRACE_FSP_DISPATCHER (0x40000000)
+#define DEBUG_TRACE_ACLINDEX (0x80000000)
+
+#define DebugTrace(INDENT,LEVEL,M) { \
+ LONG _i; \
+ if (((LEVEL) == 0) || \
+ (NtfsDebugTraceLevel & (LEVEL)) != 0) { \
+ _i = (ULONG)PsGetCurrentThread(); \
+ DbgPrint("%08lx:",_i); \
+ if ((INDENT) < 0) { \
+ NtfsDebugTraceIndent += (INDENT); \
+ } \
+ if (NtfsDebugTraceIndent < 0) { \
+ NtfsDebugTraceIndent = 0; \
+ } \
+ for (_i = 0; _i < NtfsDebugTraceIndent; _i += 1) { \
+ DbgPrint(" "); \
+ } \
+ DbgPrint M; \
+ if ((INDENT) > 0) { \
+ NtfsDebugTraceIndent += (INDENT); \
+ } \
+ } \
+}
+
+#define DebugUnwind(X) { \
+ if (AbnormalTermination()) { \
+ DebugTrace( 0, DEBUG_TRACE_UNWIND, (#X ", Abnormal termination.\n") ); \
+ } \
+}
+
+#define DebugDoit(X) X
+
+//
+// Perform log-file-full testing.
+//
+
+#define FailCheck(I,S) { \
+ PIRP_CONTEXT FailTopContext = (I)->TopLevelIrpContext; \
+ if (FailTopContext->NextFailCount != 0) { \
+ if (--FailTopContext->CurrentFailCount == 0) { \
+ FailTopContext->NextFailCount++; \
+ FailTopContext->CurrentFailCount = FailTopContext->NextFailCount; \
+ ExRaiseStatus( S ); \
+ } \
+ } \
+}
+
+#define LogFileFullFailCheck(I) FailCheck( I, STATUS_LOG_FILE_FULL )
+//
+// The following variables are used to keep track of the total amount
+// of requests processed by the file system, and the number of requests
+// that end up being processed by the Fsp thread. The first variable
+// is incremented whenever an Irp context is created (which is always
+// at the start of an Fsd entry point) and the second is incremented
+// by read request.
+//
+
+extern ULONG NtfsFsdEntryCount;
+extern ULONG NtfsFspEntryCount;
+extern ULONG NtfsIoCallDriverCount;
+
+#else
+
+#define DebugTrace(INDENT,LEVEL,M) {NOTHING;}
+#define DebugUnwind(X) {NOTHING;}
+#define DebugDoit(X) NOTHING
+
+#define FailCheck(I,S)
+#define LogFileFullFailCheck(I)
+
+#endif // NTFSDBG
+
+//
+// The following macro is for all people who compile with the DBG switch
+// set, not just NTFSDBG users
+//
+
+#ifdef NTFSDBG
+
+#define DbgDoit(X) {X;}
+
+#define ASSERT_VCB(V) { \
+ ASSERT((NodeType(V) == NTFS_NTC_VCB)); \
+}
+
+#define ASSERT_OPTIONAL_VCB(V) { \
+ ASSERT(((V) == NULL) || \
+ (NodeType(V) == NTFS_NTC_VCB)); \
+}
+
+#define ASSERT_FCB(F) { \
+ ASSERT((NodeType(F) == NTFS_NTC_FCB)); \
+}
+
+#define ASSERT_OPTIONAL_FCB(F) { \
+ ASSERT(((F) == NULL) || \
+ (NodeType(F) == NTFS_NTC_FCB)); \
+}
+
+#define ASSERT_SCB(S) { \
+ ASSERT((NodeType(S) == NTFS_NTC_SCB_DATA) || \
+ (NodeType(S) == NTFS_NTC_SCB_MFT) || \
+ (NodeType(S) == NTFS_NTC_SCB_INDEX) || \
+ (NodeType(S) == NTFS_NTC_SCB_ROOT_INDEX)); \
+}
+
+#define ASSERT_OPTIONAL_SCB(S) { \
+ ASSERT(((S) == NULL) || \
+ (NodeType(S) == NTFS_NTC_SCB_DATA) || \
+ (NodeType(S) == NTFS_NTC_SCB_MFT) || \
+ (NodeType(S) == NTFS_NTC_SCB_INDEX) || \
+ (NodeType(S) == NTFS_NTC_SCB_ROOT_INDEX)); \
+}
+
+#define ASSERT_CCB(C) { \
+ ASSERT((NodeType(C) == NTFS_NTC_CCB_DATA) || \
+ (NodeType(C) == NTFS_NTC_CCB_INDEX)); \
+}
+
+#define ASSERT_OPTIONAL_CCB(C) { \
+ ASSERT(((C) == NULL) || \
+ ((NodeType(C) == NTFS_NTC_CCB_DATA) || \
+ (NodeType(C) == NTFS_NTC_CCB_INDEX))); \
+}
+
+#define ASSERT_LCB(L) { \
+ ASSERT((NodeType(L) == NTFS_NTC_LCB)); \
+}
+
+#define ASSERT_OPTIONAL_LCB(L) { \
+ ASSERT(((L) == NULL) || \
+ (NodeType(L) == NTFS_NTC_LCB)); \
+}
+
+#define ASSERT_PREFIX_ENTRY(P) { \
+ ASSERT((NodeType(P) == NTFS_NTC_PREFIX_ENTRY)); \
+}
+
+#define ASSERT_OPTIONAL_PREFIX_ENTRY(P) { \
+ ASSERT(((P) == NULL) || \
+ (NodeType(P) == NTFS_NTC_PREFIX_ENTRY)); \
+}
+
+#define ASSERT_IRP_CONTEXT(I) { \
+ ASSERT((NodeType(I) == NTFS_NTC_IRP_CONTEXT)); \
+}
+
+#define ASSERT_OPTIONAL_IRP_CONTEXT(I) { \
+ ASSERT(((I) == NULL) || \
+ (NodeType(I) == NTFS_NTC_IRP_CONTEXT)); \
+}
+
+#define ASSERT_IRP(I) { \
+ ASSERT(((I)->Type == IO_TYPE_IRP)); \
+}
+
+#define ASSERT_OPTIONAL_IRP(I) { \
+ ASSERT(((I) == NULL) || \
+ ((I)->Type == IO_TYPE_IRP)); \
+}
+
+#define ASSERT_FILE_OBJECT(F) { \
+ ASSERT(((F)->Type == IO_TYPE_FILE)); \
+}
+
+#define ASSERT_OPTIONAL_FILE_OBJECT(F) { \
+ ASSERT(((F) == NULL) || \
+ ((F)->Type == IO_TYPE_FILE)); \
+}
+
+#define ASSERT_EXCLUSIVE_RESOURCE(R) { \
+ ASSERTMSG("ASSERT_EXCLUSIVE_RESOURCE ", ExIsResourceAcquiredExclusiveLite(R)); \
+}
+
+#define ASSERT_SHARED_RESOURCE(R) \
+ ASSERTMSG( "ASSERT_RESOURCE_NOT_MINE ", ExIsResourceAcquiredSharedLite(R));
+
+#define ASSERT_RESOURCE_NOT_MINE(R) \
+ ASSERTMSG( "ASSERT_RESOURCE_NOT_MINE ", !ExIsResourceAcquiredSharedLite(R));
+
+#define ASSERT_EXCLUSIVE_FCB(F) { \
+ if (NtfsSegmentNumber( &(F)->FileReference ) \
+ >= FIRST_USER_FILE_NUMBER) { \
+ ASSERT_EXCLUSIVE_RESOURCE(F->Resource); \
+ } \
+} \
+
+#define ASSERT_SHARED_FCB(F) { \
+ if (NtfsSegmentNumber( &(F)->FileReference ) \
+ >= FIRST_USER_FILE_NUMBER) { \
+ ASSERT_SHARED_RESOURCE(F->Resource); \
+ } \
+} \
+
+#define ASSERT_EXCLUSIVE_SCB(S) ASSERT_EXCLUSIVE_FCB(S->Fcb)
+
+#define ASSERT_SHARED_SCB(S) ASSERT_SHARED_FCB(S->Fcb)
+
+#define ASSERT_LCN_RANGE_CHECKING(V,L) { \
+ ASSERTMSG("ASSERT_LCN_RANGE_CHECKING ", \
+ ((V)->TotalClusters == 0) || ((L) <= (V)->TotalClusters)); \
+}
+
+#else
+
+#define DbgDoit(X) {NOTHING;}
+#define ASSERT_VCB(V) {DBG_UNREFERENCED_PARAMETER(V);}
+#define ASSERT_OPTIONAL_VCB(V) {DBG_UNREFERENCED_PARAMETER(V);}
+#define ASSERT_FCB(F) {DBG_UNREFERENCED_PARAMETER(F);}
+#define ASSERT_OPTIONAL_FCB(F) {DBG_UNREFERENCED_PARAMETER(F);}
+#define ASSERT_SCB(S) {DBG_UNREFERENCED_PARAMETER(S);}
+#define ASSERT_OPTIONAL_SCB(S) {DBG_UNREFERENCED_PARAMETER(S);}
+#define ASSERT_CCB(C) {DBG_UNREFERENCED_PARAMETER(C);}
+#define ASSERT_OPTIONAL_CCB(C) {DBG_UNREFERENCED_PARAMETER(C);}
+#define ASSERT_LCB(L) {DBG_UNREFERENCED_PARAMETER(L);}
+#define ASSERT_OPTIONAL_LCB(L) {DBG_UNREFERENCED_PARAMETER(L);}
+#define ASSERT_PREFIX_ENTRY(P) {DBG_UNREFERENCED_PARAMETER(P);}
+#define ASSERT_OPTIONAL_PREFIX_ENTRY(P) {DBG_UNREFERENCED_PARAMETER(P);}
+#define ASSERT_IRP_CONTEXT(I) {DBG_UNREFERENCED_PARAMETER(I);}
+#define ASSERT_OPTIONAL_IRP_CONTEXT(I) {DBG_UNREFERENCED_PARAMETER(I);}
+#define ASSERT_IRP(I) {DBG_UNREFERENCED_PARAMETER(I);}
+#define ASSERT_OPTIONAL_IRP(I) {DBG_UNREFERENCED_PARAMETER(I);}
+#define ASSERT_FILE_OBJECT(F) {DBG_UNREFERENCED_PARAMETER(F);}
+#define ASSERT_OPTIONAL_FILE_OBJECT(F) {DBG_UNREFERENCED_PARAMETER(F);}
+#define ASSERT_EXCLUSIVE_RESOURCE(R) {NOTHING;}
+#define ASSERT_SHARED_RESOURCE(R) {NOTHING;}
+#define ASSERT_RESOURCE_NOT_MINE(R) {NOTHING;}
+#define ASSERT_EXCLUSIVE_FCB(F) {NOTHING;}
+#define ASSERT_SHARED_FCB(F) {NOTHING;}
+#define ASSERT_EXCLUSIVE_SCB(S) {NOTHING;}
+#define ASSERT_SHARED_SCB(S) {NOTHING;}
+#define ASSERT_LCN_RANGE_CHECKING(V,L) {NOTHING;}
+
+#endif // DBG
+
+#endif // _NTFSDATA_
+
diff --git a/private/ntos/cntfs/ntfsexp.c b/private/ntos/cntfs/ntfsexp.c
new file mode 100644
index 000000000..defd3fd53
--- /dev/null
+++ b/private/ntos/cntfs/ntfsexp.c
@@ -0,0 +1,256 @@
+/*++
+
+Copyright (c) 1995 Microsoft Corporation
+
+Module Name:
+
+ Ntfsexp.c
+
+Abstract:
+
+ This module implements the exported routines for Ntfs
+
+Author:
+
+ Jeff Havens [JHavens] 20-Dec-1995
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define NTFS_SERVICE_KEY L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"
+
+PWSTR NtfsAddonNames [] = {
+ L"NTFSQ",
+ L"VIEWS",
+ NULL
+ };
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsLoadAddOns)
+#pragma alloc_text(PAGE, NtOfsRegisterCallBacks)
+#endif
+
+
+VOID
+NtfsLoadAddOns (
+ IN PDRIVER_OBJECT DriverObject,
+ IN PVOID Context,
+ IN ULONG Count
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to load any NTFS add-ons and notify them about
+ any previously mounted volumes.
+
+Arguments:
+
+ DriverObject - Driver object for NTFS
+
+ Context - Unused, required by I/O system.
+
+ Count - Unused, required by I/O system.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ UNICODE_STRING UnicodeString;
+ ULONG i;
+ WCHAR Buffer[80];
+
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ IRP_CONTEXT LocalIrpContext;
+ IRP LocalIrp;
+
+ PIRP_CONTEXT IrpContext;
+
+ PLIST_ENTRY Links;
+ PVCB Vcb;
+
+ PVCB VcbForTearDown = NULL;
+ BOOLEAN AcquiredGlobal = FALSE;
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER(Context);
+ UNREFERENCED_PARAMETER(Count);
+ UNREFERENCED_PARAMETER(DriverObject);
+
+ //
+ // For each add-on try to load it.
+ //
+
+ for (i = 0; NtfsAddonNames[i] != NULL; i++) {
+
+ wcscpy(Buffer, NTFS_SERVICE_KEY);
+ wcscat(Buffer, NtfsAddonNames[i]);
+
+ RtlInitUnicodeString( &UnicodeString, Buffer);
+
+ Status = ZwLoadDriver( &UnicodeString );
+
+#if DBG
+ DbgPrint("NtfsLoadAddOns: Loaded module %ws. Status = 0x%lx\n", Buffer, Status);
+#endif
+
+ }
+
+ RtlZeroMemory( &LocalIrpContext, sizeof(LocalIrpContext) );
+ RtlZeroMemory( &LocalIrp, sizeof(LocalIrp) );
+
+ IrpContext = &LocalIrpContext;
+ IrpContext->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext->NodeByteSize = sizeof(IRP_CONTEXT);
+ IrpContext->OriginatingIrp = &LocalIrp;
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+ InitializeListHead( &IrpContext->ExclusiveFcbList );
+
+ //
+ // Make sure we don't get any pop-ups
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ (VOID) ExAcquireResourceShared( &NtfsData.Resource, TRUE );
+ AcquiredGlobal = TRUE;
+
+ try {
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ try {
+
+ for (Links = NtfsData.VcbQueue.Flink;
+ Links != &NtfsData.VcbQueue;
+ Links = Links->Flink) {
+
+ Vcb = CONTAINING_RECORD(Links, VCB, VcbLinks);
+
+ IrpContext->Vcb = Vcb;
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ Status = CiMountVolume( Vcb, IrpContext);
+
+ // Bugbug: What should we do if this fails?
+ // BugBug: add call out for views.
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ }
+ }
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NOTHING;
+ }
+
+ } finally {
+
+ if (AcquiredGlobal) {
+ ExReleaseResource( &NtfsData.Resource );
+ }
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+
+}
+
+
+NTSTATUS
+NtOfsRegisterCallBacks (
+ NTFS_ADDON_TYPES NtfsAddonType,
+ PVOID CallBackTable
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by one of the NTFS add-ons to register its
+ callback routines. These routines are call by NTFS at the appropriate
+ times.
+
+Arguments:
+
+ NtfsAddonType - Indicates the type of callback table.
+
+ CallBackTable - Pointer to call back routines for addon.
+
+Return Value:
+
+ Returns a status indicating if the callbacks were accepted.
+
+--*/
+
+{
+
+ if (NtfsAddonType == ContentIndex) {
+
+ CI_CALL_BACK *CiCallBackTable = CallBackTable;
+
+ //
+ // Validate version number.
+ //
+
+ if (CiCallBackTable->CiInterfaceVersion !=
+ CI_CURRENT_INTERFACE_VERSION) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Save the call back values.
+ //
+
+ NtfsData.CiCallBackTable = CiCallBackTable;
+
+ return STATUS_SUCCESS;
+ }
+
+ if (NtfsAddonType == Views) {
+
+ VIEW_CALL_BACK *ViewCallBackTable = CallBackTable;
+
+ //
+ // Validate version number.
+ //
+
+ if (ViewCallBackTable != NULL &&
+ ViewCallBackTable->ViewInterfaceVersion !=
+ VIEW_CURRENT_INTERFACE_VERSION) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Save the call back values.
+ //
+
+ NtfsData.ViewCallBackTable = ViewCallBackTable;
+
+ return STATUS_SUCCESS;
+ }
+
+ return STATUS_INVALID_PARAMETER;
+
+}
diff --git a/private/ntos/cntfs/ntfsinit.c b/private/ntos/cntfs/ntfsinit.c
new file mode 100644
index 000000000..fdab14bd1
--- /dev/null
+++ b/private/ntos/cntfs/ntfsinit.c
@@ -0,0 +1,946 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsInit.c
+
+Abstract:
+
+ This module implements the DRIVER_INITIALIZATION routine for Ntfs
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+NTSTATUS
+DriverEntry (
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ );
+
+VOID
+NtfsInitializeNtfsData (
+ IN PDRIVER_OBJECT DriverObject
+ );
+
+NTSTATUS
+NtfsGet8dot3NameStatus (
+ IN PUNICODE_STRING KeyName,
+ IN PUNICODE_STRING ValueName,
+ IN OUT PULONG Value
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(INIT, DriverEntry)
+#pragma alloc_text(INIT, NtfsGet8dot3NameStatus)
+#pragma alloc_text(PAGE, NtfsInitializeNtfsData)
+#endif
+
+#define COMPATIBILITY_MODE_KEY_NAME L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\FileSystem"
+#define COMPATIBILITY_MODE_VALUE_NAME L"NtfsDisable8dot3NameCreation"
+
+#define EXTENDED_CHAR_MODE_VALUE_NAME L"NtfsAllowExtendedCharacterIn8dot3Name"
+
+#define DISABLE_LAST_ACCESS_VALUE_NAME L"NtfsDisableLastAccessUpdate"
+
+#define KEY_WORK_AREA ((sizeof(KEY_VALUE_FULL_INFORMATION) + \
+ sizeof(ULONG)) + 128)
+
+
+NTSTATUS
+DriverEntry(
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING RegistryPath
+ )
+
+/*++
+
+Routine Description:
+
+ This is the initialization routine for the Ntfs file system
+ device driver. This routine creates the device object for the FileSystem
+ device and performs all other driver initialization.
+
+Arguments:
+
+ DriverObject - Pointer to driver object created by the system.
+
+Return Value:
+
+ NTSTATUS - The function value is the final status from the initialization
+ operation.
+
+--*/
+
+{
+ NTSTATUS Status;
+ UNICODE_STRING UnicodeString;
+ PDEVICE_OBJECT DeviceObject;
+
+ UNICODE_STRING KeyName;
+ UNICODE_STRING ValueName;
+ ULONG Value;
+
+ UNREFERENCED_PARAMETER( RegistryPath );
+
+ PAGED_CODE();
+
+ //
+ // Compute the last access increment. We convert the number of
+ // minutes to number of 1/100 of nanoseconds. We have to be careful
+ // not to overrun 32 bits for any multiplier.
+ //
+ // To reach 1/100 of nanoseconds per minute we take
+ //
+ // 1/100 nanoseconds * 10 = 1 microsecond
+ // * 1000 = 1 millesecond
+ // * 1000 = 1 second
+ // * 60 = 1 minute
+ //
+ // Then multiply this by the last access increment in minutes.
+ //
+
+ NtfsLastAccess = Int32x32To64( ( 10 * 1000 * 1000 * 60 ), LAST_ACCESS_INCREMENT_MINUTES );
+
+ //
+ // Create the device object.
+ //
+
+ RtlInitUnicodeString( &UnicodeString, L"\\Ntfs" );
+
+ Status = IoCreateDevice( DriverObject,
+ 0,
+ &UnicodeString,
+ FILE_DEVICE_DISK_FILE_SYSTEM,
+ 0,
+ FALSE,
+ &DeviceObject );
+
+ if (!NT_SUCCESS( Status )) {
+
+ return Status;
+ }
+
+ //
+ // Note that because of the way data caching is done, we set neither
+ // the Direct I/O or Buffered I/O bit in DeviceObject->Flags. If
+ // data is not in the cache, or the request is not buffered, we may,
+ // set up for Direct I/O by hand.
+ //
+
+ //
+ // Initialize the driver object with this driver's entry points.
+ //
+
+ DriverObject->MajorFunction[IRP_MJ_CREATE] = (PDRIVER_DISPATCH)NtfsFsdCreate;
+ DriverObject->MajorFunction[IRP_MJ_CLOSE] = (PDRIVER_DISPATCH)NtfsFsdClose;
+ DriverObject->MajorFunction[IRP_MJ_READ] = (PDRIVER_DISPATCH)NtfsFsdRead;
+ DriverObject->MajorFunction[IRP_MJ_WRITE] = (PDRIVER_DISPATCH)NtfsFsdWrite;
+ DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION] = (PDRIVER_DISPATCH)NtfsFsdQueryInformation;
+ DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = (PDRIVER_DISPATCH)NtfsFsdSetInformation;
+ DriverObject->MajorFunction[IRP_MJ_QUERY_EA] = (PDRIVER_DISPATCH)NtfsFsdQueryEa;
+ DriverObject->MajorFunction[IRP_MJ_SET_EA] = (PDRIVER_DISPATCH)NtfsFsdSetEa;
+ DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS] = (PDRIVER_DISPATCH)NtfsFsdFlushBuffers;
+ DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = (PDRIVER_DISPATCH)NtfsFsdQueryVolumeInformation;
+ DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION] = (PDRIVER_DISPATCH)NtfsFsdSetVolumeInformation;
+ DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] = (PDRIVER_DISPATCH)NtfsFsdDirectoryControl;
+ DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = (PDRIVER_DISPATCH)NtfsFsdFileSystemControl;
+ DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)NtfsFsdDeviceControl;
+ DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL] = (PDRIVER_DISPATCH)NtfsFsdLockControl;
+ DriverObject->MajorFunction[IRP_MJ_CLEANUP] = (PDRIVER_DISPATCH)NtfsFsdCleanup;
+ DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY] = (PDRIVER_DISPATCH)NtfsFsdQuerySecurityInfo;
+ DriverObject->MajorFunction[IRP_MJ_SET_SECURITY] = (PDRIVER_DISPATCH)NtfsFsdSetSecurityInfo;
+ DriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = (PDRIVER_DISPATCH)NtfsFsdShutdown;
+
+ DriverObject->FastIoDispatch = &NtfsFastIoDispatch;
+
+ NtfsFastIoDispatch.SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);
+ NtfsFastIoDispatch.FastIoCheckIfPossible = NtfsFastIoCheckIfPossible; // CheckForFastIo
+ NtfsFastIoDispatch.FastIoRead = NtfsCopyReadA; // Read
+ NtfsFastIoDispatch.FastIoWrite = NtfsCopyWriteA; // Write
+ NtfsFastIoDispatch.FastIoQueryBasicInfo = NtfsFastQueryBasicInfo; // QueryBasicInfo
+ NtfsFastIoDispatch.FastIoQueryStandardInfo = NtfsFastQueryStdInfo; // QueryStandardInfo
+ NtfsFastIoDispatch.FastIoLock = NtfsFastLock; // Lock
+ NtfsFastIoDispatch.FastIoUnlockSingle = NtfsFastUnlockSingle; // UnlockSingle
+ NtfsFastIoDispatch.FastIoUnlockAll = NtfsFastUnlockAll; // UnlockAll
+ NtfsFastIoDispatch.FastIoUnlockAllByKey = NtfsFastUnlockAllByKey; // UnlockAllByKey
+ NtfsFastIoDispatch.FastIoDeviceControl = NULL; // IoDeviceControl
+ NtfsFastIoDispatch.FastIoDetachDevice = NULL;
+ NtfsFastIoDispatch.FastIoQueryNetworkOpenInfo = NtfsFastQueryNetworkOpenInfo;
+ NtfsFastIoDispatch.AcquireFileForNtCreateSection = NtfsAcquireForCreateSection;
+ NtfsFastIoDispatch.ReleaseFileForNtCreateSection = NtfsReleaseForCreateSection;
+ NtfsFastIoDispatch.AcquireForModWrite = NtfsAcquireFileForModWrite;
+ NtfsFastIoDispatch.MdlRead = NtfsMdlReadA;
+ NtfsFastIoDispatch.MdlReadComplete = FsRtlMdlReadCompleteDev;
+ NtfsFastIoDispatch.PrepareMdlWrite = NtfsPrepareMdlWriteA;
+ NtfsFastIoDispatch.MdlWriteComplete = FsRtlMdlWriteCompleteDev;
+#ifdef _CAIRO_
+ NtfsFastIoDispatch.FastIoReadCompressed = NtfsCopyReadC;
+ NtfsFastIoDispatch.FastIoWriteCompressed = NtfsCopyWriteC;
+ NtfsFastIoDispatch.MdlReadCompleteCompressed = NtfsMdlReadCompleteCompressed;
+ NtfsFastIoDispatch.MdlWriteCompleteCompressed = NtfsMdlWriteCompleteCompressed;
+#endif _CAIRO_
+ NtfsFastIoDispatch.FastIoQueryOpen = NtfsNetworkOpenCreate;
+ NtfsFastIoDispatch.AcquireForCcFlush = NtfsAcquireFileForCcFlush;
+ NtfsFastIoDispatch.ReleaseForCcFlush = NtfsReleaseFileForCcFlush;
+
+ //
+ // Initialize the global ntfs data structure
+ //
+
+ NtfsInitializeNtfsData( DriverObject );
+
+ ExInitializeFastMutex( &StreamFileCreationFastMutex );
+
+ //
+ // Initialize the Ntfs Mcb global data queue and variables
+ //
+
+ ExInitializeFastMutex( &NtfsMcbFastMutex );
+ InitializeListHead( &NtfsMcbLruQueue );
+ NtfsMcbCleanupInProgress = FALSE;
+
+ switch ( MmQuerySystemSize() ) {
+
+ case MmSmallSystem:
+
+ NtfsMcbHighWaterMark = 1000;
+ NtfsMcbLowWaterMark = 500;
+ NtfsMcbCurrentLevel = 0;
+ break;
+
+ case MmMediumSystem:
+
+ NtfsMcbHighWaterMark = 1000;
+ NtfsMcbLowWaterMark = 500;
+ NtfsMcbCurrentLevel = 0;
+ break;
+
+ case MmLargeSystem:
+ default:
+
+ NtfsMcbHighWaterMark = 1000;
+ NtfsMcbLowWaterMark = 500;
+ NtfsMcbCurrentLevel = 0;
+ break;
+ }
+
+ //
+ // Allocate and initialize the free Eresource array
+ //
+
+ if ((NtfsData.FreeEresourceArray =
+ ExAllocatePoolWithTag(NonPagedPool, (NtfsData.FreeEresourceTotal * sizeof(PERESOURCE)), 'rftN')) == NULL) {
+
+ KeBugCheck( NTFS_FILE_SYSTEM );
+ }
+
+ RtlZeroMemory( NtfsData.FreeEresourceArray, NtfsData.FreeEresourceTotal * sizeof(PERESOURCE) );
+
+ //
+ //
+ // Register the file system with the I/O system
+ //
+
+ IoRegisterFileSystem(DeviceObject);
+
+ //
+ // Initialize logging.
+ //
+
+ NtfsInitializeLogging();
+
+ //
+ // Initialize global variables. (ntfsdata.c assumes 2-digit value for
+ // $FILE_NAME)
+ //
+
+ ASSERT(($FILE_NAME >= 0x10) && ($FILE_NAME < 0x100));
+
+ RtlInitUnicodeString( &NtfsFileNameIndex, NtfsFileNameIndexName );
+
+ //
+ // Support extended character in shortname
+ //
+
+ //
+ // Read the registry to determine if we are to create short names.
+ //
+
+ KeyName.Buffer = COMPATIBILITY_MODE_KEY_NAME;
+ KeyName.Length = sizeof( COMPATIBILITY_MODE_KEY_NAME ) - sizeof( WCHAR );
+ KeyName.MaximumLength = sizeof( COMPATIBILITY_MODE_KEY_NAME );
+
+ ValueName.Buffer = COMPATIBILITY_MODE_VALUE_NAME;
+ ValueName.Length = sizeof( COMPATIBILITY_MODE_VALUE_NAME ) - sizeof( WCHAR );
+ ValueName.MaximumLength = sizeof( COMPATIBILITY_MODE_VALUE_NAME );
+
+ Status = NtfsGet8dot3NameStatus( &KeyName, &ValueName, &Value );
+
+ //
+ // If we didn't find the value or the value is zero then create the 8.3
+ // names.
+ //
+
+ if (!NT_SUCCESS( Status ) || Value == 0) {
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_CREATE_8DOT3_NAMES );
+ }
+
+ //
+ // Read the registry to determine if we allow extended character in short name.
+ //
+
+ ValueName.Buffer = EXTENDED_CHAR_MODE_VALUE_NAME;
+ ValueName.Length = sizeof( EXTENDED_CHAR_MODE_VALUE_NAME ) - sizeof( WCHAR );
+ ValueName.MaximumLength = sizeof( EXTENDED_CHAR_MODE_VALUE_NAME );
+
+ Status = NtfsGet8dot3NameStatus( &KeyName, &ValueName, &Value );
+
+ //
+ // If we didn't find the value or the value is zero then does not allow
+ // extended character in 8.3 names.
+ //
+
+ if (NT_SUCCESS( Status ) && Value == 1) {
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_ALLOW_EXTENDED_CHAR );
+ }
+
+ //
+ // Read the registry to determine if we should disable last access updates.
+ //
+
+ ValueName.Buffer = DISABLE_LAST_ACCESS_VALUE_NAME;
+ ValueName.Length = sizeof( DISABLE_LAST_ACCESS_VALUE_NAME ) - sizeof( WCHAR );
+ ValueName.MaximumLength = sizeof( DISABLE_LAST_ACCESS_VALUE_NAME );
+
+ Status = NtfsGet8dot3NameStatus( &KeyName, &ValueName, &Value );
+
+ //
+ // If we didn't find the value or the value is zero then does not allow
+ // extended character in 8.3 names.
+ //
+
+ if (NT_SUCCESS( Status ) && Value == 1) {
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_DISABLE_LAST_ACCESS );
+ }
+
+ //
+ // Setup the CheckPointAllVolumes callback item, timer, dpc, and
+ // status.
+ //
+
+ ExInitializeWorkItem( &NtfsData.VolumeCheckpointItem,
+ NtfsCheckpointAllVolumes,
+ (PVOID)NULL );
+
+ KeInitializeTimer( &NtfsData.VolumeCheckpointTimer );
+
+ KeInitializeDpc( &NtfsData.VolumeCheckpointDpc,
+ NtfsVolumeCheckpointDpc,
+ NULL );
+ NtfsData.TimerStatus = TIMER_NOT_SET;
+
+ //
+ // Allocate first reserved buffer for USA writes
+ //
+
+ NtfsReserved1 = NtfsAllocatePool( NonPagedPool, LARGE_BUFFER_SIZE );
+ NtfsReserved2 = NtfsAllocatePool( NonPagedPool, LARGE_BUFFER_SIZE );
+ NtfsReserved3 = NtfsAllocatePool( NonPagedPool, LARGE_BUFFER_SIZE );
+ ExInitializeFastMutex( &NtfsReservedBufferMutex );
+ ExInitializeResource( &NtfsReservedBufferResource );
+
+ //
+ // Zero out the global upcase table, that way we'll fill it in on
+ // our first successful mount
+ //
+
+ NtfsData.UpcaseTable = NULL;
+ NtfsData.UpcaseTableSize = 0;
+
+#ifdef _CAIRO_
+
+ ExInitializeFastMutex( &NtfsScavengerLock );
+ NtfsScavengerWorkList = NULL;
+ NtfsScavengerRunning = FALSE;
+
+ //
+ // Request the load add-on routine be called after all the drivers have
+ // initialized.
+ //
+
+ IoRegisterDriverReinitialization( DriverObject, NtfsLoadAddOns, NULL);
+
+#endif
+
+ //
+ // And return to our caller
+ //
+
+ return( STATUS_SUCCESS );
+}
+
+
+VOID
+NtfsInitializeNtfsData (
+ IN PDRIVER_OBJECT DriverObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the global ntfs data record
+
+Arguments:
+
+ DriverObject - Supplies the driver object for NTFS
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ USHORT FileLockMaxDepth;
+ USHORT IoContextMaxDepth;
+ USHORT IrpContextMaxDepth;
+ USHORT KeventMaxDepth;
+ USHORT ScbNonpagedMaxDepth;
+ USHORT ScbSnapshotMaxDepth;
+
+ USHORT CcbDataMaxDepth;
+ USHORT CcbMaxDepth;
+ USHORT DeallocatedRecordsMaxDepth;
+ USHORT FcbDataMaxDepth;
+ USHORT FcbIndexMaxDepth;
+ USHORT IndexContextMaxDepth;
+ USHORT LcbMaxDepth;
+ USHORT NukemMaxDepth;
+ USHORT ScbDataMaxDepth;
+
+ PSECURITY_SUBJECT_CONTEXT SubjectContext = NULL;
+ BOOLEAN CapturedSubjectContext = FALSE;
+
+ PACL SystemDacl = NULL;
+ ULONG SystemDaclLength;
+
+ PSID AdminSid = NULL;
+ PSID SystemSid = NULL;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeNtfsData\n") );
+
+ //
+ // Zero the record and set its node type code and size
+ //
+
+ RtlZeroMemory( &NtfsData, sizeof(NTFS_DATA));
+
+ NtfsData.NodeTypeCode = NTFS_NTC_DATA_HEADER;
+ NtfsData.NodeByteSize = sizeof(NTFS_DATA);
+
+ //
+ // Initialize the queue of mounted Vcbs
+ //
+
+ InitializeListHead(&NtfsData.VcbQueue);
+
+ //
+ // This list head keeps track of closes yet to be done.
+ //
+
+ InitializeListHead( &NtfsData.AsyncCloseList );
+ InitializeListHead( &NtfsData.DelayedCloseList );
+
+ ExInitializeWorkItem( &NtfsData.NtfsCloseItem,
+ (PWORKER_THREAD_ROUTINE)NtfsFspClose,
+ NULL );
+
+ //
+ // Set the driver object, device object, and initialize the global
+ // resource protecting the file system
+ //
+
+ NtfsData.DriverObject = DriverObject;
+
+ ExInitializeResource( &NtfsData.Resource );
+
+ //
+ // Now allocate and initialize the s-list structures used as our pool
+ // of IRP context records. The size of the zone is based on the
+ // system memory size. We also initialize the spin lock used to protect
+ // the zone.
+ //
+
+ KeInitializeSpinLock( &NtfsData.StrucSupSpinLock );
+ {
+
+ switch ( MmQuerySystemSize() ) {
+
+ case MmSmallSystem:
+
+ NtfsData.FreeEresourceTotal = 14;
+
+ //
+ // Nonpaged Lookaside list maximum depths
+ //
+
+ FileLockMaxDepth = 8;
+ IoContextMaxDepth = 8;
+ IrpContextMaxDepth = 4;
+ KeventMaxDepth = 8;
+ ScbNonpagedMaxDepth = 8;
+ ScbSnapshotMaxDepth = 8;
+
+ //
+ // Paged Lookaside list maximum depths
+ //
+
+ CcbDataMaxDepth = 4;
+ CcbMaxDepth = 4;
+ DeallocatedRecordsMaxDepth = 8;
+ FcbDataMaxDepth = 8;
+ FcbIndexMaxDepth = 4;
+ IndexContextMaxDepth = 8;
+ LcbMaxDepth = 4;
+ NukemMaxDepth = 8;
+ ScbDataMaxDepth = 4;
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_SMALL_SYSTEM );
+ NtfsMaxDelayedCloseCount = MAX_DELAYED_CLOSE_COUNT;
+
+ break;
+
+ case MmMediumSystem:
+
+ NtfsData.FreeEresourceTotal = 30;
+
+ //
+ // Nonpaged Lookaside list maximum depths
+ //
+
+ FileLockMaxDepth = 8;
+ IoContextMaxDepth = 8;
+ IrpContextMaxDepth = 8;
+ KeventMaxDepth = 8;
+ ScbNonpagedMaxDepth = 30;
+ ScbSnapshotMaxDepth = 8;
+
+ //
+ // Paged Lookaside list maximum depths
+ //
+
+ CcbDataMaxDepth = 12;
+ CcbMaxDepth = 6;
+ DeallocatedRecordsMaxDepth = 8;
+ FcbDataMaxDepth = 30;
+ FcbIndexMaxDepth = 12;
+ IndexContextMaxDepth = 8;
+ LcbMaxDepth = 12;
+ NukemMaxDepth = 8;
+ ScbDataMaxDepth = 12;
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_MEDIUM_SYSTEM );
+ NtfsMaxDelayedCloseCount = 4 * MAX_DELAYED_CLOSE_COUNT;
+
+ break;
+
+ case MmLargeSystem:
+
+ SetFlag( NtfsData.Flags, NTFS_FLAGS_LARGE_SYSTEM );
+ NtfsMaxDelayedCloseCount = 16 * MAX_DELAYED_CLOSE_COUNT;
+
+ if (MmIsThisAnNtAsSystem()) {
+
+ NtfsData.FreeEresourceTotal = 256;
+
+ //
+ // Nonpaged Lookaside list maximum depths
+ //
+
+ FileLockMaxDepth = 8;
+ IoContextMaxDepth = 8;
+ IrpContextMaxDepth = 256;
+ KeventMaxDepth = 8;
+ ScbNonpagedMaxDepth = 128;
+ ScbSnapshotMaxDepth = 8;
+
+ //
+ // Paged Lookaside list maximum depths
+ //
+
+ CcbDataMaxDepth = 40;
+ CcbMaxDepth = 20;
+ DeallocatedRecordsMaxDepth = 8;
+ FcbDataMaxDepth = 128;
+ FcbIndexMaxDepth = 40;
+ IndexContextMaxDepth = 8;
+ LcbMaxDepth = 40;
+ NukemMaxDepth = 8;
+ ScbDataMaxDepth = 40;
+
+ } else {
+
+ NtfsData.FreeEresourceTotal = 128;
+
+ //
+ // Nonpaged Lookaside list maximum depths
+ //
+
+ FileLockMaxDepth = 8;
+ IoContextMaxDepth = 8;
+ IrpContextMaxDepth = 64;
+ KeventMaxDepth = 8;
+ ScbNonpagedMaxDepth = 64;
+ ScbSnapshotMaxDepth = 8;
+
+ //
+ // Paged Lookaside list maximum depths
+ //
+
+ CcbDataMaxDepth = 20;
+ CcbMaxDepth = 10;
+ DeallocatedRecordsMaxDepth = 8;
+ FcbDataMaxDepth = 64;
+ FcbIndexMaxDepth = 20;
+ IndexContextMaxDepth = 8;
+ LcbMaxDepth = 20;
+ NukemMaxDepth = 8;
+ ScbDataMaxDepth = 20;
+ }
+
+ break;
+ }
+
+ NtfsMinDelayedCloseCount = NtfsMaxDelayedCloseCount * 4 / 5;
+
+ }
+
+ //
+ // Initialize our various lookaside lists. To make it a bit more readable we'll
+ // define two quick macros to do the initialization
+ //
+
+#if DBG && i386 && defined (NTFSPOOLCHECK)
+#define NPagedInit(L,S,T,D) { ExInitializeNPagedLookasideList( (L), NtfsDebugAllocatePoolWithTag, NtfsDebugFreePool, POOL_RAISE_IF_ALLOCATION_FAILURE, S, T, D); }
+#define PagedInit(L,S,T,D) { ExInitializePagedLookasideList( (L), NtfsDebugAllocatePoolWithTag, NtfsDebugFreePool, POOL_RAISE_IF_ALLOCATION_FAILURE, S, T, D); }
+#else // DBG && i386
+#define NPagedInit(L,S,T,D) { ExInitializeNPagedLookasideList( (L), NULL, NULL, POOL_RAISE_IF_ALLOCATION_FAILURE, S, T, D); }
+#define PagedInit(L,S,T,D) { ExInitializePagedLookasideList( (L), NULL, NULL, POOL_RAISE_IF_ALLOCATION_FAILURE, S, T, D); }
+#endif // DBG && i386
+
+ NPagedInit( &NtfsFileLockLookasideList, sizeof(FILE_LOCK), 'kftN', FileLockMaxDepth );
+ NPagedInit( &NtfsIoContextLookasideList, sizeof(NTFS_IO_CONTEXT), 'IftN', IoContextMaxDepth );
+ NPagedInit( &NtfsIrpContextLookasideList, sizeof(IRP_CONTEXT), 'iftN', IrpContextMaxDepth );
+ NPagedInit( &NtfsKeventLookasideList, sizeof(KEVENT), 'KftN', KeventMaxDepth );
+ NPagedInit( &NtfsScbNonpagedLookasideList, sizeof(SCB_NONPAGED), 'nftN', ScbNonpagedMaxDepth );
+ NPagedInit( &NtfsScbSnapshotLookasideList, sizeof(SCB_SNAPSHOT), 'TftN', ScbSnapshotMaxDepth );
+
+ PagedInit( &NtfsCcbLookasideList, sizeof(CCB), 'CftN', CcbMaxDepth );
+ PagedInit( &NtfsCcbDataLookasideList, sizeof(CCB_DATA), 'cftN', CcbDataMaxDepth );
+ PagedInit( &NtfsDeallocatedRecordsLookasideList, sizeof(DEALLOCATED_RECORDS), 'DftN', DeallocatedRecordsMaxDepth );
+ PagedInit( &NtfsFcbDataLookasideList, sizeof(FCB_DATA), 'fftN', FcbDataMaxDepth );
+ PagedInit( &NtfsFcbIndexLookasideList, sizeof(FCB_INDEX), 'FftN', FcbIndexMaxDepth );
+ PagedInit( &NtfsIndexContextLookasideList, sizeof(INDEX_CONTEXT), 'EftN', IndexContextMaxDepth );
+ PagedInit( &NtfsLcbLookasideList, sizeof(LCB), 'lftN', LcbMaxDepth );
+ PagedInit( &NtfsNukemLookasideList, sizeof(NUKEM), 'NftN', NukemMaxDepth );
+ PagedInit( &NtfsScbDataLookasideList, SIZEOF_SCB_DATA, 'sftN', ScbDataMaxDepth );
+
+ //
+ // Initialize the cache manager callback routines, First are the routines
+ // for normal file manipulations, followed by the routines for
+ // volume manipulations.
+ //
+
+ {
+ PCACHE_MANAGER_CALLBACKS Callbacks = &NtfsData.CacheManagerCallbacks;
+
+ Callbacks->AcquireForLazyWrite = &NtfsAcquireScbForLazyWrite;
+ Callbacks->ReleaseFromLazyWrite = &NtfsReleaseScbFromLazyWrite;
+ Callbacks->AcquireForReadAhead = &NtfsAcquireScbForReadAhead;
+ Callbacks->ReleaseFromReadAhead = &NtfsReleaseScbFromReadAhead;
+ }
+
+ {
+ PCACHE_MANAGER_CALLBACKS Callbacks = &NtfsData.CacheManagerVolumeCallbacks;
+
+ Callbacks->AcquireForLazyWrite = &NtfsAcquireVolumeFileForLazyWrite;
+ Callbacks->ReleaseFromLazyWrite = &NtfsReleaseVolumeFileFromLazyWrite;
+ Callbacks->AcquireForReadAhead = NULL;
+ Callbacks->ReleaseFromReadAhead = NULL;
+ }
+
+ //
+ // Initialize the queue of read ahead threads
+ //
+
+ InitializeListHead(&NtfsData.ReadAheadThreads);
+
+ //
+ // Set up global pointer to our process.
+ //
+
+ NtfsData.OurProcess = PsGetCurrentProcess();
+
+ //
+ // Use a try-finally to cleanup on errors.
+ //
+
+ try {
+
+ SECURITY_DESCRIPTOR NewDescriptor;
+ SID_IDENTIFIER_AUTHORITY Authority = SECURITY_NT_AUTHORITY;
+
+ SubjectContext = NtfsAllocatePool( PagedPool, sizeof( SECURITY_SUBJECT_CONTEXT ));
+ SeCaptureSubjectContext( SubjectContext );
+ CapturedSubjectContext = TRUE;
+
+ //
+ // Build the default security descriptor which gives full access to
+ // system and administrator.
+ //
+
+ AdminSid = (PSID) NtfsAllocatePool( PagedPool, RtlLengthRequiredSid( 2 ));
+ RtlInitializeSid( AdminSid, &Authority, 2 );
+ *(RtlSubAuthoritySid( AdminSid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
+ *(RtlSubAuthoritySid( AdminSid, 1 )) = DOMAIN_ALIAS_RID_ADMINS;
+
+ SystemSid = (PSID) NtfsAllocatePool( PagedPool, RtlLengthRequiredSid( 1 ));
+ RtlInitializeSid( SystemSid, &Authority, 1 );
+ *(RtlSubAuthoritySid( SystemSid, 0 )) = SECURITY_LOCAL_SYSTEM_RID;
+
+ SystemDaclLength = sizeof( ACL ) +
+ (2 * sizeof( ACCESS_ALLOWED_ACE )) +
+ SeLengthSid( AdminSid ) +
+ SeLengthSid( SystemSid ) +
+ 8; // The 8 is just for good measure
+
+ SystemDacl = NtfsAllocatePool( PagedPool, SystemDaclLength );
+
+ Status = RtlCreateAcl( SystemDacl, SystemDaclLength, ACL_REVISION2 );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ Status = RtlAddAccessAllowedAce( SystemDacl,
+ ACL_REVISION2,
+ GENERIC_ALL,
+ SystemSid );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ Status = RtlAddAccessAllowedAce( SystemDacl,
+ ACL_REVISION2,
+ GENERIC_ALL,
+ AdminSid );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ Status = RtlCreateSecurityDescriptor( &NewDescriptor,
+ SECURITY_DESCRIPTOR_REVISION1 );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ Status = RtlSetDaclSecurityDescriptor( &NewDescriptor,
+ TRUE,
+ SystemDacl,
+ FALSE );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ Status = SeAssignSecurity( NULL,
+ &NewDescriptor,
+ &NtfsData.DefaultDescriptor,
+ FALSE,
+ SubjectContext,
+ IoGetFileObjectGenericMapping(),
+ PagedPool );
+
+ if (!NT_SUCCESS( Status )) { leave; }
+
+ NtfsData.DefaultDescriptorLength = RtlLengthSecurityDescriptor( NtfsData.DefaultDescriptor );
+
+ ASSERT( SeValidSecurityDescriptor( NtfsData.DefaultDescriptorLength,
+ NtfsData.DefaultDescriptor ));
+
+ } finally {
+
+ if (CapturedSubjectContext) {
+
+ SeReleaseSubjectContext( SubjectContext );
+ }
+
+ if (SubjectContext != NULL) { NtfsFreePool( SubjectContext ); }
+
+ if (SystemDacl != NULL) { NtfsFreePool( SystemDacl ); }
+
+ if (AdminSid != NULL) { NtfsFreePool( AdminSid ); }
+
+ if (SystemSid != NULL) { NtfsFreePool( SystemSid ); }
+ }
+
+ //
+ // Raise if we hit an error building the security descriptor.
+ //
+
+ if (!NT_SUCCESS( Status )) { ExRaiseStatus( Status ); }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeNtfsData -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local Support routine
+//
+
+NTSTATUS
+NtfsGet8dot3NameStatus (
+ IN PUNICODE_STRING KeyName,
+ IN PUNICODE_STRING ValueName,
+ IN OUT PULONG Value
+ )
+
+/*++
+
+Routine Description:
+
+ Given a unicode value name this routine will go into the registry
+ location for the 8dot3 name generation information and get the
+ value.
+
+Arguments:
+
+ ValueName - the unicode name for the registry value located in the
+ double space configuration location of the registry.
+ Value - a pointer to the ULONG for the result.
+
+Return Value:
+
+ NTSTATUS
+
+ If STATUS_SUCCESSFUL is returned, the location *Value will be
+ updated with the DWORD value from the registry. If any failing
+ status is returned, this value is untouched.
+
+--*/
+
+{
+ HANDLE Handle;
+ NTSTATUS Status;
+ ULONG RequestLength;
+ ULONG ResultLength;
+ UCHAR Buffer[KEY_WORK_AREA];
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ PKEY_VALUE_FULL_INFORMATION KeyValueInformation;
+
+ InitializeObjectAttributes( &ObjectAttributes,
+ KeyName,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL);
+
+ Status = ZwOpenKey( &Handle,
+ KEY_READ,
+ &ObjectAttributes);
+
+ if (!NT_SUCCESS( Status )) {
+
+ return Status;
+ }
+
+ RequestLength = KEY_WORK_AREA;
+
+ KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)Buffer;
+
+ while (TRUE) {
+
+ Status = ZwQueryValueKey( Handle,
+ ValueName,
+ KeyValueFullInformation,
+ KeyValueInformation,
+ RequestLength,
+ &ResultLength);
+
+ ASSERT( Status != STATUS_BUFFER_OVERFLOW );
+
+ if (Status == STATUS_BUFFER_OVERFLOW) {
+
+ //
+ // Try to get a buffer big enough.
+ //
+
+ if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
+
+ NtfsFreePool( KeyValueInformation );
+ }
+
+ RequestLength += 256;
+
+ KeyValueInformation = (PKEY_VALUE_FULL_INFORMATION)
+ ExAllocatePoolWithTag( PagedPool,
+ RequestLength,
+ 'xftN');
+
+ if (!KeyValueInformation) {
+ return STATUS_NO_MEMORY;
+ }
+
+ } else {
+
+ break;
+ }
+ }
+
+ ZwClose(Handle);
+
+ if (NT_SUCCESS(Status)) {
+
+ if (KeyValueInformation->DataLength != 0) {
+
+ PULONG DataPtr;
+
+ //
+ // Return contents to the caller.
+ //
+
+ DataPtr = (PULONG)
+ ((PUCHAR)KeyValueInformation + KeyValueInformation->DataOffset);
+ *Value = *DataPtr;
+
+ } else {
+
+ //
+ // Treat as if no value was found
+ //
+
+ Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ }
+
+ if (KeyValueInformation != (PKEY_VALUE_FULL_INFORMATION)Buffer) {
+
+ NtfsFreePool(KeyValueInformation);
+ }
+
+ return Status;
+}
diff --git a/private/ntos/cntfs/ntfslog.h b/private/ntos/cntfs/ntfslog.h
new file mode 100644
index 000000000..90a860ee5
--- /dev/null
+++ b/private/ntos/cntfs/ntfslog.h
@@ -0,0 +1,711 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsLog.h
+
+Abstract:
+
+ This module defines the Ntfs-specific log file structures.
+
+Author:
+
+ Tom Miller [TomM] 21-Jul-1991
+
+Revision History:
+
+--*/
+
+#ifndef _NTFSLOG_
+#define _NTFSLOG_
+
+
+//
+// The following type defines the Ntfs log operations.
+//
+// The comment specifies the record type which follows the record.
+// These record types are defined either here or in ntfs.h.
+//
+
+typedef enum _NTFS_LOG_OPERATION {
+
+ Noop = 0x00, //
+ CompensationLogRecord = 0x01, //
+ InitializeFileRecordSegment = 0x02, // FILE_RECORD_SEGMENT_HEADER
+ DeallocateFileRecordSegment = 0x03, //
+ WriteEndOfFileRecordSegment = 0x04, // ATTRIBUTE_RECORD_HEADER
+ CreateAttribute = 0x05, // ATTRIBUTE_RECORD_HEADER
+ DeleteAttribute = 0x06, //
+ UpdateResidentValue = 0x07, // (value)
+ UpdateNonresidentValue = 0x08, // (value)
+ UpdateMappingPairs = 0x09, // (value = mapping pairs bytes)
+ DeleteDirtyClusters = 0x0A, // array of LCN_RANGE
+ SetNewAttributeSizes = 0x0B, // NEW_ATTRIBUTE_SIZES
+ AddIndexEntryRoot = 0x0C, // INDEX_ENTRY
+ DeleteIndexEntryRoot = 0x0D, // INDEX_ENTRY
+ AddIndexEntryAllocation = 0x0E, // INDEX_ENTRY
+ DeleteIndexEntryAllocation = 0x0F, // INDEX_ENTRY
+ WriteEndOfIndexBuffer = 0x10, // INDEX_ENTRY
+ SetIndexEntryVcnRoot = 0x11, // VCN
+ SetIndexEntryVcnAllocation = 0x12, // VCN
+ UpdateFileNameRoot = 0x13, // DUPLICATED_INFORMATION
+ UpdateFileNameAllocation = 0x14, // DUPLICATED_INFORMATION
+ SetBitsInNonresidentBitMap = 0x15, // BITMAP_RANGE
+ ClearBitsInNonresidentBitMap = 0x16, // BITMAP_RANGE
+ HotFix = 0x17, //
+ EndTopLevelAction = 0x18, //
+ PrepareTransaction = 0x19, //
+ CommitTransaction = 0x1A, //
+ ForgetTransaction = 0x1B, //
+ OpenNonresidentAttribute = 0x1C, // OPEN_ATTRIBUTE_ENTRY+ATTRIBUTE_NAME_ENTRY
+ OpenAttributeTableDump = 0x1D, // OPEN_ATTRIBUTE_ENTRY array
+ AttributeNamesDump = 0x1E, // (all attribute names)
+ DirtyPageTableDump = 0x1F, // DIRTY_PAGE_ENTRY array
+ TransactionTableDump = 0x20, // TRANSACTION_ENTRY array
+ UpdateRecordDataRoot = 0x21, // (value)
+ UpdateRecordDataAllocation = 0x22 // (value)
+
+} NTFS_LOG_OPERATION, *PNTFS_LOG_OPERATION;
+
+
+//
+// The Ntfs log record header precedes every log record written to
+// disk by Ntfs.
+//
+
+//
+// Log record header.
+//
+
+typedef struct _NTFS_LOG_RECORD_HEADER {
+
+ //
+ // Log Operations (LOG_xxx codes)
+ //
+
+ USHORT RedoOperation;
+ USHORT UndoOperation;
+
+ //
+ // Offset to Redo record, and its length
+ //
+
+ USHORT RedoOffset;
+ USHORT RedoLength;
+
+ //
+ // Offset to Undo record, and its length. Note, for some Redo/Undo
+ // combinations, the expected records may be the same, and thus
+ // these two values will be identical to the above values.
+ //
+
+ USHORT UndoOffset;
+ USHORT UndoLength;
+
+ //
+ // Open attribute table index to which this update applies. Index 0 is
+ // always reserved for the MFT itself. The value of this field
+ // essentially distinguishes two cases for this update, which will be
+ // referred to as MFT update and nonresident attribute update.
+ //
+ // MFT updates are for initialization and deletion of file record
+ // segments and updates to resident attributes.
+ //
+ // Nonresident attribute updates are used to update attributes which
+ // have been allocated externally to the MFT.
+ //
+
+ USHORT TargetAttribute;
+
+ //
+ // Number of Lcns in use at end of header.
+ //
+
+ USHORT LcnsToFollow;
+
+ //
+ // Byte offset and Vcn for which this update is to be applied. If the
+ // TargetAttribute is the MFT, then the Vcn will always be the exact
+ // Vcn of the start of the file record segment being modified, even
+ // if the modification happens to be in a subsequent cluster of the
+ // same file record. The byte offset in this case is the offset to
+ // the attribute being changed. For the Mft, AttributeOffset may be used
+ // to represent the offset from the start of the attribute record
+ // at which an update is to be applied.
+ //
+ // If the update is to some other (nonresident) attribute, then
+ // TargetVcn and RecordOffset may be used to calculate the reference
+ // point for the update. The ClusterBlockOffset refers to the number
+ // of 512 byte blocks this structure is from the beginning of the
+ // logged Vcn.
+ //
+ // As a bottom line, the exact use of these fields is up to the
+ // writer of this particular log operation, and the associated
+ // restart routines for this attribute.
+ //
+
+ USHORT RecordOffset;
+ USHORT AttributeOffset;
+ USHORT ClusterBlockOffset;
+ USHORT Reserved;
+ VCN TargetVcn;
+
+ //
+ // Run information. This is a variable-length array of LcnsToFollow
+ // entries, only the first of which is declared. Note that the writer
+ // always writes log records according to the physical page size on his
+ // machine, however whenever the log file is being read, no assumption
+ // is made about page size. This is to facilitate moving disks between
+ // systems with different page sizes.
+ //
+
+ LCN LcnsForPage[1];
+
+ //
+ // Immediately following the last run is a log-operation-specific record
+ // whose length may be calculated by subtracting the length of this header
+ // from the length of the entire record returned by LFS. These records
+ // are defined below.
+ //
+
+} NTFS_LOG_RECORD_HEADER, *PNTFS_LOG_RECORD_HEADER;
+
+
+//
+// RESTART AREA STRUCTURES
+//
+// The following structures are present in the Restart Area.
+//
+
+//
+// Generic Restart Table
+//
+// This is a generic table definition for the purpose of describing one
+// of the three table structures used at Restart: the Open Attribute Table,
+// the Dirty Pages Table, and the Transaction Table. This simple structure
+// allows for common initialization and free list management. Allocation
+// and Deallocation and lookup by index are extremely fast, while lookup
+// by value (only performed in the Dirty Pages Table during Restart) is
+// a little slower. I.e., all accesses to these tables during normal
+// operation are extremely fast.
+//
+// If fast access to a table entry by value becomes an issue, then the
+// table may be supplemented by an external Generic Table - it is probably
+// not a good idea to make the Generic Table be part of the structure
+// written to the Log File.
+//
+// Entries in a Restart Table should start with:
+//
+// ULONG AllocatedOrNextFree;
+//
+// An allocated entry will have the pattern RESTART_ENTRY_ALLOCATED
+// in this field.
+//
+
+#define RESTART_ENTRY_ALLOCATED (0xFFFFFFFF)
+
+typedef struct _RESTART_TABLE {
+
+ //
+ // Entry size, in bytes
+ //
+
+ USHORT EntrySize;
+
+ //
+ // Total number of entries in table
+ //
+
+ USHORT NumberEntries;
+
+ //
+ // Number entries that are allocated
+ //
+
+ USHORT NumberAllocated;
+
+ //
+ // Reserved for alignment
+ //
+
+ USHORT Reserved[3];
+
+ //
+ // Free goal - Offset after which entries should be freed to end of
+ // list, as opposed to front. At each checkpoint, the table may be
+ // truncated if there are enough free entries at the end of the list.
+ // Expressed as an offset from the start of this structure.
+ //
+
+ ULONG FreeGoal;
+
+ //
+ // First Free entry (head of list) and Last Free entry (used to deallocate
+ // beyond Free Goal). Expressed as an offset from the start of this
+ // structure.
+ //
+
+ ULONG FirstFree;
+ ULONG LastFree;
+
+ //
+ // The table itself starts here.
+ //
+
+} RESTART_TABLE, *PRESTART_TABLE;
+
+//
+// Macro to get a pointer to an entry in a Restart Table, from the Table
+// pointer and entry index.
+//
+
+#define GetRestartEntryFromIndex(TBL,INDX) ( \
+ (PVOID)((PCHAR)(TBL)->Table + (INDX)) \
+)
+
+//
+// Macro to get an index for an entry in a Restart Table, from the Table
+// pointer and entry pointer.
+//
+
+#define GetIndexFromRestartEntry(TBL,ENTRY) ( \
+ (ULONG)((PCHAR)(ENTRY) - (PCHAR)(TBL)->Table) \
+)
+
+//
+// Macro to see if an entry in a Restart Table is allocated.
+//
+
+#define IsRestartTableEntryAllocated(PTR) ( \
+ (BOOLEAN)(*(PULONG)(PTR) == RESTART_ENTRY_ALLOCATED) \
+)
+
+//
+// Macro to retrieve the size of a Restart Table in bytes.
+//
+
+#define SizeOfRestartTable(TBL) ( \
+ (ULONG)(((TBL)->Table->NumberEntries * \
+ (TBL)->Table->EntrySize) + \
+ sizeof(RESTART_TABLE)) \
+)
+
+//
+// Macro to see if Restart Table is empty. It is empty if the
+// number allocated is zero.
+//
+
+#define IsRestartTableEmpty(TBL) (!(TBL)->Table->NumberAllocated)
+
+//
+// Macro to see if an index is within the currently allocated size
+// for that table.
+//
+
+#define IsRestartIndexWithinTable(TBL,INDX) ( \
+ (BOOLEAN)((INDX) < SizeOfRestartTable(TBL)) \
+)
+
+//
+// Macros to acquire and release a Restart Table.
+//
+
+#define NtfsAcquireExclusiveRestartTable(TBL,WAIT) { \
+ ExAcquireResourceExclusive( &(TBL)->Resource,(WAIT)); \
+}
+
+#define NtfsAcquireSharedRestartTable(TBL,WAIT) { \
+ ExAcquireResourceShared( &(TBL)->Resource,(WAIT)); \
+}
+
+#define NtfsReleaseRestartTable(TBL) { \
+ ExReleaseResource(&(TBL)->Resource); \
+}
+
+//
+// Define some tuning parameters to keep the restart tables a
+// reasonable size.
+//
+
+#define INITIAL_NUMBER_TRANSACTIONS (5)
+#define HIGHWATER_TRANSACTION_COUNT (10)
+#define INITIAL_NUMBER_ATTRIBUTES (8)
+#define HIGHWATER_ATTRIBUTE_COUNT (16)
+
+//
+// Attribute Name Entry. This is a simple structure used to store
+// all of the attribute names for the Open Attribute Table during
+// checkpoint processing. The Attribute Names record written to the log
+// is a series of Attribute Name Entries terminated by an entry with
+// Index == NameLength == 0. The end of the table may be tested for by
+// looking for either of these fields to be 0, as 0 is otherwise invalid
+// for both.
+//
+// Note that the size of this structure is equal to the overhead for storing
+// an attribute name in the table, including the UNICODE_NULL.
+//
+
+typedef struct _ATTRIBUTE_NAME_ENTRY {
+
+ //
+ // Index for Attibute with this name in the Open Attribute Table.
+ //
+
+ USHORT Index;
+
+ //
+ // Length of attribute name to follow in bytes, including a terminating
+ // UNICODE_NULL.
+ //
+
+ USHORT NameLength;
+
+ //
+ // Start of attribute name
+ //
+
+ WCHAR Name[1];
+
+} ATTRIBUTE_NAME_ENTRY, *PATTRIBUTE_NAME_ENTRY;
+
+//
+// Open Attribute Table
+//
+// One entry exists in the Open Attribute Table for each nonresident
+// attribute of each file that is open with modify access.
+//
+// This table is initialized at Restart to the maximum of
+// DEFAULT_ATTRIBUTE_TABLE_SIZE or the size of the table in the log file.
+// It is maintained in the running system.
+//
+
+#pragma pack(4)
+
+typedef struct _OPEN_ATTRIBUTE_ENTRY {
+
+ //
+ // Entry is allocated if this field contains RESTART_ENTRY_ALLOCATED.
+ // Otherwise, it is a free link.
+ //
+
+ ULONG AllocatedOrNextFree;
+
+ //
+ // The following overlay either contains an optional pointer to an
+ // Attribute Name Entry from the Analysis Phase of Restart, or a
+ // pointer to an Scb once attributes have been open and in the normal
+ // running system.
+ //
+ // Specifically, after the Analysis Phase of Restart:
+ //
+ // AttributeName == NULL if there is no attribute name, or the
+ // attribute name was captured in the Attribute
+ // Names Dump in the last successful checkpoint.
+ // AttributeName != NULL if an OpenNonresidentAttribute log record
+ // was encountered, and an Attribute Name Entry
+ // was allocated at that time (and must be
+ // deallocated when no longer needed).
+ //
+ // Once the Nonresident Attributes have been opened during Restart,
+ // and in the running system, this is an Scb pointer.
+ //
+
+ union {
+ PWSTR AttributeName;
+ PSCB Scb;
+ } Overlay;
+
+ //
+ // File Reference of file containing attribute.
+ //
+
+ FILE_REFERENCE FileReference;
+
+ //
+ // Lsn of OpenNonresidentAttribute log record, to distinguish reuses
+ // of this open file record. Log records referring to this Open
+ // Attribute Entry Index, but with Lsns older than this field, can
+ // only occur when the attribute was subsequently deleted - these
+ // log records can be ignored.
+ //
+
+ LSN LsnOfOpenRecord;
+
+ //
+ // Flag to say if dirty pages seen for this attribute during dirty
+ // page scan.
+ //
+
+ BOOLEAN DirtyPagesSeen;
+
+ //
+ // Flag to indicate if the pointer in Overlay above is to an Scb or
+ // attribute name. It is only used during restart when cleaning up
+ // the open attribute table.
+ //
+
+ BOOLEAN AttributeNamePresent;
+
+ //
+ // Reserved for alignment
+ //
+
+ UCHAR Reserved[2];
+
+ //
+ // The following two fields identify the actual attribute
+ // with respect to its file. We identify the attribute by
+ // its type code and name. When the Restart Area is written,
+ // all of the names for all of the open attributes are temporarily
+ // copied to the end of the Restart Area.
+ //
+
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ UNICODE_STRING AttributeName;
+
+ //
+ // This field is only relevant to indices, i.e., if AttributeTypeCode
+ // above is $INDEX_ALLOCATION.
+ //
+
+ ULONG BytesPerIndexBuffer;
+
+} OPEN_ATTRIBUTE_ENTRY, *POPEN_ATTRIBUTE_ENTRY;
+
+#pragma pack()
+
+#define SIZEOF_OPEN_ATTRIBUTE_ENTRY ( \
+ FIELD_OFFSET( OPEN_ATTRIBUTE_ENTRY, BytesPerIndexBuffer ) + 4 \
+)
+
+//
+// Dirty Pages Table
+//
+// One entry exists in the Dirty Pages Table for each page which is
+// dirty at the time the Restart Area is written.
+//
+// This table is initialized at Restart to the maximum of
+// DEFAULT_DIRTY_PAGES_TABLE_SIZE or the size of the table in the log file.
+// It is *not* maintained in the running system.
+//
+
+#pragma pack(4)
+
+typedef struct _DIRTY_PAGE_ENTRY {
+
+ //
+ // Entry is allocated if this field contains RESTART_ENTRY_ALLOCATED.
+ // Otherwise, it is a free link.
+ //
+
+ ULONG AllocatedOrNextFree;
+
+ //
+ // Target attribute index. This is the index into the Open Attribute
+ // Table to which this dirty page entry applies.
+ //
+
+ ULONG TargetAttribute;
+
+ //
+ // Length of transfer, in case this is the end of file, and we cannot
+ // write an entire page.
+ //
+
+ ULONG LengthOfTransfer;
+
+ //
+ // Number of Lcns in the array at end of this structure. See comment
+ // with this array.
+ //
+
+ ULONG LcnsToFollow;
+
+ //
+ // Reserved for alignment
+ //
+
+ ULONG Reserved;
+
+ //
+ // Vcn of dirty page.
+ //
+
+ VCN Vcn;
+
+ //
+ // OldestLsn for log record for which the update has not yet been
+ // written through to disk.
+ //
+
+ LSN OldestLsn;
+
+ //
+ // Run information. This is a variable-length array of LcnsToFollow
+ // entries, only the first of which is declared. Note that the writer
+ // always writes pages according to the physical page size on his
+ // machine, however whenever the log file is being read, no assumption
+ // is made about page size. This is to facilitate moving disks between
+ // systems with different page sizes.
+ //
+
+ LCN LcnsForPage[1];
+
+} DIRTY_PAGE_ENTRY, *PDIRTY_PAGE_ENTRY;
+
+#pragma pack()
+
+//
+// Transaction Table
+//
+// One transaction entry exists for each existing transaction at the time
+// the Restart Area is written.
+//
+// Currently only local transactions are supported, and the transaction
+// ID is simply used to index into this table.
+//
+// This table is initialized at Restart to the maximum of
+// DEFAULT_TRANSACTION_TABLE_SIZE or the size of the table in the log file.
+// It is maintained in the running system.
+//
+
+typedef struct _TRANSACTION_ENTRY {
+
+ //
+ // Entry is allocated if this field contains RESTART_ENTRY_ALLOCATED.
+ // Otherwise, it is a free link.
+ //
+
+ ULONG AllocatedOrNextFree;
+
+ //
+ // Transaction State
+ //
+
+ UCHAR TransactionState;
+
+ //
+ // Reserved for proper alignment
+ //
+
+ UCHAR Reserved[3];
+
+ //
+ // First Lsn for transaction. This tells us how far back in the log
+ // we may have to read to abort the transaction.
+ //
+
+ LSN FirstLsn;
+
+ //
+ // PreviousLsn written for the transaction and UndoNextLsn (next record
+ // which should be undone in the event of a rollback.
+ //
+
+ LSN PreviousLsn;
+ LSN UndoNextLsn;
+
+ //
+ // Number of of undo log records pending abort, and total undo size.
+ //
+
+ ULONG UndoRecords;
+ LONG UndoBytes;
+
+} TRANSACTION_ENTRY, *PTRANSACTION_ENTRY;
+
+//
+// Restart record
+//
+// The Restart record used by NTFS is small, and it only describes where
+// the above information has been written to the log. The above records
+// may be considered logically part of NTFS's restart area.
+//
+
+typedef struct _RESTART_AREA {
+
+ //
+ // Version numbers of NTFS Restart Implementation
+ //
+
+ ULONG MajorVersion;
+ ULONG MinorVersion;
+
+ //
+ // Lsn of Start of Checkpoint. This is the Lsn at which the Analysis
+ // Phase of Restart must begin.
+ //
+
+ LSN StartOfCheckpoint;
+
+ //
+ // Lsns at which the four tables above plus the attribute names reside.
+ //
+
+ LSN OpenAttributeTableLsn;
+ LSN AttributeNamesLsn;
+ LSN DirtyPageTableLsn;
+ LSN TransactionTableLsn;
+
+ //
+ // Lengths of the above structures in bytes.
+ //
+
+ ULONG OpenAttributeTableLength;
+ ULONG AttributeNamesLength;
+ ULONG DirtyPageTableLength;
+ ULONG TransactionTableLength;
+
+} RESTART_AREA, *PRESTART_AREA;
+
+
+//
+// RECORD STRUCTURES USED BY LOG RECORDS
+//
+
+//
+// Set new attribute sizes
+//
+
+typedef struct _NEW_ATTRIBUTE_SIZES {
+
+ LONGLONG AllocationSize;
+ LONGLONG ValidDataLength;
+ LONGLONG FileSize;
+ LONGLONG TotalAllocated;
+
+} NEW_ATTRIBUTE_SIZES, *PNEW_ATTRIBUTE_SIZES;
+
+#define SIZEOF_FULL_ATTRIBUTE_SIZES ( \
+ sizeof( NEW_ATTRIBUTE_SIZES ) \
+)
+
+#define SIZEOF_PARTIAL_ATTRIBUTE_SIZES ( \
+ FIELD_OFFSET( NEW_ATTRIBUTE_SIZES, TotalAllocated ) \
+)
+
+//
+// Describe a bitmap range
+//
+
+typedef struct _BITMAP_RANGE {
+
+ ULONG BitMapOffset;
+ ULONG NumberOfBits;
+
+} BITMAP_RANGE, *PBITMAP_RANGE;
+
+//
+// Describe a range of Lcns
+//
+
+typedef struct _LCN_RANGE {
+
+ LCN StartLcn;
+ LONGLONG Count;
+
+} LCN_RANGE, *PLCN_RANGE;
+
+#endif // _NTFSLOG_
diff --git a/private/ntos/cntfs/ntfsproc.h b/private/ntos/cntfs/ntfsproc.h
new file mode 100644
index 000000000..a4ea71fc5
--- /dev/null
+++ b/private/ntos/cntfs/ntfsproc.h
@@ -0,0 +1,5685 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsProc.h
+
+Abstract:
+
+ This module defines all of the globally used procedures in the Ntfs
+ file system.
+
+Author:
+
+ Brian Andrew [BrianAn] 21-May-1991
+ David Goebel [DavidGoe]
+ Gary Kimura [GaryKi]
+ Tom Miller [TomM]
+
+Revision History:
+
+--*/
+
+#ifndef _NTFSPROC_
+#define _NTFSPROC_
+
+#pragma warning(error:4100) // Unreferenced formal parameter
+#pragma warning(error:4705) // Statement has no effect
+
+#include <ntos.h>
+#include <string.h>
+#include <zwapi.h>
+#include <FsRtl.h>
+#include <ntrtl.h>
+#include <lfs.h>
+#include <ntdddisk.h>
+#include <NtIoLogc.h>
+
+#include "nodetype.h"
+#include "Ntfs.h"
+
+#ifdef _CAIRO_
+
+#ifndef INLINE
+// definition of inline
+#define INLINE _inline
+#endif
+
+#include <ntfsexp.h>
+#endif
+
+#include "NtfsStru.h"
+#include "NtfsData.h"
+#include "NtfsLog.h"
+
+//**** x86 compiler bug ****
+
+#if defined(_M_IX86)
+#undef Int64ShraMod32
+#define Int64ShraMod32(a, b) ((LONGLONG)(a) >> (b))
+#endif
+
+//
+// Tag all of our allocations if tagging is turned on
+//
+
+//
+// Default module pool tag
+//
+
+#define MODULE_POOL_TAG ('0ftN')
+
+#if !(DBG && i386 && defined (NTFSPOOLCHECK))
+
+//
+// Non-debug allocate and free goes directly to the FsRtl routines
+//
+
+#define NtfsAllocatePoolWithTagNoRaise(a,b,c) NtfsAllocatePoolWithTag((a),(b),(c))
+#define NtfsAllocatePoolWithTag(a,b,c) FsRtlAllocatePoolWithTag((a),(b),(c))
+#define NtfsAllocatePool(a,b) FsRtlAllocatePoolWithTag((a),(b),MODULE_POOL_TAG)
+#define NtfsFreePool(pv) ExFreePool(pv)
+
+#else // !DBG
+
+//
+// Debugging routines capture the stack backtrace for allocates and frees
+//
+
+#define NtfsAllocatePoolWithTagNoRaise(a,b,c) NtfsDebugAllocatePoolWithTagNoRaise((a),(b),(c))
+#define NtfsAllocatePoolWithTag(a,b,c) NtfsDebugAllocatePoolWithTag((a),(b),(c))
+#define NtfsAllocatePool(a,b) NtfsDebugAllocatePoolWithTag((a),(b),MODULE_POOL_TAG)
+#define NtfsFreePool(pv) NtfsDebugFreePool(pv)
+
+PVOID
+NtfsDebugAllocatePoolWithTagNoRaise (
+ POOL_TYPE Pool,
+ ULONG Length,
+ ULONG Tag);
+
+PVOID
+NtfsDebugAllocatePoolWithTag (
+ POOL_TYPE Pool,
+ ULONG Length,
+ ULONG Tag);
+
+VOID
+NtfsDebugFreePool (
+ PVOID pv);
+
+#endif // !DBG
+
+
+//
+// Local character comparison macros that we might want to later move to ntfsproc
+//
+
+#define IsCharZero(C) (((C) & 0x000000ff) == 0x00000000)
+#define IsCharMinus1(C) (((C) & 0x000000ff) == 0x000000ff)
+#define IsCharLtrZero(C) (((C) & 0x00000080) == 0x00000080)
+#define IsCharGtrZero(C) (!IsCharLtrZero(C) && !IsCharZero(C))
+
+//
+// The following two macro are used to find the first byte to really store
+// in the mapping pairs. They take as input a pointer to the LargeInteger we are
+// trying to store and a pointer to a character pointer. The character pointer
+// on return points to the first byte that we need to output. That's we skip
+// over the high order 0x00 or 0xff bytes.
+//
+
+typedef struct _SHORT2 {
+ USHORT LowPart;
+ USHORT HighPart;
+} SHORT2, *PSHORT2;
+
+typedef struct _CHAR2 {
+ UCHAR LowPart;
+ UCHAR HighPart;
+} CHAR2, *PCHAR2;
+
+#define GetPositiveByte(LI,CP) { \
+ *(CP) = (PCHAR)(LI); \
+ if ((LI)->HighPart != 0) { *(CP) += 4; } \
+ if (((PSHORT2)(*(CP)))->HighPart != 0) { *(CP) += 2; } \
+ if (((PCHAR2)(*(CP)))->HighPart != 0) { *(CP) += 1; } \
+ if (IsCharLtrZero(*(*CP))) { *(CP) += 1; } \
+}
+
+#define GetNegativeByte(LI,CP) { \
+ *(CP) = (PCHAR)(LI); \
+ if ((LI)->HighPart != 0xffffffff) { *(CP) += 4; } \
+ if (((PSHORT2)(*(CP)))->HighPart != 0xffff) { *(CP) += 2; } \
+ if (((PCHAR2)(*(CP)))->HighPart != 0xff) { *(CP) += 1; } \
+ if (!IsCharLtrZero(*(*CP))) { *(CP) += 1; } \
+}
+
+
+//
+// The following two macro are used by the Fsd/Fsp exception handlers to
+// process an exception. The first macro is the exception filter used in the
+// Fsd/Fsp to decide if an exception should be handled at this level.
+// The second macro decides if the exception is to be finished off by
+// completing the IRP, and cleaning up the Irp Context, or if we should
+// bugcheck. Exception values such as STATUS_FILE_INVALID (raised by
+// VerfySup.c) cause us to complete the Irp and cleanup, while exceptions
+// such as accvio cause us to bugcheck.
+//
+// The basic structure for fsd/fsp exception handling is as follows:
+//
+// NtfsFsdXxx(..)
+// {
+// try {
+//
+// ..
+//
+// } except(NtfsExceptionFilter( IrpContext, GetExceptionRecord() )) {
+//
+// Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+// }
+//
+// Return Status;
+// }
+//
+// To explicitly raise an exception that we expect, such as
+// STATUS_FILE_INVALID, use the below macro NtfsRaiseStatus). To raise a
+// status from an unknown origin (such as CcFlushCache()), use the macro
+// NtfsNormalizeAndRaiseStatus. This will raise the status if it is expected,
+// or raise STATUS_UNEXPECTED_IO_ERROR if it is not.
+//
+// Note that when using these two macros, the original status is placed in
+// IrpContext->ExceptionStatus, signaling NtfsExceptionFilter and
+// NtfsProcessException that the status we actually raise is by definition
+// expected.
+//
+
+LONG
+NtfsExceptionFilter (
+ IN PIRP_CONTEXT IrpContext,
+ IN PEXCEPTION_POINTERS ExceptionPointer
+ );
+
+NTSTATUS
+NtfsProcessException (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL,
+ IN NTSTATUS ExceptionCode
+ );
+
+VOID
+NtfsRaiseStatus (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ );
+
+ULONG
+NtfsRaiseStatusFunction (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status
+ );
+
+//
+// VOID
+// NtfsNormalAndRaiseStatus (
+// IN PRIP_CONTEXT IrpContext,
+// IN NT_STATUS Status
+// IN NT_STATUS NormalStatus
+// );
+//
+
+#define NtfsNormalizeAndRaiseStatus(IC,STAT,NOR_STAT) { \
+ (IC)->ExceptionStatus = (STAT); \
+ ExRaiseStatus(FsRtlNormalizeNtstatus((STAT),NOR_STAT)); \
+}
+
+//
+// Informational popup routine.
+//
+
+VOID
+NtfsRaiseInformationHardError (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ );
+
+
+//
+// Allocation support routines, implemented in AllocSup.c
+//
+// These routines are for querying, allocating and truncating clusters
+// for individual data streams.
+//
+
+//
+// ****Temporary definitions - to be added to Mcb support more
+// efficiently.
+//
+
+#ifdef SYSCACHE
+
+BOOLEAN
+FsRtlIsSyscacheFile (
+ IN PFILE_OBJECT FileObject
+ );
+
+VOID
+FsRtlVerifySyscacheData (
+ IN PFILE_OBJECT FileObject,
+ IN PVOID Buffer,
+ IN ULONG Length,
+ IN ULONG Offset
+ );
+#endif
+
+//
+// The following routine takes an Vbo and returns the lbo and size of
+// the run corresponding to the Vbo. It function result is TRUE if
+// the Vbo has a valid Lbo mapping and FALSE otherwise.
+//
+
+ULONG
+NtfsPreloadAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn
+ );
+
+BOOLEAN
+NtfsLookupAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN VCN Vcn,
+ OUT PLCN Lcn,
+ OUT PLONGLONG ClusterCount,
+ OUT PVOID *RangePtr OPTIONAL,
+ OUT PULONG RunIndex OPTIONAL
+ );
+
+//
+// The following two routines modify the allocation of a data stream
+// represented by an Scb.
+//
+
+BOOLEAN
+NtfsAllocateAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN AllocateAll,
+ IN BOOLEAN LogIt,
+ IN LONGLONG Size,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT NewLocation OPTIONAL
+ );
+
+VOID
+NtfsAddAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN LONGLONG ClusterCount,
+ IN BOOLEAN AskForMore
+ );
+
+VOID
+NtfsDeleteAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject OPTIONAL,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN BreakupAllowed
+ );
+
+//
+// Routines for Mcb to Mapping Pairs operations
+//
+
+ULONG
+NtfsGetSizeForMappingPairs (
+ IN PNTFS_MCB Mcb,
+ IN ULONG BytesAvailable,
+ IN VCN LowestVcn,
+ IN PVCN StopOnVcn OPTIONAL,
+ OUT PVCN StoppedOnVcn
+ );
+
+VOID
+NtfsBuildMappingPairs (
+ IN PNTFS_MCB Mcb,
+ IN VCN LowestVcn,
+ IN OUT PVCN HighestVcn,
+ OUT PCHAR MappingPairs
+ );
+
+VCN
+NtfsGetHighestVcn (
+ IN PIRP_CONTEXT IrpContext,
+ IN VCN LowestVcn,
+ IN PCHAR MappingPairs
+ );
+
+BOOLEAN
+NtfsReserveClusters (
+ IN PIRP_CONTEXT IrpContext OPTIONAL,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG ByteCount
+ );
+
+VOID
+NtfsFreeReservedClusters (
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG ByteCount
+ );
+
+VOID
+NtfsFreeFinalReservedClusters (
+ IN PVCB Vcb,
+ IN LONGLONG ClusterCount
+ );
+
+
+//
+// Attribute lookup routines, implemented in AttrSup.c
+//
+
+//
+// This macro detects if we are enumerating through base or external
+// attributes, and calls the appropriate function.
+//
+// BOOLEAN
+// LookupNextAttribute (
+// IN PRIP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN ATTRIBUTE_TYPE_CODE Code,
+// IN PUNICODE_STRING Name OPTIONAL,
+// IN BOOLEAN IgnoreCase,
+// IN PVOID Value OPTIONAL,
+// IN ULONG ValueLength,
+// IN PATTRIBUTE_ENUMERATION_CONTEXT Context
+// );
+//
+
+#define LookupNextAttribute(IRPCTXT,FCB,CODE,NAME,IC,VALUE,LENGTH,CONTEXT) \
+ ( (CONTEXT)->AttributeList.Bcb == NULL \
+ ? NtfsLookupInFileRecord( (IRPCTXT), \
+ (FCB), \
+ NULL, \
+ (CODE), \
+ (NAME), \
+ NULL, \
+ (IC), \
+ (VALUE), \
+ (LENGTH), \
+ (CONTEXT)) \
+ : NtfsLookupExternalAttribute((IRPCTXT), \
+ (FCB), \
+ (CODE), \
+ (NAME), \
+ NULL, \
+ (IC), \
+ (VALUE), \
+ (LENGTH), \
+ (CONTEXT)) )
+
+BOOLEAN
+NtfsLookupExternalAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN PVCN Vcn OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+
+
+//
+// The following two routines do lookups based on the attribute definitions.
+//
+
+ATTRIBUTE_TYPE_CODE
+NtfsGetAttributeTypeCode (
+ IN PVCB Vcb,
+ IN UNICODE_STRING AttributeTypeName
+ );
+
+
+//
+// PATTRIBUTE_DEFINITION_COLUMNS
+// NtfsGetAttributeDefinition (
+// IN PVCB Vcb,
+// IN ATTRIBUTE_TYPE_CODE AttributeTypeCode
+// )
+//
+
+#define NtfsGetAttributeDefinition(Vcb,AttributeTypeCode) \
+ (&Vcb->AttributeDefinitions[(AttributeTypeCode / 0x10) - 1])
+
+//
+// This routine looks up the attribute uniquely-qualified by the specified
+// Attribute Code and case-sensitive name. The attribute may not be unique
+// if IgnoreCase is specified.
+//
+
+
+BOOLEAN
+NtfsLookupInFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFILE_REFERENCE BaseFileReference OPTIONAL,
+ IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+ IN PUNICODE_STRING QueriedName OPTIONAL,
+ IN PVCN Vcn OPTIONAL,
+ IN BOOLEAN IgnoreCase,
+ IN PVOID QueriedValue OPTIONAL,
+ IN ULONG QueriedValueLength,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+
+//
+// This routine attempts to find the fist occurrence of an attribute with
+// the specified AttributeTypeCode and the specified QueriedName in the
+// specified BaseFileReference. If we find one, its attribute record is
+// pinned and returned.
+//
+// BOOLEAN
+// NtfsLookupAttributeByName (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PFILE_REFERENCE BaseFileReference,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// IN PUNICODE_STRING QueriedName OPTIONAL,
+// IN PVCN Vcn OPTIONAL,
+// IN BOOLEAN IgnoreCase,
+// OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupAttributeByName(IrpContext,Fcb,BaseFileReference,QueriedTypeCode,QueriedName,Vcn,IgnoreCase,Context) \
+ NtfsLookupInFileRecord( IrpContext, \
+ Fcb, \
+ BaseFileReference, \
+ QueriedTypeCode, \
+ QueriedName, \
+ Vcn, \
+ IgnoreCase, \
+ NULL, \
+ 0, \
+ Context )
+
+
+//
+// This function continues where the prior left off.
+//
+// BOOLEAN
+// NtfsLookupNextAttributeByName (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// IN PUNICODE_STRING QueriedName OPTIONAL,
+// IN BOOLEAN IgnoreCase,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+#define NtfsLookupNextAttributeByName(IrpContext,Fcb,QueriedTypeCode,QueriedName,IgnoreCase,Context) \
+ LookupNextAttribute( IrpContext, \
+ Fcb, \
+ QueriedTypeCode, \
+ QueriedName, \
+ IgnoreCase, \
+ NULL, \
+ 0, \
+ Context )
+
+//
+// The following routines find the attribute record for a given Scb.
+// And also update the scb from the attribute
+//
+// VOID
+// NtfsLookupAttributeForScb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb,
+// IN PVCN Vcn OPTIONAL,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupAttributeForScb(IrpContext,Scb,Vcn,Context) \
+ if (!NtfsLookupAttributeByName( IrpContext, \
+ Scb->Fcb, \
+ &Scb->Fcb->FileReference, \
+ Scb->AttributeTypeCode, \
+ &Scb->AttributeName, \
+ Vcn, \
+ FALSE, \
+ Context )) { \
+ DebugTrace( 0, 0, ("Could not find attribute for Scb @ %08lx\n", Scb )); \
+ ASSERTMSG("Could not find attribute for Scb\n", FALSE); \
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb ); \
+ }
+
+
+//
+// This routine looks up and returns the next attribute for a given Scb.
+//
+// BOOLEAN
+// NtfsLookupNextAttributeForScb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupNextAttributeForScb(IrpContext,Scb,Context) \
+ NtfsLookupNextAttributeByName( IrpContext, \
+ Scb->Fcb, \
+ Scb->AttributeTypeCode, \
+ &Scb->AttributeName, \
+ FALSE, \
+ Context )
+
+VOID
+NtfsUpdateScbFromAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB Scb,
+ IN PATTRIBUTE_RECORD_HEADER AttrHeader OPTIONAL
+ );
+
+//
+// The following routines deal with the Fcb and the duplicated information field.
+//
+
+VOID
+NtfsUpdateFcbInfoFromDisk (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN LoadSecurity,
+ IN OUT PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+ OUT POLD_SCB_SNAPSHOT UnnamedDataSizes OPTIONAL
+ );
+
+//
+// These routines looks up the first/next attribute, i.e., they may be used
+// to retrieve all atributes for a file record.
+//
+// If the Bcb in the Found Attribute structure changes in the Next call, then
+// the previous Bcb is autmatically unpinned and the new one pinned.
+//
+
+//
+// This routine attempts to find the fist occurrence of an attribute with
+// the specified AttributeTypeCode in the specified BaseFileReference. If we
+// find one, its attribute record is pinned and returned.
+//
+// BOOLEAN
+// NtfsLookupAttribute (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PFILE_REFERENCE BaseFileReference,
+// OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupAttribute(IrpContext,Fcb,BaseFileReference,Context) \
+ NtfsLookupInFileRecord( IrpContext, \
+ Fcb, \
+ BaseFileReference, \
+ $UNUSED, \
+ NULL, \
+ NULL, \
+ FALSE, \
+ NULL, \
+ 0, \
+ Context )
+
+//
+// This function continues where the prior left off.
+//
+// BOOLEAN
+// NtfsLookupNextAttribute (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupNextAttribute(IrpContext,Fcb,Context) \
+ LookupNextAttribute( IrpContext, \
+ Fcb, \
+ $UNUSED, \
+ NULL, \
+ FALSE, \
+ NULL, \
+ 0, \
+ Context )
+
+
+//
+// These routines looks up the first/next attribute of the given type code.
+//
+// If the Bcb in the Found Attribute structure changes in the Next call, then
+// the previous Bcb is autmatically unpinned and the new one pinned.
+//
+
+
+//
+// This routine attempts to find the fist occurrence of an attribute with
+// the specified AttributeTypeCode in the specified BaseFileReference. If we
+// find one, its attribute record is pinned and returned.
+//
+// BOOLEAN
+// NtfsLookupAttributeByCode (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PFILE_REFERENCE BaseFileReference,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupAttributeByCode(IrpContext,Fcb,BaseFileReference,QueriedTypeCode,Context) \
+ NtfsLookupInFileRecord( IrpContext, \
+ Fcb, \
+ BaseFileReference, \
+ QueriedTypeCode, \
+ NULL, \
+ NULL, \
+ FALSE, \
+ NULL, \
+ 0, \
+ Context )
+
+
+//
+// This function continues where the prior left off.
+//
+// BOOLEAN
+// NtfsLookupNextAttributeByCode (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupNextAttributeByCode(IrpContext,Fcb,QueriedTypeCode,Context) \
+ LookupNextAttribute( IrpContext, \
+ Fcb, \
+ QueriedTypeCode, \
+ NULL, \
+ FALSE, \
+ NULL, \
+ 0, \
+ Context )
+
+//
+// These routines looks up the first/next occurrence of an attribute by its
+// Attribute Code and exact attribute value (consider using RtlCompareMemory).
+// The value contains everything outside of the standard attribute header,
+// so for example, to look up the File Name attribute by value, the caller
+// must form a record with not only the file name in it, but with the
+// ParentDirectory filled in as well. The length should be exact, and not
+// include any unused (such as in DOS_NAME) or reserved characters.
+//
+// If the Bcb changes in the Next call, then the previous Bcb is autmatically
+// unpinned and the new one pinned.
+//
+
+
+//
+// This routine attempts to find the fist occurrence of an attribute with
+// the specified AttributeTypeCode and the specified QueriedValue in the
+// specified BaseFileReference. If we find one, its attribute record is
+// pinned and returned.
+//
+// BOOLEAN
+// NtfsLookupAttributeByValue (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PFILE_REFERENCE BaseFileReference,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// IN PVOID QueriedValue,
+// IN ULONG QueriedValueLength,
+// OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupAttributeByValue(IrpContext,Fcb,BaseFileReference,QueriedTypeCode,QueriedValue,QueriedValueLength,Context) \
+ NtfsLookupInFileRecord( IrpContext, \
+ Fcb, \
+ BaseFileReference, \
+ QueriedTypeCode, \
+ NULL, \
+ NULL, \
+ FALSE, \
+ QueriedValue, \
+ QueriedValueLength, \
+ Context )
+
+//
+// This function continues where the prior left off.
+//
+// BOOLEAN
+// NtfsLookupNextAttributeByValue (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN ATTRIBUTE_TYPE_CODE QueriedTypeCode,
+// IN PVOID QueriedValue,
+// IN ULONG QueriedValueLength,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+// )
+//
+
+#define NtfsLookupNextAttributeByValue(IrpContext,Fcb,QueriedTypeCode,QueriedValue,QueriedValueLength,Context) \
+ LookupNextAttribute( IrpContext, \
+ Fcb, \
+ QueriedTypeCode, \
+ NULL, \
+ FALSE, \
+ QueriedValue, \
+ QueriedValueLength, \
+ Context )
+
+
+VOID
+NtfsCleanupAttributeContext(
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+ );
+
+//
+//
+//
+// Here are some routines/macros for dealing with Attribute Enumeration
+// Contexts.
+//
+// VOID
+// NtfsInitializeAttributeContext(
+// OUT PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+// VOID
+// NtfsPinMappedAttribute(
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN OUT PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+// PATTRIBUTE_RECORD_HEADER
+// NtfsFoundAttribute(
+// IN PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+// PBCB
+// NtfsFoundBcb(
+// IN PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+// PFILE_RECORD
+// NtfsContainingFileRecord (
+// IN PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+// LONGLONG
+// NtfsMftOffset (
+// IN PATTRIBUTE_ENUMERATION_CONTEXT AttributeContext
+// );
+//
+
+#define NtfsInitializeAttributeContext(CTX) { \
+ RtlZeroMemory( (CTX), sizeof(ATTRIBUTE_ENUMERATION_CONTEXT) ); \
+}
+
+#define NtfsPinMappedAttribute(IC,V,CTX) { \
+ NtfsPinMappedData( (IC), \
+ (V)->MftScb, \
+ (CTX)->FoundAttribute.MftFileOffset, \
+ (V)->BytesPerFileRecordSegment, \
+ &(CTX)->FoundAttribute.Bcb ); \
+}
+
+#define NtfsFoundAttribute(CTX) ( \
+ (CTX)->FoundAttribute.Attribute \
+)
+
+#define NtfsFoundBcb(CTX) ( \
+ (CTX)->FoundAttribute.Bcb \
+)
+
+#define NtfsContainingFileRecord(CTX) ( \
+ (CTX)->FoundAttribute.FileRecord \
+)
+
+#define NtfsMftOffset(CTX) ( \
+ (CTX)->FoundAttribute.MftFileOffset \
+)
+
+//
+// This routine returns whether an attribute is resident or not.
+//
+// BOOLEAN
+// NtfsIsAttributeResident (
+// IN PATTRIBUTE_RECORD_HEADER Attribute
+// );
+//
+// PVOID
+// NtfsAttributeValue (
+// IN PATTRIBUTE_RECORD_HEADER Attribute
+// );
+//
+
+#define NtfsIsAttributeResident(ATTR) ( \
+ ((ATTR)->FormCode == RESIDENT_FORM) \
+)
+
+#define NtfsAttributeValue(ATTR) ( \
+ ((PCHAR)(ATTR) + (ULONG)(ATTR)->Form.Resident.ValueOffset) \
+)
+
+//
+// This routine modifies the valid data length and file size on disk for
+// a given Scb.
+//
+
+VOID
+NtfsWriteFileSizes (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLONGLONG ValidDataLength,
+ IN BOOLEAN AdvanceOnly,
+ IN BOOLEAN LogIt
+ );
+
+//
+// This routine updates the standard information attribute from the
+// information in the Fcb.
+//
+
+VOID
+NtfsUpdateStandardInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+//
+// This routine grows and updates the standard information attribute from
+// the information in the Fcb.
+//
+
+VOID
+NtfsGrowStandardInformation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+//
+// Attribute FILE_NAME routines. These routines deal with filename attributes.
+//
+
+// VOID
+// NtfsBuildFileNameAttribute (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFILE_REFERENCE ParentDirectory,
+// IN UNICODE_STRING FileName,
+// IN UCHAR Flags,
+// OUT PFILE_NAME FileNameValue
+// );
+//
+
+#define NtfsBuildFileNameAttribute(IC,PD,FN,FL,PFNA) { \
+ (PFNA)->ParentDirectory = *(PD); \
+ (PFNA)->FileNameLength = (UCHAR)((FN).Length >> 1); \
+ (PFNA)->Flags = FL; \
+ RtlMoveMemory( (PFNA)->FileName, (FN).Buffer, (ULONG)(FN).Length ); \
+}
+
+BOOLEAN
+NtfsLookupEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN BOOLEAN IgnoreCase,
+ IN OUT PUNICODE_STRING Name,
+ IN OUT PFILE_NAME *FileNameAttr,
+ IN OUT PUSHORT FileNameAttrLength,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ OUT PINDEX_ENTRY *IndexEntry,
+ OUT PBCB *IndexEntryBcb
+ );
+
+//
+// Macro to decide when to create an attribute resident.
+//
+// BOOLEAN
+// NtfsShouldAttributeBeResident (
+// IN PVCB Vcb,
+// IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+// IN ULONG Size
+// );
+//
+
+#define RS(S) ((S) + SIZEOF_RESIDENT_ATTRIBUTE_HEADER)
+
+#define NtfsShouldAttributeBeResident(VC,FR,S) ( \
+ (BOOLEAN)((RS(S) <= ((FR)->BytesAvailable - (FR)->FirstFreeByte)) || \
+ (RS(S) < (VC)->BigEnoughToMove)) \
+)
+
+//
+// Attribute creation/modification routines
+//
+// These three routines do *not* presuppose either the Resident or Nonresident
+// form, with the single exception that if the attribute is indexed, then
+// it must be Resident.
+//
+// NtfsMapAttributeValue and NtfsChangeAttributeValue implement transparent
+// access to small to medium sized attributes (such as $ACL and $EA), and
+// work whether the attribute is resident or nonresident. The design target
+// is 0-64KB in size. Attributes larger than 256KB (or more accurrately,
+// whatever the virtual mapping granularity is in the Cache Manager) will not
+// work correctly.
+//
+
+VOID
+NtfsCreateAttributeWithValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN USHORT AttributeFlags,
+ IN PFILE_REFERENCE WhereIndexed OPTIONAL,
+ IN BOOLEAN LogIt,
+ OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsMapAttributeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PVOID *Buffer,
+ OUT PULONG Length,
+ OUT PBCB *Bcb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsChangeAttributeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG ValueOffset,
+ IN PVOID Value OPTIONAL,
+ IN ULONG ValueLength,
+ IN BOOLEAN SetNewLength,
+ IN BOOLEAN LogNonresidentToo,
+ IN BOOLEAN CreateSectionUnderway,
+ IN BOOLEAN PreserveContext,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsConvertToNonresident (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PATTRIBUTE_RECORD_HEADER Attribute,
+ IN BOOLEAN CreateSectionUnderway,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context OPTIONAL
+ );
+
+VOID
+NtfsDeleteAttributeRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN PreserveFileRecord,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsDeleteAllocationFromRecord (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN BOOLEAN BreakupAllowed
+ );
+
+BOOLEAN
+NtfsChangeAttributeSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ULONG Length,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsAddToAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN MFT_SEGMENT_REFERENCE SegmentReference,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsDeleteFromAttributeList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+BOOLEAN
+NtfsRewriteMftMapping (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsSetTotalAllocatedField (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN USHORT CompressionState
+ );
+
+//
+// The following three routines dealing with allocation are to be
+// called by allocsup.c only. Other software must call the routines
+// in allocsup.c
+//
+
+BOOLEAN
+NtfsCreateAttributeWithAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN UseContext,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context
+ );
+
+VOID
+NtfsAddAttributeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN PVCN StartingVcn OPTIONAL,
+ IN PVCN ClusterCount OPTIONAL
+ );
+
+VOID
+NtfsDeleteAttributeAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN LogIt,
+ IN PVCN StopOnVcn,
+ IN OUT PATTRIBUTE_ENUMERATION_CONTEXT Context,
+ IN BOOLEAN TruncateToVcn
+ );
+
+//
+// To delete a file, you must first ask if it is deleteable from the ParentScb
+// used to get there for your caller, and then you can delete it if it is.
+//
+
+//
+// BOOLEAN
+// NtfsIsLinkDeleteable (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// OUT PBOOLEAN NonEmptyIndex,
+// OUT PBOOLEAN LastLink
+// );
+//
+
+#define NtfsIsLinkDeleteable(IC,FC,NEI,LL) ((BOOLEAN) \
+ (((*(LL) = ((BOOLEAN) (FC)->LinkCount == 1)), (FC)->LinkCount > 1) || \
+ (NtfsIsFileDeleteable( (IC), (FC), (NEI) ))) \
+)
+
+BOOLEAN
+NtfsIsFileDeleteable (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PBOOLEAN NonEmptyIndex
+ );
+
+VOID
+NtfsDeleteFile (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB ParentScb OPTIONAL,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ );
+
+VOID
+NtfsPrepareForUpdateDuplicate (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PLCB *Lcb,
+ IN OUT PSCB *ParentScb,
+ IN BOOLEAN AcquireShared
+ );
+
+VOID
+NtfsUpdateDuplicateInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PLCB Lcb OPTIONAL,
+ IN PSCB ParentScb OPTIONAL
+ );
+
+VOID
+NtfsUpdateLcbDuplicateInfo (
+ IN PFCB Fcb,
+ IN PLCB Lcb
+ );
+
+VOID
+NtfsUpdateFcb (
+ IN PFCB Fcb
+ );
+
+//
+// The following routines add and remove hard links.
+//
+
+VOID
+NtfsAddLink (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN CreatePrimaryLink,
+ IN PSCB ParentScb,
+ IN PFCB Fcb,
+ IN PFILE_NAME FileNameAttr,
+ IN PBOOLEAN LogIt OPTIONAL,
+ OUT PUCHAR FileNameFlags,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ IN PNAME_PAIR NamePair OPTIONAL
+ );
+
+VOID
+NtfsRemoveLink (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB ParentScb,
+ IN UNICODE_STRING LinkName,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ );
+
+VOID
+NtfsRemoveLinkViaFlags (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb,
+ IN UCHAR FileNameFlags,
+ IN OUT PNAME_PAIR NamePair OPTIONAL
+ );
+
+//
+// These routines are intended for low-level attribute access, such as within
+// attrsup, or for applying update operations from the log during restart.
+//
+
+VOID
+NtfsRestartInsertAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN PVOID ValueOrMappingPairs OPTIONAL,
+ IN ULONG Length
+ );
+
+VOID
+NtfsRestartRemoveAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset
+ );
+
+VOID
+NtfsRestartChangeAttributeSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN ULONG NewRecordLength
+ );
+
+VOID
+NtfsRestartChangeValue (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN PVOID Data OPTIONAL,
+ IN ULONG Length,
+ IN BOOLEAN SetNewLength
+ );
+
+VOID
+NtfsRestartChangeMapping (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN PVOID Data,
+ IN ULONG Length
+ );
+
+VOID
+NtfsRestartWriteEndOfFileRecord (
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER OldAttribute,
+ IN PATTRIBUTE_RECORD_HEADER NewAttributes,
+ IN ULONG SizeOfNewAttributes
+ );
+
+
+//
+// Bitmap support routines. Implemented in BitmpSup.c
+//
+
+//
+// The following routines are used for allocating and deallocating clusters
+// on the disk. The first routine initializes the allocation support
+// routines and must be called for each newly mounted/verified volume.
+// The next two routines allocate and deallocate clusters via Mcbs.
+// The last three routines are simple query routines.
+//
+
+VOID
+NtfsInitializeClusterAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+BOOLEAN
+NtfsAllocateClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PSCB Scb,
+ IN VCN StartingVcn,
+ IN BOOLEAN AllocateAll,
+ IN LONGLONG ClusterCount,
+ IN OUT PLONGLONG DesiredClusterCount
+ );
+
+VOID
+NtfsAddBadCluster (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LCN Lcn
+ );
+
+BOOLEAN
+NtfsDeallocateClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PNTFS_MCB Mcb,
+ IN VCN StartingVcn,
+ IN VCN EndingVcn,
+ OUT PLONGLONG TotalAllocated OPTIONAL
+ );
+
+VOID
+NtfsCleanupClusterAllocationHints (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_MCB Mcb
+ );
+
+VOID
+NtfsScanEntireBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Rescan
+ );
+
+VOID
+NtfsUninitializeCachedBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+//
+// The following two routines are called at Restart to make bitmap
+// operations in the volume bitmap recoverable.
+//
+
+VOID
+NtfsRestartSetBitsInBitMap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRTL_BITMAP Bitmap,
+ IN ULONG BitMapOffset,
+ IN ULONG NumberOfBits
+ );
+
+VOID
+NtfsRestartClearBitsInBitMap (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRTL_BITMAP Bitmap,
+ IN ULONG BitMapOffset,
+ IN ULONG NumberOfBits
+ );
+
+//
+// The following routines are for allocating and deallocating records
+// based on a bitmap attribute (e.g., allocating mft file records based on
+// the bitmap attribute of the mft). If necessary the routines will
+// also extend/truncate the data and bitmap attributes to satisfy the
+// operation.
+//
+
+VOID
+NtfsInitializeRecordAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB DataScb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute,
+ IN ULONG BytesPerRecord,
+ IN ULONG ExtendGranularity, // In terms of records
+ IN ULONG TruncateGranularity, // In terms of records
+ IN OUT PRECORD_ALLOCATION_CONTEXT RecordAllocationContext
+ );
+
+VOID
+NtfsUninitializeRecordAllocation (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PRECORD_ALLOCATION_CONTEXT RecordAllocationContext
+ );
+
+ULONG
+NtfsAllocateRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Hint,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ );
+
+VOID
+NtfsDeallocateRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Index,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ );
+
+VOID
+NtfsReserveMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ );
+
+ULONG
+NtfsAllocateMftReservedRecord (
+ IN OUT PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ );
+
+VOID
+NtfsDeallocateRecordsComplete (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+BOOLEAN
+NtfsIsRecordAllocated (
+ IN PIRP_CONTEXT IrpContext,
+ IN PRECORD_ALLOCATION_CONTEXT RecordAllocationContext,
+ IN ULONG Index,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT BitmapAttribute
+ );
+
+VOID
+NtfsScanMftBitmap (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb
+ );
+
+BOOLEAN
+NtfsCreateMftHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+BOOLEAN
+NtfsFindMftFreeTail (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PLONGLONG FileOffset
+ );
+
+
+//
+// Buffer control routines for data caching using internal attribute
+// streams implemented in CacheSup.c
+//
+
+#define NtfsCreateInternalAttributeStream(IC,S,U) { \
+ NtfsCreateInternalStreamCommon((IC),(S),(U),FALSE); \
+}
+
+#define NtfsCreateInternalCompressedStream(IC,S,U) { \
+ NtfsCreateInternalStreamCommon((IC),(S),(U),TRUE); \
+}
+
+VOID
+NtfsCreateInternalStreamCommon (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN UpdateScb,
+ IN BOOLEAN CompressedStream
+ );
+
+BOOLEAN
+NtfsDeleteInternalAttributeStream (
+ IN PSCB Scb,
+ IN BOOLEAN ForceClose
+ );
+
+//
+// The following routines provide direct access to data in an attribute.
+//
+
+VOID
+NtfsMapStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ );
+
+VOID
+NtfsPinMappedData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ IN OUT PVOID *Bcb
+ );
+
+VOID
+NtfsPinStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ );
+
+VOID
+NtfsPreparePinWriteStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Zero,
+ OUT PVOID *Bcb,
+ OUT PVOID *Buffer
+ );
+
+NTSTATUS
+NtfsCompleteMdl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+BOOLEAN
+NtfsZeroData (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFILE_OBJECT FileObject,
+ IN LONGLONG StartingZero,
+ IN LONGLONG ByteCount
+ );
+
+//
+// VOID
+// NtfsFreeBcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN OUT PBCB *Bcb
+// );
+//
+// VOID
+// NtfsUnpinBcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN OUT PBCB *Bcb,
+// );
+//
+
+#define NtfsFreeBcb(IC,BC) { \
+ ASSERT_IRP_CONTEXT(IC); \
+ if (*(BC) != NULL) \
+ { \
+ CcFreePinnedData(*(BC)); \
+ *(BC) = NULL; \
+ } \
+}
+
+#define NtfsUnpinBcb(BC) { \
+ if (*(BC) != NULL) \
+ { \
+ CcUnpinData(*(BC)); \
+ *(BC) = NULL; \
+ } \
+}
+
+
+//
+// Ntfs structure check routines in CheckSup.c
+//
+
+BOOLEAN
+NtfsCheckFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord
+ );
+
+BOOLEAN
+NtfsCheckAttributeRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ );
+
+BOOLEAN
+NtfsCheckIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PINDEX_ROOT IndexRoot,
+ IN ULONG AttributeSize
+ );
+
+BOOLEAN
+NtfsCheckIndexBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ );
+
+BOOLEAN
+NtfsCheckIndexHeader (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_HEADER IndexHeader,
+ IN ULONG BytesAvailable
+ );
+
+BOOLEAN
+NtfsCheckLogRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN ULONG LogRecordLength,
+ IN TRANSACTION_ID TransactionId
+ );
+
+BOOLEAN
+NtfsCheckRestartTable (
+ IN PRESTART_TABLE RestartTable,
+ IN ULONG TableSize
+ );
+
+
+//
+// Collation routines, implemented in ColatSup.c
+//
+// These routines perform low-level collation operations, primarily
+// for IndexSup. All of these routines are dispatched to via dispatch
+// tables indexed by the collation rule. The dispatch tables are
+// defined here, and the actual implementations are in colatsup.c
+//
+
+typedef
+FSRTL_COMPARISON_RESULT
+(*PCOMPARE_VALUES) (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN FSRTL_COMPARISON_RESULT WildCardIs,
+ IN BOOLEAN IgnoreCase
+ );
+
+typedef
+BOOLEAN
+(*PIS_IN_EXPRESSION) (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+typedef
+BOOLEAN
+(*PARE_EQUAL) (
+ IN PWCH UnicodeTable,
+ IN PVOID Value,
+ IN PINDEX_ENTRY IndexEntry,
+ IN BOOLEAN IgnoreCase
+ );
+
+typedef
+BOOLEAN
+(*PCONTAINS_WILDCARD) (
+ IN PVOID Value
+ );
+
+typedef
+VOID
+(*PUPCASE_VALUE) (
+ IN PWCH UnicodeTable,
+ IN ULONG UnicodeTableSize,
+ IN OUT PVOID Value
+ );
+
+extern PCOMPARE_VALUES NtfsCompareValues[COLLATION_NUMBER_RULES];
+extern PIS_IN_EXPRESSION NtfsIsInExpression[COLLATION_NUMBER_RULES];
+extern PARE_EQUAL NtfsIsEqual[COLLATION_NUMBER_RULES];
+extern PCONTAINS_WILDCARD NtfsContainsWildcards[COLLATION_NUMBER_RULES];
+extern PUPCASE_VALUE NtfsUpcaseValue[COLLATION_NUMBER_RULES];
+
+BOOLEAN
+NtfsFileNameIsInExpression (
+ IN PWCH UnicodeTable,
+ IN PFILE_NAME ExpressionName,
+ IN PFILE_NAME FileName,
+ IN BOOLEAN IgnoreCase
+ );
+
+BOOLEAN
+NtfsFileNameIsEqual (
+ IN PWCH UnicodeTable,
+ IN PFILE_NAME ExpressionName,
+ IN PFILE_NAME FileName,
+ IN BOOLEAN IgnoreCase
+ );
+
+
+//
+// Compression on the wire routines in CowSup.c
+//
+
+BOOLEAN
+NtfsCopyReadC (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ OUT PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+NTSTATUS
+NtfsCompressedCopyRead (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ OUT PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN ULONG CompressionUnitSize,
+ IN ULONG ChunkSize
+ );
+
+BOOLEAN
+NtfsMdlReadCompleteCompressed (
+ IN struct _FILE_OBJECT *FileObject,
+ IN PMDL MdlChain,
+ IN struct _DEVICE_OBJECT *DeviceObject
+ );
+
+BOOLEAN
+NtfsCopyWriteC (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ IN PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+NTSTATUS
+NtfsCompressedCopyWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PVOID Buffer,
+ OUT PMDL *MdlChain,
+ IN PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN ULONG CompressionUnitSize,
+ IN ULONG ChunkSize,
+ IN ULONG EngineMatches
+ );
+
+BOOLEAN
+NtfsMdlWriteCompleteCompressed (
+ IN struct _FILE_OBJECT *FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PMDL MdlChain,
+ IN struct _DEVICE_OBJECT *DeviceObject
+ );
+
+
+//
+// Device I/O routines, implemented in DevIoSup.c
+//
+// These routines perform the actual device read and writes. They only affect
+// the on disk structure and do not alter any other data structures.
+//
+
+VOID
+NtfsLockUserBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PIRP Irp,
+ IN LOCK_OPERATION Operation,
+ IN ULONG BufferLength
+ );
+
+PVOID
+NtfsMapUserBuffer (
+ IN OUT PIRP Irp
+ );
+
+NTSTATUS
+NtfsVolumeDasdIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PVCB Vcb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ );
+
+VOID
+NtfsPagingFileIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ );
+
+NTSTATUS
+NtfsNonCachedIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount,
+ IN ULONG CompressedStream
+ );
+
+VOID
+NtfsNonCachedNonAlignedIo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN ULONG ByteCount
+ );
+
+VOID
+NtfsTransformUsaBlock (
+ IN PSCB Scb,
+ IN OUT PVOID SystemBuffer,
+ IN OUT PVOID Buffer,
+ IN ULONG Length
+ );
+
+VOID
+NtfsCreateMdlAndBuffer (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ThisScb,
+ IN UCHAR NeedTwoBuffers,
+ IN OUT PULONG Length,
+ OUT PMDL *Mdl OPTIONAL,
+ OUT PVOID *Buffer
+ );
+
+VOID
+NtfsDeleteMdlAndBuffer (
+ IN PMDL Mdl OPTIONAL,
+ IN PVOID Buffer OPTIONAL
+ );
+
+VOID
+NtfsWriteClusters (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSCB Scb,
+ IN VBO StartingVbo,
+ IN PVOID Buffer,
+ IN ULONG ClusterCount
+ );
+
+
+//
+// The following support routines are contained int Ea.c
+//
+
+PFILE_FULL_EA_INFORMATION
+NtfsMapExistingEas (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PBCB *EaBcb,
+ OUT PULONG EaLength
+ );
+
+NTSTATUS
+NtfsBuildEaList (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PEA_LIST_HEADER EaListHeader,
+ IN PFILE_FULL_EA_INFORMATION UserEaList,
+ OUT PULONG ErrorOffset
+ );
+
+VOID
+NtfsReplaceFileEas (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PEA_LIST_HEADER EaList
+ );
+
+
+//
+// The following routines are used to manipulate the fscontext fields
+// of the file object, implemented in FilObSup.c
+//
+
+typedef enum _TYPE_OF_OPEN {
+
+ UnopenedFileObject = 1,
+ UserFileOpen,
+ UserDirectoryOpen,
+ UserVolumeOpen,
+ StreamFileOpen,
+#ifdef _CAIRO_
+ UserPropertySetOpen
+#endif // _CAIRO_
+
+} TYPE_OF_OPEN;
+
+VOID
+NtfsSetFileObject (
+ IN PFILE_OBJECT FileObject,
+ IN TYPE_OF_OPEN TypeOfOpen,
+ IN PSCB Scb,
+ IN PCCB Ccb OPTIONAL
+ );
+
+//
+// TYPE_OF_OPEN
+// NtfsDecodeFileObject (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFILE_OBJECT FileObject,
+// OUT PVCB *Vcb,
+// OUT PFCB *Fcb,
+// OUT PSCB *Scb,
+// OUT PCCB *Ccb,
+// IN BOOLEAN RaiseOnError
+// );
+//
+
+#ifndef _CAIRO_
+#define NtfsDecodeFileObject(IC,FO,V,F,S,C,R) ( \
+ ( *(S) = (PSCB)(FO)->FsContext), \
+ ((*(S) != NULL) \
+ ? ((*(V) = (*(S))->Vcb), \
+ (*(C) = (PCCB)(FO)->FsContext2), \
+ (*(F) = (*(S))->Fcb), \
+ ((R) \
+ && !FlagOn((*(V))->VcbState, VCB_STATE_VOLUME_MOUNTED) \
+ && ((*(C) == NULL) \
+ || ((*(C))->TypeOfOpen != UserVolumeOpen) \
+ || !FlagOn((*(V))->VcbState, VCB_STATE_LOCKED)) \
+ && NtfsRaiseStatusFunction((IC), STATUS_VOLUME_DISMOUNTED)), \
+ ((*(C) == NULL) \
+ ? StreamFileOpen \
+ : (*(C))->TypeOfOpen)) \
+ : UnopenedFileObject) \
+)
+#else // _CAIRO_
+
+#define FlagOn(F,SF) ( \
+ (((F) & (SF))) \
+)
+
+_inline TYPE_OF_OPEN
+NtfsDecodeFileObject (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ OUT PVCB *Vcb,
+ OUT PFCB *Fcb,
+ OUT PSCB *Scb,
+ OUT PCCB *Ccb,
+ IN BOOLEAN RaiseOnError
+ )
+
+/*++
+
+Routine Description:
+
+ This routine decodes a file object into a Vcb, Fcb, Scb, and Ccb.
+
+Arguments:
+
+ IrpContext - The Irp context to use for raising on an error.
+
+ FileObject - The file object to decode.
+
+ Vcb - Where to store the Vcb.
+
+ Fcb - Where to store the Fcb.
+
+ Scb - Where to store the Scb.
+
+ Ccb - Where to store the Ccb.
+
+ RaiseOnError - If FALSE, we do not raise if we encounter an error.
+ Otherwise we do raise if we encounter an error.
+
+Return Value:
+
+ Type of open
+
+--*/
+
+{
+ *Scb = (PSCB)FileObject->FsContext;
+
+ if (*Scb != NULL) {
+
+ *Vcb = (*Scb)->Vcb;
+ *Ccb = (PCCB)FileObject->FsContext2;
+ *Fcb = (*Scb)->Fcb;
+
+ //
+ // If the caller wants us to raise, let's see if there's anything
+ // we should raise.
+ //
+
+ if (RaiseOnError &&
+ !FlagOn((*Vcb)->VcbState, VCB_STATE_VOLUME_MOUNTED) &&
+ ((*Ccb == NULL) ||
+ ((*Ccb)->TypeOfOpen != UserVolumeOpen) ||
+ !FlagOn((*Vcb)->VcbState, VCB_STATE_LOCKED))) {
+
+ NtfsRaiseStatusFunction( IrpContext, STATUS_VOLUME_DISMOUNTED );
+ }
+
+ //
+ // Every open except a StreamFileOpen has a Ccb.
+ //
+
+ if (*Ccb == NULL) {
+
+ return StreamFileOpen;
+
+ } else {
+
+ return (*Ccb)->TypeOfOpen;
+ }
+
+ } else {
+
+ //
+ // No Scb, we assume the file wasn't open.
+ //
+
+ return UnopenedFileObject;
+ }
+}
+#endif // _CAIRO_
+
+//
+// PSCB
+// NtfsFastDecodeUserFileOpen (
+// IN PFILE_OBJECT FileObject
+// );
+//
+
+#define NtfsFastDecodeUserFileOpen(FO) ( \
+ (((FO)->FsContext2 != NULL) && (((PCCB)(FO)->FsContext2)->TypeOfOpen == UserFileOpen)) ? \
+ (PSCB)(FO)->FsContext : NULL \
+)
+
+VOID
+NtfsUpdateScbFromFileObject (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN PSCB Scb,
+ IN BOOLEAN CheckTimeStamps
+ );
+
+//
+// Ntfs-private FastIo routines.
+//
+
+BOOLEAN
+NtfsCopyReadA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ OUT PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsCopyWriteA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ IN PVOID Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsMdlReadA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsPrepareMdlWriteA (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsWaitForIoAtEof (
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN OUT PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PEOF_WAIT_BLOCK EofWaitBlock
+ );
+
+VOID
+NtfsFinishIoAtEof (
+ IN PFSRTL_ADVANCED_FCB_HEADER Header
+ );
+
+//
+// VOID
+// FsRtlLockFsRtlHeader (
+// IN PFSRTL_ADVANCED_FCB_HEADER FsRtlHeader
+// );
+//
+// VOID
+// FsRtlUnlockFsRtlHeader (
+// IN PFSRTL_ADVANCED_FCB_HEADER FsRtlHeader
+// );
+//
+
+#define FsRtlLockFsRtlHeader(H) { \
+ EOF_WAIT_BLOCK eb; \
+ LARGE_INTEGER ef = {FILE_WRITE_TO_END_OF_FILE, -1}; \
+ ExAcquireFastMutex( (H)->FastMutex ); \
+ if (((H)->Flags & FSRTL_FLAG_EOF_ADVANCE_ACTIVE)) { \
+ NtfsWaitForIoAtEof( (H), &ef, 0, &eb ); \
+ } \
+ (H)->Flags |= FSRTL_FLAG_EOF_ADVANCE_ACTIVE; \
+ ExReleaseFastMutex( (H)->FastMutex ); \
+}
+
+#define FsRtlUnlockFsRtlHeader(H) { \
+ ExAcquireFastMutex( (H)->FastMutex ); \
+ NtfsFinishIoAtEof( (H) ); \
+ ExReleaseFastMutex( (H)->FastMutex ); \
+}
+
+
+//
+// Indexing routine interfaces, implemented in IndexSup.c.
+//
+
+VOID
+NtfsCreateIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE IndexedAttributeType,
+ IN COLLATION_RULE CollationRule,
+ IN ULONG BytesPerIndexBuffer,
+ IN UCHAR BlocksPerIndexBuffer,
+ IN PATTRIBUTE_ENUMERATION_CONTEXT Context OPTIONAL,
+ IN USHORT AttributeFlags,
+ IN BOOLEAN NewIndex,
+ IN BOOLEAN LogIt
+ );
+
+VOID
+NtfsUpdateIndexScbFromAttribute (
+ IN PSCB Scb,
+ IN PATTRIBUTE_RECORD_HEADER IndexRootAttr
+ );
+
+BOOLEAN
+NtfsFindIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN IgnoreCase,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PINDEX_ENTRY *IndexEntry
+ );
+
+VOID
+NtfsUpdateFileNameInIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFILE_NAME FileName,
+ IN PDUPLICATED_INFORMATION Info,
+ IN OUT PQUICK_INDEX QuickIndex OPTIONAL
+ );
+
+VOID
+NtfsAddIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN ULONG ValueLength,
+ IN PFILE_REFERENCE FileReference,
+ OUT PQUICK_INDEX QuickIndex OPTIONAL
+ );
+
+VOID
+NtfsDeleteIndexEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN PFILE_REFERENCE FileReference
+ );
+
+VOID
+NtfsPushIndexRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+BOOLEAN
+NtfsRestartIndexEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN PVOID Value,
+ IN BOOLEAN IgnoreCase,
+ IN BOOLEAN NextFlag,
+ OUT PINDEX_ENTRY *IndexEntry,
+ IN PFCB AcquiredFcb OPTIONAL
+ );
+
+BOOLEAN
+NtfsContinueIndexEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN BOOLEAN NextFlag,
+ OUT PINDEX_ENTRY *IndexEntry
+ );
+
+PFILE_NAME
+NtfsRetrieveOtherFileName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb,
+ IN PSCB Scb,
+ IN PINDEX_ENTRY IndexEntry,
+ IN OUT PINDEX_CONTEXT OtherContext,
+ IN PFCB AcquiredFcb OPTIONAL,
+ OUT PBOOLEAN SynchronizationError
+ );
+
+VOID
+NtfsCleanupAfterEnumeration (
+ IN PIRP_CONTEXT IrpContext,
+ IN PCCB Ccb
+ );
+
+BOOLEAN
+NtfsIsIndexEmpty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ );
+
+VOID
+NtfsDeleteIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING AttributeName
+ );
+
+VOID
+NtfsInitializeIndexContext (
+ OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+NtfsCleanupIndexContext (
+ IN PIRP_CONTEXT IrpContext,
+ OUT PINDEX_CONTEXT IndexContext
+ );
+
+VOID
+NtfsReinitializeIndexContext (
+ IN PIRP_CONTEXT IrpContext,
+ OUT PINDEX_CONTEXT IndexContext
+ );
+
+//
+// PVOID
+// NtfsFoundIndexEntry (
+// IN PIRP_CONTEXT IrpContext,
+// IN PINDEX_ENTRY IndexEntry
+// );
+//
+
+#define NtfsFoundIndexEntry(IE) ((PVOID) \
+ ((PUCHAR) (IE) + sizeof( INDEX_ENTRY )) \
+)
+
+//
+// Restart routines for IndexSup
+//
+
+VOID
+NtfsRestartInsertSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute,
+ IN PINDEX_ENTRY BeforeIndexEntry
+ );
+
+VOID
+NtfsRestartInsertSimpleAllocation (
+ IN PINDEX_ENTRY InsertIndexEntry,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer,
+ IN PINDEX_ENTRY BeforeIndexEntry
+ );
+
+VOID
+NtfsRestartWriteEndOfIndex (
+ IN PINDEX_HEADER IndexHeader,
+ IN PINDEX_ENTRY OverwriteIndexEntry,
+ IN PINDEX_ENTRY FirstNewIndexEntry,
+ IN ULONG Length
+ );
+
+VOID
+NtfsRestartSetIndexBlock(
+ IN PINDEX_ENTRY IndexEntry,
+ IN LONGLONG IndexBlock
+ );
+
+VOID
+NtfsRestartUpdateFileName(
+ IN PINDEX_ENTRY IndexEntry,
+ IN PDUPLICATED_INFORMATION Info
+ );
+
+VOID
+NtfsRestartDeleteSimpleRoot (
+ IN PIRP_CONTEXT IrpContext,
+ IN PINDEX_ENTRY IndexEntry,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PATTRIBUTE_RECORD_HEADER Attribute
+ );
+
+VOID
+NtfsRestartDeleteSimpleAllocation (
+ IN PINDEX_ENTRY IndexEntry,
+ IN PINDEX_ALLOCATION_BUFFER IndexBuffer
+ );
+
+VOID
+NtOfsRestartUpdateDataInIndex(
+ IN PINDEX_ENTRY IndexEntry,
+ IN PVOID IndexData,
+ IN ULONG Length );
+
+
+//
+// Ntfs Logging Routine interfaces in LogSup.c
+//
+
+LSN
+NtfsWriteLog (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PBCB Bcb OPTIONAL,
+ IN NTFS_LOG_OPERATION RedoOperation,
+ IN PVOID RedoBuffer OPTIONAL,
+ IN ULONG RedoLength,
+ IN NTFS_LOG_OPERATION UndoOperation,
+ IN PVOID UndoBuffer OPTIONAL,
+ IN ULONG UndoLength,
+ IN LONGLONG StreamOffset,
+ IN ULONG RecordOffset,
+ IN ULONG AttributeOffset,
+ IN ULONG StructureSize
+ );
+
+VOID
+NtfsCheckpointVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN OwnsCheckpoint,
+ IN BOOLEAN CleanVolume,
+ IN BOOLEAN FlushVolume,
+ IN LSN LastKnownLsn
+ );
+
+VOID
+NtfsCheckpointForLogFileFull (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsCommitCurrentTransaction (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsCheckpointCurrentTransaction (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsInitializeLogging (
+ );
+
+VOID
+NtfsStartLogFile (
+ IN PSCB LogFileScb,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsStopLogFile (
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsInitializeRestartTable (
+ IN ULONG EntrySize,
+ IN ULONG NumberEntries,
+ OUT PRESTART_POINTERS TablePointer
+ );
+
+VOID
+InitializeNewTable (
+ IN ULONG EntrySize,
+ IN ULONG NumberEntries,
+ OUT PRESTART_POINTERS TablePointer
+ );
+
+VOID
+NtfsFreeRestartTable (
+ IN PRESTART_POINTERS TablePointer
+ );
+
+VOID
+NtfsExtendRestartTable (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG NumberNewEntries,
+ IN ULONG FreeGoal
+ );
+
+ULONG
+NtfsAllocateRestartTableIndex (
+ IN PRESTART_POINTERS TablePointer
+ );
+
+PVOID
+NtfsAllocateRestartTableFromIndex (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG Index
+ );
+
+VOID
+NtfsFreeRestartTableIndex (
+ IN PRESTART_POINTERS TablePointer,
+ IN ULONG Index
+ );
+
+PVOID
+NtfsGetFirstRestartTable (
+ IN PRESTART_POINTERS TablePointer
+ );
+
+PVOID
+NtfsGetNextRestartTable (
+ IN PRESTART_POINTERS TablePointer,
+ IN PVOID Current
+ );
+
+//
+// VOID
+// NtfsNormalizeAndCleanupTransaction (
+// IN PIRP_CONTEXT IrpContext,
+// IN NTSTATUS *Status,
+// IN BOOLEAN AlwaysRaise,
+// IN NTSTATUS NormalizeStatus
+// );
+//
+// VOID
+// NtfsCleanupTransaction (
+// IN PIRP_CONTEXT IrpContext,
+// IN NTSTATUS Status,
+// IN BOOLEAN AlwaysRaise
+// );
+//
+
+#define NtfsNormalizeAndCleanupTransaction(IC,PSTAT,RAISE,NORM_STAT) { \
+ if (!NT_SUCCESS( (IC)->TopLevelIrpContext->ExceptionStatus )) { \
+ NtfsRaiseStatus( (IC), (IC)->TopLevelIrpContext->ExceptionStatus, NULL, NULL ); \
+ } else if (!NT_SUCCESS( *(PSTAT) )) { \
+ *(PSTAT) = FsRtlNormalizeNtstatus( *(PSTAT), (NORM_STAT) ); \
+ if ((RAISE) || ((IC)->TopLevelIrpContext->TransactionId != 0)) { \
+ NtfsRaiseStatus( (IC), *(PSTAT), NULL, NULL ); \
+ } \
+ } \
+}
+
+#define NtfsCleanupTransaction(IC,STAT,RAISE) { \
+ if (!NT_SUCCESS( (IC)->TopLevelIrpContext->ExceptionStatus )) { \
+ NtfsRaiseStatus( (IC), (IC)->TopLevelIrpContext->ExceptionStatus, NULL, NULL ); \
+ } else if (!NT_SUCCESS( STAT ) && \
+ ((RAISE) || ((IC)->TopLevelIrpContext->TransactionId != 0))) { \
+ NtfsRaiseStatus( (IC), (STAT), NULL, NULL ); \
+ } \
+}
+
+
+//
+// NTFS MCB support routine, implemented in McbSup.c
+//
+
+//
+// An Ntfs Mcb is a superset of the regular mcb package. In
+// addition to the regular Mcb functions it will unload mapping
+// information to keep it overall memory usage down
+//
+
+VOID
+NtfsInitializeNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN PFSRTL_ADVANCED_FCB_HEADER FcbHeader,
+ IN PNTFS_MCB_INITIAL_STRUCTS McbStructs,
+ IN POOL_TYPE PoolType
+ );
+
+VOID
+NtfsUninitializeNtfsMcb (
+ IN PNTFS_MCB Mcb
+ );
+
+VOID
+NtfsRemoveNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ IN LONGLONG Count
+ );
+
+VOID
+NtfsUnloadNtfsMcbRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn,
+ IN BOOLEAN TruncateOnly,
+ IN BOOLEAN AlreadySynchronized
+ );
+
+ULONG
+NtfsNumberOfRangesInNtfsMcb (
+ IN PNTFS_MCB Mcb
+ );
+
+BOOLEAN
+NtfsNumberOfRunsInRange(
+ IN PNTFS_MCB Mcb,
+ IN PVOID RangePtr,
+ OUT PULONG NumberOfRuns
+ );
+
+BOOLEAN
+NtfsLookupLastNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ OUT PLONGLONG Vcn,
+ OUT PLONGLONG Lcn
+ );
+
+ULONG
+NtfsMcbLookupArrayIndex (
+ IN PNTFS_MCB Mcb,
+ IN VCN Vcn
+ );
+
+BOOLEAN
+NtfsSplitNtfsMcb (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ IN LONGLONG Amount
+ );
+
+BOOLEAN
+NtfsAddNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ IN LONGLONG Lcn,
+ IN LONGLONG RunCount,
+ IN BOOLEAN AlreadySynchronized
+ );
+
+BOOLEAN
+NtfsLookupNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG Vcn,
+ OUT PLONGLONG Lcn OPTIONAL,
+ OUT PLONGLONG CountFromLcn OPTIONAL,
+ OUT PLONGLONG StartingLcn OPTIONAL,
+ OUT PLONGLONG CountFromStartingLcn OPTIONAL,
+ OUT PVOID *RangePtr OPTIONAL,
+ OUT PULONG RunIndex OPTIONAL
+ );
+
+BOOLEAN
+NtfsGetNextNtfsMcbEntry (
+ IN PNTFS_MCB Mcb,
+ IN PVOID *RangePtr,
+ IN ULONG RunIndex,
+ OUT PLONGLONG Vcn,
+ OUT PLONGLONG Lcn,
+ OUT PLONGLONG Count
+ );
+
+//
+// BOOLEAN
+// NtfsGetSequentialMcbEntry (
+// IN PNTFS_MCB Mcb,
+// IN PVOID *RangePtr,
+// IN ULONG RunIndex,
+// OUT PLONGLONG Vcn,
+// OUT PLONGLONG Lcn,
+// OUT PLONGLONG Count
+// );
+//
+
+#define NtfsGetSequentialMcbEntry(MC,RGI,RNI,V,L,C) ( \
+ NtfsGetNextNtfsMcbEntry(MC,RGI,RNI,V,L,C) || \
+ (RNI = 0) || \
+ NtfsGetNextNtfsMcbEntry(MC,RGI,MAXULONG,V,L,C) || \
+ ((RNI = MAXULONG) == 0) \
+ )
+
+
+VOID
+NtfsDefineNtfsMcbRange (
+ IN PNTFS_MCB Mcb,
+ IN LONGLONG StartingVcn,
+ IN LONGLONG EndingVcn,
+ IN BOOLEAN AlreadySynchronized
+ );
+
+//
+// VOID
+// NtfsAcquireNtfsMcbMutex (
+// IN PNTFS_MCB Mcb
+// );
+//
+// VOID
+// NtfsReleaseNtfsMcbMutex (
+// IN PNTFS_MCB Mcb
+// );
+//
+
+#define NtfsAcquireNtfsMcbMutex(M) { \
+ ExAcquireFastMutex((M)->FastMutex); \
+}
+
+#define NtfsReleaseNtfsMcbMutex(M) { \
+ ExReleaseFastMutex((M)->FastMutex); \
+}
+
+
+//
+// MFT access routines, implemented in MftSup.c
+//
+
+//
+// This routine may only be used to read the Base file record segment, and
+// it checks that this is true.
+//
+
+VOID
+NtfsReadFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_REFERENCE FileReference,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *BaseFileRecord,
+ OUT PATTRIBUTE_RECORD_HEADER *FirstAttribute,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ );
+
+//
+// These routines can read/pin any record in the MFT.
+//
+
+VOID
+NtfsReadMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PMFT_SEGMENT_REFERENCE SegmentReference,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ );
+
+VOID
+NtfsPinMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PMFT_SEGMENT_REFERENCE SegmentReference,
+ IN BOOLEAN PreparingToWrite,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord,
+ OUT PLONGLONG MftFileOffset OPTIONAL
+ );
+
+//
+// The following routines are used to setup, allocate, and deallocate
+// file records in the Mft.
+//
+
+MFT_SEGMENT_REFERENCE
+NtfsAllocateMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN MftData
+ );
+
+VOID
+NtfsInitializeMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PMFT_SEGMENT_REFERENCE MftSegment,
+ IN OUT PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN PBCB Bcb,
+ IN BOOLEAN Directory
+ );
+
+VOID
+NtfsDeallocateMftRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG FileNumber
+ );
+
+BOOLEAN
+NtfsIsMftIndexInHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG Index,
+ OUT PULONG HoleLength OPTIONAL
+ );
+
+VOID
+NtfsFillMftHole (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG Index
+ );
+
+VOID
+NtfsLogMftFileRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord,
+ IN LONGLONG MftOffset,
+ IN PBCB FileRecordBcb,
+ IN BOOLEAN RedoOperation
+ );
+
+BOOLEAN
+NtfsDefragMft (
+ IN PDEFRAG_MFT DefragMft
+ );
+
+VOID
+NtfsCheckForDefrag (
+ IN OUT PVCB Vcb
+ );
+
+VOID
+NtfsInitializeMftHoleRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN ULONG FirstIndex,
+ IN ULONG RecordCount
+ );
+
+
+//
+// Name support routines, implemented in NameSup.c
+//
+
+typedef enum _PARSE_TERMINATION_REASON {
+
+ EndOfPathReached,
+ NonSimpleName,
+ IllegalCharacterInName,
+ MalFormedName,
+ AttributeOnly,
+ VersionNumberPresent
+
+} PARSE_TERMINATION_REASON;
+
+#define NtfsDissectName(Path,FirstName,RemainingName) \
+ ( FsRtlDissectName( Path, FirstName, RemainingName ) )
+
+BOOLEAN
+NtfsParseName (
+ IN UNICODE_STRING Name,
+ IN BOOLEAN WildCardsPermissible,
+ OUT PBOOLEAN FoundIllegalCharacter,
+ OUT PNTFS_NAME_DESCRIPTOR ParsedName
+ );
+
+PARSE_TERMINATION_REASON
+NtfsParsePath (
+ IN UNICODE_STRING Path,
+ IN BOOLEAN WildCardsPermissible,
+ OUT PUNICODE_STRING FirstPart,
+ OUT PNTFS_NAME_DESCRIPTOR Name,
+ OUT PUNICODE_STRING RemainingPart
+ );
+
+VOID
+NtfsPreprocessName (
+ IN UNICODE_STRING InputString,
+ OUT PUNICODE_STRING FirstPart,
+ OUT PUNICODE_STRING AttributeCode,
+ OUT PUNICODE_STRING AttributeName,
+ OUT PBOOLEAN TrailingBackslash
+ );
+
+VOID
+NtfsUpcaseName (
+ IN PWCH UpcaseTable,
+ IN ULONG UpcaseTableSize,
+ IN OUT PUNICODE_STRING InputString
+ );
+
+FSRTL_COMPARISON_RESULT
+NtfsCollateNames (
+ IN PWCH UpcaseTable,
+ IN ULONG UpcaseTableSize,
+ IN PUNICODE_STRING Expression,
+ IN PUNICODE_STRING Name,
+ IN FSRTL_COMPARISON_RESULT WildIs,
+ IN BOOLEAN IgnoreCase
+ );
+
+#define NtfsIsNameInExpression(UC,EX,NM,IC) \
+ FsRtlIsNameInExpression( (EX), (NM), (IC), (UC) )
+
+BOOLEAN
+NtfsIsFileNameValid (
+ IN PUNICODE_STRING FileName,
+ IN BOOLEAN WildCardsPermissible
+ );
+
+BOOLEAN
+NtfsIsFatNameValid (
+ IN PUNICODE_STRING FileName,
+ IN BOOLEAN WildCardsPermissible
+ );
+
+BOOLEAN
+NtfsIsDosNameInCurrentCodePage(
+ IN PUNICODE_STRING FileName
+ );
+
+//
+// Ntfs works very hard to make sure that all names are kept in upper case
+// so that most comparisons are done case SENSITIVE. Name testing for
+// case SENSITIVE can be very quick since RtlEqualMemory is an inline operation
+// on several processors.
+//
+// NtfsAreNamesEqual is used when the caller does not know for sure whether
+// or not case is important. In the case where IgnoreCase is a known value,
+// the compiler can easily optimize the relevant clause.
+//
+
+#define NtfsAreNamesEqual(UpcaseTable,Name1,Name2,IgnoreCase) \
+ ((IgnoreCase) ? FsRtlAreNamesEqual( (Name1), (Name2), (IgnoreCase), (UpcaseTable) ) \
+ : ((Name1)->Length == (Name2)->Length && \
+ RtlEqualMemory( (Name1)->Buffer, (Name2)->Buffer, (Name1)->Length )))
+
+
+//
+// Largest matching prefix searching routines, implemented in PrefxSup.c
+//
+
+VOID
+NtfsInsertPrefix (
+ IN PLCB Lcb,
+ IN BOOLEAN IgnoreCase
+ );
+
+VOID
+NtfsRemovePrefix (
+ IN PLCB Lcb
+ );
+
+PLCB
+NtfsFindPrefix (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB StartingScb,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ IN OUT UNICODE_STRING FullFileName,
+ IN BOOLEAN IgnoreCase,
+ OUT PBOOLEAN DosOnlyComponent,
+ OUT PUNICODE_STRING RemainingName
+ );
+
+BOOLEAN
+NtfsInsertNameLink (
+ IN PRTL_SPLAY_LINKS *RootNode,
+ IN PNAME_LINK NameLink
+ );
+
+//
+// VOID
+// NtfsRemoveNameLink (
+// IN PRTL_SPLAY_LINKS *RootNode,
+// IN PNAME_LINK NameLink
+// );
+//
+
+#define NtfsRemoveNameLink(RN,NL) { \
+ *(RN) = RtlDelete( &(NL)->Links ); \
+}
+
+PNAME_LINK
+NtfsFindNameLink (
+ IN PRTL_SPLAY_LINKS *RootNode,
+ IN PUNICODE_STRING Name
+ );
+
+//
+// The following macro is useful for traversing the queue of Prefixes
+// attached to a given Lcb
+//
+// PPREFIX_ENTRY
+// NtfsGetNextPrefix (
+// IN PIRP_CONTEXT IrpContext,
+// IN PLCB Lcb,
+// IN PPREFIX_ENTRY PreviousPrefixEntry
+// );
+//
+
+#define NtfsGetNextPrefix(IC,LC,PPE) ((PPREFIX_ENTRY) \
+ ((PPE) == NULL ? \
+ (IsListEmpty(&(LC)->PrefixQueue) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((LC)->PrefixQueue.Flink, PREFIX_ENTRY, LcbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PPREFIX_ENTRY)(PPE))->LcbLinks.Flink == &(LC)->PrefixQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PPREFIX_ENTRY)(PPE))->LcbLinks.Flink, PREFIX_ENTRY, LcbLinks.Flink) \
+ ) \
+ ) \
+)
+
+
+//
+// Resources support routines/macros, implemented in ResrcSup.c
+
+// These routines raise CANT_WAIT if the resource cannot be acquired
+// and wait is set to false in the Irp context.
+//
+
+VOID
+NtfsAcquireExclusiveGlobal (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsAcquireSharedGlobal (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsAcquireAllFiles (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Exclusive,
+ IN BOOLEAN AcquirePagingIo
+ );
+
+VOID
+NtfsReleaseAllFiles (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN ReleasePagingIo
+ );
+
+BOOLEAN
+NtfsAcquireExclusiveVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN RaiseOnCantWait
+ );
+
+BOOLEAN
+NtfsAcquireSharedVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN RaiseOnCantWait
+ );
+
+#define NtfsAcquireExclusivePagingIo(IC,FCB) { \
+ ASSERT((IC)->FcbWithPagingExclusive == NULL); \
+ ExAcquireResourceExclusive((FCB)->PagingIoResource, TRUE); \
+ (IC)->FcbWithPagingExclusive = (FCB); \
+}
+
+#define NtfsReleasePagingIo(IC,FCB) { \
+ ASSERT((IC)->FcbWithPagingExclusive == (FCB)); \
+ ExReleaseResource((FCB)->PagingIoResource); \
+ (IC)->FcbWithPagingExclusive = NULL; \
+}
+
+BOOLEAN
+NtfsAcquireFcbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN DontWait
+ );
+
+VOID
+NtfsReleaseFcbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+VOID
+NtfsReleaseScbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+BOOLEAN
+NtfsAcquireExclusiveFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb OPTIONAL,
+ IN BOOLEAN NoDeleteCheck,
+ IN BOOLEAN DontWait
+ );
+
+VOID
+NtfsAcquireSharedFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb OPTIONAL,
+ IN BOOLEAN NoDeleteCheck
+ );
+
+VOID
+NtfsReleaseFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+VOID
+NtfsAcquireExclusiveScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+VOID
+NtfsAcquireSharedScbForTransaction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+VOID
+NtfsReleaseSharedResources (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+//
+// VOID
+// NtfsAcquireSharedScb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb
+// );
+//
+// VOID
+// NtfsReleaseScb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb
+// );
+//
+// VOID
+// NtfsReleaseGlobal (
+// IN PIRP_CONTEXT IrpContext
+// );
+//
+// VOID
+// NtfsAcquireFcbTable (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// );
+//
+// VOID
+// NtfsReleaseFcbTable (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsLockFcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb
+// );
+//
+// VOID
+// NtfsUnlockFcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb
+// );
+//
+// VOID
+// NtfsAcquireFcbSecurity (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// );
+//
+// VOID
+// NtfsReleaseFcbSecurity (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsAcquireCheckpoint (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// );
+//
+// VOID
+// NtfsReleaseCheckpoint (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsWaitOnCheckpointNotify (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsSetCheckpointNotify (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsResetCheckpointNotify (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsAcquireReservedClusters (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID
+// NtfsReleaseReservedClusters (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+// VOID NtfsAcquireFsrtlHeader (
+// IN PSCB Scb
+// );
+//
+// VOID NtfsReleaseFsrtlHeader (
+// IN PSCB Scb
+// );
+//
+// VOID
+// NtfsReleaseVcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb
+// );
+//
+
+VOID
+NtfsReleaseVcbCheckDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN UCHAR MajorCode,
+ IN PFILE_OBJECT FileObject OPTIONAL
+ );
+
+#define NtfsAcquireSharedScb(IC,S) { \
+ NtfsAcquireSharedFcb((IC),(S)->Fcb, S, FALSE); \
+}
+
+#define NtfsReleaseScb(IC,S) { \
+ NtfsReleaseFcb((IC),(S)->Fcb); \
+}
+
+#define NtfsReleaseGlobal(IC) { \
+ ExReleaseResource( &NtfsData.Resource ); \
+}
+
+#define NtfsAcquireFcbTable(IC,V) { \
+ ExAcquireFastMutexUnsafe( &(V)->FcbTableMutex ); \
+}
+
+#define NtfsReleaseFcbTable(IC,V) { \
+ ExReleaseFastMutexUnsafe( &(V)->FcbTableMutex ); \
+}
+
+#define NtfsLockFcb(IC,F) { \
+ ExAcquireFastMutexUnsafe( (F)->FcbMutex ); \
+}
+
+#define NtfsUnlockFcb(IC,F) { \
+ ExReleaseFastMutexUnsafe( (F)->FcbMutex ); \
+}
+
+#define NtfsAcquireFcbSecurity(V) { \
+ ExAcquireFastMutexUnsafe( &(V)->FcbSecurityMutex ); \
+}
+
+#define NtfsReleaseFcbSecurity(V) { \
+ ExReleaseFastMutexUnsafe( &(V)->FcbSecurityMutex ); \
+}
+
+#define NtfsAcquireCheckpoint(IC,V) { \
+ ExAcquireFastMutexUnsafe( &(V)->CheckpointMutex ); \
+}
+
+#define NtfsReleaseCheckpoint(IC,V) { \
+ ExReleaseFastMutexUnsafe( &(V)->CheckpointMutex ); \
+}
+
+#define NtfsWaitOnCheckpointNotify(IC,V) { \
+ NTSTATUS _Status; \
+ _Status = KeWaitForSingleObject( &(V)->CheckpointNotifyEvent, \
+ Executive, \
+ KernelMode, \
+ FALSE, \
+ NULL ); \
+ if (!NT_SUCCESS( _Status )) { \
+ NtfsRaiseStatus( IrpContext, _Status, NULL, NULL ); \
+ } \
+}
+
+#define NtfsSetCheckpointNotify(IC,V) { \
+ KeSetEvent( &(V)->CheckpointNotifyEvent, 0, FALSE ); \
+}
+
+#define NtfsResetCheckpointNotify(IC,V) { \
+ KeClearEvent( &(V)->CheckpointNotifyEvent ); \
+}
+
+#define NtfsAcquireReservedClusters(V) { \
+ ExAcquireFastMutex( &(V)->FcbSecurityMutex ); \
+}
+
+#define NtfsReleaseReservedClusters(V) { \
+ ExReleaseFastMutex( &(V)->FcbSecurityMutex ); \
+}
+
+#define NtfsAcquireFsrtlHeader(S) { \
+ ExAcquireFastMutex((S)->Header.FastMutex); \
+}
+
+#define NtfsReleaseFsrtlHeader(S) { \
+ ExReleaseFastMutex((S)->Header.FastMutex); \
+}
+
+#define NtfsReleaseVcb(IC,V) { \
+ ExReleaseResource( &(V)->Resource ); \
+}
+
+//
+// Macros to test resources for exclusivity.
+//
+
+#define NtfsIsExclusiveResource(R) ( \
+ ExIsResourceAcquiredExclusive(R) \
+)
+
+#define NtfsIsExclusiveFcb(F) ( \
+ (NtfsIsExclusiveResource((F)->Resource)) \
+)
+
+#define NtfsIsExclusiveFcbPagingIo(F) ( \
+ (NtfsIsExclusiveResource((F)->PagingIoResource)) \
+)
+
+#define NtfsIsExclusiveScb(S) ( \
+ (NtfsIsExclusiveFcb((S)->Fcb)) \
+)
+
+#define NtfsIsExclusiveVcb(V) ( \
+ (NtfsIsExclusiveResource(&(V)->Resource)) \
+)
+
+//
+// The following are cache manager call backs. They return FALSE
+// if the resource cannot be acquired with waiting and wait is false.
+//
+
+BOOLEAN
+NtfsAcquireVolumeForClose (
+ IN PVOID Vcb,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsReleaseVolumeFromClose (
+ IN PVOID Vcb
+ );
+
+BOOLEAN
+NtfsAcquireScbForLazyWrite (
+ IN PVOID Null,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsReleaseScbFromLazyWrite (
+ IN PVOID Null
+ );
+
+NTSTATUS
+NtfsAcquireFileForModWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER EndingOffset,
+ OUT PERESOURCE *ResourceToRelease,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+NTSTATUS
+NtfsAcquireFileForCcFlush (
+ IN PFILE_OBJECT FileObject,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+NTSTATUS
+NtfsReleaseFileForCcFlush (
+ IN PFILE_OBJECT FileObject,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+VOID
+NtfsAcquireForCreateSection (
+ IN PFILE_OBJECT FileObject
+ );
+
+VOID
+NtfsReleaseForCreateSection (
+ IN PFILE_OBJECT FileObject
+ );
+
+
+BOOLEAN
+NtfsAcquireScbForReadAhead (
+ IN PVOID Null,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsReleaseScbFromReadAhead (
+ IN PVOID Null
+ );
+
+BOOLEAN
+NtfsAcquireVolumeFileForClose (
+ IN PVOID Vcb,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsReleaseVolumeFileFromClose (
+ IN PVOID Vcb
+ );
+
+BOOLEAN
+NtfsAcquireVolumeFileForLazyWrite (
+ IN PVOID Vcb,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsReleaseVolumeFileFromLazyWrite (
+ IN PVOID Vcb
+ );
+
+
+//
+// Ntfs Logging Routine interfaces in RestrSup.c
+//
+
+BOOLEAN
+NtfsRestartVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsAbortTransaction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PTRANSACTION_ENTRY Transaction OPTIONAL
+ );
+
+NTSTATUS
+NtfsCloseAttributesFromRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+
+//
+// Security support routines, implemented in SecurSup.c
+//
+
+//
+// VOID
+// NtfsTraverseCheck (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB ParentFcb,
+// IN PIRP Irp
+// );
+//
+// VOID
+// NtfsOpenCheck (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PFCB ParentFcb OPTIONAL,
+// IN PIRP Irp
+// );
+//
+// VOID
+// NtfsCreateCheck (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB ParentFcb,
+// IN PIRP Irp
+// );
+//
+
+#define NtfsTraverseCheck(IC,F,IR) { \
+ NtfsAccessCheck( IC, \
+ F, \
+ NULL, \
+ IR, \
+ FILE_TRAVERSE, \
+ TRUE ); \
+}
+
+#define NtfsOpenCheck(IC,F,PF,IR) { \
+ NtfsAccessCheck( IC, \
+ F, \
+ PF, \
+ IR, \
+ IoGetCurrentIrpStackLocation(IR)->Parameters.Create.SecurityContext->DesiredAccess, \
+ FALSE ); \
+}
+
+#define NtfsCreateCheck(IC,PF,IR) { \
+ NtfsAccessCheck( IC, \
+ PF, \
+ NULL, \
+ IR, \
+ (FlagOn(IoGetCurrentIrpStackLocation(IR)->Parameters.Create.Options, FILE_DIRECTORY_FILE) ? \
+ FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE), \
+ TRUE ); \
+}
+
+VOID
+NtfsAssignSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ParentFcb,
+ IN PIRP Irp,
+ IN PFCB NewFcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord, // BUGBUG delete
+ IN PBCB FileRecordBcb, // BUGBUG delete
+ IN LONGLONG FileOffset, // BUGBUG delete
+ IN OUT PBOOLEAN LogIt // BUGBUG delete
+ );
+
+NTSTATUS
+NtfsModifySecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_INFORMATION SecurityInformation,
+ OUT PSECURITY_DESCRIPTOR SecurityDescriptor
+ );
+
+NTSTATUS
+NtfsQuerySecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_INFORMATION SecurityInformation,
+ OUT PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN OUT PULONG SecurityDescriptorLength
+ );
+
+VOID
+NtfsAccessCheck (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+ IN PIRP Irp,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN CheckOnly
+ );
+
+NTSTATUS
+NtfsCheckFileForDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN BOOLEAN FcbExisted,
+ IN PINDEX_ENTRY IndexEntry
+ );
+
+VOID
+NtfsCheckIndexForAddOrDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ParentFcb,
+ IN ACCESS_MASK DesiredAccess
+ );
+
+VOID
+NtfsUpdateFcbSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+#ifdef _CAIRO_
+ IN SECURITY_ID SecurityId,
+#endif
+ IN PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN ULONG SecurityDescriptorLength
+ );
+
+VOID
+NtfsDereferenceSharedSecurity (
+ IN OUT PFCB Fcb
+ );
+
+BOOLEAN
+NtfsNotifyTraverseCheck (
+ IN PCCB Ccb,
+ IN PFCB Fcb,
+ IN PSECURITY_SUBJECT_CONTEXT SubjectContext
+ );
+
+#ifdef _CAIRO_
+VOID
+NtfsInitializeSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB Fcb
+ );
+
+VOID
+NtfsLoadSecurityDescriptorById (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL
+ );
+
+VOID
+NtOfsPurgeSecurityCache (
+ IN PVCB Vcb
+ );
+
+FSRTL_COMPARISON_RESULT
+NtOfsCollateSecurityHash (
+ IN PINDEX_KEY Key1,
+ IN PINDEX_KEY Key2,
+ IN PVOID CollationData
+ );
+#endif
+
+
+//
+// In-memory structure support routine, implemented in StrucSup.c
+//
+
+//
+// Routines to create and destory the Vcb
+//
+
+VOID
+NtfsInitializeVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb,
+ IN PDEVICE_OBJECT TargetDeviceObject,
+ IN PVPB Vpb
+ );
+
+VOID
+NtfsDeleteVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB *Vcb
+ );
+
+//
+// Routines to create and destory the Fcb
+//
+
+PFCB
+NtfsCreateRootFcb ( // also creates the root lcb
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+PFCB
+NtfsCreateFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN FILE_REFERENCE FileReference,
+ IN BOOLEAN IsPagingFile,
+ IN BOOLEAN LargeFcb,
+ OUT PBOOLEAN ReturnedExistingFcb OPTIONAL
+ );
+
+VOID
+NtfsDeleteFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB *Fcb,
+ OUT PBOOLEAN AcquiredFcbTable
+ );
+
+PFCB
+NtfsGetNextFcbTableEntry (
+ IN PVCB Vcb,
+ IN PVOID *RestartKey
+ );
+
+//
+// Routines to create and destroy the Scb
+//
+
+PSCB
+NtfsCreateScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName,
+ IN BOOLEAN ReturnExistingOnly,
+ OUT PBOOLEAN ReturnedExistingScb OPTIONAL
+ );
+
+PSCB
+NtfsCreatePrerestartScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_REFERENCE FileReference,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN ULONG BytesPerIndexBuffer
+ );
+
+VOID
+NtfsDeleteScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB *Scb
+ );
+
+VOID
+NtfsUpdateNormalizedName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PSCB Scb,
+ IN PFILE_NAME FileName OPTIONAL,
+ IN BOOLEAN CheckBufferSizeOnly
+ );
+
+VOID
+NtfsBuildNormalizedName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ OUT PUNICODE_STRING FileName
+ );
+
+VOID
+NtfsSnapshotScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+VOID
+NtfsUpdateScbSnapshots (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+VOID
+NtfsRestoreScbSnapshots (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN Higher
+ );
+
+VOID
+NtfsFreeSnapshotsForFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+BOOLEAN
+NtfsCreateFileLock (
+ IN PSCB Scb,
+ IN BOOLEAN RaiseOnError
+ );
+
+//
+//
+// A general purpose teardown routine that helps cleanup the
+// the Fcb/Scb structures
+//
+
+VOID
+NtfsTeardownStructures (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID FcbOrScb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN CheckForAttributeTable,
+ IN BOOLEAN DontWaitForAcquire,
+ OUT PBOOLEAN RemovedFcb OPTIONAL
+ );
+
+//
+// Routines to create, destory and walk through the Lcbs
+//
+
+PLCB
+NtfsCreateLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN UNICODE_STRING LastComponentFileName,
+ IN UCHAR FileNameFlags,
+ OUT PBOOLEAN ReturnedExistingLcb OPTIONAL
+ );
+
+VOID
+NtfsDeleteLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PLCB *Lcb
+ );
+
+VOID
+NtfsMoveLcb ( // also munges the ccb and fileobjects filenames
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB Lcb,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING TargetDirectoryName,
+ IN PUNICODE_STRING LastComponentName,
+ IN UCHAR FileNameFlags,
+ IN BOOLEAN CheckBufferSizeOnly
+ );
+
+VOID
+NtfsRenameLcb ( // also munges the ccb and fileobjects filenames
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB Lcb,
+ IN PUNICODE_STRING LastComponentFileName,
+ IN UCHAR FileNameFlags,
+ IN BOOLEAN CheckBufferSizeOnly
+ );
+
+VOID
+NtfsCombineLcbs (
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB PrimaryLcb,
+ IN PLCB AuxLcb
+ );
+
+PLCB
+NtfsLookupLcbByFlags (
+ IN PFCB Fcb,
+ IN UCHAR FileNameFlags
+ );
+
+ULONG
+NtfsLookupNameLengthViaLcb (
+ IN PFCB Fcb,
+ OUT PBOOLEAN LeadingBackslash
+ );
+
+VOID
+NtfsFileNameViaLcb (
+ IN PFCB Fcb,
+ IN PWCHAR FileName,
+ ULONG Length,
+ ULONG BytesToCopy
+ );
+
+//
+// VOID
+// NtfsLinkCcbToLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PCCB Ccb,
+// IN PLCB Lcb
+// );
+//
+
+#define NtfsLinkCcbToLcb(IC,C,L) { \
+ InsertTailList( &(L)->CcbQueue, &(C)->LcbLinks ); \
+ (C)->Lcb = (L); \
+}
+
+//
+// VOID
+// NtfsUnlinkCcbFromLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PCCB Ccb
+// );
+//
+
+#define NtfsUnlinkCcbFromLcb(IC,C) { \
+ if ((C)->Lcb != NULL) { \
+ RemoveEntryList( &(C)->LcbLinks ); \
+ (C)->Lcb = NULL; \
+ } \
+}
+
+//
+// Routines to create and destory the Ccb
+//
+
+PCCB
+NtfsCreateCcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN Indexed,
+ IN USHORT EaModificationCount,
+ IN ULONG Flags,
+ IN UNICODE_STRING FileName,
+ IN ULONG LastFileNameOffset
+ );
+
+VOID
+NtfsDeleteCcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PCCB *Ccb
+ );
+
+//
+// Routines to create and destory the IrpContext
+//
+
+PIRP_CONTEXT
+NtfsCreateIrpContext (
+ IN PIRP Irp OPTIONAL,
+ IN BOOLEAN Wait
+ );
+
+VOID
+NtfsDeleteIrpContext (
+ IN OUT PIRP_CONTEXT *IrpContext
+ );
+
+//
+// Routine for scanning the Fcbs within the graph hierarchy
+//
+
+PSCB
+NtfsGetNextScb (
+ IN PSCB Scb,
+ IN PSCB TerminationScb
+ );
+
+//
+// The following macros are useful for traversing the queues interconnecting
+// fcbs, scb, and lcbs.
+//
+// PSCB
+// NtfsGetNextChildScb (
+// IN PFCB Fcb,
+// IN PSCB PreviousChildScb
+// );
+//
+// PLCB
+// NtfsGetNextParentLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PLCB PreviousParentLcb
+// );
+//
+// PLCB
+// NtfsGetNextChildLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb,
+// IN PLCB PreviousChildLcb
+// );
+//
+// PLCB
+// NtfsGetPrevChildLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PSCB Scb,
+// IN PLCB PreviousChildLcb
+// );
+//
+// PLCB
+// NtfsGetNextParentLcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PFCB Fcb,
+// IN PLCB PreviousChildLcb
+// );
+//
+// PCCB
+// NtfsGetNextCcb (
+// IN PIRP_CONTEXT IrpContext,
+// IN PLCB Lcb,
+// IN PCCB PreviousCcb
+// );
+//
+
+#define NtfsGetNextChildScb(F,P) ((PSCB) \
+ ((P) == NULL ? \
+ (IsListEmpty(&(F)->ScbQueue) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((F)->ScbQueue.Flink, SCB, FcbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PSCB)(P))->FcbLinks.Flink == &(F)->ScbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PSCB)(P))->FcbLinks.Flink, SCB, FcbLinks.Flink) \
+ ) \
+ ) \
+)
+
+#define NtfsGetNextParentLcb(F,P) ((PLCB) \
+ ((P) == NULL ? \
+ (IsListEmpty(&(F)->LcbQueue) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((F)->LcbQueue.Flink, LCB, FcbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PLCB)(P))->FcbLinks.Flink == &(F)->LcbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PLCB)(P))->FcbLinks.Flink, LCB, FcbLinks.Flink) \
+ ) \
+ ) \
+)
+
+#define NtfsGetNextChildLcb(S,P) ((PLCB) \
+ ((P) == NULL ? \
+ ((((NodeType(S) == NTFS_NTC_SCB_DATA) || (NodeType(S) == NTFS_NTC_SCB_MFT)) \
+ || IsListEmpty(&(S)->ScbType.Index.LcbQueue)) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((S)->ScbType.Index.LcbQueue.Flink, LCB, ScbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PLCB)(P))->ScbLinks.Flink == &(S)->ScbType.Index.LcbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PLCB)(P))->ScbLinks.Flink, LCB, ScbLinks.Flink) \
+ ) \
+ ) \
+)
+
+#define NtfsGetPrevChildLcb(S,P) ((PLCB) \
+ ((P) == NULL ? \
+ ((((NodeType(S) == NTFS_NTC_SCB_DATA) || (NodeType(S) == NTFS_NTC_SCB_MFT)) \
+ || IsListEmpty(&(S)->ScbType.Index.LcbQueue)) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((S)->ScbType.Index.LcbQueue.Blink, LCB, ScbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PLCB)(P))->ScbLinks.Blink == &(S)->ScbType.Index.LcbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PLCB)(P))->ScbLinks.Blink, LCB, ScbLinks.Flink) \
+ ) \
+ ) \
+)
+
+#define NtfsGetNextParentLcb(F,P) ((PLCB) \
+ ((P) == NULL ? \
+ (IsListEmpty(&(F)->LcbQueue) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((F)->LcbQueue.Flink, LCB, FcbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PLCB)(P))->FcbLinks.Flink == &(F)->LcbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PLCB)(P))->FcbLinks.Flink, LCB, FcbLinks.Flink) \
+ ) \
+ ) \
+)
+
+#define NtfsGetNextCcb(L,P) ((PCCB) \
+ ((P) == NULL ? \
+ (IsListEmpty(&(L)->CcbQueue) ? \
+ NULL \
+ : \
+ CONTAINING_RECORD((L)->CcbQueue.Flink, CCB, LcbLinks.Flink) \
+ ) \
+ : \
+ ((PVOID)((PCCB)(P))->LcbLinks.Flink == &(L)->CcbQueue.Flink ? \
+ NULL \
+ : \
+ CONTAINING_RECORD(((PCCB)(P))->LcbLinks.Flink, CCB, LcbLinks.Flink) \
+ ) \
+ ) \
+)
+
+//
+// VOID
+// NtfsDeleteFcbTableEntry (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN FILE_REFERENCE FileReference
+// );
+//
+
+#define NtfsDeleteFcbTableEntry(V,FR) { \
+ FCB_TABLE_ELEMENT _Key; \
+ _Key.FileReference = FR; \
+ (VOID) RtlDeleteElementGenericTable( &(V)->FcbTable, &_Key ); \
+}
+
+//
+// The following four routines are for incrementing and decrementing the cleanup
+// counts and the close counts. In all of the structures
+//
+
+VOID
+NtfsIncrementCleanupCounts (
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN NonCachedHandle
+ );
+
+VOID
+NtfsIncrementCloseCounts (
+ IN PSCB Scb,
+ IN BOOLEAN SystemFile,
+ IN BOOLEAN ReadOnly
+ );
+
+VOID
+NtfsDecrementCleanupCounts (
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN NonCachedHandle
+ );
+
+VOID
+NtfsDecrementCloseCounts (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN SystemFile,
+ IN BOOLEAN ReadOnly,
+ IN BOOLEAN DecrementCountsOnly
+ );
+
+PERESOURCE
+NtfsAllocateEresource (
+ );
+
+VOID
+NtfsFreeEresource (
+ IN PERESOURCE Eresource
+ );
+
+PVOID
+NtfsAllocateFcbTableEntry (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN CLONG ByteSize
+ );
+
+VOID
+NtfsFreeFcbTableEntry (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN PVOID Buffer
+ );
+
+
+//
+// Time conversion support routines, implemented as a macro
+//
+// VOID
+// NtfsGetCurrentTime (
+// IN PIRP_CONTEXT IrpContext,
+// IN LONGLONG Time
+// );
+//
+
+#define NtfsGetCurrentTime(_IC,_T) { \
+ ASSERT_IRP_CONTEXT(_IC); \
+ KeQuerySystemTime((PLARGE_INTEGER)&(_T)); \
+}
+
+//
+// Time routine to check if last access should be updated.
+//
+// VOID
+// NtfsCheckLastAccess (
+// IN PIRP_CONTEXT IrpContext,
+// IN OUT PFCB Fcb
+// );
+//
+
+#define NtfsCheckLastAccess(_IC,_FCB) { \
+ LONGLONG _LastAccessTimeWithInc; \
+ _LastAccessTimeWithInc = NtfsLastAccess + (_FCB)->Info.LastAccessTime; \
+ if (( _LastAccessTimeWithInc < (_FCB)->CurrentLastAccess )) { \
+ (_FCB)->Info.LastAccessTime = (_FCB)->CurrentLastAccess; \
+ SetFlag( (_FCB)->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS ); \
+ SetFlag( (_FCB)->FcbState, FCB_STATE_UPDATE_STD_INFO ); \
+ } \
+}
+
+
+//
+// Low level verification routines, implemented in VerfySup.c
+//
+
+BOOLEAN
+NtfsPerformVerifyOperation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsPerformDismountOnVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN DoCompleteDismount
+ );
+
+BOOLEAN
+NtfsPingVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsVolumeCheckpointDpc (
+ IN PKDPC Dpc,
+ IN PVOID DeferredContext,
+ IN PVOID SystemArgument1,
+ IN PVOID SystemArgument2
+ );
+
+VOID
+NtfsCheckpointAllVolumes (
+ PVOID Parameter
+ );
+
+VOID
+NtfsVerifyOperationIsLegal (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsIoCallSelf (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN UCHAR MajorFunction
+ );
+
+BOOLEAN
+NtfsLogEvent (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_USER_DATA UserData OPTIONAL,
+ IN NTSTATUS LogCode,
+ IN NTSTATUS FinalStatus
+ );
+
+VOID
+NtfsMarkVolumeDirty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsUpdateVersionNumber (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN UCHAR MajorVersion,
+ IN UCHAR MinorVersion
+ );
+
+VOID
+NtfsPostVcbIsCorrupt (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status OPTIONAL,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ );
+
+
+//
+// Work queue routines for posting and retrieving an Irp, implemented in
+// workque.c
+//
+
+VOID
+NtfsOplockComplete (
+ IN PVOID Context,
+ IN PIRP Irp
+ );
+
+VOID
+NtfsPrePostIrp (
+ IN PVOID Context,
+ IN PIRP Irp OPTIONAL
+ );
+
+VOID
+NtfsAddToWorkque (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL
+ );
+
+NTSTATUS
+NtfsPostRequest (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL
+ );
+
+
+//
+// Miscellaneous support macros.
+//
+// ULONG
+// FlagOn (
+// IN ULONG Flags,
+// IN ULONG SingleFlag
+// );
+//
+// BOOLEAN
+// BooleanFlagOn (
+// IN ULONG Flags,
+// IN ULONG SingleFlag
+// );
+//
+// VOID
+// SetFlag (
+// IN ULONG Flags,
+// IN ULONG SingleFlag
+// );
+//
+// VOID
+// ClearFlag (
+// IN ULONG Flags,
+// IN ULONG SingleFlag
+// );
+//
+// ULONG
+// WordAlign (
+// IN ULONG Pointer
+// );
+//
+// ULONG
+// LongAlign (
+// IN ULONG Pointer
+// );
+//
+// ULONG
+// QuadAlign (
+// IN ULONG Pointer
+// );
+//
+// UCHAR
+// CopyUchar1 (
+// IN PUCHAR Destination,
+// IN PUCHAR Source
+// );
+//
+// UCHAR
+// CopyUchar2 (
+// IN PUSHORT Destination,
+// IN PUCHAR Source
+// );
+//
+// UCHAR
+// CopyUchar4 (
+// IN PULONG Destination,
+// IN PUCHAR Source
+// );
+//
+// PVOID
+// Add2Ptr (
+// IN PVOID Pointer,
+// IN ULONG Increment
+// );
+//
+// ULONG
+// PtrOffset (
+// IN PVOID BasePtr,
+// IN PVOID OffsetPtr
+// );
+//
+
+#define FlagOn(F,SF) ( \
+ (((F) & (SF))) \
+)
+
+#define BooleanFlagOn(F,SF) ( \
+ (BOOLEAN)(((F) & (SF)) != 0) \
+)
+
+#define SetFlag(F,SF) { \
+ (F) |= (SF); \
+}
+
+#define ClearFlag(F,SF) { \
+ (F) &= ~(SF); \
+}
+
+#define WordAlign(P) ( \
+ ((((ULONG)(P)) + 1) & 0xfffffffe) \
+)
+
+#define LongAlign(P) ( \
+ ((((ULONG)(P)) + 3) & 0xfffffffc) \
+)
+
+#define QuadAlign(P) ( \
+ ((((ULONG)(P)) + 7) & 0xfffffff8) \
+)
+
+//
+// Conversions between bytes and clusters. Typically we will round up to the
+// next cluster unless the macro specifies trucate.
+//
+
+#define ClusterAlign(V,P) ( \
+ ((((ULONG)(P)) + (V)->ClusterMask) & (V)->InverseClusterMask) \
+)
+
+#define ClusterOffset(V,P) ( \
+ (((ULONG)(P)) & (V)->ClusterMask) \
+)
+
+#define ClustersFromBytes(V,P) ( \
+ (((ULONG)(P)) + (V)->ClusterMask) >> (V)->ClusterShift \
+)
+
+#define BytesFromClusters(V,P) ( \
+ ((ULONG)(P)) << (V)->ClusterShift \
+)
+
+#define LlClustersFromBytes(V,L) ( \
+ Int64ShraMod32(((L) + (LONGLONG) (V)->ClusterMask), (CCHAR)(V)->ClusterShift) \
+)
+
+#define LlClustersFromBytesTruncate(V,L) ( \
+ Int64ShraMod32((L), (CCHAR)(V)->ClusterShift) \
+)
+
+#define LlBytesFromClusters(V,C) ( \
+ Int64ShllMod32((C), (CCHAR)(V)->ClusterShift) \
+)
+
+//
+// Conversions between bytes and file records
+//
+
+#define BytesFromFileRecords(V,B) ( \
+ ((ULONG)(B)) << (V)->MftShift \
+)
+
+#define FileRecordsFromBytes(V,F) ( \
+ ((ULONG)(F)) >> (V)->MftShift \
+)
+
+#define LlBytesFromFileRecords(V,F) ( \
+ Int64ShllMod32((F), (CCHAR)(V)->MftShift) \
+)
+
+#define LlFileRecordsFromBytes(V,B) ( \
+ Int64ShraMod32((B), (CCHAR)(V)->MftShift) \
+)
+
+//
+// Conversions between bytes and index blocks
+//
+
+#define BytesFromIndexBlocks(B,S) ( \
+ ((ULONG)(B)) << (S) \
+)
+
+#define LlBytesFromIndexBlocks(B,S) ( \
+ Int64ShllMod32((B), (S)) \
+)
+
+//
+// Conversions between bytes and log blocks (512 byte blocks)
+//
+
+#define BytesFromLogBlocks(B) ( \
+ ((ULONG) (B)) << DEFAULT_INDEX_BLOCK_BYTE_SHIFT \
+)
+
+#define LogBlocksFromBytesTruncate(B) ( \
+ ((ULONG) (B)) >> DEFAULT_INDEX_BLOCK_BYTE_SHIFT \
+)
+
+#define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I)))
+
+#define PtrOffset(B,O) ((ULONG)((ULONG)(O) - (ULONG)(B)))
+
+//
+// The following support macros deal with dir notify support.
+//
+// ULONG
+// NtfsBuildDirNotifyFilter (
+// IN PIRP_CONTEXT IrpContext,
+// IN ULONG Flags
+// );
+//
+// VOID
+// NtfsReportDirNotify (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN PUNICODE_STRING FullFileName,
+// IN USHORT TargetNameOffset,
+// IN PUNICODE_STRING StreamName OPTIONAL,
+// IN PUNICODE_STRING NormalizedParentName OPTIONAL,
+// IN ULONG Filter,
+// IN ULONG Action,
+// IN PFCB ParentFcb OPTIONAL
+// );
+//
+// VOID
+// NtfsUnsafeReportDirNotify (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN PUNICODE_STRING FullFileName,
+// IN USHORT TargetNameOffset,
+// IN PUNICODE_STRING StreamName OPTIONAL,
+// IN PUNICODE_STRING NormalizedParentName OPTIONAL,
+// IN ULONG Filter,
+// IN ULONG Action,
+// IN PFCB ParentFcb OPTIONAL
+// );
+//
+
+#define NtfsBuildDirNotifyFilter(IC,F) ( \
+ FlagOn( (F), FCB_INFO_CHANGED_ALLOC_SIZE ) ? \
+ (FlagOn( (F), FCB_INFO_VALID_NOTIFY_FLAGS ) | FILE_NOTIFY_CHANGE_SIZE) : \
+ FlagOn( (F), FCB_INFO_VALID_NOTIFY_FLAGS ) \
+)
+
+#define NtfsReportDirNotify(IC,V,FN,O,SN,NPN,F,A,PF) { \
+ try { \
+ FsRtlNotifyFullReportChange( (V)->NotifySync, \
+ &(V)->DirNotifyList, \
+ (PSTRING) (FN), \
+ (USHORT) (O), \
+ (PSTRING) (SN), \
+ (PSTRING) (NPN), \
+ F, \
+ A, \
+ PF ); \
+ } except (FsRtlIsNtstatusExpected( GetExceptionCode() ) ? \
+ EXCEPTION_EXECUTE_HANDLER : \
+ EXCEPTION_CONTINUE_SEARCH) { \
+ NOTHING; \
+ } \
+}
+
+#define NtfsUnsafeReportDirNotify(IC,V,FN,O,SN,NPN,F,A,PF) { \
+ FsRtlNotifyFullReportChange( (V)->NotifySync, \
+ &(V)->DirNotifyList, \
+ (PSTRING) (FN), \
+ (USHORT) (O), \
+ (PSTRING) (SN), \
+ (PSTRING) (NPN), \
+ F, \
+ A, \
+ PF ); \
+}
+
+
+//
+// The following types and macros are used to help unpack the packed and
+// misaligned fields found in the Bios parameter block
+//
+
+typedef union _UCHAR1 {
+ UCHAR Uchar[1];
+ UCHAR ForceAlignment;
+} UCHAR1, *PUCHAR1;
+
+typedef union _UCHAR2 {
+ UCHAR Uchar[2];
+ USHORT ForceAlignment;
+} UCHAR2, *PUCHAR2;
+
+typedef union _UCHAR4 {
+ UCHAR Uchar[4];
+ ULONG ForceAlignment;
+} UCHAR4, *PUCHAR4;
+
+#define CopyUchar1(D,S) { \
+ *((UCHAR1 *)(D)) = *((UNALIGNED UCHAR1 *)(S)); \
+}
+
+#define CopyUchar2(D,S) { \
+ *((UCHAR2 *)(D)) = *((UNALIGNED UCHAR2 *)(S)); \
+}
+
+#define CopyUchar4(D,S) { \
+ *((UCHAR4 *)(D)) = *((UNALIGNED UCHAR4 *)(S)); \
+}
+
+//
+// The following routines are used to set up and restore the top level
+// irp field in the local thread. They are contained in ntfsdata.c
+//
+
+
+PTOP_LEVEL_CONTEXT
+NtfsSetTopLevelIrp (
+ IN PTOP_LEVEL_CONTEXT TopLevelContext,
+ IN BOOLEAN ForceTopLevel,
+ IN BOOLEAN SetTopLevel
+ );
+
+//
+// BOOLEAN
+// NtfsIsTopLevelRequest (
+// IN PIRP_CONTEXT IrpContext
+// );
+//
+// BOOLEAN
+// NtfsIsTopLevelNtfs (
+// IN PIRP_CONTEXT IrpContext
+// );
+//
+// VOID
+// NtfsRestoreTopLevelIrp (
+// IN PTOP_LEVEL_CONTEXT TopLevelContext
+// );
+//
+// PTOP_LEVEL_CONTEXT
+// NtfsGetTopLevelContext (
+// );
+//
+// PSCB
+// NtfsGetTopLevelHotFixScb (
+// );
+//
+// VCN
+// NtfsGetTopLevelHotFixVcn (
+// );
+//
+// BOOLEAN
+// NtfsIsTopLevelHotFixScb (
+// IN PSCB Scb
+// );
+//
+// VOID
+// NtfsUpdateIrpContextWithTopLevel (
+// IN PIRP_CONTEXT IrpContext,
+// IN PTOP_LEVEL_CONTEXT TopLevelContext
+// );
+//
+
+#define NtfsRestoreTopLevelIrp(TLC) { \
+ (TLC)->Ntfs = 0; \
+ IoSetTopLevelIrp( (PIRP) (TLC)->SavedTopLevelIrp ); \
+}
+
+#define NtfsGetTopLevelContext() ( \
+ (PTOP_LEVEL_CONTEXT) IoGetTopLevelIrp() \
+)
+
+#define NtfsIsTopLevelRequest(IC) ( \
+ ((BOOLEAN) ((NtfsGetTopLevelContext())->TopLevelRequest) && \
+ (((IC) == (IC)->TopLevelIrpContext))) \
+)
+
+#define NtfsIsTopLevelNtfs(IC) ( \
+ ((BOOLEAN) (((IC) == (IC)->TopLevelIrpContext))) \
+)
+
+#define NtfsGetTopLevelHotFixScb() ( \
+ (NtfsGetTopLevelContext())->ScbBeingHotFixed \
+)
+
+#define NtfsGetTopLevelHotFixVcn() ( \
+ (NtfsGetTopLevelContext())->VboBeingHotFixed \
+)
+
+#define NtfsIsTopLevelHotFixScb(S) ( \
+ ((BOOLEAN) (NtfsGetTopLevelHotFixScb() == (S))) \
+)
+
+#define NtfsUpdateIrpContextWithTopLevel(IC,TLC) { \
+ if ((TLC)->TopLevelIrpContext == NULL) { \
+ (TLC)->TopLevelIrpContext = (IC); \
+ } \
+ (IC)->TopLevelIrpContext = (TLC)->TopLevelIrpContext; \
+}
+
+#ifdef NTFS_CHECK_BITMAP
+VOID
+NtfsBadBitmapCopy (
+ IN PIRP_CONTEXT IrpContext,
+ IN ULONG BadBit,
+ IN ULONG Length
+ );
+
+BOOLEAN
+NtfsCheckBitmap (
+ IN PVCB Vcb,
+ IN ULONG Lcn,
+ IN ULONG Count,
+ IN BOOLEAN Set
+ );
+#endif
+
+
+//
+// The FSD Level dispatch routines. These routines are called by the
+// I/O system via the dispatch table in the Driver Object.
+//
+// They each accept as input a pointer to a device object (actually most
+// expect a volume device object, with the exception of the file system
+// control function which can also take a file system device object), and
+// a pointer to the IRP. They either perform the function at the FSD level
+// or post the request to the FSP work queue for FSP level processing.
+//
+
+NTSTATUS
+NtfsFsdCleanup ( // implemented in Cleanup.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdClose ( // implemented in Close.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdCreate ( // implemented in Create.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdDeviceControl ( // implemented in DevCtrl.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsDeviceIoControl ( // implemented in FsCtrl.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN ULONG IoCtl,
+ IN PVOID InputBuffer OPTIONAL,
+ IN ULONG InputBufferLength,
+ IN PVOID OutputBuffer OPTIONAL,
+ IN ULONG OutputBufferLength
+ );
+
+NTSTATUS
+NtfsFsdDirectoryControl ( // implemented in DirCtrl.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdQueryEa ( // implemented in Ea.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdSetEa ( // implemented in Ea.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdQueryInformation ( // implemented in FileInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdSetInformation ( // implemented in FileInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdFlushBuffers ( // implemented in Flush.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFlushUserStream ( // implemented in Flush.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLONGLONG FileOffset OPTIONAL,
+ IN ULONG Length
+ );
+
+NTSTATUS
+NtfsFlushVolume ( // implemented in Flush.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN FlushCache,
+ IN BOOLEAN PurgeFromCache,
+ IN BOOLEAN ReleaseAllFiles,
+ IN BOOLEAN MarkFilesForDismount
+ );
+
+NTSTATUS
+NtfsFlushLsnStreams ( // implemented in Flush.c
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsFlushAndPurgeFcb ( // implemented in Flush.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+VOID
+NtfsFlushAndPurgeScb ( // implemented in Flush.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PSCB ParentScb OPTIONAL
+ );
+
+NTSTATUS
+NtfsFsdFileSystemControl ( // implemented in FsCtrl.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdLockControl ( // implemented in LockCtrl.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdRead ( // implemented in Read.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdQuerySecurityInfo ( // implemented in SeInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdSetSecurityInfo ( // implemented in SeInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdShutdown ( // implemented in Shutdown.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdQueryVolumeInformation ( // implemented in VolInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdSetVolumeInformation ( // implemented in VolInfo.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsFsdWrite ( // implemented in Write.c
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ );
+
+//
+// The following macro is used to determine if an FSD thread can block
+// for I/O or wait for a resource. It returns TRUE if the thread can
+// block and FALSE otherwise. This attribute can then be used to call
+// the FSD & FSP common work routine with the proper wait value.
+//
+//
+// BOOLEAN
+// CanFsdWait (
+// IN PIRP Irp
+// );
+//
+
+#define CanFsdWait(I) IoIsOperationSynchronous(I)
+
+
+//
+// The FSP level dispatch/main routine. This is the routine that takes
+// IRP's off of the work queue and calls the appropriate FSP level
+// work routine.
+//
+
+VOID
+NtfsFspDispatch ( // implemented in FspDisp.c
+ IN PVOID Context
+ );
+
+//
+// The following routines are the FSP work routines that are called
+// by the preceding NtfsFspDispath routine. Each takes as input a pointer
+// to the IRP, perform the function, and return a pointer to the volume
+// device object that they just finished servicing (if any). The return
+// pointer is then used by the main Fsp dispatch routine to check for
+// additional IRPs in the volume's overflow queue.
+//
+// Each of the following routines is also responsible for completing the IRP.
+// We moved this responsibility from the main loop to the individual routines
+// to allow them the ability to complete the IRP and continue post processing
+// actions.
+//
+
+NTSTATUS
+NtfsCommonCleanup ( // implemented in Cleanup.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+VOID
+NtfsFspClose ( // implemented in Close.c
+ IN PVCB ThisVcb OPTIONAL
+ );
+
+BOOLEAN
+NtfsAddScbToFspClose ( // implemented in Close.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN DelayClose
+ );
+
+BOOLEAN
+NtfsNetworkOpenCreate ( // implemented in Create.c
+ IN PIRP Irp,
+ OUT PFILE_NETWORK_OPEN_INFORMATION Buffer,
+ IN PDEVICE_OBJECT VolumeDeviceObject
+ );
+
+NTSTATUS
+NtfsCommonCreate ( // implemented in Create.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ OUT PFILE_NETWORK_OPEN_INFORMATION NetworkInfo OPTIONAL
+ );
+
+NTSTATUS
+NtfsCommonVolumeOpen ( // implemented in Create.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonDeviceControl ( // implemented in DevCtrl.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonDirectoryControl ( // implemented in DirCtrl.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonQueryEa ( // implemented in Ea.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonSetEa ( // implemented in Ea.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonQueryInformation ( // implemented in FileInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonSetInformation ( // implemented in FileInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonFlushBuffers ( // implemented in Flush.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonFileSystemControl ( // implemented in FsCtrl.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonLockControl ( // implemented in LockCtrl.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonRead ( // implemented in Read.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN BOOLEAN AcquireScb
+ );
+
+NTSTATUS
+NtfsCommonQuerySecurityInfo ( // implemented in SeInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonSetSecurityInfo ( // implemented in SeInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonQueryVolumeInfo ( // implemented in VolInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonSetVolumeInfo ( // implemented in VolInfo.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+NTSTATUS
+NtfsCommonWrite ( // implemented in Write.c
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ );
+
+
+//
+// The following procedure is used by the FSP and FSD routines to complete
+// an IRP.
+//
+// Note that this allows either the Irp or the IrpContext to be
+// null, however the only legal order to do this in is:
+//
+// NtfsCompleteRequest( NULL, &Irp, Status ); // completes Irp & preserves context
+// ..
+// NtfsCompleteRequest( &IrpContext, NULL, DontCare ); // deallocates context
+//
+// This would typically be done in order to pass a "naked" IrpContext off to
+// the Fsp for post processing, such as read ahead.
+//
+
+VOID
+NtfsCompleteRequest (
+ IN OUT PIRP_CONTEXT *IrpContext OPTIONAL,
+ IN OUT PIRP *Irp OPTIONAL,
+ IN NTSTATUS Status
+ );
+
+//
+// Here are the callbacks used by the I/O system for checking for fast I/O or
+// doing a fast query info call, or doing fast lock calls.
+//
+
+BOOLEAN
+NtfsFastIoCheckIfPossible (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN BOOLEAN Wait,
+ IN ULONG LockKey,
+ IN BOOLEAN CheckForReadOperation,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastQueryBasicInfo (
+ IN PFILE_OBJECT FileObject,
+ IN BOOLEAN Wait,
+ IN OUT PFILE_BASIC_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastQueryStdInfo (
+ IN PFILE_OBJECT FileObject,
+ IN BOOLEAN Wait,
+ IN OUT PFILE_STANDARD_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastLock (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PLARGE_INTEGER Length,
+ PEPROCESS ProcessId,
+ ULONG Key,
+ BOOLEAN FailImmediately,
+ BOOLEAN ExclusiveLock,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastUnlockSingle (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PLARGE_INTEGER Length,
+ PEPROCESS ProcessId,
+ ULONG Key,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastUnlockAll (
+ IN PFILE_OBJECT FileObject,
+ PEPROCESS ProcessId,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastUnlockAllByKey (
+ IN PFILE_OBJECT FileObject,
+ PVOID ProcessId,
+ ULONG Key,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PDEVICE_OBJECT DeviceObject
+ );
+
+BOOLEAN
+NtfsFastQueryNetworkOpenInfo (
+ IN struct _FILE_OBJECT *FileObject,
+ IN BOOLEAN Wait,
+ OUT struct _FILE_NETWORK_OPEN_INFORMATION *Buffer,
+ OUT struct _IO_STATUS_BLOCK *IoStatus,
+ IN struct _DEVICE_OBJECT *DeviceObject
+ );
+
+VOID
+NtfsFastIoQueryCompressionInfo (
+ IN PFILE_OBJECT FileObject,
+ OUT PFILE_COMPRESSION_INFORMATION Buffer,
+ OUT PIO_STATUS_BLOCK IoStatus
+ );
+
+VOID
+NtfsFastIoQueryCompressedSize (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ OUT PULONG CompressedSize
+ );
+
+//
+// The following macro is used by the dispatch routines to determine if
+// an operation is to be done with or without WriteThrough.
+//
+// BOOLEAN
+// IsFileWriteThrough (
+// IN PFILE_OBJECT FileObject,
+// IN PVCB Vcb
+// );
+//
+
+#define IsFileWriteThrough(FO,V) ((BOOLEAN)( \
+ FlagOn((FO)->Flags, FO_WRITE_THROUGH) || \
+ FlagOn((V)->VcbState, VCB_STATE_REMOVABLE_MEDIA)) \
+)
+
+//
+// The following macro is used to set the is fast i/o possible field in
+// the common part of the non paged fcb
+//
+//
+// BOOLEAN
+// NtfsIsFastIoPossible (
+// IN PSCB Scb
+// );
+//
+
+#define NtfsIsFastIoPossible(S) (BOOLEAN)( \
+ (!FlagOn((S)->Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED) || \
+ !FsRtlOplockIsFastIoPossible( &(S)->ScbType.Data.Oplock )) \
+ \
+ ? FastIoIsNotPossible \
+ \
+ : ((((S)->ScbType.Data.FileLock == NULL \
+ || !FsRtlAreThereCurrentFileLocks( (S)->ScbType.Data.FileLock )) && \
+ !FlagOn((S)->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) \
+ \
+ ? FastIoIsPossible \
+ \
+ : FastIoIsQuestionable \
+ \
+ ) \
+)
+
+//
+// The following macro is used to detemine if the file object is opened
+// for read only access (i.e., it is not also opened for write access or
+// delete access).
+//
+// BOOLEAN
+// IsFileObjectReadOnly (
+// IN PFILE_OBJECT FileObject
+// );
+//
+
+#define IsFileObjectReadOnly(FO) (!((FO)->WriteAccess | (FO)->DeleteAccess))
+
+
+//
+// The following macros are used to establish the semantics needed
+// to do a return from within a try-finally clause. As a rule every
+// try clause must end with a label call try_exit. For example,
+//
+// try {
+// :
+// :
+//
+// try_exit: NOTHING;
+// } finally {
+//
+// :
+// :
+// }
+//
+// Every return statement executed inside of a try clause should use the
+// try_return macro. If the compiler fully supports the try-finally construct
+// then the macro should be
+//
+// #define try_return(S) { return(S); }
+//
+// If the compiler does not support the try-finally construct then the macro
+// should be
+//
+// #define try_return(S) { S; goto try_exit; }
+//
+
+#define try_return(S) { S; goto try_exit; }
+
+
+//
+// Simple initialization for a name pair
+//
+// VOID
+// NtfsInitializeNamePair(PNAME_PAIR PNp);
+//
+
+#define NtfsInitializeNamePair(PNp) { \
+ (PNp)->Short.Buffer = (PNp)->ShortBuffer; \
+ (PNp)->Long.Buffer = (PNp)->LongBuffer; \
+ (PNp)->Short.Length = 0; \
+ (PNp)->Long.Length = 0; \
+ (PNp)->Short.MaximumLength = sizeof((PNp)->ShortBuffer); \
+ (PNp)->Long.MaximumLength = sizeof((PNp)->LongBuffer); \
+}
+
+//
+// Copy a length of WCHARs into a side of a name pair. Only copy the first name
+// that fits to avoid useless work if more than three links are encountered (per
+// BrianAn), very rare case. We use the filename flags to figure out what kind of
+// name we have.
+//
+// VOID
+// NtfsCopyNameToNamePair(
+// PNAME_PAIR PNp,
+// WCHAR Source,
+// ULONG SourceLen,
+// UCHAR NameFlags);
+//
+
+#define NtfsCopyNameToNamePair(PNp, Source, SourceLen, NameFlags) { \
+ if (!FlagOn((NameFlags), FILE_NAME_DOS)) { \
+ if ((PNp)->Long.Length == 0) { \
+ if ((PNp)->Long.MaximumLength < ((SourceLen)*sizeof(WCHAR))) { \
+ if ((PNp)->Long.Buffer != (PNp)->LongBuffer) { \
+ NtfsFreePool((PNp)->Long.Buffer); \
+ (PNp)->Long.Buffer = (PNp)->LongBuffer; \
+ (PNp)->Long.MaximumLength = sizeof((PNp)->LongBuffer); \
+ } \
+ (PNp)->Long.Buffer = NtfsAllocatePool(PagedPool,(SourceLen)*sizeof(WCHAR)); \
+ (PNp)->Long.MaximumLength = (SourceLen)*sizeof(WCHAR); \
+ } \
+ RtlCopyMemory((PNp)->Long.Buffer, (Source), (SourceLen)*sizeof(WCHAR)); \
+ (PNp)->Long.Length = (SourceLen)*sizeof(WCHAR); \
+ } \
+ } else { \
+ ASSERT((PNp)->Short.Buffer == (PNp)->ShortBuffer); \
+ if ((PNp)->Short.Length == 0) { \
+ RtlCopyMemory((PNp)->Short.Buffer, (Source), (SourceLen)*sizeof(WCHAR)); \
+ (PNp)->Short.Length = (SourceLen)*sizeof(WCHAR); \
+ } \
+ } \
+}
+
+//
+// Set up a previously used name pair for reuse.
+//
+// VOID
+// NtfsResetNamePair(PNAME_PAIR PNp);
+//
+
+#define NtfsResetNamePair(PNp) { \
+ if ((PNp)->Long.Buffer != (PNp)->LongBuffer) { \
+ NtfsFreePool((PNp)->Long.Buffer); \
+ } \
+ NtfsInitializeNamePair(PNp); \
+}
+
+//
+// Cairo support stuff.
+//
+
+typedef VOID
+(*FILE_RECORD_WALK) (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PVOID Context
+ );
+
+NTSTATUS
+NtfsIterateMft (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN OUT PFILE_REFERENCE FileReference,
+ IN FILE_RECORD_WALK FileRecordFunction,
+ IN PVOID Context
+ );
+
+VOID
+NtfsPostSpecial (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN POST_SPECIAL_CALLOUT PostSpecialCallout,
+ IN PVOID Context
+ );
+
+VOID
+NtfsSpecialDispatch (
+ PVOID Context
+ );
+
+#ifdef _CAIRO_
+
+VOID
+NtfsLoadAddOns (
+ IN struct _DRIVER_OBJECT *DriverObject,
+ IN PVOID Context,
+ IN ULONG Count
+ );
+
+NTSTATUS
+NtfsTryOpenFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PFCB *CurrentFcb,
+ IN FILE_REFERENCE FileReference
+ );
+
+INLINE
+NTSTATUS
+CiObjectChanged (
+ IN PVCB Vcb,
+ IN OBJECT_HANDLE Object,
+ IN USN Usn,
+ IN ULONG Changes
+ )
+{
+ if (Vcb->ContentIndex != NULL)
+ {
+ return NtfsData.CiCallBackTable->CiObjectChanged(
+ Vcb->ContentIndex,
+ Object,
+ Usn,
+ Changes
+ );
+ } else {
+ return STATUS_SUCCESS;
+ }
+
+}
+
+INLINE
+NTSTATUS
+CiUpdatesLost (
+ IN PVCB Vcb
+ )
+{
+ if (Vcb->ContentIndex != NULL)
+ {
+ return NtfsData.CiCallBackTable->CiUpdatesLost( Vcb->ContentIndex );
+ } else {
+ return STATUS_SUCCESS;
+ }
+
+}
+
+INLINE
+NTSTATUS
+CiMountVolume (
+ IN PVCB Vcb,
+ IN PIRP_CONTEXT IrpContext
+ )
+{
+ if (NtfsData.CiCallBackTable != NULL) {
+
+ return NtfsData.CiCallBackTable->CiMountVolume(
+ IrpContext,
+ &Vcb->ContentIndex
+ );
+
+ } else {
+ return STATUS_SUCCESS;
+ }
+
+}
+
+INLINE
+NTSTATUS
+CiDismountVolume (
+ IN PVCB Vcb,
+ IN PIRP_CONTEXT IrpContext
+ )
+{
+ if (Vcb->ContentIndex != NULL)
+ {
+ return NtfsData.CiCallBackTable->CiDismountVolume(
+ Vcb->ContentIndex,
+ IrpContext
+ );
+ } else {
+ return STATUS_SUCCESS;
+ }
+
+}
+
+INLINE
+NTSTATUS
+CiShutdown (
+ IN PIRP_CONTEXT IrpContext
+ )
+{
+ if (NtfsData.CiCallBackTable != NULL) {
+
+ return NtfsData.CiCallBackTable->CiShutdown( IrpContext );
+
+ } else {
+ return STATUS_SUCCESS;
+ }
+}
+
+INLINE
+NTSTATUS
+CiFileSystemControl (
+ IN PVCB Vcb,
+ IN ULONG FsControlCode,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT ULONG *OutBufferLength,
+ OUT PVOID OutBuffer,
+ IN PIRP_CONTEXT IrpContext
+ )
+{
+ if (Vcb->ContentIndex != NULL)
+ {
+ return NtfsData.CiCallBackTable->CiFileSystemControl(
+ Vcb->ContentIndex,
+ FsControlCode,
+ InBufferLength,
+ InBuffer,
+ OutBufferLength,
+ OutBuffer,
+ IrpContext
+ );
+ } else {
+ return STATUS_INVALID_PARAMETER;
+ }
+}
+
+//
+// The following define controls whether quota operations are done
+// on this FCB.
+//
+
+#define NtfsPerformQuotaOperation(FCB) ((FCB)->QuotaControl != NULL)
+
+VOID
+NtfsAcquireQuotaControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ );
+
+VOID
+NtfsCalculateQuotaAdjustment (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PLONGLONG Delta
+ );
+
+VOID
+NtfsDereferenceQuotaControlBlock (
+ IN PVCB Vcb,
+ IN PQUOTA_CONTROL_BLOCK *QuotaControl
+ );
+
+VOID
+NtfsExpandQuotaToAllocationSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ );
+
+VOID
+NtfsFixupQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ );
+
+NTSTATUS
+NtfsFsQuotaQueryInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsFsQuotaSetInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
+ IN ULONG Length
+ );
+
+VOID
+NtfsGetRemainingQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN ULONG OwnerId,
+ OUT PLONGLONG RemainingQuota,
+ IN OUT PQUICK_INDEX_HINT QuickIndexHint OPTIONAL
+ );
+
+ULONG
+NtfsGetCallersUserId (
+ IN PIRP_CONTEXT IrpContext
+ );
+
+ULONG
+NtfsGetOwnerId (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSID Sid,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo OPTIONAL
+ );
+
+VOID
+NtfsInitializeQuotaControlBlock (
+ IN PFCB Fcb
+ );
+
+VOID
+NtfsInitializeQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsMoveQuotaOwner (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_DESCRIPTOR Security
+ );
+
+
+VOID
+NtfsPostRepairQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsReleaseQuotaControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ );
+
+VOID
+NtfsUpdateFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PLONGLONG Delta,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN CheckQuota
+ );
+
+VOID
+NtfsUpdateQuotaDefaults (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION FileQuotaInfo
+ );
+
+INLINE
+VOID
+NtfsConditionallyFixupQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+{
+ if (FlagOn(Fcb->Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED)) {
+ NtfsFixupQuota ( IrpContext, Fcb );
+ }
+}
+
+INLINE
+VOID
+NtfsConditionallyUpdateQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PLONGLONG Delta,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN CheckQuota
+ )
+{
+ if (NtfsPerformQuotaOperation(Fcb) &&
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_QUOTA_DISABLE )) {
+ NtfsUpdateFileQuota( IrpContext, Fcb, Delta, LogIt, CheckQuota );
+ }
+}
+
+extern BOOLEAN NtfsAllowFixups;
+
+INLINE
+VOID
+NtfsReleaseQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Acquired
+ )
+{
+ if (Acquired) {
+ NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
+ }
+}
+
+INLINE
+VOID
+NtfsAcquireSecurityStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PBOOLEAN Acquired
+ )
+{
+ if (Vcb->SecurityDescriptorStream != NULL) {
+
+ ASSERT(!NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsExclusiveScb( Vcb->SecurityDescriptorStream ));
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->SecurityDescriptorStream );
+ *Acquired = TRUE;
+ }
+}
+
+INLINE
+VOID
+NtfsReleaseSecurityStream (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Acquired
+ )
+{
+ if (Acquired) {
+ NtfsReleaseScb( IrpContext, Vcb->SecurityDescriptorStream );
+ }
+}
+
+//
+// Define the quota charge for resident streams.
+//
+
+#define NtfsResidentStreamQuota( Vcb ) ((LONG) Vcb->BytesPerFileRecordSegment)
+
+
+//
+// The following macro tests to see if it is ok for an internal routine to
+// write to the volume.
+//
+
+#define NtfsIsVcbAvailable( Vcb ) (FlagOn( Vcb->VcbState, \
+ VCB_STATE_VOLUME_MOUNTED | \
+ VCB_STATE_FLAG_SHUTDOWN | \
+ VCB_STATE_PERFORMED_DISMOUNT | \
+ VCB_STATE_LOCKED) == VCB_STATE_VOLUME_MOUNTED)
+
+#else
+
+#define NtfsConditionallyUpdateQuota( IC, F, D, L, C )
+
+#define NtfsExpandQuotaToAllocationSize( IC, S )
+
+#define NtfsConditionallyFixupQuota( IC, F )
+
+#define NtfsReleaseQuotaIndex( IC, V, B )
+
+#define NtfsMoveQuotaOwner( IC, F, S )
+
+#define NtfsPerformQuotaOperation(FCB) (FALSE)
+
+#define NtfsAcquireSecurityStream( IC, V, B )
+
+#define NtfsReleaseSecurityStream( IC, V, B )
+
+#endif // _CAIRO_
+
+#endif // _NTFSPROC_
diff --git a/private/ntos/cntfs/ntfsstru.h b/private/ntos/cntfs/ntfsstru.h
new file mode 100644
index 000000000..8214d0545
--- /dev/null
+++ b/private/ntos/cntfs/ntfsstru.h
@@ -0,0 +1,3503 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ NtfsStru.h
+
+Abstract:
+
+ This module defines the data structures that make up the major
+ internal part of the Ntfs file system.
+
+ The global data structures start with the NtfsData record. It
+ contains a pointer to a File System Device object, and a queue of
+ Vcb's. There is a Vcb for every currently mounted volume. The
+ Vcb's are allocated as the extension to a volume device object.
+
+ +--------+
+ |NtfsData| +--------+
+ | | --> |FilSysDo|
+ | | | |
+ | | <+ +--------+
+ +--------+ |
+ |
+ | +--------+ +--------+
+ | |VolDo | |VolDo |
+ | | | | |
+ | +--------+ +--------+
+ +> |Vcb | <-> |Vcb | <-> ...
+ | | | |
+ +--------+ +--------+
+
+ The File System Device Object contains the global work queue for
+ NTFS while each volume device object contains an overflow work queue.
+
+ Each Vcb contains a table of all Fcbs for the volume indexed by their
+ file reference (Called the FcbTable). And each Vcb contains a pointer
+ a root Lcb for the volume. An Lcb is used to connect an indexed Scb
+ (i.e., a directory) to an Fcb and give it a name.
+
+ The following diagram shows the root structure.
+
+ +--------+
+ |Vcb |
+ | | +---+ +--------+
+ | | -|Lcb|-> |RootFcb |
+ +--------+ |'\'| | |
+ +---+ | |
+ +--------+
+
+ Each Scb will only have one parent Fcb but multiple Fcb children (each
+ connected via an Lcb). An Fcb can have multiple Scb parents (via
+ Lcbs) and multiple Scb Children.
+
+ Now associated with each Fcb is potentially many Scbs. An Scb
+ is allocated for each opened stream file object (i.e., an attribute
+ that the file system is manipulating as a stream file). Each Scb
+ contains a common fsrtl header and information necessary for doing
+ I/O to the stream.
+
+ +--------+
+ |Fcb | +--------+ +--------+
+ | | <-> |Scb | <-> |Scb | <-> ...
+ +--------+ | | | |
+ +--------+ +--------+
+
+ In the following diagram we have two index scb (Scb1 and Scb2). The
+ are two file opened under Scb1 both for the same File. The file was
+ opened once with the name LcbA and another time with the name LcbB.
+ Scb2 also has two opened file one is Fcb1 and named LcbC and the other
+ is Fcb2 and named LcbD. Fcb1 has two opened Scbs under it (Scb3 and
+ Scb4), and Fcb2 has one opened Scb underneath it (Scb5).
+
+
+ +--------+ +--------+
+ |Scb | |Scb |
+ | 1 | | 2 |
+ | | | |
+ +--------+ +--------+
+
+ | | | |
+
+ Lcb Lcb Lcb Lcb
+ A B C D
+
+ | | +--------+ | | +--------+
+ | +---> |Fcb | <---+ +---> |Fcb |
+ | | 1 | | 2 |
+ +--------> | | | |
+ +--------+ +--------+
+ ^ ^ ^ ^
+ +------------+ +------------+ +----+ +----+
+ | | | |
+ | +--------+ +--------+ | | +--------+ |
+ +> |Scb | <--> |Scb | <+ +> |Scb | <+
+ | 3 | | 4 | | 5 |
+ | | | | | |
+ +--------+ +--------+ +--------+
+
+ In addition off of each Lcb is a list of Ccb and Prefix entries. The
+ Ccb list is for each ccb that has opened that File (fcb) via the name.
+ The Prefix list contains the prefix table entries that we are caching.
+
+
+ The NtfsData, all Vcbs, and the paging file Fcb, and all Scbs are
+ allocated out of nonpaged pool. The Fcbs are allocated out of paged
+ pool.
+
+ The resources protecting the NTFS memory structures are setup as
+ follows:
+
+ 1. There is a global resource in the NtfsData record. This resource
+ protects the NtfsData record which includes any changes to its
+ Vcb queue.
+
+ 2. There is a resource per Vcb. This resource pretects the Vcb record
+ which includes adding and removing Fcbs, and Scbs
+
+ 3. There is a single resource protecting an Fcb and its assigned
+ Scbs. This resource protects any changes to the Fcb, and Scb
+ records. The way this one works is that each Fcb, and Scb point
+ to the resource. The Scb also contain back pointers to their
+ parent Fcb but we cannot use this pointer to get the resource
+ because the Fcb might be in nonpaged pool.
+
+ +--------+
+ |Fcb | +--------+ +--------+
+ | | <-> |Scb | <-> |Scb | <-> ...
+ +--------+ | | | |
+ +--------+ +--------+
+ |
+ | | |
+ | v |
+ | |
+ | +--------+ |
+ +-----> |Resource| <-----+
+ | |
+ +--------+
+
+
+
+ There are several types of opens possible for each file object handled by
+ NTFS. They are UserFileOpen, UserDirectoryOpen, UserVolumeOpen,
+ StreamFileOpen, and UserProperytSetOpen. The first three types correspond
+ to user opens on files, directories, and dasd respectively. The last type
+ is for any file object created by NTFS for its stream I/O (e.g., the volume
+ bitmap). The file system uses the FsContext and FsContext2 fields of
+ the file object to store the fcb/scb/ccb associated with the file object.
+
+ Type of open FsContext FsContext2
+ ------------ --------- ----------
+
+ UserPropertySetOpen Pointer to Scb Pointer to Ccb
+ UserFileOpen
+
+ UserDirectoryOpen Pointer to Scb Pointer to Ccb
+
+ UserVolumeOpen Pointer to Scb Pointer to Ccb
+
+ StreamFileOpen Pointer to Scb null
+
+ The only part of the NTFS code that actually needs to know this
+ information is in FilObSup.c. But we talk about it here to help
+ developers debug the system.
+
+
+ To mount a new NTFS volume requires a bit of juggling. The idea is
+ to have as little setup in memory as necessary to recoginize the
+ volume, call a restart routine that will recover the volume, and then
+ precede with the mounting. To aid in this the regular directory
+ structures of the Fcb is bypassed. In its place we have a linked list
+ of Fcbs off of the Vcb. This is done because during recovery we do
+ not know where an Fcb belongs in the directory hierarchy. So at
+ restart time all new fcbs get put in this prerestart Fcb list. Then
+ after restart whenever we create a new Fcb we search this list for a
+ match (on file reference). If we find one we remove the fcb from this
+ list and move it to the proper place in the directory hierarchy tree
+ (fcb tree).
+
+Author:
+
+ Brian Andrew [BrianAn] 21-May-1991
+ David Goebel [DavidGoe]
+ Gary Kimura [GaryKi]
+ Tom Miller [TomM]
+
+Revision History:
+
+--*/
+
+#ifndef _NTFSSTRU_
+#define _NTFSSTRU_
+
+typedef PVOID PBCB; //**** Bcb's are now part of the cache module
+
+//
+// Define how many freed structures we are willing to keep around
+//
+
+#define FREE_FCB_TABLE_SIZE (8)
+
+#define MAX_DELAYED_CLOSE_COUNT (0x10)
+
+//
+// Timer status types. These are used in NtfsSetDirtyBcb synchronization with
+// checkpoint-all-volumes activity.
+//
+
+typedef enum TIMER_STATUS {
+ TIMER_SET = 0,
+ TIMER_NOT_SET = 1,
+} TIMER_STATUS;
+
+
+//
+// The NTFS_DATA record is the top record in the NTFS file system
+// in-memory data structure. This structure must be allocated from
+// non-paged pool.
+//
+
+typedef struct _NTFS_DATA {
+
+ //
+ // The type and size of this record (must be NTFS_NTC_DATA_HEADER)
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // A pointer to the Driver object we were initialized with
+ //
+
+ PDRIVER_OBJECT DriverObject;
+
+ //
+ // A queue of all the devices that are mounted by the file system.
+ // Corresponds to the field Vcb->VcbLinks;
+ //
+
+ LIST_ENTRY VcbQueue;
+
+ //
+ // A resource variable to control access to the global NTFS data
+ // record
+ //
+
+ ERESOURCE Resource;
+
+ //
+ // The following list entry is used for performing closes that can't
+ // be done in the context of the original caller.
+ //
+
+ LIST_ENTRY AsyncCloseList;
+
+ BOOLEAN AsyncCloseActive;
+ BOOLEAN ReduceDelayedClose;
+
+ ULONG AsyncCloseCount;
+
+ //
+ // A pointer to our EPROCESS struct, which is a required input to the
+ // Cache Management subsystem.
+ //
+
+ PEPROCESS OurProcess;
+
+ //
+ // The following fields describe the deferred close file objects.
+ //
+
+ ULONG DelayedCloseCount;
+
+ LIST_ENTRY DelayedCloseList;
+
+ //
+ // This is the ExWorkerItem that does both kinds of deferred closes.
+ //
+
+ WORK_QUEUE_ITEM NtfsCloseItem;
+
+ //
+ // The spinlock protects access to the zone/lists
+ //
+
+ KSPIN_LOCK StrucSupSpinLock;
+
+ UCHAR FreeFcbTableSize;
+ UCHAR UnusedUchar[3];
+
+ PVOID *FreeFcbTableArray[ FREE_FCB_TABLE_SIZE ];
+ //
+ // Free arrays are dynamically sized
+ //
+
+ ULONG FreeEresourceSize;
+ ULONG FreeEresourceTotal;
+ ULONG FreeEresourceMiss;
+ PERESOURCE *FreeEresourceArray;
+
+ //
+ // Cache manager call back structures, which must be passed on each
+ // call to CcInitializeCacheMap.
+ //
+
+ CACHE_MANAGER_CALLBACKS CacheManagerCallbacks;
+ CACHE_MANAGER_CALLBACKS CacheManagerVolumeCallbacks;
+
+ //
+ // The following fields are used for the CheckpointVolumes()
+ // callback.
+ //
+
+ KDPC VolumeCheckpointDpc;
+ KTIMER VolumeCheckpointTimer;
+
+ WORK_QUEUE_ITEM VolumeCheckpointItem;
+ TIMER_STATUS TimerStatus;
+
+ //
+ // Flags. These are the flags for the volume.
+ //
+
+ UCHAR Flags;
+
+ //
+ // This is a list of all of the threads currently doing read ahead.
+ // We will not hot fix for these threads.
+ //
+
+ LIST_ENTRY ReadAheadThreads;
+
+ //
+ // The following table of unicode values is the case mapping, with
+ // the size in number of Unicode characters. We keep a global copy
+ // and let each Vcb use this copy if the upcase table for the volume
+ // matches.
+ //
+
+ PWCH UpcaseTable;
+ ULONG UpcaseTableSize;
+
+ //
+ // Pointer to a default security descriptor.
+ //
+
+ PSECURITY_DESCRIPTOR DefaultDescriptor;
+ ULONG DefaultDescriptorLength;
+
+#ifdef _CAIRO_
+
+ //
+ // Call back for content index.
+ //
+
+ CI_CALL_BACK *CiCallBackTable;
+
+ //
+ // Call back table for views.
+ //
+
+ VIEW_CALL_BACK *ViewCallBackTable;
+
+#endif
+
+} NTFS_DATA;
+typedef NTFS_DATA *PNTFS_DATA;
+
+#define NTFS_FLAGS_SMALL_SYSTEM (0x01)
+#define NTFS_FLAGS_MEDIUM_SYSTEM (0x02)
+#define NTFS_FLAGS_LARGE_SYSTEM (0x04)
+#define NTFS_FLAGS_CREATE_8DOT3_NAMES (0X10)
+#define NTFS_FLAGS_ALLOW_EXTENDED_CHAR (0x20)
+#define NTFS_FLAGS_DISABLE_LAST_ACCESS (0x40)
+
+
+//
+// The record allocation context structure is used by the routines that
+// allocate and deallocate records based on a bitmap (for example the mft
+// bitmap or the index bitmap). The context structure needs to be
+// defined here because the mft bitmap context is declared as part of the
+// vcb.
+//
+
+typedef struct _RECORD_ALLOCATION_CONTEXT {
+
+ //
+ // The following field is a pointer to the scb for the data part of
+ // the file that this bitmap controls. For example, it is a pointer
+ // to the data attribute for the MFT.
+ //
+ // NOTE !!!! The Data Scb must remain the first entry in this
+ // structure. If we need to uninitialize and reinitialize this
+ // structure in the running system we don't want to touch this field.
+ //
+ // NOTE !!!! The code that clears the record allocation context
+ // expects the BitmapScb field to follow the Data Scb field.
+ //
+
+ struct _SCB *DataScb;
+
+ //
+ // The following field is used to indicate if the bitmap attribute is
+ // in a resident form or a nonresident form. If the bitmap is in a
+ // resident form then the pointer is null, and whenever a bitmap
+ // routine is called it must also be passed an attribute enumeration
+ // context to be able to read the bitmap. If the field is not null
+ // then it points to the scb for the non resident bitmap attribute
+ //
+
+ struct _SCB *BitmapScb;
+
+ //
+ // The following two fields describe the current size of the bitmap
+ // (in bits) and the number of free bits currently in the bitmap.
+ // A value of MAXULONG in the CurrentBitmapSize indicates that we
+ // need to reinitialize the record context structure.
+ //
+
+ ULONG CurrentBitmapSize;
+ ULONG NumberOfFreeBits;
+
+ //
+ // The following field contains the index of last bit that we know
+ // to be set. This is used for truncation purposes.
+ //
+
+ LONG IndexOfLastSetBit;
+
+ //
+ // The following three fields are used to indicate the allocation
+ // size for the bitmap (i.e., each bit in the bitmap represents how
+ // many bytes in the data attribute). Also it indicates the
+ // granularity with which we will either extend or shrink the bitmap.
+ //
+
+ ULONG BytesPerRecord;
+
+ ULONG ExtendGranularity;
+ ULONG TruncateGranularity;
+
+} RECORD_ALLOCATION_CONTEXT;
+typedef RECORD_ALLOCATION_CONTEXT *PRECORD_ALLOCATION_CONTEXT;
+
+
+//
+// The Vcb (Volume control Block) record corresponds to every volume
+// mounted by the file system. They are ordered in a queue off of
+// NtfsData.VcbQueue. This structure must be allocated from non-paged
+// pool
+//
+
+#define DEFAULT_ATTRIBUTE_TABLE_SIZE (32)
+#define DEFAULT_TRANSACTION_TABLE_SIZE (32)
+#define DEFAULT_DIRTY_PAGES_TABLE_SIZE (64)
+
+//
+// The Restart Pointers structure is the actual structure supported by
+// routines and macros to get at a Restart Table. This structure is
+// required since the restart table itself may move, so one must first
+// acquire the resource to synchronize, then follow the pointer to the
+// table.
+//
+
+typedef struct _RESTART_POINTERS {
+
+ //
+ // Resource to synchronize with table moves. This resource must
+ // be held shared while dealing with pointers to table entries,
+ // and exclusive to move the table.
+ //
+
+ ERESOURCE Resource;
+
+ //
+ // Pointer to the actual Restart Table.
+ //
+
+ struct _RESTART_TABLE *Table;
+
+ //
+ // Spin Lock synchronizing allocates and deletes of entries in the
+ // table. The resource must be held at least shared.
+ //
+
+ KSPIN_LOCK SpinLock;
+
+ //
+ // Remember if the resource was initialized.
+ //
+
+ BOOLEAN ResourceInitialized;
+
+ //
+ // For quad & cache line alignment
+ //
+
+ UCHAR Unused[7];
+
+} RESTART_POINTERS, *PRESTART_POINTERS;
+
+
+//
+// The NTFS MCB structure is a super set of the FsRtl Large Mcb package
+//
+// This structure is ideally aligned on an odd quadword (address ends in 8).
+//
+
+typedef struct _NTFS_MCB_ENTRY {
+
+ LIST_ENTRY LruLinks;
+ struct _NTFS_MCB *NtfsMcb;
+ struct _NTFS_MCB_ARRAY *NtfsMcbArray;
+ LARGE_MCB LargeMcb;
+
+} NTFS_MCB_ENTRY;
+typedef NTFS_MCB_ENTRY *PNTFS_MCB_ENTRY;
+
+typedef struct _NTFS_MCB_ARRAY {
+
+ VCN StartingVcn;
+ VCN EndingVcn;
+ PNTFS_MCB_ENTRY NtfsMcbEntry;
+ PVOID Unused;
+
+} NTFS_MCB_ARRAY;
+typedef NTFS_MCB_ARRAY *PNTFS_MCB_ARRAY;
+
+typedef struct _NTFS_MCB {
+
+ PFSRTL_ADVANCED_FCB_HEADER FcbHeader;
+ POOL_TYPE PoolType;
+ ULONG NtfsMcbArraySizeInUse;
+ ULONG NtfsMcbArraySize;
+ PNTFS_MCB_ARRAY NtfsMcbArray;
+ PFAST_MUTEX FastMutex;
+
+} NTFS_MCB;
+typedef NTFS_MCB *PNTFS_MCB;
+
+//
+// Define some additional Ntfs Mcb structures to accomodate small to
+// medium files with fewer pool allocations. This space will be
+// unused for large files (more than three ranges).
+//
+
+typedef union {
+
+ //
+ // For the first phase, embed one array element and its Mcb entry.
+ //
+
+ struct {
+
+ NTFS_MCB_ARRAY SingleMcbArrayEntry;
+
+ NTFS_MCB_ENTRY McbEntry;
+
+ } Phase1;
+
+ //
+ // For the second phase, we can at least store three entries in
+ // the Mcb Array.
+ //
+
+ struct {
+
+ NTFS_MCB_ARRAY ThreeMcbArrayEntries[3];
+
+ } Phase2;
+
+} NTFS_MCB_INITIAL_STRUCTS;
+typedef NTFS_MCB_INITIAL_STRUCTS *PNTFS_MCB_INITIAL_STRUCTS;
+
+
+//
+// Structure used to track the deallocated clusters.
+//
+
+typedef struct _DEALLOCATED_CLUSTERS {
+
+ LARGE_MCB Mcb;
+ LSN Lsn;
+ LONGLONG ClusterCount;
+
+} DEALLOCATED_CLUSTERS, *PDEALLOCATED_CLUSTERS;
+
+//
+// The Vcb structure corresponds to every mounted NTFS volume in the
+// system
+//
+
+#define VCB_SECURITY_CACHE_BY_ID_SIZE 31
+#define VCB_SECURITY_CACHE_BY_HASH_SIZE 31
+
+typedef struct _VCB {
+
+ //
+ // The type and size of this record (must be NTFS_NTC_VCB)
+ //
+ // Assumption here is that this structure is embedded within a
+ // device object and the base of this structure in on an even
+ // 64-bit boundary. We will put the embedded structures on
+ // the same boundaries they would be on if allocated from pool
+ // (odd 64-bit boundary) except if the structure would fit
+ // within a 16 byte cache line.
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // A pointer the device object passed in by the I/O system on a mount
+ // This is the target device object that the file system talks to
+ // when it needs to do any I/O (e.g., the disk stripper device
+ // object).
+ //
+ //
+
+ PDEVICE_OBJECT TargetDeviceObject;
+
+ //
+ // The links for the queue of all the Vcbs in the system.
+ // Corresponds to the filld NtfsData.VcbQueue
+ //
+
+ LIST_ENTRY VcbLinks;
+
+ //
+ // Pointer to the Scb for the special system file. If the field is
+ // null then we haven't yet built the scb for that system file. Also
+ // the pointer to the stream file object is located in the scb.
+ //
+ // NOTE: AcquireExclusiveFiles depends on this order. Any change
+ // here should be checked with the code there.
+ //
+
+ struct _SCB *MftScb;
+ struct _SCB *Mft2Scb;
+ struct _SCB *LogFileScb;
+ struct _SCB *VolumeDasdScb;
+ struct _SCB *RootIndexScb;
+ struct _SCB *BitmapScb;
+ struct _SCB *AttributeDefTableScb;
+ struct _SCB *UpcaseTableScb;
+ struct _SCB *BadClusterFileScb;
+ struct _SCB *QuotaTableScb;
+ struct _SCB *OwnerIdTableScb;
+ struct _SCB *MftBitmapScb;
+
+ //
+ // File object for log file. We always dereference the file object for the
+ // log file last. This will allow us to synchronize when the Vpb for the
+ // volume is deleted.
+ //
+
+ PFILE_OBJECT LogFileObject;
+
+ //
+ // The internal state of the volume. This is a collection of Vcb
+ // state flags. The VcbState is synchronized with the Vcb resource.
+ // The MftDefragState is synchronized with the CheckpointEvent.
+ // The MftReserveFlags are sychronized with the MftScb.
+ //
+
+ ULONG MftReserveFlags;
+ ULONG MftDefragState;
+ ULONG VcbState;
+
+ //
+ // A pointer to an array of structs to hold performance counters,
+ // one array element for each processor. The array is allocated
+ // from non-paged pool. If this member is deleted, replace with
+ // padding.
+ //
+
+ PFILESYSTEM_STATISTICS Statistics;
+
+ //
+ // A count of the number of file objects that have any file/directory
+ // opened on this volume. And a count of the number of special system
+ // files that we have open
+ //
+
+ CLONG CleanupCount;
+ CLONG CloseCount;
+ CLONG ReadOnlyCloseCount;
+ CLONG SystemFileCloseCount;
+
+ //
+ // The following fields are used by the BitmpSup routines. The first
+ // value contains the total number of clusters on the volume, this
+ // is computed from the boot sector information. The second value
+ // is the current number of free clusters available for allocation on
+ // the volume. Allocation is handled by using a free space mcb that
+ // describes some small window of known clusters that are free.
+ //
+ // The last field is for storing local volume specific data needed by
+ // the bitmap routines
+ //
+
+ LONGLONG TotalClusters;
+ LONGLONG FreeClusters;
+ LONGLONG DeallocatedClusters;
+
+ //
+ // Total number of reserved clusters on the volume, must be less than
+ // or equal to FreeClusters. Use the security fast mutex as a
+ // convenient end resource for this field.
+ //
+
+ LONGLONG TotalReserved;
+
+ LARGE_MCB FreeSpaceMcb;
+
+ ULONG FreeSpaceMcbMaximumSize;
+ ULONG FreeSpaceMcbTrimToSize;
+
+ //
+ // Last Lcn used for fresh allocation
+ //
+
+ LCN LastBitmapHint;
+
+ //
+ // The root Lcb for this volume.
+ //
+
+ struct _LCB *RootLcb;
+
+ //
+ // A pointer to the VPB for the volume passed in by the I/O system on
+ // a mount.
+ //
+
+ PVPB Vpb;
+
+ //
+ // This field contains a calculated value which determines when an
+ // individual attribute is large enough to be moved to free up file
+ // record space. (The calculation of this variable must be
+ // considered in conjunction with the constant
+ // MAX_MOVEABLE_ATTRIBUTES below.)
+ //
+
+ ULONG BigEnoughToMove;
+
+ //
+ // The following volume-specific parameters are extracted from the
+ // Boot Sector.
+ //
+
+ ULONG DefaultBlocksPerIndexAllocationBuffer;
+ ULONG DefaultBytesPerIndexAllocationBuffer;
+
+ ULONG BytesPerSector;
+ ULONG BytesPerCluster;
+ ULONG BytesPerFileRecordSegment;
+
+ //
+ // Zero clusters per file record segment indicates that clusters are larger than
+ // file records. Zero file record segments per clusters indicates that
+ // file records are larger than clusters.
+ //
+
+ ULONG ClustersPerFileRecordSegment;
+ ULONG FileRecordsPerCluster;
+
+ LCN MftStartLcn;
+ LCN Mft2StartLcn;
+ LONGLONG NumberSectors;
+
+ //
+ // The following fields are used to verify that an NTFS volume hasn't
+ // changed. The serial number is stored in the boot sector on disk,
+ // and the four times are from the standard information field of the
+ // volume file.
+ //
+
+ LONGLONG VolumeSerialNumber;
+
+ LONGLONG VolumeCreationTime;
+ LONGLONG VolumeLastModificationTime;
+ LONGLONG VolumeLastChangeTime;
+ LONGLONG VolumeLastAccessTime;
+
+ //
+ // Convenient constants for the conversion macros. Shift and mask values are for
+ //
+ // Clusters <=> Bytes
+ // FileRecords <=> Bytes
+ // FileRecords <=> Clusters
+ //
+ // Note that you must know whether to shift right or left when using the
+ // file record/cluster shift value.
+ //
+
+ ULONG ClusterMask; // BytesPerCluster - 1
+ ULONG InverseClusterMask; // ~ClusterMask
+ ULONG ClusterShift; // 2**ClusterShift == BytesPerCluster
+
+ ULONG MftShift; // 2**MftShift == BytesPerFileRecord
+ ULONG MftToClusterShift; // 2**MftClusterShift == ClusterPerFileRecordSegment
+ // 2**MftToClusterShift == FileRecordsPerCluster
+
+ //
+ // Clusters per page will be 1 if the cluster size is larger than the page size
+ //
+
+ ULONG ClustersPerPage;
+
+ ULONG MftReserved;
+ ULONG MftCushion;
+
+ //
+ // Mutex to synchronize access to the Fcb table.
+ //
+
+ FAST_MUTEX FcbTableMutex;
+
+ //
+ // Mutex to synchronize access to the security descriptors.
+ //
+
+ FAST_MUTEX FcbSecurityMutex;
+
+ //
+ // Synchronization objects for checkpoint operations.
+ //
+
+ FAST_MUTEX CheckpointMutex;
+ KEVENT CheckpointNotifyEvent;
+
+ ULONG CheckpointFlags;
+
+ //
+ // We don't allow compression on a system with a cluster size greater than
+ // 4k. Use a mask value here to quickly check allowed compression on
+ // this volume.
+ //
+
+ USHORT AttributeFlagsMask;
+ USHORT UnusedUshort;
+
+ //
+ // The count of free records is based on the size of the Mft and the
+ // allocated records. The hole count is the count of how many file
+ // records are not allocated.
+ //
+
+ ULONG MftHoleGranularity;
+
+ ULONG MftFreeRecords;
+ ULONG MftHoleRecords;
+
+ //
+ // Lfs Log Handle for this volume
+ //
+
+ LFS_LOG_HANDLE LogHandle;
+
+ //
+ // Variables for Mft hole calculations.
+ //
+
+ ULONG MftHoleMask;
+ ULONG MftHoleInverseMask;
+
+ //
+ // The count of the bitmap bits per hole. This is the number of file
+ // records per hole. Must be converted to clusters to find a hole in
+ // the Mft Mcb.
+ //
+
+ ULONG MftClustersPerHole;
+ ULONG MftHoleClusterMask;
+ ULONG MftHoleClusterInverseMask;
+
+ //
+ // Open attribute table.
+ //
+
+ LSN LastRestartArea;
+ RESTART_POINTERS OpenAttributeTable;
+
+ //
+ // Transaction table.
+ //
+
+ LSN LastBaseLsn;
+ RESTART_POINTERS TransactionTable;
+
+ //
+ // LSNs of the end of the last checkpoint and the last RestartArea.
+ // Normally the RestartArea Lsn is greater than the other one,
+ // however if the VcbState indicates that a checkpoint is in
+ // progress, then these Lsns are in flux.
+ //
+
+ LSN EndOfLastCheckpoint;
+
+ //
+ // The following string contains the device name for this partition.
+ //
+
+ UNICODE_STRING DeviceName;
+
+ //
+ // The following table of unicode values is the case mapping, with
+ // the size in number of Unicode characters. If the upcase table
+ // that is stored in NtfsData matches the one for the volume then
+ // we'll simply use that one and not allocate a special one for the
+ // volume.
+ //
+
+ PWCH UpcaseTable;
+ ULONG UpcaseTableSize;
+
+ //
+ // A table of all the fcb that have been created for this volume.
+ //
+
+ RTL_GENERIC_TABLE FcbTable;
+
+ //
+ // The following is the head of a list of notify Irps.
+ //
+
+ LIST_ENTRY DirNotifyList;
+
+ //
+ // The following is used to synchronize the dir notify list.
+ //
+
+ PNOTIFY_SYNC NotifySync;
+
+ //
+ // The following field is a pointer to the file object that has the
+ // volume locked. if the VcbState has the locked flag set.
+ //
+
+ PFILE_OBJECT FileObjectWithVcbLocked;
+
+ //
+ // The following two fields are used by the bitmap routines to
+ // determine what is called the mft zone. The Mft zone are those
+ // clusters on the disk were we will try and put the mft and only the
+ // mft unless the disk is getting too full.
+ //
+
+ LCN MftZoneStart;
+ LCN MftZoneEnd;
+
+ //
+ // The following are used to track the deallocated clusters waiting
+ // for a checkpoint. The pointers are used so we can toggle the
+ // use of the structures.
+ //
+
+ PDEALLOCATED_CLUSTERS PriorDeallocatedClusters;
+ PDEALLOCATED_CLUSTERS ActiveDeallocatedClusters;
+
+ DEALLOCATED_CLUSTERS DeallocatedClusters1;
+ DEALLOCATED_CLUSTERS DeallocatedClusters2;
+
+ //
+ // The following field is used for mft bitmap allocation
+ //
+
+ RECORD_ALLOCATION_CONTEXT MftBitmapAllocationContext;
+
+ //
+ // A resource variable to control access to the volume specific data
+ // structures
+ //
+
+ ERESOURCE Resource;
+
+ //
+ // This is a pointer to the attribute definitions for the volume
+ // which are loaded into nonpaged pool.
+ //
+
+ PATTRIBUTE_DEFINITION_COLUMNS AttributeDefinitions;
+
+ //
+ // Log header reservation. This is the amount to add to the reservation
+ // amount to compensate for the commit record. Lfs reserves differently
+ // for its log record header and the body of a log record.
+ //
+
+ ULONG LogHeaderReservation;
+
+ //
+ // Count of outstanding notify handles. If zero we can noop the notify calls.
+ //
+
+ ULONG NotifyCount;
+
+ //
+ // File property (shortname/longname/createtime) tunneling structure
+ //
+
+ TUNNEL Tunnel;
+
+#ifdef _CAIRO_
+
+ struct _SCB *SecurityDescriptorStream;
+ struct _SCB *SecurityIdIndex;
+ struct _SCB *SecurityDescriptorHashIndex;
+ SECURITY_ID NextSecurityId;
+ struct _SHARED_SECURITY *SecurityCacheById[VCB_SECURITY_CACHE_BY_ID_SIZE];
+ struct _SHARED_SECURITY **SecurityCacheByHash[VCB_SECURITY_CACHE_BY_HASH_SIZE];
+
+ //
+ // Context index per volume handle.
+ //
+
+ CONTENT_INDEX_HANDLE ContentIndex;
+
+ //
+ // The following items are for Quota support.
+ // The QuotaControlTable is the root of the quota control blocks.
+ //
+
+ RTL_GENERIC_TABLE QuotaControlTable;
+
+ //
+ // Lock used for QuotaControlTable;
+ //
+
+ FAST_MUTEX QuotaControlLock;
+
+ //
+ // Quota state and flags are protected by the QuotaControlLock above
+ //
+
+ ULONG QuotaState;
+
+ //
+ // QuotaFlags are a copy of the flags from default user index entry.
+ //
+
+ ULONG QuotaFlags;
+
+ //
+ // Current file reference used by the quota repair code.
+ //
+
+ FILE_REFERENCE QuotaFileReference;
+
+ //
+ // The next owner Id to be allocated.
+ //
+
+ ULONG QuotaOwnerId;
+
+ //
+ // Delete sequence number. The value gets incremented each time
+ // an owner id is marked for deletion.
+ //
+
+ ULONG QuotaDeleteSecquence;
+
+#endif
+
+#ifdef NTFS_CHECK_BITMAP
+ PRTL_BITMAP BitmapCopy;
+ ULONG BitmapPages;
+#endif
+
+} VCB;
+typedef VCB *PVCB;
+
+//
+// These are the VcbState flags. Synchronized with the Vcb resource.
+//
+
+#define VCB_STATE_VOLUME_MOUNTED (0x00000001)
+#define VCB_STATE_LOCKED (0x00000002)
+#define VCB_STATE_REMOVABLE_MEDIA (0x00000004)
+#define VCB_STATE_VOLUME_MOUNTED_DIRTY (0x00000008)
+#define VCB_STATE_RESTART_IN_PROGRESS (0x00000010)
+#define VCB_STATE_FLAG_SHUTDOWN (0x00000020)
+#define VCB_STATE_NO_SECONDARY_AVAILABLE (0x00000040)
+#define VCB_STATE_RELOAD_FREE_CLUSTERS (0x00000080)
+#define VCB_STATE_ALREADY_BALANCED (0x00000100)
+#define VCB_STATE_VOL_PURGE_IN_PROGRESS (0x00000200)
+#define VCB_STATE_TEMP_VPB (0x00000400)
+#define VCB_STATE_PERFORMED_DISMOUNT (0x00000800)
+#define VCB_STATE_VALID_LOG_HANDLE (0x00001000)
+#define VCB_STATE_DELETE_UNDERWAY (0x00002000)
+#define VCB_STATE_REDUCED_MFT (0x00004000)
+#define VCB_STATE_EXPLICIT_LOCK (0x00010000)
+#define VCB_STATE_DISALLOW_DISMOUNT (0x00020000)
+
+//
+// These are the flags for the Mft and the reserveration state.
+// Although these are in the Vcb they are synchronized with
+// the resource in the MftScb.
+//
+
+#define VCB_MFT_RECORD_RESERVED (0x00000001)
+#define VCB_MFT_RECORD_15_USED (0x00000002)
+
+//
+// These are the MftDefragState flags. Synchronized with the
+// CheckpointEvent.
+//
+
+#define VCB_MFT_DEFRAG_PERMITTED (0x00000001)
+#define VCB_MFT_DEFRAG_ENABLED (0x00000002)
+#define VCB_MFT_DEFRAG_TRIGGERED (0x00000004)
+#define VCB_MFT_DEFRAG_ACTIVE (0x00000008)
+#define VCB_MFT_DEFRAG_EXCESS_MAP (0x00000010)
+
+//
+// These are the Checkpoint flags. Synchronized with the
+// CheckpointEvent. These flags are in the CheckpointFlags
+// field.
+//
+
+#define VCB_DUMMY_CHECKPOINT_POSTED (0x00000001)
+#define VCB_CHECKPOINT_IN_PROGRESS (0x00000002)
+#define VCB_LAST_CHECKPOINT_CLEAN (0x00000004)
+
+//
+// These are Vcb quota state flags. Synchronized with the
+// QuotaControlLock. These flags are in the QuotaState field.
+//
+
+#define VCB_QUOTA_REPAIR_POSTED (0x00000100)
+#define VCB_QUOTA_CLEAR_RUNNING (0x00000200)
+#define VCB_QUOTA_INDEX_REPAIR (0x00000300)
+#define VCB_QUOTA_OWNER_VERIFY (0x00000400)
+#define VCB_QUOTA_RECALC_STARTED (0x00000500)
+#define VCB_QUOTA_DELETEING_IDS (0x00000600)
+#define VCB_QUOTA_REPAIR_RUNNING (0x00000700)
+#define VCB_QUOTA_SAVE_QUOTA_FLAGS (0x00001000)
+
+//
+// This is the maximum number of attributes in a file record which could
+// be considered for moving. This value should be changed only in
+// conjunction with the initialization of the BigEnoughToMove field
+// above.
+//
+
+#define MAX_MOVEABLE_ATTRIBUTES (3)
+
+
+//
+// The Volume Device Object is an I/O system device object with a
+// workqueue and an VCB record appended to the end. There are multiple
+// of these records, one for every mounted volume, and are created during
+// a volume mount operation. The work queue is for handling an overload
+// of work requests to the volume.
+//
+
+typedef struct _VOLUME_DEVICE_OBJECT {
+
+ DEVICE_OBJECT DeviceObject;
+
+ //
+ // The following field tells how many requests for this volume have
+ // either been enqueued to ExWorker threads or are currently being
+ // serviced by ExWorker threads. If the number goes above
+ // a certain threshold, put the request on the overflow queue to be
+ // executed later.
+ //
+
+ ULONG PostedRequestCount;
+
+ //
+ // The following field indicates the number of IRP's waiting
+ // to be serviced in the overflow queue.
+ //
+
+ ULONG OverflowQueueCount;
+
+ //
+ // The following field contains the queue header of the overflow
+ // queue. The Overflow queue is a list of IRP's linked via the IRP's
+ // ListEntry field.
+ //
+
+ LIST_ENTRY OverflowQueue;
+
+ //
+ // The following spinlock protects access to all the above fields.
+ //
+
+ KSPIN_LOCK OverflowQueueSpinLock;
+
+ //
+ // This is the file system specific volume control block.
+ //
+
+ VCB Vcb;
+
+} VOLUME_DEVICE_OBJECT;
+typedef VOLUME_DEVICE_OBJECT *PVOLUME_DEVICE_OBJECT;
+
+
+
+//
+// The following structure is used to perform a quick lookup of an
+// index entry for the update duplicate information call.
+//
+
+typedef struct _QUICK_INDEX {
+
+ //
+ // Change count for the Scb Index stream when this snapshot is made.
+ //
+
+ ULONG ChangeCount;
+
+ //
+ // This is the offset of the entry in the buffer. A value of zero is
+ // used for an entry in the root index.
+ //
+
+ ULONG BufferOffset;
+
+ //
+ // Captured Lsn for page containing this entry.
+ //
+
+ LSN CapturedLsn;
+
+ //
+ // This is the IndexBlock for the index bucket.
+ //
+
+ LONGLONG IndexBlock;
+
+} QUICK_INDEX;
+
+typedef QUICK_INDEX *PQUICK_INDEX;
+
+//
+// This structure is used to contain a link name and connections into
+// the splay tree for the parent.
+//
+
+typedef struct _NAME_LINK {
+
+ UNICODE_STRING LinkName;
+ RTL_SPLAY_LINKS Links;
+
+} NAME_LINK, *PNAME_LINK;
+
+//
+// The Lcb record corresponds to every open path between an Scb and an
+// Fcb. It denotes the name which was used to go from the scb to the fcb
+// and it also contains a queue of ccbs that have opened the fcb via that
+// name and also a queue of Prefix Entries that will get us to this lcb
+//
+
+typedef struct _OVERLAY_LCB {
+
+ //
+ // We will need a FILE_NAME_ATTR in order to lookup the entry
+ // for the UpdateDuplicateInfo calls. We would like to keep
+ // one around but there are 0x38 bytes in it which will be unused.
+ // Instead we will overlay the Lcb with one of these. Then we can
+ // store other data within the unused bytes.
+ //
+ // NOTE - This will save an allocation but the sizes much match exactly
+ // or the name will be in the wrong location. This structure will
+ // overlay a FILE_NAME attribute exactly. The fields below which are
+ // prefaced with 'Overlay' correspond to the fields in the filename
+ // attribute which are being used with this structure.
+ //
+
+ FILE_REFERENCE OverlayParentDirectory;
+
+ //
+ // A queue of Ccbs that have the Fcb (via this edge) opened.
+ // Corresponds to Ccb->LcbLinks
+ //
+
+ LIST_ENTRY CcbQueue;
+
+ //
+ // The following is the case-sensitive name link.
+ //
+
+ NAME_LINK ExactCaseLink;
+
+ //
+ // The following are used to perform the lookup for the update
+ // duplicate information.
+ //
+
+ PFILE_NAME FileNameAttr;
+
+ QUICK_INDEX QuickIndex;
+
+ UCHAR OverlayFileNameLength;
+
+ UCHAR OverlayFlags;
+
+ WCHAR OverlayFileName[1];
+
+} OVERLAY_LCB, *POVERLAY_LCB;
+
+typedef struct _LCB {
+
+ //
+ // Type and size of this record must be NTFS_NTC_LCB
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // Internal state of the Lcb
+ //
+
+ ULONG LcbState;
+
+ //
+ // The links for all the Lcbs that emminate out of an Scb and a
+ // pointer back to the Scb. Corresponds to Scb->LcbQueue.
+ //
+
+ LIST_ENTRY ScbLinks;
+ struct _SCB *Scb;
+
+ //
+ // This is the number of unclean handles on this link.
+ //
+
+ ULONG CleanupCount;
+
+ //
+ // The links for all the Lcbs that go into an Fcb and a pointer
+ // back to the Fcb. Corresponds to Fcb->LcbQueue.
+ //
+
+ LIST_ENTRY FcbLinks;
+ struct _FCB *Fcb;
+
+ //
+ // This is the number of references to this link. The parent
+ // Scb must be owned to modify this count.
+ //
+
+ ULONG ReferenceCount;
+
+ //
+ // The following is the case-insensitive name link.
+ //
+
+ NAME_LINK IgnoreCaseLink;
+
+ //
+ // These are the flags for the changes to this link and the
+ // change count for the duplicated information on this link.
+ //
+
+ ULONG InfoFlags;
+
+ //
+ // We will need a FILE_NAME_ATTR in order to lookup the entry
+ // for the UpdateDuplicateInfo calls. We would like to keep
+ // one around but there are 0x38 bytes in it which will be unused.
+ // Instead we will overlay the Lcb with one of these. Then we can
+ // store other data within the unused bytes.
+ //
+ // NOTE - This will save an allocation but the sizes much match exactly
+ // or the name will be in the wrong location.
+ //
+
+ union {
+
+ FILE_NAME;
+ OVERLAY_LCB;
+
+ };
+
+} LCB;
+typedef LCB *PLCB;
+
+#define LCB_STATE_DELETE_ON_CLOSE (0x00000001)
+#define LCB_STATE_LINK_IS_GONE (0x00000002)
+#define LCB_STATE_EXACT_CASE_IN_TREE (0x00000004)
+#define LCB_STATE_IGNORE_CASE_IN_TREE (0x00000008)
+
+#define LcbSplitPrimaryLink( LCB ) \
+ ((LCB)->FileNameAttr->Flags == FILE_NAME_NTFS \
+ || (LCB)->FileNameAttr->Flags == FILE_NAME_DOS )
+
+#define LcbSplitPrimaryComplement( LCB ) \
+ (((LCB)->FileNameAttr->Flags == FILE_NAME_NTFS) ? \
+ FILE_NAME_DOS : FILE_NAME_NTFS)
+
+#define LcbLinkIsDeleted( LCB ) \
+ ((FlagOn( (LCB)->LcbState, LCB_STATE_DELETE_ON_CLOSE )) \
+ || ((FlagOn( (LCB)->FileNameAttr->Flags, FILE_NAME_DOS | FILE_NAME_NTFS )) \
+ && (FlagOn((LCB)->Fcb->FcbState,FCB_STATE_PRIMARY_LINK_DELETED ))))
+
+#define SIZEOF_LCB (FIELD_OFFSET( LCB, FileName ) + sizeof( WCHAR ))
+
+
+//
+// The Fcb record corresponds to every open file and directory, and to
+// every directory on an opened path.
+//
+// The structure is really divided into two parts. FCB can be allocated
+// from paged pool while the SCB must be allocated from non-paged
+// pool. There is an SCB for every file stream associated with the Fcb.
+//
+// Note that the Fcb, multiple Scb records all use the same resource so
+// if we need to grab exclusive access to the Fcb we only need to grab
+// one resource and we've blocked all the scbs
+//
+
+typedef struct _FCB {
+
+ //
+ // Type and size of this record must be NTFS_NTC_FCB
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // A pointer to the Vcb containing this Fcb
+ //
+
+ PVCB Vcb;
+
+ //
+ // The following field contains the file reference for the Fcb
+ //
+
+ FILE_REFERENCE FileReference;
+
+ //
+ // A count of the number of file objects that have been opened for
+ // this file, but not yet been cleaned up yet.
+ // This count gets decremented in NtfsCommonCleanup,
+ // while the CloseCount below gets decremented in NtfsCommonClose.
+ // Sync: Vcb X | Vcb S and Fcb X.
+ //
+
+ CLONG CleanupCount;
+
+ //
+ // A count of the number of file objects that have opened
+ // this file.
+ // Sync: Use InterlockedIncrement/Decrement to change. Critical users
+ // have Vcb X | Vcb S and Fcb X. Other callers will temporarily increment
+ // and decrement this value but they always start at a non-zero value.
+ //
+
+ CLONG CloseCount;
+
+ //
+ // A count of other references to this Fcb.
+ // Sync: Use the FcbTable mutex in Vcb.
+ //
+
+ CLONG ReferenceCount;
+
+ //
+ // The internal state of the Fcb.
+ // Sync: Some flags are set on creation and then left. Safe to test at any time.
+ // Otherwise use Fcb X | Fcb S with Fcb mutex to change. Critical flags which
+ // reflect state of file (DELETED, etc) will always be changed with Fcb X.
+ //
+
+ ULONG FcbState;
+
+ //
+ // Relevant counts for delete checking.
+ //
+
+ ULONG FcbDenyDelete;
+ ULONG FcbDeleteFile;
+
+ //
+ // The Queue of all the Lcb that we are part of. The list is
+ // actually ordered in a small sense. The get next scb routine that
+ // traverses the Fcb/Scb graph always will put the current lcb edge
+ // that it is traversing into the front of this queue.
+ //
+
+ LIST_ENTRY LcbQueue;
+
+ //
+ // A queue of Scb associated with the fcb. Corresponds to Scb->FcbLinks.
+ // Sync: Must own Fcb X | Fcb S with FcbMutex.
+ //
+
+ LIST_ENTRY ScbQueue;
+
+ //
+ // These are the links for the list of exclusively-owned Scbs off of
+ // the IrpContext. We need to keep track of the exclusive count
+ // in the Fcb before our acquire so we know how many times to release
+ // it.
+ //
+
+ LIST_ENTRY ExclusiveFcbLinks;
+
+ //
+ // The following field is used to store a pointer to the resource
+ // protecting the Fcb
+ //
+
+ PERESOURCE Resource;
+
+ //
+ // This is the count of the number of times the current transaction
+ // has acquired the main resource.
+ //
+
+ USHORT BaseExclusiveCount;
+
+ //
+ // This counts the number of times the Ea's on this file have been
+ // modified.
+ //
+
+ USHORT EaModificationCount;
+
+ //
+ // The following field contains a pointer to the resource
+ // synchronizing a changing FileSize with paging Io.
+ //
+
+ PERESOURCE PagingIoResource;
+
+ //
+ // Copy of the duplicated information for this Fcb.
+ // Also a flags field to tell us what has changed in the structure.
+ //
+
+ ULONG InfoFlags;
+ DUPLICATED_INFORMATION Info;
+ USHORT LinkCount;
+ USHORT TotalLinks;
+
+ //
+ // This is the actual last access for the main stream of this file.
+ // We don't store it in the duplicated information until we are ready
+ // to write it out to disk. Whenever we update the standard
+ // information we will copy the data out of the this field into the
+ // duplicate information.
+ //
+
+ LONGLONG CurrentLastAccess;
+
+ //
+ // The following fields contains a pointer to the security descriptor
+ // for this file. The field can start off null and later be loaded
+ // in by any of the security support routines. On delete Fcb the
+ // field pool should be deallocated when the fcb goes away
+ //
+
+ struct _SHARED_SECURITY *SharedSecurity;
+ ULONG CreateSecurityCount;
+
+ //
+ // This is a pointer to a shared security descriptor for
+ // a non-index child of this directory. Ignored for non-directory
+ // files.
+ //
+
+ struct _SHARED_SECURITY *ChildSharedSecurity;
+
+ //
+ // This is the count of file objects for this Fcb on the delayed
+ // close queue. Used to determine whether we need to dereference
+ // a file object we create in the compressed write path.
+ // Synchronize this field with the interlocked routines.
+ //
+
+ ULONG DelayedCloseCount;
+
+ //
+ // Fast Mutex used to synchronize access to Fcb fields. This is also used as
+ // the fast mutex for the individual Scb's except for those that may need their
+ // own (Mft, PagingFile, AttributeList).
+ //
+
+ PFAST_MUTEX FcbMutex;
+
+#ifdef _CAIRO_
+
+ //
+ // Class Id from the bidirectional Class Id index
+ //
+
+ ULONG ClassId;
+
+ //
+ // Id for file owner, from bidir security index
+ //
+
+ ULONG OwnerId;
+
+ //
+ // SecurityId for the file - translates via bidir index to
+ // granted access Acl.
+ //
+
+ ULONG SecurityId;
+
+ //
+ // Pointer to the Quota Control block for the file.
+ //
+
+ struct _QUOTA_CONTROL_BLOCK *QuotaControl;
+
+ //
+ // Update sequence number for this file.
+ //
+
+ ULONGLONG Usn;
+
+#endif _CAIRO_
+} FCB;
+typedef FCB *PFCB;
+
+#define FCB_STATE_FILE_DELETED (0x00000001)
+#define FCB_STATE_NONPAGED (0x00000002)
+#define FCB_STATE_PAGING_FILE (0x00000004)
+#define FCB_STATE_DUP_INITIALIZED (0x00000008)
+#define FCB_STATE_UPDATE_STD_INFO (0x00000010)
+#define FCB_STATE_PRIMARY_LINK_DELETED (0x00000020)
+#define FCB_STATE_IN_FCB_TABLE (0x00000040)
+#define FCB_STATE_SYSTEM_FILE (0x00000100)
+#define FCB_STATE_COMPOUND_DATA (0x00000200)
+#define FCB_STATE_COMPOUND_INDEX (0x00000400)
+#define FCB_STATE_LARGE_STD_INFO (0x00000800)
+#define FCB_STATE_MODIFIED_SECURITY (0x00001000)
+
+#define FCB_INFO_CHANGED_CREATE FILE_NOTIFY_CHANGE_CREATION // (0x00000040)
+#define FCB_INFO_CHANGED_LAST_MOD FILE_NOTIFY_CHANGE_LAST_WRITE // (0x00000010)
+#define FCB_INFO_CHANGED_LAST_CHANGE (0x80000000)
+#define FCB_INFO_CHANGED_LAST_ACCESS FILE_NOTIFY_CHANGE_LAST_ACCESS // (0x00000020)
+#define FCB_INFO_CHANGED_ALLOC_SIZE (0x40000000)
+#define FCB_INFO_CHANGED_FILE_SIZE FILE_NOTIFY_CHANGE_SIZE // (0x00000008)
+#define FCB_INFO_CHANGED_FILE_ATTR FILE_NOTIFY_CHANGE_ATTRIBUTES // (0x00000004)
+#define FCB_INFO_CHANGED_EA_SIZE FILE_NOTIFY_CHANGE_EA // (0x00000080)
+
+#define FCB_INFO_MODIFIED_SECURITY FILE_NOTIFY_CHANGE_SECURITY // (0x00000100)
+
+//
+// Subset of the Fcb Info flags used to track duplicate info.
+//
+
+#define FCB_INFO_DUPLICATE_FLAGS (FCB_INFO_CHANGED_CREATE | \
+ FCB_INFO_CHANGED_LAST_MOD | \
+ FCB_INFO_CHANGED_LAST_CHANGE | \
+ FCB_INFO_CHANGED_LAST_ACCESS | \
+ FCB_INFO_CHANGED_ALLOC_SIZE | \
+ FCB_INFO_CHANGED_FILE_SIZE | \
+ FCB_INFO_CHANGED_FILE_ATTR | \
+ FCB_INFO_CHANGED_EA_SIZE )
+
+//
+// Subset of the Fcb Info flags used to track notifies.
+//
+
+#define FCB_INFO_NOTIFY_FLAGS (FCB_INFO_CHANGED_CREATE | \
+ FCB_INFO_CHANGED_LAST_MOD | \
+ FCB_INFO_CHANGED_LAST_ACCESS | \
+ FCB_INFO_CHANGED_ALLOC_SIZE | \
+ FCB_INFO_CHANGED_FILE_SIZE | \
+ FCB_INFO_CHANGED_FILE_ATTR | \
+ FCB_INFO_CHANGED_EA_SIZE | \
+ FCB_INFO_MODIFIED_SECURITY )
+
+//
+// Subset of the Fcb Info flags used to track notifies. The allocation flag
+// is removed from the full notify flags because dir notify overloads
+// the file size flag for allocation and file size.
+//
+
+#define FCB_INFO_VALID_NOTIFY_FLAGS (FCB_INFO_CHANGED_CREATE | \
+ FCB_INFO_CHANGED_LAST_MOD | \
+ FCB_INFO_CHANGED_LAST_ACCESS | \
+ FCB_INFO_CHANGED_FILE_SIZE | \
+ FCB_INFO_CHANGED_FILE_ATTR | \
+ FCB_INFO_CHANGED_EA_SIZE | \
+ FCB_INFO_MODIFIED_SECURITY )
+
+#define FCB_CREATE_SECURITY_COUNT (5)
+#define FCB_LARGE_ACL_SIZE (512)
+
+
+//
+// The following three structures are the separate union structures for
+// Scb structure.
+//
+
+typedef struct _SCB_DATA {
+
+ //
+ // Total number of reserved bytes
+ //
+
+ LONGLONG TotalReserved;
+
+ //
+ // Mask to remember which compression units were written
+ // in a stream.
+ //
+
+#ifdef SYSCACHE
+ PULONG WriteMask;
+ ULONG WriteSequence;
+#endif
+
+ //
+ // The following field is used by the oplock module
+ // to maintain current oplock information.
+ //
+
+ OPLOCK Oplock;
+
+ //
+ // The following field is used by the filelock module
+ // to maintain current byte range locking information.
+ //
+
+ PFILE_LOCK FileLock;
+
+ //
+ // Pointer to an Mcb describing the reserved space for
+ // dirty compression units in compressed files which do
+ // not currently have a user section.
+ //
+
+ PRTL_BITMAP ReservedBitMap;
+
+ ULONG PadUlong;
+
+} SCB_DATA, *PSCB_DATA;
+
+typedef struct _SCB_INDEX {
+
+ //
+ // This is a list of records within the index allocation stream which
+ // have been deallocated in the current transaction.
+ //
+
+ LIST_ENTRY RecentlyDeallocatedQueue;
+
+ //
+ // A queue of all the lcbs that are opened under this Scb.
+ // Corresponds to Lcb->ScbLinks
+ //
+
+ LIST_ENTRY LcbQueue;
+
+ //
+ // Record allocation context, for managing the allocation of the
+ // INDEX_ALLOCATION_ATTRIBUTE, if one exists.
+ //
+
+ RECORD_ALLOCATION_CONTEXT RecordAllocationContext;
+
+ //
+ // The following are the splay links of Lcbs opened under this
+ // Scb. Note that not all of the Lcb in the list above may
+ // be in the splay links below.
+ //
+
+ PRTL_SPLAY_LINKS ExactCaseNode;
+ PRTL_SPLAY_LINKS IgnoreCaseNode;
+
+ //
+ // Normalized name. This is the path from the root to this directory
+ // without any of the short-name-only links.
+ //
+
+ UNICODE_STRING NormalizedName;
+
+ //
+ // A change count incremented every time an index buffer is deleted.
+ //
+
+ ULONG ChangeCount;
+
+ //
+ // Define a union to distinguish directory indices from view indices
+ //
+
+ union {
+
+ //
+ // For directories we store the following.
+ //
+
+ struct {
+
+ //
+ // Type of attribute being indexed.
+ //
+
+ ATTRIBUTE_TYPE_CODE AttributeBeingIndexed;
+
+ //
+ // Collation rule, for how the indexed attribute is collated.
+ //
+
+ ULONG CollationRule;
+ };
+
+ //
+ // For view indexes we store the CollationFunction and data.
+ //
+
+#ifdef _CAIRO_
+ struct {
+
+ PCOLLATION_FUNCTION CollationFunction;
+ PVOID CollationData;
+ };
+#endif
+ };
+
+ //
+ // Size of Index Allocation Buffer in bytes, or 0 if not yet
+ // initialized.
+ //
+
+ ULONG BytesPerIndexBuffer;
+
+ //
+ // Size of Index Allocation Buffers in units of blocks, or 0
+ // if not yet initialized.
+ //
+
+ UCHAR BlocksPerIndexBuffer;
+
+ //
+ // Shift value when converting from index blocks to bytes.
+ //
+
+ UCHAR IndexBlockByteShift;
+
+ //
+ // Flag to indicate whether the RecordAllocationContext has been
+ // initialized or not. If it is not initialized, this means
+ // either that there is no external index allocation, or that
+ // it simply has not been initialized yet.
+ //
+
+ BOOLEAN AllocationInitialized;
+
+ UCHAR PadUchar;
+
+ //
+ // Index Depth Hint
+ //
+
+ USHORT IndexDepthHint;
+
+ USHORT PadUshort;
+
+} SCB_INDEX, *PSCB_INDEX;
+
+typedef struct _SCB_MFT {
+
+ //
+ // This is a list of records within the Mft Scb stream which
+ // have been deallocated in the current transaction.
+ //
+
+ LIST_ENTRY RecentlyDeallocatedQueue;
+
+ //
+ // The following Mcb's are used to track clusters being added and
+ // removed from the Mcb for the Scb. This Scb must always be fully
+ // loaded after an abort. We can't depend on reloading on the next
+ // LookupAllocation call. Instead we keep one Mcb with the clusters
+ // added and one Mcb with the clusters removed. During the restore
+ // phase of abort we will adjust the Mft Mcb by reversing the
+ // operations done during the transactions.
+ //
+
+ LARGE_MCB AddedClusters;
+ LARGE_MCB RemovedClusters;
+
+ //
+ // The following are the changes made to the Mft file as file records
+ // are added, freed or allocated. Also the change in the number of
+ // file records which are part of holes.
+ //
+
+ LONG FreeRecordChange;
+ LONG HoleRecordChange;
+
+ //
+ // The following field contains index of a reserved free record. To
+ // keep us out of the chicken & egg problem of the Mft being able to
+ // be self mapping we added the ability to reserve an mft record
+ // to describe additional mft data allocation within previous mft
+ // run. A value of zero means that index has not been reserved.
+ //
+
+ ULONG ReservedIndex;
+
+ ULONG PadUlong;
+
+} SCB_MFT, *PSCB_MFT;
+
+//
+// The following is the non-paged part of the scb.
+//
+
+typedef struct _SCB_NONPAGED {
+
+ //
+ // Type and size of this record must be NTFS_NTC_SCB_NONPAGED
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // Index allocated for this file in the Open Attribute Table.
+ //
+
+ ULONG OpenAttributeTableIndex;
+
+ //
+ // The following field contains a record of special pointers used by
+ // MM and Cache to manipluate section objects. Note that the values
+ // are set outside of the file system. However the file system on an
+ // open/create will set the file object's SectionObject field to
+ // point to this field
+ //
+
+ SECTION_OBJECT_POINTERS SegmentObject;
+
+ //
+ // Copy of the Vcb pointer so we can find the Vcb in the dirty page
+ // callback routine.
+ //
+
+ PVCB Vcb;
+ SECTION_OBJECT_POINTERS SegmentObjectC;
+
+} SCB_NONPAGED, *PSCB_NONPAGED;
+
+
+//
+// The following structure is the stream control block. There can
+// be multiple records per fcb. One is created for each attribute being
+// handled as a stream file.
+//
+
+typedef struct _SCB {
+
+ //
+ // The following field is used for fast I/O. It contains the node
+ // type code and size, indicates if fast I/O is possible, contains
+ // allocation, file, and valid data size, a resource, and call back
+ // pointers for FastIoRead and FastMdlRead.
+ //
+ // The node type codes for the Scb must be either NTFS_NTC_SCB_INDEX,
+ // NTFS_NTC_SCB_ROOT_INDEX, or NTFS_NTC_SCB_DATA. Which one it is
+ // determines the state of the union below.
+ //
+
+ FSRTL_ADVANCED_FCB_HEADER Header;
+
+ //
+ // The links for the queue of Scb off of a given Fcb. And a pointer
+ // back to the Fcb. Corresponds to Fcb->ScbQueue
+ //
+
+ LIST_ENTRY FcbLinks;
+ PFCB Fcb;
+
+ //
+ // A pointer to the Vcb containing this Scb
+ //
+
+ PVCB Vcb;
+
+ //
+ // The internal state of the Scb.
+ //
+
+ ULONG ScbState;
+
+ //
+ // A count of the number of file objects opened on this stream
+ // which represent user non-cached handles. We use this count to
+ // determine when to flush and purge the data section in only
+ // non-cached handles remain on the file.
+ //
+
+ CLONG NonCachedCleanupCount;
+
+ //
+ // A count of the number of file objects that have been opened for
+ // this attribute, but not yet been cleaned up yet.
+ // This count gets decremented in NtfsCommonCleanup,
+ // while the CloseCount below gets decremented in NtfsCommonClose.
+ //
+
+ CLONG CleanupCount;
+
+ //
+ // A count of the number of file objects that have opened
+ // this attribute.
+ //
+
+ CLONG CloseCount;
+
+ //
+ // Share Access structure for this stream.
+ //
+
+ SHARE_ACCESS ShareAccess;
+
+ //
+ // The following two fields identify the actual attribute for this
+ // Scb with respect to its file. We identify the attribute by
+ // its type code and name.
+ //
+
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ UNICODE_STRING AttributeName;
+
+ //
+ // Stream File Object for internal use. This field is NULL if the
+ // file stream is not being accessed internally.
+ //
+
+ PFILE_OBJECT FileObject;
+
+ //
+ // These pointers are used to detect writes that eminated from the
+ // cache manager's worker thread. It prevents lazy writer threads,
+ // who already have the Fcb shared, from trying to acquire it
+ // exclusive, and thus causing a deadlock. We have to store two
+ // threads, because the second thread could be writing the compressed
+ // stream
+ //
+
+ PVOID LazyWriteThread[2];
+
+ //
+ // Pointer to the non-paged section objects and open attribute
+ // table index.
+ //
+
+ PSCB_NONPAGED NonpagedScb;
+
+ //
+ // The following field contains the mcb for this Scb and some initial
+ // structures for small and medium files.
+ //
+
+ NTFS_MCB Mcb;
+ NTFS_MCB_INITIAL_STRUCTS McbStructs;
+
+ //
+ // Compression unit from attribute record.
+ //
+
+ ULONG CompressionUnit;
+
+ //
+ // AttributeFlags and CompressionUnitShift from attribute record
+ //
+
+ USHORT AttributeFlags;
+ UCHAR CompressionUnitShift;
+ UCHAR PadUchar;
+
+ //
+ // Valid Data to disk - as updated by NtfsPrepareBuffers
+ //
+
+ LONGLONG ValidDataToDisk;
+
+ //
+ // Number of clusters added due to Split Mcb calls. The user has
+ // not asked for this allocation.
+ //
+
+ LONGLONG ExcessFromSplitMcb;
+
+ //
+ // Actual allocated bytes for this file.
+ //
+
+ LONGLONG TotalAllocated;
+
+ //
+ // Used by advanced Scb Header
+ //
+
+ LIST_ENTRY EofListHead;
+
+ //
+ // Defragmentation parameters
+ //
+
+ union {
+
+ PMOVE_FILE_DATA MoveData;
+
+ } Union;
+
+ //
+ // Pointer to structure containing snapshotted Scb values, or NULL
+ // if the values have not been snapshotted.
+ //
+
+ struct _SCB_SNAPSHOT * ScbSnapshot;
+ ULONG PadUlong;
+
+ //
+ // Scb Type union, for different types of Scbs
+ //
+
+ union {
+
+ SCB_DATA Data;
+ SCB_INDEX Index;
+ SCB_MFT Mft;
+
+ } ScbType;
+
+} SCB;
+typedef SCB *PSCB;
+
+#define SIZEOF_SCB_DATA \
+ (FIELD_OFFSET(SCB,ScbType)+sizeof(SCB_DATA))
+
+#define SIZEOF_SCB_INDEX \
+ (FIELD_OFFSET(SCB,ScbType)+sizeof(SCB_INDEX))
+
+#define SIZEOF_SCB_MFT \
+ (FIELD_OFFSET(SCB,ScbType)+sizeof(SCB_MFT))
+
+#define SCB_STATE_TRUNCATE_ON_CLOSE (0x00000001)
+#define SCB_STATE_DELETE_ON_CLOSE (0x00000002)
+#define SCB_STATE_CHECK_ATTRIBUTE_SIZE (0x00000004)
+#define SCB_STATE_ATTRIBUTE_RESIDENT (0x00000008)
+#define SCB_STATE_UNNAMED_DATA (0x00000010)
+#define SCB_STATE_HEADER_INITIALIZED (0x00000020)
+#define SCB_STATE_NONPAGED (0x00000040)
+#define SCB_STATE_USA_PRESENT (0x00000080)
+#define SCB_STATE_ATTRIBUTE_DELETED (0x00000100)
+#define SCB_STATE_FILE_SIZE_LOADED (0x00000200)
+#define SCB_STATE_MODIFIED_NO_WRITE (0x00000400)
+#define SCB_STATE_QUOTA_ENLARGED (0x00000800)
+#define SCB_STATE_SUBJECT_TO_QUOTA (0x00001000)
+#define SCB_STATE_UNINITIALIZE_ON_RESTORE (0x00002000)
+#define SCB_STATE_RESTORE_UNDERWAY (0x00004000)
+#define SCB_STATE_NOTIFY_ADD_STREAM (0x00008000)
+#define SCB_STATE_NOTIFY_REMOVE_STREAM (0x00010000)
+#define SCB_STATE_NOTIFY_RESIZE_STREAM (0x00020000)
+#define SCB_STATE_NOTIFY_MODIFY_STREAM (0x00040000)
+#define SCB_STATE_TEMPORARY (0x00080000)
+#define SCB_STATE_COMPRESSED (0x00100000)
+#define SCB_STATE_REALLOCATE_ON_WRITE (0x00200000)
+#define SCB_STATE_DELAY_CLOSE (0x00400000)
+#define SCB_STATE_WRITE_ACCESS_SEEN (0x00800000)
+#define SCB_STATE_CONVERT_UNDERWAY (0x01000000)
+#define SCB_STATE_VIEW_INDEX (0x02000000)
+#define SCB_STATE_DELETE_COLLATION_DATA (0x04000000)
+#define SCB_STATE_VOLUME_DISMOUNTED (0x08000000)
+
+#ifdef SYSCACHE
+#define SCB_STATE_SYSCACHE_FILE (0x80000000)
+#endif
+
+#define MAX_SCB_ASYNC_ACQUIRE (0xf000)
+
+
+//
+// Determine whether an attribute type code can be compressed. The current
+// implementation of Ntfs does not allow logged streams to be compressed.
+//
+
+#define NtfsIsTypeCodeCompressible(C) ((C) == $DATA)
+
+//
+// Detect whether an attribute contains user data
+//
+
+#ifndef _CAIRO_
+#define NtfsIsTypeCodeUserData(C) ((C) == $DATA)
+#else // _CAIRO_
+#define NtfsIsTypeCodeUserData(C) ((C) == $DATA || (C) == $PROPERTY_SET)
+#endif
+
+
+//
+// Detect whether an attribute should be subject to quota enforcement
+//
+
+#define NtfsIsTypeCodeSubjectToQuota(C) NtfsIsTypeCodeUserData(C)
+
+
+//
+// Define FileObjectFlags flags that should be propagated to stream files
+// so that the Cache Manager will see them.
+//
+
+#define NTFS_FO_PROPAGATE_TO_STREAM (FO_SEQUENTIAL_ONLY | FO_TEMPORARY_FILE)
+
+
+//
+// Structure to contain snapshotted Scb values for error recovery.
+//
+
+typedef struct _SCB_SNAPSHOT {
+
+ //
+ // Links for list snapshot structures off of IrpContext
+ //
+
+ LIST_ENTRY SnapshotLinks;
+
+ //
+ // Saved values of the corresponding Scb (or FsRtl Header) fields
+ // The low bit of allocation size is set to remember when the
+ // attribute was resident. The next bit, bit 1, is set to remember
+ // when the attribute was compressed.
+ //
+
+ LONGLONG AllocationSize;
+ LONGLONG FileSize;
+ LONGLONG ValidDataLength;
+ LONGLONG ValidDataToDisk;
+ LONGLONG TotalAllocated;
+
+ VCN LowestModifiedVcn;
+ VCN HighestModifiedVcn;
+
+ //
+ // Pointer to the Scb which has been snapped.
+ //
+
+ PSCB Scb;
+
+#ifdef _CAIRO_
+
+ //
+ // Used to hold the Scb State.
+ //
+
+ ULONG ScbState;
+
+#else
+
+ //
+ // For quadword & cache line alignment
+ //
+
+ ULONG Unused;
+
+#endif // _CAIRO_
+
+} SCB_SNAPSHOT;
+typedef SCB_SNAPSHOT *PSCB_SNAPSHOT;
+
+
+//
+// The Ccb record is allocated for every file object. This is the full
+// CCB including the index-specific fields.
+//
+
+typedef struct _CCB {
+
+ //
+ // Type and size of this record (must be NTFS_NTC_CCB_DATA or
+ // NTFS_NTC_CCB_INDEX)
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // Ccb flags.
+ //
+
+ ULONG Flags;
+
+ //
+ // This is a unicode string for the full filename used to
+ // open this file.
+ //
+
+ UNICODE_STRING FullFileName;
+ USHORT LastFileNameOffset;
+
+ //
+ // This is the Ccb Ea modification count. If this count is in
+ // sync with the Fcb value, then the above offset is valid.
+ //
+
+ USHORT EaModificationCount;
+
+ //
+ // This is the offset of the next Ea to return to the user.
+ //
+
+ ULONG NextEaOffset;
+
+ //
+ // The links for the queue of Ccb off of a given Lcb and a pointer
+ // back to the Lcb. Corresponds to Lcb->CcbQueue
+ //
+
+ LIST_ENTRY LcbLinks;
+ PLCB Lcb;
+
+ //
+ // Type of Open for this Ccb
+ //
+
+ UCHAR TypeOfOpen;
+ UCHAR PadBytes[3];
+
+#ifdef _CAIRO_
+
+ //
+ // Keeps the owner id of the opener. Used by quota to determine the
+ // amount of free space.
+ //
+
+ ULONG OwnerId;
+
+#endif // _CAIRO_
+
+ //////////////////////////////////////////////////////////////////////////
+ // //
+ // READ BELOW BEFORE CHANGING THIS STRUCTURE //
+ // //
+ // All of the enumeration fields must be after this point. Otherwise //
+ // we will waste space in the CCB_DATA defined below. //
+ // //
+ // Also - The first defined field past this point must be used in //
+ // defining the CCB_DATA structure below. //
+ // //
+ //////////////////////////////////////////////////////////////////////////
+
+ //
+ // Pointer to the index context structure for enumerations.
+ //
+
+ struct _INDEX_CONTEXT *IndexContext;
+
+ //
+ // The query template is used to filter directory query requests.
+ // It originally is set to null and on the first call the
+ // NtQueryDirectory it is set the the input filename or "*" if no
+ // name is supplied. All subsquent queries then use this template.
+ //
+
+ ULONG QueryLength;
+ PVOID QueryBuffer;
+
+ //
+ // The last returned value. A copy of an IndexEntry is saved. We
+ // only grow this buffer, to avoid always deallocating and
+ // reallocating.
+ //
+
+ ULONG IndexEntryLength;
+ PINDEX_ENTRY IndexEntry;
+
+ //
+ // File reference for file record we need to read for another name,
+ // and for which Fcb should be found and acquired when restarting
+ // an enumeration.
+ //
+
+ union {
+
+ LONGLONG LongValue;
+
+ FILE_REFERENCE FileReference;
+
+ } FcbToAcquire;
+
+} CCB;
+typedef CCB *PCCB;
+
+//
+// The size of the CCB_DATA is the quadaligned size of the common
+// header.
+//
+// NOTE - This define assumes the first field of the index portion is the
+// IndexContext field.
+//
+
+typedef struct _CCB_DATA {
+
+ LONGLONG Opaque[ (FIELD_OFFSET( CCB, IndexContext ) + 7) / 8 ];
+
+} CCB_DATA;
+typedef CCB_DATA *PCCB_DATA;
+
+#define CCB_FLAG_IGNORE_CASE (0x00000001)
+#define CCB_FLAG_OPEN_AS_FILE (0x00000002)
+#define CCB_FLAG_WILDCARD_IN_EXPRESSION (0x00000004)
+#define CCB_FLAG_OPEN_BY_FILE_ID (0x00000008)
+#define CCB_FLAG_USER_SET_LAST_MOD_TIME (0x00000010)
+#define CCB_FLAG_USER_SET_LAST_CHANGE_TIME (0x00000020)
+#define CCB_FLAG_USER_SET_LAST_ACCESS_TIME (0x00000040)
+#define CCB_FLAG_TRAVERSE_CHECK (0x00000080)
+
+#define CCB_FLAG_RETURN_DOT (0x00000100)
+#define CCB_FLAG_RETURN_DOTDOT (0x00000200)
+#define CCB_FLAG_DOT_RETURNED (0x00000400)
+#define CCB_FLAG_DOTDOT_RETURNED (0x00000800)
+
+#define CCB_FLAG_DELETE_FILE (0x00001000)
+#define CCB_FLAG_DENY_DELETE (0x00002000)
+
+#define CCB_FLAG_ALLOCATED_FILE_NAME (0x00004000)
+#define CCB_FLAG_CLEANUP (0x00008000)
+#define CCB_FLAG_SYSTEM_HIVE (0x00010000)
+
+#define CCB_FLAG_PARENT_HAS_DOS_COMPONENT (0x00020000)
+#define CCB_FLAG_DELETE_ON_CLOSE (0x00040000)
+#define CCB_FLAG_CLOSE (0x00080000)
+
+#define CCB_FLAG_UPDATE_LAST_MODIFY (0x00100000)
+#define CCB_FLAG_UPDATE_LAST_CHANGE (0x00200000)
+#define CCB_FLAG_SET_ARCHIVE (0x00400000)
+
+#define CCB_FLAG_DIR_NOTIFY (0x00800000)
+#define CCB_FLAG_ALLOW_XTENDED_DASD_IO (0x01000000)
+
+
+//
+// We will attempt to allocate the following out of a single pool block
+// on a per file basis.
+//
+// FCB, LCB, SCB, CCB, FILE_NAME
+//
+// The following compound Fcb's will be allocated and then the individual
+// components can be allocated out of them. The FCB will never be allocated
+// individually but it is possible that the embedded structures may be.
+// A zero in the node type field means these are available. These sizes are
+// selected to fill the Fcb out to a pool block boundary (0x20) bytes.
+// Note that we leave room for both the exact and ignore case names.
+//
+
+#define MAX_DATA_FILE_NAME (17)
+#define MAX_INDEX_FILE_NAME (17)
+
+typedef struct _FCB_DATA {
+
+ FCB Fcb;
+ UCHAR Scb[SIZEOF_SCB_DATA];
+ CCB_DATA Ccb;
+ UCHAR Lcb[SIZEOF_LCB];
+ WCHAR FileName[(2*MAX_DATA_FILE_NAME) - 1];
+
+} FCB_DATA;
+typedef FCB_DATA *PFCB_DATA;
+
+typedef struct _FCB_INDEX {
+
+ FCB Fcb;
+ UCHAR Scb[SIZEOF_SCB_INDEX];
+ CCB Ccb;
+ UCHAR Lcb[SIZEOF_LCB];
+ WCHAR FileName[(2*MAX_INDEX_FILE_NAME) - 1];
+
+} FCB_INDEX;
+typedef FCB_INDEX *PFCB_INDEX;
+
+
+typedef VOID
+(*POST_SPECIAL_CALLOUT) (
+ IN struct _IRP_CONTEXT *IrpContext,
+ IN OUT PVOID Context
+ );
+
+//
+// The Irp Context record is allocated for every orginating Irp. It is
+// created by the Fsd dispatch routines, and deallocated by the
+// NtfsComplete request routine.
+//
+
+typedef struct _IRP_CONTEXT {
+
+ //
+ // Type and size of this record (must be NTFS_NTC_IRP_CONTEXT)
+ //
+ // Assumption here is that this structure is allocated from pool so
+ // base of structure is on an odd 64-bit boundary.
+ //
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ //
+ // Irp Context flags
+ //
+
+ ULONG Flags;
+
+ //
+ // The following field contains the NTSTATUS value used when we are
+ // unwinding due to an exception. We will temporarily store the Ccb
+ // for a delayed or deferred close here while the request is queued.
+ //
+
+ NTSTATUS ExceptionStatus;
+
+ //
+ // Transaction Id for this request, which must be qualified by Vcb.
+ // We will store the type of open for a delayed or async close here
+ // while the request is queued.
+ //
+
+ TRANSACTION_ID TransactionId;
+ PVCB Vcb;
+
+ //
+ // This is the IrpContext for the top level request.
+ //
+
+ struct _IRP_CONTEXT *TopLevelIrpContext;
+
+ //
+ // The following union contains pointers to the IoContext for I/O
+ // based requests and a pointer to a security context for requests
+ // which need to capture the subject context in the calling thread.
+ //
+
+ union {
+
+
+ // The following context block is used for non-cached Io.
+
+ struct _NTFS_IO_CONTEXT *NtfsIoContext;
+
+ // The following field is used for cached compressed reads/writes
+
+ PFSRTL_AUXILIARY_BUFFER AuxiliaryBuffer;
+
+ // The following is the captured subject context.
+
+ PSECURITY_SUBJECT_CONTEXT SubjectContext;
+
+ // The following is used during create for oplock cleanup.
+
+ struct _OPLOCK_CLEANUP *OplockCleanup;
+
+ // The following is used by NtfsPostSpecial to pass the
+ // function to be called.
+
+ POST_SPECIAL_CALLOUT PostSpecialCallout;
+
+ } Union;
+
+ //
+ // A pointer to the originating Irp. We will store the Scb for
+ // delayed or async closes here while the request is queued.
+ //
+
+ PIRP OriginatingIrp;
+
+ //
+ // Major and minor function codes copied from the Irp
+ //
+
+ UCHAR MajorFunction;
+ UCHAR MinorFunction;
+
+ //
+ // Length of Scb array for transactions below. Zero indicates unused. One indicates
+ // to treat it as a pointer to an Scb. Greater than one indicates it is an allocated
+ // pool block with an array of Scb's.
+ //
+
+ USHORT SharedScbSize;
+
+ //
+ // Pointer to Scb acquired shared for transaction or pointer to array of Scb's acquired
+ // shared for transaction. Use the SharedScbSize field above to determine
+ // meaning of this pointer.
+ //
+
+ PVOID SharedScb;
+
+ //
+ // This is a list of exclusively-owned Scbs which may only be
+ // released after the transaction is committed.
+ //
+
+ LIST_ENTRY ExclusiveFcbList;
+
+ //
+ // The following field is used to maintain a queue of records that
+ // have been deallocated while processing this irp context.
+ //
+
+ LIST_ENTRY RecentlyDeallocatedQueue;
+
+ //
+ // The following is the number of clusters deallocated in the current
+ // request. We want to ignore them when figuring if a request for
+ // clusters (NtfsAllocateClusters) should free the clusters in the
+ // recently deallocated queue.
+ //
+
+ LONGLONG DeallocatedClusters;
+
+
+ //
+ // This structure contains the first ScbSnapshot for a modifying
+ // request which acquires files exclusive and snaps Scb values.
+ // If the SnapshotLinks field contains NULLs, then no data has
+ // been snapshot for this request, and the list is empty. If
+ // the links are not NULL, then this snapshot structure is in
+ // use. If the SnapshotLinks are not NULL, and do not represent
+ // an empty list, then there are addtional dynamically allocated
+ // snapshot structures in this list.
+ //
+
+ SCB_SNAPSHOT ScbSnapshot;
+
+ //
+ // This is the Last Restart Area Lsn captured from the Vcb at
+ // the time log file full was raised. The caller will force
+ // a checkpoint if this has not changed by the time he gets
+ // the global resource exclusive.
+ //
+
+ LSN LastRestartArea;
+
+ //
+ // This structure is used for posting to the Ex worker threads.
+ //
+
+ WORK_QUEUE_ITEM WorkQueueItem;
+
+ //
+ // This is the change in the free clusters on the volume during the
+ // transaction for this IrpContext. If we abort the current request
+ // we will subtract these from the current count of free clusters
+ // in the Vcb. This is signed because we may be allocating or
+ // deallocating the clusters.
+ //
+
+ LONGLONG FreeClusterChange;
+
+ //
+ // This is a pointer to the Fcb whose paging I/O resource is held
+ // exclusively in order to change FileSize or ValidDataLength fields
+ // in the Scb.
+ //
+
+ PFCB FcbWithPagingExclusive;
+
+ //
+ // Originating Device (required for workque algorithms)
+ //
+
+ PDEVICE_OBJECT RealDevice;
+
+#if NTFSDBG
+ //
+ // Debugging field for breadth-first verification of log-file-full. When the
+ // NextFailCount is non-zero, we decrement the CurrentFailCount. When
+ // CurrentFailCount goes to zero, we increment NextFailCount, set
+ // CurrentFailCount to NextFailCount and raise STATUS_LOG_FILE_FULL.
+ //
+
+ ULONG CurrentFailCount;
+ ULONG NextFailCount;
+#endif
+
+} IRP_CONTEXT;
+typedef IRP_CONTEXT *PIRP_CONTEXT;
+
+
+#define IRP_CONTEXT_FLAG_EXCESS_LOG_FULL (0x00000001)
+#define IRP_CONTEXT_FLAG_WROTE_LOG (0x00000002)
+#define IRP_CONTEXT_FLAG_WAIT (0x00000004)
+#define IRP_CONTEXT_FLAG_WRITE_THROUGH (0x00000008)
+#define IRP_CONTEXT_LARGE_ALLOCATION (0x00000010)
+#define IRP_CONTEXT_DEFERRED_WRITE (0x00000020)
+#define IRP_CONTEXT_FLAG_ALLOC_CONTEXT (0x00000040)
+#define IRP_CONTEXT_FLAG_ALLOC_SECURITY (0x00000080)
+#define IRP_CONTEXT_MFT_RECORD_15_USED (0x00000100)
+#define IRP_CONTEXT_MFT_RECORD_RESERVED (0x00000200)
+#define IRP_CONTEXT_FLAG_IN_FSP (0x00000400)
+#define IRP_CONTEXT_FLAG_RAISED_STATUS (0x00000800)
+#define IRP_CONTEXT_FLAG_IN_TEARDOWN (0x00001000)
+#define IRP_CONTEXT_FLAG_ACQUIRE_VCB_EX (0x00002000)
+#define IRP_CONTEXT_FLAG_CALL_SELF (0x00004000)
+#define IRP_CONTEXT_FLAG_DONT_DELETE (0x00008000)
+#define IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY (0x00010000)
+#define IRP_CONTEXT_FLAG_FORCE_POST (0X00020000)
+#define IRP_CONTEXT_FLAG_WRITE_SEEN (0X00040000)
+#define IRP_CONTEXT_FLAG_MODIFIED_BITMAP (0x00080000)
+#define IRP_CONTEXT_FLAG_DASD_OPEN (0x00100000)
+#define IRP_CONTEXT_FLAG_QUOTA_DISABLE (0x00200000)
+#define IRP_CONTEXT_FLAG_CHECKPOINT_ACTIVE (0x01000000)
+
+//
+// The following flags are request specific and should never be tested in
+// any of the general routines.
+//
+
+//
+// Following flags are for create only.
+//
+
+#define IRP_CONTEXT_FLAG_ACQUIRE_PAGING (0x80000000)
+#define IRP_CONTEXT_FLAG_CREATE_MOD_SCB (0x40000000)
+
+//
+// Following flags are for close only.
+//
+
+#define IRP_CONTEXT_FLAG_READ_ONLY_FO (0x80000000)
+
+//
+// The following flags need to be cleared when a request is posted.
+//
+
+#define IRP_CONTEXT_FLAGS_CLEAR_ON_POST \
+ (IRP_CONTEXT_FLAG_EXCESS_LOG_FULL | \
+ IRP_CONTEXT_LARGE_ALLOCATION | \
+ IRP_CONTEXT_MFT_RECORD_15_USED | \
+ IRP_CONTEXT_MFT_RECORD_RESERVED | \
+ IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY | \
+ IRP_CONTEXT_FLAG_RAISED_STATUS | \
+ IRP_CONTEXT_FLAG_WRITE_SEEN | \
+ IRP_CONTEXT_FLAG_MODIFIED_BITMAP | \
+ IRP_CONTEXT_FLAG_WROTE_LOG | \
+ IRP_CONTEXT_FLAG_FORCE_POST | \
+ IRP_CONTEXT_FLAG_CALL_SELF | \
+ IRP_CONTEXT_FLAG_CREATE_MOD_SCB | \
+ IRP_CONTEXT_FLAG_ACQUIRE_PAGING )
+
+//
+// The following flags need to be cleared when a request is retried.
+//
+
+#define IRP_CONTEXT_FLAGS_CLEAR_ON_RETRY \
+ (IRP_CONTEXT_FLAG_EXCESS_LOG_FULL | \
+ IRP_CONTEXT_LARGE_ALLOCATION | \
+ IRP_CONTEXT_DEFERRED_WRITE | \
+ IRP_CONTEXT_MFT_RECORD_15_USED | \
+ IRP_CONTEXT_MFT_RECORD_RESERVED | \
+ IRP_CONTEXT_FLAG_RAISED_STATUS | \
+ IRP_CONTEXT_FLAG_DONT_DELETE | \
+ IRP_CONTEXT_FLAG_WRITE_SEEN | \
+ IRP_CONTEXT_FLAG_MODIFIED_BITMAP | \
+ IRP_CONTEXT_FLAG_WROTE_LOG | \
+ IRP_CONTEXT_FLAG_FORCE_POST | \
+ IRP_CONTEXT_FLAG_CALL_SELF | \
+ IRP_CONTEXT_FLAG_CREATE_MOD_SCB | \
+ IRP_CONTEXT_FLAG_ACQUIRE_PAGING )
+
+
+//
+// The top level context is used to determine whether this request has
+// other requests below it on the stack.
+//
+
+typedef struct _TOP_LEVEL_CONTEXT {
+
+ BOOLEAN TopLevelRequest;
+ BOOLEAN ValidSavedTopLevel;
+ BOOLEAN OverflowReadThread;
+
+ ULONG Ntfs;
+
+ VCN VboBeingHotFixed;
+
+ PSCB ScbBeingHotFixed;
+
+ PIRP SavedTopLevelIrp;
+
+ PIRP_CONTEXT TopLevelIrpContext;
+
+} TOP_LEVEL_CONTEXT;
+typedef TOP_LEVEL_CONTEXT *PTOP_LEVEL_CONTEXT;
+
+
+//
+// The found attribute part of the attribute enumeration context
+// describes an attribute record that had been located or created. It
+// may refer to either a base or attribute list.
+//
+
+typedef struct _FOUND_ATTRIBUTE {
+
+ //
+ // The following identify the attribute which was mapped. These are
+ // necessary if forcing the range of bytes into memory by pinning.
+ // These include the Bcb on which the attribute was read (if this
+ // field is NULL, this is the initial attribute) and the offset of
+ // the record segment in the Mft.
+ //
+
+ LONGLONG MftFileOffset;
+
+ //
+ // Pointer to the Attribute Record
+ //
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ //
+ // Pointer to the containing record segment.
+ //
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ //
+ // Bcb for mapped/pinned FileRecord
+ //
+
+ PBCB Bcb;
+
+ //
+ // Some state information.
+ //
+
+ BOOLEAN AttributeDeleted;
+ BOOLEAN AttributeAllocationDeleted;
+
+} FOUND_ATTRIBUTE;
+typedef FOUND_ATTRIBUTE *PFOUND_ATTRIBUTE;
+
+//
+// The structure guides enumeration through the attribute list.
+//
+
+typedef struct _ATTRIBUTE_LIST_CONTEXT {
+
+ //
+ // This points to the first attribute list entry; it is advanced
+ // when we are searching for a particular exteral attribute.
+ //
+
+ PATTRIBUTE_LIST_ENTRY Entry;
+
+ //
+ // A Bcb for the attribute list.
+ //
+
+ PBCB Bcb;
+
+ //
+ // This field is used to remember the location of the Attribute
+ // List attribute within the base file record, if existent.
+ //
+
+ PATTRIBUTE_RECORD_HEADER AttributeList;
+
+ //
+ // This points to the first entry in the attribute list. This is
+ // needed when the attribute list is non-resident.
+ //
+
+ PATTRIBUTE_LIST_ENTRY FirstEntry;
+
+ //
+ // This points just beyond the final attribute list entry.
+ //
+
+ PATTRIBUTE_LIST_ENTRY BeyondFinalEntry;
+
+ //
+ // This is the Bcb for the data portion of a non-resident attribute.
+ //
+
+ PBCB NonresidentListBcb;
+
+} ATTRIBUTE_LIST_CONTEXT;
+typedef ATTRIBUTE_LIST_CONTEXT *PATTRIBUTE_LIST_CONTEXT;
+
+//
+// The Attribute Enumeration Context structure returns information on an
+// attribute which has been found by one of the Attribute Lookup or
+// Creation routines. It is also used as an IN OUT structure to perform
+// further lookups/modifications to attributes. It does not have a node
+// type code and size since it is usually allocated on the caller's
+// stack.
+//
+
+typedef struct _ATTRIBUTE_ENUMERATION_CONTEXT {
+
+ //
+ // Contains the actual attribute we found.
+ //
+
+ FOUND_ATTRIBUTE FoundAttribute;
+
+ //
+ // Allows enumeration through the attribute list.
+ //
+
+ ATTRIBUTE_LIST_CONTEXT AttributeList;
+
+} ATTRIBUTE_ENUMERATION_CONTEXT;
+typedef ATTRIBUTE_ENUMERATION_CONTEXT *PATTRIBUTE_ENUMERATION_CONTEXT;
+
+
+//
+// Define struct which will be used to remember the path that was
+// followed to locate a given INDEX_ENTRY or insertion point for an
+// INDEX_ENTRY. This structure is always filled in by LookupIndexEntry.
+//
+// The Index Lookup Stack is generally allocated as a local variable in
+// one of the routines in this module that may be called from another
+// module. A pointer to this stack is then passed in to some of the
+// internal routines.
+//
+// The first entry in the stack describes context in the INDEX attribute
+// in the file record, and all subsequent stack entries refer to Index
+// buffers in the INDEX_ALLOCATION attribute.
+//
+// Outside of indexsup.c, this structure should only be passed as an
+// "opaque" context, and individual fields should not be referenced.
+//
+
+typedef struct _INDEX_LOOKUP_STACK {
+
+ //
+ // Bcb pointer for the Index Buffer. In the "bottom" (first entry)
+ // of the stack this field contains a NULL, and the Bcb must be found
+ // via the Attribute Enumeration Context.
+ //
+
+ PBCB Bcb;
+
+ //
+ // Pointer to the start of the File Record or Index Buffer
+ //
+
+ PVOID StartOfBuffer;
+
+ //
+ // Pointer to Index Header in the File Record or Index Buffer
+ //
+
+ PINDEX_HEADER IndexHeader;
+
+ //
+ // Pointer to to the current INDEX_ENTRY on search path
+ //
+
+ PINDEX_ENTRY IndexEntry;
+
+ //
+ // Index block of the index buffer
+ //
+
+ LONGLONG IndexBlock;
+
+ //
+ // Saved Lsn for faster enumerations
+ //
+
+ LSN CapturedLsn;
+
+} INDEX_LOOKUP_STACK;
+
+typedef INDEX_LOOKUP_STACK *PINDEX_LOOKUP_STACK;
+
+#define INDEX_LOOKUP_STACK_SIZE (3)
+
+//
+// Index Context structure.
+//
+// This structure maintains a context which describes the lookup stack to
+// a given index entry. It includes the Attribute Enumeration Context
+// for the Index Root, the Index lookup stack remembering the path to the
+// index entry, and the current stack pointer within the stack pointing
+// to the stack entry for the current index entry or where we are at in a
+// bucket split or delete operation.
+//
+// Outside of indexsup.c, this structure should only be passed as an
+// "opaque" context, and individual fields should not be referenced.
+//
+
+typedef struct _INDEX_CONTEXT {
+
+ //
+ // Attribute Enumeration Context for the Index Root
+ //
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+
+ //
+ // Base of dynamically allocated lookup stack - either points
+ // to the one above or a dynamically allocated larger one.
+ //
+
+ PINDEX_LOOKUP_STACK Base;
+
+ //
+ // Stack pointer to top of Lookup Stack. This field essentially
+ // remembers how deep the index Btree is.
+ //
+
+ PINDEX_LOOKUP_STACK Top;
+
+ //
+ // Index lookup stack.
+ //
+
+ INDEX_LOOKUP_STACK LookupStack[INDEX_LOOKUP_STACK_SIZE];
+
+ //
+ // Stack pointer within the Index Lookup Stack
+ //
+
+ PINDEX_LOOKUP_STACK Current;
+
+ //
+ // Captured Scb (Index type) change count
+ //
+
+ ULONG ScbChangeCount;
+
+ //
+ // This field remembers where the index root attribute was last
+ // seen, to support correct operation of FindMoveableIndexRoot.
+ //
+
+ PATTRIBUTE_RECORD_HEADER OldAttribute;
+
+ //
+ // Number of entries allocated in the lookup stack.
+ //
+
+ USHORT NumberEntries;
+
+ //
+ // Flags
+ //
+
+ USHORT Flags;
+
+#ifdef _CAIRO_
+
+ //
+ // For enumerations via NtOfsReadRecords, the MatchFunction and MatchData
+ // are stored here.
+ //
+
+ PMATCH_FUNCTION MatchFunction;
+ PVOID MatchData;
+
+#endif _CAIRO_
+
+ //
+ // Fcb which was acquired and must be released.
+ //
+
+ PFCB AcquiredFcb;
+
+ //
+ // Add field to preserve quad & cache line alignment
+ //
+
+ ULONG Unused;
+
+} INDEX_CONTEXT;
+
+typedef INDEX_CONTEXT *PINDEX_CONTEXT;
+
+//
+// Fcb table is acquired and must be freed.
+//
+
+#define INDX_CTX_FLAG_FCB_TABLE_ACQUIRED (01)
+
+
+//
+// Context structure for asynchronous I/O calls. Most of these fields
+// are actually only required for the Read/Write Multiple routines, but
+// the caller must allocate one as a local variable anyway before knowing
+// whether there are multiple requests are not. Therefore, a single
+// structure is used for simplicity.
+//
+
+typedef struct _NTFS_IO_CONTEXT {
+
+ //
+ // These two fields are used for multiple run Io
+ //
+
+ LONG IrpCount;
+ PIRP MasterIrp;
+ UCHAR IrpSpFlags;
+ BOOLEAN AllocatedContext;
+ BOOLEAN PagingIo;
+
+ union {
+
+
+ // This element handles the asynchronous non-cached Io
+
+
+ struct {
+
+ PERESOURCE Resource;
+ ERESOURCE_THREAD ResourceThreadId;
+ ULONG RequestedByteCount;
+
+ } Async;
+
+
+ // and this element handles the synchronous non-cached Io.
+
+
+ KEVENT SyncEvent;
+
+ } Wait;
+
+
+} NTFS_IO_CONTEXT;
+
+typedef NTFS_IO_CONTEXT *PNTFS_IO_CONTEXT;
+
+//
+// An array of these structures is passed to NtfsMultipleAsync describing
+// a set of runs to execute in parallel. Risc compilers will add an
+// unused long word anyway to align each array entry.
+//
+
+typedef struct _IO_RUN {
+
+ VBO StartingVbo;
+ LBO StartingLbo;
+ ULONG BufferOffset;
+ ULONG ByteCount;
+ PIRP SavedIrp;
+ ULONG Unused;
+
+} IO_RUN;
+typedef IO_RUN *PIO_RUN;
+
+
+//
+// This structure is used by the name manipulation routines to described
+// a parsed file name componant.
+//
+
+typedef struct _NTFS_NAME_DESCRIPTOR {
+
+ //
+ // The follow flag tells which fields were present in the name.
+ //
+
+ ULONG FieldsPresent;
+
+ UNICODE_STRING FileName;
+ UNICODE_STRING AttributeType;
+ UNICODE_STRING AttributeName;
+ ULONG VersionNumber;
+
+} NTFS_NAME_DESCRIPTOR;
+typedef NTFS_NAME_DESCRIPTOR *PNTFS_NAME_DESCRIPTOR;
+
+//
+// Define the bits in FieldsPresent above.
+//
+
+#define FILE_NAME_PRESENT_FLAG (1)
+#define ATTRIBUTE_TYPE_PRESENT_FLAG (2)
+#define ATTRIBUTE_NAME_PRESENT_FLAG (4)
+#define VERSION_NUMBER_PRESENT_FLAG (8)
+
+
+//
+// The following is used to perform Ea related routines.
+//
+
+typedef struct _EA_LIST_HEADER {
+
+ //
+ // The size of buffer needed to pack these Ea's
+ //
+
+ ULONG PackedEaSize;
+
+ //
+ // This is the count of Ea's with their NEED_EA
+ // bit set.
+ //
+
+ USHORT NeedEaCount;
+
+ //
+ // The size of the buffer needed to return all Ea's
+ // in their unpacked form.
+ //
+
+ ULONG UnpackedEaSize;
+
+ //
+ // This is the size of the buffer used to store the ea's
+ //
+
+ ULONG BufferSize;
+
+ //
+ // This is the pointer to the first entry in the list.
+ //
+
+ PFILE_FULL_EA_INFORMATION FullEa;
+
+} EA_LIST_HEADER;
+typedef EA_LIST_HEADER *PEA_LIST_HEADER;
+
+
+//
+// The following structure is used to maintain a list of recently
+// deallocated records so that the file system will not reuse a recently
+// deallocated record until it is safe to do so. Each instance of this
+// structure is placed on two queues. One queue is per index SCB and the
+// other queue is per Irp Context.
+//
+// Whenever we delete a record we allocate a new structure if necessary
+// and add it to the scb queue and the irp context queue. We indicate in
+// the structure the index of the record we just deallocated.
+//
+// Whenever we need to allocate a new record we filter out any canidate
+// we want to allocate to avoid allocating one in the scb's recently
+// deallocated queue.
+//
+// Whenever we delete an irp context we scan through its recently
+// deallocated queue removing it from the scb queue.
+//
+
+#define DEALLOCATED_RECORD_ENTRIES 32
+
+typedef struct _DEALLOCATED_RECORDS {
+
+ //
+ // The following field links this structure into the
+ // Scb->RecentlyDeallocatedQueue
+ //
+
+ LIST_ENTRY ScbLinks;
+
+ //
+ // The following field links this structure into the
+ // IrpContext->RecentlyDeallocatedQueue
+ //
+
+ LIST_ENTRY IrpContextLinks;
+
+ //
+ // This is a pointer to the Scb that this record is part of
+ //
+
+ PSCB Scb;
+
+ //
+ // The following two fields describe the total size of this structure
+ // and the number of entries actually being used. NumberOfEntries is
+ // the size of the Index array and NextFreeEntryis the index of the
+ // next free slot. If NumberOfEntries is equal to NextFreeEntry then
+ // this structure is full
+ //
+
+ ULONG NumberOfEntries;
+ ULONG NextFreeEntry;
+
+ //
+ // This is an array of indices that have been dealloated.
+ //
+
+ ULONG Index[DEALLOCATED_RECORD_ENTRIES];
+
+} DEALLOCATED_RECORDS;
+typedef DEALLOCATED_RECORDS *PDEALLOCATED_RECORDS;
+
+#define DEALLOCATED_RECORDS_HEADER_SIZE \
+ (FIELD_OFFSET( DEALLOCATED_RECORDS, Index ))
+
+typedef struct _FCB_TABLE_ELEMENT {
+
+ FILE_REFERENCE FileReference;
+ PFCB Fcb;
+
+} FCB_TABLE_ELEMENT;
+typedef FCB_TABLE_ELEMENT *PFCB_TABLE_ELEMENT;
+
+
+//
+// Security descriptor information. This structure is used to allow
+// Fcb's to share security descriptors.
+//
+
+typedef struct _SHARED_SECURITY {
+
+ PFCB ParentFcb;
+ ULONG ReferenceCount;
+#ifndef _CAIRO_
+ ULONG SecurityDescriptorXLength;
+#else // _CAIRO_
+ SECURITY_DESCRIPTOR_HEADER Header;
+#endif // _CAIRO_
+ UCHAR SecurityDescriptor[1];
+
+} SHARED_SECURITY, *PSHARED_SECURITY;
+
+#ifndef _CAIRO_
+#define GetSharedSecurityLength(SS) ((SS)->SecurityDescriptorXLength)
+#define SetSharedSecurityLength(SS,LENGTH) ((SS)->SecurityDescriptorXLength = (LENGTH))
+#else // _CAIRO_
+#define GetSharedSecurityLength(SS) (GETSECURITYDESCRIPTORLENGTH( &(SS)->Header ))
+#define SetSharedSecurityLength(SS,LENGTH) (SetSecurityDescriptorLength( &(SS)->Header,(LENGTH) ))
+#endif // _CAIRO_
+
+
+//
+// The following structure is used to store the state of an Scb to use
+// during unwind operations. We keep a copy of all of the file sizes.
+//
+
+typedef struct _OLD_SCB_SNAPSHOT {
+
+ LONGLONG AllocationSize;
+ LONGLONG FileSize;
+ LONGLONG ValidDataLength;
+ LONGLONG TotalAllocated;
+
+ UCHAR CompressionUnit;
+ BOOLEAN Resident;
+ USHORT AttributeFlags;
+
+} OLD_SCB_SNAPSHOT, *POLD_SCB_SNAPSHOT;
+
+//
+// Structure used to track the number of threads doing read ahead, so
+// that we do not hot fix for them.
+//
+
+typedef struct _READ_AHEAD_THREAD {
+
+ //
+ // Links of read ahead structures.
+ //
+
+ LIST_ENTRY Links;
+
+ //
+ // Thread Id
+ //
+
+ PVOID Thread;
+
+} READ_AHEAD_THREAD, *PREAD_AHEAD_THREAD;
+
+//
+// Structure used to post to Defrag Mft routine.
+//
+
+typedef struct _DEFRAG_MFT {
+
+ //
+ // This structure is used for posting to the Ex worker threads.
+ //
+
+ WORK_QUEUE_ITEM WorkQueueItem;
+
+ PVCB Vcb;
+
+ BOOLEAN DeallocateWorkItem;
+
+} DEFRAG_MFT, *PDEFRAG_MFT;
+
+//
+// Structure for remembering file records to delete.
+//
+
+typedef struct _NUKEM {
+
+ struct _NUKEM *Next;
+ ULONG RecordNumbers[4];
+
+} NUKEM, *PNUKEM;
+
+//
+// Structure for picking up file name pairs for property tunneling. Space is allocated for
+// the names so that this can be used on the stack. The size of LongBuffer should be sized
+// so that it will capture the vast majority of long names. Fallback code can go to pool
+// if required - but rarely.
+//
+
+
+typedef struct _NAME_PAIR {
+
+ //
+ // The FILE_NAME_DOS component
+ //
+
+ UNICODE_STRING Short;
+
+ //
+ // The FILE_NAME_NTFS component
+ //
+
+ UNICODE_STRING Long;
+
+ // Allocate space for an 8.3 name and a 26 char name. 26 isn't quite random -
+ // it puts this structure at 96 bytes.
+ //
+
+ WCHAR ShortBuffer[8+1+3];
+ WCHAR LongBuffer[26];
+
+} NAME_PAIR, *PNAME_PAIR;
+
+//
+// Following structure is used at the time a request is posted to the oplock package
+// to perform any cleanup to do at that time.
+//
+
+typedef struct _OPLOCK_CLEANUP {
+
+ //
+ // This is the original name and any allocated name buffer used during create.
+ // We must restore the original name in the file object on error.
+ //
+
+ UNICODE_STRING OriginalFileName;
+ UNICODE_STRING FullFileName;
+ UNICODE_STRING ExactCaseName;
+ PFILE_OBJECT FileObject;
+
+} OPLOCK_CLEANUP, *POPLOCK_CLEANUP;
+
+
+#ifdef _CAIRO_
+
+//
+// This is the quota control block which are stored as table elments in the quota
+// control table.
+//
+
+//
+// This structure is stored as part of a generic table which has a header
+// that is 5 ULONG long. In order to make the QuickIndexHint be quad word
+// aligned without wasting 8 bytes. This structure is marked as pack4.
+//
+
+#include "pshpack4.h"
+
+typedef struct _QUOTA_CONTROL_BLOCK {
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+ ULONG OwnerId;
+ ULONG Flags;
+ LONG ReferenceCount;
+ PFAST_MUTEX QuotaControlLock;
+ QUICK_INDEX_HINT QuickIndexHint;
+} QUOTA_CONTROL_BLOCK, *PQUOTA_CONTROL_BLOCK;
+
+#include "poppack.h"
+
+//
+// Define the quota control flags.
+//
+
+#define QUOTA_FLAG_LIMIT_POSTED (0x00000001)
+
+//
+// Define the minimum amount of time between quota events. Currently this is
+// 1 hour.
+//
+
+#define MIN_QUOTA_NOTIFY_TIME (60i64 * 60 * 1000 * 1000 * 10)
+
+#endif // _CAIRO_
+
+
+//
+// Following macro is used to initialize UNICODE strings
+//
+
+#define CONSTANT_UNICODE_STRING(s) { sizeof( s ) - 2, sizeof( s ), s }
+
+#endif // _NTFSSTRU_
diff --git a/private/ntos/cntfs/prefxsup.c b/private/ntos/cntfs/prefxsup.c
new file mode 100644
index 000000000..01f6bb21d
--- /dev/null
+++ b/private/ntos/cntfs/prefxsup.c
@@ -0,0 +1,805 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ PrefxSup.c
+
+Abstract:
+
+ This module implements the Ntfs Prefix support routines
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Bug check file id for this module
+//
+
+#define BugCheckFileId (NTFS_BUG_CHECK_PREFXSUP)
+
+//
+// The debug trace level for this module
+//
+
+#define Dbg (DEBUG_TRACE_PREFXSUP)
+
+//
+// Local procedures
+//
+
+FSRTL_COMPARISON_RESULT
+NtfsFullCompareNames (
+ IN PUNICODE_STRING NameA,
+ IN PUNICODE_STRING NameB
+ );
+
+//
+// FSRTL_COMPARISON_RESULT
+// NtfsCompareNames (
+// IN PUNICODE_STRING NameA,
+// IN PUNICODE_STRING NameB
+// );
+//
+
+#define NtfsCompareNames(NAMEA,NAMEB) ( \
+ ((NAMEA)->Buffer[0] == (NAMEB)->Buffer[0] ? \
+ NtfsFullCompareNames( NAMEA, NAMEB ) : \
+ ((NAMEA)->Buffer[0] < (NAMEB)->Buffer[0] ? \
+ LessThan : \
+ GreaterThan)) \
+)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsFindPrefix)
+#pragma alloc_text(PAGE, NtfsFindNameLink)
+#pragma alloc_text(PAGE, NtfsFullCompareNames)
+#pragma alloc_text(PAGE, NtfsInsertNameLink)
+#pragma alloc_text(PAGE, NtfsInsertPrefix)
+#pragma alloc_text(PAGE, NtfsRemovePrefix)
+#endif
+
+
+VOID
+NtfsInsertPrefix (
+ IN PLCB Lcb,
+ IN BOOLEAN IgnoreCase
+ )
+
+/*++
+
+Routine Description:
+
+ This routine inserts the names in the given Lcb into the links for the
+ parent.
+
+Arguments:
+
+ Lcb - This is the link to insert.
+
+ IgnoreCase - Indicates if we should insert the case-insensitive name.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ //
+ // Attempt to insert the case-insensitive name. It is possible that
+ // we can't enter this name.
+ //
+
+ if (IgnoreCase) {
+
+ if (!FlagOn( Lcb->LcbState, LCB_STATE_IGNORE_CASE_IN_TREE ) &&
+ NtfsInsertNameLink( &Lcb->Scb->ScbType.Index.IgnoreCaseNode,
+ &Lcb->IgnoreCaseLink )) {
+
+ SetFlag( Lcb->LcbState, LCB_STATE_IGNORE_CASE_IN_TREE );
+ }
+
+ } else if (!FlagOn( Lcb->LcbState, LCB_STATE_EXACT_CASE_IN_TREE )) {
+
+ if (!NtfsInsertNameLink( &Lcb->Scb->ScbType.Index.ExactCaseNode,
+ &Lcb->ExactCaseLink )) {
+
+ NtfsBugCheck( 0, 0, 0 );
+ }
+
+ SetFlag( Lcb->LcbState, LCB_STATE_EXACT_CASE_IN_TREE );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsRemovePrefix (
+ IN PLCB Lcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deletes all of the Prefix entries that exist for the input
+ Lcb.
+
+Arguments:
+
+ Lcb - Supplies the Lcb whose prefixes are to be removed
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ //
+ // Remove the case-insensitive link.
+ //
+
+ if (FlagOn( Lcb->LcbState, LCB_STATE_IGNORE_CASE_IN_TREE )) {
+
+ NtfsRemoveNameLink( &Lcb->Scb->ScbType.Index.IgnoreCaseNode,
+ &Lcb->IgnoreCaseLink );
+
+ ClearFlag( Lcb->LcbState, LCB_STATE_IGNORE_CASE_IN_TREE );
+ }
+
+ //
+ // Now do the same for the exact case name.
+ //
+
+ if (FlagOn( Lcb->LcbState, LCB_STATE_EXACT_CASE_IN_TREE )) {
+
+ NtfsRemoveNameLink( &Lcb->Scb->ScbType.Index.ExactCaseNode,
+ &Lcb->ExactCaseLink );
+
+ ClearFlag( Lcb->LcbState, LCB_STATE_EXACT_CASE_IN_TREE );
+ }
+
+ return;
+}
+
+
+PLCB
+NtfsFindPrefix (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB StartingScb,
+ OUT PFCB *CurrentFcb,
+ OUT PLCB *LcbForTeardown,
+ IN OUT UNICODE_STRING FullFileName,
+ IN BOOLEAN IgnoreCase,
+ OUT PBOOLEAN DosOnlyComponent,
+ OUT PUNICODE_STRING RemainingName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine begins from the given Scb and walks through all of
+ components of the name looking for the longest match in the prefix
+ splay trees. The search is relative to the starting Scb so the
+ full name may not begin with a '\'. On return this routine will
+ update Current Fcb with the lowest point it has travelled in the
+ tree. It will also hold only that resource on return and it must
+ hold that resource.
+
+Arguments:
+
+ StartingScb - Supplies the Scb to start the search from.
+
+ CurrentFcb - Address to store the lowest Fcb we find on this search.
+ On return we will have acquired this Fcb.
+
+ LcbForTeardown - If we encounter an Lcb we must teardown on error we
+ store it here.
+
+ FullFileName - Supplies the input string to search for. After the search the
+ buffer for this string will be modified so that for the characters that did
+ match will be the exact case of what we found.
+
+ IgnoreCase - Indicates if we are doing a case-insensitive compare.
+
+ DosOnlyComponent - Set to TRUE if we traverse an Lcb which is a DOS-ONLY
+ name.
+
+ RemainingName - Returns the string when the prefix no longer matches.
+ For example, if the input string is "alpha\beta" only matches the
+ root directory then the remaining string is "alpha\beta". If the
+ same string matches an LCB for "alpha" then the remaining string is
+ "beta".
+
+Return Value:
+
+ PLCB - Returns a pointer to the Lcb corresponding to the longest match
+ in the splay tree. NULL if we didn't even find one entry.
+
+--*/
+
+{
+ PSCB LastScb = NULL;
+ PLCB LastLcb = NULL;
+ PLCB ThisLcb;
+ PNAME_LINK NameLink;
+ UNICODE_STRING NextComponent;
+ UNICODE_STRING Tail;
+ BOOLEAN DroppedParent;
+ BOOLEAN NeedSnapShot = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // Start by setting the remaining name to the full name to be parsed.
+ //
+
+ *RemainingName = FullFileName;
+
+ //
+ // If there are no characters left or the starting Scb is not an index
+ // or the name begins with a ':' then return without looking up the name.
+ //
+
+ if (RemainingName->Length == 0 ||
+ StartingScb->AttributeTypeCode != $INDEX_ALLOCATION ||
+ RemainingName->Buffer[0] == L':') {
+
+ return NULL;
+ }
+
+ //
+ // Loop until we find the longest matching prefix.
+ //
+
+ while (TRUE) {
+
+ //
+ // Get the next component off of the list.
+ //
+
+ NtfsDissectName( *RemainingName,
+ &NextComponent,
+ &Tail );
+
+ //
+ // Check if this name is in the splay tree for this Scb.
+ //
+
+ if (IgnoreCase) {
+
+ NameLink = NtfsFindNameLink( &StartingScb->ScbType.Index.IgnoreCaseNode,
+ &NextComponent );
+
+ ThisLcb = CONTAINING_RECORD( NameLink, LCB, IgnoreCaseLink );
+
+ } else {
+
+ NameLink = NtfsFindNameLink( &StartingScb->ScbType.Index.ExactCaseNode,
+ &NextComponent );
+
+ ThisLcb = CONTAINING_RECORD( NameLink, LCB, ExactCaseLink );
+ }
+
+ //
+ // If we didn't find a match then return the Fcb for the current Scb.
+ //
+
+ if (NameLink == NULL) {
+
+ if (NeedSnapShot) {
+
+ //
+ // NtfsCreateScb was not called on the StartingScb so take a
+ // snapshot now.
+ //
+
+ NtfsSnapshotScb( IrpContext, StartingScb );
+ }
+
+ return LastLcb;
+ }
+
+ //
+ // If this is a case-insensitive match then copy the exact case of the name into
+ // the input buffer.
+ //
+
+ if (IgnoreCase) {
+
+ RtlCopyMemory( NextComponent.Buffer,
+ ThisLcb->ExactCaseLink.LinkName.Buffer,
+ NextComponent.Length );
+ }
+
+ //
+ // Update the caller's remaining name string to reflect the fact that we found
+ // a match.
+ //
+
+ *RemainingName = Tail;
+
+ //
+ // Before we give up the previous Lcb check if the name was a DOS-ONLY
+ // name and set the return boolean if so.
+ //
+
+ if (LastLcb != NULL &&
+ LastLcb->FileNameAttr->Flags == FILE_NAME_DOS) {
+
+ *DosOnlyComponent = TRUE;
+ }
+
+ //
+ // Update the pointers to the Lcb.
+ //
+
+ LastLcb = ThisLcb;
+
+ DroppedParent = FALSE;
+
+ //
+ // We want to acquire the next Fcb and release the one we currently
+ // have. Try to do a fast acquire.
+ //
+
+ if (!NtfsAcquireFcbWithPaging( IrpContext, ThisLcb->Fcb, TRUE )) {
+
+ //
+ // Reference the link and Fcb so they don't go away.
+ //
+
+ ThisLcb->ReferenceCount += 1;
+
+ NtfsAcquireFcbTable( IrpContext, StartingScb->Vcb );
+ ThisLcb->Fcb->ReferenceCount += 1;
+ NtfsReleaseFcbTable( IrpContext, StartingScb->Vcb );
+
+ //
+ // Set the IrpContext to acquire paging io resources if our target
+ // has one. This will lock the MappedPageWriter out of this file.
+ //
+
+ if (ThisLcb->Fcb->PagingIoResource != NULL) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING );
+ }
+
+ NtfsReleaseScbWithPaging( IrpContext, StartingScb );
+
+ NtfsAcquireFcbWithPaging( IrpContext, ThisLcb->Fcb, FALSE );
+
+ NtfsAcquireExclusiveScb( IrpContext, StartingScb );
+ ThisLcb->ReferenceCount -= 1;
+ NtfsReleaseScb( IrpContext, StartingScb );
+
+ NtfsAcquireFcbTable( IrpContext, StartingScb->Vcb );
+ ThisLcb->Fcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, StartingScb->Vcb );
+
+ DroppedParent = TRUE;
+
+ } else {
+
+ //
+ // Don't forget to release the starting Scb.
+ //
+
+ NtfsReleaseScbWithPaging( IrpContext, StartingScb );
+ }
+
+ *LcbForTeardown = ThisLcb;
+ *CurrentFcb = ThisLcb->Fcb;
+
+ //
+ // It is possible that the Lcb we just found could have been removed
+ // from the prefix table in the window where we dropped the parent Scb.
+ // In that case we need to check that it is still in the prefix
+ // table. If not then raise CANT_WAIT to force a rescan through the
+ // prefix table.
+ //
+
+ if (DroppedParent &&
+ !FlagOn( ThisLcb->LcbState,
+ LCB_STATE_IGNORE_CASE_IN_TREE | LCB_STATE_EXACT_CASE_IN_TREE )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // If we found a match but the Fcb is uninitialized or is not a directory
+ // then we are done. Also finished if the remaining name length is 0.
+ //
+
+ if (!FlagOn( ThisLcb->Fcb->FcbState, FCB_STATE_DUP_INITIALIZED ) ||
+ !IsDirectory( &ThisLcb->Fcb->Info ) ||
+ RemainingName->Length == 0) {
+
+ return LastLcb;
+ }
+
+ //
+ // Get the Scb for the $INDEX_ALLOCATION for this Fcb.
+ //
+
+ LastScb = StartingScb;
+
+ // Since the SCB is usually track on the end of the SCB look
+ // for it in the FCB first.
+
+ if (FlagOn( ThisLcb->Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
+ (SafeNodeType( &((PFCB_INDEX) ThisLcb->Fcb)->Scb ) == NTFS_NTC_SCB_INDEX)) {
+
+ NeedSnapShot = TRUE;
+
+ StartingScb = (PSCB) &((PFCB_INDEX) ThisLcb->Fcb)->Scb;
+
+ ASSERT(!FlagOn( StartingScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED) &&
+ (StartingScb->AttributeTypeCode == $INDEX_ALLOCATION) &&
+ NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable, &StartingScb->AttributeName, &NtfsFileNameIndex, FALSE ));
+
+ } else {
+
+ NeedSnapShot = FALSE;
+
+ StartingScb = NtfsCreateScb( IrpContext,
+ ThisLcb->Fcb,
+ $INDEX_ALLOCATION,
+ &NtfsFileNameIndex,
+ FALSE,
+ NULL );
+ }
+
+ //
+ // If there is no normalized name in this Scb, find it now.
+ //
+
+ if (StartingScb->ScbType.Index.NormalizedName.Buffer == NULL &&
+ LastScb->ScbType.Index.NormalizedName.Buffer != NULL) {
+
+ NtfsUpdateNormalizedName( IrpContext, LastScb, StartingScb, NULL, FALSE );
+ }
+ }
+}
+
+
+BOOLEAN
+NtfsInsertNameLink (
+ IN PRTL_SPLAY_LINKS *RootNode,
+ IN PNAME_LINK NameLink
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will insert a name in the splay tree pointed to
+ by RootNode.
+
+ The name could already exist in this tree for a case-insensitive tree.
+ In that case we simply return FALSE and do nothing.
+
+Arguments:
+
+ RootNode - Supplies a pointer to the table.
+
+ NameLink - Contains the new link to enter.
+
+Return Value:
+
+ BOOLEAN - TRUE if the name is inserted, FALSE otherwise.
+
+--*/
+
+{
+ FSRTL_COMPARISON_RESULT Comparison;
+ PNAME_LINK Node;
+
+ PAGED_CODE();
+
+ RtlInitializeSplayLinks( &NameLink->Links );
+
+ //
+ // If we are the first entry in the tree, just become the root.
+ //
+
+ if (*RootNode == NULL) {
+
+ *RootNode = &NameLink->Links;
+
+ return TRUE;
+ }
+
+ Node = CONTAINING_RECORD( *RootNode, NAME_LINK, Links );
+
+ while (TRUE) {
+
+ //
+ // Compare the prefix in the tree with the prefix we want
+ // to insert.
+ //
+
+ Comparison = NtfsCompareNames( &Node->LinkName, &NameLink->LinkName );
+
+ //
+ // If we found the entry, return immediately.
+ //
+
+ if (Comparison == EqualTo) {
+
+ return FALSE;
+ }
+
+ //
+ // If the tree prefix is greater than the new prefix then
+ // we go down the left subtree
+ //
+
+ if (Comparison == GreaterThan) {
+
+ //
+ // We want to go down the left subtree, first check to see
+ // if we have a left subtree
+ //
+
+ if (RtlLeftChild( &Node->Links ) == NULL) {
+
+ //
+ // there isn't a left child so we insert ourselves as the
+ // new left child
+ //
+
+ RtlInsertAsLeftChild( &Node->Links, &NameLink->Links );
+
+ //
+ // and exit the while loop
+ //
+
+ break;
+
+ } else {
+
+ //
+ // there is a left child so simply go down that path, and
+ // go back to the top of the loop
+ //
+
+ Node = CONTAINING_RECORD( RtlLeftChild( &Node->Links ),
+ NAME_LINK,
+ Links );
+ }
+
+ } else {
+
+ //
+ // The tree prefix is either less than or a proper prefix
+ // of the new string. We treat both cases as less than when
+ // we do insert. So we want to go down the right subtree,
+ // first check to see if we have a right subtree
+ //
+
+ if (RtlRightChild( &Node->Links ) == NULL) {
+
+ //
+ // These isn't a right child so we insert ourselves as the
+ // new right child
+ //
+
+ RtlInsertAsRightChild( &Node->Links, &NameLink->Links );
+
+ //
+ // and exit the while loop
+ //
+
+ break;
+
+ } else {
+
+ //
+ // there is a right child so simply go down that path, and
+ // go back to the top of the loop
+ //
+
+ Node = CONTAINING_RECORD( RtlRightChild( &Node->Links ),
+ NAME_LINK,
+ Links );
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+PNAME_LINK
+NtfsFindNameLink (
+ IN PRTL_SPLAY_LINKS *RootNode,
+ IN PUNICODE_STRING Name
+ )
+
+/*++
+
+Routine Description:
+
+ This routine searches through a splay link tree looking for a match for the
+ input name. If we find the corresponding name we will rebalance the
+ tree.
+
+Arguments:
+
+ RootNode - Supplies the parent to search.
+
+ Name - This is the name to search for. Note if we are doing a case
+ insensitive search the name would have been upcased already.
+
+Return Value:
+
+ PNAME_LINK - The name link found or NULL if there is no match.
+
+--*/
+
+{
+ FSRTL_COMPARISON_RESULT Comparison;
+ PNAME_LINK Node;
+ PRTL_SPLAY_LINKS Links;
+
+ PAGED_CODE();
+
+ Links = *RootNode;
+
+ while (Links != NULL) {
+
+ Node = CONTAINING_RECORD( Links, NAME_LINK, Links );
+
+ //
+ // Compare the prefix in the tree with the full name
+ //
+
+ Comparison = NtfsCompareNames( &Node->LinkName, Name );
+
+ //
+ // See if they don't match
+ //
+
+ if (Comparison == GreaterThan) {
+
+ //
+ // The prefix is greater than the full name
+ // so we go down the left child
+ //
+
+ Links = RtlLeftChild( Links );
+
+ //
+ // And continue searching down this tree
+ //
+
+ } else if (Comparison == LessThan) {
+
+ //
+ // The prefix is less than the full name
+ // so we go down the right child
+ //
+
+ Links = RtlRightChild( Links );
+
+ //
+ // And continue searching down this tree
+ //
+
+ } else {
+
+ //
+ // We found it.
+ //
+ // Splay the tree and save the new root.
+ //
+
+ *RootNode = RtlSplay( Links );
+
+ return Node;
+ }
+ }
+
+ //
+ // We didn't find the Link.
+ //
+
+ return NULL;
+}
+
+
+
+//
+// Local support routine
+//
+
+FSRTL_COMPARISON_RESULT
+NtfsFullCompareNames (
+ IN PUNICODE_STRING NameA,
+ IN PUNICODE_STRING NameB
+ )
+
+/*++
+
+Routine Description:
+
+ This function compares two names as fast as possible. Note that since
+ this comparison is case sensitive we can do a direct memory comparison.
+
+Arguments:
+
+ NameA & NameB - The names to compare.
+
+Return Value:
+
+ COMPARISON - returns
+
+ LessThan if NameA < NameB lexicalgraphically,
+ GreaterThan if NameA > NameB lexicalgraphically,
+ EqualTo if NameA is equal to NameB
+
+--*/
+
+{
+ ULONG i;
+ ULONG MinLength;
+
+ PAGED_CODE();
+
+ //
+ // Figure out the minimum of the two lengths
+ //
+
+ if (NameA->Length < NameB->Length) {
+
+ MinLength = NameA->Length;
+
+ } else {
+
+ MinLength = NameB->Length;
+ }
+
+ //
+ // Loop through looking at all of the characters in both strings
+ // testing for equalilty, less than, and greater than
+ //
+
+ i = RtlCompareMemory( NameA->Buffer, NameB->Buffer, MinLength );
+
+
+ if (i < MinLength) {
+
+ return (NameA->Buffer[i/2] < NameB->Buffer[i/2] ?
+ LessThan :
+ GreaterThan);
+ }
+
+ if (NameA->Length < NameB->Length) {
+
+ return LessThan;
+ }
+
+ if (NameA->Length > NameB->Length) {
+
+ return GreaterThan;
+ }
+
+ return EqualTo;
+}
diff --git a/private/ntos/cntfs/quotasup.c b/private/ntos/cntfs/quotasup.c
new file mode 100644
index 000000000..a6c219680
--- /dev/null
+++ b/private/ntos/cntfs/quotasup.c
@@ -0,0 +1,5064 @@
+/*++
+
+Copyright (c) 1996 Microsoft Corporation
+
+Module Name:
+
+ Quota.c
+
+Abstract:
+
+ This module implements the quota support routines for Ntfs
+
+Author:
+
+ Jeff Havens [JHavens] 29-Feb-1996
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define Dbg DEBUG_TRACE_QUOTA
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('QFtN')
+
+#define MAXIMUM_SID_LENGTH \
+ (FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * SID_MAX_SUB_AUTHORITIES)
+
+#define MAXIMUM_QUOTA_ROW (SIZEOF_QUOTA_USER_DATA + MAXIMUM_SID_LENGTH + sizeof(ULONG))
+
+//
+// CAIROBUG: This is temporary until have the correct routine to load the
+// security descriptor.
+//
+
+VOID
+NtfsLoadSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL
+ );
+
+//
+// Local quota support routines.
+//
+
+VOID
+NtfsClearAndVerifyQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsClearPerFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVOID Context
+ );
+
+VOID
+NtfsDeleteUnsedIds (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsMarkUserLimit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID Context
+ );
+
+VOID
+NtfsMarkQuotaCorrupt (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsPostUserLimit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ );
+
+NTSTATUS
+NtfsPrepareForDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSID Sid
+ );
+
+VOID
+NtfsRepairQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID Context
+ );
+
+VOID
+NtfsRepairPerFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVOID Context
+ );
+
+VOID
+NtfsSaveQuotaFlags (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+NtfsSaveQuotaFlagsSafe (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+NTSTATUS
+NtfsVerifyOwnerIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+RTL_GENERIC_COMPARE_RESULTS
+NtfsQuotaTableCompare (
+ IN PRTL_GENERIC_TABLE Table,
+ PVOID FirstStruct,
+ PVOID SecondStruct
+ );
+
+PVOID
+NtfsQuotaTableAllocate (
+ IN PRTL_GENERIC_TABLE Table,
+ CLONG ByteSize
+ );
+
+VOID
+NtfsQuotaTableFree (
+ IN PRTL_GENERIC_TABLE Table,
+ PVOID Buffer
+ );
+
+BOOLEAN NtfsAllowFixups = 1;
+BOOLEAN NtfsCheckQuota = 0;
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAcquireQuotaControl)
+#pragma alloc_text(PAGE, NtfsCalculateQuotaAdjustment)
+#pragma alloc_text(PAGE, NtfsClearAndVerifyQuotaIndex)
+#pragma alloc_text(PAGE, NtfsClearPerFileQuota)
+#pragma alloc_text(PAGE, NtfsDeleteUnsedIds)
+#pragma alloc_text(PAGE, NtfsDereferenceQuotaControlBlock)
+#pragma alloc_text(PAGE, NtfsExpandQuotaToAllocationSize)
+#pragma alloc_text(PAGE, NtfsFixupQuota)
+#pragma alloc_text(PAGE, NtfsFsQuotaQueryInfo)
+#pragma alloc_text(PAGE, NtfsFsQuotaSetInfo)
+#pragma alloc_text(PAGE, NtfsGetCallersUserId)
+#pragma alloc_text(PAGE, NtfsGetOwnerId)
+#pragma alloc_text(PAGE, NtfsGetRemainingQuota)
+#pragma alloc_text(PAGE, NtfsInitializeQuotaControlBlock)
+#pragma alloc_text(PAGE, NtfsInitializeQuotaIndex)
+#pragma alloc_text(PAGE, NtfsMarkQuotaCorrupt)
+#pragma alloc_text(PAGE, NtfsMarkUserLimit)
+#pragma alloc_text(PAGE, NtfsMoveQuotaOwner)
+#pragma alloc_text(PAGE, NtfsPostUserLimit)
+#pragma alloc_text(PAGE, NtfsPostRepairQuotaIndex)
+#pragma alloc_text(PAGE, NtfsPrepareForDelete)
+#pragma alloc_text(PAGE, NtfsReleaseQuotaControl)
+#pragma alloc_text(PAGE, NtfsRepairQuotaIndex)
+#pragma alloc_text(PAGE, NtfsSaveQuotaFlags)
+#pragma alloc_text(PAGE, NtfsSaveQuotaFlagsSafe)
+#pragma alloc_text(PAGE, NtfsQuotaTableCompare)
+#pragma alloc_text(PAGE, NtfsQuotaTableAllocate)
+#pragma alloc_text(PAGE, NtfsQuotaTableFree)
+#pragma alloc_text(PAGE, NtfsUpdateFileQuota)
+#pragma alloc_text(PAGE, NtfsUpdateQuotaDefaults)
+#pragma alloc_text(PAGE, NtfsVerifyOwnerIndex)
+#endif
+
+VOID
+NtfsAcquireQuotaControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ )
+
+/*++
+
+Routine Description:
+
+ Acquire the quota control block and quota index for shared update. Multiple
+ transactions can update then index, but only one thread can update a
+ particular index.
+
+Arguments:
+
+ QuotaControl - Quota control block to be acquired.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PVOID *Position;
+ PVOID *ScbArray;
+ ULONG Count;
+
+ PAGED_CODE();
+
+ ASSERT( QuotaControl->ReferenceCount > 0 );
+
+ //
+ // Make sure we have a free spot in the Scb array in the IrpContext.
+ //
+
+ if (IrpContext->SharedScb == NULL) {
+
+ Position = &IrpContext->SharedScb;
+ IrpContext->SharedScbSize = 1;
+
+ //
+ // Too bad the first one is not available. If the current size is one then allocate a
+ // new block and copy the existing value to it.
+ //
+
+ } else if (IrpContext->SharedScbSize == 1) {
+
+ if (IrpContext->SharedScb == QuotaControl) {
+
+ //
+ // The quota block has already been aquired.
+ //
+
+ return;
+ }
+
+ ScbArray = NtfsAllocatePool( PagedPool, sizeof( PVOID ) * 4 );
+ RtlZeroMemory( ScbArray, sizeof( PVOID ) * 4 );
+ *ScbArray = IrpContext->SharedScb;
+ IrpContext->SharedScb = ScbArray;
+ IrpContext->SharedScbSize = 4;
+ Position = ScbArray + 1;
+
+ //
+ // Otherwise look through the existing array and look for a free spot. Allocate a larger
+ // array if we need to grow it.
+ //
+
+ } else {
+
+ Position = IrpContext->SharedScb;
+ Count = IrpContext->SharedScbSize;
+
+ do {
+
+ if (*Position == NULL) {
+
+ break;
+ }
+
+ if (*Position == QuotaControl) {
+
+ //
+ // The quota block has already been aquired.
+ //
+
+ return;
+ }
+
+ Count -= 1;
+ Position += 1;
+
+ } while (Count != 0);
+
+ //
+ // If we didn't find one then allocate a new structure.
+ //
+
+ if (Count == 0) {
+
+ ScbArray = NtfsAllocatePool( PagedPool, sizeof( PVOID ) * IrpContext->SharedScbSize * 2 );
+ RtlZeroMemory( ScbArray, sizeof( PVOID ) * IrpContext->SharedScbSize * 2 );
+ RtlCopyMemory( ScbArray,
+ IrpContext->SharedScb,
+ sizeof( PVOID ) * IrpContext->SharedScbSize );
+
+ NtfsFreePool( IrpContext->SharedScb );
+ IrpContext->SharedScb = ScbArray;
+ Position = ScbArray + IrpContext->SharedScbSize;
+ IrpContext->SharedScbSize *= 2;
+ }
+ }
+
+ //
+ // The following assert is bougus, but I want know if we hit the case
+ // where create is acquiring the scb stream shared.
+ // Then make sure that the resource is released in create.c
+ //
+
+ ASSERT( IrpContext->MajorFunction != IRP_MJ_CREATE || IrpContext->OriginatingIrp != NULL || NtfsIsExclusiveScb( IrpContext->Vcb->QuotaTableScb ));
+
+ //
+ // Increase the reference count so the quota control block is not delete
+ // while it is in the shared list.
+ //
+
+ ASSERT( QuotaControl->ReferenceCount > 0 );
+ InterlockedIncrement( &QuotaControl->ReferenceCount );
+
+ ExAcquireResourceShared( IrpContext->Vcb->QuotaTableScb->Header.Resource, TRUE );
+ ExAcquireFastMutexUnsafe( QuotaControl->QuotaControlLock );
+
+ *Position = QuotaControl;
+
+}
+
+VOID
+NtfsCalculateQuotaAdjustment (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ OUT PLONGLONG Delta
+ )
+/*++
+
+Routine Description:
+
+ This routine scans the user data streams in a file and determines
+ by how much the quota needs to be adjusted.
+
+Arguments:
+
+ Fcb - Fcb whose quota usage is being modified.
+
+ Delta - Returns the amount of quota adjustment required for the file.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PSCB Scb;
+ UNICODE_STRING AttributeName;
+ BOOLEAN MoreToGo;
+
+ PAGED_CODE();
+
+ ASSERT_EXCLUSIVE_FCB( Fcb );
+
+ //
+ // There is nothing to do if the standard infor has not been
+ // expanded yet.
+ //
+
+ if (!FlagOn(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO)) {
+ *Delta = 0;
+ return;
+ }
+
+
+ NtfsInitializeAttributeContext( &Context );
+
+ //
+ // Enumerate all of the attributes.
+ //
+
+ for (MoreToGo = NtfsLookupAttribute( IrpContext, Fcb, &Fcb->FileReference, &Context );
+ MoreToGo;
+ MoreToGo = NtfsLookupNextAttribute( IrpContext, Fcb, &Context )) {
+
+ //
+ // Point to the current attribute.
+ //
+
+ Attribute = NtfsFoundAttribute( &Context );
+
+ if (Attribute->TypeCode == $STANDARD_INFORMATION) {
+
+ //
+ // Initialize quota amount to the value current
+ // in the standard information structure.
+ //
+
+ *Delta = -(LONGLONG) ((PSTANDARD_INFORMATION)
+ NtfsAttributeValue( Attribute ))->QuotaCharged;
+
+ }
+
+ if (!NtfsIsTypeCodeSubjectToQuota( Attribute->TypeCode ) ||
+ (Attribute->FormCode == NONRESIDENT_FORM &&
+ Attribute->Form.Nonresident.LowestVcn != 0)) {
+
+ continue;
+ }
+
+ //
+ // See if there is a Scb for this attribute.
+ //
+
+ NtfsInitializeStringFromAttribute( &AttributeName, Attribute );
+
+ Scb = NtfsCreateScb( IrpContext,
+ Fcb,
+ Attribute->TypeCode,
+ &AttributeName,
+ TRUE,
+ NULL );
+
+ if (Scb != NULL &&
+ FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED )) {
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+
+ *Delta += NtfsResidentStreamQuota( Scb->Vcb );
+
+ } else {
+
+ *Delta += Scb->Header.AllocationSize.QuadPart;
+ }
+
+ } else if (Attribute->FormCode == NONRESIDENT_FORM) {
+
+ *Delta += Attribute->Form.Nonresident.FileSize;
+
+ } else {
+
+ *Delta += Attribute->Form.Resident.ValueLength;
+ }
+
+ }
+
+ NtfsCleanupAttributeContext( &Context );
+
+}
+
+VOID
+NtfsClearAndVerifyQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine iterates over the quota user data index and verifies the back
+ pointer to the owner id index. It also zeros the quota used field for
+ each owner.
+
+Arguments:
+
+ Vcb - Pointer to the volume control block whoes index is to be operated
+ on.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ INDEX_KEY IndexKey;
+ INDEX_ROW OwnerRow;
+ MAP_HANDLE MapHandle;
+ PQUOTA_USER_DATA UserData;
+ PINDEX_ROW QuotaRow;
+ PVOID RowBuffer;
+ NTSTATUS Status;
+ ULONG OwnerId;
+ ULONG Count;
+ ULONG i;
+ PSCB QuotaScb = Vcb->QuotaTableScb;
+ PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
+ PINDEX_ROW IndexRow = NULL;
+ PREAD_CONTEXT ReadContext = NULL;
+ BOOLEAN IndexAcquired = FALSE;
+
+ //
+ // Assert the table is empty.
+ //
+
+ ASSERT( Vcb->QuotaControlTable.TableRoot == NULL);
+
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ //
+ // Allocate a buffer lager enough for several rows.
+ //
+
+ RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
+
+ try {
+
+ //
+ // Allocate a bunch of index row entries.
+ //
+
+ Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
+
+ IndexRow = NtfsAllocatePool( PagedPool,
+ Count * sizeof( INDEX_ROW ) );
+
+ //
+ // Iterate through the quota entries. Start where we left off.
+ //
+
+ OwnerId = Vcb->QuotaFileReference.SegmentNumberLowPart;
+ IndexKey.KeyLength = sizeof( OwnerId );
+ IndexKey.Key = &OwnerId;
+
+ Status = NtOfsReadRecords( IrpContext,
+ QuotaScb,
+ &ReadContext,
+ &IndexKey,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ PAGE_SIZE,
+ RowBuffer );
+
+
+ while (NT_SUCCESS( Status )) {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
+ IndexAcquired = TRUE;
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ Status = STATUS_VOLUME_DISMOUNTED;
+ leave;
+ }
+
+ QuotaRow = IndexRow;
+
+ for (i = 0; i < Count; i++, QuotaRow++) {
+
+ UserData = QuotaRow->DataPart.Data;
+
+ //
+ // Validate the record is long enough for the Sid.
+ //
+
+ IndexKey.KeyLength = RtlLengthSid( &UserData->QuotaSid );
+
+ if ( IndexKey.KeyLength + SIZEOF_QUOTA_USER_DATA >
+ QuotaRow->DataPart.DataLength ||
+ !RtlValidSid( &UserData->QuotaSid )) {
+
+ ASSERT( FALSE );
+
+ //
+ // The sid is bad delete the record.
+ //
+
+ NtOfsDeleteRecords( IrpContext,
+ QuotaScb,
+ 1,
+ &QuotaRow->KeyPart );
+
+ continue;
+ }
+
+ IndexKey.Key = &UserData->QuotaSid;
+
+ //
+ // Look up the Sid is in the owner id index.
+ //
+
+ Status = NtOfsFindRecord( IrpContext,
+ OwnerIdScb,
+ &IndexKey,
+ &OwnerRow,
+ &MapHandle,
+ NULL );
+
+ ASSERT( NT_SUCCESS( Status ) );
+
+ if (!NT_SUCCESS( Status )) {
+
+ //
+ // The owner id entry is missing. Add one back in.
+ //
+
+ OwnerRow.KeyPart = IndexKey;
+ OwnerRow.DataPart.DataLength = QuotaRow->KeyPart.KeyLength;
+ OwnerRow.DataPart.Data = QuotaRow->KeyPart.Key;
+
+ NtOfsAddRecords( IrpContext,
+ OwnerIdScb,
+ 1,
+ &OwnerRow,
+ FALSE );
+
+
+ } else {
+
+ //
+ // Verify that the owner id's match.
+ //
+
+ if (*((PULONG) QuotaRow->KeyPart.Key) !=
+ *((PULONG) OwnerRow.DataPart.Data)) {
+
+ ASSERT( FALSE );
+
+ //
+ // Keep the quota record with the lower
+ // quota id. Delete the one with the higher
+ // quota id. Note this is the simple approach
+ // and not best case of the lower id does not
+ // exist. In that case a user entry will be delete
+ // and be reassigned a default quota.
+ //
+
+ if (*((PULONG) QuotaRow->KeyPart.Key) <
+ *((PULONG) OwnerRow.DataPart.Data)) {
+
+ //
+ // Make the ownid's match.
+ //
+
+ OwnerRow.KeyPart = IndexKey;
+ OwnerRow.DataPart.DataLength = QuotaRow->KeyPart.KeyLength;
+ OwnerRow.DataPart.Data = QuotaRow->KeyPart.Key;
+
+ NtOfsUpdateRecord( IrpContext,
+ OwnerIdScb,
+ 1,
+ &OwnerRow,
+ NULL,
+ NULL );
+
+ } else {
+
+ //
+ // Delete this record and proceed.
+ //
+
+
+ NtOfsDeleteRecords( IrpContext,
+ QuotaScb,
+ 1,
+ &QuotaRow->KeyPart );
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ continue;
+
+ }
+ }
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ }
+
+ //
+ // Set the quota used to zero.
+ //
+
+ UserData->QuotaUsed = 0;
+ QuotaRow->DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ QuotaRow,
+ NULL,
+ NULL );
+
+ }
+
+ //
+ // Release the indexes and commit what has been done so far.
+ //
+
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ IndexAcquired = FALSE;
+
+ //
+ // Complete the request which commits the pending
+ // transaction if there is one and releases of the
+ // acquired resources. The IrpContext will not
+ // be deleted because the no delete flag is set.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ //
+ // Remember how far we got so we can restart correctly.
+ //
+
+ Vcb->QuotaFileReference.SegmentNumberLowPart =
+ *((PULONG) IndexRow[Count - 1].KeyPart.Key);
+
+ //
+ // Make sure the next free id is beyond the current ids.
+ //
+
+ if (Vcb->QuotaOwnerId <=
+ Vcb->QuotaFileReference.SegmentNumberLowPart) {
+
+ ASSERT( Vcb->QuotaOwnerId > Vcb->QuotaFileReference.SegmentNumberLowPart );
+ Vcb->QuotaOwnerId =
+ Vcb->QuotaFileReference.SegmentNumberLowPart + 1;
+ }
+
+ //
+ // Look up the next set of entries in the quota index.
+ //
+
+ Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
+ Status = NtOfsReadRecords( IrpContext,
+ QuotaScb,
+ &ReadContext,
+ NULL,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ PAGE_SIZE,
+ RowBuffer );
+
+ }
+
+ ASSERT( Status == STATUS_NO_MORE_MATCHES || Status == STATUS_NO_MATCH);
+
+ } finally {
+
+ NtfsFreePool( RowBuffer );
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ if (IndexAcquired) {
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ if (IndexRow != NULL) {
+ NtfsFreePool( IndexRow );
+ }
+
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+ }
+
+}
+
+VOID
+NtfsClearPerFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine clears the quota charged field in each file on the volume. The
+ Quata control block is also released in fcb.
+
+Arguments:
+
+ Fcb - Fcb for the file to be processed.
+
+ Context - Unsed.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ ULONGLONG NewQuota;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PSTANDARD_INFORMATION StandardInformation;
+ NTSTATUS Status;
+ PQUOTA_CONTROL_BLOCK QuotaControl = Fcb->QuotaControl;
+ PVCB Vcb = Fcb->Vcb;
+
+
+ UNREFERENCED_PARAMETER( Context);
+
+ PAGED_CODE();
+
+ //
+ // There is nothing to do if the standard info has not been
+ // expanded yet.
+ //
+
+ if (!FlagOn(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO)) {
+ return;
+ }
+
+ //
+ // Use a try-finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // Initialize the context structure.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ StandardInformation = (PSTANDARD_INFORMATION)
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ ASSERT(NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength ==
+ sizeof( STANDARD_INFORMATION ));
+
+ NewQuota = 0;
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ FIELD_OFFSET(STANDARD_INFORMATION, QuotaCharged),
+ &NewQuota,
+ sizeof( StandardInformation->QuotaCharged),
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+ //
+ // Release the quota control block for this fcb.
+ //
+
+ if (QuotaControl != NULL) {
+ NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+}
+
+VOID
+NtfsDeleteUnsedIds (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine iterates over the quota user data index and removes any
+ entries still marked as deleted.
+
+Arguments:
+
+ Vcb - Pointer to the volume control block whoes index is to be operated
+ on.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ INDEX_KEY IndexKey;
+ INDEX_ROW OwnerRow;
+ PINDEX_KEY KeyPtr;
+ PQUOTA_USER_DATA UserData;
+ PINDEX_ROW QuotaRow;
+ PVOID RowBuffer;
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG OwnerId;
+ ULONG Count;
+ ULONG i;
+ PSCB QuotaScb = Vcb->QuotaTableScb;
+ PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
+ PINDEX_ROW IndexRow = NULL;
+ PREAD_CONTEXT ReadContext = NULL;
+ BOOLEAN IndexAcquired = FALSE;
+
+ //
+ // Allocate a buffer lager enough for several rows.
+ //
+
+ RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
+
+ try {
+
+ //
+ // Allocate a bunch of index row entries.
+ //
+
+ Count = PAGE_SIZE / sizeof( QUOTA_USER_DATA );
+
+ IndexRow = NtfsAllocatePool( PagedPool,
+ Count * sizeof( INDEX_ROW ) );
+
+ //
+ // Iterate through the quota entries. Start where we left off.
+ //
+
+ OwnerId = Vcb->QuotaFileReference.SegmentNumberLowPart;
+ IndexKey.KeyLength = sizeof( OwnerId );
+ IndexKey.Key = &OwnerId;
+ KeyPtr = &IndexKey;
+
+ while (NT_SUCCESS( Status )) {
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ IndexAcquired = TRUE;
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ Status = STATUS_VOLUME_DISMOUNTED;
+ leave;
+ }
+
+ //
+ // Make sure the delete secquence number has not changed since
+ // the scan was delete.
+ //
+
+ if ((PVOID) Vcb->QuotaDeleteSecquence !=
+ IrpContext->Union.NtfsIoContext) {
+
+ //
+ // The scan needs to be restarted. Set the state to posted
+ // and raise status can not wait which will cause us to retry.
+ //
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ Status = NtOfsReadRecords( IrpContext,
+ QuotaScb,
+ &ReadContext,
+ KeyPtr,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ PAGE_SIZE,
+ RowBuffer );
+
+ if (!NT_SUCCESS( Status )) {
+ break;
+ }
+
+ QuotaRow = IndexRow;
+
+ for (i = 0; i < Count; i++, QuotaRow++) {
+ PQUOTA_CONTROL_BLOCK QuotaControl;
+
+ UserData = QuotaRow->DataPart.Data;
+
+ if (!FlagOn( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
+ continue;
+ }
+
+ //
+ // Check to see if there is a quota control entry
+ // for this id.
+ //
+
+ ASSERT( FIELD_OFFSET(QUOTA_CONTROL_BLOCK, OwnerId) <=
+ FIELD_OFFSET(INDEX_ROW, KeyPart.Key));
+
+ QuotaControl = RtlLookupElementGenericTable(
+ &Vcb->QuotaControlTable,
+ (PCHAR) &QuotaRow->KeyPart.Key -
+ FIELD_OFFSET(QUOTA_CONTROL_BLOCK, OwnerId));
+
+ //
+ // If there is a quota control entry or there is now
+ // some quota charged, then clear the deleted flag
+ // and update the entry.
+ //
+
+ if (QuotaControl != NULL || UserData->QuotaUsed != 0) {
+
+ ASSERT( QuotaControl == NULL && UserData->QuotaUsed == 0 );
+ ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
+
+ QuotaRow->DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ QuotaRow,
+ NULL,
+ NULL );
+
+ continue;
+ }
+
+ //
+ // Delete the user quota data record.
+ //
+
+ NtOfsDeleteRecords( IrpContext,
+ QuotaScb,
+ 1,
+ &QuotaRow->KeyPart );
+
+
+ IndexKey.Key = &UserData->QuotaSid;
+ IndexKey.KeyLength = RtlLengthSid( &UserData->QuotaSid );
+
+ //
+ // Delete the owner id record.
+ //
+
+
+ NtOfsDeleteRecords( IrpContext,
+ OwnerIdScb,
+ 1,
+ &IndexKey );
+
+ }
+
+ //
+ // Release the indexes and commit what has been done so far.
+ //
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ IndexAcquired = FALSE;
+
+ //
+ // Complete the request which commits the pending
+ // transaction if there is one and releases of the
+ // acquired resources. The IrpContext will not
+ // be deleted because the no delete flag is set.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_DONT_DELETE );
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ //
+ // Remember how far we got so we can restart correctly.
+ //
+
+ Vcb->QuotaFileReference.SegmentNumberLowPart =
+ *((PULONG) IndexRow[Count - 1].KeyPart.Key);
+
+ KeyPtr = NULL;
+
+ }
+
+ ASSERT( Status == STATUS_NO_MORE_MATCHES || Status == STATUS_NO_MATCH);
+
+ } finally {
+
+ NtfsFreePool( RowBuffer );
+
+ if (IndexAcquired) {
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ if (IndexRow != NULL) {
+ NtfsFreePool( IndexRow );
+ }
+
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+ }
+
+}
+
+VOID
+NtfsDereferenceQuotaControlBlock (
+ IN PVCB Vcb,
+ IN PQUOTA_CONTROL_BLOCK *QuotaControl
+ )
+/*++
+
+Routine Description:
+
+ This routine dereferences the quota control block.
+ If reference count is now zero the block will be deallocated.
+
+Arguments:
+
+ Vcb - Vcb for the volume that own the quota contorl block.
+
+ QuotaControl - Quota control block to be derefernece.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONG ReferenceCount;
+
+ PAGED_CODE();
+
+ //
+ // Lock the quota table.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ try {
+
+ //
+ // Update the reference count and add set the pointer to NULL
+ // in the Fcb.
+ //
+
+ ReferenceCount = InterlockedDecrement( &(*QuotaControl)->ReferenceCount );
+
+ ASSERT(ReferenceCount >= 0);
+
+ if (ReferenceCount == 0) {
+ RtlDeleteElementGenericTable( &Vcb->QuotaControlTable,
+ *QuotaControl );
+
+ }
+
+ *QuotaControl = NULL;
+
+ } finally {
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ }
+}
+
+VOID
+NtfsExpandQuotaToAllocationSize (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+/*++
+
+Routine Description:
+
+ This function changes the quota charged for a stream from file size to
+ allocation size.
+
+Arguments:
+
+ Scb - Supplies a pointer to the stream being opened.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG Delta;
+
+ PAGED_CODE();
+
+ if (!NtfsPerformQuotaOperation( Scb->Fcb ) ||
+ !FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) ||
+ FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED )) {
+
+ return;
+ }
+
+#if DBG
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
+ ACCESS_MASK DesiredAccess;
+
+ ASSERT(IrpContext->MajorFunction == IRP_MJ_CREATE);
+ DesiredAccess = IoGetCurrentIrpStackLocation( IrpContext->OriginatingIrp )->
+ Parameters.Create.SecurityContext->DesiredAccess;
+ ASSERT( FlagOn( DesiredAccess, FILE_WRITE_DATA | FILE_APPEND_DATA ));
+ }
+
+#endif // DBG
+
+ ASSERT( FlagOn( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED ));
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ Delta = NtfsResidentStreamQuota( Scb->Vcb ) -
+ (LONG) Scb->Header.FileSize.LowPart;
+ } else {
+ Delta = Scb->Header.AllocationSize.QuadPart -
+ Scb->Header.FileSize.QuadPart;
+ }
+
+ if (Delta != 0) {
+
+ //
+ // Do not check quota when opening a file.
+ //
+
+ NtfsUpdateFileQuota( IrpContext,
+ Scb->Fcb,
+ &Delta,
+ TRUE,
+ FALSE );
+
+ }
+
+ SetFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+}
+
+VOID
+NtfsFixupQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine ensures that the charged field is correct in the
+ standard information attribute of a file. If there is a problem
+ the it is fixed.
+
+Arguments:
+
+ Fcb - Pointer to the FCB of the file being opened.
+
+Return Value:
+
+ NONE
+
+--*/
+
+{
+ LONGLONG Delta;
+
+ PAGED_CODE();
+
+ ASSERT(FlagOn(Fcb->Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED));
+
+ if (Fcb->OwnerId != QUOTA_INVALID_ID) {
+
+ NtfsInitializeQuotaControlBlock( Fcb );
+ }
+
+ if (NtfsPerformQuotaOperation(Fcb)) {
+
+ NtfsCalculateQuotaAdjustment( IrpContext, Fcb, &Delta );
+
+ ASSERT(NtfsAllowFixups || FlagOn(Fcb->Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING) || Delta == 0);
+
+ if (Delta != 0) {
+#if DBG
+
+ if (IrpContext->OriginatingIrp != NULL ) {
+ PFILE_OBJECT FileObject;
+
+ FileObject = IoGetCurrentIrpStackLocation(
+ IrpContext->OriginatingIrp )->FileObject;
+
+ if (FileObject != NULL && FileObject->FileName.Buffer != NULL) {
+ DbgPrint( "NtfsFixupQuota: Quota fix up required on %Z of %I64x bytes\n",
+ &FileObject->FileName,
+ Delta );
+ }
+ }
+#endif
+
+ NtfsUpdateFileQuota( IrpContext, Fcb, &Delta, TRUE, FALSE );
+ }
+ }
+}
+
+NTSTATUS
+NtfsFsQuotaQueryInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the quota information for the volume.
+
+Arguments:
+
+ Vcb - Volume control block for the volume to be quered.
+
+ FileQuotaInfo - Buffer to return the data.
+
+ Length - In the size of the buffer. Out the amount of space remaining.
+
+Return Value:
+
+ Returns the status of the operation.
+
+--*/
+
+{
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ PINDEX_KEY KeyPtr;
+ PQUOTA_USER_DATA UserData;
+ PVOID RowBuffer;
+ ULONG SidLength;
+ NTSTATUS Status;
+ ULONG OwnerId;
+ ULONG Count = 1;
+ PREAD_CONTEXT ReadContext = NULL;
+
+ PAGED_CODE();
+
+ if (*Length < sizeof(FILE_QUOTA_INFORMATION)) {
+
+ //
+ // The user buffer is way too small.
+ //
+
+ return STATUS_BUFFER_TOO_SMALL;
+ }
+
+ //
+ // Return nothing if quotas are not enabled.
+ //
+
+ if (Vcb->QuotaTableScb == NULL) {
+
+ return STATUS_SUCCESS;
+
+ }
+
+ //
+ // Allocate a buffer lager enough for the largest quota entry and key.
+ //
+
+ RowBuffer = NtfsAllocatePool( PagedPool, MAXIMUM_QUOTA_ROW );
+
+ try {
+
+ //
+ // Initialize the new entry offset to zero.
+ //
+
+ FileQuotaInfo->NextEntryOffset = 0;
+
+ //
+ // Look up each entry in the quota index.
+ //
+
+ OwnerId = QUOTA_FISRT_USER_ID;
+ IndexKey.KeyLength = sizeof( OwnerId );
+ IndexKey.Key = &OwnerId;
+ KeyPtr = &IndexKey;
+
+ while (NT_SUCCESS( Status = NtOfsReadRecords( IrpContext,
+ Vcb->QuotaTableScb,
+ &ReadContext,
+ KeyPtr,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ &IndexRow,
+ MAXIMUM_QUOTA_ROW,
+ RowBuffer ))) {
+
+ ASSERT(Count == 1);
+
+ KeyPtr = NULL;
+
+ UserData = IndexRow.DataPart.Data;
+
+ //
+ // Skip this entry if it has been deleted.
+ //
+
+ if (FlagOn( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
+ continue;
+ }
+
+ //
+ // Determine if this entry will fit in the buffer.
+ //
+
+ SidLength = IndexRow.DataPart.DataLength - SIZEOF_QUOTA_USER_DATA;
+
+ if (*Length < SidLength + FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid) ) {
+
+ //
+ // Set the next entry offset to zero to
+ // indicate list termination.
+ //
+
+ FileQuotaInfo->NextEntryOffset = 0;
+
+ Status = STATUS_BUFFER_OVERFLOW;
+
+ break;
+ }
+
+ //
+ // Advance to the next entry. The first time though the loop
+ // NextEntryOffset is zero so FileQuotaInfo does not
+ // change.
+ //
+
+ FileQuotaInfo = Add2Ptr( FileQuotaInfo,
+ FileQuotaInfo->NextEntryOffset );
+
+ //
+ // Fill in the user buffer for this entry.
+ //
+
+ FileQuotaInfo->SidLength = SidLength;
+ FileQuotaInfo->ChangeTime.QuadPart = UserData->QuotaChangeTime;
+ FileQuotaInfo->QuotaUsed.QuadPart = UserData->QuotaUsed;
+ FileQuotaInfo->QuotaThreshold.QuadPart = UserData->QuotaThreshold;
+ FileQuotaInfo->QuotaLimit.QuadPart = UserData->QuotaLimit;
+ RtlCopyMemory( &FileQuotaInfo->Sid,
+ &UserData->QuotaSid,
+ SidLength );
+
+ //
+ // Calculate the next offset.
+ //
+
+ FileQuotaInfo->NextEntryOffset = QuadAlign( SidLength +
+ FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid));
+
+ //
+ // Add the offset to the amount used.
+ // NextEntryOffset may be sligthly larger than Length due to
+ // rounding of the previous entry size to longlong.
+ //
+
+ if (*Length > FileQuotaInfo->NextEntryOffset) {
+ *Length -= FileQuotaInfo->NextEntryOffset;
+ } else {
+ *Length = 0;
+ }
+
+ }
+
+ ASSERT( Status == STATUS_NO_MORE_MATCHES || Status == STATUS_NO_MATCH || Status == STATUS_BUFFER_OVERFLOW);
+
+ if (Status == STATUS_NO_MORE_MATCHES || Status == STATUS_NO_MATCH) {
+
+ //
+ // That was the last entry. We are done.
+ //
+
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Set the next entry offset to zero to
+ // indicate list termination.
+ //
+
+ FileQuotaInfo->NextEntryOffset = 0;
+
+ } finally {
+
+ NtfsFreePool( RowBuffer );
+
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+ }
+
+ return Status;
+}
+
+NTSTATUS
+NtfsFsQuotaSetInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine sets the quota information on the volume for the
+ owner pasted in from the user buffer.
+
+Arguments:
+
+ Vcb - Volume control block for the volume to be changed.
+
+ FileQuotaInfo - Buffer to return the data.
+
+ Length - The size of the buffer in bytes.
+
+Return Value:
+
+ Returns the status of the operation.
+
+--*/
+
+{
+ PFILE_QUOTA_INFORMATION LocalQuotaInfo;
+ ULONG SidLength;
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONG OwnerId;
+ ULONG LengthUsed = 0;
+
+ PAGED_CODE();
+
+ //
+ // Return nothing if quotas are not enabled.
+ //
+
+ if (Vcb->QuotaTableScb == NULL) {
+
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ }
+
+ //
+ // Validate the entire buffer before doing any work.
+ //
+
+ LocalQuotaInfo = FileQuotaInfo;
+
+ while (TRUE) {
+
+ if (Length < LengthUsed + FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid)) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (Length < LengthUsed +
+ FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid) +
+ LocalQuotaInfo->SidLength) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Validate the sid and length.
+ //
+
+ if (LocalQuotaInfo->SidLength < sizeof(SID)) {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ SidLength = RtlLengthSid( &LocalQuotaInfo->Sid );
+
+ if (LocalQuotaInfo->SidLength < SidLength ||
+ SidLength > MAXIMUM_SID_LENGTH ||
+ !RtlValidSid(&LocalQuotaInfo->Sid)) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (LocalQuotaInfo->NextEntryOffset == 0) {
+
+ //
+ // The buffer looks good.
+ //
+
+ break;
+ }
+
+ //
+ // Ensure the entries are non-overlapping.
+ //
+
+ if (LocalQuotaInfo->NextEntryOffset <
+ LocalQuotaInfo->SidLength +
+ FIELD_OFFSET(FILE_QUOTA_INFORMATION, Sid)) {
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Add the offset to the amount used.
+ //
+
+ LengthUsed += LocalQuotaInfo->NextEntryOffset;
+
+ //
+ // Advance to the next entry.
+ //
+
+ LocalQuotaInfo = Add2Ptr( LocalQuotaInfo,
+ LocalQuotaInfo->NextEntryOffset);
+
+ }
+
+ //
+ // Perform the requested updates.
+ //
+
+ LocalQuotaInfo = FileQuotaInfo;
+
+ while (TRUE) {
+
+ if (LocalQuotaInfo->QuotaLimit.QuadPart == -2) {
+
+ Status = NtfsPrepareForDelete( IrpContext,
+ Vcb,
+ &LocalQuotaInfo->Sid );
+
+ if (!NT_SUCCESS( Status )) {
+ break;
+ }
+
+ } else {
+
+ NtfsGetOwnerId( IrpContext,
+ &LocalQuotaInfo->Sid,
+ LocalQuotaInfo );
+
+ }
+
+ if (LocalQuotaInfo->NextEntryOffset == 0) {
+ break;
+ }
+
+ //
+ // Advance to the next entry.
+ //
+
+ LocalQuotaInfo = Add2Ptr( LocalQuotaInfo,
+ LocalQuotaInfo->NextEntryOffset);
+
+ }
+
+ //
+ // If the quota tracking has been requested and the quotas need to be
+ // repaired then try to repair them now.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED ) &&
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT |
+ QUOTA_FLAG_PENDING_DELETES )) {
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+
+ return Status;
+}
+
+ULONG
+NtfsGetOwnerId (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSID Sid,
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine determines the owner id for the requested SID. First the
+ Sid is looked up in the Owner Id index. If the entry exists, then that
+ owner id is returned. If the sid does not exist then new entry is
+ created in the owner id index.
+
+Arguments:
+
+ Sid - Security id to determine the owner id.
+
+ FileQuotaInfo - Optional quota data to update quota index with.
+
+Return Value:
+
+ Owner Id for the security id.
+
+--*/
+
+{
+ ULONG OwnerId;
+ ULONG DefaultId;
+ ULONG SidLength;
+ NTSTATUS Status;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ PQUOTA_USER_DATA NewQuotaData = NULL;
+ QUICK_INDEX_HINT QuickIndexHint;
+ PSCB QuotaScb;
+ PVCB Vcb = IrpContext->Vcb;
+ PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
+
+ BOOLEAN ExistingRecord;
+
+ PAGED_CODE();
+
+ //
+ // Determine the Sid length.
+ //
+
+ SidLength = RtlLengthSid( Sid );
+
+ IndexKey.KeyLength = SidLength;
+ IndexKey.Key = Sid;
+
+ //
+ // If there is quota information to update or there are pending deletes
+ // then long path must be taken where the user quota entry is found.
+ //
+
+ if (FileQuotaInfo == NULL &&
+ !FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
+
+ //
+ // Acquire the owner id index shared.
+ //
+
+ NtfsAcquireSharedScb( IrpContext, OwnerIdScb );
+
+ try {
+
+ //
+ // Assume the Sid is in the index.
+ //
+
+ Status = NtOfsFindRecord( IrpContext,
+ OwnerIdScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ //
+ // If the sid was found then capture is value.
+ //
+
+ if (NT_SUCCESS(Status)) {
+
+ ASSERT(IndexRow.DataPart.DataLength == sizeof(ULONG));
+ OwnerId = *((PULONG) IndexRow.DataPart.Data);
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ }
+
+ } finally {
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ }
+
+ //
+ // If the sid was found we are done.
+ //
+
+ if (NT_SUCCESS(Status)) {
+ return OwnerId;
+ }
+
+ }
+
+ //
+ // Acquire Owner id and quota index exclusive.
+ //
+
+ QuotaScb = Vcb->QuotaTableScb;
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
+
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ try {
+
+ //
+ // Verify that the sid is still not in the index.
+ //
+
+ Status = NtOfsFindRecord( IrpContext,
+ OwnerIdScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ //
+ // If the sid was found then capture the owner id.
+ //
+
+ ExistingRecord = NT_SUCCESS(Status);
+
+ if (ExistingRecord) {
+
+ ASSERT(IndexRow.DataPart.DataLength == sizeof(ULONG));
+ OwnerId = *((PULONG) IndexRow.DataPart.Data);
+
+ if (FileQuotaInfo == NULL &&
+ !FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
+ leave;
+ }
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ } else {
+
+ //
+ // Allocate a new owner id and update the owner index.
+ //
+
+ OwnerId = Vcb->QuotaOwnerId++;
+
+ IndexRow.KeyPart.KeyLength = SidLength;
+ IndexRow.KeyPart.Key = Sid;
+ IndexRow.DataPart.Data = &OwnerId;
+ IndexRow.DataPart.DataLength = sizeof(OwnerId);
+
+ NtOfsAddRecords( IrpContext,
+ OwnerIdScb,
+ 1,
+ &IndexRow,
+ FALSE );
+
+ }
+
+ //
+ // Allocate space for the new quota user data.
+ //
+
+ NewQuotaData = NtfsAllocatePool( PagedPool,
+ SIZEOF_QUOTA_USER_DATA + SidLength);
+
+ if (ExistingRecord) {
+
+ //
+ // Find the existing record and update it.
+ //
+
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &OwnerId;
+
+ RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ &QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+
+ ASSERT( NT_SUCCESS( Status ));
+ NtfsMarkQuotaCorrupt( IrpContext, Vcb );
+ OwnerId = QUOTA_INVALID_ID;
+ leave;
+ }
+
+ ASSERT( IndexRow.DataPart.DataLength == SIZEOF_QUOTA_USER_DATA + SidLength );
+
+ RtlCopyMemory( NewQuotaData, IndexRow.DataPart.Data, IndexRow.DataPart.DataLength );
+
+ ASSERT( RtlEqualMemory( &NewQuotaData->QuotaSid, Sid, SidLength ));
+
+ //
+ // Update the changed fields in the record.
+ //
+
+ if (FileQuotaInfo != NULL) {
+
+ ClearFlag( NewQuotaData->QuotaFlags, QUOTA_FLAG_DEFAULT_LIMITS );
+ NewQuotaData->QuotaThreshold = FileQuotaInfo->QuotaThreshold.QuadPart;
+ NewQuotaData->QuotaLimit = FileQuotaInfo->QuotaLimit.QuadPart;
+ KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData->QuotaChangeTime );
+
+ } else if (!FlagOn( NewQuotaData->QuotaFlags, QUOTA_FLAG_ID_DELETED )) {
+
+ //
+ // There is nothing to update just return.
+ //
+
+ leave;
+ }
+
+ //
+ // Always clear the deleted flag.
+ //
+
+ ClearFlag( NewQuotaData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
+
+ //
+ // The key length does not change.
+ //
+
+ IndexRow.KeyPart.Key = &OwnerId;
+ IndexRow.DataPart.Data = NewQuotaData;
+ IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexRow,
+ &QuickIndexHint,
+ &MapHandle );
+
+ leave;
+ }
+
+ if (FileQuotaInfo == NULL) {
+
+ //
+ // Look up the default quota limits.
+ //
+
+ DefaultId = QUOTA_DEFAULTS_ID;
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &DefaultId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ if (!NT_SUCCESS( Status )) {
+
+ ASSERT( NT_SUCCESS( Status ));
+ NtfsRaiseStatus( IrpContext,
+ STATUS_QUOTA_LIST_INCONSISTENT,
+ NULL,
+ Vcb->QuotaTableScb->Fcb );
+
+ }
+
+ ASSERT(IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA);
+
+ //
+ // Initialize the new quota entry with the defaults.
+ //
+
+ RtlCopyMemory( NewQuotaData,
+ IndexRow.DataPart.Data,
+ SIZEOF_QUOTA_USER_DATA );
+
+ NewQuotaData->QuotaFlags &= QUOTA_FLAG_USER_MASK;
+
+ } else {
+
+ //
+ // Initialize the new record with the new data.
+ //
+
+ RtlZeroMemory( NewQuotaData, SIZEOF_QUOTA_USER_DATA);
+
+ NewQuotaData->QuotaVersion = QUOTA_USER_VERSION;
+ NewQuotaData->QuotaThreshold = FileQuotaInfo->QuotaThreshold.QuadPart;
+ NewQuotaData->QuotaLimit = FileQuotaInfo->QuotaLimit.QuadPart;
+
+ }
+
+ //
+ // Copy the Sid into the new record.
+ //
+
+ RtlCopyMemory( &NewQuotaData->QuotaSid, Sid, SidLength);
+ KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData->QuotaChangeTime );
+
+ //
+ // Add the new quota data record to the index.
+ //
+
+ IndexRow.KeyPart.KeyLength = sizeof(ULONG);
+ IndexRow.KeyPart.Key = &OwnerId;
+ IndexRow.DataPart.Data = NewQuotaData;
+ IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA + SidLength;
+
+ NtOfsAddRecords( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexRow,
+ TRUE );
+
+ } finally {
+
+ if (NewQuotaData != NULL) {
+ NtfsFreePool( NewQuotaData );
+ }
+
+ //
+ // Release the index map handle and index resources.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ }
+
+ return OwnerId;
+}
+
+VOID
+NtfsGetRemainingQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN ULONG OwnerId,
+ OUT PLONGLONG RemainingQuota,
+ IN OUT PQUICK_INDEX_HINT QuickIndexHint OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine returns the remaining amount of quota a user has before a
+ the quota limit is reached.
+
+Arguments:
+
+ Fcb - Fcb whose quota usage is being checked.
+
+ OwnerId - Supplies the owner id to look up.
+
+ RemainingQuota - Returns the remaining amount of quota in bytes.
+
+ QuickIndexHint - Supplies an optional hint where to look of the value.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PQUOTA_USER_DATA UserData;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ NTSTATUS Status;
+ PVCB Vcb = IrpContext->Vcb;
+
+ PAGED_CODE();
+
+ //
+ // Initialize the map handle.
+ //
+
+ NtOfsInitializeMapHandle( &MapHandle );
+ NtfsAcquireSharedScb( IrpContext, Vcb->QuotaTableScb );
+
+ try {
+
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &OwnerId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // This look up should not fail.
+ //
+
+ ASSERT( NT_SUCCESS(Status) );
+
+ //
+ // There is one case where this could occur. That is a
+ // owner id could be deleted while this ccb was in use.
+ //
+
+ *RemainingQuota = 0;
+ leave;
+ }
+
+ UserData = IndexRow.DataPart.Data;
+
+ if (UserData->QuotaUsed > UserData->QuotaLimit) {
+
+ *RemainingQuota = 0;
+
+ } else {
+
+ *RemainingQuota = UserData->QuotaLimit - UserData->QuotaUsed;
+ }
+
+ } finally {
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
+ }
+
+}
+
+
+VOID
+NtfsInitializeQuotaControlBlock (
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This rouinte initializes the quota control block field in the Fcb. First
+ a lookup is done in the quota control table for an existing quota control
+ block. If there is no quota control block, then a new one is created.
+
+Arguments:
+
+ Fcb - Pointer to fcb being opened.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ PQUOTA_CONTROL_BLOCK QuotaControl;
+ BOOLEAN NewEntry;
+ PQUOTA_CONTROL_BLOCK InitQuotaControl = NULL;
+ PFAST_MUTEX Lock = NULL;
+ PVCB Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ ASSERT(Fcb->OwnerId != 0);
+ ASSERT(Fcb->QuotaControl == NULL);
+ ASSERT( NtfsIsExclusiveFcb( Fcb ));
+
+ //
+ // Lock the quota table.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ try {
+
+ QuotaControl = RtlLookupElementGenericTable( &Vcb->QuotaControlTable,
+ (PCHAR) &Fcb->OwnerId -
+ FIELD_OFFSET(QUOTA_CONTROL_BLOCK, OwnerId));
+
+ if (QuotaControl == NULL) {
+
+ //
+ // Allocate and initialize the template control block.
+ // Allocate enough space
+ // in the quota control block for the index data part.
+ // This is used as the new record when calling update record.
+ //
+
+ InitQuotaControl = NtfsAllocatePool( PagedPool,
+ sizeof(QUOTA_CONTROL_BLOCK) +
+ SIZEOF_QUOTA_USER_DATA );
+
+ RtlZeroMemory( InitQuotaControl,
+ sizeof(QUOTA_CONTROL_BLOCK) +
+ SIZEOF_QUOTA_USER_DATA );
+
+ InitQuotaControl->NodeTypeCode = NTFS_NTC_QUOTA_CONTROL;
+ InitQuotaControl->NodeByteSize = sizeof(QUOTA_CONTROL_BLOCK) +
+ SIZEOF_QUOTA_USER_DATA;
+ InitQuotaControl->OwnerId = Fcb->OwnerId;
+
+ //
+ // Allocate and initialize the lock.
+ //
+
+ Lock = NtfsAllocatePoolWithTag( NonPagedPool,
+ sizeof(FAST_MUTEX),
+ 'QftN' );
+
+ ExInitializeFastMutex( Lock );
+
+ //
+ // Insert table element into table.
+ //
+
+ QuotaControl = RtlInsertElementGenericTable( &Vcb->QuotaControlTable,
+ InitQuotaControl,
+ sizeof(QUOTA_CONTROL_BLOCK) +
+ SIZEOF_QUOTA_USER_DATA,
+ &NewEntry );
+
+ ASSERT(((ULONG) &QuotaControl->QuickIndexHint & (sizeof(LONGLONG) - 1)) == 0);
+
+ QuotaControl->QuotaControlLock = Lock;
+ Lock = NULL;
+
+ }
+
+ //
+ // Update the reference count and add set the pointer in the Fcb.
+ //
+
+ InterlockedIncrement( &QuotaControl->ReferenceCount );
+
+ Fcb->QuotaControl = QuotaControl;
+ ASSERT( Fcb->OwnerId == QuotaControl->OwnerId );
+
+ } finally {
+
+ //
+ // Clean up.
+ //
+
+ if (InitQuotaControl != NULL) {
+ NtfsFreePool( InitQuotaControl );
+
+ if (Lock != NULL) {
+ NtfsFreePool( Lock );
+ }
+ }
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ }
+}
+
+VOID
+NtfsInitializeQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens the quota index for the volume. If the index does not
+ exist it is created and initialized.
+
+Arguments:
+
+ Fcb - Pointer to Fcb for the quota file.
+
+ Vcb - Volume control block for volume be mounted.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG Key;
+ NTSTATUS Status;
+ INDEX_ROW IndexRow;
+ MAP_HANDLE MapHandle;
+ QUOTA_USER_DATA QuotaData;
+ UNICODE_STRING IndexName = { sizeof( L"$Q" ) - 2, sizeof( L"$Q" ), L"$Q" };
+
+ PAGED_CODE();
+
+ //
+ // Initialize quota table and fast mutex.
+ //
+
+ ExInitializeFastMutex( &Vcb->QuotaControlLock );
+
+ RtlInitializeGenericTable( &Vcb->QuotaControlTable,
+ NtfsQuotaTableCompare,
+ NtfsQuotaTableAllocate,
+ NtfsQuotaTableFree,
+ NULL );
+
+ NtOfsCreateIndex( IrpContext,
+ Fcb,
+ IndexName,
+ CREATE_OR_OPEN,
+ 0,
+ NtOfsCollateUlong,
+ NULL,
+ &Vcb->QuotaTableScb );
+
+ IndexName.Buffer = L"$O";
+
+ NtOfsCreateIndex( IrpContext,
+ Fcb,
+ IndexName,
+ CREATE_OR_OPEN,
+ 0,
+ NtOfsCollateSid,
+ NULL,
+ &Vcb->OwnerIdTableScb );
+
+ //
+ // Find the next owner id to allocate.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
+
+ try {
+
+ //
+ // Initialize quota delete secquence number.
+ //
+
+ Vcb->QuotaDeleteSecquence = 1;
+
+ //
+ // Load the quota flags.
+ //
+
+ Key = QUOTA_DEFAULTS_ID;
+ IndexRow.KeyPart.KeyLength = sizeof( ULONG );
+ IndexRow.KeyPart.Key = &Key;
+
+ Status = NtOfsFindRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ &IndexRow.KeyPart,
+ &IndexRow,
+ &MapHandle,
+ NULL);
+
+ if (NT_SUCCESS(Status)) {
+
+ //
+ // The index already exists, just initialize the quota
+ // fields in the VCB.
+ //
+
+ Vcb->QuotaFlags =
+ ((PQUOTA_USER_DATA) IndexRow.DataPart.Data)->QuotaFlags;
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ } else if (Status == STATUS_NO_MATCH) {
+
+ //
+ // The index was newly created.
+ // Create a default quota data row.
+ //
+
+ Key = QUOTA_DEFAULTS_ID;
+
+ RtlZeroMemory( &QuotaData, sizeof(QUOTA_USER_DATA));
+
+ //
+ // Indicate that the quota needs to be rebuilt.
+ //
+
+ QuotaData.QuotaVersion = QUOTA_USER_VERSION;
+
+ QuotaData.QuotaFlags = QUOTA_FLAG_DEFAULT_LIMITS;
+
+ //
+ // CAIROBUG: For now always enable quota traking and enforment.
+ //
+
+ SetFlag( QuotaData.QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_ENFORCEMENT_ENABLED |
+ QUOTA_FLAG_TRACKING_REQUESTED |
+ QUOTA_FLAG_LOG_THRESHOLD |
+ QUOTA_FLAG_LOG_LIMIT);
+
+ QuotaData.QuotaThreshold = MAXULONGLONG;
+ QuotaData.QuotaLimit = MAXULONGLONG;
+ KeQuerySystemTime( (PLARGE_INTEGER) &QuotaData.QuotaChangeTime );
+
+ IndexRow.KeyPart.KeyLength = sizeof( ULONG );
+ IndexRow.KeyPart.Key = &Key;
+ IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+ IndexRow.DataPart.Data = &QuotaData;
+
+ NtOfsAddRecords( IrpContext,
+ Vcb->QuotaTableScb,
+ 1,
+ &IndexRow,
+ TRUE );
+
+ Vcb->QuotaOwnerId = QUOTA_FISRT_USER_ID;
+
+ Vcb->QuotaFlags = QuotaData.QuotaFlags;
+
+ }
+
+ Key = MAXULONG;
+ IndexRow.KeyPart.KeyLength = sizeof( ULONG );
+ IndexRow.KeyPart.Key = &Key;
+
+ Status = NtOfsFindLastRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ &IndexRow.KeyPart,
+ &IndexRow,
+ &MapHandle );
+
+ if (!NT_SUCCESS( Status) ) {
+
+ //
+ // This call should never fail.
+ //
+
+ ASSERT( NT_SUCCESS( Status) );
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT);
+ leave;
+ }
+
+ Key = *((PULONG) IndexRow.KeyPart.Key) + 1;
+
+ if (Key < QUOTA_FISRT_USER_ID) {
+ Key = QUOTA_FISRT_USER_ID;
+ }
+
+ Vcb->QuotaOwnerId = Key;
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ //
+ // Fix up the quota on the root directory.
+ //
+
+ NtfsConditionallyFixupQuota( IrpContext, Vcb->RootIndexScb->Fcb );
+
+ } finally {
+ NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
+ }
+
+ //
+ // If the quota tracking has been requested and the quotas need to be
+ // repaired then try to repair them now.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED) &&
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE | QUOTA_FLAG_CORRUPT | QUOTA_FLAG_PENDING_DELETES )) {
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+}
+
+VOID
+NtfsMarkQuotaCorrupt (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine attempts to mark the quota index corrupt. It will
+ also attempt post a request to rebuild the quota index.
+
+Arguments:
+
+ Vcb - Supplies a pointer the the volume who quota data is corrupt.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+
+ DbgPrint( "NtfsMarkQuotaCorrupt: Marking quota dirty on Vcb = %lx\n", Vcb);
+
+ if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT )) {
+
+ //
+ // If the quota were not previous corrupt then log an event
+ // so others know this occured.
+ //
+
+ NtfsLogEvent( IrpContext,
+ NULL,
+ IO_FILE_QUOTA_CORRUPT,
+ STATUS_FILE_CORRUPT_ERROR );
+ }
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_CORRUPT );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
+
+ //
+ // Since the index is corrupt there is no point in tracking the
+ // quota usage.
+ //
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED );
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ //
+ // Do not save the flags here since the quota scb may be acquired
+ // shared. The repair will save the flags when it runs.
+ // Try to fix the problems.
+ //
+
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+
+}
+
+
+VOID
+NtfsMarkUserLimit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine marks a user's quota data entry to indicate that the user
+ has exceeded quota. The event is also logged.
+
+Arguments:
+
+ Context - Supplies a pointer to the referenced quota control block.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PQUOTA_CONTROL_BLOCK QuotaControl = Context;
+ PVCB Vcb = IrpContext->Vcb;
+ LARGE_INTEGER CurrentTime;
+ PQUOTA_USER_DATA UserData;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ NTSTATUS Status;
+ BOOLEAN IndexAcquired;
+
+ PAGED_CODE();
+
+ DbgPrint( "NtfsMarkUserLimit: Quota limit called for owner id = %lx\n", QuotaControl->OwnerId );
+
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
+ IndexAcquired = TRUE;
+
+ try {
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ leave;
+ }
+
+ //
+ // Get the user's quota data entry.
+ //
+
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &QuotaControl->OwnerId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ &QuotaControl->QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // This look up should not fail.
+ //
+
+ ASSERT( NT_SUCCESS(Status) );
+ NtfsMarkQuotaCorrupt( IrpContext, IrpContext->Vcb );
+ leave;
+ }
+
+ //
+ // Space is allocated for the new record after the quota control
+ // block.
+ //
+
+ UserData = (PQUOTA_USER_DATA) (QuotaControl + 1);
+ ASSERT(IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA );
+
+ RtlCopyMemory( UserData,
+ IndexRow.DataPart.Data,
+ SIZEOF_QUOTA_USER_DATA );
+
+ KeQuerySystemTime( &CurrentTime );
+ UserData->QuotaChangeTime = CurrentTime.QuadPart;
+
+ //
+ // Indicate that user exceeded quota.
+ //
+
+ UserData->QuotaExceededTime = CurrentTime.QuadPart;
+ SetFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
+
+ //
+ // Log the limit event. If this fails then leave.
+ //
+
+ if (!NtfsLogEvent( IrpContext,
+ IndexRow.DataPart.Data,
+ IO_FILE_QUOTA_LIMIT,
+ STATUS_DISK_FULL )) {
+ leave;
+ }
+
+ //
+ // The key length does not change.
+ //
+
+ IndexRow.KeyPart.Key = &QuotaControl->OwnerId;
+ IndexRow.DataPart.Data = UserData;
+ IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ 1,
+ &IndexRow,
+ &QuotaControl->QuickIndexHint,
+ &MapHandle );
+
+ } finally {
+
+ //
+ // No matter what happened we need to dereference the quota control
+ // block and clear the post flag.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ASSERT( FlagOn( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED ));
+ ClearFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ NtfsDereferenceQuotaControlBlock( Vcb, &QuotaControl );
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ NtfsReleaseScb( IrpContext, Vcb->QuotaTableScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ }
+
+}
+
+
+VOID
+NtfsMoveQuotaOwner (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_DESCRIPTOR Security
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the owner id and quota charged for a file when the
+ file owner is changed.
+
+Arguments:
+
+ Fcb - Pointer to fcb being opened.
+
+ Security - Pointer to the new security descriptor
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LONGLONG QuotaCharged;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PSTANDARD_INFORMATION StandardInformation;
+ PSID Sid;
+ ULONG OwnerId;
+ NTSTATUS Status;
+ BOOLEAN OwnerDefaulted;
+
+ PAGED_CODE();
+
+ if (!NtfsPerformQuotaOperation(Fcb)) {
+ return;
+ }
+
+ //
+ // Extract the security id from the security descriptor.
+ //
+
+ Status = RtlGetOwnerSecurityDescriptor( Security,
+ &Sid,
+ &OwnerDefaulted );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, Fcb );
+ }
+
+ //
+ // Generate a owner id for the Fcb.
+ //
+
+ OwnerId = NtfsGetOwnerId( IrpContext, Sid, NULL );
+
+ if (OwnerId == Fcb->OwnerId) {
+
+ //
+ // The owner is not changing so just return.
+ //
+
+ return;
+ }
+
+ //
+ // Initialize the context structure and map handle.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+
+ //
+ // Preacquire the quota index exclusive since an entry may need to
+ // be added.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Fcb->Vcb->QuotaTableScb );
+
+ try {
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ StandardInformation = (PSTANDARD_INFORMATION)
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ QuotaCharged = -((LONGLONG) StandardInformation->QuotaCharged);
+
+ NtfsCleanupAttributeContext( &AttrContext );
+
+ //
+ // Remove the quota from the old owner.
+ //
+
+ NtfsUpdateFileQuota( IrpContext,
+ Fcb,
+ &QuotaCharged,
+ TRUE,
+ FALSE );
+
+ //
+ // Set the new owner id.
+ //
+
+ Fcb->OwnerId = OwnerId;
+
+ //
+ // Note the old quota block is kept around until the operation is
+ // complete. This is so the recovery code does not have allocate
+ // a memory if the old quota block is needed. This is done in
+ // NtfsCommonSetSecurityInfo.
+ //
+
+ Fcb->QuotaControl = NULL;
+ NtfsInitializeQuotaControlBlock( Fcb );
+
+ //
+ // Try to charge the quota to the new owner.
+ //
+
+ QuotaCharged = (LONGLONG) StandardInformation->QuotaCharged;
+ NtfsUpdateFileQuota( IrpContext,
+ Fcb,
+ &QuotaCharged,
+ TRUE,
+ TRUE );
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtfsReleaseScb( IrpContext, Fcb->Vcb->QuotaTableScb );
+ }
+}
+
+VOID
+NtfsPostRepairQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine posts a request to recalculate all of the user quota data.
+
+Arguments:
+
+ Vcb - Volume control block for volume whos quota needs to be fixed.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+
+ try {
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ if (FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING)) {
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ leave;
+ }
+
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED);
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ //
+ // Post this special request.
+ //
+
+ NtfsPostSpecial( IrpContext,
+ Vcb,
+ NtfsRepairQuotaIndex,
+ NULL );
+
+
+ } finally {
+
+ if (AbnormalTermination()) {
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_POSTED)
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ }
+ }
+}
+
+VOID
+NtfsPostUserLimit (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ )
+
+/*++
+
+Routine Description:
+
+ This routine posts a request to save the fact that the user has exceeded
+ their limit.
+
+Arguments:
+
+ Vcb - Volume control block for volume whos quota needs to be fixed.
+
+ QuotaControl - Quota control block for the user.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+
+ try {
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ if (FlagOn( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED )) {
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ leave;
+ }
+
+ SetFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
+
+ //
+ // Reference the quota control block so it does not go away.
+ //
+
+ InterlockedIncrement( &QuotaControl->ReferenceCount );
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ //
+ // Post this special request.
+ //
+
+ NtfsPostSpecial( IrpContext,
+ Vcb,
+ NtfsMarkUserLimit,
+ QuotaControl );
+
+ } finally {
+
+ if (AbnormalTermination()) {
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( QuotaControl->Flags, QUOTA_FLAG_LIMIT_POSTED );
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ }
+ }
+}
+
+NTSTATUS
+NtfsPrepareForDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PSID Sid
+ )
+
+/*++
+
+Routine Description:
+
+ This routine determines if an owner id is a candidate for deletion. If
+ the id appears deletable its user data is reset to the defaults and the
+ entry is marked as deleted. Later a worker thread will do the actual
+ deletion.
+
+Arguments:
+
+ Vcb - Supplies a pointer to the volume containing the entry to be deleted.
+
+ Sid - Security id to to be deleted.
+
+Return Value:
+
+ Returns a status indicating of the id was deletable at this time.
+
+--*/
+{
+ ULONG OwnerId;
+ ULONG DefaultOwnerId;
+ NTSTATUS Status = STATUS_SUCCESS;
+ INDEX_ROW IndexRow;
+ INDEX_ROW NewIndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ PQUOTA_CONTROL_BLOCK QuotaControl;
+ QUOTA_USER_DATA NewQuotaData;
+ PSCB QuotaScb = Vcb->QuotaTableScb;
+ PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
+
+ PAGED_CODE();
+
+ //
+ // Determine the Sid length.
+ //
+
+ IndexKey.KeyLength = RtlLengthSid( Sid );
+ IndexKey.Key = Sid;
+
+ //
+ // Acquire Owner id and quota index exclusive.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ try {
+
+ //
+ // Look up the SID in the owner index.
+ //
+
+ Status = NtOfsFindRecord( IrpContext,
+ OwnerIdScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+ leave;
+ }
+
+ //
+ // If the sid was found then capture the owner id.
+ //
+
+ ASSERT(IndexRow.DataPart.DataLength == sizeof(ULONG));
+ OwnerId = *((PULONG) IndexRow.DataPart.Data);
+
+ //
+ // Release the index map handle.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ //
+ // Find the existing record and update it.
+ //
+
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &OwnerId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+
+ ASSERT( NT_SUCCESS( Status ));
+ NtfsMarkQuotaCorrupt( IrpContext, Vcb );
+ leave;
+ }
+
+ RtlCopyMemory( &NewQuotaData, IndexRow.DataPart.Data, SIZEOF_QUOTA_USER_DATA );
+
+ //
+ // Check to see if there is a quota control entry
+ // for this id.
+ //
+
+ ASSERT( FIELD_OFFSET(QUOTA_CONTROL_BLOCK, OwnerId) <=
+ FIELD_OFFSET(INDEX_ROW, KeyPart.Key));
+
+ QuotaControl = RtlLookupElementGenericTable(
+ &Vcb->QuotaControlTable,
+ (PCHAR) &IndexRow.KeyPart.Key -
+ FIELD_OFFSET(QUOTA_CONTROL_BLOCK, OwnerId));
+
+ //
+ // If there is a quota control entry or there is now
+ // some quota charged, then the entry cannot be deleted.
+ //
+
+ if (QuotaControl != NULL || NewQuotaData.QuotaUsed != 0) {
+
+ Status = STATUS_CANNOT_DELETE;
+ leave;
+ }
+
+ //
+ // Find the default quota record.
+ //
+
+ DefaultOwnerId = QUOTA_DEFAULTS_ID;
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &DefaultOwnerId;
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ NULL );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
+ }
+
+ //
+ // Set the user entry to the current defaults. Then if the entry
+ // is really inuse it will appear that is came back after the delete.
+ //
+
+ RtlCopyMemory( &NewQuotaData,
+ IndexRow.DataPart.Data,
+ SIZEOF_QUOTA_USER_DATA );
+
+ NewQuotaData.QuotaFlags &= QUOTA_FLAG_USER_MASK;
+
+ //
+ // Set the deleted flag.
+ //
+
+ SetFlag( NewQuotaData.QuotaFlags, QUOTA_FLAG_ID_DELETED );
+
+ //
+ // The key length does not change.
+ //
+
+ NewIndexRow.KeyPart.Key = &OwnerId;
+ NewIndexRow.KeyPart.KeyLength = sizeof(ULONG);
+ NewIndexRow.DataPart.Data = &NewQuotaData;
+ NewIndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ &NewIndexRow,
+ NULL,
+ NULL );
+
+ //
+ // Update the delete secquence number this is used to indicate
+ // another id has been deleted. If the repair code is in the
+ // middle of its scan it must restart the scan.
+ //
+
+ Vcb->QuotaDeleteSecquence++;
+
+ //
+ // Indicate there are pending deletes.
+ //
+
+ if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
+
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES );
+
+ ASSERT( IndexRow.DataPart.DataLength <= sizeof( QUOTA_USER_DATA ));
+
+ RtlCopyMemory( &NewQuotaData,
+ IndexRow.DataPart.Data,
+ IndexRow.DataPart.DataLength );
+
+ //
+ // Update the changed fields in the record.
+ //
+
+ NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
+
+ //
+ // Note the sizes in the IndexRow stay the same.
+ //
+
+ IndexRow.KeyPart.Key = &DefaultOwnerId;
+ IndexRow.DataPart.Data = &NewQuotaData;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexRow,
+ NULL,
+ NULL );
+
+ }
+
+ } finally {
+
+ //
+ // Release the index map handle and index resources.
+ //
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ }
+
+ return Status;
+}
+
+VOID
+NtfsRepairQuotaIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by a worker thread to fix the quota indexes
+ and recalculate all of the quota values.
+
+Arguments:
+
+ Context - Unused.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVCB Vcb = IrpContext->Vcb;
+ ULONG State;
+ NTSTATUS Status;
+ ULONG RetryCount = 0;
+
+ UNREFERENCED_PARAMETER( Context);
+
+ try {
+
+ DbgPrint( "NtfsRepairQuotaIndex: Starting quota repair. Vcb = %lx\n", Vcb );
+
+ //
+ // Acquire the volume exclusive and the quota lock.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ Status = STATUS_SUCCESS;
+
+ if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED )) {
+
+ //
+ // There is no point in doing any of this work if tracking
+ // is not requested.
+ //
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else if (FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING ) ==
+ VCB_QUOTA_REPAIR_POSTED) {
+
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT |
+ QUOTA_FLAG_PENDING_DELETES) ==
+ QUOTA_FLAG_PENDING_DELETES ) {
+
+ //
+ // Only the last to phases need to be run.
+ //
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_RECALC_STARTED );
+
+ State = VCB_QUOTA_RECALC_STARTED;
+
+ //
+ // Capture the delete secquence number. If it changes
+ // before the actual deletes are done then we have to
+ // start over.
+ //
+
+ IrpContext->Union.NtfsIoContext = (PVOID) Vcb->QuotaDeleteSecquence;
+
+ } else {
+
+ //
+ // We are starting just starting. Clear the quota tracking
+ // flags and indicate the current state.
+ //
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_CLEAR_RUNNING |
+ VCB_QUOTA_SAVE_QUOTA_FLAGS);
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED );
+
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE );
+
+ State = VCB_QUOTA_CLEAR_RUNNING;
+ }
+
+ //
+ // Initialize the File reference to the root index.
+ //
+
+ NtfsSetSegmentNumber( &Vcb->QuotaFileReference,
+ 0,
+ ROOT_FILE_NAME_INDEX_NUMBER );
+ Vcb->QuotaFileReference.SequenceNumber = 0;
+
+ NtfsLogEvent( IrpContext,
+ NULL,
+ IO_FILE_QUOTA_STARTED,
+ STATUS_SUCCESS );
+
+ } else {
+ State = FlagOn( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING);
+ }
+
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
+
+ NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
+ }
+
+ if (!NT_SUCCESS( Status )) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // Determine the current state
+ //
+
+ switch (State) {
+ case VCB_QUOTA_CLEAR_RUNNING:
+
+ DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting clear per file quota.\n" ));
+
+ //
+ // Clear the quota charged field in each file and clear
+ // all of the quota control blocks from the fcbs.
+ //
+
+ Status = NtfsIterateMft( IrpContext,
+ Vcb,
+ &Vcb->QuotaFileReference,
+ NtfsClearPerFileQuota,
+ NULL );
+
+ if (Status == STATUS_END_OF_FILE) {
+ Status = STATUS_SUCCESS;
+ }
+
+ if (!NT_SUCCESS( Status )) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+RestartVerifyQuotaIndex:
+
+ //
+ // Update the state to the next phase.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_INDEX_REPAIR);
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ //
+ // NtfsClearAndVerifyQuotaIndex uses the low part of the
+ // file reference to store the current owner id.
+ // Intialize this to the first user id.
+ //
+
+ Vcb->QuotaFileReference.SegmentNumberLowPart = QUOTA_FISRT_USER_ID;
+
+ //
+ // Fall through.
+ //
+
+ case VCB_QUOTA_INDEX_REPAIR:
+
+ DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting clear quota index.\n" ));
+
+ //
+ // Clear the quota used for each owner id.
+ //
+
+ NtfsClearAndVerifyQuotaIndex( IrpContext, Vcb );
+
+ //
+ // Update the state to the next phase.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_OWNER_VERIFY);
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ //
+ // Note NtfsVerifyOwnerIndex does not use any restart state,
+ // since it normally does not preform any transactions.
+ //
+
+ //
+ // Fall through.
+ //
+
+ case VCB_QUOTA_OWNER_VERIFY:
+
+ DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting verify owner index.\n" ));
+
+ //
+ // Verify the owner's id points to quota user data.
+ //
+
+ Status = NtfsVerifyOwnerIndex( IrpContext, Vcb );
+
+ //
+ // Restart the rebuild with the quota index phase.
+ //
+
+ if (!NT_SUCCESS( Status ) ) {
+
+ if (RetryCount < 2) {
+
+ RetryCount++;
+ goto RestartVerifyQuotaIndex;
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ }
+
+ //
+ // Update the state to the next phase.
+ // Start tracking quota and do enforcement as requested.
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_RECALC_STARTED |
+ VCB_QUOTA_SAVE_QUOTA_FLAGS);
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED)) {
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED);
+ Status = STATUS_SUCCESS;
+ } else {
+
+ //
+ // There is no point in doing any of this work if tracking
+ // is not requested.
+ //
+
+ Status = STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Capture the delete secquence number. If it changes
+ // before the actual deletes are done then we have to
+ // start over.
+ //
+
+ IrpContext->Union.NtfsIoContext = (PVOID) Vcb->QuotaDeleteSecquence;
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
+
+ NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
+ }
+
+ if (!NT_SUCCESS( Status )) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // Initialize the File reference to the first user file.
+ //
+
+ NtfsSetSegmentNumber( &Vcb->QuotaFileReference,
+ 0,
+ ROOT_FILE_NAME_INDEX_NUMBER );
+ Vcb->QuotaFileReference.SequenceNumber = 0;
+
+ //
+ // Fall through.
+ //
+
+ case VCB_QUOTA_RECALC_STARTED:
+
+ DebugTrace( 4, Dbg, ( "NtfsRepairQuotaIndex: Starting per file quota usage.\n" ));
+
+ //
+ // Fix the user files.
+ //
+
+ Status = NtfsIterateMft( IrpContext,
+ Vcb,
+ &Vcb->QuotaFileReference,
+ NtfsRepairPerFileQuota,
+ NULL );
+
+ if (Status == STATUS_END_OF_FILE) {
+ Status = STATUS_SUCCESS;
+ }
+
+ //
+ // Everything is done indicate we are up to date.
+ //
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES)) {
+
+ //
+ // Need to actually delete the ids.
+ //
+
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_DELETEING_IDS );
+ State = VCB_QUOTA_DELETEING_IDS;
+
+ //
+ // NtfsDeleteUnsedIds uses the low part of the
+ // file reference to store the current owner id.
+ // Intialize this to the first user id.
+ //
+
+ Vcb->QuotaFileReference.SegmentNumberLowPart = QUOTA_FISRT_USER_ID;
+
+ }
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT );
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
+ NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
+ }
+
+ if (State != VCB_QUOTA_DELETEING_IDS) {
+ break;
+ }
+
+ case VCB_QUOTA_DELETEING_IDS:
+
+ //
+ // Remove and ids which are marked for deletion.
+ //
+
+ NtfsDeleteUnsedIds( IrpContext, Vcb );
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES );
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ if (FlagOn( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS )) {
+ NtfsSaveQuotaFlagsSafe( IrpContext, Vcb );
+ }
+
+ break;
+
+ default:
+
+ ASSERT( FALSE );
+ Status = STATUS_INVALID_PARAMETER;
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ if (NT_SUCCESS( Status )) {
+ NtfsLogEvent( IrpContext,
+ NULL,
+ IO_FILE_QUOTA_SUCCEEDED,
+ Status );
+ }
+
+ } except(NtfsExceptionFilter(IrpContext, GetExceptionInformation())) {
+
+ Status = IrpContext->TopLevelIrpContext->ExceptionStatus;
+ }
+
+ DbgPrint( "NtfsRepairQuotaIndex: Quota repair done. Status = %8lx Context = %lx\n", Status, (ULONG) NtfsSegmentNumber( &Vcb->QuotaFileReference ));
+
+ if (!NT_SUCCESS( Status )) {
+
+ //
+ // If we will not be called back then clear the running state bits.
+ //
+
+ if ((Status != STATUS_CANT_WAIT && Status != STATUS_LOG_FILE_FULL)) {
+
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_REPAIR_RUNNING );
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+
+ NtfsLogEvent( IrpContext, NULL, IO_FILE_QUOTA_FAILED, Status );
+ }
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+}
+
+VOID
+NtfsReleaseQuotaControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_CONTROL_BLOCK QuotaControl
+ )
+
+/*++
+
+Routine Description:
+
+ This function is called by transcation control to release the quota control
+ block and quota index after a transcation has been completed.
+
+Arguments:
+
+ QuotaControl - Quota control block to be released.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PVCB Vcb = IrpContext->Vcb;
+ PAGED_CODE();
+
+ ExReleaseFastMutexUnsafe( QuotaControl->QuotaControlLock );
+ ExReleaseResource( Vcb->QuotaTableScb->Header.Resource );
+
+ NtfsDereferenceQuotaControlBlock( Vcb, &QuotaControl );
+}
+
+VOID
+NtfsRepairPerFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PVOID Context
+ )
+/*++
+
+Routine Description:
+
+ This routine calculate the quota used by a file and update the
+ update QuotaCharged field in the standard info as well as QuotaUsed
+ in the user's index structure. If the owner id is not set this is
+ also updated at this time.
+
+Arguments:
+
+ Fcb - Fcb for the file to be processed.
+
+ Context - Unsed.
+
+Return Value:
+
+ NONE.
+
+--*/
+{
+ LONGLONG Delta;
+ INDEX_KEY IndexKey;
+ INDEX_ROW IndexRow;
+ PREAD_CONTEXT ReadContext = NULL;
+ ULONG Count;
+ PSID Sid;
+ PSCB Scb;
+ PVCB Vcb = Fcb->Vcb;
+ NTSTATUS Status;
+ BOOLEAN OwnerDefaulted;
+ BOOLEAN SetOwnerId = FALSE;
+ BOOLEAN StdInfoGrown = FALSE;
+ BOOLEAN QuotaAcquired;
+
+ UNREFERENCED_PARAMETER( Context);
+
+ //
+ // Preacquire the quota index in case the mft has to be grown
+ //
+
+ ASSERT(!NtfsIsExclusiveScb( Vcb->MftScb ) || NtfsIsExclusiveScb( Vcb->QuotaTableScb ));
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->QuotaTableScb );
+ QuotaAcquired = TRUE;
+
+ try {
+
+ if (Fcb->OwnerId != QUOTA_INVALID_ID) {
+
+ //
+ // Verify the id actually exists in the index.
+ //
+
+ Count = 0;
+ IndexKey.Key = &Fcb->OwnerId;
+ IndexKey.KeyLength = sizeof( Fcb->OwnerId );
+ Status = NtOfsReadRecords( IrpContext,
+ Vcb->QuotaTableScb,
+ &ReadContext,
+ &IndexKey,
+ NtOfsMatchUlongExact,
+ &IndexKey,
+ &Count,
+ &IndexRow,
+ 0,
+ NULL );
+
+ if (!NT_SUCCESS( Status )) {
+
+ ASSERT( NT_SUCCESS( Status ));
+
+ //
+ // There is no user quota data for this id assign a
+ // new one to the file.
+ //
+
+ Fcb->OwnerId = QUOTA_INVALID_ID;
+
+ if ( Fcb->QuotaControl != NULL) {
+
+ //
+ // If there is a quota control block it is now bougus
+ // Free it up a new one will be generated below.
+ //
+
+ NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
+ }
+
+ }
+
+ NtOfsFreeReadContext( ReadContext );
+ }
+
+ if (Fcb->OwnerId == QUOTA_INVALID_ID) {
+
+ if (Fcb->SharedSecurity == NULL) {
+ NtfsLoadSecurityDescriptor ( IrpContext,
+ Fcb,
+ NULL );
+
+ }
+
+ ASSERT(Fcb->SharedSecurity != NULL);
+
+ //
+ // Extract the security id from the security descriptor.
+ //
+
+ Status = RtlGetOwnerSecurityDescriptor(
+ Fcb->SharedSecurity->SecurityDescriptor,
+ &Sid,
+ &OwnerDefaulted );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, Fcb);
+ }
+
+ //
+ // Generate a owner id for the Fcb.
+ //
+
+ Fcb->OwnerId = NtfsGetOwnerId( IrpContext,
+ Sid,
+ NULL );
+
+ SetOwnerId = TRUE;
+
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+
+ } else {
+
+ //
+ // Grow the standard information.
+ //
+
+ StdInfoGrown = TRUE;
+ NtfsGrowStandardInformation( IrpContext, Fcb );
+
+ }
+
+ }
+
+ //
+ // Initialize the quota control block.
+ //
+
+ if ( Fcb->QuotaControl == NULL) {
+ NtfsInitializeQuotaControlBlock( Fcb );
+ }
+
+ //
+ // Walk of the SCB's on this FCB and set the enlagered quota
+ // flag so that NtfsCalculateQuotaAdjustment works correctly.
+ //
+
+ NtfsLockFcb( IrpContext, Fcb );
+
+ Scb = NULL;
+ while ((Scb = NtfsGetNextChildScb( Fcb, Scb )) != NULL) {
+
+ ASSERT_SCB( Scb );
+
+ //
+ // For every scb already in the fcb's queue check for a $DATA
+ // type code and non-zero cleanup count.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED) &&
+ FlagOn( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA ) &&
+ Scb->CleanupCount > 0) {
+
+ ASSERT( NtfsIsTypeCodeSubjectToQuota( Scb->AttributeTypeCode ));
+
+ SetFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+ }
+ }
+
+ NtfsUnlockFcb( IrpContext, Fcb );
+
+ NtfsCalculateQuotaAdjustment( IrpContext, Fcb, &Delta );
+
+ ASSERT(NtfsAllowFixups || FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE ) || Delta == 0);
+
+ if (Delta != 0 ||
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_PENDING_DELETES )) {
+
+ NtfsUpdateFileQuota( IrpContext, Fcb, &Delta, TRUE, FALSE );
+
+ }
+
+ if (SetOwnerId) {
+
+ //
+ // If the owner id was set then commit the transaction now.
+ // That way if a raise occurs the OwnerId can be cleared before
+ // the function returns. No resources are released.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ }
+
+ } finally {
+
+ //
+ // Clear any Fcb changes if the operation failed.
+ // This is so when a retry occurs the necessary
+ // operations are done.
+ //
+
+ if (AbnormalTermination()) {
+
+
+ if (StdInfoGrown) {
+ ClearFlag( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO );
+ }
+
+ if (SetOwnerId) {
+
+ Fcb->OwnerId = QUOTA_INVALID_ID;
+
+ if (Fcb->QuotaControl != NULL) {
+ NtfsDereferenceQuotaControlBlock( Vcb, &Fcb->QuotaControl );
+ }
+ }
+ }
+
+ NtfsReleaseQuotaIndex( IrpContext, Vcb, QuotaAcquired );
+ }
+}
+
+
+VOID
+NtfsUpdateFileQuota (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PLONGLONG Delta,
+ IN BOOLEAN LogIt,
+ IN BOOLEAN CheckQuota
+ )
+/*++
+
+Routine Description:
+
+ This routine updates the quota amount for a file and owner by the
+ requested amount. If quota is being increated and the CheckQuota is true
+ than the new quota amount will be tested for quota violations. If the
+ hard limit is exceeded an error is raised. If the LogIt flags is not set
+ then changes to the standard information structure are not logged.
+ Changes to the user quota data are always logged.
+
+Arguments:
+
+ Fcb - Fcb whose quota usage is being modified.
+
+ Delta - Supplies the signed amount to change the quota for the file.
+
+ LogIt - Indicates whether we should log this change.
+
+ CheckQuota - Indicates whether we should check for quota violations.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+
+ ULONGLONG NewQuota;
+ LARGE_INTEGER CurrentTime;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PSTANDARD_INFORMATION StandardInformation;
+ PQUOTA_USER_DATA UserData;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ NTSTATUS Status;
+ PQUOTA_CONTROL_BLOCK QuotaControl = Fcb->QuotaControl;
+ PVCB Vcb = Fcb->Vcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsUpdateFileQuota: Entered\n") );
+
+ ASSERT(FlagOn(Fcb->FcbState, FCB_STATE_LARGE_STD_INFO));
+
+ //
+ // Use a try-finally to cleanup the attribute context.
+ //
+
+ try {
+
+ //
+ // Initialize the context structure and map handle.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &AttrContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ StandardInformation = (PSTANDARD_INFORMATION)
+ NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ ASSERT(NtfsFoundAttribute( &AttrContext )->Form.Resident.ValueLength ==
+ sizeof( STANDARD_INFORMATION ));
+ ASSERT( (LONGLONG) StandardInformation->QuotaCharged >= -*Delta);
+
+ NewQuota = StandardInformation->QuotaCharged + *Delta;
+
+ if ((LONGLONG) NewQuota < 0) {
+
+ //
+ // Do not let the quota data go negitive.
+ //
+
+ NewQuota = 0;
+ }
+
+ if (LogIt) {
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ FIELD_OFFSET(STANDARD_INFORMATION, QuotaCharged),
+ &NewQuota,
+ sizeof( StandardInformation->QuotaCharged),
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+ } else {
+
+ //
+ // Just update the value in the standard information
+ // it will be logged later.
+ //
+
+ StandardInformation->QuotaCharged = NewQuota;
+ }
+
+ //
+ // Update the quota information block.
+ //
+
+ NtfsAcquireQuotaControl( IrpContext, QuotaControl );
+
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &QuotaControl->OwnerId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ &QuotaControl->QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // This look up should not fail.
+ //
+
+ ASSERT( NT_SUCCESS(Status) );
+ NtfsMarkQuotaCorrupt( IrpContext, IrpContext->Vcb );
+ leave;
+ }
+
+ //
+ // Space is allocated for the new record after the quota control
+ // block.
+ //
+
+ UserData = (PQUOTA_USER_DATA) (QuotaControl + 1);
+ ASSERT(IndexRow.DataPart.DataLength >= SIZEOF_QUOTA_USER_DATA );
+
+ RtlCopyMemory( UserData,
+ IndexRow.DataPart.Data,
+ SIZEOF_QUOTA_USER_DATA );
+
+ ASSERT( (LONGLONG) UserData->QuotaUsed >= -*Delta);
+
+ UserData->QuotaUsed += *Delta;
+
+ if ((LONGLONG) UserData->QuotaUsed < 0) {
+
+ //
+ // Do not let the quota data go negative.
+ //
+
+ UserData->QuotaUsed = 0;
+ }
+
+ //
+ // CAIROBUG: Consider moderating how often we do this once we are
+ // not logging the whole record.
+ //
+
+ KeQuerySystemTime( &CurrentTime );
+ UserData->QuotaChangeTime = CurrentTime.QuadPart;
+
+ if (CheckQuota &&
+ *Delta > 0) {
+
+ if (UserData->QuotaUsed > UserData->QuotaLimit &&
+ UserData->QuotaUsed >
+ UserData->QuotaLimit + Vcb->BytesPerCluster) {
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD) &&
+ (!FlagOn( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED ) ||
+ (ULONGLONG) CurrentTime.QuadPart >
+ UserData->QuotaExceededTime + MIN_QUOTA_NOTIFY_TIME)) {
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED)) {
+
+ //
+ // The operation to mark the user's quota data entry
+ // must be posted since any changes to the entry
+ // will be undone by the following raise.
+ //
+
+ NtfsPostUserLimit( IrpContext, Vcb, QuotaControl );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, Fcb );
+
+ } else {
+
+ //
+ // Log the fact that quota was exceeded.
+ //
+
+ if (NtfsLogEvent( IrpContext,
+ IndexRow.DataPart.Data,
+ IO_FILE_QUOTA_LIMIT,
+ STATUS_SUCCESS )) {
+
+ //
+ // The event was successfuly logged. Do not log
+ // another for a while.
+ //
+
+ DbgPrint("NtfsUpdateFileQuota: Quota Limit exceeded. OwnerId = %lx\n", QuotaControl->OwnerId);
+
+ UserData->QuotaExceededTime = CurrentTime.QuadPart;
+ SetFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
+
+ }
+
+ }
+
+ } else if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED)) {
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, Fcb );
+ }
+
+ }
+
+ if (UserData->QuotaUsed > UserData->QuotaThreshold &&
+ (ULONGLONG) CurrentTime.QuadPart >
+ UserData->QuotaExceededTime + MIN_QUOTA_NOTIFY_TIME) {
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD)) {
+
+ if (NtfsLogEvent( IrpContext,
+ IndexRow.DataPart.Data,
+ IO_FILE_QUOTA_THRESHOLD,
+ STATUS_SUCCESS )) {
+
+ //
+ // The event was successfuly logged. Do not log
+ // another for a while.
+ //
+
+ DbgPrint("NtfsUpdateFileQuota: Quota threshold exceeded. OwnerId = %lx\n", QuotaControl->OwnerId);
+
+ UserData->QuotaExceededTime = CurrentTime.QuadPart;
+ }
+ }
+
+ //
+ // Now is a good time to clear the limit reached flag.
+ //
+
+ ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_LIMIT_REACHED );
+ }
+ }
+
+ //
+ // Always clear the deleted flag.
+ //
+
+ ClearFlag( UserData->QuotaFlags, QUOTA_FLAG_ID_DELETED );
+
+ //
+ // Only log the part that changed.
+ //
+
+ IndexRow.KeyPart.Key = &QuotaControl->OwnerId;
+ IndexRow.DataPart.Data = UserData;
+ IndexRow.DataPart.DataLength = SIZEOF_QUOTA_USER_DATA;
+
+ NtOfsUpdateRecord( IrpContext,
+ Vcb->QuotaTableScb,
+ 1,
+ &IndexRow,
+ &QuotaControl->QuickIndexHint,
+ &MapHandle );
+
+ } finally {
+
+ DebugUnwind( NtfsUpdateFileQuota );
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ DebugTrace( -1, Dbg, ("NtfsUpdateFileQuota: Exit\n") );
+ }
+}
+
+VOID
+NtfsSaveQuotaFlags (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine saves the quota flags in the defaults quota entry.
+
+Arguments:
+
+ Vcb - Volume control block for volume be query.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG OwnerId;
+ NTSTATUS Status;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ QUICK_INDEX_HINT QuickIndexHint;
+ QUOTA_USER_DATA NewQuotaData;
+ PSCB QuotaScb;
+
+ PAGED_CODE();
+
+ //
+ // Acquire quota index exclusive.
+ //
+
+ QuotaScb = Vcb->QuotaTableScb;
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtOfsInitializeMapHandle( &MapHandle );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ try {
+
+ //
+ // Find the default quota record and update it.
+ //
+
+ OwnerId = QUOTA_DEFAULTS_ID;
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &OwnerId;
+
+ RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ &QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
+ }
+
+ ASSERT( IndexRow.DataPart.DataLength <= sizeof( QUOTA_USER_DATA ));
+
+ RtlCopyMemory( &NewQuotaData,
+ IndexRow.DataPart.Data,
+ IndexRow.DataPart.DataLength );
+
+ //
+ // Update the changed fields in the record.
+ //
+
+ NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
+
+ //
+ // Note the sizes in the IndexRow stay the same.
+ //
+
+ IndexRow.KeyPart.Key = &OwnerId;
+ IndexRow.DataPart.Data = &NewQuotaData;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexRow,
+ &QuickIndexHint,
+ &MapHandle );
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS);
+
+ } finally {
+
+ //
+ // Release the index map handle and scb.
+ //
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+
+ }
+}
+
+VOID
+NtfsSaveQuotaFlagsSafe (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine safely saves the quota flags in the defaults quota entry.
+ It acquires the volume shared, checks to see if it is ok to write,
+ updates the flags and finally commits the transaction.
+
+Arguments:
+
+ Vcb - Volume control block for volume be query.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ ASSERT( IrpContext->TransactionId == 0);
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+
+ //
+ // Do the work.
+ //
+
+ NtfsSaveQuotaFlags( IrpContext, Vcb );
+
+ //
+ // Set the irp context flags to indicate that we are in the
+ // fsp and that the irp context should not be deleted when
+ // complete request or process exception are called. The in
+ // fsp flag keeps us from raising in a few places. These
+ // flags must be set inside the loop since they are cleared
+ // under certain conditions.
+ //
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_DONT_DELETE | IRP_CONTEXT_FLAG_IN_FSP);
+
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ } finally {
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ }
+}
+
+VOID
+NtfsUpdateQuotaDefaults (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION FileControlInfo
+ )
+
+/*++
+
+Routine Description:
+
+ This function updates the default settings index entry for quotas.
+
+Arguments:
+
+ Vcb - Volume control block for volume be query.
+
+ FileQuotaInfo - Optional quota data to update quota index with.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG OwnerId;
+ NTSTATUS Status;
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ MAP_HANDLE MapHandle;
+ QUOTA_USER_DATA NewQuotaData;
+ QUICK_INDEX_HINT QuickIndexHint;
+ ULONG Flags;
+ PSCB QuotaScb;
+
+ PAGED_CODE();
+
+ //
+ // Acquire quota index exclusive.
+ //
+
+ QuotaScb = Vcb->QuotaTableScb;
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtOfsInitializeMapHandle( &MapHandle );
+ ExAcquireFastMutex( &Vcb->QuotaControlLock );
+
+ try {
+
+ //
+ // Find the default quota record and update it.
+ //
+
+ OwnerId = QUOTA_DEFAULTS_ID;
+ IndexKey.KeyLength = sizeof(ULONG);
+ IndexKey.Key = &OwnerId;
+
+ RtlZeroMemory( &QuickIndexHint, sizeof( QuickIndexHint ));
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &IndexRow,
+ &MapHandle,
+ &QuickIndexHint );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, STATUS_QUOTA_LIST_INCONSISTENT, NULL, QuotaScb->Fcb );
+ }
+
+ ASSERT( IndexRow.DataPart.DataLength == SIZEOF_QUOTA_USER_DATA );
+
+ RtlCopyMemory( &NewQuotaData,
+ IndexRow.DataPart.Data,
+ IndexRow.DataPart.DataLength );
+
+ //
+ // Update the changed fields in the record.
+ //
+
+ NewQuotaData.QuotaThreshold = FileControlInfo->DefaultQuotaThreshold.QuadPart;
+ NewQuotaData.QuotaLimit = FileControlInfo->DefaultQuotaLimit.QuadPart;
+ KeQuerySystemTime( (PLARGE_INTEGER) &NewQuotaData.QuotaChangeTime );
+
+ //
+ // Update the quota flags.
+ //
+
+ Flags = FlagOn( FileControlInfo->FileSystemControlFlags,
+ FILE_VC_QUOTA_MASK );
+
+ switch (Flags) {
+ case FILE_VC_QUOTA_NONE:
+
+ //
+ // Disable quotas
+ //
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED |
+ QUOTA_FLAG_ENFORCEMENT_ENABLED |
+ QUOTA_FLAG_TRACKING_REQUESTED );
+
+ break;
+
+ case FILE_VC_QUOTA_TRACK:
+
+ //
+ // Clear the enforment flags.
+ //
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED );
+
+ //
+ // Request tracking be enabled.
+ //
+
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED );
+ break;
+
+ case FILE_VC_QUOTA_ENFORCE:
+
+ //
+ // Set the enforcement and tracking enabled flags.
+ //
+
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_ENFORCEMENT_ENABLED |
+ QUOTA_FLAG_TRACKING_REQUESTED);
+
+ break;
+ }
+
+ //
+ // If quota tracking is not now
+ // enabled then the quota data will need
+ // to be rebuild so indicate quotas are out of date.
+ // Note the out of date flags always set of quotas
+ // are disabled.
+ //
+
+ if (!FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED )) {
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE );
+ }
+
+ //
+ // Track the logging flags.
+ //
+
+ ClearFlag( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD |
+ QUOTA_FLAG_LOG_LIMIT );
+
+ if (FlagOn( FileControlInfo->FileSystemControlFlags,
+ FILE_VC_LOG_QUOTA_THRESHOLD )) {
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD );
+ }
+
+ if (FlagOn( FileControlInfo->FileSystemControlFlags,
+ FILE_VC_LOG_QUOTA_LIMIT )) {
+ SetFlag( Vcb->QuotaFlags, QUOTA_FLAG_LOG_LIMIT );
+ }
+
+
+ SetFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
+
+ //
+ // Save the new flags in the new index entry.
+ //
+
+ NewQuotaData.QuotaFlags = Vcb->QuotaFlags;
+
+ //
+ // Note the sizes in the IndexRow stays the same.
+ //
+
+ IndexRow.KeyPart.Key = &OwnerId;
+ IndexRow.DataPart.Data = &NewQuotaData;
+
+ NtOfsUpdateRecord( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexRow,
+ &QuickIndexHint,
+ &MapHandle );
+
+ ClearFlag( Vcb->QuotaState, VCB_QUOTA_SAVE_QUOTA_FLAGS );
+
+ } finally {
+
+ //
+ // Release the index map handle and scb.
+ //
+
+ ExReleaseFastMutex( &Vcb->QuotaControlLock );
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ NtfsReleaseScb( IrpContext, QuotaScb );
+
+ }
+
+ //
+ // If the quota tracking has been requested and the quotas need to be
+ // repaired then try to repair them now.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_REQUESTED ) &&
+ FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT |
+ QUOTA_FLAG_PENDING_DELETES )) {
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+
+}
+
+NTSTATUS
+NtfsVerifyOwnerIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine iterates over the owner id index and verifies the pointer
+ to the quota user data index.
+
+Arguments:
+
+ Vcb - Pointer to the volume control block whoes index is to be operated
+ on.
+
+Return Value:
+
+ Returns a status indicating if the owner index was ok.
+
+--*/
+
+{
+ INDEX_KEY IndexKey;
+ INDEX_ROW QuotaRow;
+ MAP_HANDLE MapHandle;
+ PQUOTA_USER_DATA UserData;
+ PINDEX_ROW OwnerRow;
+ PVOID RowBuffer;
+ NTSTATUS Status;
+ NTSTATUS ReturnStatus = STATUS_SUCCESS;
+ ULONG OwnerId;
+ ULONG Count;
+ ULONG i;
+ PSCB QuotaScb = Vcb->QuotaTableScb;
+ PSCB OwnerIdScb = Vcb->OwnerIdTableScb;
+ PINDEX_ROW IndexRow = NULL;
+ PREAD_CONTEXT ReadContext = NULL;
+ BOOLEAN IndexAcquired = FALSE;
+
+ NtOfsInitializeMapHandle( &MapHandle );
+
+ //
+ // Allocate a buffer lager enough for several rows.
+ //
+
+ RowBuffer = NtfsAllocatePool( PagedPool, PAGE_SIZE );
+
+ try {
+
+ //
+ // Allocate a bunch of index row entries.
+ //
+
+ Count = PAGE_SIZE / sizeof( SID );
+
+ IndexRow = NtfsAllocatePool( PagedPool,
+ Count * sizeof( INDEX_ROW ) );
+
+ //
+ // Iterate through the owner id entries. Start with a zero sid.
+ //
+
+ RtlZeroMemory( IndexRow, sizeof( SID ));
+ IndexKey.KeyLength = sizeof( SID );
+ IndexKey.Key = IndexRow;
+
+ Status = NtOfsReadRecords( IrpContext,
+ OwnerIdScb,
+ &ReadContext,
+ &IndexKey,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ PAGE_SIZE,
+ RowBuffer );
+
+
+ while (NT_SUCCESS( Status )) {
+
+ //
+ // Acquire the VCB shared and check whether we should
+ // continue.
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, TRUE );
+ NtfsAcquireExclusiveScb( IrpContext, QuotaScb );
+ NtfsAcquireExclusiveScb( IrpContext, OwnerIdScb );
+ IndexAcquired = TRUE;
+
+ if (!NtfsIsVcbAvailable( Vcb )) {
+
+ //
+ // The volume is going away, bail out.
+ //
+
+ Status = STATUS_VOLUME_DISMOUNTED;
+ leave;
+ }
+
+ OwnerRow = IndexRow;
+
+ for (i = 0; i < Count; i++, OwnerRow++) {
+
+ IndexKey.KeyLength = OwnerRow->DataPart.DataLength;
+ IndexKey.Key = OwnerRow->DataPart.Data;
+
+ //
+ // Look up the Owner id in the quota index.
+ //
+
+ Status = NtOfsFindRecord( IrpContext,
+ QuotaScb,
+ &IndexKey,
+ &QuotaRow,
+ &MapHandle,
+ NULL );
+
+ ASSERT( NT_SUCCESS( Status ) );
+
+ if (!NT_SUCCESS( Status )) {
+
+ //
+ // The quota entry is missing just delete this row;
+ //
+
+ NtOfsDeleteRecords( IrpContext,
+ OwnerIdScb,
+ 1,
+ &OwnerRow->KeyPart );
+
+ continue;
+ }
+
+ UserData = QuotaRow.DataPart.Data;
+
+ ASSERT( OwnerRow->KeyPart.KeyLength == QuotaRow.DataPart.DataLength - SIZEOF_QUOTA_USER_DATA &&
+ RtlEqualMemory( OwnerRow->KeyPart.Key, &UserData->QuotaSid, OwnerRow->KeyPart.KeyLength ));
+
+ if ( OwnerRow->KeyPart.KeyLength !=
+ QuotaRow.DataPart.DataLength - SIZEOF_QUOTA_USER_DATA ||
+ !RtlEqualMemory( OwnerRow->KeyPart.Key,
+ &UserData->QuotaSid,
+ OwnerRow->KeyPart.KeyLength )) {
+
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ //
+ // The Sids do not match delete both of these records.
+ // This causes the user whatever their Sid is to get
+ // the defaults.
+ //
+
+
+ NtOfsDeleteRecords( IrpContext,
+ OwnerIdScb,
+ 1,
+ &OwnerRow->KeyPart );
+
+ NtOfsDeleteRecords( IrpContext,
+ QuotaScb,
+ 1,
+ &IndexKey );
+
+ ReturnStatus = STATUS_QUOTA_LIST_INCONSISTENT;
+ }
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ }
+
+ //
+ // Release the indexes and commit what has been done so far.
+ //
+
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ IndexAcquired = FALSE;
+
+ //
+ // Complete the request which commits the pending
+ // transaction if there is one and releases of the
+ // acquired resources. The IrpContext will not
+ // be deleted because the no delete flag is set.
+ //
+
+ SetFlag( IrpContext->Flags,
+ IRP_CONTEXT_FLAG_DONT_DELETE );
+ NtfsCompleteRequest( &IrpContext, NULL, STATUS_SUCCESS );
+
+ //
+ // Look up the next set of entries in the quota index.
+ //
+
+ Count = PAGE_SIZE / sizeof( SID );
+ Status = NtOfsReadRecords( IrpContext,
+ OwnerIdScb,
+ &ReadContext,
+ NULL,
+ NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ PAGE_SIZE,
+ RowBuffer );
+
+ }
+
+ ASSERT( Status == STATUS_NO_MORE_MATCHES || Status == STATUS_NO_MATCH);
+
+ } finally {
+
+ NtfsFreePool( RowBuffer );
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ if (IndexAcquired) {
+ NtfsReleaseScb( IrpContext, QuotaScb );
+ NtfsReleaseScb( IrpContext, OwnerIdScb );
+ NtfsReleaseVcb( IrpContext, Vcb );
+ }
+
+ if (IndexRow != NULL) {
+ NtfsFreePool( IndexRow );
+ }
+
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+ }
+
+ return ReturnStatus;
+}
+
+RTL_GENERIC_COMPARE_RESULTS
+NtfsQuotaTableCompare (
+ IN PRTL_GENERIC_TABLE Table,
+ PVOID FirstStruct,
+ PVOID SecondStruct
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine to compare two quota table elements
+
+Arguments:
+
+ Table - Supplies the generic table being queried. Not used.
+
+ FirstStruct - Supplies the first quota table element to compare
+
+ SecondStruct - Supplies the second quota table element to compare
+
+Return Value:
+
+ RTL_GENERIC_COMPARE_RESULTS - The results of comparing the two
+ input structures
+
+--*/
+{
+ ULONG Key1 = ((PQUOTA_CONTROL_BLOCK) FirstStruct)->OwnerId;
+ ULONG Key2 = ((PQUOTA_CONTROL_BLOCK) SecondStruct)->OwnerId;
+
+ PAGED_CODE();
+
+ UNREFERENCED_PARAMETER( Table );
+
+ if (Key1 < Key2) {
+
+ return GenericLessThan;
+ }
+
+ if (Key1 > Key2) {
+
+ return GenericGreaterThan;
+ }
+
+ return GenericEqual;
+}
+
+PVOID
+NtfsQuotaTableAllocate (
+ IN PRTL_GENERIC_TABLE Table,
+ CLONG ByteSize
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine to allocate memory
+
+Arguments:
+
+ Table - Supplies the generic table being used
+
+ ByteSize - Supplies the number of bytes to allocate
+
+Return Value:
+
+ PVOID - Returns a pointer to the allocated data
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Table );
+
+ PAGED_CODE();
+
+ return NtfsAllocatePoolWithTag( PagedPool, ByteSize, 'QftN' );
+}
+
+VOID
+NtfsQuotaTableFree (
+ IN PRTL_GENERIC_TABLE Table,
+ IN PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine to free memory
+
+Arguments:
+
+ Table - Supplies the generic table being used
+
+ Buffer - Supplies pointer to the buffer to be freed
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Table );
+
+ PAGED_CODE();
+
+ NtfsFreePool( Buffer );
+}
+
+
+//
+// The SID of the opener needs to be captured so that the current free space
+// can be return. There are current no good interfaces do this with so
+// for now just cheat.
+//
+
+ULONG
+NtfsGetCallersUserId (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine finds the calling thread's SID and translates it to an
+ owner id.
+
+Arguments:
+
+Return Value:
+
+ Returns the owner id.
+
+--*/
+
+{
+ PSECURITY_SUBJECT_CONTEXT SubjectContext;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ NTSTATUS Status;
+ ULONG OwnerId;
+ PSID Sid;
+ BOOLEAN OwnerDefaulted;
+
+ PAGED_CODE();
+
+ SubjectContext = &IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->
+ Parameters.Create.SecurityContext->AccessState->
+ SubjectSecurityContext;
+
+ //
+ // Create a new security descriptor for the file and raise if there is
+ // an error
+ //
+
+ Status = SeAssignSecurity( NULL,
+ NULL,
+ &SecurityDescriptor,
+ FALSE,
+ SubjectContext,
+ IoGetFileObjectGenericMapping(),
+ PagedPool );
+
+ if (!NT_SUCCESS( Status )) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ try {
+
+ //
+ // Extract the security id from the security descriptor.
+ //
+
+ Status = RtlGetOwnerSecurityDescriptor(
+ SecurityDescriptor,
+ &Sid,
+ &OwnerDefaulted );
+
+ if (!NT_SUCCESS(Status)) {
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ OwnerId = NtfsGetOwnerId( IrpContext, Sid, NULL);
+
+ } finally {
+ SeDeassignSecurity( &SecurityDescriptor );
+ }
+
+ return OwnerId;
+}
+
diff --git a/private/ntos/cntfs/read.c b/private/ntos/cntfs/read.c
new file mode 100644
index 000000000..680765145
--- /dev/null
+++ b/private/ntos/cntfs/read.c
@@ -0,0 +1,1956 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Read.c
+
+Abstract:
+
+ This module implements the File Read routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Brian Andrew BrianAn 15-Aug-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_READ)
+
+//
+// Define stack overflow read threshhold.
+//
+
+#ifdef _X86_
+#if DBG
+#define OVERFLOW_READ_THRESHHOLD (0xB80)
+#else
+#define OVERFLOW_READ_THRESHHOLD (0xA00)
+#endif
+#else
+#define OVERFLOW_READ_THRESHHOLD (0x1000)
+#endif // _X86_
+
+//
+// Local procedure prototypes
+//
+
+//
+// The following procedure is used to handling read stack overflow operations.
+//
+
+VOID
+NtfsStackOverflowRead (
+ IN PVOID Context,
+ IN PKEVENT Event
+ );
+
+//
+// VOID
+// SafeZeroMemory (
+// IN PUCHAR At,
+// IN ULONG ByteCount
+// );
+//
+
+//
+// This macro just puts a nice little try-except around RtlZeroMemory
+//
+
+#define SafeZeroMemory(AT,BYTE_COUNT) { \
+ try { \
+ RtlZeroMemory((AT), (BYTE_COUNT)); \
+ } except(EXCEPTION_EXECUTE_HANDLER) { \
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_USER_BUFFER, NULL, NULL );\
+ } \
+}
+
+#define CollectReadStats(VCB,OPEN_TYPE,SCB,FCB,BYTE_COUNT) { \
+ PFILESYSTEM_STATISTICS FsStats = &(VCB)->Statistics[KeGetCurrentProcessorNumber()]; \
+ if (!FlagOn( (FCB)->FcbState, FCB_STATE_SYSTEM_FILE)) { \
+ if (NtfsIsTypeCodeUserData( (SCB)->AttributeTypeCode )) { \
+ FsStats->UserFileReads += 1; \
+ FsStats->UserFileReadBytes += (ULONG)(BYTE_COUNT); \
+ } else { \
+ FsStats->Ntfs.UserIndexReads += 1; \
+ FsStats->Ntfs.UserIndexReadBytes += (ULONG)(BYTE_COUNT); \
+ } \
+ } else { \
+ FsStats->MetaDataReads += 1; \
+ FsStats->MetaDataReadBytes += (ULONG)(BYTE_COUNT); \
+ \
+ if ((SCB) == (VCB)->MftScb) { \
+ FsStats->Ntfs.MftReads += 1; \
+ FsStats->Ntfs.MftReadBytes += (ULONG)(BYTE_COUNT); \
+ } else if ((SCB) == (VCB)->RootIndexScb) { \
+ FsStats->Ntfs.RootIndexReads += 1; \
+ FsStats->Ntfs.RootIndexReadBytes += (ULONG)(BYTE_COUNT); \
+ } else if ((SCB) == (VCB)->BitmapScb) { \
+ FsStats->Ntfs.BitmapReads += 1; \
+ FsStats->Ntfs.BitmapReadBytes += (ULONG)(BYTE_COUNT); \
+ } else if ((SCB) == (VCB)->MftBitmapScb) { \
+ FsStats->Ntfs.MftBitmapReads += 1; \
+ FsStats->Ntfs.MftBitmapReadBytes += (ULONG)(BYTE_COUNT); \
+ } \
+ } \
+}
+
+
+NTSTATUS
+NtfsFsdRead (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the driver entry to the common read routine for NtReadFile calls.
+ For synchronous requests, the CommonRead is called with Wait == TRUE,
+ which means the request will always be completed in the current thread,
+ and never passed to the Fsp. If it is not a synchronous request,
+ CommonRead is called with Wait == FALSE, which means the request
+ will be passed to the Fsp only if there is a need to block.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+ ULONG RetryCount = 0;
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ ASSERT_IRP( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsFsdRead\n") );
+
+ //
+ // Call the common Read routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Always make the reads appear to be top level. As long as we don't have
+ // log file full we won't post these requests. This will prevent paging
+ // reads from trying to attach to uninitialized top level requests.
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, TRUE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ if (ThreadTopLevelContext->ScbBeingHotFixed != NULL) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY );
+ }
+
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ //
+ // If this is an Mdl complete request, don't go through
+ // common read.
+ //
+
+ ASSERT(!FlagOn( IrpContext->MinorFunction, IRP_MN_DPC ));
+
+ if (FlagOn( IrpContext->MinorFunction, IRP_MN_COMPLETE )) {
+
+ DebugTrace( 0, Dbg, ("Calling NtfsCompleteMdl\n") );
+ Status = NtfsCompleteMdl( IrpContext, Irp );
+
+ //
+ // Check if we have enough stack space to process this request. If there
+ // isn't enough then we will create a new thread to process this single
+ // request
+ //
+
+ } else if (IoGetRemainingStackSize() < OVERFLOW_READ_THRESHHOLD) {
+
+ PKEVENT Event;
+ PFILE_OBJECT FileObject;
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+ PERESOURCE Resource;
+
+ DebugTrace( 0, Dbg, ("Getting too close to stack limit pass request to Fsp\n") );
+
+ //
+ // Decode the file object to get the Scb
+ //
+
+ FileObject = IoGetCurrentIrpStackLocation(Irp)->FileObject;
+
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // We cannot post any compressed reads, because that would interfere
+ // with our reserved buffer strategy. We may currently own
+ // NtfsReservedBufferResource, and it is important for our read to
+ // be able to get a buffer.
+ //
+
+ ASSERT(Scb->CompressionUnit == 0);
+
+ //
+ // Allocate an event and get shared on the scb. We won't grab the
+ // Scb for the paging file path or for non-cached io for our
+ // system files.
+ //
+
+ Event = (PKEVENT)ExAllocateFromNPagedLookasideList( &NtfsKeventLookasideList );
+ KeInitializeEvent( Event, NotificationEvent, FALSE );
+
+ if ((FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )
+ && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) ||
+ (NtfsLeqMftRef( &Fcb->FileReference, &VolumeFileReference ))) {
+
+ //
+ // There is nothing to release in this case.
+ //
+
+ Resource = NULL;
+
+ } else {
+
+ Resource = Scb->Header.Resource;
+ ExAcquireResourceShared( Resource, TRUE );
+ }
+
+ try {
+
+ //
+ // Make the Irp just like a regular post request and
+ // then send the Irp to the special overflow thread.
+ // After the post we will wait for the stack overflow
+ // read routine to set the event that indicates we can
+ // now release the scb resource and return.
+ //
+
+ NtfsPrePostIrp( IrpContext, Irp );
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE ) &&
+ FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ FsRtlPostPagingFileStackOverflow( IrpContext, Event, NtfsStackOverflowRead );
+
+ } else {
+
+ FsRtlPostStackOverflow( IrpContext, Event, NtfsStackOverflowRead );
+ }
+
+ //
+ // And wait for the worker thread to complete the item
+ //
+
+ (VOID) KeWaitForSingleObject( Event, Executive, KernelMode, FALSE, NULL );
+
+ Status = STATUS_PENDING;
+
+ } finally {
+
+ if (Resource != NULL) {
+
+ ExReleaseResource( Resource );
+ }
+
+ ExFreeToNPagedLookasideList( &NtfsKeventLookasideList, Event );
+ }
+
+ //
+ // Identify read requests which can't wait and post them to the
+ // Fsp.
+ //
+
+ } else {
+
+ //
+ // Capture the auxiliary buffer and clear its address if it
+ // is not supposed to be deleted by the I/O system on I/O completion.
+ //
+
+ if (Irp->Tail.Overlay.AuxiliaryBuffer != NULL) {
+
+ IrpContext->Union.AuxiliaryBuffer =
+ (PFSRTL_AUXILIARY_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
+
+ if (!FlagOn(IrpContext->Union.AuxiliaryBuffer->Flags,
+ FSRTL_AUXILIARY_FLAG_DEALLOCATE)) {
+
+ Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
+ }
+ }
+
+ Status = NtfsCommonRead( IrpContext, Irp, TRUE );
+ }
+
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ ExceptionCode = GetExceptionCode();
+
+ if (ExceptionCode == STATUS_FILE_DELETED) {
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_END_OF_FILE;
+
+ Irp->IoStatus.Information = 0;
+ }
+
+ Status = NtfsProcessException( IrpContext,
+ Irp,
+ ExceptionCode );
+ }
+
+ //
+ // Retry if this is a top level request, and the Irp was not completed due
+ // to a retryable error.
+ //
+
+ RetryCount += 1;
+
+ } while ((Status == STATUS_CANT_WAIT || Status == STATUS_LOG_FILE_FULL) &&
+ TopLevelContext.TopLevelRequest);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdRead -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+NtfsStackOverflowRead (
+ IN PVOID Context,
+ IN PKEVENT Event
+ )
+
+/*++
+
+Routine Description:
+
+ This routine processes a read request that could not be processed by
+ the fsp thread because of stack overflow potential.
+
+Arguments:
+
+ Context - Supplies the IrpContext being processed
+
+ Event - Supplies the event to be signaled when we are done processing this
+ request.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+ PIRP_CONTEXT IrpContext = Context;
+
+ //
+ // Make it now look like we can wait for I/O to complete
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+
+ //
+ // Do the read operation protected by a try-except clause
+ //
+
+ try {
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ //
+ // Set the flag to indicate that we are in the overflow thread.
+ //
+
+ TopLevelContext.OverflowReadThread = TRUE;
+
+ (VOID) NtfsCommonRead( IrpContext, IrpContext->OriginatingIrp, FALSE );
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ ExceptionCode = GetExceptionCode();
+
+ if (ExceptionCode == STATUS_FILE_DELETED) {
+
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_END_OF_FILE;
+ IrpContext->OriginatingIrp->IoStatus.Information = 0;
+ }
+
+ (VOID) NtfsProcessException( IrpContext, IrpContext->OriginatingIrp, ExceptionCode );
+ }
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+
+ //
+ // Set the stack overflow item's event to tell the original
+ // thread that we're done and then go get another work item.
+ //
+
+ KeSetEvent( Event, 0, FALSE );
+}
+
+
+NTSTATUS
+NtfsCommonRead (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp,
+ IN BOOLEAN AcquireScb
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Read called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+ AcquireScb - Indicates if this routine should acquire the scb
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ EOF_WAIT_BLOCK EofWaitBlock;
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+
+ PTOP_LEVEL_CONTEXT TopLevelContext;
+
+ VBO StartingVbo;
+ LONGLONG ByteCount;
+ LONGLONG ByteRange;
+ ULONG RequestedByteCount;
+
+ PBCB Bcb = NULL;
+
+ BOOLEAN FoundAttribute = FALSE;
+ BOOLEAN PostIrp = FALSE;
+ BOOLEAN OplockPostIrp = FALSE;
+
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN ReleaseScb;
+ BOOLEAN PagingIoAcquired = FALSE;
+ BOOLEAN DoingIoAtEof = FALSE;
+
+ BOOLEAN Wait;
+ BOOLEAN PagingIo;
+ BOOLEAN NonCachedIo;
+ BOOLEAN SynchronousIo;
+
+ NTFS_IO_CONTEXT LocalContext;
+
+ //
+ // A system buffer is only used if we have to access the
+ // buffer directly from the Fsp to clear a portion or to
+ // do a synchronous I/O, or a cached transfer. It is
+ // possible that our caller may have already mapped a
+ // system buffer, in which case we must remember this so
+ // we do not unmap it on the way out.
+ //
+
+ PVOID SystemBuffer = NULL;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonRead\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("ByteCount = %08lx\n", IrpSp->Parameters.Read.Length) );
+ DebugTrace( 0, Dbg, ("ByteOffset = %016I64x\n", IrpSp->Parameters.Read.ByteOffset) );
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // Let's kill invalid read requests.
+ //
+
+ if (TypeOfOpen != UserFileOpen &&
+#ifdef _CAIRO_
+ DebugDoit( TypeOfOpen != UserPropertySetOpen && )
+#endif // _CAIRO_
+ TypeOfOpen != UserVolumeOpen &&
+ TypeOfOpen != StreamFileOpen) {
+
+ DebugTrace( 0, Dbg, ("Invalid file object for read\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_INVALID_DEVICE_REQUEST) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ //
+ // Initialize the appropriate local variables.
+ //
+
+ Wait = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ PagingIo = BooleanFlagOn( Irp->Flags, IRP_PAGING_IO );
+ NonCachedIo = BooleanFlagOn( Irp->Flags,IRP_NOCACHE );
+ SynchronousIo = BooleanFlagOn( FileObject->Flags, FO_SYNCHRONOUS_IO );
+
+ //
+ // Extract starting Vbo and offset.
+ //
+
+ StartingVbo = IrpSp->Parameters.Read.ByteOffset.QuadPart;
+
+ ByteCount = IrpSp->Parameters.Read.Length;
+ ByteRange = StartingVbo + ByteCount;
+
+ RequestedByteCount = (ULONG)ByteCount;
+
+ //
+ // Check for a null request, and return immediately
+ //
+
+ if ((ULONG)ByteCount == 0) {
+
+ DebugTrace( 0, Dbg, ("No bytes to read\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_SUCCESS) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Make sure there is an initialized NtfsIoContext block.
+ //
+
+ if (TypeOfOpen == UserVolumeOpen
+ || NonCachedIo) {
+
+ //
+ // If there is a context pointer, we need to make sure it was
+ // allocated and not a stale stack pointer.
+ //
+
+ if (IrpContext->Union.NtfsIoContext == NULL
+ || !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT )) {
+
+ //
+ // If we can wait, use the context on the stack. Otherwise
+ // we need to allocate one.
+ //
+
+ if (Wait) {
+
+ IrpContext->Union.NtfsIoContext = &LocalContext;
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ } else {
+
+ IrpContext->Union.NtfsIoContext = (PNTFS_IO_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIoContextLookasideList );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+ }
+ }
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ //
+ // Store whether we allocated this context structure in the structure
+ // itself.
+ //
+
+ IrpContext->Union.NtfsIoContext->AllocatedContext =
+ BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ if (Wait) {
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+
+ } else {
+
+ IrpContext->Union.NtfsIoContext->PagingIo = PagingIo;
+ IrpContext->Union.NtfsIoContext->Wait.Async.ResourceThreadId =
+ ExGetCurrentResourceThread();
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount =
+ (ULONG)ByteCount;
+ }
+ }
+
+ //
+ // Handle volume Dasd here.
+ //
+
+ if (TypeOfOpen == UserVolumeOpen) {
+
+ NTSTATUS Status;
+
+ //
+ // If the caller has not asked for extended DASD IO access then
+ // limit with the volume size.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_ALLOW_XTENDED_DASD_IO )) {
+
+ //
+ // If the starting vbo is past the end of the volume, we are done.
+ //
+
+ if (Scb->Header.FileSize.QuadPart <= StartingVbo) {
+
+ DebugTrace( 0, Dbg, ("No bytes to read\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", STATUS_END_OF_FILE) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_END_OF_FILE );
+ return STATUS_END_OF_FILE;
+
+ //
+ // If the write extends beyond the end of the volume, truncate the
+ // bytes to write.
+ //
+
+ } else if (Scb->Header.FileSize.QuadPart < ByteRange) {
+
+ ByteCount = Scb->Header.FileSize.QuadPart - StartingVbo;
+
+ if (!Wait) {
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount =
+ (ULONG)ByteCount;
+ }
+ }
+ }
+
+ Status = NtfsVolumeDasdIo( IrpContext,
+ Irp,
+ Vcb,
+ StartingVbo,
+ (ULONG)ByteCount );
+
+ //
+ // If the volume was opened for Synchronous IO, update the current
+ // file position.
+ //
+
+ if (SynchronousIo && !PagingIo &&
+ NT_SUCCESS(Status)) {
+
+ IrpSp->FileObject->CurrentByteOffset.QuadPart = StartingVbo + Irp->IoStatus.Information;
+ }
+
+ DebugTrace( 0, Dbg, ("Complete with %08lx bytes read\n", Irp->IoStatus.Information) );
+ DebugTrace( -1, Dbg, ("NtfsCommonRead: Exit -> %08lx\n", Status) );
+
+ if (Wait) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ return Status;
+ }
+
+ //
+ // Keep a pointer to the common fsrtl header.
+ //
+
+ Header = &Scb->Header;
+
+ //
+ // If this is a paging file, just send it to the device driver.
+ // We assume Mm is a good citizen.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )
+ && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL );
+ }
+
+ //
+ // Do the usual STATUS_PENDING things.
+ //
+
+ IoMarkIrpPending( Irp );
+
+ //
+ // Perform the actual IO, it will be completed when the io finishes.
+ //
+
+ NtfsPagingFileIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ (ULONG)ByteCount );
+
+ //
+ // We, nor anybody else, need the IrpContext any more.
+ //
+
+ NtfsCompleteRequest( &IrpContext, NULL, 0 );
+
+ return STATUS_PENDING;
+ }
+
+ //
+ // Accumulate interesting statistics.
+ //
+
+ if (PagingIo) {
+ CollectReadStats( Vcb, TypeOfOpen, Scb, Fcb, ByteCount );
+ }
+
+
+ //
+ // Use a try-finally to free Scb and buffers on the way out.
+ // At this point we can treat all requests identically since we
+ // have a usable Scb for each of them. (Volume, User or Stream file)
+ //
+
+ try {
+
+ //
+ // This case corresponds to a non-directory file read.
+ //
+
+ LONGLONG FileSize;
+ LONGLONG ValidDataLength;
+
+ //
+ // If this is a noncached transfer and is not a paging I/O, and
+ // the file has a data section, then we will do a flush here
+ // to avoid stale data problems. Note that we must flush before
+ // acquiring the Fcb shared since the write may try to acquire
+ // it exclusive. This is not necessary for compressed files, since
+ // we will turn user noncached writes into cached writes.
+ //
+
+ if (!PagingIo &&
+ NonCachedIo &&
+ (FileObject->SectionObjectPointer->DataSectionObject != NULL)) {
+
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, TRUE );
+
+ if (Scb->CompressionUnit == 0) {
+
+ //
+ // It is possible that this read is part of a top level request or
+ // is being called by MM to create an image section. We will update
+ // the top-level context to reflect this. All of the exception
+ // handling will correctly handle the log file full in this case.
+ //
+
+ TopLevelContext = NtfsGetTopLevelContext();
+
+ if (TopLevelContext->SavedTopLevelIrp != NULL) {
+
+ TopLevelContext->TopLevelRequest = FALSE;
+ }
+
+ CcFlushCache( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ &Irp->IoStatus );
+
+ //
+ // Make sure the data got out to disk.
+ //
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ ExAcquireResourceExclusive( Scb->Header.PagingIoResource, TRUE );
+ ExReleaseResource( Scb->Header.PagingIoResource );
+
+ //
+ // Check for errors in the flush.
+ //
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &Irp->IoStatus.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+
+ } else {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+ }
+
+ //
+ // We need shared access to the Scb before proceeding.
+ // We won't acquire the Scb for a non-cached read of the first 4
+ // file records.
+ //
+
+ if (AcquireScb &&
+
+ (!NonCachedIo || NtfsGtrMftRef( &Fcb->FileReference, &VolumeFileReference))) {
+
+ //
+ // Figure out if we have been entered during the posting
+ // of a top level request.
+ //
+
+ TopLevelContext = NtfsGetTopLevelContext();
+
+ //
+ // Initially we always force reads to appear to be top level
+ // requests. If we reach this point the read not to the paging
+ // file so it is safe to determine if we are really a top level
+ // request. If there is an Ntfs request above us we will clear
+ // the TopLevelRequest field in the TopLevelContext.
+ //
+
+ if (TopLevelContext->ValidSavedTopLevel) {
+ TopLevelContext->TopLevelRequest = FALSE;
+ }
+
+ //
+ // If this is not a paging I/O (cached or user noncached I/O),
+ // then acquire the paging I/O resource. (Note, you can only
+ // do cached I/O to user streams, and they always have a paging
+ // I/O resource.
+ //
+
+ if (!PagingIo) {
+
+ //
+ // If we cannot acquire the resource, then raise.
+ //
+
+ if (!ExAcquireSharedWaitForExclusive( Scb->Header.PagingIoResource, Wait )) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ PagingIoAcquired = TRUE;
+
+ //
+ // Check if we have already gone through cleanup on this handle.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_CLEANUP )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CLOSED, NULL, NULL );
+ }
+
+ //
+ // The reason that we always handle the user requests through the cache,
+ // is that there is no better way to deal with alignment issues, for
+ // the frequent case where the user noncached I/O is not an integral of
+ // the Compression Unit. Also, the way we synchronize the case where
+ // a compression unit is being moved to a different spot on disk during
+ // a write, is to keep the pages locked in memory during the write, so
+ // that there will be no need to read the disk at the same time. (If
+ // we allowed real noncached I/O, then we would somehow have to synchronize
+ // the noncached read with the write of the same data.)
+ //
+ // Bottom line is we can only really support cached reads to compresed
+ // files.
+ //
+
+ if ((Scb->CompressionUnit != 0) && NonCachedIo) {
+
+ NonCachedIo = FALSE;
+
+ if (Scb->FileObject == NULL) {
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ FileObject = Scb->FileObject;
+ }
+
+ //
+ // If this is async I/O directly to the disk we need to check that
+ // we don't exhaust the number of times a single thread can
+ // acquire the resource.
+ //
+
+ if (!Wait && NonCachedIo) {
+
+ if (ExIsResourceAcquiredShared(Scb->Header.PagingIoResource) > MAX_SCB_ASYNC_ACQUIRE) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.Resource = Scb->Header.PagingIoResource;
+ }
+
+ //
+ // Now check if the attribute has been deleted or if the
+ // volume has been dismounted.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED | SCB_STATE_VOLUME_DISMOUNTED)) {
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL );
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+ }
+
+ //
+ // If this is a paging I/O, and there is a paging I/O resource, then
+ // we acquire the main resource here. Note that for most paging I/Os
+ // (like faulting for cached I/O), we already own the paging I/O resource,
+ // so we acquire nothing here! But, for other cases like user-mapped files,
+ // we do check if paging I/O is acquired, and acquire the main resource if
+ // not. The point is, we need some guarantee still that the file will not
+ // be truncated.
+ //
+
+ } else if ((Scb->Header.PagingIoResource != NULL) &&
+ !ExIsResourceAcquiredShared(Scb->Header.PagingIoResource)) {
+
+ //
+ // If we cannot acquire the resource, then raise.
+ //
+
+ if (!ExAcquireResourceShared( Scb->Header.Resource, Wait )) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ ScbAcquired = TRUE;
+
+ //
+ // Now check if the attribute has been deleted or if the
+ // volume has been dismounted.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED | SCB_STATE_VOLUME_DISMOUNTED)) {
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL );
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+ }
+ }
+ }
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
+
+ ReleaseScb = FALSE;
+
+ if (AcquireScb && !ScbAcquired) {
+
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+ ScbAcquired = TRUE;
+ ReleaseScb = TRUE;
+ }
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ if (ReleaseScb) {
+
+ ExReleaseResource( Scb->Header.Resource );
+ ScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // We check whether we can proceed
+ // based on the state of the file oplocks.
+ //
+
+ if (TypeOfOpen == UserFileOpen) {
+
+ Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsPrePostIrp );
+
+ if (Status != STATUS_SUCCESS) {
+
+ OplockPostIrp = TRUE;
+ PostIrp = TRUE;
+ try_return( NOTHING );
+ }
+
+ //
+ // This oplock call can affect whether fast IO is possible.
+ // We may have broken an oplock to no oplock held. If the
+ // current state of the file is FastIoIsNotPossible then
+ // recheck the fast IO state.
+ //
+
+ if (Scb->Header.IsFastIoPossible == FastIoIsNotPossible) {
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ //
+ // We have to check for read access according to the current
+ // state of the file locks.
+ //
+
+ if (!PagingIo
+ && Scb->ScbType.Data.FileLock != NULL
+ && !FsRtlCheckLockForReadAccess( Scb->ScbType.Data.FileLock,
+ Irp )) {
+
+ try_return( Status = STATUS_FILE_LOCK_CONFLICT );
+ }
+ }
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we are reading beyond ValidDataLength. We have to
+ // do it now so that our reads are not nooped. We only need to block
+ // on nonrecursive I/O (cached or page fault to user section, because
+ // if it is paging I/O, we must be part of a reader or writer who is
+ // synchronized.
+ //
+
+ if ((ByteRange > Header->ValidDataLength.QuadPart) && !PagingIo) {
+
+ //
+ // We must serialize with anyone else doing I/O at beyond
+ // ValidDataLength, and then remember if we need to declare
+ // when we are done. If our caller has already serialized
+ // with EOF then there is nothing for us to do here.
+ //
+
+ if ((IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == Fcb) ||
+ (IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == (PFCB) Scb)) {
+
+ DoingIoAtEof = TRUE;
+
+ } else {
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ &EofWaitBlock );
+
+ //
+ // Set the Flag if we are in fact beyond ValidDataLength.
+ //
+
+ if (DoingIoAtEof) {
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+ IrpContext->FcbWithPagingExclusive = (PFCB) Scb;
+ }
+ }
+ }
+
+ //
+ // Get file sizes from the Scb.
+ //
+ // We must get ValidDataLength first since it is always
+ // increased second (the case we are unprotected) and
+ // we don't want to capture ValidDataLength > FileSize.
+ //
+
+ ValidDataLength = Header->ValidDataLength.QuadPart;
+ FileSize = Header->FileSize.QuadPart;
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Optimize for the case where we are trying to fault in an entire
+ // compression unit, even if past the end of the file. Go ahead
+ // and round the local FileSize to a compression unit boundary.
+ // This will allow all of these pages to come into memory when
+ // CC touches the first page out of memory. Otherwise CC will
+ // force them into memory one page at a time.
+ //
+
+ if (PagingIo && (Scb->CompressionUnit != 0) && !FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+ FileSize += (Scb->CompressionUnit - 1);
+ ((PLARGE_INTEGER) &FileSize)->LowPart &= ~(Scb->CompressionUnit - 1);
+ }
+
+ //
+ // If the read starts beyond End of File, return EOF.
+ //
+
+ if (StartingVbo >= FileSize) {
+
+ DebugTrace( 0, Dbg, ("End of File\n") );
+
+ try_return ( Status = STATUS_END_OF_FILE );
+ }
+
+ //
+ // If the read extends beyond EOF, truncate the read
+ //
+
+ if (ByteRange > FileSize) {
+
+ ByteCount = FileSize - StartingVbo;
+ ByteRange = StartingVbo + ByteCount;
+
+ RequestedByteCount = (ULONG)ByteCount;
+
+ if (NonCachedIo && !Wait) {
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount =
+ (ULONG)ByteCount;
+ }
+ }
+
+
+ //
+ // HANDLE THE NONCACHED RESIDENT ATTRIBUTE CASE
+ //
+ // We let the cached case take the normal path for the following
+ // reasons:
+ //
+ // o To insure data coherency if a user maps the file
+ // o To get a page in the cache to keep the Fcb around
+ // o So the data can be accessed via the Fast I/O path
+ //
+ // The disadvantage is the overhead to fault the data in the
+ // first time, but we may be able to do this with asynchronous
+ // read ahead.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT | SCB_STATE_CONVERT_UNDERWAY ) &&
+ NonCachedIo) {
+
+ ReleaseScb = FALSE;
+
+ if (AcquireScb && !ScbAcquired) {
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+ ScbAcquired = TRUE;
+ ReleaseScb = TRUE;
+ }
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )
+ && NonCachedIo) {
+
+ PUCHAR AttrValue;
+
+ //
+ // Get hold of the user's buffer.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // This is a resident attribute, we need to look it up
+ // and copy the desired range of bytes to the user's
+ // buffer.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ FoundAttribute = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ AttrValue = NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ RtlCopyMemory( SystemBuffer,
+ Add2Ptr( AttrValue, ((ULONG)StartingVbo) ),
+ (ULONG)ByteCount );
+
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+
+ try_return( Status = STATUS_SUCCESS );
+
+ } else {
+
+ if (ReleaseScb) {
+ ExReleaseResource( Scb->Header.Resource );
+ ScbAcquired = FALSE;
+ }
+ }
+ }
+
+
+ //
+ // HANDLE THE NON-CACHED CASE
+ //
+
+ if (NonCachedIo) {
+
+ ULONG BytesToRead;
+
+ ULONG SectorSize;
+
+ ULONG ZeroOffset;
+ ULONG ZeroLength = 0;
+
+ DebugTrace( 0, Dbg, ("Non cached read.\n") );
+
+ //
+ // For a compressed stream, which is user-mapped, reserve space
+ // as pages come in.
+ //
+
+ if (FlagOn(Header->Flags, FSRTL_FLAG_USER_MAPPED_FILE) &&
+ FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) &&
+ !NtfsReserveClusters(IrpContext, Scb, StartingVbo, (ULONG)ByteCount)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ //
+ // Start by zeroing any part of the read after Valid Data
+ //
+
+ if (ByteRange > ValidDataLength) {
+
+ ReleaseScb = FALSE;
+
+ //
+ // We need to look at ValidDataToDisk, because it could be higher.
+ //
+
+ //
+ // We have to have the main resource to look at ValidDataToDisk.
+ //
+
+ if (AcquireScb && !ScbAcquired) {
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+ ScbAcquired = TRUE;
+ ReleaseScb = TRUE;
+ }
+
+ //
+ // If ValidDataToDisk is actually greater than
+ // ValidDataLength, then we must have lost a page
+ // during the middle of a write, and we should not
+ // zero that data on the way back in!
+ //
+
+ if (ValidDataLength < Scb->ValidDataToDisk) {
+ ValidDataLength = Scb->ValidDataToDisk;
+ }
+
+ if (ByteRange > ValidDataLength) {
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ if (StartingVbo < ValidDataLength) {
+
+ //
+ // Assume we will zero the entire amount.
+ //
+
+ ZeroLength = (ULONG)ByteCount;
+
+ //
+ // The new byte count and the offset to start filling with zeroes.
+ //
+
+ ByteCount = ValidDataLength - StartingVbo;
+ ZeroOffset = (ULONG)ByteCount;
+
+ //
+ // Now reduce the amount to zero by the zero offset.
+ //
+
+ ZeroLength -= ZeroOffset;
+
+ //
+ // If this was non-cached I/O then convert it to synchronous.
+ // This is because we don't want to zero the buffer now or
+ // we will lose the data when the driver purges the cache.
+ //
+
+ if (!Wait) {
+
+ Wait = TRUE;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ //
+ // Store whether we allocated this context structure in the structure
+ // itself.
+ //
+
+ IrpContext->Union.NtfsIoContext->AllocatedContext =
+ BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+ }
+
+ } else {
+
+ //
+ // All we have to do now is sit here and zero the
+ // user's buffer, no reading is required.
+ //
+
+ SafeZeroMemory( (PUCHAR)SystemBuffer, (ULONG)ByteCount );
+
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+
+ try_return ( Status = STATUS_SUCCESS );
+ }
+ }
+
+ //
+ // Now free the Scb if we only acquired it here.
+ //
+
+ if (ReleaseScb) {
+ ExReleaseResource( Scb->Header.Resource );
+ ScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // Get the sector size
+ //
+
+ SectorSize = Vcb->BytesPerSector;
+
+ //
+ // Round up to a sector boundry
+ //
+
+ BytesToRead = ((ULONG)ByteCount + (SectorSize - 1)) & ~(SectorSize - 1);
+
+ //
+ // Call a special routine if we do not have sector alignment
+ // and the file is not compressed.
+ //
+
+ if ((((ULONG)StartingVbo) & (SectorSize - 1)
+ || BytesToRead > IrpSp->Parameters.Read.Length)
+
+ &&
+
+ (Scb->CompressionUnit == 0)) {
+
+ //
+ // If we can't wait, we must post this.
+ //
+
+ if (!Wait) {
+
+ try_return( PostIrp = TRUE );
+ }
+
+ //
+ // Do the physical read.
+ //
+
+ ASSERT(FileObject->SectionObjectPointer == &Scb->NonpagedScb->SegmentObject);
+
+ NtfsNonCachedNonAlignedIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ (ULONG)ByteCount );
+
+ BytesToRead = (ULONG)ByteCount;
+
+ } else {
+
+ //
+ // Just to help reduce confusion. At this point:
+ //
+ // RequestedByteCount - is the number of bytes originally
+ // taken from the Irp, but constrained
+ // to filesize.
+ //
+ // ByteCount - is RequestedByteCount constrained to
+ // ValidDataLength.
+ //
+ // BytesToRead - is ByteCount rounded up to sector
+ // boundry. This is the number of bytes
+ // that we must physically read.
+ //
+
+ //
+ // Perform the actual IO
+ //
+
+ if (NtfsNonCachedIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ BytesToRead,
+ (FileObject->SectionObjectPointer != &Scb->NonpagedScb->SegmentObject) )
+ == STATUS_PENDING) {
+
+ IrpContext->Union.NtfsIoContext = NULL;
+ PagingIoAcquired = FALSE;
+ Irp = NULL;
+
+ try_return( Status = STATUS_PENDING );
+ }
+ }
+
+ //
+ // If the call didn't succeed, raise the error status
+ //
+
+ if (!NT_SUCCESS( Status = Irp->IoStatus.Status )) {
+
+ NtfsNormalizeAndRaiseStatus( IrpContext,
+ Status,
+ STATUS_UNEXPECTED_IO_ERROR );
+ }
+
+ //
+ // Else set the Irp information field to reflect the
+ // entire desired read.
+ //
+
+ ASSERT( Irp->IoStatus.Information == BytesToRead );
+
+ Irp->IoStatus.Information = RequestedByteCount;
+
+ //
+ // If we rounded up to a sector boundry before, zero out
+ // the other garbage we read from the disk.
+ //
+
+ if (BytesToRead > (ULONG)ByteCount) {
+
+ if (SystemBuffer == NULL) {
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+ }
+
+ SafeZeroMemory( (PUCHAR)SystemBuffer + (ULONG)ByteCount,
+ BytesToRead - (ULONG)ByteCount );
+ }
+
+ //
+ // If we need to zero the tail of the buffer because of valid data
+ // then do so now.
+ //
+
+ if (ZeroLength != 0) {
+
+ if (SystemBuffer == NULL) {
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+ }
+
+ SafeZeroMemory( Add2Ptr( SystemBuffer, ZeroOffset ), ZeroLength );
+ }
+
+ //
+ // The transfer is complete.
+ //
+
+ try_return( Status );
+
+ } // if No Intermediate Buffering
+
+
+ //
+ // HANDLE THE CACHED CASE
+ //
+
+ else {
+
+ //
+ // We need to go through the cache for this
+ // file object. First handle the noncompressed calls.
+ //
+
+#ifdef _CAIRO_
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_COMPRESSED)) {
+#endif _CAIRO_
+
+ //
+ // We delay setting up the file cache until now, in case the
+ // caller never does any I/O to the file, and thus
+ // FileObject->PrivateCacheMap == NULL.
+ //
+
+ if (FileObject->PrivateCacheMap == NULL) {
+
+ DebugTrace( 0, Dbg, ("Initialize cache mapping.\n") );
+
+ //
+ // Now initialize the cache map.
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ CcInitializeCacheMap( FileObject,
+ (PCC_FILE_SIZES)&Header->AllocationSize,
+ FALSE,
+ &NtfsData.CacheManagerCallbacks,
+ Scb );
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ CcSetReadAheadGranularity( FileObject, READ_AHEAD_GRANULARITY );
+ }
+
+ //
+ // DO A NORMAL CACHED READ, if the MDL bit is not set,
+ //
+
+ DebugTrace( 0, Dbg, ("Cached read.\n") );
+
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) {
+
+ //
+ // Get hold of the user's buffer.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Now try to do the copy.
+ //
+
+ if (!CcCopyRead( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ Wait,
+ SystemBuffer,
+ &Irp->IoStatus )) {
+
+ DebugTrace( 0, Dbg, ("Cached Read could not wait\n") );
+
+ try_return( PostIrp = TRUE );
+ }
+
+ Status = Irp->IoStatus.Status;
+
+ ASSERT( NT_SUCCESS( Status ));
+
+ try_return( Status );
+ }
+
+ //
+ // HANDLE A MDL READ
+ //
+
+ else {
+
+ DebugTrace( 0, Dbg, ("MDL read.\n") );
+
+ ASSERT( Wait );
+
+ CcMdlRead( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ &Irp->MdlAddress,
+ &Irp->IoStatus );
+
+ Status = Irp->IoStatus.Status;
+
+ ASSERT( NT_SUCCESS( Status ));
+
+ try_return( Status );
+ }
+
+ //
+ // Handle the compressed calls.
+ //
+
+#ifdef _CAIRO_
+ } else {
+
+ PCOMPRESSED_DATA_INFO CompressedDataInfo;
+ PMDL *NewMdl;
+
+ ASSERT((StartingVbo & (NTFS_CHUNK_SIZE - 1)) == 0);
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+ try_return(Status = STATUS_INVALID_READ_MODE);
+ }
+
+ if ((Header->FileObjectC == NULL) ||
+ (Header->FileObjectC->PrivateCacheMap == NULL)) {
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ NtfsCreateInternalCompressedStream( IrpContext, Scb, FALSE );
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+
+ //
+ // Assume success.
+ //
+
+ Irp->IoStatus.Status = Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = (ULONG)(ByteRange - StartingVbo);
+
+ //
+ // Based on the Mdl minor function, set up the appropriate
+ // parameters for the call below.
+ //
+
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) {
+
+ //
+ // Get hold of the user's buffer.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+ NewMdl = NULL;
+
+ } else {
+
+ //
+ // We will deliver the Mdl directly to the Irp.
+ //
+
+ SystemBuffer = NULL;
+ NewMdl = &Irp->MdlAddress;
+ }
+
+ CompressedDataInfo = (PCOMPRESSED_DATA_INFO)IrpContext->Union.AuxiliaryBuffer->Buffer;
+
+ CompressedDataInfo->CompressionFormatAndEngine =
+ (USHORT)((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1);
+ CompressedDataInfo->CompressionUnitShift = (UCHAR)(Scb->CompressionUnitShift + Vcb->ClusterShift);
+ CompressedDataInfo->ChunkShift = NTFS_CHUNK_SHIFT;
+ CompressedDataInfo->ClusterShift = (UCHAR)Vcb->ClusterShift;
+ CompressedDataInfo->Reserved = 0;
+ CompressedDataInfo->NumberOfChunks = 0;
+
+ //
+ // Do the compressed read in common code with the Fast Io path.
+ // We do it from a loop because we may need to create the other
+ // data stream.
+ //
+
+ while (TRUE) {
+
+ Status = NtfsCompressedCopyRead( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ SystemBuffer,
+ NewMdl,
+ CompressedDataInfo,
+ IrpContext->Union.AuxiliaryBuffer->Length,
+ IoGetRelatedDeviceObject(FileObject),
+ Header,
+ Scb->CompressionUnit,
+ NTFS_CHUNK_SIZE );
+
+ //
+ // On successful Mdl requests we hang on to the PagingIo resource.
+ //
+
+ if ((NewMdl != NULL) && NT_SUCCESS(Status)) {
+ PagingIoAcquired = FALSE;
+ }
+
+ //
+ // Check for the status that says we need to create the normal
+ // data stream, else we are done.
+ //
+
+ if (Status != STATUS_NOT_MAPPED_DATA) {
+ break;
+ }
+
+ //
+ // Create the normal data stream and loop back to try again.
+ //
+
+ ASSERT(Scb->FileObject == NULL);
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+ }
+#endif _CAIRO_
+ }
+
+ try_exit: NOTHING;
+
+ //
+ // If the request was not posted, deal with it.
+ //
+
+ if (Irp) {
+
+ if (!PostIrp) {
+
+ LONGLONG ActualBytesRead;
+
+ DebugTrace( 0, Dbg, ("Completing request with status = %08lx\n",
+ Status));
+
+ DebugTrace( 0, Dbg, (" Information = %08lx\n",
+ Irp->IoStatus.Information));
+
+ //
+ // Record the total number of bytes actually read
+ //
+
+ ActualBytesRead = Irp->IoStatus.Information;
+
+ //
+ // If the file was opened for Synchronous IO, update the current
+ // file position. Make sure to use the original file object
+ // not an internal stream we may use within this routine.
+ //
+
+ if (!PagingIo) {
+
+ if (SynchronousIo) {
+
+ IrpSp->FileObject->CurrentByteOffset.QuadPart = StartingVbo + ActualBytesRead;
+ }
+
+ //
+ // On success, do the following to let us update last access time.
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ SetFlag( IrpSp->FileObject->Flags, FO_FILE_FAST_IO_READ );
+ }
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("Passing request to Fsp\n") );
+
+ if (!OplockPostIrp) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+ }
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCommonRead );
+
+ //
+ // Clean up any Bcb from read compressed.
+ //
+
+ if (Bcb != NULL) {
+
+ CcUnpinData( Bcb );
+ }
+
+ //
+ // If the Scb has been acquired, release it.
+ //
+
+ if (PagingIoAcquired) {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+
+ if (Irp) {
+
+ if (ScbAcquired) {
+
+ ExReleaseResource( Scb->Header.Resource );
+ }
+ }
+
+ //
+ // Free the attribute enumeration context if
+ // used.
+ //
+
+ if (FoundAttribute) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ //
+ // Complete the request if we didn't post it and no exception
+ //
+ // Note that NtfsCompleteRequest does the right thing if either
+ // IrpContext or Irp are NULL
+ //
+
+ if (!PostIrp && !AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext,
+ Irp ? &Irp : NULL,
+ Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonRead -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
diff --git a/private/ntos/cntfs/resrcsup.c b/private/ntos/cntfs/resrcsup.c
new file mode 100644
index 000000000..e6d186118
--- /dev/null
+++ b/private/ntos/cntfs/resrcsup.c
@@ -0,0 +1,2114 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ ResrcSup.c
+
+Abstract:
+
+ This module implements the Ntfs Resource acquisition routines
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAcquireAllFiles)
+#pragma alloc_text(PAGE, NtfsAcquireExclusiveFcb)
+#pragma alloc_text(PAGE, NtfsAcquireExclusiveScb)
+#pragma alloc_text(PAGE, NtfsAcquireSharedScbForTransaction)
+#pragma alloc_text(PAGE, NtfsAcquireExclusiveGlobal)
+#pragma alloc_text(PAGE, NtfsAcquireExclusiveVcb)
+#pragma alloc_text(PAGE, NtfsAcquireFcbWithPaging)
+#pragma alloc_text(PAGE, NtfsAcquireForCreateSection)
+#pragma alloc_text(PAGE, NtfsAcquireScbForLazyWrite)
+#pragma alloc_text(PAGE, NtfsAcquireSharedGlobal)
+#pragma alloc_text(PAGE, NtfsAcquireFileForCcFlush)
+#pragma alloc_text(PAGE, NtfsAcquireFileForModWrite)
+#pragma alloc_text(PAGE, NtfsAcquireSharedVcb)
+#pragma alloc_text(PAGE, NtfsAcquireVolumeFileForClose)
+#pragma alloc_text(PAGE, NtfsAcquireVolumeFileForLazyWrite)
+#pragma alloc_text(PAGE, NtfsAcquireVolumeForClose)
+#pragma alloc_text(PAGE, NtfsReleaseAllFiles)
+#pragma alloc_text(PAGE, NtfsReleaseFcbWithPaging)
+#pragma alloc_text(PAGE, NtfsReleaseFileForCcFlush)
+#pragma alloc_text(PAGE, NtfsReleaseForCreateSection)
+#pragma alloc_text(PAGE, NtfsReleaseScbFromLazyWrite)
+#pragma alloc_text(PAGE, NtfsReleaseScbWithPaging)
+#pragma alloc_text(PAGE, NtfsReleaseSharedResources)
+#pragma alloc_text(PAGE, NtfsReleaseVolumeFileFromClose)
+#pragma alloc_text(PAGE, NtfsReleaseVolumeFileFromLazyWrite)
+#pragma alloc_text(PAGE, NtfsReleaseVolumeFromClose)
+#endif
+
+
+VOID
+NtfsAcquireExclusiveGlobal (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires exclusive access to the global resource.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+
+ PAGED_CODE();
+
+ if (!ExAcquireResourceExclusive( &NtfsData.Resource, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsAcquireSharedGlobal (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires shared access to the global resource.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+
+ PAGED_CODE();
+
+ if (!ExAcquireResourceShared( &NtfsData.Resource, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsAcquireAllFiles (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN Exclusive,
+ IN BOOLEAN AcquirePagingIo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine non-recursively requires all files on a volume.
+
+Arguments:
+
+ Vcb - Supplies the volume
+
+ Exclusive - Indicates if we should be acquiring all the files exclusively.
+ If FALSE then we acquire all the files shared except for files with
+ streams which could be part of transactions.
+
+ AcquirePagingIo - Indicates if we need to acquire the paging io resource
+ exclusively. Only needed if a future call will flush the volume
+ (i.e. shutdown)
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB Fcb;
+ PSCB *Scb;
+ PSCB NextScb;
+ PVOID RestartKey;
+
+ PAGED_CODE();
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ RestartKey = NULL;
+ while (TRUE) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ Fcb = NtfsGetNextFcbTableEntry(Vcb, &RestartKey);
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ if (Fcb == NULL) {
+
+ break;
+ }
+
+ ASSERT_FCB( Fcb );
+
+ //
+ // We can skip over the Fcb's for any of the Scb's in the Vcb.
+ // We delay acquiring those to avoid deadlocks.
+ //
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER) {
+
+ //
+ // If there is a paging Io resource then acquire this if required.
+ //
+
+ if (AcquirePagingIo && (Fcb->PagingIoResource != NULL)) {
+
+ ExAcquireResourceExclusive( Fcb->PagingIoResource, TRUE );
+ }
+
+ //
+ // Acquire this Fcb whether or not the underlying file has been deleted.
+ //
+
+ if (Exclusive ||
+ IsDirectory( &Fcb->Info )) {
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, FALSE );
+
+ } else {
+
+ //
+ // Assume that we only need this file shared. We will then
+ // look for Lsn related streams.
+ //
+
+ NtfsAcquireSharedFcb( IrpContext, Fcb, NULL, TRUE );
+
+ //
+ // Walk through all of the Scb's for the file and look for
+ // an Lsn protected stream.
+ //
+
+ NtfsLockFcb( IrpContext, Fcb );
+
+ NextScb = NULL;
+
+ while (NextScb = NtfsGetNextChildScb( Fcb, NextScb )) {
+
+ if (!(NextScb->AttributeTypeCode == $DATA ||
+ NextScb->AttributeTypeCode == $EA)) {
+
+ break;
+ }
+ }
+
+ NtfsUnlockFcb( IrpContext, Fcb );
+
+ //
+ // If we found a protected Scb then release and reacquire the Fcb
+ // exclusively.
+ //
+
+ if (NextScb != NULL) {
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, FALSE );
+ }
+ }
+ }
+ }
+
+ //
+ // Now acquire the Fcb's in the Vcb.
+ //
+
+ Scb = &Vcb->QuotaTableScb;
+
+ while (TRUE) {
+
+ if ((*Scb != NULL)
+ && (*Scb != Vcb->BitmapScb)) {
+
+ if (AcquirePagingIo && ((*Scb)->Fcb->PagingIoResource != NULL)) {
+
+ ExAcquireResourceExclusive( (*Scb)->Fcb->PagingIoResource, TRUE );
+ }
+
+ NtfsAcquireExclusiveFcb( IrpContext, (*Scb)->Fcb, NULL, TRUE, FALSE );
+ }
+
+ if (Scb == &Vcb->MftScb) {
+
+ break;
+ }
+
+ Scb -= 1;
+ }
+
+ //
+ // Treat the bitmap as an end resource and acquire it last.
+ //
+
+ if (Vcb->BitmapScb != NULL) {
+
+ if (AcquirePagingIo && (Vcb->BitmapScb->Fcb->PagingIoResource != NULL)) {
+
+ ExAcquireResourceExclusive( Vcb->BitmapScb->Fcb->PagingIoResource, TRUE );
+ }
+
+ NtfsAcquireExclusiveFcb( IrpContext, Vcb->BitmapScb->Fcb, NULL, TRUE, FALSE );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsReleaseAllFiles (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN ReleasePagingIo
+ )
+
+/*++
+
+Routine Description:
+
+ This routine non-recursively requires all files on a volume.
+
+Arguments:
+
+ Vcb - Supplies the volume
+
+ ReleasePagingIo - Indicates whether we should release the paging io resources
+ as well.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB Fcb;
+ PSCB *Scb;
+ PVOID RestartKey;
+
+ PAGED_CODE();
+
+ //
+ // Loop to flush all of the prerestart streams, to do the loop
+ // we cycle through the Fcb Table and for each fcb we acquire it.
+ //
+
+ RestartKey = NULL;
+ while (TRUE) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ Fcb = NtfsGetNextFcbTableEntry(Vcb, &RestartKey);
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ if (Fcb == NULL) {
+
+ break;
+ }
+
+ ASSERT_FCB( Fcb );
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER) {
+
+ //
+ // Release the file.
+ //
+
+ if (ReleasePagingIo && (Fcb->PagingIoResource != NULL)) {
+
+ ExReleaseResource( Fcb->PagingIoResource );
+ }
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+ }
+
+ //
+ // Now release the Fcb's in the Vcb.
+ //
+
+ Scb = &Vcb->QuotaTableScb;
+
+ while (TRUE) {
+
+ if (*Scb != NULL) {
+
+ if (ReleasePagingIo && ((*Scb)->Fcb->PagingIoResource != NULL)) {
+
+ ExReleaseResource( (*Scb)->Fcb->PagingIoResource );
+ }
+
+ NtfsReleaseFcb( IrpContext, (*Scb)->Fcb );
+ }
+
+ if (Scb == &Vcb->MftScb) {
+
+ break;
+ }
+
+ Scb -= 1;
+ }
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAcquireExclusiveVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN RaiseOnCantWait
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires exclusive access to the Vcb.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to acquire
+
+ RaiseOnCantWait - Indicates if we should raise on an acquisition error
+ or simply return a BOOLEAN indicating that we couldn't get the
+ resource.
+
+Return Value:
+
+ BOOLEAN - Indicates if we were able to acquire the resource. This is really
+ only meaningful if the RaiseOnCantWait value is FALSE.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_VCB(Vcb);
+
+ PAGED_CODE();
+
+ if (ExAcquireResourceExclusive( &Vcb->Resource, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
+
+ return TRUE;
+ }
+
+ if (RaiseOnCantWait) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ return FALSE;
+}
+
+
+BOOLEAN
+NtfsAcquireSharedVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN RaiseOnCantWait
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires shared access to the Vcb.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to acquire
+
+ RaiseOnCantWait - Indicates if we should raise on an acquisition error
+ or simply return a BOOLEAN indicating that we couldn't get the
+ resource.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_VCB(Vcb);
+
+ PAGED_CODE();
+
+ if (ExAcquireResourceShared( &Vcb->Resource, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
+
+ return TRUE;
+ }
+
+ if (RaiseOnCantWait) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+
+ } else {
+
+ return FALSE;
+ }
+}
+
+
+VOID
+NtfsReleaseVcbCheckDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN UCHAR MajorCode,
+ IN PFILE_OBJECT FileObject OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will release the Vcb. We will also test here whether we should
+ teardown the Vcb at this point. If this is the last open queued to a dismounted
+ volume or the last close from a failed mount or the failed mount then we will
+ want to test the Vcb for a teardown.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to acquire
+
+ MajorCode - Indicates what type of operation we were called from.
+
+ FileObject - Optionally supplies the file object whose VPB pointer we need to
+ zero out
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_VCB(Vcb);
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT ) &&
+ (Vcb->CloseCount == 0)) {
+
+ ULONG ReferenceCount;
+ ULONG ResidualCount;
+
+ KIRQL SavedIrql;
+ BOOLEAN DeleteVcb = FALSE;
+
+ ASSERT_EXCLUSIVE_RESOURCE( &Vcb->Resource );
+
+ //
+ // The volume has gone through dismount. Now we need to decide if this
+ // release of the Vcb is the last reference for this volume. If so we
+ // can tear the volume down.
+ //
+ // We compare the reference count in the Vpb with the state of the volume
+ // and the type of operation. We also need to check if there is a
+ // referenced log file object.
+ //
+
+ IoAcquireVpbSpinLock( &SavedIrql );
+
+ ReferenceCount = Vcb->Vpb->ReferenceCount;
+
+ IoReleaseVpbSpinLock( SavedIrql );
+
+ ResidualCount = 0;
+
+ if (Vcb->LogFileObject != NULL) {
+
+ ResidualCount = 1;
+ }
+
+ if (MajorCode == IRP_MJ_CREATE) {
+
+ ResidualCount += 1;
+ }
+
+ //
+ // If the residual count is the same as the count in the Vpb then we
+ // can delete the Vpb.
+ //
+
+ if (ResidualCount == ReferenceCount) {
+
+ SetFlag( Vcb->VcbState, VCB_STATE_DELETE_UNDERWAY );
+
+ ExReleaseResource( &Vcb->Resource );
+
+ //
+ // Never delete the Vcb unless this is the last release of
+ // this Vcb.
+ //
+
+ if (!ExIsResourceAcquiredExclusive( &Vcb->Resource ) &&
+ (ExIsResourceAcquiredShared( &Vcb->Resource ) == 0)) {
+
+ if (ARGUMENT_PRESENT(FileObject)) { FileObject->Vpb = NULL; }
+
+ //
+ // If this is a create then the IO system will handle the
+ // Vpb.
+ //
+
+ if (MajorCode == IRP_MJ_CREATE) {
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_TEMP_VPB );
+ }
+
+ //
+ // Use the global resource to synchronize the DeleteVcb process.
+ //
+
+ (VOID) ExAcquireResourceExclusive( &NtfsData.Resource, TRUE );
+
+ RemoveEntryList( &Vcb->VcbLinks );
+
+ ExReleaseResource( &NtfsData.Resource );
+
+ NtfsDeleteVcb( IrpContext, &Vcb );
+
+ } else {
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_DELETE_UNDERWAY );
+ }
+
+ } else {
+
+ ExReleaseResource( &Vcb->Resource );
+ }
+
+ } else {
+
+ ExReleaseResource( &Vcb->Resource );
+ }
+}
+
+
+BOOLEAN
+NtfsAcquireFcbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN DontWait
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used in the create path only. It acquires the Fcb
+ and also the paging IO resource if it exists but only if the create
+ operation was doing a supersede/overwrite operation.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to acquire
+
+ DontWait - If TRUE this overrides the wait value in the IrpContext.
+ We won't wait for the resource and return whether the resource
+ was acquired.
+
+Return Value:
+
+ BOOLEAN - TRUE if acquired. FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN Status = FALSE;
+ BOOLEAN Wait = FALSE;
+ BOOLEAN PagingIoAcquired = FALSE;
+
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_FCB(Fcb);
+
+ PAGED_CODE();
+
+ //
+ // Sanity check that this is create. The supersede flag is only
+ // set in the create path and only tested here.
+ //
+
+ ASSERT( IrpContext->MajorFunction == IRP_MJ_CREATE );
+
+ if (!DontWait && FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Wait = TRUE;
+ }
+
+ //
+ // Free any exclusive paging I/O resource, we currently have, which
+ // must presumably be from a directory with a paging I/O resource.
+ //
+ // We defer releasing the paging io resource when we have logged
+ // changes against a stream. The only transaction that should be
+ // underway at this point is the create file case where we allocated
+ // a file record. In this case it is OK to release the paging io
+ // resource for the parent.
+ //
+
+ if (IrpContext->FcbWithPagingExclusive != NULL) {
+ // ASSERT(IrpContext->TransactionId == 0);
+ NtfsReleasePagingIo( IrpContext, IrpContext->FcbWithPagingExclusive );
+ }
+
+ //
+ // Loop until we get it right - worst case is twice through loop.
+ //
+
+ while (TRUE) {
+
+ //
+ // Acquire Paging I/O first. Testing for the PagingIoResource
+ // is not really safe without holding the main resource, so we
+ // correct for that below.
+ //
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) &&
+ (Fcb->PagingIoResource != NULL)) {
+ if (!ExAcquireResourceExclusive( Fcb->PagingIoResource, Wait )) {
+ break;
+ }
+ IrpContext->FcbWithPagingExclusive = Fcb;
+ PagingIoAcquired = TRUE;
+ }
+
+ //
+ // Let's acquire this Fcb exclusively.
+ //
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, TRUE, DontWait )) {
+
+ if (PagingIoAcquired) {
+ ASSERT(IrpContext->TransactionId == 0);
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+ break;
+ }
+
+ //
+ // If we now do not see a paging I/O resource we are golden,
+ // othewise we can absolutely release and acquire the resources
+ // safely in the right order, since a resource in the Fcb is
+ // not going to go away.
+ //
+
+ if (!FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ACQUIRE_PAGING ) ||
+ PagingIoAcquired ||
+ (Fcb->PagingIoResource == NULL)) {
+
+ Status = TRUE;
+ break;
+ }
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+
+ return Status;
+}
+
+
+VOID
+NtfsReleaseFcbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases access to the Fcb, including its
+ paging I/O resource if it exists.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to acquire
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_FCB(Fcb);
+
+ PAGED_CODE();
+
+ //
+ // We test that we currently hold the paging Io exclusive before releasing
+ // it. Checking the ExclusivePagingFcb in the IrpContext tells us if
+ // it is ours.
+ //
+
+ if ((IrpContext->TransactionId == 0) &&
+ (IrpContext->FcbWithPagingExclusive == Fcb)) {
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+}
+
+
+VOID
+NtfsReleaseScbWithPaging (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases access to the Scb, including its
+ paging I/O resource if it exists.
+
+Arguments:
+
+ Scb - Supplies the Fcb to acquire
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFCB Fcb = Scb->Fcb;
+
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_SCB(Scb);
+
+ PAGED_CODE();
+
+ //
+ // Release the paging Io resource in the Scb under the following
+ // conditions.
+ //
+ // - No transaction underway
+ // - This paging Io resource is in the IrpContext
+ // (This last test insures there is a paging IO resource
+ // and we own it).
+ //
+
+ if ((IrpContext->TransactionId == 0) &&
+ (IrpContext->FcbWithPagingExclusive == Fcb)) {
+ NtfsReleasePagingIo( IrpContext, Fcb );
+ }
+
+ NtfsReleaseScb( IrpContext, Scb );
+}
+
+
+BOOLEAN
+NtfsAcquireExclusiveFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb OPTIONAL,
+ IN BOOLEAN NoDeleteCheck,
+ IN BOOLEAN DontWait
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires exclusive access to the Fcb.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to acquire
+
+ Scb - This is the Scb for which we are acquiring the Fcb
+
+ NoDeleteCheck - If TRUE, we don't do any check for deleted files but
+ always acquire the Fcb.
+
+ DontWait - If TRUE this overrides the wait value in the IrpContext.
+ We won't wait for the resource and return whether the resource
+ was acquired.
+
+Return Value:
+
+ BOOLEAN - TRUE if acquired. FALSE otherwise.
+
+--*/
+
+{
+ NTSTATUS Status;
+ BOOLEAN Wait;
+
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_FCB(Fcb);
+
+ PAGED_CODE();
+
+ Status = STATUS_CANT_WAIT;
+
+ if (DontWait ||
+ !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT )) {
+
+ Wait = FALSE;
+
+ } else {
+
+ Wait = TRUE;
+ }
+
+ if (ExAcquireResourceExclusive( Fcb->Resource, Wait )) {
+
+ //
+ // The link count should be non-zero or the file has been
+ // deleted. We allow deleted files to be acquired for close and
+ // also allow them to be acquired recursively in case we
+ // acquire them a second time after marking them deleted (i.e. rename)
+ //
+
+ if (NoDeleteCheck
+
+ ||
+
+ (IrpContext->MajorFunction == IRP_MJ_CLOSE)
+
+ ||
+
+ (IrpContext->MajorFunction == IRP_MJ_CREATE)
+
+ ||
+
+ (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )
+ && (!ARGUMENT_PRESENT( Scb )
+ || !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )))) {
+
+ //
+ // Put Fcb in the exclusive Fcb list for this IrpContext,
+ // excluding the bitmap for the volume, since we do not need
+ // to modify its file record and do not want unnecessary
+ // serialization/deadlock problems.
+ //
+
+ if ((Fcb->Vcb->BitmapScb == NULL) ||
+ (Fcb->Vcb->BitmapScb->Fcb != Fcb)) {
+
+ //
+ // We need to check if this Fcb is already in an
+ // exclusive list. If it is then we want to attach
+ // the current IrpContext to the IrpContext holding
+ // this Fcb.
+ //
+
+ if (Fcb->ExclusiveFcbLinks.Flink == NULL) {
+
+ ASSERT( Fcb->BaseExclusiveCount == 0 );
+
+ InsertTailList( &IrpContext->ExclusiveFcbList,
+ &Fcb->ExclusiveFcbLinks );
+ }
+
+ Fcb->BaseExclusiveCount += 1;
+ }
+
+ return TRUE;
+ }
+
+ //
+ // We need to release the Fcb and remember the status code.
+ //
+
+ ExReleaseResource( Fcb->Resource );
+ Status = STATUS_FILE_DELETED;
+
+ } else if (DontWait) {
+
+ return FALSE;
+ }
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+}
+
+
+VOID
+NtfsAcquireSharedFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb OPTIONAL,
+ IN BOOLEAN NoDeleteCheck
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires shared access to the Fcb.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to acquire
+
+ Scb - This is the Scb for which we are acquiring the Fcb
+
+ NoDeleteCheck - If TRUE then acquire the file even if it has been deleted.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ ASSERT_IRP_CONTEXT(IrpContext);
+ ASSERT_FCB(Fcb);
+
+ Status = STATUS_CANT_WAIT;
+
+ if (ExAcquireResourceShared( Fcb->Resource, BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT))) {
+
+ //
+ // The link count should be non-zero or the file has been
+ // deleted.
+ //
+
+ if (NoDeleteCheck ||
+ (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) &&
+ (!ARGUMENT_PRESENT( Scb ) ||
+ !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )))) {
+
+ //
+ // It's possible that this is a recursive shared aquisition of an
+ // Fcb we own exclusively at the top level. In that case we
+ // need to bump the acquisition count.
+ //
+
+ if (Fcb->ExclusiveFcbLinks.Flink != NULL) {
+
+ Fcb->BaseExclusiveCount += 1;
+ }
+
+ return;
+ }
+
+ //
+ // We need to release the Fcb and remember the status code.
+ //
+
+ ExReleaseResource( Fcb->Resource );
+ Status = STATUS_FILE_DELETED;
+ }
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+}
+
+
+VOID
+NtfsReleaseFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases the specified Fcb resource. If the Fcb is acquired
+ exclusive, and a transaction is still active, then the release is nooped
+ in order to preserve two-phase locking. If there is no longer an active
+ transaction, then we remove the Fcb from the Exclusive Fcb List off the
+ IrpContext, and clear the Flink as a sign. Fcbs are released when the
+ transaction is commited.
+
+Arguments:
+
+ Fcb - Fcb to release
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ //
+ // Check if this resource is owned exclusively and we are at the last
+ // release for this transaction.
+ //
+
+ if (Fcb->ExclusiveFcbLinks.Flink != NULL) {
+
+ if (Fcb->BaseExclusiveCount == 1) {
+
+ //
+ // If there is a transaction then noop this request.
+ //
+
+ if (IrpContext->TransactionId != 0) {
+
+ return;
+ }
+
+ RemoveEntryList( &Fcb->ExclusiveFcbLinks );
+ Fcb->ExclusiveFcbLinks.Flink = NULL;
+
+
+ //
+ // This is a good time to free any Scb snapshots for this Fcb.
+ //
+
+ NtfsFreeSnapshotsForFcb( IrpContext, Fcb );
+ }
+
+ Fcb->BaseExclusiveCount -= 1;
+ }
+
+ ASSERT((Fcb->ExclusiveFcbLinks.Flink == NULL && Fcb->BaseExclusiveCount == 0) ||
+ (Fcb->ExclusiveFcbLinks.Flink != NULL && Fcb->BaseExclusiveCount != 0));
+
+ ExReleaseResource( Fcb->Resource );
+}
+
+
+VOID
+NtfsAcquireExclusiveScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine acquires exclusive access to the Scb.
+
+ This routine will raise if it cannot acquire the resource and wait
+ in the IrpContext is false.
+
+Arguments:
+
+ Scb - Scb to acquire
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ NtfsAcquireExclusiveFcb( IrpContext, Scb->Fcb, Scb, FALSE, FALSE );
+
+ ASSERT( Scb->Fcb->ExclusiveFcbLinks.Flink != NULL
+ || (Scb->Vcb->BitmapScb != NULL
+ && Scb->Vcb->BitmapScb == Scb) );
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED)) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+}
+
+
+VOID
+NtfsAcquireSharedScbForTransaction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to acquire an Scb shared in order to perform updates to
+ the an Scb stream. This is used if the transaction writes to a range of the
+ stream without changing the size or position of the data. The caller must
+ already provide synchronization to the data itself.
+
+ There is no corresponding Scb release. It will be released when the transaction commits.
+ We will acquire the Scb exclusive if it is not yet in the open attribute table.
+
+Arguments:
+
+ Scb - Scb to acquire
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSCB *Position;
+ PSCB *ScbArray;
+ ULONG Count;
+
+ PAGED_CODE();
+
+ //
+ // Make sure we have a free spot in the Scb array in the IrpContext.
+ //
+
+ if (IrpContext->SharedScb == NULL) {
+
+ Position = (PSCB *) &IrpContext->SharedScb;
+ IrpContext->SharedScbSize = 1;
+
+ //
+ // Too bad the first one is not available. If the current size is one then allocate a
+ // new block and copy the existing value to it.
+ //
+
+ } else if (IrpContext->SharedScbSize == 1) {
+
+ ScbArray = NtfsAllocatePool( PagedPool, sizeof( PSCB ) * 4 );
+ RtlZeroMemory( ScbArray, sizeof( PSCB ) * 4 );
+ *ScbArray = IrpContext->SharedScb;
+ IrpContext->SharedScb = ScbArray;
+ IrpContext->SharedScbSize = 4;
+ Position = ScbArray + 1;
+
+ //
+ // Otherwise look through the existing array and look for a free spot. Allocate a larger
+ // array if we need to grow it.
+ //
+
+ } else {
+
+ Position = IrpContext->SharedScb;
+ Count = IrpContext->SharedScbSize;
+
+ do {
+
+ if (*Position == NULL) {
+
+ break;
+ }
+
+ Count -= 1;
+ Position += 1;
+
+ } while (Count != 0);
+
+ //
+ // If we didn't find one then allocate a new structure.
+ //
+
+ if (Count == 0) {
+
+ ScbArray = NtfsAllocatePool( PagedPool, sizeof( PSCB ) * IrpContext->SharedScbSize * 2 );
+ RtlZeroMemory( ScbArray, sizeof( PSCB ) * IrpContext->SharedScbSize * 2 );
+ RtlCopyMemory( ScbArray,
+ IrpContext->SharedScb,
+ sizeof( PSCB ) * IrpContext->SharedScbSize );
+
+ NtfsFreePool( IrpContext->SharedScb );
+ IrpContext->SharedScb = ScbArray;
+ Position = ScbArray + IrpContext->SharedScbSize;
+ IrpContext->SharedScbSize *= 2;
+ }
+ }
+
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+
+ if (Scb->NonpagedScb->OpenAttributeTableIndex == 0) {
+
+ ExReleaseResource( Scb->Header.Resource );
+ ExAcquireResourceExclusive( Scb->Header.Resource, TRUE );
+ }
+
+ *Position = Scb;
+
+ return;
+}
+
+VOID
+NtfsReleaseSharedResources (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ The routine releases all of the resources acquired shared for
+ transaction. The SharedScb structure is freed if necessary and
+ the Irp Context field is cleared.
+
+Arguments:
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+
+ PAGED_CODE();
+
+ //
+ // If only one then free the Scb main resource.
+ //
+
+ if (IrpContext->SharedScbSize == 1) {
+
+#ifdef _CAIRO_
+ if (SafeNodeType(IrpContext->SharedScb) == NTFS_NTC_QUOTA_CONTROL) {
+ NtfsReleaseQuotaControl( IrpContext,
+ (PQUOTA_CONTROL_BLOCK) IrpContext->SharedScb );
+ } else {
+ ExReleaseResource( ((PSCB) IrpContext->SharedScb)->Header.Resource );
+ }
+
+#else
+ ExReleaseResource( ((PSCB) IrpContext->SharedScb)->Header.Resource );
+#endif // _CAIRO_
+
+ //
+ // Otherwise traverse the array and look for Scb's to release.
+ //
+
+ } else {
+
+ PSCB *NextScb;
+ ULONG Count;
+
+ NextScb = IrpContext->SharedScb;
+ Count = IrpContext->SharedScbSize;
+
+ do {
+
+ if (*NextScb != NULL) {
+
+#ifdef _CAIRO_
+ if (SafeNodeType(*NextScb) == NTFS_NTC_QUOTA_CONTROL) {
+
+ NtfsReleaseQuotaControl( IrpContext,
+ (PQUOTA_CONTROL_BLOCK) *NextScb );
+ } else {
+
+ ExReleaseResource( (*NextScb)->Header.Resource );
+ }
+
+#else
+ ExReleaseResource( (*NextScb)->Header.Resource );
+#endif // _CAIRO_
+
+ }
+
+ Count -= 1;
+ NextScb += 1;
+
+ } while (Count != 0);
+
+ NtfsFreePool( IrpContext->SharedScb );
+ }
+
+ IrpContext->SharedScb = NULL;
+ IrpContext->SharedScbSize = 0;
+
+}
+
+BOOLEAN
+NtfsAcquireVolumeForClose (
+ IN PVOID OpaqueVcb,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer prior to its
+ performing closes to the file. This callback is necessary to
+ avoid deadlocks with the Lazy Writer. (Note that normal closes
+ acquire the Vcb, and then call the Cache Manager, who must acquire
+ some of his internal structures. If the Lazy Writer could not call
+ this routine first, and were to issue a write after locking Caching
+ data structures, then a deadlock could occur.)
+
+Arguments:
+
+ Vcb - The Vcb which was specified as a close context parameter for this
+ routine.
+
+ Wait - TRUE if the caller is willing to block.
+
+Return Value:
+
+ FALSE - if Wait was specified as FALSE and blocking would have
+ been required. The Fcb is not acquired.
+
+ TRUE - if the Vcb has been acquired
+
+--*/
+
+{
+ PVCB Vcb = (PVCB)OpaqueVcb;
+
+ ASSERT_VCB(Vcb);
+
+ PAGED_CODE();
+
+ //
+ // Do the code of acquire exclusive Vcb but without the IrpContext
+ //
+
+ if (ExAcquireResourceExclusive( &Vcb->Resource, Wait )) {
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+VOID
+NtfsReleaseVolumeFromClose (
+ IN PVOID OpaqueVcb
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer after its
+ performing closes on the file.
+
+Arguments:
+
+ Vcb - The Vcb which was specified as a close context parameter for this
+ routine.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVCB Vcb = (PVCB)OpaqueVcb;
+
+ ASSERT_VCB(Vcb);
+
+ PAGED_CODE();
+
+ NtfsReleaseVcb( NULL, Vcb );
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAcquireScbForLazyWrite (
+ IN PVOID OpaqueScb,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer prior to its
+ performing lazy writes to the file. This callback is necessary to
+ avoid deadlocks with the Lazy Writer. (Note that normal writes
+ acquire the Fcb, and then call the Cache Manager, who must acquire
+ some of his internal structures. If the Lazy Writer could not call
+ this routine first, and were to issue a write after locking Caching
+ data structures, then a deadlock could occur.)
+
+Arguments:
+
+ OpaqueScb - The Scb which was specified as a context parameter for this
+ routine.
+
+ Wait - TRUE if the caller is willing to block.
+
+Return Value:
+
+ FALSE - if Wait was specified as FALSE and blocking would have
+ been required. The Fcb is not acquired.
+
+ TRUE - if the Scb has been acquired
+
+--*/
+
+{
+ BOOLEAN AcquiredFile = TRUE;
+
+ ULONG CompressedStream = (ULONG)OpaqueScb & 1;
+ PSCB Scb = (PSCB)((ULONG)OpaqueScb & ~1);
+ PFCB Fcb = Scb->Fcb;
+
+ ASSERT_SCB(Scb);
+
+ PAGED_CODE();
+
+ //
+ // Acquire the Scb only for those files that the write will
+ // acquire it for, i.e., not the first set of system files.
+ // Otherwise we can deadlock, for example with someone needing
+ // a new Mft record.
+ //
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) <= MASTER_FILE_TABLE2_NUMBER) {
+
+ //
+ // We need to synchronize the lazy writer with the clean volume
+ // checkpoint. We do this by acquiring and immediately releasing this
+ // Scb. This is to prevent the lazy writer from flushing the log file
+ // when the space may be at a premium.
+ //
+
+ if (ExAcquireResourceShared( Scb->Header.Resource, Wait )) {
+
+ ExReleaseResource( Scb->Header.Resource );
+ AcquiredFile = TRUE;
+ }
+
+ //
+ // Now acquire either the main or paging io resource depending on the
+ // state of the file.
+ //
+
+ } else if (Scb->Header.PagingIoResource != NULL) {
+ AcquiredFile = ExAcquireResourceShared( Scb->Header.PagingIoResource, Wait );
+ } else {
+ AcquiredFile = ExAcquireResourceShared( Scb->Header.Resource, Wait );
+ }
+
+ if (AcquiredFile) {
+
+ //
+ // We assume the Lazy Writer only acquires this Scb once. When he
+ // has acquired it, then he has eliminated anyone who would extend
+ // valid data, since they must take out the resource exclusive.
+ // Therefore, it should be guaranteed that this flag is currently
+ // clear (the ASSERT), and then we will set this flag, to insure
+ // that the Lazy Writer will never try to advance Valid Data, and
+ // also not deadlock by trying to get the Fcb exclusive.
+ //
+
+ ASSERT( Scb->LazyWriteThread[CompressedStream] == NULL );
+
+ Scb->LazyWriteThread[CompressedStream] = PsGetCurrentThread();
+
+ //
+ // Make Cc top level, so that we will not post or retry on errors.
+ // (If it is not NULL, it must be one of our internal calls to this
+ // routine, such as from Restart or Hot Fix.)
+ //
+
+ if (IoGetTopLevelIrp() == NULL) {
+ IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP);
+ }
+ }
+
+ return AcquiredFile;
+}
+
+
+VOID
+NtfsReleaseScbFromLazyWrite (
+ IN PVOID OpaqueScb
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer after its
+ performing lazy writes to the file.
+
+Arguments:
+
+ Scb - The Scb which was specified as a context parameter for this
+ routine.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ULONG CompressedStream = (ULONG)OpaqueScb & 1;
+ PSCB Scb = (PSCB)((ULONG)OpaqueScb & ~1);
+ PFCB Fcb = Scb->Fcb;
+
+ ASSERT_SCB(Scb);
+
+ PAGED_CODE();
+
+ //
+ // Clear the toplevel at this point, if we set it above.
+ //
+
+ if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP) {
+ IoSetTopLevelIrp( NULL );
+ }
+
+ Scb->LazyWriteThread[CompressedStream] = NULL;
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) <= MASTER_FILE_TABLE2_NUMBER) {
+
+ NOTHING;
+
+ } else if (Scb->Header.PagingIoResource != NULL) {
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ } else {
+ ExReleaseResource( Scb->Header.Resource );
+ }
+
+ return;
+}
+
+
+NTSTATUS
+NtfsAcquireFileForModWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER EndingOffset,
+ OUT PERESOURCE *ResourceToRelease,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+{
+ BOOLEAN AcquiredFile;
+
+ PSCB Scb = (PSCB) (FileObject->FsContext);
+ PFCB Fcb = Scb->Fcb;
+
+ ASSERT_SCB(Scb);
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ PAGED_CODE();
+
+ //
+ // Acquire the Scb only for those files that the write will
+ // acquire it for, i.e., not the first set of system files.
+ // Otherwise we can deadlock, for example with someone needing
+ // a new Mft record.
+ //
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) <= MASTER_FILE_TABLE2_NUMBER) {
+
+ //
+ // We need to synchronize the lazy writer with the clean volume
+ // checkpoint. We do this by acquiring and immediately releasing this
+ // Scb. This is to prevent the lazy writer from flushing the log file
+ // when the space may be at a premium.
+ //
+
+ if (AcquiredFile = ExAcquireResourceShared( Scb->Header.Resource, FALSE )) {
+ ExReleaseResource( Scb->Header.Resource );
+ }
+ *ResourceToRelease = NULL;
+
+ //
+ // Now acquire either the main or paging io resource depending on the
+ // state of the file.
+ //
+
+ } else {
+
+ //
+ // Figure out which resource to acquire.
+ //
+
+ if (Scb->Header.PagingIoResource != NULL) {
+ *ResourceToRelease = Scb->Header.PagingIoResource;
+ } else {
+ *ResourceToRelease = Scb->Header.Resource;
+ }
+
+ //
+ // Try to acquire the resource with Wait FALSE
+ //
+
+ AcquiredFile = ExAcquireResourceShared( *ResourceToRelease, FALSE );
+
+ //
+ // If we got the resource, check if he is possibly trying to extend
+ // ValidDataLength. If so that will cause us to go into useless mode
+ // possibly doing actual I/O writing zeros out to the file past actual
+ // valid data in the cache. This is so inefficient that it is better
+ // to tell MM not to do this write.
+ //
+
+ if (AcquiredFile) {
+ if (Scb->CompressionUnit != 0) {
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ if ((EndingOffset->QuadPart > Scb->ValidDataToDisk) &&
+ (EndingOffset->QuadPart < (Scb->Header.FileSize.QuadPart + PAGE_SIZE - 1)) &&
+ !FlagOn(Scb->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE)) {
+
+ ExReleaseResource(*ResourceToRelease);
+ AcquiredFile = FALSE;
+ *ResourceToRelease = NULL;
+ }
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+ } else {
+ *ResourceToRelease = NULL;
+ }
+ }
+
+ return (AcquiredFile ? STATUS_SUCCESS : STATUS_CANT_WAIT);
+}
+
+NTSTATUS
+NtfsAcquireFileForCcFlush (
+ IN PFILE_OBJECT FileObject,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+{
+ PFSRTL_COMMON_FCB_HEADER Header = FileObject->FsContext;
+
+ PAGED_CODE();
+
+ if (Header->PagingIoResource != NULL) {
+ ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+ }
+
+ return STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+}
+
+NTSTATUS
+NtfsReleaseFileForCcFlush (
+ IN PFILE_OBJECT FileObject,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+{
+ PFSRTL_COMMON_FCB_HEADER Header = FileObject->FsContext;
+
+ PAGED_CODE();
+
+ if (Header->PagingIoResource != NULL) {
+ ExReleaseResource( Header->PagingIoResource );
+ }
+
+ return STATUS_SUCCESS;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+}
+
+VOID
+NtfsAcquireForCreateSection (
+ IN PFILE_OBJECT FileObject
+ )
+
+{
+ PSCB Scb = (PSCB)FileObject->FsContext;
+
+ PAGED_CODE();
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ ExAcquireResourceExclusive( Scb->Header.PagingIoResource, TRUE );
+ }
+}
+
+VOID
+NtfsReleaseForCreateSection (
+ IN PFILE_OBJECT FileObject
+ )
+
+{
+ PSCB Scb = (PSCB)FileObject->FsContext;
+
+ PAGED_CODE();
+
+ if (Scb->Header.PagingIoResource != NULL) {
+
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+}
+
+
+BOOLEAN
+NtfsAcquireScbForReadAhead (
+ IN PVOID OpaqueScb,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer prior to its
+ performing read ahead to the file.
+
+Arguments:
+
+ Scb - The Scb which was specified as a context parameter for this
+ routine.
+
+ Wait - TRUE if the caller is willing to block.
+
+Return Value:
+
+ FALSE - if Wait was specified as FALSE and blocking would have
+ been required. The Fcb is not acquired.
+
+ TRUE - if the Scb has been acquired
+
+--*/
+
+{
+ PREAD_AHEAD_THREAD ReadAheadThread;
+ PVOID CurrentThread;
+ KIRQL OldIrql;
+ PSCB Scb = (PSCB)OpaqueScb;
+ PFCB Fcb = Scb->Fcb;
+ BOOLEAN AcquiredFile = FALSE;
+
+ ASSERT_SCB(Scb);
+
+ //
+ // Acquire the Scb only for those files that the read wil
+ // acquire it for, i.e., not the first set of system files.
+ // Otherwise we can deadlock, for example with someone needing
+ // a new Mft record.
+ //
+
+ if ((Scb->Header.PagingIoResource == NULL) ||
+ ExAcquireResourceShared( Scb->Header.PagingIoResource, Wait )) {
+
+ AcquiredFile = TRUE;
+
+ //
+ // Add our thread to the read ahead list.
+ //
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &OldIrql );
+
+ CurrentThread = (PVOID)PsGetCurrentThread();
+ ReadAheadThread = (PREAD_AHEAD_THREAD)NtfsData.ReadAheadThreads.Flink;
+
+ while ((ReadAheadThread != (PREAD_AHEAD_THREAD)&NtfsData.ReadAheadThreads) &&
+ (ReadAheadThread->Thread != NULL)) {
+
+ //
+ // We better not already see ourselves.
+ //
+
+ ASSERT( ReadAheadThread->Thread != CurrentThread );
+
+ ReadAheadThread = (PREAD_AHEAD_THREAD)ReadAheadThread->Links.Flink;
+ }
+
+ //
+ // If we hit the end of the list, then allocate a new one. Note we
+ // should have at most one entry per critical worker thread in the
+ // system.
+ //
+
+ if (ReadAheadThread == (PREAD_AHEAD_THREAD)&NtfsData.ReadAheadThreads) {
+
+ ReadAheadThread = ExAllocatePoolWithTag( NonPagedPool, sizeof(READ_AHEAD_THREAD), 'RftN' );
+
+ //
+ // If we failed to allocate an entry, clean up and raise.
+ //
+
+ if (ReadAheadThread == NULL) {
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, OldIrql );
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) > VOLUME_DASD_NUMBER) {
+
+ if (Scb->Header.PagingIoResource != NULL) {
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+ }
+
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+ InsertTailList( &NtfsData.ReadAheadThreads, &ReadAheadThread->Links );
+ }
+
+ ReadAheadThread->Thread = CurrentThread;
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, OldIrql );
+ }
+
+ return AcquiredFile;
+}
+
+
+VOID
+NtfsReleaseScbFromReadAhead (
+ IN PVOID OpaqueScb
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer after its
+ read ahead.
+
+Arguments:
+
+ Scb - The Scb which was specified as a context parameter for this
+ routine.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PREAD_AHEAD_THREAD ReadAheadThread;
+ PVOID CurrentThread;
+ KIRQL OldIrql;
+ PSCB Scb = (PSCB)OpaqueScb;
+ PFCB Fcb = Scb->Fcb;
+
+ ASSERT_SCB(Scb);
+
+ //
+ // Free our read ahead entry.
+ //
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &OldIrql );
+
+ CurrentThread = (PVOID)PsGetCurrentThread();
+ ReadAheadThread = (PREAD_AHEAD_THREAD)NtfsData.ReadAheadThreads.Flink;
+
+ while ((ReadAheadThread != (PREAD_AHEAD_THREAD)&NtfsData.ReadAheadThreads) &&
+ (ReadAheadThread->Thread != CurrentThread)) {
+
+ ReadAheadThread = (PREAD_AHEAD_THREAD)ReadAheadThread->Links.Flink;
+ }
+
+ ASSERT(ReadAheadThread != (PREAD_AHEAD_THREAD)&NtfsData.ReadAheadThreads);
+
+ ReadAheadThread->Thread = NULL;
+
+ //
+ // Move him to the end of the list so all the allocated entries are at
+ // the front, and we simplify our scans.
+ //
+
+ RemoveEntryList( &ReadAheadThread->Links );
+ InsertTailList( &NtfsData.ReadAheadThreads, &ReadAheadThread->Links );
+
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, OldIrql );
+
+ if (Scb->Header.PagingIoResource != NULL) {
+ ExReleaseResource( Scb->Header.PagingIoResource );
+ }
+
+ return;
+}
+
+
+BOOLEAN
+NtfsAcquireVolumeFileForClose (
+ IN PVOID Null,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ the volume file. It is subsequently called by the Lazy Writer prior to its
+ performing lazy writes to the volume file. This callback may one day be
+ necessary to avoid deadlocks with the Lazy Writer, however, now
+ we do not need to acquire any resource for the volume file,
+ so this routine is simply a noop.
+
+Arguments:
+
+ Null - Not required.
+
+ Wait - TRUE if the caller is willing to block.
+
+Return Value:
+
+ TRUE
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Null );
+ UNREFERENCED_PARAMETER( Wait );
+
+ PAGED_CODE();
+
+ return TRUE;
+}
+
+
+VOID
+NtfsReleaseVolumeFileFromClose (
+ IN PVOID Null
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer after its
+ performing lazy writes to the file.
+
+Arguments:
+
+ Null - Not required.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Null );
+
+ PAGED_CODE();
+
+ return;
+}
+
+
+
+BOOLEAN
+NtfsAcquireVolumeFileForLazyWrite (
+ IN PVOID Vcb,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ the volume file. It is subsequently called by the Lazy Writer prior to its
+ performing lazy writes to the volume file. This callback may one day be
+ necessary to avoid deadlocks with the Lazy Writer, however, now
+ NtfsCommonWrite does not need to acquire any resource for the volume file,
+ so this routine is simply a noop.
+
+Arguments:
+
+ Vcb - The Vcb which was specified as a context parameter for this
+ routine.
+
+ Wait - TRUE if the caller is willing to block.
+
+Return Value:
+
+ TRUE
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Vcb );
+ UNREFERENCED_PARAMETER( Wait );
+
+ PAGED_CODE();
+
+ return TRUE;
+}
+
+
+VOID
+NtfsReleaseVolumeFileFromLazyWrite (
+ IN PVOID Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ The address of this routine is specified when creating a CacheMap for
+ a file. It is subsequently called by the Lazy Writer after its
+ performing lazy writes to the file.
+
+Arguments:
+
+ Vcb - The Vcb which was specified as a context parameter for this
+ routine.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ UNREFERENCED_PARAMETER( Vcb );
+
+ PAGED_CODE();
+
+ return;
+}
diff --git a/private/ntos/cntfs/restrsup.c b/private/ntos/cntfs/restrsup.c
new file mode 100644
index 000000000..238106e1e
--- /dev/null
+++ b/private/ntos/cntfs/restrsup.c
@@ -0,0 +1,4744 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ RestrSup.c
+
+Abstract:
+
+ This module implements the Ntfs routine to perform Restart on an
+ Ntfs volume, i.e., to restore a consistent state to the volume that
+ existed before the last failure.
+
+Author:
+
+ Tom Miller [TomM] 24-Jul-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// **** This is a way to disable a restart to get a volume going "as-is".
+//
+
+BOOLEAN NtfsDisableRestart = FALSE;
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_LOGSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('RFtN')
+
+//
+// The following macro returns the length of the log record header of
+// of log record.
+//
+//
+// ULONG
+// NtfsLogRecordHeaderLength (
+// IN PIRP_CONTEXT IrpContext,
+// IN PNTFS_LOG_RECORD_HEADER LogRecord
+// );
+//
+
+#define NtfsLogRecordHeaderLength( IC, LR ) \
+ (sizeof( NTFS_LOG_RECORD_HEADER ) \
+ + (((PNTFS_LOG_RECORD_HEADER) (LR))->LcnsToFollow > 1 \
+ ? (((PNTFS_LOG_RECORD_HEADER) (LR))->LcnsToFollow - 1) \
+ * sizeof( LCN ) \
+ : 0 ))
+
+//
+//
+// Local procedure prototypes
+//
+
+VOID
+InitializeRestartState (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PRESTART_POINTERS DirtyPageTable,
+ OUT PATTRIBUTE_NAME_ENTRY *AttributeNames,
+ OUT PLSN CheckpointLsn
+ );
+
+VOID
+ReleaseRestartState (
+ IN PVCB Vcb,
+ IN PRESTART_POINTERS DirtyPageTable,
+ IN PATTRIBUTE_NAME_ENTRY AttributeNames,
+ IN BOOLEAN ReleaseVcbTables
+ );
+
+VOID
+AnalysisPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LSN CheckpointLsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable,
+ OUT PLSN RedoLsn
+ );
+
+VOID
+RedoPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LSN RedoLsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable
+ );
+
+VOID
+UndoPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+VOID
+DoAction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN NTFS_LOG_OPERATION Operation,
+ IN PVOID Data,
+ IN ULONG Length,
+ IN ULONG LogRecordLength,
+ IN PLSN RedoLsn OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PLSN *PageLsn
+ );
+
+VOID
+PinMftRecordForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord
+ );
+
+VOID
+OpenAttributeForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ OUT PSCB *Scb
+ );
+
+VOID
+PinAttributeForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN ULONG Length OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PVOID *Buffer,
+ OUT PSCB *Scb
+ );
+
+BOOLEAN
+FindDirtyPage (
+ IN PRESTART_POINTERS DirtyPageTable,
+ IN ULONG TargetAttribute,
+ IN VCN Vcn,
+ OUT PDIRTY_PAGE_ENTRY *DirtyPageEntry
+ );
+
+VOID
+PageUpdateAnalysis (
+ IN PVCB Vcb,
+ IN LSN Lsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord
+ );
+
+VOID
+OpenStreamFromAttributeEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN POPEN_ATTRIBUTE_ENTRY AttributeEntry
+ );
+
+VOID
+OpenAttributesForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PRESTART_POINTERS DirtyPageTable
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, AnalysisPass)
+#pragma alloc_text(PAGE, DoAction)
+#pragma alloc_text(PAGE, FindDirtyPage)
+#pragma alloc_text(PAGE, InitializeRestartState)
+#pragma alloc_text(PAGE, NtfsCloseAttributesFromRestart)
+#pragma alloc_text(PAGE, NtfsRestartVolume)
+#pragma alloc_text(PAGE, OpenAttributeForRestart)
+#pragma alloc_text(PAGE, OpenAttributesForRestart)
+#pragma alloc_text(PAGE, OpenStreamFromAttributeEntry)
+#pragma alloc_text(PAGE, PageUpdateAnalysis)
+#pragma alloc_text(PAGE, PinAttributeForRestart)
+#pragma alloc_text(PAGE, PinMftRecordForRestart)
+#pragma alloc_text(PAGE, RedoPass)
+#pragma alloc_text(PAGE, ReleaseRestartState)
+#pragma alloc_text(PAGE, UndoPass)
+#endif
+
+
+BOOLEAN
+NtfsRestartVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by the mount process after the log file has been
+ started, to restart the volume. Restarting the volume means restoring
+ it to a consistent state as of the last request which was successfully
+ completed and written to the log for this volume.
+
+ The Restart process is a standard recovery from the Log File in three
+ passes: Analysis Pass, Redo Pass and Undo pass. Each one of these passes
+ is implemented in a separate routine in this module.
+
+Arguments:
+
+ Vcb - Vcb for the volume which is to be restarted.
+
+Return Value:
+
+ FALSE - if no updates were applied during restart
+ TRUE - if updates were applied
+
+--*/
+
+{
+ RESTART_POINTERS DirtyPageTable;
+ LSN CheckpointLsn;
+ LSN RedoLsn;
+ PATTRIBUTE_NAME_ENTRY AttributeNames = NULL;
+ BOOLEAN UpdatesApplied = FALSE;
+ BOOLEAN ReleaseVcbTables = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, 0, ("NtfsRestartVolume:\n") );
+ DebugTrace( 0, 0, ("Vcb = %08lx\n", Vcb) );
+
+ RtlZeroMemory( &DirtyPageTable, sizeof(RESTART_POINTERS) );
+
+ //
+ // Use try-finally to insure cleanup on the way out.
+ //
+
+ try {
+
+ //
+ // First we initialize the Open Attribute Table, Transaction Table,
+ // and Dirty Page Table from our last Checkpoint (as found from our
+ // Restart Area) in the log.
+ //
+
+ InitializeRestartState( IrpContext,
+ Vcb,
+ &DirtyPageTable,
+ &AttributeNames,
+ &CheckpointLsn );
+
+ ReleaseVcbTables = TRUE;
+
+ //
+ // If the CheckpointLsn is zero, then this is a freshly formattted
+ // disk and we have no work to do.
+ //
+
+ if (CheckpointLsn.QuadPart == 0) {
+
+ LfsResetUndoTotal( Vcb->LogHandle, 2, QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
+ try_return(NOTHING);
+ }
+
+ //
+ // Start the analysis pass from the Checkpoint Lsn. This pass potentially
+ // updates all of the tables, and returns the RedoLsn, which is the Lsn
+ // at which the Redo Pass is to begin.
+ //
+
+ if (!NtfsDisableRestart) {
+ AnalysisPass( IrpContext, Vcb, CheckpointLsn, &DirtyPageTable, &RedoLsn );
+ }
+
+ //
+ // Only proceed if the the Dirty Page Table or Transaction table are
+ // not empty.
+ //
+
+ if (!IsRestartTableEmpty(&DirtyPageTable)
+
+ ||
+
+ !IsRestartTableEmpty(&Vcb->TransactionTable)) {
+
+ UpdatesApplied = TRUE;
+
+ //
+ // Before starting the Redo Pass, we have to reopen all of the
+ // attributes with dirty pages, and preinitialize their Mcbs with the
+ // mapping information from the Dirty Page Table.
+ //
+
+ OpenAttributesForRestart( IrpContext, Vcb, &DirtyPageTable );
+
+ //
+ // Perform the Redo Pass, to restore all of the dirty pages to the same
+ // contents that they had immediately before the crash.
+ //
+
+ RedoPass( IrpContext, Vcb, RedoLsn, &DirtyPageTable );
+
+ //
+ // Finally, perform the Undo Pass to undo any updates which may exist
+ // for transactions which did not complete.
+ //
+
+ UndoPass( IrpContext, Vcb );
+ }
+
+ //
+ // Now that we know that there is no one to abort, we can initialize our
+ // Undo requirements, to our standard starting point to include the size
+ // of our Restart Area (for a clean checkpoint) + a page, which is the
+ // worst case loss when flushing the volume causes Lfs to flush to Lsn.
+ //
+
+ LfsResetUndoTotal( Vcb->LogHandle, 2, QuadAlign(sizeof(RESTART_AREA)) + (2 * PAGE_SIZE) );
+
+ //
+ // If we got an exception, we can at least clean up on the way out.
+ //
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ DebugUnwind( NtfsRestartVolume );
+
+ //
+ // Free up any resources tied down with the Restart State.
+ //
+
+ ReleaseRestartState( Vcb,
+ &DirtyPageTable,
+ AttributeNames,
+ ReleaseVcbTables );
+ }
+ DebugTrace( -1, 0, ("NtfsRestartVolume -> %02lx\n", UpdatesApplied) );
+
+ return UpdatesApplied;
+}
+
+
+VOID
+NtfsAbortTransaction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PTRANSACTION_ENTRY Transaction OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine aborts a transaction by undoing all of its actions.
+
+ The Undo actions are all performed in the common routine DoAction,
+ which is also used by the Redo Pass.
+
+Arguments:
+
+ Vcb - Vcb for the Volume. NOTE - This argument is not guaranteed to
+ be valid if Transaction is NULL and there is no Transaction ID
+ in the IrpContext.
+
+ Transaction - Pointer to the transaction entry of the transaction to be
+ aborted, or NULL to abort current transaction (if there is
+ one).
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LFS_LOG_CONTEXT LogContext;
+ PNTFS_LOG_RECORD_HEADER LogRecord;
+ ULONG LogRecordLength;
+ PVOID Data;
+ LONG Length;
+ LSN LogRecordLsn;
+ LSN UndoRecordLsn;
+ LFS_RECORD_TYPE RecordType;
+ TRANSACTION_ID TransactionId;
+ LSN UndoNextLsn;
+ LSN PreviousLsn;
+ TRANSACTION_ID SavedTransaction = IrpContext->TransactionId;
+
+ DebugTrace( +1, Dbg, ("NtfsAbortTransaction:\n") );
+
+ //
+ // If a transaction was specified, then we have to set our transaction Id
+ // into the IrpContext (it was saved above), since NtfsWriteLog requires
+ // it.
+ //
+
+ if (ARGUMENT_PRESENT(Transaction)) {
+
+ IrpContext->TransactionId = GetIndexFromRestartEntry( &Vcb->TransactionTable,
+ Transaction );
+
+ UndoNextLsn = Transaction->UndoNextLsn;
+
+ //
+ // Set the flag in the IrpContext so we will always write the commit
+ // record.
+ //
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WROTE_LOG );
+
+ //
+ // Otherwise, we are aborting the current transaction, and we must get the
+ // pointer to its transaction entry.
+ //
+
+ } else {
+
+ if (IrpContext->TransactionId == 0) {
+
+ DebugTrace( -1, Dbg, ("NtfsAbortTransaction->VOID (no transaction)\n") );
+
+ return;
+ }
+
+ //
+ // Synchronize access to the transaction table in case the table
+ // is growing.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ Transaction = GetRestartEntryFromIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ UndoNextLsn = Transaction->UndoNextLsn;
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ //
+ // If we are aborting the current transaction (by default or explicit
+ // request), then restore 0 on return because he will be gone.
+ //
+
+ if (IrpContext->TransactionId == SavedTransaction) {
+
+ SavedTransaction = 0;
+ }
+
+ DebugTrace( 0, Dbg, ("Transaction = %08lx\n", Transaction) );
+
+ //
+ // We only have to do anything if the transaction has something in its
+ // UndoNextLsn field.
+ //
+
+ if (UndoNextLsn.QuadPart != 0) {
+
+ PBCB PageBcb = NULL;
+
+ //
+ // Read the first record to be undone by this transaction.
+ //
+
+ LfsReadLogRecord( Vcb->LogHandle,
+ UndoNextLsn,
+ LfsContextUndoNext,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord );
+
+ //
+ // Now loop to read all of our log records forwards, until we hit
+ // the end of the file, cleaning up at the end.
+ //
+
+ try {
+
+ do {
+
+ PLSN PageLsn;
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ LogRecordLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Undo of Log Record at: %08lx\n", LogRecord) );
+ DebugTrace( 0, Dbg, ("Log Record Lsn = %016I64x\n", LogRecordLsn) );
+
+ //
+ // Log the Undo operation as a CLR, i.e., it has no undo,
+ // and the UndoNext points to the UndoNext of the current
+ // log record.
+ //
+ // Don't do this if the undo is a noop. This is not only
+ // efficient, but in the case of a clean shutdown, there
+ // will be no Scb to pick up from the table below.
+ //
+
+ if (LogRecord->UndoOperation != Noop) {
+
+ ULONG i;
+ PSCB Scb;
+
+ VCN Vcn;
+ LONGLONG Size;
+
+ //
+ // Acquire and release the restart table. We must synchronize
+ // even though our entry can't be removed because the table
+ // could be growing (or shrinking) and the table pointer
+ // could be changing.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable,
+ TRUE );
+
+ Scb = ((POPEN_ATTRIBUTE_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->OpenAttributeTable,
+ LogRecord->TargetAttribute))->Overlay.Scb;
+
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // If we have Lcn's to process and restart is in progress,
+ // then we need to check if this is part of a partial page.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) &&
+ (LogRecord->LcnsToFollow != 0)) {
+
+ LCN TargetLcn;
+ LONGLONG SectorCount, SectorsInRun;
+ BOOLEAN MappingInMcb;
+
+ //
+ // If the mapping isn't already in the table or the
+ // mapping corresponds to a hole in the mapping, we
+ // need to make sure there is no partial page already
+ // in memory.
+ //
+
+ if (!(MappingInMcb = NtfsLookupNtfsMcbEntry( &Scb->Mcb,
+ LogRecord->TargetVcn,
+ &TargetLcn,
+ &SectorCount,
+ NULL,
+ &SectorsInRun,
+ NULL,
+ NULL )) ||
+ (TargetLcn == UNUSED_LCN) ||
+ ((ULONG)SectorCount) < LogRecord->LcnsToFollow) {
+
+ VCN StartingPageVcn;
+ ULONG ClusterOffset;
+ BOOLEAN FlushAndPurge;
+
+ FlushAndPurge = FALSE;
+
+ //
+ // Remember the Vcn at the start of the containing
+ // page.
+ //
+
+ ClusterOffset = ((ULONG)LogRecord->TargetVcn) & (Vcb->ClustersPerPage - 1);
+
+ StartingPageVcn = LogRecord->TargetVcn;
+ ((PLARGE_INTEGER) &StartingPageVcn)->LowPart &= ~(Vcb->ClustersPerPage - 1);
+
+ //
+ // If this mapping was not in the Mcb, then if the
+ // Mcb is empty or the last entry is not in this page
+ // then there is nothing to do.
+ //
+
+ if (!MappingInMcb) {
+
+ LCN LastLcn;
+ VCN LastVcn;
+
+ if ((ClusterOffset != 0) &&
+ NtfsLookupLastNtfsMcbEntry( &Scb->Mcb,
+ &LastVcn,
+ &LastLcn ) &&
+ (LastVcn >= StartingPageVcn)) {
+
+ FlushAndPurge = TRUE;
+ }
+
+ //
+ // If the mapping showed a hole, then the entire
+ // page needs to be a hole. We know that this mapping
+ // can't be the last mapping on the page. We just
+ // need to starting point and the number of clusters
+ // required for the run.
+ //
+
+ } else if (TargetLcn == UNUSED_LCN) {
+
+ if (((ClusterOffset + (ULONG) SectorCount) < Vcb->ClustersPerPage) ||
+ ((ClusterOffset + (ULONG) SectorCount) > (ULONG) SectorsInRun)) {
+
+ FlushAndPurge = TRUE;
+ }
+
+ //
+ // In the rare case where we are extending an existing mapping
+ // let's flush and purge.
+ //
+
+ } else {
+
+ FlushAndPurge = TRUE;
+ }
+
+ if (FlushAndPurge) {
+
+ LONGLONG StartingOffset;
+ IO_STATUS_BLOCK Iosb;
+
+ StartingOffset = LlBytesFromClusters( Vcb, StartingPageVcn );
+ StartingOffset += BytesFromLogBlocks( LogRecord->ClusterBlockOffset );
+
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&StartingOffset,
+ PAGE_SIZE,
+ &Iosb );
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &Iosb.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+
+ if (!CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&StartingOffset,
+ PAGE_SIZE,
+ FALSE )) {
+
+ KdPrint(("NtfsUndoPass: Unable to purge page\n"));
+
+ NtfsRaiseStatus( IrpContext, STATUS_INTERNAL_ERROR, NULL, NULL );
+ }
+ }
+ }
+ }
+
+ //
+ // Loop to add the allocated Vcns. Note that the page
+ // may not have been dirty, which means we may not have
+ // added the run information in the Redo Pass, so we
+ // add it here.
+ //
+
+ for (i = 0, Vcn = LogRecord->TargetVcn, Size = LlBytesFromClusters( Vcb, Vcn + 1 );
+ i < (ULONG)LogRecord->LcnsToFollow;
+ i++, Vcn = Vcn + 1, Size = Size + Vcb->BytesPerCluster ) {
+
+ //
+ // Add this run to the Mcb if the Vcn has not been deleted,
+ // and it is not for the fixed part of the Mft.
+ //
+
+ if ((LogRecord->LcnsForPage[i] != 0)
+
+ &&
+
+ (NtfsSegmentNumber( &Scb->Fcb->FileReference ) > MASTER_FILE_TABLE2_NUMBER ||
+ (Size >= ((VOLUME_DASD_NUMBER + 1) * Vcb->BytesPerFileRecordSegment)) ||
+ (Scb->AttributeTypeCode != $DATA))) {
+
+ //
+ // We test here if we are performing restart. In that case
+ // we need to test if the Lcn's are already in the Mcb.
+ // If not, then we want to flush and purge the page in
+ // case we have zeroed any half pages.
+ //
+
+ while (!NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ Vcn,
+ LogRecord->LcnsForPage[i],
+ (LONGLONG)1,
+ FALSE )) {
+
+ NtfsRemoveNtfsMcbEntry( &Scb->Mcb,
+ Vcn,
+ 1 );
+ }
+ }
+
+ if (Size > Scb->Header.AllocationSize.QuadPart) {
+
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = Size;
+
+ //
+ // Update the Cache Manager if we have a file object.
+ //
+
+ if (Scb->FileObject != NULL) {
+
+ CcSetFileSizes( Scb->FileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+ }
+ }
+ }
+
+ //
+ // Point to the Redo Data and get its length.
+ //
+
+ Data = (PVOID)((PCHAR)LogRecord + LogRecord->UndoOffset);
+ Length = LogRecord->UndoLength;
+
+ //
+ // Once we have logged the Undo operation, it is time to apply
+ // the undo action.
+ //
+
+ DoAction( IrpContext,
+ Vcb,
+ LogRecord,
+ LogRecord->UndoOperation,
+ Data,
+ Length,
+ LogRecordLength,
+ NULL,
+ &PageBcb,
+ &PageLsn );
+
+ UndoRecordLsn =
+ NtfsWriteLog( IrpContext,
+ Scb,
+ PageBcb,
+ LogRecord->UndoOperation,
+ Data,
+ Length,
+ CompensationLogRecord,
+ (PVOID)&UndoNextLsn,
+ LogRecord->RedoLength,
+ LlBytesFromClusters( Vcb, LogRecord->TargetVcn ) + BytesFromLogBlocks( LogRecord->ClusterBlockOffset ),
+ LogRecord->RecordOffset,
+ LogRecord->AttributeOffset,
+ BytesFromClusters( Vcb, LogRecord->LcnsToFollow ));
+
+ if (PageLsn != NULL) {
+ *PageLsn = UndoRecordLsn;
+ }
+
+ NtfsUnpinBcb( &PageBcb );
+ }
+
+ //
+ // Keep reading and looping back until we have read the last record
+ // for this transaction.
+ //
+
+ } while (LfsReadNextLogRecord( Vcb->LogHandle,
+ LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord ));
+
+ //
+ // Now "commit" this guy, just to clean up the transaction table and
+ // make sure we do not try to abort him again.
+ //
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ } finally {
+
+ NtfsUnpinBcb( &PageBcb );
+
+ //
+ // Finally we can kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+
+ //
+ // If we raised out of this routine, we want to be sure to remove
+ // this entry from the transaction table. Otherwise it will
+ // be written to disk with the transaction table.
+ //
+
+ if (AbnormalTermination()
+ && IrpContext->TransactionId != 0) {
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+ }
+
+ //
+ // This is a wierd case where we are aborting a guy who has not written anything.
+ // Either his empty transaction entry was captured during a checkpoint and we are
+ // in restart, or he failed to write his first log record. The important thing
+ // is to at least go ahead and free his transaction entry.
+ //
+
+ } else {
+
+ //
+ // We can now free the transaction table index, because we are
+ // done with it now.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ IrpContext->TransactionId );
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ IrpContext->TransactionId = SavedTransaction;
+
+ DebugTrace( -1, Dbg, ("NtfsAbortTransaction->VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+InitializeRestartState (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ OUT PRESTART_POINTERS DirtyPageTable,
+ OUT PATTRIBUTE_NAME_ENTRY *AttributeNames,
+ OUT PLSN CheckpointLsn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes the volume state for restart, as a first step
+ in performing restart on the volume. Essentially it reads the last
+ Ntfs Restart Area on the volume, and then loads all of the Restart
+ Tables. The Open Attribute Table and Transaction Table are allocated,
+ read in, and linked to the Vcb in the normal way. (The names for the
+ Open Attribute Table are separately read into pool, in order to fix
+ up the Unicode Name Strings in the Attribute Entries, for the duration
+ of Restart, after which they must switch over to use the same name as
+ in the Scb as they do in the running system.) In addition, the Dirty
+ Pages Table is read and returned directly, since it is only during
+ Restart anyway.
+
+ The Checkpoint Lsn is also returned. This is the Lsn at which the
+ Analysis Pass should start.
+
+Arguments:
+
+ Vcb - Vcb for volume which is being restarted.
+
+ DirtyPageTable - Returns the Dirty Page Table read from the log.
+
+ AttributeNames - Returns pointer to AttributeNames buffer, which should
+ be deleted at the end of Restart, if not NULL
+
+ CheckpointLsn - Returns the Checkpoint Lsn to be passed to the
+ Analysis Pass.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ RESTART_AREA RestartArea;
+ LFS_LOG_CONTEXT LogContext;
+ LSN RestartAreaLsn;
+ PNTFS_LOG_RECORD_HEADER LogRecord;
+ ULONG LogHeaderLength;
+ PATTRIBUTE_NAME_ENTRY Name;
+ LFS_RECORD_TYPE RecordType;
+ TRANSACTION_ID TransactionId;
+ LSN UndoNextLsn;
+ LSN PreviousLsn;
+ ULONG RestartAreaLength = sizeof(RESTART_AREA);
+ BOOLEAN CleanupLogContext = FALSE;
+ BOOLEAN ReleaseTransactionTable = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("InitializeRestartState:\n") );
+ DebugTrace( 0, Dbg, ("DirtyPageTable = %08lx\n", DirtyPageTable) );
+
+ *AttributeNames = NULL;
+ *CheckpointLsn = Li0;
+
+ NtfsInitializeRestartTable( sizeof(DIRTY_PAGE_ENTRY)
+ + (Vcb->ClustersPerPage - 1) * sizeof( LCN ),
+ 32,
+ DirtyPageTable );
+
+ //
+ // Read our Restart Area.
+ //
+
+ LfsReadRestartArea( Vcb->LogHandle,
+ &RestartAreaLength,
+ &RestartArea,
+ &RestartAreaLsn );
+
+ DebugTrace( 0, Dbg, ("RestartArea read at %08lx\n", &RestartArea) );
+
+ //
+ // If we get back zero for Restart Area Length, then zero it and procede.
+ // Generally this will only happen on a virgin disk.
+ //
+
+ if ((RestartAreaLength == 0) || NtfsDisableRestart) {
+ RtlZeroMemory( &RestartArea, sizeof(RESTART_AREA) );
+ RestartAreaLength = sizeof(RESTART_AREA);
+ }
+
+ ASSERT((RestartArea.MajorVersion == 0) && (RestartArea.MinorVersion == 0) &&
+ (RestartAreaLength >= sizeof(RESTART_AREA)));
+
+ //
+ // Return the Start Of Checkpoint Lsn.
+ //
+
+ *CheckpointLsn = RestartArea.StartOfCheckpoint;
+
+ try {
+
+ //
+ // Allocate and Read in the Transaction Table.
+ //
+
+ if (RestartArea.TransactionTableLength != 0) {
+
+ //
+ // Workaround for compiler bug.
+ //
+
+ PreviousLsn = RestartArea.TransactionTableLsn;
+
+ LfsReadLogRecord( Vcb->LogHandle,
+ PreviousLsn,
+ LfsContextPrevious,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &RestartAreaLength,
+ (PVOID *) &LogRecord );
+
+ CleanupLogContext = TRUE;
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ RestartAreaLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now check that this is a valid restart table.
+ //
+
+ if (!NtfsCheckRestartTable( Add2Ptr( LogRecord, LogRecord->RedoOffset ),
+ RestartAreaLength - LogRecord->RedoOffset)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Subtract the length of the log page header and increment the
+ // pointer for
+ //
+
+ LogHeaderLength = NtfsLogRecordHeaderLength( IrpContext, LogRecord );
+
+ RestartAreaLength -= LogHeaderLength;
+
+ ASSERT( RestartAreaLength >= RestartArea.TransactionTableLength );
+
+ //
+ // TEMPCODE RESTART_DEBUG There is already a buffer.
+ //
+
+ NtfsFreePool( Vcb->TransactionTable.Table );
+
+ Vcb->TransactionTable.Table =
+ NtfsAllocatePool( NonPagedPool, RestartAreaLength );
+
+ RtlCopyMemory( Vcb->TransactionTable.Table,
+ Add2Ptr( LogRecord, LogHeaderLength ),
+ RestartAreaLength );
+
+ //
+ // Kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+ CleanupLogContext = FALSE;
+ }
+
+ //
+ // TEMPCODE RESTART_DEBUG There is already a structure.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable, TRUE );
+ ReleaseTransactionTable = TRUE;
+
+ //
+ // The next record back should be the Dirty Pages Table.
+ //
+
+ if (RestartArea.DirtyPageTableLength != 0) {
+
+ //
+ // Workaround for compiler bug.
+ //
+
+ PreviousLsn = RestartArea.DirtyPageTableLsn;
+
+ LfsReadLogRecord( Vcb->LogHandle,
+ PreviousLsn,
+ LfsContextPrevious,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &RestartAreaLength,
+ (PVOID *) &LogRecord );
+
+ CleanupLogContext = TRUE;
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ RestartAreaLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now check that this is a valid restart table.
+ //
+
+ if (!NtfsCheckRestartTable( Add2Ptr( LogRecord, LogRecord->RedoOffset ),
+ RestartAreaLength - LogRecord->RedoOffset)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Subtract the length of the log page header and increment the
+ // pointer for
+ //
+
+ LogHeaderLength = NtfsLogRecordHeaderLength( IrpContext, LogRecord );
+
+ RestartAreaLength -= LogHeaderLength;
+
+ ASSERT( RestartAreaLength >= RestartArea.DirtyPageTableLength );
+
+ DirtyPageTable->Table =
+ NtfsAllocatePool( NonPagedPool, RestartAreaLength );
+
+ RtlCopyMemory( DirtyPageTable->Table,
+ Add2Ptr( LogRecord, LogHeaderLength ),
+ RestartAreaLength );
+
+ //
+ // Kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+ CleanupLogContext = FALSE;
+
+ //
+ // If the cluster size is larger than the page size we may have
+ // multiple entries for the same Vcn. Go through the table
+ // and remove the duplicates, remembering the oldest Lsn values.
+ //
+
+ if (Vcb->BytesPerCluster > PAGE_SIZE) {
+
+ PDIRTY_PAGE_ENTRY CurrentEntry;
+ PDIRTY_PAGE_ENTRY NextEntry;
+
+ CurrentEntry = NtfsGetFirstRestartTable( DirtyPageTable );
+
+ while (CurrentEntry != NULL) {
+
+ NextEntry = CurrentEntry;
+
+ while ((NextEntry = NtfsGetNextRestartTable( DirtyPageTable, NextEntry )) != NULL) {
+
+ if ((NextEntry->TargetAttribute == CurrentEntry->TargetAttribute) &&
+ (NextEntry->Vcn == CurrentEntry->Vcn)) {
+
+ if (NextEntry->OldestLsn.QuadPart < CurrentEntry->OldestLsn.QuadPart) {
+
+ CurrentEntry->OldestLsn.QuadPart = NextEntry->OldestLsn.QuadPart;
+ }
+
+ NtfsFreeRestartTableIndex( DirtyPageTable,
+ GetIndexFromRestartEntry( DirtyPageTable,
+ NextEntry ));
+ }
+ }
+
+ CurrentEntry = NtfsGetNextRestartTable( DirtyPageTable, CurrentEntry );
+ }
+ }
+
+ //
+ // If there was no dirty page table, then just initialize an empty one.
+ //
+
+ }
+
+ NtfsAcquireExclusiveRestartTable( DirtyPageTable, TRUE );
+
+
+ //
+ // The next record back should be the Attribute Names.
+ //
+
+ if (RestartArea.AttributeNamesLength != 0) {
+
+ //
+ // Workaround for compiler bug.
+ //
+
+ PreviousLsn = RestartArea.AttributeNamesLsn;
+
+ LfsReadLogRecord( Vcb->LogHandle,
+ PreviousLsn,
+ LfsContextPrevious,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &RestartAreaLength,
+ (PVOID *) &LogRecord );
+
+ CleanupLogContext = TRUE;
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ RestartAreaLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Subtract the length of the log page header and increment the
+ // pointer for
+ //
+
+ LogHeaderLength = NtfsLogRecordHeaderLength( IrpContext, LogRecord );
+
+ RestartAreaLength -= LogHeaderLength;
+
+ ASSERT( RestartAreaLength >= RestartArea.AttributeNamesLength );
+
+ *AttributeNames =
+ NtfsAllocatePool( NonPagedPool, RestartAreaLength );
+
+ RtlCopyMemory( *AttributeNames,
+ Add2Ptr( LogRecord, LogHeaderLength ),
+ RestartAreaLength );
+
+ //
+ // Kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+ CleanupLogContext = FALSE;
+ }
+
+ //
+ // The next record back should be the Attribute Table.
+ //
+
+ if (RestartArea.OpenAttributeTableLength != 0) {
+
+ POPEN_ATTRIBUTE_ENTRY OpenEntry;
+
+ //
+ // Workaround for compiler bug.
+ //
+
+ PreviousLsn = RestartArea.OpenAttributeTableLsn;
+
+ LfsReadLogRecord( Vcb->LogHandle,
+ PreviousLsn,
+ LfsContextPrevious,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &RestartAreaLength,
+ (PVOID *) &LogRecord );
+
+ CleanupLogContext = TRUE;
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ RestartAreaLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Now check that this is a valid restart table.
+ //
+
+ if (!NtfsCheckRestartTable( Add2Ptr( LogRecord, LogRecord->RedoOffset ),
+ RestartAreaLength - LogRecord->RedoOffset)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Subtract the length of the log page header and increment the
+ // pointer for
+ //
+
+ LogHeaderLength = NtfsLogRecordHeaderLength( IrpContext, LogRecord );
+
+ RestartAreaLength -= LogHeaderLength;
+
+ ASSERT( RestartAreaLength >= RestartArea.OpenAttributeTableLength );
+
+ //
+ // TEMPCODE RESTART_DEBUG There is already a buffer.
+ //
+
+ NtfsFreePool( Vcb->OpenAttributeTable.Table );
+
+ Vcb->OpenAttributeTable.Table =
+ NtfsAllocatePool( NonPagedPool, RestartAreaLength );
+
+ RtlCopyMemory( Vcb->OpenAttributeTable.Table,
+ Add2Ptr( LogRecord, LogHeaderLength ),
+ RestartAreaLength );
+
+ //
+ // First loop to clear all of the Scb pointers in case we
+ // have a premature abort and want to clean up.
+ //
+
+ OpenEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (OpenEntry != NULL) {
+
+ OpenEntry->Overlay.Scb = NULL;
+ OpenEntry->AttributeNamePresent = FALSE;
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ OpenEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ OpenEntry );
+ }
+
+ //
+ // Kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+ CleanupLogContext = FALSE;
+ }
+
+ //
+ // TEMPCODE RESTART_DEBUG There is already a structure.
+ //
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->OpenAttributeTable, TRUE );
+
+ //
+ // The only other thing we have to do before returning is patch up the
+ // Unicode String's in the Attribute Table to point to their respective
+ // attribute names.
+ //
+
+ if (RestartArea.AttributeNamesLength != 0) {
+
+ Name = *AttributeNames;
+
+ while (Name->Index != 0) {
+
+ POPEN_ATTRIBUTE_ENTRY Entry;
+
+ Entry = (POPEN_ATTRIBUTE_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->OpenAttributeTable, Name->Index );
+
+ Entry->AttributeName.MaximumLength =
+ Entry->AttributeName.Length = Name->NameLength;
+ Entry->AttributeName.Buffer = (PWSTR)&Name->Name[0];
+
+ Name = (PATTRIBUTE_NAME_ENTRY)((PCHAR)Name +
+ sizeof(ATTRIBUTE_NAME_ENTRY) +
+ (ULONG)Name->NameLength );
+ }
+ }
+
+ ReleaseTransactionTable = FALSE;
+
+ } finally {
+
+ //
+ // Release the transaction table if we acquired it and then
+ // raised during this routine.
+ //
+
+ if (ReleaseTransactionTable) {
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ if (CleanupLogContext) {
+
+ //
+ // Kill the log handle.
+ //
+
+ LfsTerminateLogQuery( Vcb->LogHandle, LogContext );
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("AttributeNames > %08lx\n", *AttributeNames) );
+ DebugTrace( 0, Dbg, ("CheckpointLsn > %016I64x\n", *CheckpointLsn) );
+ DebugTrace( -1, Dbg, ("NtfsInitializeRestartState -> VOID\n") );
+}
+
+
+VOID
+ReleaseRestartState (
+ IN PVCB Vcb,
+ IN PRESTART_POINTERS DirtyPageTable,
+ IN PATTRIBUTE_NAME_ENTRY AttributeNames,
+ IN BOOLEAN ReleaseVcbTables
+ )
+
+/*++
+
+Routine Description:
+
+ This routine releases all of the restart state.
+
+Arguments:
+
+ Vcb - Vcb for the volume being restarted.
+
+ DirtyPageTable - pointer to the dirty page table, if one was allocated.
+
+ AttributeNames - pointer to the attribute names buffer, if one was allocated.
+
+ ReleaseVcbTables - TRUE if we are to release the restart tables in the Vcb,
+ FALSE otherwise.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ //
+ // If the caller successfully had a successful restart, then we must release
+ // the transaction and open attribute tables.
+ //
+
+ if (ReleaseVcbTables) {
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+ }
+
+ //
+ // Free the dirty page table, if there is one.
+ //
+
+ if (DirtyPageTable != NULL) {
+ NtfsFreeRestartTable( DirtyPageTable );
+ }
+
+ //
+ // Free the temporary attribute names buffer, if there is one.
+ //
+
+ if (AttributeNames != NULL) {
+ NtfsFreePool( AttributeNames );
+ }
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+AnalysisPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LSN CheckpointLsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable,
+ OUT PLSN RedoLsn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the analysis phase of Restart. Starting at
+ the CheckpointLsn, it reads all records written by Ntfs, and takes
+ the following actions:
+
+ For all log records which create or update attributes, a check is
+ made to see if the affected page(s) are already in the Dirty Pages
+ Table. For any page that is not, it is added, and the OldestLsn
+ field is set to the Lsn of the log record.
+
+ The transaction table is updated on transaction state changes,
+ and also to maintain the PreviousLsn and UndoNextLsn fields.
+
+ If any attributes are truncated or deleted (including delete of
+ an entire file), then any corrsponding pages in the Dirty Page
+ Table are deleted.
+
+ When attributes or entire files are deleted, the respective entries
+ are deleted from the Open Attribute Table.
+
+ For Hot Fix records, the Dirty Pages Table is scanned for the HotFixed
+ Vcn, and if one is found, the Lcn field in the table is updated to
+ the new location.
+
+ When the end of the log file is encountered, the Dirty Page Table is
+ scanned for the Oldest of the OldestLsn fields. This value is returned
+ as the RedoLsn, i.e., the point at which the Redo Pass must occur.
+
+Arguments:
+
+ Vcb - Volume which is being restarted.
+
+ CheckpointLsn - Lsn at which the Analysis Pass is to begin.
+
+ DirtyPageTable - Pointer to a pointer to the Dirty Page Table, as
+ found from the last Restart Area.
+
+ RedoLsn - Returns point at which the Redo Pass should begin.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LFS_LOG_CONTEXT LogContext;
+ PNTFS_LOG_RECORD_HEADER LogRecord;
+ ULONG LogRecordLength;
+ LSN LogRecordLsn = CheckpointLsn;
+ PRESTART_POINTERS TransactionTable = &Vcb->TransactionTable;
+ PRESTART_POINTERS OpenAttributeTable = &Vcb->OpenAttributeTable;
+ LFS_LOG_HANDLE LogHandle = Vcb->LogHandle;
+ LFS_RECORD_TYPE RecordType;
+ TRANSACTION_ID TransactionId;
+ PTRANSACTION_ENTRY Transaction;
+ LSN UndoNextLsn;
+ LSN PreviousLsn;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("AnalysisPass:\n") );
+ DebugTrace( 0, Dbg, ("CheckpointLsn = %016I64x\n", CheckpointLsn) );
+
+ *RedoLsn = Li0; //**** LfsZeroLsn;
+
+ //
+ // Read the first Lsn.
+ //
+
+ LfsReadLogRecord( LogHandle,
+ CheckpointLsn,
+ LfsContextForward,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord );
+
+ //
+ // Use a try-finally to cleanup the query context.
+ //
+
+ try {
+
+ //
+ // Since the checkpoint remembers the previous Lsn, not the one he wants to
+ // start at, we must always skip the first record.
+ //
+ // Loop to read all subsequent records to the end of the log file.
+ //
+
+ while ( LfsReadNextLogRecord( LogHandle,
+ LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord )) {
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ LogRecordLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // The first Lsn after the previous Lsn remembered in the checkpoint is
+ // the first candidate for the RedoLsn.
+ //
+
+ if (RedoLsn->QuadPart == 0) {
+ *RedoLsn = LogRecordLsn;
+ }
+
+ if (RecordType != LfsClientRecord) {
+ continue;
+ }
+
+ DebugTrace( 0, Dbg, ("Analysis of LogRecord at: %08lx\n", LogRecord) );
+ DebugTrace( 0, Dbg, ("Log Record Lsn = %016I64x\n", LogRecordLsn) );
+ DebugTrace( 0, Dbg, ("LogRecord->RedoOperation = %08lx\n", LogRecord->RedoOperation) );
+ DebugTrace( 0, Dbg, ("TransactionId = %08lx\n", TransactionId) );
+
+ //
+ // Now update the Transaction Table for this transaction. If there is no
+ // entry present or it is unallocated we allocate the entry.
+ //
+
+ Transaction = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ if (!IsRestartIndexWithinTable( &Vcb->TransactionTable, TransactionId ) ||
+ !IsRestartTableEntryAllocated( Transaction )) {
+
+ Transaction = (PTRANSACTION_ENTRY) NtfsAllocateRestartTableFromIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ Transaction->TransactionState = TransactionActive;
+ Transaction->FirstLsn = LogRecordLsn;
+ }
+
+ Transaction->PreviousLsn =
+ Transaction->UndoNextLsn = LogRecordLsn;
+
+ //
+ // If this is a compensation log record (CLR), then change the UndoNextLsn to
+ // be the UndoNextLsn of this record.
+ //
+
+ if (LogRecord->UndoOperation == CompensationLogRecord) {
+
+ Transaction->UndoNextLsn = UndoNextLsn;
+ }
+
+ //
+ // Dispatch to handle log record depending on type.
+ //
+
+ switch (LogRecord->RedoOperation) {
+
+ //
+ // The following cases are performing various types of updates
+ // and need to make the appropriate updates to the Transaction
+ // and Dirty Page Tables.
+ //
+
+ case InitializeFileRecordSegment:
+ case DeallocateFileRecordSegment:
+ case WriteEndOfFileRecordSegment:
+ case CreateAttribute:
+ case DeleteAttribute:
+ case UpdateResidentValue:
+ case UpdateNonresidentValue:
+ case UpdateMappingPairs:
+ case SetNewAttributeSizes:
+ case AddIndexEntryRoot:
+ case DeleteIndexEntryRoot:
+ case AddIndexEntryAllocation:
+ case DeleteIndexEntryAllocation:
+ case WriteEndOfIndexBuffer:
+ case SetIndexEntryVcnRoot:
+ case SetIndexEntryVcnAllocation:
+ case UpdateFileNameRoot:
+ case UpdateFileNameAllocation:
+ case SetBitsInNonresidentBitMap:
+ case ClearBitsInNonresidentBitMap:
+ case UpdateRecordDataRoot:
+ case UpdateRecordDataAllocation:
+
+ PageUpdateAnalysis( Vcb,
+ LogRecordLsn,
+ DirtyPageTable,
+ LogRecord );
+
+ break;
+
+ //
+ // This case is deleting clusters from a nonresident attribute,
+ // thus it deletes a range of pages from the Dirty Page Table.
+ // This log record is written each time a nonresident attribute
+ // is truncated, whether explicitly or as part of deletion.
+ //
+ // Processing one of these records is pretty compute-intensive
+ // (three nested loops, where a couple of them can be large),
+ // but this is the code that prevents us from dropping, for example,
+ // index updates into the middle of user files, if the index stream
+ // is truncated and the sectors are reallocated to a user file
+ // and we crash after the user data has been written.
+ //
+ // I.e., note the following sequence:
+ //
+ // <checkpoint>
+ // <Index update>
+ // <Index page deleted>
+ // <Same cluster(s) reallocated to user file>
+ // <User data written>
+ //
+ // CRASH!
+ //
+ // Since the user data was not logged (else there would be no problem),
+ // It could get overwritten while applying the index update after a
+ // crash - Pisses off the user as well as the security dudes!
+ //
+
+ case DeleteDirtyClusters:
+
+ {
+ PDIRTY_PAGE_ENTRY DirtyPage;
+ PLCN_RANGE LcnRange;
+ ULONG i, j;
+ LCN FirstLcn, LastLcn;
+ ULONG RangeCount = LogRecord->RedoLength / sizeof(LCN_RANGE);
+
+ //
+ // Point to the Lcn range array.
+ //
+
+ LcnRange = Add2Ptr(LogRecord, LogRecord->RedoOffset);
+
+ //
+ // Loop through all of the Lcn ranges in this log record.
+ //
+
+ for (i = 0; i < RangeCount; i++) {
+
+ FirstLcn = LcnRange[i].StartLcn;
+ LastLcn = FirstLcn + (LcnRange[i].Count - 1);
+
+ DebugTrace( 0, Dbg, ("Deleting from FirstLcn = %016I64x\n",
+ FirstLcn));
+ DebugTrace( 0, Dbg, ("Deleting to LastLcn = %016I64x\n",
+ LastLcn ));
+
+ //
+ // Point to first Dirty Page Entry.
+ //
+
+ DirtyPage = NtfsGetFirstRestartTable( DirtyPageTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (DirtyPage != NULL) {
+
+ //
+ // Loop through all of the Lcns for this dirty page.
+ //
+
+ for (j = 0; j < (ULONG)DirtyPage->LcnsToFollow; j++) {
+
+ if ((DirtyPage->LcnsForPage[j] >= FirstLcn) &&
+ (DirtyPage->LcnsForPage[j] <= LastLcn)) {
+
+ DirtyPage->LcnsForPage[j] = 0;
+ }
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ DirtyPage = NtfsGetNextRestartTable( DirtyPageTable,
+ DirtyPage );
+ }
+ }
+ }
+
+ break;
+
+ //
+ // When a record is encountered for a nonresident attribute that
+ // was opened, we have to add an entry to the Open Attribute Table.
+ //
+
+ case OpenNonresidentAttribute:
+
+ {
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+ ULONG NameSize;
+
+ //
+ // If the table is not currently big enough, then we must
+ // expand it.
+ //
+
+ if (!IsRestartIndexWithinTable( &Vcb->OpenAttributeTable,
+ (ULONG)LogRecord->TargetAttribute )) {
+
+ ULONG NeededEntries;
+
+ //
+ // Compute how big the table needs to be. Add 10 extra entries
+ // for some cushion.
+ //
+
+ NeededEntries = (LogRecord->TargetAttribute / Vcb->OpenAttributeTable.Table->EntrySize);
+ NeededEntries = (NeededEntries + 10 - Vcb->OpenAttributeTable.Table->NumberEntries);
+
+ NtfsExtendRestartTable( &Vcb->OpenAttributeTable,
+ NeededEntries,
+ MAXULONG );
+ }
+
+ ASSERT( IsRestartIndexWithinTable( &Vcb->OpenAttributeTable,
+ (ULONG)LogRecord->TargetAttribute ));
+
+ //
+ // Calculate size of Attribute Name Entry, if there is one.
+ //
+
+ NameSize = LogRecord->UndoLength;
+
+ //
+ // Point to the entry being opened.
+ //
+
+ AttributeEntry = (POPEN_ATTRIBUTE_ENTRY)NtfsAllocateRestartTableFromIndex(
+ &Vcb->OpenAttributeTable,
+ LogRecord->TargetAttribute );
+
+ //
+ // The attribute entry better either not be allocated or it must
+ // be for the same file.
+ //
+
+ // **** May eliminate this test.
+ //
+ // ASSERT( !IsRestartTableEntryAllocated(AttributeEntry) ||
+ // xxEql(AttributeEntry->FileReference,
+ // ((POPEN_ATTRIBUTE_ENTRY)Add2Ptr(LogRecord,
+ // LogRecord->RedoOffset))->FileReference));
+
+ //
+ // Initialize this entry from the log record.
+ //
+
+ RtlCopyMemory( AttributeEntry,
+ (PCHAR)LogRecord + LogRecord->RedoOffset,
+ sizeof(OPEN_ATTRIBUTE_ENTRY) );
+
+ ASSERT( IsRestartTableEntryAllocated(AttributeEntry) );
+
+ //
+ // If there is a name at the end, then allocate space to
+ // copy it into, and do the copy. We also set the buffer
+ // pointer in the string descriptor, although note that the
+ // lengths must be correct.
+ //
+
+ if (NameSize != 0) {
+
+ AttributeEntry->Overlay.AttributeName =
+ NtfsAllocatePool( NonPagedPool, NameSize );
+ RtlCopyMemory( AttributeEntry->Overlay.AttributeName,
+ Add2Ptr(LogRecord, LogRecord->UndoOffset),
+ NameSize );
+ AttributeEntry->AttributeName.Buffer =
+ AttributeEntry->Overlay.AttributeName;
+
+ AttributeEntry->AttributeNamePresent = TRUE;
+
+ //
+ // Otherwise, show there is no name.
+ //
+
+ } else {
+ AttributeEntry->Overlay.AttributeName = NULL;
+ AttributeEntry->AttributeName.Buffer = NULL;
+ AttributeEntry->AttributeNamePresent = FALSE;
+ }
+ }
+
+ break;
+
+ //
+ // For HotFix records, we need to update the Lcn in the Dirty Page
+ // Table.
+ //
+
+ case HotFix:
+
+ {
+ PDIRTY_PAGE_ENTRY DirtyPage;
+
+ //
+ // First see if the Vcn is currently in the Dirty Page
+ // Table. If not, there is nothing to do.
+ //
+
+ if (FindDirtyPage( DirtyPageTable,
+ LogRecord->TargetAttribute,
+ LogRecord->TargetVcn,
+ &DirtyPage )) {
+
+ //
+ // Index to the Lcn in question in the Dirty Page Entry
+ // and rewrite it with the Hot Fixed Lcn from the log
+ // record. Note that it is ok to just use the LowPart
+ // of the Vcns to calculate the array offset, because
+ // any multiple of 2**32 is guaranteed to be on a page
+ // boundary!
+ //
+
+ if (DirtyPage->LcnsForPage[((ULONG)LogRecord->TargetVcn) - ((ULONG)DirtyPage->Vcn)] != 0) {
+
+ DirtyPage->LcnsForPage[((ULONG)LogRecord->TargetVcn) - ((ULONG)DirtyPage->Vcn)] = LogRecord->LcnsForPage[0];
+ }
+ }
+ }
+
+ break;
+
+ //
+ // For end top level action, we will just update the transaction
+ // table to skip the top level action on undo.
+ //
+
+ case EndTopLevelAction:
+
+ {
+ PTRANSACTION_ENTRY Transaction;
+
+ //
+ // Now update the Transaction Table for this transaction.
+ //
+
+ Transaction = (PTRANSACTION_ENTRY)GetRestartEntryFromIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ Transaction->PreviousLsn = LogRecordLsn;
+ Transaction->UndoNextLsn = UndoNextLsn;
+
+ }
+
+ break;
+
+ //
+ // For Prepare Transaction, we just change the state of our entry.
+ //
+
+ case PrepareTransaction:
+
+ {
+ PTRANSACTION_ENTRY CurrentEntry;
+
+ CurrentEntry = GetRestartEntryFromIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ ASSERT( !IsRestartTableEntryAllocated( CurrentEntry ));
+
+ CurrentEntry->TransactionState = TransactionPrepared;
+ }
+
+ break;
+
+ //
+ // For Commit Transaction, we just change the state of our entry.
+ //
+
+ case CommitTransaction:
+
+ {
+ PTRANSACTION_ENTRY CurrentEntry;
+
+ CurrentEntry = GetRestartEntryFromIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ ASSERT( !IsRestartTableEntryAllocated( CurrentEntry ));
+
+ CurrentEntry->TransactionState = TransactionCommitted;
+ }
+
+ break;
+
+ //
+ // For forget, we can delete our transaction entry, since the transaction
+ // will not have to be aborted.
+ //
+
+ case ForgetTransaction:
+
+ {
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ TransactionId );
+ }
+
+ break;
+
+ //
+ // The following cases require no action in the Analysis Pass.
+ //
+
+ case Noop:
+ case OpenAttributeTableDump:
+ case AttributeNamesDump:
+ case DirtyPageTableDump:
+ case TransactionTableDump:
+
+ break;
+
+ //
+ // All codes will be explicitly handled. If we see a code we
+ // do not expect, then we are in trouble.
+ //
+
+ default:
+
+ DebugTrace( 0, Dbg, ("Unexpected Log Record Type: %04lx\n", LogRecord->RedoOperation) );
+ DebugTrace( 0, Dbg, ("Record address: %08lx\n", LogRecord) );
+ DebugTrace( 0, Dbg, ("Record length: %08lx\n", LogRecordLength) );
+
+ ASSERTMSG( "Unknown Action!\n", FALSE );
+
+ break;
+ }
+ }
+
+ } finally {
+
+ //
+ // Finally we can kill the log handle.
+ //
+
+ LfsTerminateLogQuery( LogHandle, LogContext );
+ }
+
+ //
+ // Now we just have to scan the Dirty Page Table and Transaction Table
+ // for the lowest Lsn, and return it as the Redo Lsn.
+ //
+
+ {
+ PDIRTY_PAGE_ENTRY DirtyPage;
+
+ //
+ // Point to first Dirty Page Entry.
+ //
+
+ DirtyPage = NtfsGetFirstRestartTable( DirtyPageTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (DirtyPage != NULL) {
+
+ //
+ // Update the Redo Lsn if this page has an older one.
+ //
+
+ if ((DirtyPage->OldestLsn.QuadPart != 0) &&
+ (DirtyPage->OldestLsn.QuadPart < RedoLsn->QuadPart)) {
+
+ *RedoLsn = DirtyPage->OldestLsn;
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ DirtyPage = NtfsGetNextRestartTable( DirtyPageTable,
+ DirtyPage );
+ }
+ }
+
+ {
+ PTRANSACTION_ENTRY Transaction;
+
+ //
+ // Point to first Transaction Entry.
+ //
+
+ Transaction = NtfsGetFirstRestartTable( &Vcb->TransactionTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (Transaction != NULL) {
+
+ //
+ // Update the Redo Lsn if this transaction has an older one.
+ //
+
+ if ((Transaction->FirstLsn.QuadPart != 0) &&
+ (Transaction->FirstLsn.QuadPart < RedoLsn->QuadPart)) {
+
+ *RedoLsn = Transaction->FirstLsn;
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ Transaction = NtfsGetNextRestartTable( &Vcb->TransactionTable,
+ Transaction );
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("RedoLsn > %016I64x\n", *RedoLsn) );
+ DebugTrace( 0, Dbg, ("AnalysisPass -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+RedoPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN LSN RedoLsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the Redo Pass of Restart. Beginning at the
+ Redo Lsn established during the Analysis Pass, the redo operations
+ of all log records are applied, until the end of file is encountered.
+
+ Updates are only applied to clusters in the dirty page table. If a
+ cluster was deleted, then its entry will have been deleted during the
+ Analysis Pass.
+
+ The Redo actions are all performed in the common routine DoAction,
+ which is also used by the Undo Pass.
+
+Arguments:
+
+ Vcb - Volume which is being restarted.
+
+ RedoLsn - Lsn at which the Redo Pass is to begin.
+
+ DirtyPageTable - Pointer to the Dirty Page Table, as reconstructed
+ from the Analysis Pass.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ LFS_LOG_CONTEXT LogContext;
+ PNTFS_LOG_RECORD_HEADER LogRecord;
+ ULONG LogRecordLength;
+ PVOID Data;
+ ULONG Length;
+ LFS_RECORD_TYPE RecordType;
+ TRANSACTION_ID TransactionId;
+ LSN UndoNextLsn;
+ LSN PreviousLsn;
+ ULONG i, SavedLength;
+
+ LSN LogRecordLsn = RedoLsn;
+ LFS_LOG_HANDLE LogHandle = Vcb->LogHandle;
+ PBCB PageBcb = NULL;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("RedoPass:\n") );
+ DebugTrace( 0, Dbg, ("RedoLsn = %016I64x\n", RedoLsn) );
+ DebugTrace( 0, Dbg, ("DirtyPageTable = %08lx\n", DirtyPageTable) );
+
+ //
+ // If the dirty page table is empty, then we can skip the entire Redo Pass.
+ //
+
+ if (IsRestartTableEmpty( DirtyPageTable )) {
+ return;
+ }
+
+ //
+ // Read the record at the Redo Lsn, before falling into common code
+ // to handle each record.
+ //
+
+ LfsReadLogRecord( LogHandle,
+ RedoLsn,
+ LfsContextForward,
+ &LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord );
+
+ //
+ // Now loop to read all of our log records forwards, until we hit
+ // the end of the file, cleaning up at the end.
+ //
+
+ try {
+
+ do {
+
+ PDIRTY_PAGE_ENTRY DirtyPage;
+ PLSN PageLsn;
+ BOOLEAN FoundPage;
+
+ if (RecordType != LfsClientRecord) {
+ continue;
+ }
+
+ //
+ // Check that the log record is valid.
+ //
+
+ if (!NtfsCheckLogRecord( IrpContext,
+ LogRecord,
+ LogRecordLength,
+ TransactionId )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Redo of LogRecord at: %08lx\n", LogRecord) );
+ DebugTrace( 0, Dbg, ("Log Record Lsn = %016I64x\n", LogRecordLsn) );
+
+ //
+ // Ignore log records that do not update pages.
+ //
+
+ if (LogRecord->LcnsToFollow == 0) {
+
+ DebugTrace( 0, Dbg, ("Skipping log record (no update)\n") );
+
+ continue;
+ }
+
+ //
+ // Consult Dirty Page Table to see if we have to apply this update.
+ // If the page is not there, or if the Lsn of this Log Record is
+ // older than the Lsn in the Dirty Page Table, then we do not have
+ // to apply the update.
+ //
+
+ FoundPage = FindDirtyPage( DirtyPageTable,
+ LogRecord->TargetAttribute,
+ LogRecord->TargetVcn,
+ &DirtyPage );
+
+ if (!FoundPage
+
+ ||
+
+ (LogRecordLsn.QuadPart < DirtyPage->OldestLsn.QuadPart)) {
+
+ DebugDoit(
+
+ DebugTrace( 0, Dbg, ("Skipping log record operation %08lx\n",
+ LogRecord->RedoOperation ));
+
+ if (!FoundPage) {
+ DebugTrace( 0, Dbg, ("Page not in dirty page table\n") );
+ } else {
+ DebugTrace( 0, Dbg, ("Page Lsn more current: %016I64x\n",
+ DirtyPage->OldestLsn) );
+ }
+ );
+
+ continue;
+
+ //
+ // We also skip the update if the entry was never put in the Mcb for
+ // the file.
+
+ } else {
+
+ POPEN_ATTRIBUTE_ENTRY ThisEntry;
+ PSCB TargetScb;
+ LCN TargetLcn;
+
+ //
+ // Check that the entry is within the table and is allocated.
+ //
+
+ if (!IsRestartIndexWithinTable( &Vcb->OpenAttributeTable,
+ LogRecord->TargetAttribute )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ ThisEntry = (POPEN_ATTRIBUTE_ENTRY) GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
+ LogRecord->TargetAttribute );
+
+ if (!IsRestartTableEntryAllocated( ThisEntry )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ TargetScb = ThisEntry->Overlay.Scb;
+
+ //
+ // If there is no Scb it means that we don't have an entry in Open
+ // Attribute Table for this attribute.
+ //
+
+ if (TargetScb == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ if (!NtfsLookupNtfsMcbEntry( &TargetScb->Mcb,
+ LogRecord->TargetVcn,
+ &TargetLcn,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL ) ||
+
+ (TargetLcn == UNUSED_LCN)) {
+
+ DebugTrace( 0, Dbg, ("Clusters removed from page entry\n") );
+ continue;
+ }
+ }
+
+ //
+ // Point to the Redo Data and get its length.
+ //
+
+ Data = (PVOID)((PCHAR)LogRecord + LogRecord->RedoOffset);
+ Length = LogRecord->RedoLength;
+
+ //
+ // Shorten length by any Lcns which were deleted.
+ //
+
+ SavedLength = Length;
+
+ for (i = (ULONG)LogRecord->LcnsToFollow; i != 0; i--) {
+
+ ULONG AllocatedLength;
+ ULONG VcnOffset;
+
+ VcnOffset = BytesFromLogBlocks( LogRecord->ClusterBlockOffset ) + LogRecord->RecordOffset + LogRecord->AttributeOffset;
+
+ //
+ // If the Vcn in question is allocated, we can just get out.
+ //
+
+ if (DirtyPage->LcnsForPage[((ULONG)LogRecord->TargetVcn) - ((ULONG)DirtyPage->Vcn) + i - 1] != 0) {
+ break;
+ }
+
+ //
+ // The only log records that update pages but have a length of zero
+ // are deleting things from Usa-protected structures. If we hit such
+ // a log record and any Vcn has been deleted within the Usa structure,
+ // let us assume that the entire Usa structure has been deleted. Change
+ // the SavedLength to be nonzero to cause us to skip this log record
+ // at the end of this for loop!
+ //
+
+ if (SavedLength == 0) {
+ SavedLength = 1;
+ }
+
+ //
+ // Calculate the allocated space left relative to the log record Vcn,
+ // after removing this unallocated Vcn.
+ //
+
+ AllocatedLength = BytesFromClusters( Vcb, i - 1 );
+
+ //
+ // If the update described in this log record goes beyond the allocated
+ // space, then we will have to reduce the length.
+ //
+
+ if ((VcnOffset + Length) > AllocatedLength) {
+
+ //
+ // If the specified update starts at or beyond the allocated length, then
+ // we must set length to zero.
+ //
+
+ if (VcnOffset >= AllocatedLength) {
+
+ Length = 0;
+
+ //
+ // Otherwise set the length to end exactly at the end of the previous
+ // cluster.
+ //
+
+ } else {
+
+ Length = AllocatedLength - VcnOffset;
+ }
+ }
+ }
+
+ //
+ // If the resulting Length from above is now zero, we can skip this log record.
+ //
+
+ if ((Length == 0) && (SavedLength != 0)) {
+ continue;
+ }
+
+ //
+ // Apply the Redo operation in a common routine.
+ //
+
+ DoAction( IrpContext,
+ Vcb,
+ LogRecord,
+ LogRecord->RedoOperation,
+ Data,
+ Length,
+ LogRecordLength,
+ &LogRecordLsn,
+ &PageBcb,
+ &PageLsn );
+
+ if (PageLsn != NULL) {
+ *PageLsn = LogRecordLsn;
+ }
+
+ if (PageBcb != NULL) {
+
+ CcSetDirtyPinnedData( PageBcb, &LogRecordLsn );
+
+ NtfsUnpinBcb( &PageBcb );
+ }
+
+ //
+ // Keep reading and looping back until end of file.
+ //
+
+ } while (LfsReadNextLogRecord( LogHandle,
+ LogContext,
+ &RecordType,
+ &TransactionId,
+ &UndoNextLsn,
+ &PreviousLsn,
+ &LogRecordLsn,
+ &LogRecordLength,
+ (PVOID *)&LogRecord ));
+
+ } finally {
+
+ NtfsUnpinBcb( &PageBcb );
+
+ //
+ // Finally we can kill the log handle.
+ //
+
+ LfsTerminateLogQuery( LogHandle, LogContext );
+ }
+
+ DebugTrace( -1, Dbg, ("RedoPass -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+UndoPass (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs the Undo Pass of Restart. It does this by scanning
+ the Transaction Table produced by the Analysis Pass. For every transaction
+ in this table which is in the active state, all of its Undo log records, as
+ linked together by the UndoNextLsn, are applied to undo the logged operation.
+ Note that all pages at this point should be uptodate with the contents they
+ had at about the time of the crash. The dirty page table is not consulted
+ during the Undo Pass, all relevant Undo operations are unconditionally
+ performed.
+
+ The Undo actions are all performed in the common routine DoAction,
+ which is also used by the Redo Pass.
+
+Arguments:
+
+ Vcb - Volume which is being restarted.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PTRANSACTION_ENTRY Transaction;
+ POPEN_ATTRIBUTE_ENTRY OpenEntry;
+ PRESTART_POINTERS TransactionTable = &Vcb->TransactionTable;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("UndoPass:\n") );
+
+ //
+ // Point to first Transaction Entry.
+ //
+
+ Transaction = NtfsGetFirstRestartTable( TransactionTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (Transaction != NULL) {
+
+ if ((Transaction->TransactionState == TransactionActive)
+
+ &&
+
+ (Transaction->UndoNextLsn.QuadPart != 0)) {
+
+ //
+ // Abort transaction if it is active and has undo work to do.
+ //
+
+ NtfsAbortTransaction( IrpContext, Vcb, Transaction );
+
+ //
+ // Remove this entry from the transaction table.
+ //
+
+ } else {
+
+ TRANSACTION_ID TransactionId = GetIndexFromRestartEntry( &Vcb->TransactionTable,
+ Transaction );
+
+ NtfsAcquireExclusiveRestartTable( &Vcb->TransactionTable,
+ TRUE );
+
+ NtfsFreeRestartTableIndex( &Vcb->TransactionTable,
+ TransactionId );
+
+ NtfsReleaseRestartTable( &Vcb->TransactionTable );
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ Transaction = NtfsGetNextRestartTable( TransactionTable, Transaction );
+ }
+
+ //
+ // Now we will flush and purge all the streams to verify that the purges
+ // will work.
+ //
+
+ OpenEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (OpenEntry != NULL) {
+
+ IO_STATUS_BLOCK IoStatus;
+ PSCB Scb;
+
+ Scb = OpenEntry->Overlay.Scb;
+
+ //
+ // We clean up the Scb only if it exists and this is index in the
+ // OpenAttributeTable that this Scb actually refers to.
+ // If this Scb has several entries in the table, this check will insure
+ // that it only gets cleaned up once.
+ //
+
+ if ((Scb != NULL)
+ && (Scb->NonpagedScb->OpenAttributeTableIndex == GetIndexFromRestartEntry( &Vcb->OpenAttributeTable, OpenEntry))) {
+
+ //
+ // Now flush the file. It is important to call the
+ // same routine the Lazy Writer calls, so that write.c
+ // will not decide to update file size for the attribute,
+ // since we really are working here with the wrong size.
+ //
+ // We also now purge all pages, in case we go to update
+ // half of a page that was clean and read in as zeros in
+ // the Redo Pass.
+ //
+
+ NtfsAcquireScbForLazyWrite( (PVOID)Scb, TRUE );
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+ NtfsReleaseScbFromLazyWrite( (PVOID)Scb );
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &IoStatus.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+
+ if (!CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject, NULL, 0, FALSE )) {
+
+ KdPrint(("NtfsUndoPass: Unable to purge volume\n"));
+
+ NtfsRaiseStatus( IrpContext, STATUS_INTERNAL_ERROR, NULL, NULL );
+ }
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ OpenEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ OpenEntry );
+ }
+
+ DebugTrace( -1, Dbg, ("UndoPass -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+//
+// First define some "local" macros for Lsn in page manipulation.
+//
+
+//
+// Macro to check the Lsn and break (out of the switch statement in DoAction)
+// if the respective redo record need not be applied. Note that if the structure's
+// clusters were deleted, then it will read as all zero's so we also check a field
+// which must be nonzero.
+//
+
+#define CheckLsn(PAGE) { \
+ if (*(PULONG)((PMULTI_SECTOR_HEADER)(PAGE))->Signature == \
+ *(PULONG)BaadSignature) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+ \
+ if (ARGUMENT_PRESENT(RedoLsn) && \
+ ((*(PULONG)((PMULTI_SECTOR_HEADER)(PAGE))->Signature == \
+ *(PULONG)HoleSignature) || \
+ (RedoLsn->QuadPart <= ((PFILE_RECORD_SEGMENT_HEADER)(PAGE))->Lsn.QuadPart))) { \
+ /**** xxLeq(*RedoLsn,((PFILE_RECORD_SEGMENT_HEADER)(PAGE))->Lsn) ****/ \
+ DebugTrace( 0, Dbg, ("Skipping Page with Lsn: %016I64x\n", \
+ ((PFILE_RECORD_SEGMENT_HEADER)(PAGE))->Lsn) ); \
+ \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// Macros for checking File Records and Index Buffers before and after the action
+// routines. The after checks are only for debug. The before check is not
+// always possible.
+//
+
+#define CheckFileRecordBefore { \
+ if (!NtfsCheckFileRecord( IrpContext, Vcb, FileRecord )) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+#define CheckFileRecordAfter { \
+ DbgDoit(NtfsCheckFileRecord( IrpContext, Vcb, FileRecord )); \
+}
+
+#define CheckIndexBufferBefore { \
+ if (!NtfsCheckIndexBuffer( IrpContext, Scb, IndexBuffer )) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+#define CheckIndexBufferAfter { \
+ DbgDoit(NtfsCheckIndexBuffer( IrpContext, Scb, IndexBuffer )); \
+}
+
+//
+// Checks if the record offset + length will fit into a file record.
+//
+
+#define CheckWriteFileRecord { \
+ if (LogRecord->RecordOffset + Length > Vcb->BytesPerFileRecordSegment) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// Checks if the record offset in the log record points to an attribute.
+//
+
+#define CheckIfAttribute { \
+ _Length = FileRecord->FirstAttributeOffset; \
+ _AttrHeader = Add2Ptr( FileRecord, _Length ); \
+ while (_Length < LogRecord->RecordOffset) { \
+ if ((_AttrHeader->TypeCode == $END) || \
+ (_AttrHeader->RecordLength == 0)) { \
+ break; \
+ } \
+ _Length += _AttrHeader->RecordLength; \
+ _AttrHeader = NtfsGetNextRecord( _AttrHeader ); \
+ } \
+ if (_Length != LogRecord->RecordOffset) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// Checks if the attribute described by 'Data' fits within the log record
+// and will fit in the file record.
+//
+
+#define CheckInsertAttribute { \
+ _AttrHeader = (PATTRIBUTE_RECORD_HEADER) Data; \
+ if ((Length < (ULONG) SIZEOF_RESIDENT_ATTRIBUTE_HEADER) || \
+ (_AttrHeader->RecordLength & 7) || \
+ ((ULONG) Add2Ptr( Data, _AttrHeader->RecordLength ) \
+ > (ULONG) Add2Ptr( LogRecord, LogRecordLength )) || \
+ (Length > FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This checks
+// - the attribute fits if we are growing the attribute
+//
+
+#define CheckResidentFits { \
+ _AttrHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord, LogRecord->RecordOffset ); \
+ _Length = LogRecord->AttributeOffset + Length; \
+ if ((LogRecord->RedoLength == LogRecord->UndoLength) ? \
+ (LogRecord->AttributeOffset + Length > _AttrHeader->RecordLength) : \
+ ((_Length > _AttrHeader->RecordLength) && \
+ ((_Length - _AttrHeader->RecordLength) > \
+ (FileRecord->BytesAvailable - FileRecord->FirstFreeByte)))) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks that the data in this log record will fit into the
+// allocation described in the log record.
+//
+
+#define CheckNonResidentFits { \
+ if (BytesFromClusters( Vcb, LogRecord->LcnsToFollow ) \
+ < (BytesFromLogBlocks( LogRecord->ClusterBlockOffset ) + LogRecord->RecordOffset + Length)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks
+// - the attribute is non-resident.
+// - the data is beyond the mapping pairs offset.
+// - the new data begins within the current size of the attribute.
+// - the new data will fit in the file record.
+//
+
+#define CheckMappingFits { \
+ _AttrHeader = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord, LogRecord->RecordOffset );\
+ _Length = LogRecord->AttributeOffset + Length; \
+ if (NtfsIsAttributeResident( _AttrHeader ) || \
+ (LogRecord->AttributeOffset < _AttrHeader->Form.Nonresident.MappingPairsOffset) || \
+ (LogRecord->AttributeOffset > _AttrHeader->RecordLength) || \
+ ((_Length > _AttrHeader->RecordLength) && \
+ ((_Length - _AttrHeader->RecordLength) > \
+ (FileRecord->BytesAvailable - FileRecord->FirstFreeByte)))) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine simply checks that the attribute is non-resident.
+//
+
+#define CheckIfNonResident { \
+ if (NtfsIsAttributeResident( (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord, \
+ LogRecord->RecordOffset ))) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks if the record offset points to an index_root attribute.
+//
+
+#define CheckIfIndexRoot { \
+ _Length = FileRecord->FirstAttributeOffset; \
+ _AttrHeader = Add2Ptr( FileRecord, FileRecord->FirstAttributeOffset ); \
+ while (_Length < LogRecord->RecordOffset) { \
+ if ((_AttrHeader->TypeCode == $END) || \
+ (_AttrHeader->RecordLength == 0)) { \
+ break; \
+ } \
+ _Length += _AttrHeader->RecordLength; \
+ _AttrHeader = NtfsGetNextRecord( _AttrHeader ); \
+ } \
+ if ((_Length != LogRecord->RecordOffset) || \
+ (_AttrHeader->TypeCode != $INDEX_ROOT)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks if the attribute offset points to a valid index entry.
+//
+
+#define CheckIfRootIndexEntry { \
+ _Length = PtrOffset( Attribute, IndexHeader ) + \
+ IndexHeader->FirstIndexEntry; \
+ _CurrentEntry = Add2Ptr( IndexHeader, IndexHeader->FirstIndexEntry ); \
+ while (_Length < LogRecord->AttributeOffset) { \
+ if ((_Length >= Attribute->RecordLength) || \
+ (_CurrentEntry->Length == 0)) { \
+ break; \
+ } \
+ _Length += _CurrentEntry->Length; \
+ _CurrentEntry = Add2Ptr( _CurrentEntry, _CurrentEntry->Length ); \
+ } \
+ if (_Length != LogRecord->AttributeOffset) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks if the attribute offset points to a valid index entry.
+//
+
+#define CheckIfAllocationIndexEntry { \
+ ULONG _AdjustedOffset; \
+ _Length = IndexHeader->FirstIndexEntry; \
+ _AdjustedOffset = FIELD_OFFSET( INDEX_ALLOCATION_BUFFER, IndexHeader ) \
+ + IndexHeader->FirstIndexEntry; \
+ _CurrentEntry = Add2Ptr( IndexHeader, IndexHeader->FirstIndexEntry ); \
+ while (_AdjustedOffset < LogRecord->AttributeOffset) { \
+ if ((_Length >= IndexHeader->FirstFreeByte) || \
+ (_CurrentEntry->Length == 0)) { \
+ break; \
+ } \
+ _AdjustedOffset += _CurrentEntry->Length; \
+ _Length += _CurrentEntry->Length; \
+ _CurrentEntry = Add2Ptr( _CurrentEntry, _CurrentEntry->Length ); \
+ } \
+ if (_AdjustedOffset != LogRecord->AttributeOffset) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks if we can safely add this index entry.
+// - The index entry must be within the log record
+// - There must be enough space in the attribute to insert this.
+//
+
+#define CheckIfRootEntryFits { \
+ if (((ULONG) Add2Ptr( Data, IndexEntry->Length ) > (ULONG) Add2Ptr( LogRecord, LogRecordLength )) || \
+ (IndexEntry->Length > FileRecord->BytesAvailable - FileRecord->FirstFreeByte)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine checks that we can safely add this index entry.
+// - The entry must be contained in a log record.
+// - The entry must fit in the index buffer.
+//
+
+#define CheckIfAllocationEntryFits { \
+ if (((ULONG) Add2Ptr( Data, IndexEntry->Length ) > \
+ (ULONG) Add2Ptr( LogRecord, LogRecordLength )) || \
+ (IndexEntry->Length > IndexHeader->BytesAvailable - IndexHeader->FirstFreeByte)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine will check that the data will fit in the tail of an index buffer.
+//
+
+#define CheckWriteIndexBuffer { \
+ if (LogRecord->AttributeOffset + Length > \
+ (FIELD_OFFSET( INDEX_ALLOCATION_BUFFER, IndexHeader ) + \
+ IndexHeader->BytesAvailable)) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+//
+// This routine verifies that the bitmap bits are contained in the Lcns described.
+//
+
+#define CheckBitmapRange { \
+ if ((BytesFromLogBlocks( LogRecord->ClusterBlockOffset ) + \
+ ((BitMapRange->BitMapOffset + BitMapRange->NumberOfBits + 7) / 8)) > \
+ BytesFromClusters( Vcb, LogRecord->LcnsToFollow )) { \
+ NtfsMarkVolumeDirty( IrpContext, Vcb ); \
+ NtfsUnpinBcb( Bcb ); \
+ break; \
+ } \
+}
+
+VOID
+DoAction (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN NTFS_LOG_OPERATION Operation,
+ IN PVOID Data,
+ IN ULONG Length,
+ IN ULONG LogRecordLength,
+ IN PLSN RedoLsn OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PLSN *PageLsn
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is a common routine for the Redo and Undo Passes, for performing
+ the respective redo and undo operations. All Redo- and Undo-specific
+ processing is performed in RedoPass or UndoPass; in this routine all actions
+ are treated identically, regardless of whether the action is undo or redo.
+ Note that most actions are possible for both redo and undo, although some
+ are only used for one or the other.
+
+
+ Basically this routine is just a big switch statement dispatching on operation
+ code. The parameter descriptions provide some insight on how some of the
+ parameters must be initialized differently for redo or undo.
+
+Arguments:
+
+ Vcb - Vcb for the volume being restarted.
+
+ LogRecord - Pointer to the log record from which Redo or Undo is being executed.
+ Only the common fields are accessed.
+
+ Operation - The Redo or Undo operation to be performed.
+
+ Data - Pointer to the Redo or Undo buffer, depending on the caller.
+
+ Length - Length of the Redo or Undo buffer.
+
+ LogRecordLength - Length of the entire log record.
+
+ RedoLsn - For Redo this must be the Lsn of the Log Record for which the
+ redo is being applied. Must be NULL for transaction abort/undo.
+
+ Bcb - Returns the Bcb of the page to which the action was performed, or NULL.
+
+ PageLsn - Returns a pointer to where a new Lsn may be stored, or NULL.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ PSCB Scb;
+ PINDEX_HEADER IndexHeader;
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+ PINDEX_ENTRY IndexEntry;
+
+ //
+ // The following are used in the Check macros
+ //
+
+ PATTRIBUTE_RECORD_HEADER _AttrHeader; \
+ PINDEX_ENTRY _CurrentEntry; \
+ ULONG _Length;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("DoAction:\n") );
+ DebugTrace( 0, Dbg, ("Operation = %08lx\n", Operation) );
+ DebugTrace( 0, Dbg, ("Data = %08lx\n", Data) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", Length) );
+
+ //
+ // Initially clear outputs.
+ //
+
+ *Bcb = NULL;
+ *PageLsn = NULL;
+
+ //
+ // Dispatch to handle log record depending on type.
+ //
+
+ switch (Operation) {
+
+ //
+ // To initialize a file record segment, we simply do a prepare write and copy the
+ // file record in.
+ //
+
+ case InitializeFileRecordSegment:
+
+ //
+ // Check the log record and that the data is a valid file record.
+ //
+
+ CheckWriteFileRecord;
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ *PageLsn = &FileRecord->Lsn;
+
+ RtlCopyMemory( FileRecord, Data, Length );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To deallocate a file record segment, we do a prepare write (no need to read it
+ // to deallocate it), and clear FILE_RECORD_SEGMENT_IN_USE.
+ //
+
+ case DeallocateFileRecordSegment:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ *PageLsn = &FileRecord->Lsn;
+
+ ASSERT( FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )
+ || FlagOn( FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE ));
+
+ ClearFlag(FileRecord->Flags, FILE_RECORD_SEGMENT_IN_USE);
+
+ FileRecord->SequenceNumber += 1;
+
+ break;
+
+ //
+ // To write the end of a file record segment, we calculate a pointer to the
+ // destination position (OldAttribute), and then call the routine to take
+ // care of it.
+ //
+
+ case WriteEndOfFileRecordSegment:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+ CheckWriteFileRecord;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ Attribute = Add2Ptr( FileRecord, LogRecord->RecordOffset );
+
+ NtfsRestartWriteEndOfFileRecord( FileRecord,
+ Attribute,
+ (PATTRIBUTE_RECORD_HEADER)Data,
+ Length );
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // For Create Attribute, we read in the designated Mft record, and
+ // insert the attribute record from the log record.
+ //
+
+ case CreateAttribute:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+ CheckInsertAttribute;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ NtfsRestartInsertAttribute( IrpContext,
+ FileRecord,
+ LogRecord->RecordOffset,
+ (PATTRIBUTE_RECORD_HEADER)Data,
+ NULL,
+ NULL,
+ 0 );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To Delete an attribute, we read the designated Mft record and make
+ // a call to remove the attribute record.
+ //
+
+ case DeleteAttribute:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ NtfsRestartRemoveAttribute( IrpContext,
+ FileRecord,
+ LogRecord->RecordOffset );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To update a resident attribute, we read the designated Mft record and
+ // call the routine to change its value.
+ //
+
+ case UpdateResidentValue:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+ CheckResidentFits;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ NtfsRestartChangeValue( IrpContext,
+ FileRecord,
+ LogRecord->RecordOffset,
+ LogRecord->AttributeOffset,
+ Data,
+ Length,
+ (BOOLEAN)((LogRecord->RedoLength !=
+ LogRecord->UndoLength) ?
+ TRUE : FALSE) );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To update a nonresident value, we simply pin the attribute and copy
+ // the data in. Log record will limit us to a page at a time.
+ //
+
+ case UpdateNonresidentValue:
+
+ {
+ PVOID Buffer;
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext,
+ Vcb,
+ LogRecord,
+ Length,
+ Bcb,
+ &Buffer,
+ &Scb );
+
+ CheckNonResidentFits;
+
+ //
+ // Copy in the new data.
+ //
+
+ RtlCopyMemory( (PCHAR)Buffer + LogRecord->RecordOffset, Data, Length );
+
+ break;
+ }
+
+ //
+ // To update the mapping pairs in a nonresident attribute, we read the
+ // designated Mft record and call the routine to change them.
+ //
+
+ case UpdateMappingPairs:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+ CheckMappingFits;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ NtfsRestartChangeMapping( IrpContext,
+ Vcb,
+ FileRecord,
+ LogRecord->RecordOffset,
+ LogRecord->AttributeOffset,
+ Data,
+ Length );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To set new attribute sizes, we read the designated Mft record, point
+ // to the attribute, and copy in the new sizes.
+ //
+
+ case SetNewAttributeSizes:
+
+ {
+ PNEW_ATTRIBUTE_SIZES Sizes;
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfAttribute;
+ CheckIfNonResident;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ Sizes = (PNEW_ATTRIBUTE_SIZES)Data;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord +
+ LogRecord->RecordOffset);
+
+ Attribute->Form.Nonresident.AllocatedLength = Sizes->AllocationSize;
+
+ Attribute->Form.Nonresident.FileSize = Sizes->FileSize;
+
+ Attribute->Form.Nonresident.ValidDataLength = Sizes->ValidDataLength;
+
+ if (Length >= SIZEOF_FULL_ATTRIBUTE_SIZES) {
+
+ Attribute->Form.Nonresident.TotalAllocated = Sizes->TotalAllocated;
+ }
+
+ CheckFileRecordAfter;
+
+ break;
+ }
+
+ //
+ // To insert a new index entry in the root, we read the designated Mft
+ // record, point to the attribute and the insertion point, and call the
+ // same routine used in normal operation.
+ //
+
+ case AddIndexEntryRoot:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfIndexRoot;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord +
+ LogRecord->RecordOffset);
+
+ IndexEntry = (PINDEX_ENTRY)Data;
+ IndexHeader = &((PINDEX_ROOT) NtfsAttributeValue( Attribute ))->IndexHeader;
+
+ CheckIfRootIndexEntry;
+ CheckIfRootEntryFits;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ NtfsRestartInsertSimpleRoot( IrpContext,
+ IndexEntry,
+ FileRecord,
+ Attribute,
+ Add2Ptr( Attribute, LogRecord->AttributeOffset ));
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To insert a new index entry in the root, we read the designated Mft
+ // record, point to the attribute and the insertion point, and call the
+ // same routine used in normal operation.
+ //
+
+ case DeleteIndexEntryRoot:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfIndexRoot;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord +
+ LogRecord->RecordOffset);
+
+ IndexHeader = &((PINDEX_ROOT) NtfsAttributeValue( Attribute ))->IndexHeader;
+ CheckIfRootIndexEntry;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( Attribute,
+ LogRecord->AttributeOffset);
+
+ NtfsRestartDeleteSimpleRoot( IrpContext,
+ IndexEntry,
+ FileRecord,
+ Attribute );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To insert a new index entry in the allocation, we read the designated index
+ // buffer, point to the insertion point, and call the same routine used in
+ // normal operation.
+ //
+
+ case AddIndexEntryAllocation:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexEntry = (PINDEX_ENTRY)Data;
+ IndexHeader = &IndexBuffer->IndexHeader;
+
+ CheckIfAllocationIndexEntry;
+ CheckIfAllocationEntryFits;
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ NtfsRestartInsertSimpleAllocation( IndexEntry,
+ IndexBuffer,
+ Add2Ptr( IndexBuffer, LogRecord->AttributeOffset ));
+
+ CheckIndexBufferAfter;
+
+ break;
+
+ //
+ // To delete an index entry in the allocation, we read the designated index
+ // buffer, point to the deletion point, and call the same routine used in
+ // normal operation.
+ //
+
+ case DeleteIndexEntryAllocation:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ CheckIfAllocationIndexEntry;
+
+ IndexEntry = (PINDEX_ENTRY)((PCHAR)IndexBuffer + LogRecord->AttributeOffset);
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ NtfsRestartDeleteSimpleAllocation( IndexEntry, IndexBuffer );
+
+ CheckIndexBufferAfter;
+
+ break;
+
+ case WriteEndOfIndexBuffer:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ CheckIfAllocationIndexEntry;
+ CheckWriteIndexBuffer;
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY)((PCHAR)IndexBuffer + LogRecord->AttributeOffset);
+
+ NtfsRestartWriteEndOfIndex( IndexHeader,
+ IndexEntry,
+ (PINDEX_ENTRY)Data,
+ Length );
+ CheckIndexBufferAfter;
+
+ break;
+
+ //
+ // To set a new index entry Vcn in the root, we read the designated Mft
+ // record, point to the attribute and the index entry, and call the
+ // same routine used in normal operation.
+ //
+
+ case SetIndexEntryVcnRoot:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfIndexRoot;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord, LogRecord->RecordOffset );
+
+ IndexHeader = &((PINDEX_ROOT) NtfsAttributeValue( Attribute ))->IndexHeader;
+
+ CheckIfRootIndexEntry;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY)((PCHAR)Attribute +
+ LogRecord->AttributeOffset);
+
+ NtfsRestartSetIndexBlock( IndexEntry,
+ *((PLONGLONG) Data) );
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To set a new index entry Vcn in the allocation, we read the designated index
+ // buffer, point to the index entry, and call the same routine used in
+ // normal operation.
+ //
+
+ case SetIndexEntryVcnAllocation:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ CheckIfAllocationIndexEntry;
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( IndexBuffer, LogRecord->AttributeOffset );
+
+ NtfsRestartSetIndexBlock( IndexEntry,
+ *((PLONGLONG) Data) );
+ CheckIndexBufferAfter;
+
+ break;
+
+ //
+ // To update a file name in the root, we read the designated Mft
+ // record, point to the attribute and the index entry, and call the
+ // same routine used in normal operation.
+ //
+
+ case UpdateFileNameRoot:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfIndexRoot;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER) Add2Ptr( FileRecord, LogRecord->RecordOffset );
+
+ IndexHeader = &((PINDEX_ROOT) NtfsAttributeValue( Attribute ))->IndexHeader;
+ CheckIfRootIndexEntry;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( Attribute, LogRecord->AttributeOffset );
+
+ NtfsRestartUpdateFileName( IndexEntry,
+ (PDUPLICATED_INFORMATION) Data );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To update a file name in the allocation, we read the designated index
+ // buffer, point to the index entry, and call the same routine used in
+ // normal operation.
+ //
+
+ case UpdateFileNameAllocation:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ CheckIfAllocationIndexEntry;
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( IndexBuffer, LogRecord->AttributeOffset );
+
+ NtfsRestartUpdateFileName( IndexEntry,
+ (PDUPLICATED_INFORMATION) Data );
+
+ CheckIndexBufferAfter;
+
+ break;
+
+ //
+ // To set a range of bits in the volume bitmap, we just read in the a hunk
+ // of the bitmap as described by the log record, and then call the restart
+ // routine to do it.
+ //
+
+ case SetBitsInNonresidentBitMap:
+
+ {
+ PBITMAP_RANGE BitMapRange;
+ PVOID BitMapBuffer;
+ ULONG BitMapSize;
+ RTL_BITMAP Bitmap;
+
+ //
+ // Open the attribute first to get the Scb.
+ //
+
+ OpenAttributeForRestart( IrpContext, Vcb, LogRecord, &Scb );
+
+ //
+ // Pin the desired bitmap buffer.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, &BitMapBuffer, &Scb );
+
+ BitMapRange = (PBITMAP_RANGE)Data;
+
+ CheckBitmapRange;
+
+ //
+ // Initialize our bitmap description, and call the restart
+ // routine with the bitmap Scb exclusive (assuming it cannot
+ // raise).
+ //
+
+ BitMapSize = BytesFromClusters( Vcb, LogRecord->LcnsToFollow ) * 8;
+
+ RtlInitializeBitMap( &Bitmap, BitMapBuffer, BitMapSize );
+
+ NtfsRestartSetBitsInBitMap( IrpContext,
+ &Bitmap,
+ BitMapRange->BitMapOffset,
+ BitMapRange->NumberOfBits );
+
+#ifdef NTFS_CHECK_BITMAP
+ if (!FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) &&
+ (Scb == Vcb->BitmapScb) &&
+ (Vcb->BitmapCopy != NULL)) {
+
+ ULONG BitmapOffset;
+ ULONG BitmapPage;
+ ULONG StartBit;
+
+ BitmapOffset = BytesFromClusters( Vcb, LogRecord->TargetVcn ) * 8;
+
+ BitmapPage = (BitmapOffset + BitMapRange->BitMapOffset) / (PAGE_SIZE * 8);
+ StartBit = (BitmapOffset + BitMapRange->BitMapOffset) & ((PAGE_SIZE * 8) - 1);
+
+ RtlSetBits( Vcb->BitmapCopy + BitmapPage, StartBit, BitMapRange->NumberOfBits );
+ }
+#endif
+
+ break;
+ }
+
+ //
+ // To clear a range of bits in the volume bitmap, we just read in the a hunk
+ // of the bitmap as described by the log record, and then call the restart
+ // routine to do it.
+ //
+
+ case ClearBitsInNonresidentBitMap:
+
+ {
+ PBITMAP_RANGE BitMapRange;
+ PVOID BitMapBuffer;
+ ULONG BitMapSize;
+ RTL_BITMAP Bitmap;
+
+ //
+ // Open the attribute first to get the Scb.
+ //
+
+ OpenAttributeForRestart( IrpContext, Vcb, LogRecord, &Scb );
+
+ //
+ // Pin the desired bitmap buffer.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, &BitMapBuffer, &Scb );
+
+ BitMapRange = (PBITMAP_RANGE)Data;
+
+ CheckBitmapRange;
+
+ BitMapSize = BytesFromClusters( Vcb, LogRecord->LcnsToFollow ) * 8;
+
+ //
+ // Initialize our bitmap description, and call the restart
+ // routine with the bitmap Scb exclusive (assuming it cannot
+ // raise).
+ //
+
+ RtlInitializeBitMap( &Bitmap, BitMapBuffer, BitMapSize );
+
+ NtfsRestartClearBitsInBitMap( IrpContext,
+ &Bitmap,
+ BitMapRange->BitMapOffset,
+ BitMapRange->NumberOfBits );
+
+#ifdef NTFS_CHECK_BITMAP
+ if (!FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS ) &&
+ (Scb == Vcb->BitmapScb) &&
+ (Vcb->BitmapCopy != NULL)) {
+
+ ULONG BitmapOffset;
+ ULONG BitmapPage;
+ ULONG StartBit;
+
+ BitmapOffset = BytesFromClusters( Vcb, LogRecord->TargetVcn ) * 8;
+
+ BitmapPage = (BitmapOffset + BitMapRange->BitMapOffset) / (PAGE_SIZE * 8);
+ StartBit = (BitmapOffset + BitMapRange->BitMapOffset) & ((PAGE_SIZE * 8) - 1);
+
+ RtlClearBits( Vcb->BitmapCopy + BitmapPage, StartBit, BitMapRange->NumberOfBits );
+ }
+#endif
+ break;
+ }
+
+ //
+ // To update a file name in the root, we read the designated Mft
+ // record, point to the attribute and the index entry, and call the
+ // same routine used in normal operation.
+ //
+
+ case UpdateRecordDataRoot:
+
+ //
+ // Pin the desired Mft record.
+ //
+
+ PinMftRecordForRestart( IrpContext, Vcb, LogRecord, Bcb, &FileRecord );
+
+ CheckLsn( FileRecord );
+ CheckFileRecordBefore;
+ CheckIfIndexRoot;
+
+ Attribute = (PATTRIBUTE_RECORD_HEADER)((PCHAR)FileRecord +
+ LogRecord->RecordOffset);
+
+ IndexHeader = &((PINDEX_ROOT) NtfsAttributeValue( Attribute ))->IndexHeader;
+ CheckIfRootIndexEntry;
+
+ *PageLsn = &FileRecord->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY)((PCHAR)Attribute +
+ LogRecord->AttributeOffset);
+
+ NtOfsRestartUpdateDataInIndex( IndexEntry, Data, Length );
+
+ CheckFileRecordAfter;
+
+ break;
+
+ //
+ // To update a file name in the allocation, we read the designated index
+ // buffer, point to the index entry, and call the same routine used in
+ // normal operation.
+ //
+
+ case UpdateRecordDataAllocation:
+
+ //
+ // Pin the desired index buffer, and check the Lsn.
+ //
+
+ ASSERT( Length <= PAGE_SIZE );
+
+ PinAttributeForRestart( IrpContext, Vcb, LogRecord, 0, Bcb, (PVOID *)&IndexBuffer, &Scb );
+
+ CheckLsn( IndexBuffer );
+ CheckIndexBufferBefore;
+
+ IndexHeader = &IndexBuffer->IndexHeader;
+ CheckIfAllocationIndexEntry;
+
+ *PageLsn = &IndexBuffer->Lsn;
+
+ IndexEntry = (PINDEX_ENTRY)((PCHAR)IndexBuffer +
+ LogRecord->AttributeOffset);
+
+ NtOfsRestartUpdateDataInIndex( IndexEntry, Data, Length );
+
+ CheckIndexBufferAfter;
+
+ break;
+
+ //
+ // The following cases require no action during the Redo or Undo Pass.
+ //
+
+ case Noop:
+ case DeleteDirtyClusters:
+ case HotFix:
+ case EndTopLevelAction:
+ case PrepareTransaction:
+ case CommitTransaction:
+ case ForgetTransaction:
+ case CompensationLogRecord:
+ case OpenNonresidentAttribute:
+ case OpenAttributeTableDump:
+ case AttributeNamesDump:
+ case DirtyPageTableDump:
+ case TransactionTableDump:
+
+ break;
+
+ //
+ // All codes will be explicitly handled. If we see a code we
+ // do not expect, then we are in trouble.
+ //
+
+ default:
+
+ DebugTrace( 0, Dbg, ("Record address: %08lx\n", LogRecord) );
+ DebugTrace( 0, Dbg, ("Redo operation is: %04lx\n", LogRecord->RedoOperation) );
+ DebugTrace( 0, Dbg, ("Undo operation is: %04lx\n", LogRecord->RedoOperation) );
+
+ ASSERTMSG( "Unknown Action!\n", FALSE );
+
+ break;
+ }
+
+ DebugDoit(
+ if (*Bcb != NULL) {
+ DebugTrace( 0, Dbg, ("**** Update applied\n") );
+ }
+ );
+
+ DebugTrace( 0, Dbg, ("Bcb > %08lx\n", *Bcb) );
+ DebugTrace( 0, Dbg, ("PageLsn > %08lx\n", *PageLsn) );
+ DebugTrace( -1, Dbg, ("DoAction -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+PinMftRecordForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ OUT PBCB *Bcb,
+ OUT PFILE_RECORD_SEGMENT_HEADER *FileRecord
+ )
+
+/*++
+
+Routine Description:
+
+ This routine pins a record in the Mft for restart, as described
+ by the current log record.
+
+Arguments:
+
+ Vcb - Supplies the Vcb pointer for the volume
+
+ LogRecord - Supplies the pointer to the current log record.
+
+ Bcb - Returns a pointer to the Bcb for the pinned record.
+
+ FileRecord - Returns a pointer to the desired file record.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ LONGLONG SegmentReference;
+
+ PAGED_CODE();
+
+ //
+ // Calculate the file number part of the segment reference. Do this
+ // by obtaining the file offset of the file record and then convert to
+ // a file number.
+ //
+
+ SegmentReference = LlBytesFromClusters( Vcb, LogRecord->TargetVcn );
+ SegmentReference += BytesFromLogBlocks( LogRecord->ClusterBlockOffset );
+ SegmentReference = LlFileRecordsFromBytes( Vcb, SegmentReference );
+
+ //
+ // Pin the Mft record.
+ //
+
+ NtfsPinMftRecord( IrpContext,
+ Vcb,
+ (PMFT_SEGMENT_REFERENCE)&SegmentReference,
+ TRUE,
+ Bcb,
+ FileRecord,
+ NULL );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+OpenAttributeForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ OUT PSCB *Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine opens the desired attribute for restart, as described
+ by the current log record.
+
+Arguments:
+
+ Vcb - Supplies the Vcb pointer for the volume
+
+ LogRecord - Supplies the pointer to the current log record.
+
+ Scb - Returns a pointer to the Scb for the opened attribute.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+
+ PAGED_CODE();
+
+ //
+ // Get a pointer to the attribute entry for the described attribute.
+ //
+
+ AttributeEntry = (POPEN_ATTRIBUTE_ENTRY)GetRestartEntryFromIndex(
+ &Vcb->OpenAttributeTable,
+ LogRecord->TargetAttribute );
+
+ //
+ // Create the attribute stream, if not already created, and return
+ // the Scb.
+ //
+
+ OpenStreamFromAttributeEntry( IrpContext, AttributeEntry );
+
+ *Scb = AttributeEntry->Overlay.Scb;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+PinAttributeForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord,
+ IN ULONG Length OPTIONAL,
+ OUT PBCB *Bcb,
+ OUT PVOID *Buffer,
+ OUT PSCB *Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine pins the desired buffer for restart, as described
+ by the current log record.
+
+Arguments:
+
+ Vcb - Supplies the Vcb pointer for the volume
+
+ LogRecord - Supplies the pointer to the current log record.
+
+ Length - If specified we will use this to determine the length
+ to pin. This will handle the non-resident streams which may
+ change size (ACL, attribute lists). The log record may have
+ more clusters than are currently in the stream.
+
+ Bcb - Returns a pointer to the Bcb for the pinned record.
+
+ Buffer - Returns a pointer to the desired buffer.
+
+ Scb - Returns a pointer to the Scb for the attribute
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ LONGLONG FileOffset;
+ ULONG ClusterOffset;
+ ULONG PinLength;
+
+ PAGED_CODE();
+
+ //
+ // First open the described atttribute.
+ //
+
+ OpenAttributeForRestart( IrpContext, Vcb, LogRecord, Scb );
+
+ //
+ // Calculate the desired file offset and pin the buffer.
+ //
+
+ ClusterOffset = BytesFromLogBlocks( LogRecord->ClusterBlockOffset );
+
+ FileOffset = LlBytesFromClusters( Vcb, LogRecord->TargetVcn ) + ClusterOffset;
+
+ //
+ // We only want to pin the requested clusters or to the end of
+ // a page, whichever is smaller.
+ //
+
+ if (Vcb->BytesPerCluster > PAGE_SIZE) {
+
+ PinLength = PAGE_SIZE - (((ULONG) FileOffset) & (PAGE_SIZE - 1));
+
+ } else if (Length != 0) {
+
+ PinLength = Length;
+
+ } else {
+
+ PinLength = BytesFromClusters( Vcb, LogRecord->LcnsToFollow ) - ClusterOffset;
+ }
+
+ //
+ // We don't want to pin more than a page
+ //
+
+ NtfsPinStream( IrpContext,
+ *Scb,
+ FileOffset,
+ PinLength,
+ Bcb,
+ Buffer );
+}
+
+
+//
+// Internal support routine
+//
+
+BOOLEAN
+FindDirtyPage (
+ IN PRESTART_POINTERS DirtyPageTable,
+ IN ULONG TargetAttribute,
+ IN VCN Vcn,
+ OUT PDIRTY_PAGE_ENTRY *DirtyPageEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine searches for a Vcn to see if it is already in the Dirty Page
+ Table, returning the Dirty Page Entry if it is.
+
+Arguments:
+
+ DirtyPageTable - pointer to the Dirty Page Table to search.
+
+ TargetAttribute - Attribute for which the dirty Vcn is to be searched.
+
+ Vcn - Vcn to search for.
+
+ DirtyPageEntry - returns a pointer to the Dirty Page Entry if returning TRUE.
+
+Return Value:
+
+ TRUE if the page was found and is being returned, else FALSE.
+
+--*/
+
+{
+ PDIRTY_PAGE_ENTRY DirtyPage;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("FindDirtyPage:\n") );
+ DebugTrace( 0, Dbg, ("TargetAttribute = %08lx\n", TargetAttribute) );
+ DebugTrace( 0, Dbg, ("Vcn = %016I64x\n", Vcn) );
+
+ //
+ // If table has not yet been initialized, return.
+ //
+
+ if (DirtyPageTable->Table == NULL) {
+ return FALSE;
+ }
+
+ //
+ // Loop through all of the dirty pages to look for a match.
+ //
+
+ DirtyPage = NtfsGetFirstRestartTable( DirtyPageTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (DirtyPage != NULL) {
+
+
+ if ((DirtyPage->TargetAttribute == TargetAttribute)
+
+ &&
+
+ (Vcn >= DirtyPage->Vcn)) {
+
+ //
+ // Compute the Last Vcn outside of the comparison or the xxAdd and
+ // xxFromUlong will be called three times.
+ //
+
+ LONGLONG BeyondLastVcn;
+
+ BeyondLastVcn = DirtyPage->Vcn + DirtyPage->LcnsToFollow;
+
+ if (Vcn < BeyondLastVcn) {
+
+ *DirtyPageEntry = DirtyPage;
+
+ DebugTrace( 0, Dbg, ("DirtyPageEntry %08lx\n", *DirtyPageEntry) );
+ DebugTrace( -1, Dbg, ("FindDirtypage -> TRUE\n") );
+
+ return TRUE;
+ }
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ DirtyPage = NtfsGetNextRestartTable( DirtyPageTable,
+ DirtyPage );
+ }
+ *DirtyPageEntry = NULL;
+
+ DebugTrace( -1, Dbg, ("FindDirtypage -> FALSE\n") );
+
+ return FALSE;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+PageUpdateAnalysis (
+ IN PVCB Vcb,
+ IN LSN Lsn,
+ IN OUT PRESTART_POINTERS DirtyPageTable,
+ IN PNTFS_LOG_RECORD_HEADER LogRecord
+ )
+
+/*++
+
+Routine Description:
+
+ This routine updates the Dirty Pages Table during the analysis phase
+ for all log records which update a page.
+
+Arguments:
+
+ Vcb - Pointer to the Vcb for the volume.
+
+ Lsn - The Lsn of the log record.
+
+ DirtyPageTable - A pointer to the Dirty Page Table pointer, to be
+ updated and potentially expanded.
+
+ LogRecord - Pointer to the Log Record being analyzed.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PDIRTY_PAGE_ENTRY DirtyPage;
+ ULONG i;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("PageUpdateAnalysis:\n") );
+
+ if (!FindDirtyPage( DirtyPageTable,
+ LogRecord->TargetAttribute,
+ LogRecord->TargetVcn,
+ &DirtyPage )) {
+
+ ULONG ClustersPerPage;
+ ULONG PageIndex;
+
+ //
+ // Calculate the number of clusters per page in the system which wrote
+ // the checkpoint, possibly creating the table.
+ //
+
+ if (DirtyPageTable->Table != NULL) {
+ ClustersPerPage = ((DirtyPageTable->Table->EntrySize -
+ sizeof(DIRTY_PAGE_ENTRY)) / sizeof(LCN)) + 1;
+ } else {
+ ClustersPerPage = Vcb->ClustersPerPage;
+ NtfsInitializeRestartTable( sizeof(DIRTY_PAGE_ENTRY) +
+ (ClustersPerPage - 1) * sizeof(LCN),
+ 32,
+ DirtyPageTable );
+ }
+
+ //
+ // Allocate a dirty page entry.
+ //
+
+ PageIndex = NtfsAllocateRestartTableIndex( DirtyPageTable );
+
+ //
+ // Get a pointer to the entry we just allocated.
+ //
+
+ DirtyPage = (PDIRTY_PAGE_ENTRY)GetRestartEntryFromIndex( DirtyPageTable,
+ PageIndex );
+
+ //
+ // Initialize the dirty page entry.
+ //
+
+ DirtyPage->TargetAttribute = LogRecord->TargetAttribute;
+ DirtyPage->LengthOfTransfer = BytesFromClusters( Vcb, ClustersPerPage );
+ DirtyPage->LcnsToFollow = ClustersPerPage;
+ DirtyPage->Vcn = LogRecord->TargetVcn;
+ ((ULONG)DirtyPage->Vcn) &= ~(ClustersPerPage - 1);
+ DirtyPage->OldestLsn = Lsn;
+ }
+
+ //
+ // Copy the Lcns from the log record into the Dirty Page Entry.
+ //
+ // *** for different page size support, must somehow make whole routine a loop,
+ // in case Lcns do not fit below.
+ //
+
+ for (i = 0; i < (ULONG)LogRecord->LcnsToFollow; i++) {
+
+ DirtyPage->LcnsForPage[((ULONG)LogRecord->TargetVcn) - ((ULONG)DirtyPage->Vcn) + i] =
+ LogRecord->LcnsForPage[i];
+ }
+
+ DebugTrace( -1, Dbg, ("PageUpdateAnalysis -> VOID\n") );
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+OpenStreamFromAttributeEntry (
+ IN PIRP_CONTEXT IrpContext,
+ IN POPEN_ATTRIBUTE_ENTRY AttributeEntry
+ )
+
+{
+ PSCB Scb;
+
+ PAGED_CODE();
+
+ //
+ // Create the attribute stream, if not already created.
+ //
+
+ Scb = AttributeEntry->Overlay.Scb;
+
+ if (Scb->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+
+ if (FlagOn( IrpContext->Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS )) {
+
+ CcSetAdditionalCacheAttributes( Scb->FileObject, TRUE, TRUE );
+ }
+ }
+
+ return;
+}
+
+
+//
+// Internal support routine
+//
+
+VOID
+OpenAttributesForRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PRESTART_POINTERS DirtyPageTable
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called immediately after the Analysis Pass to open all of
+ the attributes in the Open Attribute Table, and preload their Mcbs with
+ any run information required to apply updates in the Dirty Page Table.
+ With this trick we are effectively doing physical I/O directly to Lbns on
+ the disk without relying on any of the file structure to be correct.
+
+Arguments:
+
+ Vcb - Vcb for the volume, for which the Open Attribute Table has been
+ initialized.
+
+ DirtyPageTable - Dirty Page table reconstructed from the Analysis Pass.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ POPEN_ATTRIBUTE_ENTRY OpenEntry;
+ PDIRTY_PAGE_ENTRY DirtyPage;
+ ULONG i;
+ PSCB TempScb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("OpenAttributesForRestart:\n") );
+
+ //
+ // First we scan the Open Attribute Table to open all of the attributes.
+ //
+
+ OpenEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (OpenEntry != NULL) {
+
+ //
+ // Create the Scb from the data in the Open Attribute Entry.
+ //
+
+ TempScb = NtfsCreatePrerestartScb( IrpContext,
+ Vcb,
+ &OpenEntry->FileReference,
+ OpenEntry->AttributeTypeCode,
+ &OpenEntry->AttributeName,
+ OpenEntry->BytesPerIndexBuffer );
+
+ //
+ // If we dynamically allocated a name for this guy, then delete
+ // it here.
+ //
+
+ if (OpenEntry->Overlay.AttributeName != NULL) {
+ NtfsFreePool(OpenEntry->Overlay.AttributeName);
+ OpenEntry->AttributeNamePresent = FALSE;
+ }
+
+ OpenEntry->AttributeName = TempScb->AttributeName;
+
+ //
+ // Now we can lay in the Scb. We must say the header is initialized
+ // to keep anyone from going to disk yet.
+ //
+
+ SetFlag( TempScb->ScbState, SCB_STATE_HEADER_INITIALIZED );
+
+ //
+ // Now store the index in the newly created Scb.
+ //
+
+ TempScb->NonpagedScb->OpenAttributeTableIndex =
+ GetIndexFromRestartEntry( &Vcb->OpenAttributeTable, OpenEntry );
+
+
+ OpenEntry->Overlay.Scb = TempScb;
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ OpenEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ OpenEntry );
+ }
+
+ //
+ // Now loop through the dirty page table to extract all of the Vcn/Lcn
+ // Mapping that we have, and insert it into the appropriate Scb.
+ //
+
+ DirtyPage = NtfsGetFirstRestartTable( DirtyPageTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (DirtyPage != NULL) {
+
+ PSCB Scb;
+
+ OpenEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
+ DirtyPage->TargetAttribute );
+
+ if (IsRestartTableEntryAllocated(OpenEntry)) {
+
+ Scb = OpenEntry->Overlay.Scb;
+
+ //
+ // Loop to add the allocated Vcns.
+ //
+
+ for (i = 0; i < DirtyPage->LcnsToFollow; i++) {
+
+ VCN Vcn;
+ LONGLONG Size;
+
+ Vcn = DirtyPage->Vcn + i;
+ Size = LlBytesFromClusters( Vcb, Vcn + 1);
+
+ //
+ // Add this run to the Mcb if the Vcn has not been deleted,
+ // and it is not for the fixed part of the Mft.
+ //
+
+ if ((DirtyPage->LcnsForPage[i] != 0)
+
+ &&
+
+ (NtfsSegmentNumber( &OpenEntry->FileReference ) > MASTER_FILE_TABLE2_NUMBER ||
+ (Size >= ((VOLUME_DASD_NUMBER + 1) * Vcb->BytesPerFileRecordSegment)) ||
+ (OpenEntry->AttributeTypeCode != $DATA))) {
+
+
+ NtfsAddNtfsMcbEntry( &Scb->Mcb,
+ Vcn,
+ DirtyPage->LcnsForPage[i],
+ (LONGLONG)1,
+ FALSE );
+
+ if (Size > Scb->Header.AllocationSize.QuadPart) {
+
+ Scb->Header.AllocationSize.QuadPart =
+ Scb->Header.FileSize.QuadPart =
+ Scb->Header.ValidDataLength.QuadPart = Size;
+ }
+ }
+ }
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ DirtyPage = NtfsGetNextRestartTable( DirtyPageTable,
+ DirtyPage );
+ }
+
+ //
+ // Now we know how big all of the files have to be, and recorded that in the
+ // Scb. We have not created streams for any of these Scbs yet, except for
+ // the Mft, Mft2 and LogFile. The size should be correct for Mft2 and LogFile,
+ // but we have to inform the Cache Manager here of the final size of the Mft.
+ //
+
+ TempScb = Vcb->MftScb;
+
+ CcSetFileSizes( TempScb->FileObject,
+ (PCC_FILE_SIZES)&TempScb->Header.AllocationSize );
+
+ DebugTrace( -1, Dbg, ("OpenAttributesForRestart -> VOID\n") );
+}
+
+
+NTSTATUS
+NtfsCloseAttributesFromRestart (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called at the end of a Restart to close any attributes
+ that had to be opened for Restart purposes. Actually what this does is
+ delete all of the internal streams so that the attributes will eventually
+ go away. This routine cannot raise because it is called in the finally of
+ MountVolume. Raising in the main line path will leave the global resource
+ acquired.
+
+Arguments:
+
+ Vcb - Vcb for the volume, for which the Open Attribute Table has been
+ initialized.
+
+Return Value:
+
+ NTSTATUS - STATUS_SUCCESS if all of the I/O completed successfully. Otherwise
+ the error in the IrpContext or the first I/O error.
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ POPEN_ATTRIBUTE_ENTRY OpenEntry;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("CloseAttributesForRestart:\n") );
+
+ //
+ // Set this flag again now, so we do not try to flush out the holes!
+ //
+
+ SetFlag(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS);
+
+ //
+ // Scan the Open Attribute Table to close all of the open attributes.
+ //
+
+ OpenEntry = NtfsGetFirstRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // Loop to end of table.
+ //
+
+ while (OpenEntry != NULL) {
+
+ IO_STATUS_BLOCK IoStatus;
+ PSCB Scb;
+
+ if (OpenEntry->AttributeNamePresent) {
+
+ NtfsFreePool( OpenEntry->Overlay.AttributeName );
+ OpenEntry->Overlay.AttributeName = NULL;
+ }
+
+ Scb = OpenEntry->Overlay.Scb;
+
+ //
+ // We clean up the Scb only if it exists and this is index in the
+ // OpenAttributeTable that this Scb actually refers to.
+ // If this Scb has several entries in the table, this check will insure
+ // that it only gets cleaned up once.
+ //
+
+ if ((Scb != NULL)
+ && (Scb->NonpagedScb->OpenAttributeTableIndex == GetIndexFromRestartEntry( &Vcb->OpenAttributeTable, OpenEntry))) {
+
+ FILE_REFERENCE FileReference;
+
+ //
+ // Only shut it down if it is not the Mft or its mirror.
+ //
+
+ FileReference = Scb->Fcb->FileReference;
+ if (NtfsSegmentNumber( &FileReference ) > LOG_FILE_NUMBER ||
+ (Scb->AttributeTypeCode != $DATA)) {
+
+ //
+ // Now flush the file. It is important to call the
+ // same routine the Lazy Writer calls, so that write.c
+ // will not decide to update file size for the attribute,
+ // since we really are working here with the wrong size.
+ //
+
+ NtfsAcquireScbForLazyWrite( (PVOID)Scb, TRUE );
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject, NULL, 0, &IoStatus );
+ NtfsReleaseScbFromLazyWrite( (PVOID)Scb );
+
+ if (NT_SUCCESS( Status )) {
+
+ if (!NT_SUCCESS( IrpContext->ExceptionStatus )) {
+
+ Status = IrpContext->ExceptionStatus;
+
+ } else if (!NT_SUCCESS( IoStatus.Status )) {
+
+ Status = FsRtlNormalizeNtstatus( IoStatus.Status,
+ STATUS_UNEXPECTED_IO_ERROR );
+ }
+ }
+
+ //
+ // If there is an Scb and it is not for a system file, then delete
+ // the stream file so it can eventually go away.
+ //
+
+ NtfsUninitializeNtfsMcb( &Scb->Mcb );
+ NtfsInitializeNtfsMcb( &Scb->Mcb,
+ &Scb->Header,
+ &Scb->McbStructs,
+ FlagOn( Scb->Fcb->FcbState,
+ FCB_STATE_PAGING_FILE ) ? NonPagedPool :
+ PagedPool );
+
+ //
+ // Now that we are restarted, we must clear the header state
+ // so that we will go look up the sizes and load the Scb
+ // from disk.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_FILE_SIZE_LOADED );
+
+ //
+ // Show the indexed portions are "uninitialized".
+ //
+
+ if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ Scb->ScbType.Index.BytesPerIndexBuffer = 0;
+ }
+
+ if (Scb->FileObject != NULL) {
+
+ NtfsDeleteInternalAttributeStream( Scb,
+ TRUE );
+ } else {
+
+ //
+ // Make sure the Scb is acquired exclusively.
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Scb->Fcb, NULL, TRUE, FALSE );
+ NtfsTeardownStructures( IrpContext,
+ Scb,
+ NULL,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+ }
+
+ } else {
+
+ //
+ // Else free the restart table entry.
+ //
+
+ NtfsFreeRestartTableIndex( &Vcb->OpenAttributeTable,
+ GetIndexFromRestartEntry( &Vcb->OpenAttributeTable,
+ OpenEntry ));
+ }
+
+ //
+ // Point to next entry in table, or NULL.
+ //
+
+ OpenEntry = NtfsGetNextRestartTable( &Vcb->OpenAttributeTable,
+ OpenEntry );
+ }
+
+ //
+ // Resume normal operation.
+ //
+
+ ClearFlag(Vcb->VcbState, VCB_STATE_RESTART_IN_PROGRESS);
+
+ DebugTrace( -1, Dbg, ("CloseAttributesForRestart -> %08lx\n", Status) );
+
+ return Status;
+}
diff --git a/private/ntos/cntfs/rwcmpsup.c b/private/ntos/cntfs/rwcmpsup.c
new file mode 100644
index 000000000..0e52c842c
--- /dev/null
+++ b/private/ntos/cntfs/rwcmpsup.c
@@ -0,0 +1,1821 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ RwCmpSup.c
+
+Abstract:
+
+ This module implements the fast I/O routines for read/write compressed.
+
+Author:
+
+ Tom Miller [TomM] 14-Jul-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+VOID
+NtfsAddToCompressedMdlChain (
+ IN OUT PMDL *MdlChain,
+ IN PVOID MdlBuffer,
+ IN ULONG MdlLength,
+ IN PBCB Bcb,
+ IN LOCK_OPERATION Operation
+ );
+
+VOID
+NtfsSetMdlBcbOwners (
+ IN PMDL MdlChain
+ );
+
+VOID
+NtfsCleanupCompressedMdlChain (
+ IN OUT PMDL *MdlChain,
+ IN ULONG Error
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCopyReadC)
+#pragma alloc_text(PAGE, NtfsCompressedCopyRead)
+#pragma alloc_text(PAGE, NtfsMdlReadCompleteCompressed)
+#pragma alloc_text(PAGE, NtfsCopyWriteC)
+#pragma alloc_text(PAGE, NtfsCompressedCopyWrite)
+#pragma alloc_text(PAGE, NtfsMdlWriteCompleteCompressed)
+#pragma alloc_text(PAGE, NtfsAddToCompressedMdlChain)
+#pragma alloc_text(PAGE, NtfsSetMdlBcbOwners)
+#pragma alloc_text(PAGE, NtfsCleanupCompressedMdlChain)
+#endif
+
+
+BOOLEAN
+NtfsCopyReadC (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ OUT PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ OUT PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached read bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy read
+ of a cached file object. For a complete description of the arguments
+ see CcCopyRead.
+
+Arguments:
+
+ FileObject - Pointer to the file object being read.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ MdlChain - Pointer to an MdlChain pointer to receive an Mdl to describe
+ the data in the cache.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+ CompressedDataInfo - Returns compressed data info with compressed chunk
+ sizes
+
+ CompressedDataInfoLength - Supplies the size of the info buffer in bytes.
+
+Return Value:
+
+ FALSE - if the data was not delivered for any reason
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ LONGLONG LocalOffset;
+ PFAST_IO_DISPATCH FastIoDispatch;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ FILE_COMPRESSION_INFORMATION CompressionInformation;
+ ULONG CompressionUnitSize, ChunkSize, CuCompressedSize;
+ BOOLEAN Status = TRUE;
+ BOOLEAN DoingIoAtEof = FALSE;
+
+ PAGED_CODE();
+
+ //
+ // You cannot have both a buffer to copy into and an MdlChain.
+ //
+
+ ASSERT((Buffer == NULL) || (MdlChain == NULL));
+
+ //
+ // Assume success.
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = Length;
+ CompressedDataInfo->NumberOfChunks = 0;
+
+ //
+ // Special case a read of zero length
+ //
+
+ if (Length != 0) {
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Make our best guess on whether we need the file exclusive
+ // or shared. Note that we do not check FileOffset->HighPart
+ // until below.
+ //
+
+ Status = ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+
+ //
+ // Now that the File is acquired shared, we can safely test if it
+ // is really cached and if we can do fast i/o and if not, then
+ // release the fcb and return.
+ //
+
+ if ((Header->FileObjectC == NULL) ||
+ (Header->FileObjectC->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible)) {
+
+ Status = FALSE;
+ goto Done;
+ }
+
+ //
+ // Get the address of the driver object's Fast I/O dispatch structure.
+ //
+
+ FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch;
+
+ //
+ // Get the compression information for this file and return those fields.
+ //
+
+ NtfsFastIoQueryCompressionInfo( FileObject, &CompressionInformation, IoStatus );
+ CompressedDataInfo->CompressionFormatAndEngine = CompressionInformation.CompressionFormat;
+ CompressedDataInfo->CompressionUnitShift = CompressionInformation.CompressionUnitShift;
+ CompressionUnitSize = 1 << CompressionInformation.CompressionUnitShift;
+ CompressedDataInfo->ChunkShift = CompressionInformation.ChunkShift;
+ CompressedDataInfo->ClusterShift = CompressionInformation.ClusterShift;
+ CompressedDataInfo->Reserved = 0;
+ ChunkSize = 1 << CompressionInformation.ChunkShift;
+
+ //
+ // If we either got an error in the call above, or the file size is less than
+ // one chunk, then return an error. (Could be an Ntfs resident attribute.)
+
+ if (!NT_SUCCESS(IoStatus->Status) || (Header->FileSize.QuadPart < ChunkSize)) {
+ Status = FALSE;
+ goto Done;
+ }
+
+ ASSERT((FileOffset->LowPart & (ChunkSize - 1)) == 0);
+
+ //
+ // If there is a normal cache section, flush that first, flushing integral
+ // compression units so we don't write them twice.
+ //
+
+ if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
+
+ LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1);
+
+ CcFlushCache( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&LocalOffset,
+ (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1),
+ NULL );
+ }
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we are reading beyond ValidDataLength. We have to
+ // do it now so that our reads are not nooped.
+ //
+
+ LocalOffset = FileOffset->QuadPart + (LONGLONG)Length;
+ if (LocalOffset > Header->ValidDataLength.QuadPart) {
+
+ //
+ // We must serialize with anyone else doing I/O at beyond
+ // ValidDataLength, and then remember if we need to declare
+ // when we are done.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are in fact beyond ValidDataLength.
+ //
+
+ if (DoingIoAtEof) {
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Check if fast I/O is questionable and if so then go ask the
+ // file system the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ ASSERT(!KeIsExecutingDpc());
+
+ //
+ // All file systems that set "Is Questionable" had better support
+ // fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the answer is
+ // anything other than GoForIt then we cannot take the fast I/O
+ // path.
+ //
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ FileOffset,
+ Length,
+ TRUE,
+ LockKey,
+ TRUE, // read operation
+ IoStatus,
+ DeviceObject )) {
+
+ //
+ // Fast I/O is not possible so release the Fcb and return.
+ //
+
+ Status = FALSE;
+ goto Done;
+ }
+ }
+
+ //
+ // Check for read past file size.
+ //
+
+ IoStatus->Information = Length;
+ if ( LocalOffset > Header->FileSize.QuadPart ) {
+
+ if ( FileOffset->QuadPart >= Header->FileSize.QuadPart ) {
+ IoStatus->Status = STATUS_END_OF_FILE;
+ IoStatus->Information = 0;
+
+ goto Done;
+ }
+
+ IoStatus->Information =
+ Length = (ULONG)( Header->FileSize.QuadPart - FileOffset->QuadPart );
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work and then
+ // release the fcb when we've done. If for whatever reason the
+ // copy read fails, then return FALSE to our caller.
+ //
+ // Also mark this as the top level "Irp" so that lower file system
+ // levels will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ IoStatus->Status = NtfsCompressedCopyRead( FileObject,
+ FileOffset,
+ Length,
+ Buffer,
+ MdlChain,
+ CompressedDataInfo,
+ CompressedDataInfoLength,
+ DeviceObject,
+ Header,
+ CompressionUnitSize,
+ ChunkSize );
+
+ Status = (BOOLEAN)NT_SUCCESS(IoStatus->Status);
+
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ Done: NOTHING;
+
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ //
+ // For the Mdl case, we must keep the resource.
+ //
+
+ if ((MdlChain == NULL) || !Status) {
+ ExReleaseResource( Header->PagingIoResource );
+ }
+
+ FsRtlExitFileSystem();
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCompressedCopyRead (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ OUT PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN ULONG CompressionUnitSize,
+ IN ULONG ChunkSize
+ )
+
+{
+ PFILE_OBJECT LocalFileObject;
+ PULONG NextReturnChunkSize;
+ PUCHAR CompressedBuffer, EndOfCompressedBuffer, ChunkBuffer;
+ LONGLONG LocalOffset;
+ ULONG CuCompressedSize;
+ PVOID MdlBuffer;
+ ULONG MdlLength;
+ BOOLEAN IsCompressed;
+ NTSTATUS Status = STATUS_SUCCESS;
+ PBCB Bcb = NULL;
+
+ UNREFERENCED_PARAMETER( CompressedDataInfoLength );
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ try {
+
+ //
+ // Get ready to loop through all of the compression units.
+ //
+
+ LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1);
+ Length = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1);
+
+ ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) +
+ (((Length >> CompressedDataInfo->ChunkShift) - 1) *
+ sizeof(ULONG))));
+
+ NextReturnChunkSize = &CompressedDataInfo->CompressedChunkSizes[0];
+
+ //
+ // Loop through desired compression units
+ //
+
+ while (TRUE) {
+
+ NtfsFastIoQueryCompressedSize( FileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ &CuCompressedSize );
+
+ ASSERT( CuCompressedSize <= CompressionUnitSize );
+
+ IsCompressed = (BOOLEAN)((CuCompressedSize != CompressionUnitSize) &&
+ (CompressedDataInfo->CompressionFormatAndEngine != 0));
+
+ //
+ // Figure out which FileObject to use.
+ //
+
+ LocalFileObject = Header->FileObjectC;
+ if (!IsCompressed) {
+ if (FileObject->PrivateCacheMap == NULL) {
+ Status = STATUS_NOT_MAPPED_DATA;
+ goto Done;
+ }
+ LocalFileObject = FileObject;
+ }
+
+ //
+ // If the CompressionUnit is not allocated, we still have to
+ // pin a page to synchronize on this buffer. We reload the
+ // correct size below.
+ //
+
+ if (CuCompressedSize == 0) {
+ CuCompressedSize = PAGE_SIZE;
+ }
+
+ //
+ // Map the compression unit in the compressed or uncompressed
+ // stream.
+ //
+
+ CcPinRead( LocalFileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ CuCompressedSize,
+ TRUE,
+ &Bcb,
+ &CompressedBuffer );
+
+ //
+ // Now that the data is pinned (we are synchronized with the
+ // CompressionUnit), we have to get the size again since it could
+ // have changed.
+ //
+
+ if (IsCompressed) {
+
+ NtfsFastIoQueryCompressedSize( FileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ &CuCompressedSize );
+
+ //
+ // In the extremely unlikely event that the compression state changed
+ // before we got the buffer pinned, just raise to get this request
+ // retried.
+ //
+
+ if (CuCompressedSize == CompressionUnitSize) {
+ ExRaiseStatus( STATUS_CANT_WAIT );
+ }
+ }
+
+ ASSERT( CuCompressedSize <= CompressionUnitSize );
+
+ IsCompressed = (BOOLEAN)((CuCompressedSize != CompressionUnitSize) &&
+ (CompressedDataInfo->CompressionFormatAndEngine != 0));
+
+ EndOfCompressedBuffer = Add2Ptr( CompressedBuffer, CuCompressedSize );
+
+ //
+ // Now loop through desired chunks
+ //
+
+ MdlLength = 0;
+
+ do {
+
+ //
+ // Assume current chunk does not compress, else get current
+ // chunk size.
+ //
+
+ if (IsCompressed) {
+ Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine,
+ &CompressedBuffer,
+ EndOfCompressedBuffer,
+ &ChunkBuffer,
+ NextReturnChunkSize );
+
+ if (!NT_SUCCESS(Status) && (Status != STATUS_NO_MORE_ENTRIES)) {
+ ExRaiseStatus(Status);
+ }
+
+ //
+ // If the file is not compressed, we have to fill in
+ // the appropriate chunk size and buffer, and advance
+ // CompressedBuffer.
+ //
+
+ } else {
+ *NextReturnChunkSize = ChunkSize;
+ ChunkBuffer = CompressedBuffer;
+ CompressedBuffer = Add2Ptr( CompressedBuffer, ChunkSize );
+ }
+ Status = STATUS_SUCCESS;
+
+ //
+ // We may not have reached the first chunk yet.
+ //
+
+ if (LocalOffset >= FileOffset->QuadPart) {
+
+ if (MdlChain != NULL) {
+
+ //
+ // If we have not started remembering an Mdl buffer,
+ // then do so now.
+ //
+
+ if (MdlLength == 0) {
+
+ MdlBuffer = ChunkBuffer;
+
+ //
+ // Otherwise we just have to increase the length
+ // and check for an uncompressed chunk, because that
+ // forces us to emit the previous Mdl since we do
+ // not transmit the chunk header in this case.
+ //
+
+ } else {
+
+ //
+ // In the rare case that we hit an individual chunk
+ // that did not compress, we have to emit what we
+ // had (which captures the Bcb pointer), and start
+ // a new Mdl buffer.
+ //
+
+ if (*NextReturnChunkSize == ChunkSize) {
+
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess );
+ Bcb = NULL;
+ MdlBuffer = ChunkBuffer;
+ MdlLength = 0;
+ }
+ }
+
+ MdlLength += *NextReturnChunkSize;
+
+ //
+ // Else copy next chunk (compressed or not).
+ //
+
+ } else {
+
+ //
+ // Copy next chunk (compressed or not).
+ //
+
+ RtlCopyBytes( Buffer,
+ ChunkBuffer,
+ (IsCompressed || (Length >= *NextReturnChunkSize)) ?
+ *NextReturnChunkSize : Length );
+
+ //
+ // Advance output buffer by bytes copied.
+ //
+
+ Buffer = (PCHAR)Buffer + *NextReturnChunkSize;
+ }
+
+ NextReturnChunkSize += 1;
+ CompressedDataInfo->NumberOfChunks += 1;
+ }
+
+ //
+ // Reduce length by chunk copied, and check if we are done.
+ //
+
+ if (Length > ChunkSize) {
+ Length -= ChunkSize;
+ } else {
+ goto Done;
+ }
+
+ LocalOffset += ChunkSize;
+
+ } while ((LocalOffset & (CompressionUnitSize - 1)) != 0);
+
+
+ //
+ // If this is an Mdl call, then it is time to add to the MdlChain
+ // before moving to the next compression unit.
+ //
+
+ if (MdlLength != 0) {
+
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess );
+ MdlLength = 0;
+
+ //
+ // Otherwise if there is still a Bcb, unpin it
+ //
+
+ } else if (Bcb != NULL) {
+
+ CcUnpinData(Bcb);
+ }
+
+ Bcb = NULL;
+ }
+
+ Done:
+
+ FileObject->Flags |= FO_FILE_FAST_IO_READ;
+
+ if ((MdlLength != 0) && NT_SUCCESS(Status)) {
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoReadAccess );
+ Bcb = NULL;
+ }
+
+ } except( FsRtlIsNtstatusExpected(Status = GetExceptionCode())
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ NOTHING;
+ }
+
+ //
+ // Unpin the Bcb if we still have it.
+ //
+
+ if (Bcb != NULL) {
+ CcUnpinData(Bcb);
+ }
+
+ //
+ // On error, cleanup any MdlChain we built up
+ //
+
+ if (!NT_SUCCESS(Status) && (MdlChain != NULL)) {
+
+ NtfsCleanupCompressedMdlChain( MdlChain, TRUE );
+
+ //
+ // Change owner Id for the Scb and Bcbs we are holding.
+ //
+
+ } else {
+
+ NtfsSetMdlBcbOwners( *MdlChain );
+ ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) );
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsMdlReadCompleteCompressed (
+ IN struct _FILE_OBJECT *FileObject,
+ IN PMDL MdlChain,
+ IN struct _DEVICE_OBJECT *DeviceObject
+ )
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ NtfsCleanupCompressedMdlChain( &MdlChain, FALSE );
+
+ //
+ // Get a real pointer to the common fcb header, and release with
+ // the Id we used.
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+ ExReleaseResourceForThread( Header->PagingIoResource, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) );
+ return TRUE;
+}
+
+
+BOOLEAN
+NtfsCopyWriteC (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN ULONG LockKey,
+ IN PVOID Buffer,
+ OUT PMDL *MdlChain,
+ OUT PIO_STATUS_BLOCK IoStatus,
+ IN PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN ULONG CompressedDataInfoLength,
+ IN PDEVICE_OBJECT DeviceObject
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a fast cached write bypassing the usual file system
+ entry routine (i.e., without the Irp). It is used to do a copy write
+ of a cached file object. For a complete description of the arguments
+ see CcCopyWrite.
+
+Arguments:
+
+ FileObject - Pointer to the file object being write.
+
+ FileOffset - Byte offset in file for desired data.
+
+ Length - Length of desired data in bytes.
+
+ Buffer - Pointer to output buffer to which data should be copied.
+
+ MdlChain - Pointer to an MdlChain pointer to receive an Mdl to describe
+ where the data may be written in the cache.
+
+ IoStatus - Pointer to standard I/O status block to receive the status
+ for the transfer.
+
+ CompressedDataInfo - Returns compressed data info with compressed chunk
+ sizes
+
+ CompressedDataInfoLength - Supplies the size of the info buffer in bytes.
+
+Return Value:
+
+ FALSE - if there is an error.
+
+ TRUE - if the data is being delivered
+
+--*/
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+ EOF_WAIT_BLOCK EofWaitBlock;
+ FILE_COMPRESSION_INFORMATION CompressionInformation;
+ ULONG CompressionUnitSize, ChunkSize;
+ ULONG EngineMatches;
+ LARGE_INTEGER NewFileSize;
+ LARGE_INTEGER OldFileSize;
+ LONGLONG LocalOffset;
+ PFAST_IO_DISPATCH FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch;
+ ULONG DoingIoAtEof = FALSE;
+ BOOLEAN Status = TRUE;
+
+ UNREFERENCED_PARAMETER( CompressedDataInfoLength );
+
+ PAGED_CODE();
+
+ //
+ // You cannot have both a buffer to copy into and an MdlChain.
+ //
+
+ ASSERT((Buffer == NULL) || (MdlChain == NULL));
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+
+ //
+ // See if it is ok to handle this in the fast path.
+ //
+
+ if (CcCanIWrite( FileObject, Length, TRUE, FALSE ) &&
+ !FlagOn(FileObject->Flags, FO_WRITE_THROUGH) &&
+ CcCopyWriteWontFlush(FileObject, FileOffset, Length)) {
+
+ //
+ // Assume our transfer will work
+ //
+
+ IoStatus->Status = STATUS_SUCCESS;
+ IoStatus->Information = Length;
+ CompressedDataInfo->NumberOfChunks = 0;
+
+ //
+ // Special case the zero byte length
+ //
+
+ if (Length != 0) {
+
+ //
+ // Enter the file system
+ //
+
+ FsRtlEnterFileSystem();
+
+ //
+ // Calculate the compression unit and chunk sizes.
+ //
+
+ CompressionUnitSize = 1 << CompressedDataInfo->CompressionUnitShift;
+ ChunkSize = 1 << CompressedDataInfo->ChunkShift;
+
+ //
+ // If there is a normal cache section, flush that first, flushing integral
+ // compression units so we don't write them twice.
+ //
+ //
+
+ if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
+
+ ULONG FlushLength = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + CompressionUnitSize - 1) &
+ ~(CompressionUnitSize - 1);
+
+ LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1);
+
+ ExAcquireResourceExclusive( Header->PagingIoResource, TRUE );
+ CcFlushCache( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&LocalOffset,
+ FlushLength,
+ NULL );
+ CcPurgeCacheSection( FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&LocalOffset,
+ FlushLength,
+ FALSE );
+ ExReleaseResource( Header->PagingIoResource );
+ }
+
+ NewFileSize.QuadPart = FileOffset->QuadPart + Length;
+
+ //
+ // Prevent truncates by acquiring paging I/O
+ //
+
+ ExAcquireResourceShared( Header->PagingIoResource, TRUE );
+
+ //
+ // Get the compression information for this file and return those fields.
+ //
+
+ NtfsFastIoQueryCompressionInfo( FileObject, &CompressionInformation, IoStatus );
+
+ //
+ // See if the engine matches, so we can pass that on to the
+ // compressed write routine.
+ //
+
+ EngineMatches =
+ ((CompressedDataInfo->CompressionFormatAndEngine == CompressionInformation.CompressionFormat) &&
+ (CompressedDataInfo->CompressionUnitShift == CompressionInformation.CompressionUnitShift) &&
+ (CompressedDataInfo->ChunkShift == CompressionInformation.ChunkShift));
+
+ //
+ // If we either got an error in the call above, or the file size is less than
+ // one chunk, then return an error. (Could be an Ntfs resident attribute.)
+ //
+
+ if (!NT_SUCCESS(IoStatus->Status) || (Header->FileSize.QuadPart < ChunkSize)) {
+ goto ErrOut;
+ }
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped. Note we do not allow
+ // FileOffset to be WRITE_TO_EOF.
+ //
+
+ ASSERT((FileOffset->LowPart & (ChunkSize - 1)) == 0);
+
+ if (NewFileSize.QuadPart > Header->ValidDataLength.QuadPart) {
+
+ //
+ // We can change FileSize and ValidDataLength if either, no one
+ // else is now, or we are still extending after waiting.
+ //
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, FileOffset, Length, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Now calculate the new FileSize and see if we wrapped the
+ // 32-bit boundary.
+ //
+
+ NewFileSize.QuadPart = FileOffset->QuadPart + Length;
+
+ //
+ // Update Filesize now so that we do not truncate reads.
+ //
+
+ OldFileSize.QuadPart = Header->FileSize.QuadPart;
+ if (NewFileSize.QuadPart > Header->FileSize.QuadPart) {
+
+ //
+ // If we are beyond AllocationSize, go to ErrOut
+ //
+
+ if (NewFileSize.QuadPart > Header->AllocationSize.QuadPart) {
+ ExReleaseFastMutex( Header->FastMutex );
+ goto ErrOut;
+ } else {
+ Header->FileSize.QuadPart = NewFileSize.QuadPart;
+ }
+ }
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Now that the File is acquired shared, we can safely test if it
+ // is really cached and if we can do fast i/o and if not, then
+ // release the fcb and return.
+ //
+ // Note, we do not want to call CcZeroData here,
+ // but rather defer zeroing to the file system, due to
+ // the need for exclusive resource acquisition. Therefore
+ // we get out if we are beyond ValidDataLength.
+ //
+
+ if ((Header->FileObjectC == NULL) ||
+ (Header->FileObjectC->PrivateCacheMap == NULL) ||
+ (Header->IsFastIoPossible == FastIoIsNotPossible) ||
+ (FileOffset->QuadPart > Header->ValidDataLength.QuadPart)) {
+
+ goto ErrOut;
+ }
+
+ //
+ // Check if fast I/O is questionable and if so then go ask
+ // the file system the answer
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsQuestionable) {
+
+ FastIoDispatch = DeviceObject->DriverObject->FastIoDispatch;
+
+ //
+ // All file system then set "Is Questionable" had better
+ // support fast I/O
+ //
+
+ ASSERT(FastIoDispatch != NULL);
+ ASSERT(FastIoDispatch->FastIoCheckIfPossible != NULL);
+
+ //
+ // Call the file system to check for fast I/O. If the
+ // answer is anything other than GoForIt then we cannot
+ // take the fast I/O path.
+ //
+
+
+ if (!FastIoDispatch->FastIoCheckIfPossible( FileObject,
+ FileOffset,
+ Length,
+ TRUE,
+ LockKey,
+ FALSE, // write operation
+ IoStatus,
+ DeviceObject )) {
+
+ //
+ // Fast I/O is not possible so cleanup and return.
+ //
+
+ goto ErrOut;
+ }
+ }
+
+ //
+ // We can do fast i/o so call the cc routine to do the work
+ // and then release the fcb when we've done. If for whatever
+ // reason the copy write fails, then return FALSE to our
+ // caller.
+ //
+ // Also mark this as the top level "Irp" so that lower file
+ // system levels will not attempt a pop-up
+ //
+
+ PsGetCurrentThread()->TopLevelIrp = FSRTL_FAST_IO_TOP_LEVEL_IRP;
+
+ ASSERT(CompressedDataInfoLength >= (sizeof(COMPRESSED_DATA_INFO) +
+ (((Length >> CompressedDataInfo->ChunkShift) - 1) *
+ sizeof(ULONG))));
+
+ Status = (BOOLEAN)NT_SUCCESS(NtfsCompressedCopyWrite( FileObject,
+ FileOffset,
+ Length,
+ Buffer,
+ MdlChain,
+ CompressedDataInfo,
+ DeviceObject,
+ Header,
+ CompressionUnitSize,
+ ChunkSize,
+ EngineMatches ));
+
+ PsGetCurrentThread()->TopLevelIrp = 0;
+
+ //
+ // If we succeeded, see if we have to update FileSize ValidDataLength.
+ //
+
+ if (Status) {
+
+ //
+ // Set this handle as having modified the file
+ //
+
+ FileObject->Flags |= FO_FILE_MODIFIED;
+
+ if (DoingIoAtEof) {
+
+ //
+ // Make sure Cc knows the current FileSize, as set above,
+ // (we may not have changed it).
+ //
+
+ CcGetFileSizePointer(FileObject)->QuadPart = Header->FileSize.QuadPart;
+
+ ExAcquireFastMutex( Header->FastMutex );
+ FileObject->Flags |= FO_FILE_SIZE_CHANGED;
+ Header->ValidDataLength = NewFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ goto Done1;
+ }
+
+ ErrOut: NOTHING;
+
+ Status = FALSE;
+ if (DoingIoAtEof) {
+ ExAcquireFastMutex( Header->FastMutex );
+ Header->FileSize = OldFileSize;
+ NtfsFinishIoAtEof( Header );
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ Done1: NOTHING;
+
+ //
+ // For the Mdl case, we must keep the resource.
+ //
+
+ if ((MdlChain == NULL) || !Status) {
+ ExReleaseResource( Header->PagingIoResource );
+ }
+
+ FsRtlExitFileSystem();
+ }
+
+ } else {
+
+ //
+ // We could not do the I/O now.
+ //
+
+ Status = FALSE;
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCompressedCopyWrite (
+ IN PFILE_OBJECT FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN ULONG Length,
+ IN PVOID Buffer,
+ OUT PMDL *MdlChain,
+ IN PCOMPRESSED_DATA_INFO CompressedDataInfo,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PFSRTL_ADVANCED_FCB_HEADER Header,
+ IN ULONG CompressionUnitSize,
+ IN ULONG ChunkSize,
+ IN ULONG EngineMatches
+ )
+
+{
+ LONGLONG LocalOffset;
+ ULONG CuCompressedSize, SizeToPin;
+ PULONG NextChunkSize, TempChunkSize;
+ PUCHAR CacheBuffer, EndOfCacheBuffer, ChunkBuffer, SavedBuffer;
+ ULONG SavedLength;
+ ULONG ClusterSize;
+ PVOID MdlBuffer;
+ ULONG MdlLength;
+ BOOLEAN IsCompressed;
+ NTSTATUS Status = STATUS_SUCCESS;
+ PBCB Bcb = NULL;
+ BOOLEAN FullOverwrite = FALSE;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+
+ try {
+
+ //
+ // Get ready to loop through all of the compression units.
+ //
+
+ LocalOffset = FileOffset->QuadPart & ~(LONGLONG)(CompressionUnitSize - 1);
+ Length = (Length + (ULONG)(FileOffset->QuadPart - LocalOffset) + ChunkSize - 1) & ~(ChunkSize - 1);
+ ClusterSize = 1 << CompressedDataInfo->ClusterShift;
+
+ NextChunkSize = &CompressedDataInfo->CompressedChunkSizes[0];
+
+ //
+ // Loop through desired compression units
+ //
+
+ while (TRUE) {
+
+ //
+ // Determine whether or not this is a full overwrite of a
+ // compression unit.
+ //
+
+ FullOverwrite = (LocalOffset >= Header->ValidDataLength.QuadPart)
+
+ ||
+
+ ((LocalOffset >= FileOffset->QuadPart) &&
+ (Length >= CompressionUnitSize));
+
+
+ //
+ // Calculate how much of current compression unit is being
+ // written, uncompressed.
+ //
+
+ SavedLength = Length;
+ if (SavedLength >= CompressionUnitSize) {
+ SavedLength = CompressionUnitSize;
+ }
+ if (LocalOffset < FileOffset->QuadPart) {
+ SavedLength -= (ULONG)(FileOffset->QuadPart - LocalOffset);
+ }
+
+ //
+ // Loop to calculate sum of chunk sizes being written.
+ //
+
+ SizeToPin = 0;
+ for (TempChunkSize = NextChunkSize;
+ TempChunkSize < (NextChunkSize + (SavedLength >> CompressedDataInfo->ChunkShift));
+ TempChunkSize++ ) {
+
+ SizeToPin += *TempChunkSize;
+ }
+
+ //
+ // If this is not a full overwrite, get the current compression unit
+ // size and make sure we pin at least that much.
+ //
+
+ if (!FullOverwrite) {
+
+ NtfsFastIoQueryCompressedSize( FileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ &CuCompressedSize );
+
+ ASSERT( CuCompressedSize <= CompressionUnitSize );
+
+ if (CuCompressedSize > SizeToPin) {
+ SizeToPin = CuCompressedSize;
+ }
+ }
+
+ //
+ // Possibly neither the new nor old data for this CompressionUnit is
+ // nonzero.
+ //
+
+ if (SizeToPin != 0) {
+
+ //
+ // At this point we are ready to overwrite data in the compression
+ // unit. See if the data is really compressed.
+ //
+
+ IsCompressed = (BOOLEAN)(((FullOverwrite && (SizeToPin <= (CompressionUnitSize - ClusterSize))) ||
+ (CuCompressedSize != CompressionUnitSize)) &&
+ EngineMatches);
+ Status = STATUS_SUCCESS;
+
+ //
+ // Save current length in case we have to restart our work in
+ // the uncompressed stream.
+ //
+
+ TempChunkSize = NextChunkSize;
+ SavedLength = Length;
+ SavedBuffer = Buffer;
+
+ if (IsCompressed) {
+
+ //
+ // Map the compression unit in the compressed stream.
+ //
+
+ if (FullOverwrite) {
+
+ //
+ // If we are overwriting the entire compression unit, then
+ // call CcPreparePinWrite so that empty pages may be used
+ // instead of reading the file. Also force the byte count
+ // to integral pages, so no one thinks we need to read the
+ // last page.
+ //
+
+ CcPreparePinWrite( Header->FileObjectC,
+ (PLARGE_INTEGER)&LocalOffset,
+ (SizeToPin + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1),
+ FALSE,
+ 3, // Wait + acquire resource exclusive!
+ &Bcb,
+ &CacheBuffer );
+
+ //
+ // If it is a full overwrite, we need to initialize an empty
+ // buffer. **** This is not completely correct, we otherwise
+ // need a routine to initialize an empty compressed data buffer.
+ //
+
+ *(PULONG)CacheBuffer = 0;
+
+ } else {
+
+ CcPinRead( Header->FileObjectC,
+ (PLARGE_INTEGER)&LocalOffset,
+ SizeToPin,
+ 3, // Wait + acquire resource exclusive!
+ &Bcb,
+ &CacheBuffer );
+
+ CcSetDirtyPinnedData( Bcb, NULL );
+
+ //
+ // Now that the data is pinned (we are synchronized with the
+ // CompressionUnit), we have to get the size again since it could
+ // have changed.
+ //
+
+ NtfsFastIoQueryCompressedSize( FileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ &CuCompressedSize );
+
+ IsCompressed = (CuCompressedSize != CompressionUnitSize);
+
+ ASSERT( CuCompressedSize <= CompressionUnitSize );
+ }
+
+ EndOfCacheBuffer = Add2Ptr( CacheBuffer, CompressionUnitSize - ClusterSize );
+ MdlLength = 0;
+
+ //
+ // Now loop through desired chunks (if it is still compressed)
+ //
+
+ if (IsCompressed) do {
+
+ //
+ // We may not have reached the first chunk yet.
+ //
+
+ if (LocalOffset >= FileOffset->QuadPart) {
+
+ //
+ // Reserve space for the current chunk.
+ //
+
+ Status = RtlReserveChunk( CompressedDataInfo->CompressionFormatAndEngine,
+ &CacheBuffer,
+ EndOfCacheBuffer,
+ &ChunkBuffer,
+ *TempChunkSize );
+
+ if (!NT_SUCCESS(Status)) {
+ break;
+ }
+
+ //
+ // If the caller wants an MdlChain, then handle the Mdl
+ // processing here.
+ //
+
+ if (MdlChain != NULL) {
+
+ //
+ // If we have not started remembering an Mdl buffer,
+ // then do so now.
+ //
+
+ if (MdlLength == 0) {
+
+ MdlBuffer = ChunkBuffer;
+
+ //
+ // Otherwise we just have to increase the length
+ // and check for an uncompressed chunk, because that
+ // forces us to emit the previous Mdl since we do
+ // not transmit the chunk header in this case.
+ //
+
+ } else {
+
+ //
+ // In the rare case that we hit an individual chunk
+ // that did not compress, we have to emit what we
+ // had (which captures the Bcb pointer), and start
+ // a new Mdl buffer.
+ //
+
+ if (*TempChunkSize == ChunkSize) {
+
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess );
+ Bcb = NULL;
+ MdlBuffer = ChunkBuffer;
+ MdlLength = 0;
+ }
+ }
+
+ MdlLength += *TempChunkSize;
+
+ //
+ // Else copy next chunk (compressed or not).
+ //
+
+ } else {
+
+ RtlCopyBytes( ChunkBuffer, Buffer, *TempChunkSize );
+
+ //
+ // Advance input buffer by bytes copied.
+ //
+
+ Buffer = (PCHAR)Buffer + *TempChunkSize;
+ }
+
+ TempChunkSize += 1;
+
+ //
+ // Reduce length by chunk copied, and check if we are done.
+ //
+
+ if (Length > ChunkSize) {
+ Length -= ChunkSize;
+ } else {
+ goto Done;
+ }
+
+ //
+ // If we are skipping over a nonexistant chunk, then we have
+ // to reserve a chunk of zeros.
+ //
+
+ } else {
+
+ //
+ // If we have not reached our chunk, then describe the current
+ // chunke in order to skip over it.
+ //
+
+ Status = RtlDescribeChunk( CompressedDataInfo->CompressionFormatAndEngine,
+ &CacheBuffer,
+ EndOfCacheBuffer,
+ &ChunkBuffer,
+ TempChunkSize );
+
+ //
+ // If there is not current chunk, we must insert a chunk of zeros.
+ //
+
+ if (Status == STATUS_NO_MORE_ENTRIES) {
+
+ Status = RtlReserveChunk( CompressedDataInfo->CompressionFormatAndEngine,
+ &CacheBuffer,
+ EndOfCacheBuffer,
+ &ChunkBuffer,
+ 0 );
+
+ if (!NT_SUCCESS(Status)) {
+ ASSERT(NT_SUCCESS(Status));
+ break;
+ }
+
+ //
+ // Get out if we got some other kind of unexpected error.
+ //
+
+ } else if (!NT_SUCCESS(Status)) {
+ ASSERT(NT_SUCCESS(Status));
+ break;
+ }
+ }
+
+ LocalOffset += ChunkSize;
+
+ } while ((LocalOffset & (CompressionUnitSize - 1)) != 0);
+
+ //
+ // If this is an Mdl call, then it is time to add to the MdlChain
+ // before moving to the next view.
+ //
+
+ if (MdlLength != 0) {
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess );
+ Bcb = NULL;
+ MdlLength = 0;
+ }
+ }
+
+ //
+ // Uncompressed loop.
+ //
+
+ if (!IsCompressed || !NT_SUCCESS(Status)) {
+
+ //
+ // If we get here for an Mdl request, just tell him to send
+ // it uncompressed!
+ //
+
+ if (MdlChain != NULL) {
+ if (NT_SUCCESS(Status)) {
+ Status = STATUS_BUFFER_OVERFLOW;
+ }
+ goto Done;
+
+ //
+ // If we are going to write the uncompressed stream,
+ // we have to make sure it is there.
+ //
+
+ } else if (FileObject->PrivateCacheMap == NULL) {
+ Status = STATUS_NOT_MAPPED_DATA;
+ goto Done;
+ }
+
+ //
+ // Restore sizes and pointers to the beginning of the
+ // current compression unit, and we will handle the
+ // data uncompressed.
+ //
+
+ LocalOffset -= SavedLength - Length;
+ Length = SavedLength;
+ Buffer = SavedBuffer;
+ TempChunkSize = NextChunkSize;
+
+ //
+ // We may have a Bcb from the above loop to unpin.
+ // Then we must flush and purge the compressed
+ // stream before proceding.
+ //
+
+ if (Bcb != NULL) {
+ CcUnpinData(Bcb);
+ Bcb = NULL;
+ }
+
+ //
+ // We must first flush and purge the compressed stream
+ // since we will be writing into the uncompressed stream.
+ // The flush is actually only necessary if we are not doing
+ // a full overwrite anyway.
+ //
+
+ if (!FullOverwrite) {
+ CcFlushCache( Header->FileObjectC->SectionObjectPointer,
+ (PLARGE_INTEGER)&LocalOffset,
+ CompressionUnitSize,
+ NULL );
+ }
+
+ CcPurgeCacheSection( Header->FileObjectC->SectionObjectPointer,
+ (PLARGE_INTEGER)&LocalOffset,
+ CompressionUnitSize,
+ FALSE );
+
+ //
+ // If LocalOffset was rounded down to a compression
+ // unit boundary (must have failed in the first
+ // compression unit), then start from the actual
+ // starting FileOffset.
+ //
+
+ if (LocalOffset < FileOffset->QuadPart) {
+ Length -= (ULONG)(FileOffset->QuadPart - LocalOffset);
+ LocalOffset = FileOffset->QuadPart;
+ }
+
+ //
+ // Map the compression unit in the uncompressed
+ // stream.
+ //
+
+ CcPinRead( FileObject,
+ (PLARGE_INTEGER)&LocalOffset,
+ (Length < CompressionUnitSize) ? Length : CompressionUnitSize,
+ TRUE,
+ &Bcb,
+ &CacheBuffer );
+
+ CcSetDirtyPinnedData( Bcb, NULL );
+
+ //
+ // Now loop through desired chunks
+ //
+
+ do {
+
+ //
+ // If this chunk is compressed, then decompress it
+ // into the cache.
+ //
+
+ if (*TempChunkSize != ChunkSize) {
+
+ Status = RtlDecompressBuffer( CompressedDataInfo->CompressionFormatAndEngine,
+ CacheBuffer,
+ ChunkSize,
+ Buffer,
+ *TempChunkSize,
+ &SavedLength );
+
+ //
+ // See if the data is ok.
+ //
+
+ if (!NT_SUCCESS(Status)) {
+ ASSERT(NT_SUCCESS(Status));
+ goto Done;
+ }
+
+ //
+ // Zero to the end of the chunk if it was not all there.
+ //
+
+ if (SavedLength != ChunkSize) {
+ RtlZeroMemory( Add2Ptr(CacheBuffer, SavedLength),
+ ChunkSize - SavedLength );
+ }
+
+ } else {
+
+ //
+ // Copy next chunk (it's not compressed).
+ //
+
+ RtlCopyBytes( CacheBuffer, Buffer, ChunkSize );
+ }
+
+ //
+ // Advance input buffer by bytes copied.
+ //
+
+ Buffer = (PCHAR)Buffer + *TempChunkSize;
+ CacheBuffer = (PCHAR)CacheBuffer + ChunkSize;
+ TempChunkSize += 1;
+
+ //
+ // Reduce length by chunk copied, and check if we are done.
+ //
+
+ if (Length > ChunkSize) {
+ Length -= ChunkSize;
+ } else {
+ goto Done;
+ }
+
+ LocalOffset += ChunkSize;
+
+ } while ((LocalOffset & (CompressionUnitSize - 1)) != 0);
+
+ CcUnpinData(Bcb);
+ Bcb = NULL;
+ }
+ }
+ }
+
+ Done: NOTHING;
+
+ if ((MdlLength != 0) && NT_SUCCESS(Status)) {
+ NtfsAddToCompressedMdlChain( MdlChain, MdlBuffer, MdlLength, Bcb, IoWriteAccess );
+ Bcb = NULL;
+ }
+
+ } except( FsRtlIsNtstatusExpected((Status = GetExceptionCode()))
+ ? EXCEPTION_EXECUTE_HANDLER
+ : EXCEPTION_CONTINUE_SEARCH ) {
+
+ NOTHING;
+ }
+
+ //
+ // Unpin the Bcb if we still have it.
+ //
+
+ if (Bcb != NULL) {
+ CcUnpinData(Bcb);
+ }
+
+ //
+ // On error, cleanup any MdlChain we built up
+ //
+
+ if (!NT_SUCCESS(Status) && (MdlChain != NULL)) {
+
+ NtfsCleanupCompressedMdlChain( MdlChain, TRUE );
+
+ //
+ // Change owner Id for the Scb and Bcbs we are holding.
+ //
+
+ } else {
+
+ NtfsSetMdlBcbOwners( *MdlChain );
+ ExSetResourceOwnerPointer( Header->PagingIoResource, (PVOID)((PCHAR)*MdlChain + 3) );
+ }
+
+ return Status;
+}
+
+
+BOOLEAN
+NtfsMdlWriteCompleteCompressed (
+ IN struct _FILE_OBJECT *FileObject,
+ IN PLARGE_INTEGER FileOffset,
+ IN PMDL MdlChain,
+ IN struct _DEVICE_OBJECT *DeviceObject
+ )
+
+{
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+ UNREFERENCED_PARAMETER( FileOffset );
+
+ NtfsCleanupCompressedMdlChain( &MdlChain, FALSE );
+
+ //
+ // Get a real pointer to the common fcb header
+ //
+
+ Header = (PFSRTL_ADVANCED_FCB_HEADER)FileObject->FsContext;
+ ExReleaseResourceForThread( Header->PagingIoResource, (ERESOURCE_THREAD)((PCHAR)MdlChain + 3) );
+ return TRUE;
+}
+
+
+VOID
+NtfsAddToCompressedMdlChain (
+ IN OUT PMDL *MdlChain,
+ IN PVOID MdlBuffer,
+ IN ULONG MdlLength,
+ IN PBCB Bcb,
+ IN LOCK_OPERATION Operation
+ )
+
+{
+ PMDL Mdl, MdlTemp;
+ ULONG SavedState;
+
+ ASSERT(sizeof(ULONG) == sizeof(PBCB));
+
+ //
+ // Now attempt to allocate an Mdl to describe the mapped data.
+ // We "lie" about the length of the buffer by one page, in order
+ // to get an extra field to store a pointer to the Bcb in.
+ //
+
+ Mdl = IoAllocateMdl( MdlBuffer,
+ (MdlLength + PAGE_SIZE),
+ FALSE,
+ FALSE,
+ NULL );
+
+ if (Mdl == NULL) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ //
+ // Now subtract out the space we reserved for our Bcb pointer
+ // and then store it.
+ //
+
+ Mdl->Size -= sizeof(ULONG);
+ Mdl->ByteCount -= PAGE_SIZE;
+ *(PBCB *)Add2Ptr(Mdl, Mdl->Size) = Bcb;
+
+ //
+ // Note that this probe should never fail, because we can
+ // trust the address returned from CcPinFileData. Therefore,
+ // if we succeed in allocating the Mdl above, we should
+ // manage to elude any expected exceptions through the end
+ // of this loop.
+ //
+
+ MmDisablePageFaultClustering(&SavedState);
+ MmProbeAndLockPages( Mdl, KernelMode, Operation );
+ MmEnablePageFaultClustering(SavedState);
+
+ //
+ // Now link the Mdl into the caller's chain
+ //
+
+ if ( *MdlChain == NULL ) {
+ *MdlChain = Mdl;
+ } else {
+ MdlTemp = CONTAINING_RECORD( *MdlChain, MDL, Next );
+ while (MdlTemp->Next != NULL) {
+ MdlTemp = MdlTemp->Next;
+ }
+ MdlTemp->Next = Mdl;
+ }
+}
+
+VOID
+NtfsSetMdlBcbOwners (
+ IN PMDL MdlChain
+ )
+
+{
+ PBCB Bcb;
+
+ while (MdlChain != NULL) {
+
+ //
+ // Unpin the Bcb we saved away, and restore the Mdl counts
+ // we altered.
+ //
+
+ Bcb = *(PBCB *)Add2Ptr(MdlChain, MdlChain->Size);
+ CcSetBcbOwnerPointer( Bcb, (PVOID)((PCHAR)MdlChain + 3) );
+
+ MdlChain = MdlChain->Next;
+ }
+}
+
+VOID
+NtfsCleanupCompressedMdlChain (
+ IN OUT PMDL *MdlChain,
+ IN ULONG Error
+ )
+
+{
+ PMDL MdlTemp;
+ PBCB Bcb;
+
+ while (*MdlChain != NULL) {
+
+ //
+ // Save a pointer to the next guy in the chain.
+ //
+
+ MdlTemp = (*MdlChain)->Next;
+
+ //
+ // Unlock the pages.
+ //
+
+ MmUnlockPages( *MdlChain );
+
+ //
+ // Unpin the Bcb we saved away, and restore the Mdl counts
+ // we altered.
+ //
+
+ Bcb = *(PBCB *)Add2Ptr((*MdlChain), (*MdlChain)->Size);
+ if (Bcb != NULL) {
+ if (Error) {
+ CcUnpinData( Bcb );
+ } else {
+ CcUnpinDataForThread( Bcb, (ERESOURCE_THREAD)((PCHAR)*MdlChain + 3) );
+ }
+ }
+
+ (*MdlChain)->Size += sizeof(ULONG);
+ (*MdlChain)->ByteCount += PAGE_SIZE;
+
+ IoFreeMdl( *MdlChain );
+
+ *MdlChain = MdlTemp;
+ }
+}
+
+
diff --git a/private/ntos/cntfs/secursup.c b/private/ntos/cntfs/secursup.c
new file mode 100644
index 000000000..ad02c9e81
--- /dev/null
+++ b/private/ntos/cntfs/secursup.c
@@ -0,0 +1,3847 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ SecurSup.c
+
+Abstract:
+
+ This module implements the Ntfs Security Support routines
+
+Author:
+
+ Gary Kimura [GaryKi] 27-Dec-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+#define Dbg (DEBUG_TRACE_SECURSUP)
+#define DbgAcl (DEBUG_TRACE_SECURSUP | DEBUG_TRACE_ACLINDEX)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('SFtN')
+
+UNICODE_STRING FileString = CONSTANT_UNICODE_STRING( L"File" );
+
+//
+// Local procedure prototypes
+//
+
+VOID
+NtfsLoadSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL
+ );
+
+VOID
+NtfsStoreSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN LogIt
+ );
+
+#ifdef _CAIRO_
+PSHARED_SECURITY
+NtOfsFindCachedSharedSecurityBySecurityId (
+ IN PVCB Vcb,
+ IN SECURITY_ID SecurityId
+ );
+
+PSHARED_SECURITY
+NtOfsFindCachedSharedSecurityByHash (
+ IN PVCB Vcb,
+ IN PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN ULONG SecurityDescriptorLength,
+ IN ULONG Hash
+ );
+
+VOID
+NtOfsAddCachedSharedSecurity (
+ IN PVCB Vcb,
+ PSHARED_SECURITY SharedSecurity
+ );
+
+VOID
+NtOfsMapSecurityIdToSecurityDescriptor (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN SECURITY_ID SecurityId,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor,
+ OUT PULONG SecurityDescriptorLength,
+ OUT PBCB *Bcb
+ );
+
+NTSTATUS
+NtOfsMatchSecurityHash (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ );
+
+VOID
+NtOfsLookupSecurityDescriptorInIndex (
+ PIRP_CONTEXT IrpContext,
+ IN OUT PSHARED_SECURITY SharedSecurity
+ );
+
+SECURITY_ID
+NtOfsGetSecurityIdFromSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN OUT PSHARED_SECURITY SharedSecurity
+ );
+#endif // _CAIRO_
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsAccessCheck)
+#pragma alloc_text(PAGE, NtfsAssignSecurity)
+#pragma alloc_text(PAGE, NtfsCheckFileForDelete)
+#pragma alloc_text(PAGE, NtfsCheckIndexForAddOrDelete)
+#pragma alloc_text(PAGE, NtfsDereferenceSharedSecurity)
+#pragma alloc_text(PAGE, NtfsLoadSecurityDescriptor)
+#pragma alloc_text(PAGE, NtfsModifySecurity)
+#pragma alloc_text(PAGE, NtfsNotifyTraverseCheck)
+#pragma alloc_text(PAGE, NtfsQuerySecurity)
+#pragma alloc_text(PAGE, NtfsStoreSecurityDescriptor)
+#ifdef _CAIRO_
+#pragma alloc_text(PAGE, NtfsInitializeSecurity)
+#pragma alloc_text(PAGE, NtfsLoadSecurityDescriptorById)
+#pragma alloc_text(PAGE, NtOfsFindCachedSharedSecurityBySecurityId)
+#pragma alloc_text(PAGE, NtOfsFindCachedSharedSecurityByHash)
+#pragma alloc_text(PAGE, NtOfsAddCachedSharedSecurity)
+#pragma alloc_text(PAGE, NtOfsPurgeSecurityCache)
+#pragma alloc_text(PAGE, NtOfsMapSecurityIdToSecurityDescriptor)
+#pragma alloc_text(PAGE, NtOfsMatchSecurityHash)
+#pragma alloc_text(PAGE, NtOfsLookupSecurityDescriptorInIndex)
+#pragma alloc_text(PAGE, NtOfsGetSecurityIdFromSecurityDescriptor)
+#pragma alloc_text(PAGE, NtOfsCollateSecurityHash)
+#endif // _CAIRO_
+#endif
+
+
+VOID
+NtfsAssignSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ParentFcb,
+ IN PIRP Irp,
+ IN PFCB NewFcb,
+ IN PFILE_RECORD_SEGMENT_HEADER FileRecord, // BUGBUG delete
+ IN PBCB FileRecordBcb, // BUGBUG delete
+ IN LONGLONG FileOffset, // BUGBUG delete
+ IN OUT PBOOLEAN LogIt // BUGBUG delete
+ )
+
+/*++
+
+Routine Description:
+
+ This routine constructs and assigns a new security descriptor to the
+ specified file/directory. The new security descriptor is placed both
+ on the fcb and on the disk.
+
+ This will only be called in the context of an open/create operation.
+ It currently MUST NOT be called to store a security descriptor for
+ an existing file, because it instructs NtfsStoreSecurityDescriptor
+ to not log the change.
+
+ If this is a large security descriptor then it is possible that
+ AllocateClusters may be called twice within the call to AddAllocation
+ when the attribute is created. If so then the second call will always
+ log the changes. In that case we need to log all of the operations to
+ create this security attribute and also we must log the current state
+ of the file record.
+
+ It is possible that our caller has already started logging operations against
+ this log record. In that case we always log the security changes.
+
+Arguments:
+
+ ParentFcb - Supplies the directory under which the new fcb exists
+
+ Irp - Supplies the Irp being processed
+
+ NewFcb - Supplies the fcb that is being assigned a new security descriptor
+
+ FileRecord - Supplies the file record for this operation. Used if we
+ have to log against the file record.
+
+ FileRecordBcb - Bcb for the file record above.
+
+ FileOffset - File offset in the Mft for this file record.
+
+ LogIt - On entry this indicates whether our caller wants this operation
+ logged. On exit we return TRUE if we logged the security change.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+
+ NTSTATUS Status;
+ BOOLEAN IsDirectory;
+ PACCESS_STATE AccessState;
+ PIO_STACK_LOCATION IrpSp;
+ ULONG SecurityDescLength;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( ParentFcb );
+ ASSERT_IRP( Irp );
+ ASSERT_FCB( NewFcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAssignSecurity...\n") );
+
+ //
+ // First decide if we are creating a file or a directory
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation(Irp);
+ if (FlagOn(IrpSp->Parameters.Create.Options, FILE_DIRECTORY_FILE)) {
+
+ IsDirectory = TRUE;
+
+ } else {
+
+ IsDirectory = FALSE;
+ }
+
+ //
+ // Extract the parts of the Irp that we need to do our assignment
+ //
+
+ AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
+
+ //
+ // Check if we need to load the security descriptor for the parent.
+ //
+
+ if (ParentFcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, ParentFcb, NULL );
+ }
+
+ ASSERT( ParentFcb->SharedSecurity != NULL );
+
+ //
+ // Create a new security descriptor for the file and raise if there is
+ // an error
+ //
+
+ if (!NT_SUCCESS( Status = SeAssignSecurity( &ParentFcb->SharedSecurity->SecurityDescriptor,
+ AccessState->SecurityDescriptor,
+ &SecurityDescriptor,
+ IsDirectory,
+ &AccessState->SubjectSecurityContext,
+ IoGetFileObjectGenericMapping(),
+ PagedPool ))) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // Load the security descriptor into the Fcb
+ //
+
+ SecurityDescLength = RtlLengthSecurityDescriptor( SecurityDescriptor );
+
+ //
+ // Make sure the length is non-zero.
+ //
+
+ if (SecurityDescLength == 0) {
+
+ SeDeassignSecurity( &SecurityDescriptor );
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ ASSERT( SeValidSecurityDescriptor( SecurityDescLength, SecurityDescriptor ));
+
+ NtfsUpdateFcbSecurity( IrpContext,
+ NewFcb,
+ ParentFcb,
+#ifdef _CAIRO_
+ SECURITY_ID_INVALID,
+#endif // _CAIRO_
+ SecurityDescriptor,
+ SecurityDescLength );
+
+ //
+ // Free the security descriptor created by Se
+ //
+
+ if (!NT_SUCCESS( Status = SeDeassignSecurity( &SecurityDescriptor ))) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // BUGBUG begin section to delete when all volumes are cairo
+ //
+
+#ifdef _CAIRO_
+ if (NewFcb->Vcb->SecurityDescriptorStream == NULL) {
+#endif
+ //
+ // If the security descriptor is large enough that it may cause us to
+ // start logging in the StoreSecurity call below then make sure everything
+ // is logged.
+ //
+
+ if (!(LogIt) &&
+ (SecurityDescLength > BytesFromClusters( NewFcb->Vcb, MAXIMUM_RUNS_AT_ONCE ))) {
+
+ //
+ // Log the current state of the file record.
+ //
+
+ FileRecord->Lsn = NtfsWriteLog( IrpContext,
+ NewFcb->Vcb->MftScb,
+ FileRecordBcb,
+ InitializeFileRecordSegment,
+ FileRecord,
+ FileRecord->FirstFreeByte,
+ Noop,
+ NULL,
+ 0,
+ FileOffset,
+ 0,
+ 0,
+ NewFcb->Vcb->BytesPerFileRecordSegment );
+
+ *LogIt = TRUE;
+ }
+#ifdef _CAIRO_
+ }
+#endif // _CAIRO_
+
+ //
+ // BUGBUG end section to delete when all volumes are cairo
+ //
+
+ //
+ // Write out the new security descriptor
+ //
+
+ NtfsStoreSecurityDescriptor( IrpContext, NewFcb, *LogIt );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsAssignSecurity -> VOID\n") );
+
+ return;
+}
+
+
+NTSTATUS
+NtfsModifySecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_INFORMATION SecurityInformation,
+ OUT PSECURITY_DESCRIPTOR SecurityDescriptor
+ )
+
+/*++
+
+Routine Description:
+
+ This routine modifies an existing security descriptor for a file/directory.
+
+Arguments:
+
+ Fcb - Supplies the Fcb whose security is being modified
+
+ SecurityInformation - Supplies the security information structure passed to
+ the file system by the I/O system.
+
+ SecurityDescriptor - Supplies the security information structure passed to
+ the file system by the I/O system.
+
+Return Value:
+
+ NTSTATUS - Returns an appropriate status value for the function results
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSECURITY_DESCRIPTOR DescriptorPtr;
+ ULONG DescriptorLength;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsModifySecurity...\n") );
+
+ //
+ // First check if we need to load the security descriptor for the file
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, Fcb, NULL );
+ }
+
+ ASSERT( Fcb->SharedSecurity != NULL);
+
+ DescriptorPtr = &Fcb->SharedSecurity->SecurityDescriptor;
+
+ //
+ // Do the modify operation. SeSetSecurityDescriptorInfo no longer
+ // frees the passed security descriptor.
+ //
+
+ if (!NT_SUCCESS( Status = SeSetSecurityDescriptorInfo( NULL,
+ SecurityInformation,
+ SecurityDescriptor,
+ &DescriptorPtr,
+ PagedPool,
+ IoGetFileObjectGenericMapping() ))) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ DescriptorLength = RtlLengthSecurityDescriptor( DescriptorPtr );
+
+ //
+ // Check for a zero length.
+ //
+
+ if (DescriptorLength == 0) {
+
+ SeDeassignSecurity( &DescriptorPtr );
+ NtfsRaiseStatus( IrpContext, STATUS_INVALID_PARAMETER, NULL, NULL );
+ }
+
+ //
+ // Update the move the quota to the new owner if necessary.
+ //
+
+ NtfsMoveQuotaOwner( IrpContext, Fcb, DescriptorPtr );
+
+ NtfsAcquireFcbSecurity( Fcb->Vcb );
+
+ NtfsDereferenceSharedSecurity( Fcb );
+
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+
+ //
+ // Load the security descriptor into the Fcb
+ //
+
+ NtfsUpdateFcbSecurity( IrpContext,
+ Fcb,
+ NULL,
+#ifdef _CAIRO_
+ SECURITY_ID_INVALID,
+#endif // _CAIRO_
+ DescriptorPtr,
+ DescriptorLength );
+
+ //
+ // Free the security descriptor created by Se
+ //
+
+ if (!NT_SUCCESS( Status = SeDeassignSecurity( &DescriptorPtr ))) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ //
+ // Now we need to store the new security descriptor on disk
+ //
+
+ NtfsStoreSecurityDescriptor( IrpContext, Fcb, TRUE );
+
+ //
+ // Remember that we modified the security on the file.
+ //
+
+ SetFlag( Fcb->InfoFlags, FCB_INFO_MODIFIED_SECURITY );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsModifySecurity -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsQuerySecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSECURITY_INFORMATION SecurityInformation,
+ OUT PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN OUT PULONG SecurityDescriptorLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to query the contents of an existing security descriptor for
+ a file/directory.
+
+Arguments:
+
+ Fcb - Supplies the file/directory being queried
+
+ SecurityInformation - Supplies the security information structure passed to
+ the file system by the I/O system.
+
+ SecurityDescriptor - Supplies the security information structure passed to
+ the file system by the I/O system.
+
+ SecurityDescriptorLength - Supplies the length of the input security descriptor
+ buffer in bytes.
+
+Return Value:
+
+ NTSTATUS - Returns an appropriate status value for the function results
+
+--*/
+
+{
+ NTSTATUS Status;
+ PSECURITY_DESCRIPTOR LocalPointer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsQuerySecurity...\n") );
+
+ //
+ // First check if we need to load the security descriptor for the file
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, Fcb, NULL );
+ }
+
+ LocalPointer = &Fcb->SharedSecurity->SecurityDescriptor;
+
+ //
+ // Now with the security descriptor loaded do the query operation but
+ // protect ourselves with a exception handler just in case the caller's
+ // buffer isn't valid
+ //
+
+ try {
+
+ Status = SeQuerySecurityDescriptorInfo( SecurityInformation,
+ SecurityDescriptor,
+ SecurityDescriptorLength,
+ &LocalPointer );
+
+ } except(EXCEPTION_EXECUTE_HANDLER) {
+
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsQuerySecurity -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+#define NTFS_SE_CONTROL (((SE_DACL_PRESENT | SE_SELF_RELATIVE) << 16) | SECURITY_DESCRIPTOR_REVISION1)
+#define NTFS_DEFAULT_ACCESS_MASK 0x001f01ff
+
+ULONG NtfsWorldAclFile[] = {
+ 0x00000000, // Null Sacl
+ 0x00000014, // Dacl
+ 0x001c0002, // Acl header
+ 0x00000001, // One ACE
+ 0x00140000, // ACE Header
+ NTFS_DEFAULT_ACCESS_MASK,
+ 0x00000101, // World Sid
+ 0x01000000,
+ 0x00000000
+ };
+
+ULONG NtfsWorldAclDir[] = {
+ 0x00000000, // Null Sacl
+ 0x00000014, // Dacl
+ 0x00300002, // Acl header
+ 0x00000002, // Two ACEs
+ 0x00140000, // ACE Header
+ NTFS_DEFAULT_ACCESS_MASK,
+ 0x00000101, // World Sid
+ 0x01000000,
+ 0x00000000,
+ 0x00140b00, // ACE Header
+ NTFS_DEFAULT_ACCESS_MASK,
+ 0x00000101, // World Sid
+ 0x01000000,
+ 0x00000000
+ };
+
+VOID
+NtfsAccessCheck (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+ IN PIRP Irp,
+ IN ACCESS_MASK DesiredAccess,
+ IN BOOLEAN CheckOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine does a general access check for the indicated desired access.
+ This will only be called in the context of an open/create operation.
+
+ If access is granted then control is returned to the caller
+ otherwise this function will do the proper Nt security calls to log
+ the attempt and then raise an access denied status.
+
+Arguments:
+
+ Fcb - Supplies the file/directory being examined
+
+ ParentFcb - Optionally supplies the parent of the Fcb being examined
+
+ Irp - Supplies the Irp being processed
+
+ DesiredAccess - Supplies a mask of the access being requested
+
+ CheckOnly - Indicates if this operation is to check the desired access
+ only and not accumulate the access granted here. In this case we
+ are guaranteed that we have passed in a hard-wired desired access
+ and MAXIMUM_ALLOWED will not be one of them.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NTSTATUS Status;
+ NTSTATUS AccessStatus;
+ NTSTATUS AccessStatusError;
+
+ PACCESS_STATE AccessState;
+
+ PIO_STACK_LOCATION IrpSp;
+
+ BOOLEAN AccessGranted;
+ ACCESS_MASK GrantedAccess;
+ PISECURITY_DESCRIPTOR SecurityDescriptor;
+ PPRIVILEGE_SET Privileges;
+ PUNICODE_STRING FileName;
+ PUNICODE_STRING RelatedFileName;
+ PUNICODE_STRING PartialFileName;
+ UNICODE_STRING FullFileName;
+ PUNICODE_STRING DeviceObjectName;
+ USHORT DeviceObjectNameLength;
+ BOOLEAN LeadingSlash;
+ BOOLEAN RelatedFileNamePresent;
+ BOOLEAN PartialFileNamePresent;
+ BOOLEAN MaximumRequested;
+ BOOLEAN MaximumDeleteAcquired;
+ BOOLEAN MaximumReadAttrAcquired;
+ BOOLEAN PerformAccessValidation;
+ BOOLEAN PerformDeleteAudit;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsAccessCheck...\n") );
+
+ //
+ // First extract the parts of the Irp that we need to do our checking
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation(Irp);
+ AccessState = IrpSp->Parameters.Create.SecurityContext->AccessState;
+
+ //
+ // Check if we need to load the security descriptor for the file
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, Fcb, ParentFcb );
+ }
+
+ ASSERT( Fcb->SharedSecurity != NULL );
+
+ SecurityDescriptor = (PISECURITY_DESCRIPTOR) Fcb->SharedSecurity->SecurityDescriptor;
+
+ //
+ // Check to see if auditing is enabled and if this is the default world ACL.
+ //
+
+ if (*((PULONG) SecurityDescriptor) == NTFS_SE_CONTROL &&
+ !SeAuditingFileEvents( TRUE, SecurityDescriptor )) {
+
+ // Directories and files have different default ACLs.
+
+ if (((Fcb->Info.FileAttributes & DUP_FILE_NAME_INDEX_PRESENT) &&
+ RtlEqualMemory(
+ &SecurityDescriptor->Sacl,
+ NtfsWorldAclDir,
+ sizeof(NtfsWorldAclDir))) ||
+ RtlEqualMemory(
+ &SecurityDescriptor->Sacl,
+ NtfsWorldAclFile,
+ sizeof(NtfsWorldAclFile))) {
+ if (FlagOn( DesiredAccess, MAXIMUM_ALLOWED )) {
+ GrantedAccess = NTFS_DEFAULT_ACCESS_MASK;
+ } else {
+ GrantedAccess = DesiredAccess & NTFS_DEFAULT_ACCESS_MASK;
+ }
+
+ if (!CheckOnly) {
+
+ SetFlag( AccessState->PreviouslyGrantedAccess, GrantedAccess );
+ ClearFlag( AccessState->RemainingDesiredAccess, (GrantedAccess | MAXIMUM_ALLOWED) );
+ }
+
+ return;
+ }
+ }
+
+ Privileges = NULL;
+ FileName = NULL;
+ RelatedFileName = NULL;
+ PartialFileName = NULL;
+ DeviceObjectName = NULL;
+ MaximumRequested = FALSE;
+ MaximumDeleteAcquired = FALSE;
+ MaximumReadAttrAcquired = FALSE;
+ PerformAccessValidation = TRUE;
+ PerformDeleteAudit = FALSE;
+
+ //
+ // Check to see if we need to perform access validation
+ //
+
+ ClearFlag( DesiredAccess, AccessState->PreviouslyGrantedAccess );
+
+ if (DesiredAccess == 0) {
+
+ //
+ // Nothing to check, skip AVR and go straight to auditing
+ //
+
+ PerformAccessValidation = FALSE;
+ AccessGranted = TRUE;
+ }
+
+ //
+ // Remember the case where MAXIMUM_ALLOWED was requested.
+ //
+
+ if (FlagOn( DesiredAccess, MAXIMUM_ALLOWED )) {
+
+ MaximumRequested = TRUE;
+ }
+
+ if (FlagOn(IrpSp->Parameters.Create.SecurityContext->FullCreateOptions,FILE_DELETE_ON_CLOSE)) {
+ PerformDeleteAudit = TRUE;
+ }
+
+ //
+ // Lock the user context, do the access check and then unlock the context
+ //
+
+ SeLockSubjectContext( &AccessState->SubjectSecurityContext );
+
+ if (PerformAccessValidation) {
+
+ AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor,
+ &AccessState->SubjectSecurityContext,
+ TRUE, // Tokens are locked
+ DesiredAccess,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &GrantedAccess,
+ &AccessStatus );
+
+ if (Privileges != NULL) {
+
+ Status = SeAppendPrivileges( AccessState, Privileges );
+ SeFreePrivileges( Privileges );
+ }
+
+ if (AccessGranted) {
+
+ ClearFlag( DesiredAccess, GrantedAccess | MAXIMUM_ALLOWED );
+
+ if (!CheckOnly) {
+
+ SetFlag( AccessState->PreviouslyGrantedAccess, GrantedAccess );
+
+ //
+ // Remember the case where MAXIMUM_ALLOWED was requested and we
+ // got everything requested from the file.
+ //
+
+ if (MaximumRequested) {
+
+ //
+ // Check whether we got DELETE and READ_ATTRIBUTES. Otherwise
+ // we will query the parent.
+ //
+
+ if (FlagOn( AccessState->PreviouslyGrantedAccess, DELETE )) {
+
+ MaximumDeleteAcquired = TRUE;
+ }
+
+ if (FlagOn( AccessState->PreviouslyGrantedAccess, FILE_READ_ATTRIBUTES )) {
+
+ MaximumReadAttrAcquired = TRUE;
+ }
+ }
+
+ ClearFlag( AccessState->RemainingDesiredAccess, (GrantedAccess | MAXIMUM_ALLOWED) );
+ }
+
+ } else {
+
+ AccessStatusError = AccessStatus;
+ }
+
+ //
+ // Check if the access is not granted and if we were given a parent fcb, and
+ // if the desired access was asking for delete or file read attributes. If so
+ // then we need to do some extra work to decide if the caller does get access
+ // based on the parent directories security descriptor. We also do the same
+ // work if MAXIMUM_ALLOWED was requested and we didn't get DELETE or
+ // FILE_READ_ATTRIBUTES.
+ //
+
+ if ((ParentFcb != NULL)
+ && ((!AccessGranted && FlagOn( DesiredAccess, DELETE | FILE_READ_ATTRIBUTES ))
+ || (MaximumRequested
+ && (!MaximumDeleteAcquired || !MaximumReadAttrAcquired)))) {
+
+ BOOLEAN DeleteAccessGranted = TRUE;
+ BOOLEAN ReadAttributesAccessGranted = TRUE;
+
+ ACCESS_MASK DeleteChildGrantedAccess = 0;
+ ACCESS_MASK ListDirectoryGrantedAccess = 0;
+
+ //
+ // Before we proceed load in the parent security descriptor
+ //
+
+ if (ParentFcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, ParentFcb, NULL );
+ }
+
+ ASSERT( ParentFcb->SharedSecurity != NULL);
+
+ //
+ // Now if the user is asking for delete access then check if the parent
+ // will granted delete access to the child, and if so then we munge the
+ // desired access
+ //
+
+ if (FlagOn( DesiredAccess, DELETE )
+ || (MaximumRequested && !MaximumDeleteAcquired)) {
+
+ DeleteAccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
+ &AccessState->SubjectSecurityContext,
+ TRUE, // Tokens are locked
+ FILE_DELETE_CHILD,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &DeleteChildGrantedAccess,
+ &AccessStatus );
+
+ if (Privileges != NULL) { SeFreePrivileges( Privileges ); }
+
+ if (DeleteAccessGranted) {
+
+ SetFlag( DeleteChildGrantedAccess, DELETE );
+ ClearFlag( DeleteChildGrantedAccess, FILE_DELETE_CHILD );
+ ClearFlag( DesiredAccess, DELETE );
+
+ } else {
+
+ AccessStatusError = AccessStatus;
+ }
+ }
+
+ //
+ // Do the same test for read attributes and munge the desired access
+ // as appropriate
+ //
+
+ if (FlagOn(DesiredAccess, FILE_READ_ATTRIBUTES)
+ || (MaximumRequested && !MaximumReadAttrAcquired)) {
+
+ ReadAttributesAccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
+ &AccessState->SubjectSecurityContext,
+ TRUE, // Tokens are locked
+ FILE_LIST_DIRECTORY,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &ListDirectoryGrantedAccess,
+ &AccessStatus );
+
+ if (Privileges != NULL) { SeFreePrivileges( Privileges ); }
+
+ if (ReadAttributesAccessGranted) {
+
+ SetFlag( ListDirectoryGrantedAccess, FILE_READ_ATTRIBUTES );
+ ClearFlag( ListDirectoryGrantedAccess, FILE_LIST_DIRECTORY );
+ ClearFlag( DesiredAccess, FILE_READ_ATTRIBUTES );
+
+ } else {
+
+ AccessStatusError = AccessStatus;
+ }
+ }
+
+ if (DesiredAccess == 0) {
+
+ //
+ // If we got either the delete or list directory access then
+ // grant access.
+ //
+
+ if (ListDirectoryGrantedAccess != 0 ||
+ DeleteChildGrantedAccess != 0) {
+
+ AccessGranted = TRUE;
+ }
+
+ } else {
+
+ //
+ // Now the desired access has been munged by removing everything the parent
+ // has granted so now do the check on the child again
+ //
+
+ AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor,
+ &AccessState->SubjectSecurityContext,
+ TRUE, // Tokens are locked
+ DesiredAccess,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &GrantedAccess,
+ &AccessStatus );
+
+ if (Privileges != NULL) {
+
+ Status = SeAppendPrivileges( AccessState, Privileges );
+ SeFreePrivileges( Privileges );
+ }
+
+ //
+ // Suppose that we asked for MAXIMUM_ALLOWED and no access was allowed
+ // on the file. In that case the call above would fail. It's possible
+ // that we were given DELETE or READ_ATTR permission from the
+ // parent directory. If we have granted any access and the only remaining
+ // desired access is MAXIMUM_ALLOWED then grant this access.
+ //
+
+ if (!AccessGranted) {
+
+ AccessStatusError = AccessStatus;
+
+ if (DesiredAccess == MAXIMUM_ALLOWED &&
+ (ListDirectoryGrantedAccess != 0 ||
+ DeleteChildGrantedAccess != 0)) {
+
+ GrantedAccess = 0;
+ AccessGranted = TRUE;
+ }
+ }
+ }
+
+ //
+ // If we are given access this time then by definition one of the earlier
+ // parent checks had to have succeeded, otherwise we would have failed again
+ // and we can update the access state
+ //
+
+ if (!CheckOnly && AccessGranted) {
+
+ SetFlag( AccessState->PreviouslyGrantedAccess,
+ (GrantedAccess | DeleteChildGrantedAccess | ListDirectoryGrantedAccess) );
+
+ ClearFlag( AccessState->RemainingDesiredAccess,
+ (GrantedAccess | MAXIMUM_ALLOWED | DeleteChildGrantedAccess | ListDirectoryGrantedAccess) );
+ }
+ }
+ }
+
+ //
+ // Now call a routine that will do the proper open audit/alarm work
+ //
+ // **** We need to expand the audit alarm code to deal with
+ // create and traverse alarms.
+ //
+
+ //
+ // First we take a shortcut and see if we should bother setting up
+ // and making the audit call.
+ //
+
+ //
+ // NOTE: Calling SeAuditingFileEvents below disables per-user auditing functionality.
+ // To make per-user auditing work again, it is necessary to change the call below to
+ // be SeAuditingFileOrGlobalEvents, which also takes the subject context.
+ //
+ // The reason for calling SeAuditingFileEvents here is because per-user auditing is
+ // not currently exposed to users, and this routine imposes less of a performance
+ // penalty than does calling SeAuditingFileOrGlobalEvents.
+ //
+
+ if (SeAuditingFileEvents( AccessGranted, &Fcb->SharedSecurity->SecurityDescriptor )) {
+
+ BOOLEAN Found;
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ PFILE_NAME FileNameAttr;
+ UNICODE_STRING FileRecordName;
+
+ NtfsInitializeAttributeContext( &Context );
+
+ try {
+
+ //
+ // Construct the file name. The file name
+ // consists of:
+ //
+ // The device name out of the Vcb +
+ //
+ // The contents of the filename in the File Object +
+ //
+ // The contents of the Related File Object if it
+ // is present and the name in the File Object
+ // does not start with a '\'
+ //
+ //
+ // Obtain the file name.
+ //
+
+ PartialFileName = &IrpSp->FileObject->FileName;
+
+ PartialFileNamePresent = (PartialFileName->Length != 0);
+
+ if (!PartialFileNamePresent &&
+ FlagOn(IrpSp->Parameters.Create.Options, FILE_OPEN_BY_FILE_ID) ||
+ (IrpSp->FileObject->RelatedFileObject != NULL &&
+ IrpSp->FileObject->RelatedFileObject->FsContext2 != NULL &&
+ FlagOn(((PCCB) IrpSp->FileObject->RelatedFileObject->FsContext2)->Flags,
+ CCB_FLAG_OPEN_BY_FILE_ID))) {
+
+ //
+ // If this file is open by id or the relative file object is
+ // then get the first file name out of the file record.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $FILE_NAME,
+ &Context );
+
+ while (Found) {
+
+ FileNameAttr = (PFILE_NAME) NtfsAttributeValue(
+ NtfsFoundAttribute( &Context ));
+
+ if (FileNameAttr->Flags != FILE_NAME_DOS) {
+
+ FileRecordName.Length = FileNameAttr->FileNameLength *
+ sizeof(WCHAR);
+ FileRecordName.MaximumLength = FileRecordName.Length;
+ FileRecordName.Buffer = FileNameAttr->FileName;
+
+ PartialFileNamePresent = TRUE;
+ PartialFileName = &FileRecordName;
+ break;
+ }
+
+ Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Fcb,
+ $FILE_NAME,
+ &Context );
+ }
+ }
+
+ //
+ // Obtain the device name.
+ //
+
+ DeviceObjectName = &Fcb->Vcb->DeviceName;
+
+ DeviceObjectNameLength = DeviceObjectName->Length;
+
+ //
+ // Compute how much space we need for the final name string
+ //
+
+ FullFileName.MaximumLength = DeviceObjectNameLength +
+ PartialFileName->Length +
+ sizeof( UNICODE_NULL ) +
+ sizeof((WCHAR)'\\');
+
+ //
+ // If the partial file name starts with a '\', then don't use
+ // whatever may be in the related file name.
+ //
+
+ if (PartialFileNamePresent &&
+ ((WCHAR)(PartialFileName->Buffer[0]) == L'\\' ||
+ PartialFileName == &FileRecordName)) {
+
+ LeadingSlash = TRUE;
+
+ } else {
+
+ //
+ // Since PartialFileName either doesn't exist or doesn't
+ // start with a '\', examine the RelatedFileName to see
+ // if it exists.
+ //
+
+ LeadingSlash = FALSE;
+
+ if (IrpSp->FileObject->RelatedFileObject != NULL) {
+
+ RelatedFileName = &IrpSp->FileObject->RelatedFileObject->FileName;
+ }
+
+ if (RelatedFileNamePresent = ((RelatedFileName != NULL) && (RelatedFileName->Length != 0))) {
+
+ FullFileName.MaximumLength += RelatedFileName->Length;
+ }
+ }
+
+ FullFileName.Buffer = NtfsAllocatePool(PagedPool, FullFileName.MaximumLength );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &Context );
+ if (AbnormalTermination()) {
+
+ SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
+ }
+ }
+
+ RtlCopyUnicodeString( &FullFileName, DeviceObjectName );
+
+ //
+ // RelatedFileNamePresent is not initialized if LeadingSlash == TRUE,
+ // but in that case we won't even examine it.
+ //
+
+ if (!LeadingSlash && RelatedFileNamePresent) {
+
+ Status = RtlAppendUnicodeStringToString( &FullFileName, RelatedFileName );
+
+ ASSERTMSG("RtlAppendUnicodeStringToString of RelatedFileName", NT_SUCCESS( Status ));
+
+ //
+ // RelatedFileName may simply be '\'. Don't append another
+ // '\' in this case.
+ //
+
+ if (RelatedFileName->Length != sizeof( WCHAR )) {
+
+ FullFileName.Buffer[ (FullFileName.Length / sizeof( WCHAR )) ] = L'\\';
+ FullFileName.Length += sizeof(WCHAR);
+ }
+ }
+
+ if (PartialFileNamePresent) {
+
+ Status = RtlAppendUnicodeStringToString( &FullFileName, PartialFileName );
+
+ //
+ // This should not fail
+ //
+
+ ASSERTMSG("RtlAppendUnicodeStringToString of PartialFileName failed", NT_SUCCESS( Status ));
+ }
+
+
+ if (PerformDeleteAudit) {
+ SeOpenObjectForDeleteAuditAlarm( &FileString,
+ NULL,
+ &FullFileName,
+ &Fcb->SharedSecurity->SecurityDescriptor,
+ AccessState,
+ FALSE,
+ AccessGranted,
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &AccessState->GenerateOnClose );
+ } else {
+ SeOpenObjectAuditAlarm( &FileString,
+ NULL,
+ &FullFileName,
+ &Fcb->SharedSecurity->SecurityDescriptor,
+ AccessState,
+ FALSE,
+ AccessGranted,
+ (KPROCESSOR_MODE)(FlagOn(IrpSp->Flags, SL_FORCE_ACCESS_CHECK) ?
+ UserMode : Irp->RequestorMode),
+ &AccessState->GenerateOnClose );
+
+ }
+
+ NtfsFreePool( FullFileName.Buffer );
+ }
+
+ SeUnlockSubjectContext( &AccessState->SubjectSecurityContext );
+
+ //
+ // If access is not granted then we will raise
+ //
+
+ if (!AccessGranted) {
+
+ DebugTrace( 0, Dbg, ("Access Denied\n") );
+
+ NtfsRaiseStatus( IrpContext, AccessStatusError, NULL, NULL );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsAccessCheck -> VOID\n") );
+
+ return;
+}
+
+
+NTSTATUS
+NtfsCheckFileForDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PFCB ThisFcb,
+ IN BOOLEAN FcbExisted,
+ IN PINDEX_ENTRY IndexEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks that the caller has permission to delete the target
+ file of a rename or set link operation.
+
+Arguments:
+
+ ParentScb - This is the parent directory for this file.
+
+ ThisFcb - This is the Fcb for the link being removed.
+
+ FcbExisted - Indicates if this Fcb was just created.
+
+ IndexEntry - This is the index entry on the disk for this file.
+
+Return Value:
+
+ NTSTATUS - Indicating whether access was granted or the reason access
+ was denied.
+
+--*/
+
+{
+ UNICODE_STRING LastComponentFileName;
+ PFILE_NAME IndexFileName;
+ PLCB ThisLcb;
+ PFCB ParentFcb = ParentScb->Fcb;
+
+ PSCB NextScb = NULL;
+
+ BOOLEAN LcbExisted;
+
+ BOOLEAN AccessGranted;
+ ACCESS_MASK GrantedAccess;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ BOOLEAN UnlockSubjectContext = FALSE;
+
+ PPRIVILEGE_SET Privileges = NULL;
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckFileForDelete: Entered\n") );
+
+ ThisLcb = NULL;
+
+ IndexFileName = (PFILE_NAME) NtfsFoundIndexEntry( IndexEntry );
+
+ //
+ // If the unclean count is non-zero, we exit with an error.
+ //
+
+ if (ThisFcb->CleanupCount != 0) {
+
+ DebugTrace( 0, Dbg, ("Unclean count of target is non-zero\n") );
+
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // We look at the index entry to see if the file is either a directory
+ // or a read-only file. We can't delete this for a target directory open.
+ //
+
+ if (IsDirectory( &ThisFcb->Info )
+ || IsReadOnly( &ThisFcb->Info )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Read only or directory\n") );
+
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // We want to scan through all of the Scb for data streams on this file
+ // and look for image sections. We must be able to remove the image section
+ // in order to delete the file. Otherwise we can get the case where an
+ // active image (with no handle) could be deleted and subsequent faults
+ // through the image section will return zeroes.
+ //
+
+ if (ThisFcb->LinkCount == 1) {
+
+ BOOLEAN DecrementScb = FALSE;
+
+ //
+ // We will increment the Scb count to prevent this Scb from going away
+ // if the flush call below generates a close. Use a try-finally to
+ // restore the count.
+ //
+
+ try {
+
+ while ((NextScb = NtfsGetNextChildScb( ThisFcb, NextScb )) != NULL) {
+
+ InterlockedIncrement( &NextScb->CloseCount );
+ DecrementScb = TRUE;
+
+ if (NtfsIsTypeCodeUserData( NextScb->AttributeTypeCode ) &&
+ !FlagOn( NextScb->ScbState, SCB_STATE_ATTRIBUTE_DELETED ) &&
+ (NextScb->NonpagedScb->SegmentObject.ImageSectionObject != NULL)) {
+
+ if (!MmFlushImageSection( &NextScb->NonpagedScb->SegmentObject,
+ MmFlushForDelete )) {
+
+ Status = STATUS_ACCESS_DENIED;
+ leave;
+ }
+ }
+
+ InterlockedDecrement( &NextScb->CloseCount );
+ DecrementScb = FALSE;
+ }
+
+ } finally {
+
+ if (DecrementScb) {
+
+ InterlockedDecrement( &NextScb->CloseCount );
+ }
+ }
+
+ if (Status != STATUS_SUCCESS) {
+
+ return Status;
+ }
+ }
+
+ //
+ // We need to check if the link to this file has been deleted. We
+ // first check if we definitely know if the link is deleted by
+ // looking at the file name flags and the Fcb flags.
+ // If that result is uncertain, we need to create an Lcb and
+ // check the Lcb flags.
+ //
+
+ if (FcbExisted) {
+
+ if (FlagOn( IndexFileName->Flags, FILE_NAME_NTFS | FILE_NAME_DOS )) {
+
+ if (FlagOn( ThisFcb->FcbState, FCB_STATE_PRIMARY_LINK_DELETED )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Link is going away\n") );
+ return STATUS_DELETE_PENDING;
+ }
+
+ //
+ // This is a Posix link. We need to create the link to test it
+ // for deletion.
+ //
+
+ } else {
+
+ LastComponentFileName.MaximumLength
+ = LastComponentFileName.Length = IndexFileName->FileNameLength * sizeof( WCHAR );
+
+ LastComponentFileName.Buffer = (PWCHAR) IndexFileName->FileName;
+
+ ThisLcb = NtfsCreateLcb( IrpContext,
+ ParentScb,
+ ThisFcb,
+ LastComponentFileName,
+ IndexFileName->Flags,
+ &LcbExisted );
+
+ if (FlagOn( ThisLcb->LcbState, LCB_STATE_DELETE_ON_CLOSE )) {
+
+ DebugTrace( -1, Dbg, ("NtfsCheckFileForDelete: Link is going away\n") );
+
+ return STATUS_DELETE_PENDING;
+ }
+ }
+ }
+
+ //
+ // Finally call the security package to check for delete access.
+ // We check for delete access on the target Fcb. If this succeeds, we
+ // are done. Otherwise we will check for delete child access on the
+ // the parent. Either is sufficient to perform the delete.
+ //
+
+ //
+ // Check if we need to load the security descriptor for the file
+ //
+
+ if (ThisFcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, ThisFcb, ParentFcb );
+ }
+
+ ASSERT( ThisFcb->SharedSecurity != NULL );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Lock the user context, do the access check and then unlock the context
+ //
+
+ SeLockSubjectContext( IrpContext->Union.SubjectContext );
+ UnlockSubjectContext = TRUE;
+
+ AccessGranted = SeAccessCheck( &ThisFcb->SharedSecurity->SecurityDescriptor,
+ IrpContext->Union.SubjectContext,
+ TRUE, // Tokens are locked
+ DELETE,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ UserMode,
+ &GrantedAccess,
+ &Status );
+
+ //
+ // Check if the access is not granted and if we were given a parent fcb, and
+ // if the desired access was asking for delete or file read attributes. If so
+ // then we need to do some extra work to decide if the caller does get access
+ // based on the parent directories security descriptor
+ //
+
+ if (!AccessGranted) {
+
+ //
+ // Before we proceed load in the parent security descriptor
+ //
+
+ if (ParentFcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, ParentFcb, NULL );
+ }
+
+ ASSERT( ParentFcb->SharedSecurity != NULL);
+
+ //
+ // Now if the user is asking for delete access then check if the parent
+ // will granted delete access to the child, and if so then we munge the
+ // desired access
+ //
+
+ AccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
+ IrpContext->Union.SubjectContext,
+ TRUE, // Tokens are locked
+ FILE_DELETE_CHILD,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ UserMode,
+ &GrantedAccess,
+ &Status );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCheckFileForDelete );
+
+ if (UnlockSubjectContext) {
+
+ SeUnlockSubjectContext( IrpContext->Union.SubjectContext );
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsCheckFileForDelete: Exit\n") );
+ }
+
+ return Status;
+}
+
+
+VOID
+NtfsCheckIndexForAddOrDelete (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB ParentFcb,
+ IN ACCESS_MASK DesiredAccess
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if a caller has permission to remove or add a link
+ within a directory.
+
+Arguments:
+
+ ParentFcb - This is the parent directory for the add or delete operation.
+
+ DesiredAccess - Indicates the type of operation. We could be adding or
+ removing and entry in the index.
+
+Return Value:
+
+ None - This routine raises on error.
+
+--*/
+
+{
+ BOOLEAN AccessGranted;
+ ACCESS_MASK GrantedAccess;
+ NTSTATUS Status;
+
+ BOOLEAN UnlockSubjectContext = FALSE;
+
+ PPRIVILEGE_SET Privileges = NULL;
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsCheckIndexForAddOrDelete: Entered\n") );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Finally call the security package to check for delete access.
+ // We check for delete access on the target Fcb. If this succeeds, we
+ // are done. Otherwise we will check for delete child access on the
+ // the parent. Either is sufficient to perform the delete.
+ //
+
+ //
+ // Check if we need to load the security descriptor for the file
+ //
+
+ if (ParentFcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, ParentFcb, NULL );
+ }
+
+ ASSERT( ParentFcb->SharedSecurity != NULL );
+
+ //
+ // Capture and lock the user context, do the access check and then unlock the context
+ //
+
+ SeLockSubjectContext( IrpContext->Union.SubjectContext );
+ UnlockSubjectContext = TRUE;
+
+ AccessGranted = SeAccessCheck( &ParentFcb->SharedSecurity->SecurityDescriptor,
+ IrpContext->Union.SubjectContext,
+ TRUE, // Tokens are locked
+ DesiredAccess,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ UserMode,
+ &GrantedAccess,
+ &Status );
+
+ //
+ // If access is not granted then we will raise
+ //
+
+ if (!AccessGranted) {
+
+ DebugTrace( 0, Dbg, ("Access Denied\n") );
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCheckIndexForAddOrDelete );
+
+ if (UnlockSubjectContext) {
+
+ SeUnlockSubjectContext( IrpContext->Union.SubjectContext );
+ }
+
+ DebugTrace( +1, Dbg, ("NtfsCheckIndexForAddOrDelete: Exit\n") );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsUpdateFcbSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL,
+#ifdef _CAIRO_
+ IN SECURITY_ID SecurityId,
+#endif // _CAIRO_
+ IN PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN ULONG SecurityDescriptorLength
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to fill in the shared security structure in
+ an Fcb. We check the parent if present to determine if we have
+ a matching security descriptor and reference the existing one if
+ so. This routine must be called while holding the Vcb so we can
+ safely access the parent structure.
+
+Arguments:
+
+ Fcb - Supplies the fcb for the file being operated on
+
+ ParentFcb - Optionally supplies a parent Fcb to examine for a
+ match. If not present, we will follow the Lcb chain in the target
+ Fcb.
+
+ SecurityDescriptor - Security Descriptor for this file.
+
+ SecurityDescriptorLength - Length of security descriptor for this file
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSHARED_SECURITY SharedSecurity = NULL;
+ PLCB ParentLcb;
+ PFCB LastParent = NULL;
+#ifdef _CAIRO_
+ ULONG Hash = 0;
+#endif // _CAIRO_
+
+ PAGED_CODE();
+
+ //
+ // Only continue with the load if the length is greater than zero
+ //
+
+ if (SecurityDescriptorLength == 0) {
+
+ return;
+ }
+
+ //
+ // Make sure the security descriptor we just read in is valid
+ //
+
+ if (!SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) {
+
+ SecurityDescriptor = NtfsData.DefaultDescriptor;
+ SecurityDescriptorLength = NtfsData.DefaultDescriptorLength;
+
+ if (!SeValidSecurityDescriptor( SecurityDescriptorLength, SecurityDescriptor )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+ }
+
+#ifdef _CAIRO_
+ //
+ // Hash security descriptor. This hash must be position independent to
+ // allow for multiple instances of the same descriptor. It is assumed
+ // that the bits within the security descriptor are all position
+ // independent, i.e, no pointers, all offsets.
+ //
+ // For speed in the hash, we consider the security descriptor as an array
+ // of ULONGs. The fragment at the end that is ignored should not affect
+ // the collision nature of this hash.
+ //
+
+ {
+ PULONG Rover = (PULONG)SecurityDescriptor;
+ ULONG Count = SecurityDescriptorLength / 4;
+
+ while (Count--)
+ {
+ Hash = ((Hash << 3) | (Hash >> (32-3))) + *Rover++;
+ }
+
+ DebugTrace( 0, Dbg, ("Hash is %08x\n", Hash) );
+ }
+#endif // _CAIRO_
+
+ //
+ // Acquire the security event and use a try-finally to insure we release it.
+ //
+
+ NtfsAcquireFcbSecurity( Fcb->Vcb );
+
+ try {
+
+ //
+ // BUGBUG - since we have a cache based on a hash of security ID's, can
+ // we just skip this walk altogether?
+ //
+
+ //
+ // If we have a parent then check if this is a matching descriptor.
+ //
+
+ if (!ARGUMENT_PRESENT( ParentFcb )
+ && !IsListEmpty( &Fcb->LcbQueue )) {
+
+ ParentLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ if (ParentLcb != Fcb->Vcb->RootLcb) {
+
+ ParentFcb = ParentLcb->Scb->Fcb;
+ }
+ }
+
+ if (ParentFcb != NULL) {
+
+ while (TRUE) {
+
+ PSHARED_SECURITY NextSharedSecurity;
+
+ //
+ // If the target Fcb is an Index then use the security descriptor for
+ // our parent. Otherwise use the descriptor for a file on the drive.
+ //
+
+ if (FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT )) {
+
+ NextSharedSecurity = ParentFcb->SharedSecurity;
+
+ } else {
+
+ NextSharedSecurity = ParentFcb->ChildSharedSecurity;
+ }
+
+ if (NextSharedSecurity != NULL) {
+
+ if (GetSharedSecurityLength(NextSharedSecurity) == SecurityDescriptorLength
+#ifdef _CAIRO_
+ && NextSharedSecurity->Header.HashKey.Hash == Hash
+#endif // _CAIRO_
+ && RtlEqualMemory( &NextSharedSecurity->SecurityDescriptor,
+ SecurityDescriptor,
+ SecurityDescriptorLength )) {
+
+ SharedSecurity = NextSharedSecurity;
+ }
+
+ break;
+ }
+
+ LastParent = ParentFcb;
+
+ if (!IsListEmpty( &ParentFcb->LcbQueue )) {
+
+ ParentLcb = CONTAINING_RECORD( ParentFcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ if (ParentLcb != Fcb->Vcb->RootLcb) {
+
+ ParentFcb = ParentLcb->Scb->Fcb;
+
+ } else {
+
+ break;
+ }
+
+ } else {
+
+ break;
+ }
+ }
+ }
+
+#ifdef _CAIRO_
+ //
+ // If we havent't found the security descriptor by walking up the tree then
+ // try to find it by hash
+ //
+
+ SharedSecurity =
+ NtOfsFindCachedSharedSecurityByHash( Fcb->Vcb,
+ SecurityDescriptor,
+ SecurityDescriptorLength,
+ Hash );
+#endif
+
+ //
+ // If we can't find an existing descriptor allocate new pool and copy
+ // security descriptor into it.
+ //
+
+ if (SharedSecurity == NULL) {
+ SharedSecurity = NtfsAllocatePool(PagedPool, FIELD_OFFSET( SHARED_SECURITY,
+ SecurityDescriptor )
+ + SecurityDescriptorLength );
+
+ //
+ // If this is a file and we have a Parent Fcb without a child
+ // descriptor we will store this one with that directory.
+ //
+
+ if (!FlagOn( Fcb->Info.FileAttributes, DUP_FILE_NAME_INDEX_PRESENT )
+ && LastParent != NULL) {
+
+ SharedSecurity->ParentFcb = LastParent;
+ ASSERT( LastParent->ChildSharedSecurity == NULL );
+
+ LastParent->ChildSharedSecurity = SharedSecurity;
+ LastParent->ChildSharedSecurity->ReferenceCount = 1;
+
+ } else {
+
+ SharedSecurity->ParentFcb = NULL;
+ SharedSecurity->ReferenceCount = 0;
+ }
+
+#ifdef _CAIRO_
+ //
+ // Initialize security index data in shared security
+ //
+
+ //
+ // Set the security id in the shared structure. If it is not
+ // invalid, also cache this shared security structure
+ //
+
+ SharedSecurity->Header.HashKey.SecurityId = SecurityId;
+ SharedSecurity->Header.HashKey.Hash = Hash;
+ if (SecurityId != SECURITY_ID_INVALID) {
+ NtOfsAddCachedSharedSecurity( Fcb->Vcb, SharedSecurity );
+ }
+
+ SetSharedSecurityLength(SharedSecurity, SecurityDescriptorLength);
+ SharedSecurity->Header.Offset = (ULONGLONG) 0xFFFFFFFFFFFFFFFFi64;
+#else // _CAIRO_
+ SetSharedSecurityLength(SharedSecurity, SecurityDescriptorLength);
+#endif // _CAIRO_
+
+ RtlCopyMemory( &SharedSecurity->SecurityDescriptor,
+ SecurityDescriptor,
+ SecurityDescriptorLength );
+
+ }
+
+ Fcb->SharedSecurity = SharedSecurity;
+ Fcb->SharedSecurity->ReferenceCount++;
+ Fcb->CreateSecurityCount++;
+
+ } finally {
+
+ DebugUnwind( NtfsUpdateFcbSecurity );
+
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+ }
+
+ return;
+}
+
+
+_inline
+VOID
+NtfsRemoveReferenceSharedSecurity (
+ IN OUT PSHARED_SECURITY SharedSecurity
+ )
+/*++
+
+Routine Description:
+
+ This routine is called to manage the reference count on a shared security
+ descriptor. If the reference count goes to zero, the shared security is
+ freed.
+
+Arguments:
+
+ SharedSecurity - security that is being dereferenced.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // Note that there will be one less reference shortly
+ //
+
+ SharedSecurity->ReferenceCount--;
+
+ //
+ // If there is another reference to this shared security *AND* this
+ // shared security is being shared as a parent's child FCB then
+ // decouple it from the parent.
+ //
+
+ if (SharedSecurity->ReferenceCount == 1 && SharedSecurity->ParentFcb != NULL) {
+
+ //
+ // Verify that the parent's child matches this shared security
+ //
+
+ ASSERT( SharedSecurity->ParentFcb->ChildSharedSecurity == SharedSecurity );
+
+ //
+ // Remove reference from parent fcb
+ //
+
+ SharedSecurity->ParentFcb->ChildSharedSecurity = NULL;
+ SharedSecurity->ReferenceCount--;
+ SharedSecurity->ParentFcb = NULL;
+ }
+
+ if (SharedSecurity->ReferenceCount == 0) {
+ NtfsFreePool( SharedSecurity );
+ }
+}
+
+ VOID
+NtfsDereferenceSharedSecurity (
+ IN OUT PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to dereference the shared security structure in
+ an Fcb and deallocate it if possible.
+
+Arguments:
+
+ Fcb - Supplies the fcb for the file being operated on.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PSHARED_SECURITY SharedSecurity;
+ PAGED_CODE();
+
+ //
+ // Remove the reference and capture the shared security if we are to free it.
+ //
+
+ SharedSecurity = Fcb->SharedSecurity;
+ Fcb->SharedSecurity = NULL;
+ NtfsRemoveReferenceSharedSecurity( SharedSecurity );
+}
+
+BOOLEAN
+NtfsNotifyTraverseCheck (
+ IN PCCB Ccb,
+ IN PFCB Fcb,
+ IN PSECURITY_SUBJECT_CONTEXT SubjectContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is the callback routine provided to the dir notify package
+ to check that a caller who is watching a tree has traverse access to
+ the directory which has the change. This routine is only called
+ when traverse access checking was turned on for the open used to
+ perform the watch.
+
+Arguments:
+
+ Ccb - This is the Ccb associated with the directory which is being
+ watched.
+
+ Fcb - This is the Fcb for the directory which contains the file being
+ modified. We want to walk up the tree from this point and check
+ that the caller has traverse access across that directory.
+ If not specified then there is no work to do.
+
+ SubjectContext - This is the subject context captured at the time the
+ dir notify call was made.
+
+Return Value:
+
+ BOOLEAN - TRUE if the caller has traverse access to the file which was
+ changed. FALSE otherwise.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ PFCB TopFcb;
+
+ IRP_CONTEXT LocalIrpContext;
+ IRP LocalIrp;
+
+ PIRP_CONTEXT IrpContext;
+
+ BOOLEAN AccessGranted;
+ ACCESS_MASK GrantedAccess;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PPRIVILEGE_SET Privileges = NULL;
+ PAGED_CODE();
+
+ //
+ // If we have no Fcb then we can return immediately.
+ //
+
+ if (Fcb == NULL) {
+
+ return TRUE;
+ }
+
+ RtlZeroMemory( &LocalIrpContext, sizeof(LocalIrpContext) );
+ RtlZeroMemory( &LocalIrp, sizeof(LocalIrp) );
+
+ IrpContext = &LocalIrpContext;
+ IrpContext->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext->NodeByteSize = sizeof(IRP_CONTEXT);
+ IrpContext->OriginatingIrp = &LocalIrp;
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+ InitializeListHead( &IrpContext->ExclusiveFcbList );
+ IrpContext->Vcb = Fcb->Vcb;
+
+ //
+ // Make sure we don't get any pop-ups
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, &TopLevelContext );
+
+ TopFcb = Ccb->Lcb->Fcb;
+
+ //
+ // Use a try-except to catch all of the errors.
+ //
+
+ try {
+
+ //
+ // Always lock the subject context.
+ //
+
+ SeLockSubjectContext( SubjectContext );
+
+ //
+ // Use a try-finally to perform local cleanup.
+ //
+
+ try {
+
+ //
+ // We look while walking up the tree.
+ //
+
+ do {
+
+ PLCB ParentLcb;
+
+ //
+ // Since this is a directory it can have only one parent. So
+ // we can use any Lcb to walk upwards.
+ //
+
+ ParentLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ Fcb = ParentLcb->Scb->Fcb;
+
+ //
+ // Check if we need to load the security descriptor for the file
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ NtfsLoadSecurityDescriptor( IrpContext, Fcb, NULL );
+ }
+
+ AccessGranted = SeAccessCheck( &Fcb->SharedSecurity->SecurityDescriptor,
+ SubjectContext,
+ TRUE, // Tokens are locked
+ FILE_TRAVERSE,
+ 0,
+ &Privileges,
+ IoGetFileObjectGenericMapping(),
+ UserMode,
+ &GrantedAccess,
+ &Status );
+
+ } while ( AccessGranted && Fcb != TopFcb );
+
+ } finally {
+
+ SeUnlockSubjectContext( SubjectContext );
+ }
+
+ } except (NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NOTHING;
+ }
+
+ NtfsRestoreTopLevelIrp( &TopLevelContext );
+
+ return AccessGranted;
+}
+
+
+#ifdef _CAIRO_
+VOID
+NtfsInitializeSecurity (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to initialize the security indexes and descriptor
+ stream.
+
+Arguments:
+
+ IrpContext - context of call
+
+ Vcb - Supplies the volume being initialized
+
+ Fcb - Supplies the file containing the seurity indexes and descriptor
+ stream.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ UNICODE_STRING SecurityIdIndexName =
+ CONSTANT_UNICODE_STRING( L"$SecurityIdIndex" );
+ UNICODE_STRING SecurityDescriptorHashIndexName =
+ CONSTANT_UNICODE_STRING( L"$SecurityDescriptorHashIndex" );
+ UNICODE_STRING SecurityDescriptorStreamName =
+ CONSTANT_UNICODE_STRING( L"$SecurityDescriptorStream" );
+
+ MAP_HANDLE Map;
+ NTSTATUS Status;
+
+ PAGED_CODE( );
+
+ //
+ // Open/Create the security descriptor stream
+ //
+
+ NtOfsCreateAttribute( IrpContext,
+ Fcb,
+ SecurityDescriptorStreamName,
+ CREATE_OR_OPEN,
+ TRUE,
+ &Vcb->SecurityDescriptorStream );
+
+ NtfsAcquireSharedScb( IrpContext, Vcb->SecurityDescriptorStream );
+
+ //
+ // Load the run information for the Security data stream.
+ // Note this call must be done after the stream is nonresident.
+ //
+
+ if (!FlagOn( Vcb->SecurityDescriptorStream->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+ NtfsPreloadAllocation( IrpContext,
+ Vcb->SecurityDescriptorStream,
+ 0,
+ MAXLONGLONG );
+ }
+
+ //
+ // Open the Security descriptor indexes and storage.
+ // BUGBUG: At present, these attributes are stored as part of the
+ // QuotaTable file record.
+ //
+
+ NtOfsCreateIndex( IrpContext,
+ Fcb,
+ SecurityIdIndexName,
+ CREATE_OR_OPEN,
+ 0,
+ NtOfsCollateUlong,
+ NULL,
+ &Vcb->SecurityIdIndex );
+
+ NtOfsCreateIndex( IrpContext,
+ Fcb,
+ SecurityDescriptorHashIndexName,
+ CREATE_OR_OPEN,
+ 0,
+ NtOfsCollateSecurityHash,
+ NULL,
+ &Vcb->SecurityDescriptorHashIndex );
+
+ //
+ // Retrieve the next security Id to allocate
+ //
+
+ try {
+
+ SECURITY_ID LastSecurityId = 0xFFFFFFFF;
+ INDEX_KEY LastKey;
+ INDEX_ROW LastRow;
+
+ LastKey.KeyLength = sizeof( SECURITY_ID );
+ LastKey.Key = &LastSecurityId;
+
+ Map.Bcb = NULL;
+
+ Status = NtOfsFindLastRecord( IrpContext,
+ Vcb->SecurityIdIndex,
+ &LastKey,
+ &LastRow,
+ &Map );
+
+ //
+ // If we've found the last key, set the next Id to allocate to be
+ // one greater than this last key.
+ //
+
+ if (Status == STATUS_SUCCESS) {
+
+ ASSERT( LastRow.KeyPart.KeyLength == sizeof( SECURITY_ID ) );
+ if (LastRow.KeyPart.KeyLength != sizeof( SECURITY_ID )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("Found last security Id in index\n") );
+ Vcb->NextSecurityId = *(SECURITY_ID *)LastRow.KeyPart.Key + 1;
+
+ //
+ // If the index is empty, then set the next Id to be the beginning of the
+ // user range.
+ //
+
+ } else if (Status == STATUS_NO_MATCH) {
+
+ DebugTrace( 0, Dbg, ("Security Id index is empty\n") );
+ Vcb->NextSecurityId = SECURITY_ID_FIRST;
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+
+ DebugTrace( 0, Dbg, ("NextSecurityId is %x\n", Vcb->NextSecurityId) );
+
+ } finally {
+
+ NtOfsReleaseMap( IrpContext, &Map );
+ }
+}
+#endif // _CAIRO_
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+PSHARED_SECURITY
+NtOfsFindCachedSharedSecurityBySecurityId (
+ IN PVCB Vcb,
+ IN SECURITY_ID SecurityId
+ )
+/*++
+
+Routine Description:
+
+ This routine maps looks up a shared security structure given the security Id by
+ looking in the per-Vcb cache. This routine assumes exclusive access to the
+ security cache.
+
+Arguments:
+
+ Vcb - Volume where security Id is cached
+
+ SecurityId - security Id for descriptor that is being retrieved
+
+Return Value:
+
+ PSHARED_SECURITY of found descriptor. Otherwise, NULL is returned.
+
+--*/
+{
+ PSHARED_SECURITY SharedSecurity;
+
+ PAGED_CODE( );
+
+ SharedSecurity = Vcb->SecurityCacheById[SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE];
+
+ //
+ // If there is no security descriptor there then no match was found
+ //
+
+ if (SharedSecurity == NULL) {
+ return NULL;
+ }
+
+ //
+ // If the security Id's don't match then no descriptor was found
+ //
+
+ if (SharedSecurity->Header.HashKey.SecurityId != SecurityId) {
+ return NULL;
+ }
+
+ //
+ // The shared security was found
+ //
+
+ return SharedSecurity;
+}
+
+#endif // _CAIRO_
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+PSHARED_SECURITY
+NtOfsFindCachedSharedSecurityByHash (
+ IN PVCB Vcb,
+ IN PSECURITY_DESCRIPTOR SecurityDescriptor,
+ IN ULONG SecurityDescriptorLength,
+ IN ULONG Hash
+ )
+/*++
+
+Routine Description:
+
+ This routine maps looks up a shared security structure given the Hash by
+ looking in the per-Vcb cache. This routine assumes exclusive access to the
+ security cache.
+
+Arguments:
+
+ Vcb - Volume where security Id is cached
+
+ SecurityDescriptor - Security descriptor being retrieved
+
+ SecurityDescriptorLength - length of descriptor.
+
+ Hash - Hash for descriptor that is being retrieved
+
+Return Value:
+
+ PSHARED_SECURITY of found shared descriptor. Otherwise, NULL is returned.
+
+--*/
+{
+ PSHARED_SECURITY *SharedSecurity;
+
+ PAGED_CODE( );
+
+ //
+ // Hash the hash into the per-volume table
+
+ SharedSecurity = Vcb->SecurityCacheByHash[Hash % VCB_SECURITY_CACHE_BY_HASH_SIZE];
+
+ //
+ // If there is no shared descriptor there, then no match
+ //
+
+ if (SharedSecurity == NULL || *SharedSecurity == NULL) {
+ return NULL;
+ }
+
+ //
+ // if the hash doesn't match then no descriptor found
+ //
+
+ if ((*SharedSecurity)->Header.HashKey.Hash != Hash) {
+ return NULL;
+ }
+
+ //
+ // If the lengths don't match then no descriptor found
+ //
+
+ if (GetSharedSecurityLength( *SharedSecurity ) != SecurityDescriptorLength) {
+ return NULL;
+ }
+
+ //
+ // If the security descriptor bits don't compare then no match
+ //
+
+ if (!RtlEqualMemory( (*SharedSecurity)->SecurityDescriptor,
+ SecurityDescriptor,
+ SecurityDescriptorLength) ) {
+ return NULL;
+ }
+
+
+ //
+ // The shared security was found
+ //
+
+ return *SharedSecurity;
+}
+
+#endif // _CAIRO_
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+void
+NtOfsAddCachedSharedSecurity (
+ IN PVCB Vcb,
+ PSHARED_SECURITY SharedSecurity
+ )
+/*++
+
+Routine Description:
+
+ This routine adds shared security to the Vcb Cache. This routine assumes
+ exclusive access to the security cache. The shared security being added
+ may have a ref count of one and may already be in the table.
+
+Arguments:
+
+ Vcb - Volume where security Id is cached
+
+ SharedSecurity - descriptor to be added to the cache
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PSHARED_SECURITY *Bucket;
+ PSHARED_SECURITY Old;
+
+ PAGED_CODE( );
+
+ //
+ // Is there an item already in the hash bucket?
+ //
+
+ Bucket = &Vcb->SecurityCacheById[SharedSecurity->Header.HashKey.SecurityId % VCB_SECURITY_CACHE_BY_ID_SIZE];
+
+ Old = *Bucket;
+
+ //
+ // Place it into the bucket and reference it
+ //
+
+ *Bucket = SharedSecurity;
+ SharedSecurity->ReferenceCount ++;
+
+ //
+ // Set up hash to point to bucket
+ //
+
+ Vcb->SecurityCacheByHash[SharedSecurity->Header.HashKey.Hash % VCB_SECURITY_CACHE_BY_HASH_SIZE] =
+ Bucket;
+
+ //
+ // Handle removing the old value from the bucket. We do this after advancing
+ // the ReferenceCount above in the case where the item is already in the bucket.
+ //
+
+ if (Old != NULL) {
+ //
+ // Remove and dereference the item in the bucket
+ //
+
+ // *Bucket = NULL;
+ NtfsRemoveReferenceSharedSecurity( Old );
+ }
+
+}
+
+#endif // _CAIRO_
+
+
+#ifdef _CAIRO_
+
+VOID
+NtOfsPurgeSecurityCache (
+ IN PVCB Vcb
+ )
+/*++
+
+Routine Description:
+
+ This routine removes all shared security from the per-Vcb cache.
+
+Arguments:
+
+ Vcb - Volume where descriptors are cached
+
+Return Value:
+
+ None.
+
+--*/
+{
+ ULONG i;
+
+ PAGED_CODE( );
+
+ //
+ // Serialize access to the security cache
+ //
+
+ NtfsAcquireFcbSecurity( Vcb );
+
+ //
+ // Walk through the cache looking for cached security
+ //
+
+ for (i = 0; i < VCB_SECURITY_CACHE_BY_ID_SIZE; i++)
+ {
+ if (Vcb->SecurityCacheById[i] != NULL) {
+ //
+ // Remove the reference to the security
+ //
+
+ PSHARED_SECURITY SharedSecurity = Vcb->SecurityCacheById[i];
+ Vcb->SecurityCacheById[i] = NULL;
+ NtfsRemoveReferenceSharedSecurity( SharedSecurity );
+ }
+ }
+
+ //
+ // Release access to the cache
+ //
+
+ NtfsReleaseFcbSecurity( Vcb );
+}
+#endif // _CAIRO_
+
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+VOID
+NtOfsMapSecurityIdToSecurityDescriptor (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN SECURITY_ID SecurityId,
+ OUT PSECURITY_DESCRIPTOR *SecurityDescriptor,
+ OUT PULONG SecurityDescriptorLength,
+ OUT PBCB *Bcb
+ )
+/*++
+
+Routine Description:
+
+ This routine maps from a security Id to the descriptor bits stored in the
+ security descriptor stream using the security Id index
+
+Arguments:
+
+ IrpContext - Context of the call
+
+ Vcb - Volume where descriptor is stored
+
+ SecurityId - security Id for descriptor that is being retrieved
+
+ SecurityDescriptor - returned security descriptor pointer
+
+ SecurityDescriptorLength - returned length of security descriptor
+
+ Bcb - returned mapping control structure
+
+Return Value:
+
+ None.
+
+--*/
+{
+ SECURITY_DESCRIPTOR_HEADER Header;
+ NTSTATUS Status;
+ MAP_HANDLE Map;
+ INDEX_ROW Row;
+ INDEX_KEY Key;
+
+ PAGED_CODE( );
+
+ DebugTrace( 0, Dbg, ("Mapping security ID %08x\n", SecurityId) );
+
+ //
+ // Lookup descriptor stream position information.
+ // The format of the key is simply the ULONG SecurityId
+ //
+
+ Key.KeyLength = sizeof( SecurityId );
+ Key.Key = &SecurityId;
+
+ Status = NtOfsFindRecord( IrpContext,
+ Vcb->SecurityIdIndex,
+ &Key,
+ &Row,
+ &Map,
+ NULL );
+
+ DebugTrace( 0, Dbg, ("Security Id lookup status = %08x\n", Status) );
+
+ //
+ // If the security Id is not found, then this volume is corrupt.
+ // We raise the error which will force CHKDSK to be run to rebuild
+ // the mapping index.
+ //
+
+ if (Status == STATUS_NO_MATCH) {
+ DebugTrace( 0, Dbg, ("SecurityId is not found in index\n") );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Save security descriptor offset and length information
+ //
+
+ Header = *(PSECURITY_DESCRIPTOR_HEADER)Row.DataPart.Data;
+ ASSERT( Header.HashKey.SecurityId == SecurityId );
+
+ //
+ // Release mapping information
+ //
+
+ NtOfsReleaseMap( IrpContext, &Map );
+
+ //
+ // Make sure that the data is the correct size
+ //
+
+ ASSERT( Row.DataPart.DataLength == sizeof( SECURITY_DESCRIPTOR_HEADER ) );
+ if (Row.DataPart.DataLength != sizeof( SECURITY_DESCRIPTOR_HEADER )) {
+ DebugTrace( 0, Dbg, ("SecurityId data doesn't have the correct length\n") );
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_CORRUPT_ERROR, NULL, NULL );
+ }
+
+ //
+ // Map security descriptor
+ //
+
+ DebugTrace( 0, Dbg, ("Mapping security descriptor stream at %I64x, len %x\n",
+ Header.Offset, Header.Length) );
+
+ NtfsMapStream(
+ IrpContext,
+ Vcb->SecurityDescriptorStream,
+ Header.Offset,
+ Header.Length,
+ Bcb,
+ SecurityDescriptor );
+
+ //
+ // Set return values
+ //
+
+ *SecurityDescriptor =
+ (PSECURITY_DESCRIPTOR) Add2Ptr( *SecurityDescriptor,
+ sizeof( SECURITY_DESCRIPTOR_HEADER ) );
+ *SecurityDescriptorLength =
+ GETSECURITYDESCRIPTORLENGTH( &Header );
+}
+
+
+VOID
+NtfsLoadSecurityDescriptorById (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine finds or creates the shared security for the specified
+ Fcb by looking in the volume cache or index
+
+Arguments:
+
+ IrpContext - Context of call
+
+ Fcb - File whose security is to be loaded
+
+ ParentFcb - FCB of parent when searching upward to find already-cached
+ descriptor
+
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PSHARED_SECURITY SharedSecurity;
+
+ PAGED_CODE( );
+
+ //
+ // Serialize access to the security cache
+ //
+
+ NtfsAcquireFcbSecurity( Fcb->Vcb );
+
+ //
+ // First, consult the Vcb cache of security Ids
+ //
+
+ SharedSecurity = NtOfsFindCachedSharedSecurityBySecurityId( Fcb->Vcb, Fcb->SecurityId );
+
+ //
+ // If we found one, store it in the Fcb and we're done
+ //
+
+ if (SharedSecurity != NULL) {
+
+ Fcb->SharedSecurity = SharedSecurity;
+ Fcb->SharedSecurity->ReferenceCount++;
+ Fcb->CreateSecurityCount += 1;
+
+ DebugTrace( 0, DbgAcl, ("Found cached security descriptor %x %x\n",
+ SharedSecurity, SharedSecurity->Header.HashKey.SecurityId) );
+
+ //
+ // Release access to security cache
+ //
+
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+
+ } else {
+ PBCB Bcb = NULL;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+
+ //
+ // Release access to security cache
+ //
+
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+ DebugTrace( 0, Dbg, ("Looking up security descriptor %x\n", Fcb->SecurityId) );
+
+ //
+ // Lock down the security stream
+ //
+
+ NtfsAcquireSharedScb( IrpContext, Fcb->Vcb->SecurityDescriptorStream );
+
+ try {
+
+ //
+ // Consult the Vcb index to map to the security descriptor
+ //
+
+ NtOfsMapSecurityIdToSecurityDescriptor( IrpContext,
+ Fcb->Vcb,
+ Fcb->SecurityId,
+ &SecurityDescriptor,
+ &SecurityDescriptorLength,
+ &Bcb );
+
+ //
+ // Generate the shared security from the security Id and descriptor
+ //
+
+ NtfsUpdateFcbSecurity( IrpContext,
+ Fcb,
+ ParentFcb,
+ Fcb->SecurityId,
+ SecurityDescriptor,
+ SecurityDescriptorLength );
+
+ } finally {
+ NtfsUnpinBcb( &Bcb );
+ NtfsReleaseScb( IrpContext, Fcb->Vcb->SecurityDescriptorStream );
+ }
+ }
+
+}
+#endif // _CAIRO_
+
+
+//
+// Local Support routine
+//
+
+VOID
+NtfsLoadSecurityDescriptor (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PFCB ParentFcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine loads the shared security descriptor into the fcb for the
+ file from disk using either the SecurityId or the $Security_Descriptor
+
+Arguments:
+
+ Fcb - Supplies the fcb for the file being operated on
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ ASSERTMSG("Must only be called with a null value here", Fcb->SharedSecurity == NULL);
+
+ DebugTrace( +1, Dbg, ("NtfsLoadSecurityDescriptor...\n") );
+
+#ifdef _CAIRO_
+ //
+ // If the file has a valid SecurityId then retrieve the security descriptor
+ // from the security descriptor index
+ //
+
+ if (Fcb->SecurityId != SECURITY_ID_INVALID) {
+
+ NtfsLoadSecurityDescriptorById( IrpContext, Fcb, ParentFcb );
+ } else
+#endif // _CAIRO_
+ {
+ PBCB Bcb = NULL;
+ PSHARED_SECURITY SharedSecurity;
+ PSECURITY_DESCRIPTOR SecurityDescriptor;
+ ULONG SecurityDescriptorLength;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+
+ try {
+ //
+ // Read in the security descriptor attribute, and it is is not present
+ // then there then the file is not protected. In that case we will
+ // use the default descriptor.
+ //
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $SECURITY_DESCRIPTOR,
+ &AttributeContext )) {
+
+ DebugTrace( 0, Dbg, ("Security Descriptor attribute does not exist\n") );
+
+ SecurityDescriptor = NtfsData.DefaultDescriptor;
+ SecurityDescriptorLength = NtfsData.DefaultDescriptorLength;
+
+ } else {
+
+ //
+ // There must be a security descriptor with a non-zero length; only
+ // applies for non-resident descriptors with valid data length.
+ //
+
+ Attribute = NtfsFoundAttribute( &AttributeContext );
+
+ if (NtfsIsAttributeResident( Attribute ) ?
+ (Attribute->Form.Resident.ValueLength == 0) :
+ (Attribute->Form.Nonresident.ValidDataLength == 0)) {
+
+ SecurityDescriptor = NtfsData.DefaultDescriptor;
+ SecurityDescriptorLength = NtfsData.DefaultDescriptorLength;
+
+ } else {
+
+ NtfsMapAttributeValue( IrpContext,
+ Fcb,
+ (PVOID *)&SecurityDescriptor,
+ &SecurityDescriptorLength,
+ &Bcb,
+ &AttributeContext );
+ }
+ }
+
+ NtfsUpdateFcbSecurity( IrpContext,
+ Fcb,
+ ParentFcb,
+#ifdef _CAIRO_
+ SECURITY_ID_INVALID,
+#endif // _CAIRO_
+ SecurityDescriptor,
+ SecurityDescriptorLength );
+
+ } finally {
+
+ DebugUnwind( NtfsLoadSecurityDescriptor );
+
+ //
+ // Cleanup our attribute enumeration context and the Bcb
+ //
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ NtfsUnpinBcb( &Bcb );
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsLoadSecurityDescriptor -> VOID\n") );
+
+ return;
+}
+
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+NTSTATUS
+NtOfsMatchSecurityHash (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ )
+
+/*++
+
+Routine Description:
+
+ Test whether an index row is worthy of returning based on its contents as
+ a row in the SecurityDescriptorHashIndex.
+
+Arguments:
+
+ IndexRow - row that is being tested
+
+ MatchData - a PVOID that is the hash function we look for.
+
+Returns:
+
+ STATUS_SUCCESS if the IndexRow matches
+ STATUS_NO_MATCH if the IndexRow does not match, but the enumeration should
+ continue
+ STATUS_NO_MORE_MATCHES if the IndexRow does not match, and the enumeration
+ should terminate
+
+
+--*/
+{
+ ASSERT(IndexRow->KeyPart.KeyLength == sizeof( SECURITY_HASH_KEY ) );
+
+ PAGED_CODE( );
+
+ if (((PSECURITY_HASH_KEY)IndexRow->KeyPart.Key)->Hash == (ULONG) MatchData) {
+ return STATUS_SUCCESS;
+ } else {
+ return STATUS_NO_MORE_MATCHES;
+ }
+}
+#endif // _CAIRO_
+
+
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+VOID
+NtOfsLookupSecurityDescriptorInIndex (
+ PIRP_CONTEXT IrpContext,
+ IN OUT PSHARED_SECURITY SharedSecurity
+ )
+
+/*++
+
+Routine Description:
+
+ Look up the security descriptor in the index. If found, return the
+ security ID.
+
+Arguments:
+
+ IrpContext - context of the call
+
+ SharedSecurity - shared security for a file
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PAGED_CODE( );
+
+ DebugTrace( +1, Dbg, ("NtOfsLookupSecurityDescriptorInIndex...\n") );
+
+ //
+ // For each matching hash record in the index, see if the actual security
+ // security descriptor matches.
+ //
+ {
+ INDEX_KEY IndexKey;
+ INDEX_ROW FoundRow;
+ PSECURITY_DESCRIPTOR_HEADER Header;
+ UCHAR HashDescriptorHeader[2 * (sizeof( SECURITY_DESCRIPTOR_HEADER ) + sizeof( ULONG ))];
+
+ PINDEX_KEY Key = &IndexKey;
+ PREAD_CONTEXT ReadContext = NULL;
+ ULONG FoundCount = 0;
+ PBCB Bcb = NULL;
+
+ IndexKey.KeyLength = sizeof( SharedSecurity->Header.HashKey );
+ IndexKey.Key = &SharedSecurity->Header.HashKey.Hash;
+
+ try {
+ //
+ // We keep reading hash records until we find a hash.
+ //
+
+ while (SharedSecurity->Header.HashKey.SecurityId == SECURITY_ID_INVALID)
+ {
+ //
+ // Read next matching SecurityHashIndex record
+ //
+
+ FoundCount = 1;
+ NtOfsReadRecords(
+ IrpContext,
+ IrpContext->Vcb->SecurityDescriptorHashIndex,
+ &ReadContext,
+ Key,
+ NtOfsMatchSecurityHash,
+ (PVOID)SharedSecurity->Header.HashKey.Hash,
+ &FoundCount,
+ &FoundRow,
+ sizeof( HashDescriptorHeader ),
+ &HashDescriptorHeader[0]);
+
+ //
+ // Set next read to read sequentially rather than explicitly
+ // seek.
+ //
+
+ Key = NULL;
+
+ //
+ // If there were no more records found, then go and establish a
+ // a new security Id.
+ //
+
+ if (FoundCount == 0) {
+ break;
+ }
+
+ //
+ // Examine the row to see if the descriptors are
+ // the same. Verify the cache contents.
+ //
+
+ ASSERT( FoundRow.DataPart.DataLength == sizeof( SECURITY_DESCRIPTOR_HEADER ) );
+ if (FoundRow.DataPart.DataLength != sizeof( SECURITY_DESCRIPTOR_HEADER )) {
+ DebugTrace( 0, Dbg, ("Found row has a bad size\n") );
+ NtfsRaiseStatus( IrpContext,
+ STATUS_DISK_CORRUPT_ERROR,
+ NULL, NULL );
+ }
+
+ Header = (PSECURITY_DESCRIPTOR_HEADER)FoundRow.DataPart.Data;
+
+ //
+ // If the length of the security descriptor in the stream is NOT
+ // the same as the current security descriptor, then a match is
+ // not possible
+ //
+
+ if (SharedSecurity->Header.Length != Header->Length) {
+ continue;
+ }
+
+ //
+ // Map security descriptor given descriptor stream position.
+ //
+
+ try {
+ PSECURITY_DESCRIPTOR_HEADER TestHeader;
+
+ NtfsMapStream(
+ IrpContext,
+ IrpContext->Vcb->SecurityDescriptorStream,
+ Header->Offset,
+ Header->Length,
+ &Bcb,
+ &TestHeader);
+
+ //
+ // Make sure index data matches stream data
+ //
+
+ ASSERT( TestHeader->HashKey.Hash == Header->HashKey.Hash &&
+ TestHeader->HashKey.SecurityId == Header->HashKey.SecurityId &&
+ TestHeader->Length == Header->Length );
+
+ //
+ // Compare byte-for-byte the security descriptors. We do not
+ // perform any rearranging of descriptors into canonical forms.
+ //
+
+ if (RtlEqualMemory( SharedSecurity->SecurityDescriptor,
+ TestHeader + 1,
+ GetSharedSecurityLength( SharedSecurity )) ) {
+ //
+ // We have a match. Save the found header
+ //
+
+ SharedSecurity->Header = *TestHeader;
+ DebugTrace( 0, DbgAcl, ("Reusing indexed security Id %x\n",
+ TestHeader->HashKey.SecurityId) );
+ }
+ } finally {
+ NtfsUnpinBcb( &Bcb );
+ }
+ }
+
+ } finally {
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+ }
+ }
+}
+#endif // _CAIRO_
+
+
+
+//
+// Local Support routine
+//
+
+#ifdef _CAIRO_
+
+SECURITY_ID
+NtOfsGetSecurityIdFromSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN OUT PSHARED_SECURITY SharedSecurity
+ )
+
+/*++
+
+Routine Description:
+
+ Return the security Id associated with a given security descriptor. If
+ there is an existing Id, return it. If no Id exists, create one.
+
+Arguments:
+
+ IrpContext - context of the call
+
+ SharedSecurity - Shared security used by file
+
+Return Value:
+
+ SECURITY_ID corresponding to the unique instantiation of the security
+ descriptor on the volume.
+
+--*/
+{
+ SECURITY_ID SavedSecurityId;
+
+ PAGED_CODE( );
+
+ DebugTrace( +1, Dbg, ("NtOfsGetSecurityIdFromSecurityDescriptor...\n") );
+
+ //
+ // Make sure the data structures don't change underneath us
+ //
+
+ NtfsAcquireSharedScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream );
+
+ //
+ // Save next Security Id. This is used if we fail to find the security
+ // descriptor in the descriptor stream.
+ //
+
+ SavedSecurityId = IrpContext->Vcb->NextSecurityId;
+
+ //
+ // Find descriptor in indexes/stream
+ //
+
+ try {
+ NtOfsLookupSecurityDescriptorInIndex( IrpContext, SharedSecurity );
+
+ //
+ // If we've found the security descriptor in the stream we're done.
+ //
+
+ if (SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) {
+ leave;
+ }
+
+ //
+ // The security descriptor is not found. Reacquire the security
+ // stream exclusive since we are about to modify it.
+ //
+
+ NtfsReleaseScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream );
+ NtfsAcquireExclusiveScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream );
+
+ //
+ // During the short interval above, we did not own the security stream.
+ // It is possible that another thread has gotten in and created this
+ // descriptor. Therefore, we must probe the indexes again.
+ //
+ // Rather than perform this expensive test *always*, we saved the next
+ // security id to be allocated above. Now that we've obtained the stream
+ // exclusive we can check to see if the saved one is the same as the next
+ // one. If so, then we need to probe the indexes. Otherwise
+ // we know that no modifications have taken place.
+ //
+
+ if (SavedSecurityId != IrpContext->Vcb->NextSecurityId) {
+ DebugTrace( 0, DbgAcl, ("SecurityId changed, rescanning\n") );
+
+ //
+ // The descriptor cache has been edited. We must search again
+ //
+
+ NtOfsLookupSecurityDescriptorInIndex( IrpContext, SharedSecurity );
+
+ //
+ // If the Id was found this time, simply return it
+ //
+
+ if (SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) {
+ leave;
+ }
+ }
+
+ //
+ // allocate security id. This does not need to be logged since we only
+ // increment this and initialize this from the max key in the index at
+ // mount time.
+ //
+
+ SharedSecurity->Header.HashKey.SecurityId =
+ IrpContext->Vcb->NextSecurityId++;
+
+ //
+ // Determine allocation location in descriptor stream. The alignment
+ // requirements for security descriptors within the stream are:
+ //
+ // DWORD alignment
+ // Not spanning a VACB_MAPPING_GRANULARITY boundary
+ //
+
+ //
+ // Get current EOF for descriptor stream
+ //
+
+ SharedSecurity->Header.Offset =
+ IrpContext->Vcb->SecurityDescriptorStream->Header.FileSize.QuadPart;
+
+ //
+ // Align to big boundary
+ //
+
+ SharedSecurity->Header.Offset =
+ (SharedSecurity->Header.Offset + 0xF) & 0xFFFFFFFFFFFFFFF0i64;
+
+ DebugTrace( 0, DbgAcl, ("Allocating SecurityId %x at %016I64x\n",
+ SharedSecurity->Header.HashKey.SecurityId,
+ SharedSecurity->Header.Offset) );
+
+ //
+ // Make sure we don't span a VACB_MAPPING_GRANULARITY boundary
+ //
+
+ if ((SharedSecurity->Header.Offset & (VACB_MAPPING_GRANULARITY - 1)) +
+ SharedSecurity->Header.Length >= VACB_MAPPING_GRANULARITY) {
+ SharedSecurity->Header.Offset =
+ (SharedSecurity->Header.Offset + VACB_MAPPING_GRANULARITY - 1) &
+ ~(VACB_MAPPING_GRANULARITY - 1);
+ }
+
+
+ //
+ // Grow security stream to make room for new descriptor and header
+ //
+
+ NtOfsSetLength( IrpContext, IrpContext->Vcb->SecurityDescriptorStream,
+ SharedSecurity->Header.Offset +
+ SharedSecurity->Header.Length);
+
+
+ //
+ // Put the new descriptor into the stream
+ //
+
+ NtOfsPutData( IrpContext, IrpContext->Vcb->SecurityDescriptorStream,
+ SharedSecurity->Header.Offset,
+ SharedSecurity->Header.Length,
+ &SharedSecurity->Header );
+
+
+ //
+ // add id->data map
+ //
+
+ {
+ INDEX_ROW Row;
+
+ Row.KeyPart.KeyLength =
+ sizeof( SharedSecurity->Header.HashKey.SecurityId );
+ Row.KeyPart.Key = &SharedSecurity->Header.HashKey.SecurityId;
+
+ Row.DataPart.DataLength = sizeof( SharedSecurity->Header );
+ Row.DataPart.Data = &SharedSecurity->Header;
+
+ NtOfsAddRecords(
+ IrpContext,
+ IrpContext->Vcb->SecurityIdIndex,
+ 1,
+ &Row,
+ FALSE );
+ }
+
+ //
+ // add hash|id->data map
+ //
+
+ {
+ INDEX_ROW Row;
+
+ Row.KeyPart.KeyLength =
+ sizeof( SharedSecurity->Header.HashKey );
+ Row.KeyPart.Key = &SharedSecurity->Header.HashKey;
+
+ Row.DataPart.DataLength = sizeof( SharedSecurity->Header );
+ Row.DataPart.Data = &SharedSecurity->Header;
+
+ NtOfsAddRecords(
+ IrpContext,
+ IrpContext->Vcb->SecurityDescriptorHashIndex,
+ 1,
+ &Row,
+ FALSE );
+ }
+ } finally {
+ NtfsReleaseScb( IrpContext, IrpContext->Vcb->SecurityDescriptorStream );
+ }
+
+ DebugTrace(-1, Dbg, ("NtOfsGetSecurityIdFromSecurityDescriptor returns %08x\n",
+ SharedSecurity->Header.HashKey.SecurityId));
+
+ return SharedSecurity->Header.HashKey.SecurityId;
+}
+#endif // _CAIRO_
+
+
+//
+// Local Support routine
+//
+
+VOID
+NtfsStoreSecurityDescriptor (
+ PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN LogIt
+ )
+
+/*++
+
+Routine Description:
+
+ This routine stores a new security descriptor already stored in the fcb
+ from memory onto the disk.
+
+Arguments:
+
+ Fcb - Supplies the fcb for the file being operated on
+
+ LogIt - Supplies whether or not the creation of a new security descriptor
+ should/ be logged or not. Modifications are always logged. This
+ parameter must only be specified as FALSE for a file which is currently
+ being created.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+
+ ATTRIBUTE_ENUMERATION_CONTEXT StdInfoContext;
+ BOOLEAN CleanupStdInfoContext = FALSE;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsStoreSecurityDescriptor...\n") );
+
+ //
+ // Initialize the attribute and find the security attribute
+ //
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+ try {
+#ifdef _CAIRO_
+ //
+ // BUGBUG - remove the following IF statement when all volumes get security
+ // descriptor streams.
+ //
+
+ if (Fcb->Vcb->SecurityDescriptorStream != NULL) {
+ //
+ // If the shared security pointer is null, then we are deleting the
+ // security descriptor altogether. If so, and we have a security
+ // attribute, indicated by NOT having large standard info, then we
+ // must delete the security attribute.
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+ if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
+ DebugTrace( 0, Dbg, ("Security Descriptor is null\n") );
+
+ //
+ // Read in the security descriptor attribute if it already
+ // doesn't exist then we're done, otherwise simply delete
+ // the attribute
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $SECURITY_DESCRIPTOR,
+ &AttributeContext )) {
+
+ DebugTrace( 0, Dbg, ("Delete existing Security Descriptor\n") );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &AttributeContext );
+ }
+ }
+
+ leave;
+ }
+
+ //
+ // We are called to replace an existing security descriptor. In the
+ // event that we have a downlevel $STANDARD_INFORMATION attribute, we
+ // must convert it to large form before we store the ACL efficiently.
+ //
+
+ if (!FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO) ) {
+ DebugTrace( 0, Dbg, ("Growing standard information\n") );
+
+ NtfsGrowStandardInformation( IrpContext, Fcb );
+
+ DebugTrace( 0, Dbg, ("Security Descriptor is null\n") );
+
+ //
+ // Read in the security descriptor attribute if it already
+ // doesn't exist then we're done, otherwise simply delete the
+ // attribute
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $SECURITY_DESCRIPTOR,
+ &AttributeContext )) {
+
+ DebugTrace( 0, Dbg, ("Delete existing Security Descriptor\n") );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &AttributeContext );
+ }
+ }
+
+ //
+ // If the shared security descriptor already has an ID assigned, then
+ // use it
+ //
+
+ if (Fcb->SharedSecurity->Header.HashKey.SecurityId != SECURITY_ID_INVALID) {
+ Fcb->SecurityId = Fcb->SharedSecurity->Header.HashKey.SecurityId;
+ DebugTrace( 0, DbgAcl, ("Reusing cached security Id %x\n", Fcb->SecurityId) );
+ } else {
+ //
+ // Find unique SecurityId for descriptor and set SecurityId in Fcb.
+ //
+
+ Fcb->SecurityId = NtOfsGetSecurityIdFromSecurityDescriptor( IrpContext,
+ Fcb->SharedSecurity );
+
+ //
+ // By serializing allocation of Id's, we have a tiny race in here
+ // where two threads could be setting the same security Id into
+ // the shared security.
+ //
+
+ ASSERT( Fcb->SharedSecurity->Header.HashKey.SecurityId == SECURITY_ID_INVALID ||
+ Fcb->SharedSecurity->Header.HashKey.SecurityId == Fcb->SecurityId );
+ Fcb->SharedSecurity->Header.HashKey.SecurityId = Fcb->SecurityId;
+
+ //
+ // Serialize access to the security cache
+ //
+
+ NtfsAcquireFcbSecurity( Fcb->Vcb );
+
+ //
+ // Cache this shared security for faster access
+ //
+
+ NtOfsAddCachedSharedSecurity( Fcb->Vcb, Fcb->SharedSecurity );
+
+ //
+ // Release access to security cache
+ //
+
+ NtfsReleaseFcbSecurity( Fcb->Vcb );
+ }
+
+
+ //
+ // We've changed the standard information for this file. We now must
+ // update the disk to make sure things are consistent.
+ //
+
+
+ leave;
+ }
+#endif // _CAIRO_
+
+ //
+ // Check if the attribute is first being modified or deleted, a null
+ // value means that we are deleting the security descriptor
+ //
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ DebugTrace( 0, Dbg, ("Security Descriptor is null\n") );
+
+ //
+ // If it already doesn't exist then we're done, otherwise simply
+ // delete the attribute
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $SECURITY_DESCRIPTOR,
+ &AttributeContext )) {
+
+ DebugTrace( 0, Dbg, ("Delete existing Security Descriptor\n") );
+
+ NtfsDeleteAttributeRecord( IrpContext,
+ Fcb,
+ TRUE,
+ FALSE,
+ &AttributeContext );
+ }
+
+ leave;
+ }
+
+ //
+ // At this point we are modifying the security descriptor so read in the
+ // security descriptor, if it does not exist then we will need to create
+ // one.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $SECURITY_DESCRIPTOR,
+ &AttributeContext )) {
+
+ DebugTrace( 0, Dbg, ("Create a new Security Descriptor\n") );
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $SECURITY_DESCRIPTOR,
+ NULL, // attribute name
+ &Fcb->SharedSecurity->SecurityDescriptor,
+ GetSharedSecurityLength(Fcb->SharedSecurity),
+ 0, // attribute flags
+ NULL, // where indexed
+ LogIt, // logit
+ &AttributeContext );
+
+ //
+ // We may be modifying the security descriptor of an NT 5.0 volume.
+ // We want to store a SecurityID in the standard information field so
+ // that if we reboot on 5.0 NTFS will know where to find the most
+ // recent security descriptor.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO )) {
+
+ LARGE_STANDARD_INFORMATION StandardInformation;
+
+ //
+ // Initialize the context structure.
+ //
+
+ NtfsInitializeAttributeContext( &StdInfoContext );
+ CleanupStdInfoContext = TRUE;
+
+ //
+ // Locate the standard information, it must be there.
+ //
+
+ if (!NtfsLookupAttributeByCode( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $STANDARD_INFORMATION,
+ &StdInfoContext )) {
+
+ DebugTrace( 0, Dbg, ("Can't find standard information\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ ASSERT( NtfsFoundAttribute( &StdInfoContext )->Form.Resident.ValueLength >= sizeof( LARGE_STANDARD_INFORMATION ));
+
+ //
+ // Copy the existing standard information to our buffer.
+ //
+
+ RtlCopyMemory( &StandardInformation,
+ NtfsAttributeValue( NtfsFoundAttribute( &StdInfoContext )),
+ sizeof( LARGE_STANDARD_INFORMATION ));
+
+ StandardInformation.SecurityId = SECURITY_ID_INVALID;
+ StandardInformation.OwnerId = 0;
+
+ //
+ // Call to change the attribute value.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0,
+ &StandardInformation,
+ sizeof( LARGE_STANDARD_INFORMATION ),
+ FALSE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &StdInfoContext );
+ }
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("Change an existing Security Descriptor\n") );
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ 0, // Value offset
+ &Fcb->SharedSecurity->SecurityDescriptor,
+ GetSharedSecurityLength( Fcb->SharedSecurity ),
+ TRUE, // logit
+ TRUE,
+ FALSE,
+ FALSE,
+ &AttributeContext );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsStoreSecurityDescriptor );
+
+ //
+ // Cleanup our attribute enumeration context
+ //
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+
+ if (CleanupStdInfoContext) {
+
+ NtfsCleanupAttributeContext( &StdInfoContext );
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsStoreSecurityDescriptor -> VOID\n") );
+
+ return;
+}
+
+
+
+/*++
+
+Routine Descriptions:
+
+ Collation routines for security hash index. Collation occurs by Hash first,
+ then security Id
+
+Arguments:
+
+ Key1 - First key to compare.
+
+ Key2 - Second key to compare.
+
+ CollationData - Optional data to support the collation.
+
+Return Value:
+
+ LessThan, EqualTo, or Greater than, for how Key1 compares
+ with Key2.
+
+--*/
+
+#ifdef _CAIRO_
+FSRTL_COMPARISON_RESULT
+NtOfsCollateSecurityHash (
+ IN PINDEX_KEY Key1,
+ IN PINDEX_KEY Key2,
+ IN PVOID CollationData
+ )
+
+{
+ PSECURITY_HASH_KEY HashKey1 = (PSECURITY_HASH_KEY) Key1->Key;
+ PSECURITY_HASH_KEY HashKey2 = (PSECURITY_HASH_KEY) Key2->Key;
+
+ UNREFERENCED_PARAMETER(CollationData);
+
+ PAGED_CODE( );
+
+ ASSERT( Key1->KeyLength == sizeof( SECURITY_HASH_KEY ) );
+ ASSERT( Key2->KeyLength == sizeof( SECURITY_HASH_KEY ) );
+
+ if (HashKey1->Hash < HashKey2->Hash) {
+ return LessThan;
+ } else if (HashKey1->Hash > HashKey2->Hash) {
+ return GreaterThan;
+ } else if (HashKey1->SecurityId < HashKey2->SecurityId) {
+ return LessThan;
+ } else if (HashKey1->SecurityId > HashKey2->SecurityId) {
+ return GreaterThan;
+ } else {
+ return EqualTo;
+ }
+}
+#endif // _CAIRO_
+
+
diff --git a/private/ntos/cntfs/seinfo.c b/private/ntos/cntfs/seinfo.c
new file mode 100644
index 000000000..e7444344b
--- /dev/null
+++ b/private/ntos/cntfs/seinfo.c
@@ -0,0 +1,578 @@
+/*++
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ SeInfo.c
+
+Abstract:
+
+ This module implements the Security Info routines for NTFS called by the
+ dispatch driver.
+
+Author:
+
+ Gary Kimura [GaryKi] 26-Dec-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_SEINFO)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonQuerySecurityInfo)
+#pragma alloc_text(PAGE, NtfsCommonSetSecurityInfo)
+#pragma alloc_text(PAGE, NtfsFsdQuerySecurityInfo)
+#pragma alloc_text(PAGE, NtfsFsdSetSecurityInfo)
+#endif
+
+
+NTSTATUS
+NtfsFsdQuerySecurityInfo (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of the Query Security Information API
+ calls.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the device object to use.
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The Fsd status for the Irp
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdQuerySecurityInfo\n") );
+
+ //
+ // Call the common query Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonQuerySecurityInfo( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdQuerySecurityInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFsdSetSecurityInfo (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of the Set Security Information API
+ calls.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the device object to use.
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The Fsd status for the Irp
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ ASSERT_IRP( Irp );
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdSetSecurityInfo\n") );
+
+ //
+ // Call the common query Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonSetSecurityInfo( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdSetSecurityInfo -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonQuerySecurityInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for querying security information called by
+ both the fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - the return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ BOOLEAN AcquiredFcb = TRUE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonQuerySecurityInfo") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // The only type of opens we accept are user file and directory opens
+ //
+
+ if ((TypeOfOpen != UserFileOpen)
+ && (TypeOfOpen != UserDirectoryOpen)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ //
+ // If the this handle does not open the entire file then refuse access.
+ //
+
+ } else if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // Our operation is to acquire the fcb, do the operation and then
+ // release the fcb. If the security descriptor for this file is
+ // not already loaded we will release the Fcb and then acquire both
+ // the Vcb and Fcb. We must have the Vcb to examine our parent's
+ // security descriptor.
+ //
+
+ NtfsAcquireSharedFcb( IrpContext, Fcb, NULL, FALSE );
+
+ try {
+
+ if (Fcb->SharedSecurity == NULL) {
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ AcquiredFcb = FALSE;
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, FALSE, FALSE );
+ AcquiredFcb = TRUE;
+ }
+
+ Status = NtfsQuerySecurity( IrpContext,
+ Fcb,
+ &IrpSp->Parameters.QuerySecurity.SecurityInformation,
+ (PSECURITY_DESCRIPTOR)Irp->UserBuffer,
+ &IrpSp->Parameters.QuerySecurity.Length );
+
+ if ( Status == STATUS_BUFFER_TOO_SMALL ) {
+
+ Irp->IoStatus.Information = IrpSp->Parameters.QuerySecurity.Length;
+
+ Status = STATUS_BUFFER_OVERFLOW;
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonQuerySecurityInfo );
+
+ if (AcquiredFcb) {
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+ }
+ }
+
+ //
+ // Now complete the request and return to our caller
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQuerySecurityInfo -> %08lx", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonSetSecurityInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Setting security information called by
+ both the fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - the return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+#ifdef _CAIRO_
+ PQUOTA_CONTROL_BLOCK OldQuotaControl;
+ ULONG OldOwnerId;
+ ULONG LargeStdInfo;
+#endif // _CAIRO_
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonSetSecurityInfo") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // The only type of opens we accept are user file and directory opens
+ //
+
+ if ((TypeOfOpen != UserFileOpen)
+ && (TypeOfOpen != UserDirectoryOpen)) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ //
+ // If the this handle does not open the entire file then refuse access.
+ //
+
+ } else if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_AS_FILE )) {
+
+ Status = STATUS_INVALID_PARAMETER;
+
+ } else {
+
+ //
+ // Our operation is to acquire the fcb, do the operation and then
+ // release the fcb
+ //
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, FALSE, FALSE );
+
+ try {
+
+#ifdef _CAIRO_
+
+ //
+ // Capture the current OwnerId, Qutoa Control Block and
+ // size of standard information.
+ //
+
+ OldQuotaControl = Fcb->QuotaControl;
+ OldOwnerId = Fcb->OwnerId;
+ LargeStdInfo = Fcb->FcbState & FCB_STATE_LARGE_STD_INFO;
+
+#endif // _CAIRO_
+
+ Status = NtfsModifySecurity( IrpContext,
+ Fcb,
+ &IrpSp->Parameters.SetSecurity.SecurityInformation,
+ IrpSp->Parameters.SetSecurity.SecurityDescriptor );
+
+ if (NT_SUCCESS( Status )) {
+
+#ifdef _CAIRO_
+ //
+ // Make sure the new security descriptor Id is written out.
+ //
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+#endif
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ //
+ // Set the flag in the Ccb to indicate this change occurred.
+ //
+
+ SetFlag( Ccb->Flags,
+ CCB_FLAG_UPDATE_LAST_CHANGE | CCB_FLAG_SET_ARCHIVE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonSetSecurityInfo );
+
+#ifdef _CAIRO_
+ if (AbnormalTermination()) {
+
+ //
+ // The request failed. Restore the owner and
+ // QuotaControl are restored.
+ //
+
+ if (Fcb->QuotaControl != OldQuotaControl &&
+ Fcb->QuotaControl != NULL) {
+
+ //
+ // A new quota control block was assigned.
+ // Dereference it.
+ //
+
+ NtfsDereferenceQuotaControlBlock( Fcb->Vcb,
+ &Fcb->QuotaControl );
+ }
+
+ Fcb->QuotaControl = OldQuotaControl;
+ Fcb->OwnerId = OldOwnerId;
+
+ if (LargeStdInfo == 0) {
+
+ //
+ // The standard information has be returned to
+ // its orginal size.
+ //
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_LARGE_STD_INFO );
+ }
+
+ } else {
+
+ //
+ // The request succeed. If the quota control block was
+ // changed then derefence the old block.
+ //
+
+ if (Fcb->QuotaControl != OldQuotaControl &&
+ OldQuotaControl != NULL) {
+
+ NtfsDereferenceQuotaControlBlock( Fcb->Vcb,
+ &OldQuotaControl);
+
+ }
+ }
+#endif // _CAIRO_
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+ }
+
+ //
+ // Now complete the request and return to our caller
+ //
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetSecurityInfo -> %08lx", Status) );
+
+ return Status;
+}
+
diff --git a/private/ntos/cntfs/shutdown.c b/private/ntos/cntfs/shutdown.c
new file mode 100644
index 000000000..927574fd3
--- /dev/null
+++ b/private/ntos/cntfs/shutdown.c
@@ -0,0 +1,340 @@
+/*++
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ Shutdown.c
+
+Abstract:
+
+ This module implements the file system shutdown routine for Ntfs
+
+Author:
+
+ Gary Kimura [GaryKi] 19-Aug-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Interal support routine
+//
+
+VOID
+NtfsCheckpointVolumeUntilDone (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ );
+
+//
+// Local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_SHUTDOWN)
+
+
+NTSTATUS
+NtfsFsdShutdown (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of shutdown. Note that Shutdown will
+ never be done asynchronously so we will never need the Fsp counterpart
+ to shutdown.
+
+ This is the shutdown routine for the Ntfs file system device driver.
+ This routine locks the global file system lock and then syncs all the
+ mounted volumes.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - Always STATUS_SUCCESS
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ PIRP_CONTEXT IrpContext;
+
+ PKEVENT Event;
+
+ PLIST_ENTRY Links;
+ PVCB Vcb;
+ PIRP NewIrp;
+ IO_STATUS_BLOCK Iosb;
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+
+ DebugTrace( +1, Dbg, ("NtfsFsdShutdown\n") );
+
+ //
+ // Allocate an Irp Context that we can use in our procedure calls
+ // and we know that shutdown will always be synchronous
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ IrpContext = NtfsCreateIrpContext( Irp, TRUE );
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ //
+ // Allocate an initialize an event for doing calls down to
+ // our target device objects
+ //
+
+ Event = (PKEVENT)ExAllocateFromNPagedLookasideList( &NtfsKeventLookasideList );
+ KeInitializeEvent( Event, NotificationEvent, FALSE );
+
+ //
+ // Get everyone else out of the way
+ //
+
+ NtfsAcquireExclusiveGlobal( IrpContext );
+
+ try {
+
+ BOOLEAN AcquiredFiles;
+ BOOLEAN AcquiredCheckpoint;
+
+ //
+ // For every volume that is mounted we will flush the
+ // volume and then shutdown the target device objects.
+ //
+
+ for (Links = NtfsData.VcbQueue.Flink;
+ Links != &NtfsData.VcbQueue;
+ Links = Links->Flink) {
+
+ //
+ // Get the Vcb and put it in the IrpContext.
+ //
+
+ Vcb = CONTAINING_RECORD(Links, VCB, VcbLinks);
+ IrpContext->Vcb = Vcb;
+
+ //
+ // If we have already been called before for this volume
+ // (and yes this does happen), skip this volume as no writes
+ // have been allowed since the first shutdown.
+ //
+
+ if ( FlagOn( Vcb->VcbState, VCB_STATE_FLAG_SHUTDOWN ) ) {
+
+ continue;
+ }
+
+ //
+ // Clear the Mft defrag flag to stop any actions behind our backs.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->MftDefragState, VCB_MFT_DEFRAG_PERMITTED );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ AcquiredFiles = FALSE;
+ AcquiredCheckpoint = FALSE;
+
+ try {
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ //
+ // Start by locking out all other checkpoint
+ // operations.
+ //
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+
+ while (FlagOn( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS )) {
+
+ //
+ // Release the checkpoint event because we cannot checkpoint now.
+ //
+
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+
+ NtfsWaitOnCheckpointNotify( IrpContext, Vcb );
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ }
+
+ SetFlag( Vcb->CheckpointFlags, VCB_CHECKPOINT_IN_PROGRESS );
+ NtfsResetCheckpointNotify( IrpContext, Vcb );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ AcquiredCheckpoint = TRUE;
+
+ NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, TRUE );
+ AcquiredFiles = TRUE;
+
+ SetFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
+
+ NtfsCheckpointVolumeUntilDone( IrpContext, Vcb );
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ NtfsStopLogFile( Vcb );
+
+ NewIrp = IoBuildSynchronousFsdRequest( IRP_MJ_SHUTDOWN,
+ Vcb->TargetDeviceObject,
+ NULL,
+ 0,
+ NULL,
+ Event,
+ &Iosb );
+
+ if (NewIrp == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ if (NT_SUCCESS(IoCallDriver( Vcb->TargetDeviceObject, NewIrp ))) {
+
+ (VOID) KeWaitForSingleObject( Event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL );
+
+ KeClearEvent( Event );
+ }
+ }
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ if (AcquiredCheckpoint) {
+
+ NtfsAcquireCheckpoint( IrpContext, Vcb );
+ ClearFlag( Vcb->CheckpointFlags,
+ VCB_CHECKPOINT_IN_PROGRESS | VCB_DUMMY_CHECKPOINT_POSTED);
+ NtfsSetCheckpointNotify( IrpContext, Vcb );
+ NtfsReleaseCheckpoint( IrpContext, Vcb );
+ }
+
+ SetFlag( Vcb->VcbState, VCB_STATE_FLAG_SHUTDOWN );
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOL_PURGE_IN_PROGRESS );
+
+ if (AcquiredFiles) {
+
+ NtfsReleaseAllFiles( IrpContext, Vcb, TRUE );
+ }
+ }
+
+ } finally {
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ ExFreeToNPagedLookasideList( &NtfsKeventLookasideList, Event );
+
+ NtfsReleaseGlobal( IrpContext );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFsdShutdown -> STATUS_SUCCESS\n") );
+
+ return STATUS_SUCCESS;
+}
+
+
+VOID
+NtfsCheckpointVolumeUntilDone (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine keeps trying to checkpoint/flush a volume until it
+ works. Doing clean checkpoints and looping back to retry on log file full.
+
+Arguments:
+
+ Vcb - Vcb to checkpoint til done
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ do {
+
+ Status = STATUS_SUCCESS;
+
+ try {
+ NtfsCheckpointVolume( IrpContext,
+ Vcb,
+ TRUE,
+ TRUE,
+ TRUE,
+ Vcb->LastRestartArea );
+ } except( (Status = GetExceptionCode()), EXCEPTION_EXECUTE_HANDLER ) {
+ NOTHING;
+ }
+
+ if (!NT_SUCCESS(Status)) {
+
+ //
+ // To make sure that we can access all of our streams correctly,
+ // we first restore all of the higher sizes before aborting the
+ // transaction. Then we restore all of the lower sizes after
+ // the abort, so that all Scbs are finally restored.
+ //
+
+ NtfsRestoreScbSnapshots( IrpContext, TRUE );
+ NtfsAbortTransaction( IrpContext, IrpContext->Vcb, NULL );
+ NtfsRestoreScbSnapshots( IrpContext, FALSE );
+
+ //
+ // A clean volume checkpoint should never get log file full
+ //
+
+ if (Status == STATUS_LOG_FILE_FULL) {
+
+ //
+ // Make sure we don't leave the error code in the top-level
+ // IrpContext field.
+ //
+
+ ASSERT( IrpContext->TransactionId == 0 );
+ IrpContext->ExceptionStatus = STATUS_SUCCESS;
+
+ NtfsCheckpointVolume( IrpContext,
+ Vcb,
+ TRUE,
+ TRUE,
+ FALSE,
+ Vcb->LastRestartArea );
+ }
+ }
+
+ } while (Status == STATUS_LOG_FILE_FULL);
+}
diff --git a/private/ntos/cntfs/sources.inc b/private/ntos/cntfs/sources.inc
new file mode 100644
index 000000000..765975b01
--- /dev/null
+++ b/private/ntos/cntfs/sources.inc
@@ -0,0 +1,89 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+MAJORCOMP=ntos
+MINORCOMP=ntfs
+
+TARGETNAME=ntfs
+TARGETTYPE=DRIVER
+
+INCLUDES=..;..\..\inc
+
+NTPROFILEINPUT=yes
+
+
+
+C_DEFINES=$(C_DEFINES) -DNTFS_ALLOW_COMPRESSED -D_NTDRIVER_
+
+!IFDEF BUILD_FOR_3_51
+C_DEFINES= $(C_DEFINES) -D_NTIFS_
+!ENDIF
+
+MSC_WARNING_LEVEL=/W3 /WX
+
+SOURCES=..\AllocSup.c \
+ ..\AttrData.c \
+ ..\AttrSup.c \
+ ..\BitmpSup.c \
+ ..\CacheSup.c \
+ ..\CheckSup.c \
+ ..\Cleanup.c \
+ ..\Close.c \
+ ..\ColatSup.c \
+ ..\Create.c \
+ ..\DevCtrl.c \
+ ..\DevIoSup.c \
+ ..\DirCtrl.c \
+ ..\Ea.c \
+ ..\FileInfo.c \
+ ..\FilObSup.c \
+ ..\Flush.c \
+ ..\FsCtrl.c \
+ ..\FspDisp.c \
+ ..\FstIoSup.c \
+ ..\IndexSup.c \
+ ..\LockCtrl.c \
+ ..\LogSup.c \
+ ..\McbSup.c \
+ ..\MftSup.c \
+ ..\NameSup.c \
+ ..\Ntfs.rc \
+ ..\NtfsData.c \
+ ..\NtfsInit.c \
+ ..\PrefxSup.c \
+ ..\Read.c \
+ ..\ResrcSup.c \
+ ..\RestrSup.c \
+ ..\SecurSup.c \
+ ..\SeInfo.c \
+ ..\Shutdown.c \
+ ..\StrucSup.c \
+ ..\VerfySup.c \
+ ..\VolInfo.c \
+ ..\WorkQue.c \
+ ..\Write.c
+
+PRECOMPILED_INCLUDE=..\ntfsproc.h
+PRECOMPILED_PCH=ntfsproc.pch
+PRECOMPILED_OBJ=ntfsproc.obj
diff --git a/private/ntos/cntfs/spec/acls.doc b/private/ntos/cntfs/spec/acls.doc
new file mode 100644
index 000000000..4b6cf2644
--- /dev/null
+++ b/private/ntos/cntfs/spec/acls.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/devplan.doc b/private/ntos/cntfs/spec/devplan.doc
new file mode 100644
index 000000000..471b01c5e
--- /dev/null
+++ b/private/ntos/cntfs/spec/devplan.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/journal.doc b/private/ntos/cntfs/spec/journal.doc
new file mode 100644
index 000000000..0e687c494
--- /dev/null
+++ b/private/ntos/cntfs/spec/journal.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/ntfs.ppt b/private/ntos/cntfs/spec/ntfs.ppt
new file mode 100644
index 000000000..36648fb00
--- /dev/null
+++ b/private/ntos/cntfs/spec/ntfs.ppt
Binary files differ
diff --git a/private/ntos/cntfs/spec/ntofsods.doc b/private/ntos/cntfs/spec/ntofsods.doc
new file mode 100644
index 000000000..4c4b81dde
--- /dev/null
+++ b/private/ntos/cntfs/spec/ntofsods.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/props.doc b/private/ntos/cntfs/spec/props.doc
new file mode 100644
index 000000000..c459549b4
--- /dev/null
+++ b/private/ntos/cntfs/spec/props.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/quota.doc b/private/ntos/cntfs/spec/quota.doc
new file mode 100644
index 000000000..c493128da
--- /dev/null
+++ b/private/ntos/cntfs/spec/quota.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/rwcmp.doc b/private/ntos/cntfs/spec/rwcmp.doc
new file mode 100644
index 000000000..303562d18
--- /dev/null
+++ b/private/ntos/cntfs/spec/rwcmp.doc
Binary files differ
diff --git a/private/ntos/cntfs/spec/usn.doc b/private/ntos/cntfs/spec/usn.doc
new file mode 100644
index 000000000..bea7edaae
--- /dev/null
+++ b/private/ntos/cntfs/spec/usn.doc
Binary files differ
diff --git a/private/ntos/cntfs/specnot2.txt b/private/ntos/cntfs/specnot2.txt
new file mode 100644
index 000000000..5c4444d81
--- /dev/null
+++ b/private/ntos/cntfs/specnot2.txt
@@ -0,0 +1,243 @@
+Here is a list of all NTFS design issues which have come up that effect the
+structure, along with current resolution (if there is one) of the issue. The
+resolution of these issues affects the "NTFS Design Specification 1.1" issued
+May 29, 1991. This list will be the final qualification to the spec until
+there is time to update it to a form which reflects the actual implementation.
+Of course the most precise definition of NTFS will always be in the header
+file which describes its structure: ntfs.h.
+
+These issues have been collected primarily from our own internal review and
+the feedback received from MarkZ. They are listed here in no particular
+order.
+
+Issue 1:
+
+ Support for nontagged attributes is a pain in the low-level attribute
+ routines, as well as in Format and ChkDsk. They are of very little
+ value to the File System in terms of space or performance.
+
+ Resolution:
+
+ Nontagged attributes are being dropped for the purposes of NTFS's own
+ use of attributes to implement the disk structure. Nontagged attributes
+ will be supported with the general table support.
+
+Issue 2:
+
+ The EXTERNAL_ATTRIBUTES attribute, should have a better name, and its
+ definition should be changed to simplify various NTFS algorithms.
+
+ Resolution:
+
+ The attribute name has been changed to the ATTRIBUTE_LIST attribute.
+ It is still only created when a file requires more than one file record
+ segment. At that time it is created to list all attributes (including
+ those in the base file record) by type code and (optional) name. it is
+ ordered by Attribute Type Code and Attribute Name.
+
+ One reason for this change is to facilitate the enumeration of all
+ attributes for a file with multiple file record segments. This
+ slightly different definition also gives NTFS's attribute placement
+ policy more freedom to shuffle attributes around within the file
+ record segments.
+
+Issue 3:
+
+ Attribute ordering rules within the file, within each file record segment,
+ and within the ATTRIBUTE_LIST were not completely specified.
+
+ Resolution:
+
+ The only rule for the ordering of attributes within each file, if there
+ are multiple file record segments, is that STANDARD_INFORMATION must be
+ in the base file record segment, and (at least the first part of) the
+ ATTRIBUTE_LIST attribute must also be in the base file record segment.
+ In general, the system should try to keep the other system-defined
+ attributes with the lowest Attribute Type Codes present in the base file
+ record segment when possible, for performance reasons.
+
+ Within each file record segment, attributes will be ordered by type code,
+ name, and then value. (If an attribute is not unique in type code and
+ name, then it must be indexed and the value must be referenced.)
+
+ The entries of the ATTRIBUTE_LIST will be ordered by attribute code and
+ name.
+
+ Reliance on these ordering rules may be used to speed up attribute lookup
+ algorithms.
+
+Issue 4:
+
+ NTFS is NOT secure on removeable media without data encryption.
+
+ Resolution:
+
+ Functionality for the encryption of communications and physical media
+ is already planned for Product 2 of NT, at which time we will decide
+ what the best mechanism will be for integrating this support with
+ removeable NTFS volumes. We must insure now that this can be implemented
+ in a upward-compatible manner.
+
+Issue 5:
+
+ It would be very desirable for WINX to have the ability to uniquely
+ identify and open files by a small number.
+
+ Resolution:
+
+ Logically the ability to use this functionality must be controlled by
+ some privilege, as it is expensive and nearly impossible to come up with a
+ consistent strategy on how to do correct path traversal checking, in a
+ system such as NTFS which supports multiple directory links to a single
+ file. Once the requirement for a special privilege is accepted, it is
+ relatively easy for NTFS to support an API which would allow files to
+ be opened by their (64-bit) File Reference number. The File Reference
+ is perfect for this purpose, as it includes a 16-bit cyclically-reused
+ sequence number to detect the attempt to use a stale File Reference.
+ I.e., the original file with the same 48-bit Base File Record address has
+ been deleted, and a new file has been created at the same address.)
+
+ THIS REQUIRES A NEW NT I/O API.
+
+Issue 6:
+
+ Enumeration of files in a directory in NT could be very slow, since
+ to get more than just a file's name requires reading (at least) the
+ base file record for the file.
+
+ Resolution:
+
+ The initial NT-based implementation of NTFS will come up with a
+ strategy for clustering file record segments together in the MFT for
+ files created in the same directory. Current thinking is that this
+ will be done *without* change to the NTFS structure definition. So,
+ for example, the first 128 files in a directory might be contiguous in
+ the MFT, and then the second 128 will also be contiguous, etc. This
+ will allow the implementation to prefetch files up to 128 file record
+ segments at a time with a large spiral read, then expect cache hits during
+ the enumeration.
+
+ Secondly, at some point the implementation will cache enumeration
+ information, to make subsequent enumeration of the same directory
+ extremely fast.
+
+Issue 7:
+
+ Is it an unnecessary complexity to NTFS to support multiple collating
+ rules, as opposed to a simple byte-comparison collation? Note that
+ frequently the caller collates himself anyway.
+
+ Resolution:
+
+ This is not resolved yet pending further discussion.
+
+ The current reason NTFS plans to support multiple collating rules,
+ is that collating in the caller can have bad performance and response
+ characteristics in large directories. For example, consider a Windows
+ App which requests the enumeration of a directory with 200 files (possibly
+ over the network to a heavily loaded server), and it is going to
+ display this enumeration in a List box with 10 or 20 lines. If it
+ does not have to collate the enumeration, it can start displaying
+ as soon as it receives part of the enumeration. Otherwise it has
+ to wait to get the entire enumeration before it can collate and display
+ anything.
+
+Issue 8:
+
+ Should there be a bit in STANDARD_INFORMATION to indicate whether a
+ file record has an INDEX attribute or not?
+
+ Resolution:
+
+ There is no plan to do this, unless we find additional reasons
+ to do so that we are missing. Currently we see how this bit could
+ speed the rejection of illegal path specifications, but it would
+ not speed the acceptance of correct ones. Note that from the structure
+ of NTFS, it is legal for a file to have both an INDEX attribute *and*,
+ for example, a DATA attribute.
+
+Issue 9:
+
+ The algorithms and consistency rules surrounding the 8.3 indices need to
+ be clarified.
+
+ Resolution:
+
+ This will be done by 7/31.
+
+Issue 10:
+
+ Why not eliminate the VERSION attribute and move it to
+ STANDARD_INFORMATION?
+
+ Resolution:
+
+ We will do this, and then define an additional file attribute
+ and/or field which controls whether or not versioning is enabled and
+ possibly how many versions are allowed for a file.
+
+Issue 11:
+
+ There should be a range of system-defined attribute codes which are
+ not allowed to be duplicated, as this will speed up some of the
+ lookup algorithms.
+
+ Resolution:
+
+ This will be done.
+
+Issue 12:
+
+ Is duplication of the log file the correct way to add redundancy to
+ NTFS to allow mounting in the event of read errors.
+
+ Resolution:
+
+ Upon further analysis, it was determined that the needed redundancy
+ was incorrectly placed. It is more important to duplicate the first
+ few entries of the MFT, than to duplicate the start of the log file.
+ This change will be made.
+
+Issue 13:
+
+ The spec describes how access to individual attribute types may be
+ controlled by special ACEs, which is incompatible with the current
+ NT APIs and our security strategy.
+
+ Resolution:
+
+ This will be fixed. Access to user-defined attributes will be controlled
+ by the READ_ATTRIBUTES and WRITE_ATTRIBUTES access rights.
+
+Issue 14:
+
+ A file attribute should be added which supports more efficient handling
+ of temporary files.
+
+ Resolution:
+
+ An attribute will be added for files, and possibly directories, which
+ will enable NTFS to communicate "temporary file" handling to the Cache
+ Manager. Temporary files will never be set dirty in the Cache Manager
+ or written to disk by the Lazy Writer, although the File Record will
+ be correctly updated to keep the volume consistant. If a temporary file
+ is deleted, then all writes to its data are eliminated. If MM discovers
+ that memory is getting tight, it may choose to flush data to temporary
+ files, so that it can free the pages. In this case the
+ correct data for the file will eventually be faulted back in.
+
+ This makes the performance of I/O to temporary files approach the
+ performance of putting them on a RAM disk. An advantage over RAM disk,
+ though, is that no one has to specify how much space should be used
+ for this purpose.
+
+Issue 15:
+
+ It would be nice to have some flag in each file record segment to say
+ if it is in use or not. This would simplify chkdsk algorithms, although
+ it would require the record to be written on deletion.
+
+ Resolution:
+
+ This will be done. It is difficult to suppress the write of the file
+ record on deletion anyway.
diff --git a/private/ntos/cntfs/specnote.txt b/private/ntos/cntfs/specnote.txt
new file mode 100644
index 000000000..90f2b24fc
--- /dev/null
+++ b/private/ntos/cntfs/specnote.txt
@@ -0,0 +1,47 @@
+
+These are a collection of notes that should be updated in the NTFS Design
+Specification. These do not include many changes which will be obvious
+from a pass through ntfs.h - which will be a necessary activity for the
+next spec update.
+
+ File Number changed to File Reference, to denote that its use can
+ become invalid (sequence number incremented).
+
+ The second copy of the log is not needed, but a second copy of the MFT
+ is, which just has the same first three file records: MFT, MFT2 and LOG.
+
+ Add a discussion of the Boot Sector / Boot file, and a figure to show
+ dual boot records and dual MFTs.
+
+ Forget the security on attributes, just use READ_ATTRIBUTE, WRITE_ATTRIBUTE
+ privilege on the file.
+
+ The boot file is strictly a matter between Format, bootstrap, and the Mount
+ code. Where it goes and what its contents are will be system-specific.
+ For NT:
+
+ There will be one boot record at sector 0, and one on the last
+ sector of the volume. Each boot record will contain the cluster size
+ and the starting LCN for the Master File, and the Master File 2.
+ As a suggestion, Format on NT should start the MFT at LCN = 1 on
+ the disk, and MFT2 should start three file record segments before the
+ other boot record. MFT2 contains mirrored copies of the first file
+ record segment for the MFT, MFT2, and the Log file. The boot file
+ on NT just contains these two boot records.
+
+ DOS may choose to actually have a bootstrap following the first
+ boot record in the boot file, and it could just be the first N
+ clusters of the disk.
+
+ "Ideas by Butzi":
+
+ Disks should schedule reads ahead of writes
+
+ Have some way to detect temporary files (file attribute
+ and/or directory attribute), and never write their data
+ (or update valid data length). This would be like putting
+ \temp on the RAM disk on OS/2.
+
+ make writes on close a dynamic option?
+
+
diff --git a/private/ntos/cntfs/strucsup.c b/private/ntos/cntfs/strucsup.c
new file mode 100644
index 000000000..ab8ef2d9d
--- /dev/null
+++ b/private/ntos/cntfs/strucsup.c
@@ -0,0 +1,7383 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ StrucSup.c
+
+
+Abstract:
+
+ This module implements the Ntfs in-memory data structure manipulation
+ routines
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+ Tom Miller [TomM] 9-Sep-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+//**** include this file for our quick hacked quota check in NtfsFreePool
+//
+
+//#include <pool.h>
+
+//
+// Temporarily reference our local attribute definitions
+//
+
+extern ATTRIBUTE_DEFINITION_COLUMNS NtfsAttributeDefinitions[];
+
+//
+// The debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_STRUCSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('sFtN')
+
+//
+// Define a structure to use when renaming or moving Lcb's so that
+// all the allocation for new filenames will succeed before munging names.
+// This new allocation can be for the filename attribute in an Lcb or the
+// filename in a Ccb.
+//
+
+typedef struct _NEW_FILENAME {
+
+ //
+ // Ntfs structure which needs the allocation.
+ //
+
+ PVOID Structure;
+ PVOID NewAllocation;
+
+} NEW_FILENAME;
+typedef NEW_FILENAME *PNEW_FILENAME;
+
+//
+// This is just a macro to do a sanity check for duplicate scbs on an Fcb
+//
+
+//
+// Local support routines
+//
+
+VOID
+NtfsCheckScbForCache (
+ IN OUT PSCB Scb
+ );
+
+BOOLEAN
+NtfsRemoveScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN CheckForAttributeTable,
+ OUT PBOOLEAN HeldByStream
+ );
+
+BOOLEAN
+NtfsPrepareFcbForRemoval (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB StartingScb OPTIONAL,
+ IN BOOLEAN CheckForAttributeTable
+ );
+
+VOID
+NtfsTeardownFromLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB StartingFcb,
+ IN PLCB StartingLcb,
+ IN BOOLEAN CheckForAttributeTable,
+ IN BOOLEAN DontWaitForAcquire,
+ OUT PBOOLEAN RemovedStartingLcb,
+ OUT PBOOLEAN RemovedStartingFcb
+ );
+
+
+//
+// The following local routines are for manipulating the Fcb Table.
+// The first three are generic table calls backs.
+//
+
+RTL_GENERIC_COMPARE_RESULTS
+NtfsFcbTableCompare (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN PVOID FirstStruct,
+ IN PVOID SecondStruct
+ );
+
+//
+// VOID
+// NtfsInsertFcbTableEntry (
+// IN PIRP_CONTEXT IrpContext,
+// IN PVCB Vcb,
+// IN PFCB Fcb,
+// IN FILE_REFERENCE FileReference
+// );
+//
+
+#define NtfsInsertFcbTableEntry(IC,V,F,FR) { \
+ FCB_TABLE_ELEMENT _Key; \
+ _Key.FileReference = (FR); \
+ _Key.Fcb = (F); \
+ (VOID) RtlInsertElementGenericTable( &(V)->FcbTable, \
+ &_Key, \
+ sizeof(FCB_TABLE_ELEMENT), \
+ NULL ); \
+}
+
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsBuildNormalizedName)
+#pragma alloc_text(PAGE, NtfsCheckScbForCache)
+#pragma alloc_text(PAGE, NtfsCombineLcbs)
+#pragma alloc_text(PAGE, NtfsCreateCcb)
+#pragma alloc_text(PAGE, NtfsCreateFcb)
+#pragma alloc_text(PAGE, NtfsCreateFileLock)
+#pragma alloc_text(PAGE, NtfsCreateLcb)
+#pragma alloc_text(PAGE, NtfsCreatePrerestartScb)
+#pragma alloc_text(PAGE, NtfsCreateRootFcb)
+#pragma alloc_text(PAGE, NtfsCreateScb)
+#pragma alloc_text(PAGE, NtfsDeleteCcb)
+#pragma alloc_text(PAGE, NtfsDeleteFcb)
+#pragma alloc_text(PAGE, NtfsDeleteLcb)
+#pragma alloc_text(PAGE, NtfsDeleteScb)
+#pragma alloc_text(PAGE, NtfsDeleteVcb)
+#pragma alloc_text(PAGE, NtfsFcbTableCompare)
+#pragma alloc_text(PAGE, NtfsGetNextFcbTableEntry)
+#pragma alloc_text(PAGE, NtfsGetNextScb)
+#pragma alloc_text(PAGE, NtfsInitializeVcb)
+#pragma alloc_text(PAGE, NtfsLookupLcbByFlags)
+#pragma alloc_text(PAGE, NtfsMoveLcb)
+#pragma alloc_text(PAGE, NtfsRemoveScb)
+#pragma alloc_text(PAGE, NtfsRenameLcb)
+#pragma alloc_text(PAGE, NtfsTeardownStructures)
+#pragma alloc_text(PAGE, NtfsUpdateNormalizedName)
+#pragma alloc_text(PAGE, NtfsUpdateScbSnapshots)
+#endif
+
+
+VOID
+NtfsInitializeVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB Vcb,
+ IN PDEVICE_OBJECT TargetDeviceObject,
+ IN PVPB Vpb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes and inserts a new Vcb record into the in-memory
+ data structure. The Vcb record "hangs" off the end of the Volume device
+ object and must be allocated by our caller.
+
+Arguments:
+
+ Vcb - Supplies the address of the Vcb record being initialized.
+
+ TargetDeviceObject - Supplies the address of the target device object to
+ associate with the Vcb record.
+
+ Vpb - Supplies the address of the Vpb to associate with the Vcb record.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG i;
+ ULONG NumberProcessors;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsInitializeVcb, Vcb = %08lx\n", Vcb) );
+
+ //
+ // First zero out the Vcb
+ //
+
+ RtlZeroMemory( Vcb, sizeof(VCB) );
+
+ //
+ // Set the node type code and size
+ //
+
+ Vcb->NodeTypeCode = NTFS_NTC_VCB;
+ Vcb->NodeByteSize = sizeof(VCB);
+
+ //
+ // Set the following Vcb flags before putting the Vcb in the
+ // Vcb queue. This will lock out checkpoints until the
+ // volume is mounted.
+ //
+
+ SetFlag( Vcb->CheckpointFlags,
+ VCB_CHECKPOINT_IN_PROGRESS |
+ VCB_LAST_CHECKPOINT_CLEAN);
+
+ //
+ // Insert this vcb record into the vcb queue off of the global data
+ // record
+ //
+
+ InsertTailList( &NtfsData.VcbQueue, &Vcb->VcbLinks );
+
+ //
+ // Set the target device object and vpb fields
+ //
+
+ Vcb->TargetDeviceObject = TargetDeviceObject;
+ Vcb->Vpb = Vpb;
+
+ //
+ // Set the state and condition fields. The removable media flag
+ // is set based on the real device's characteristics.
+ //
+
+ if (FlagOn(Vpb->RealDevice->Characteristics, FILE_REMOVABLE_MEDIA)) {
+
+ SetFlag( Vcb->VcbState, VCB_STATE_REMOVABLE_MEDIA );
+ }
+
+ SetFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
+
+ //
+ // Initialize the synchronization objects in the Vcb.
+ //
+
+ ExInitializeResource( &Vcb->Resource );
+
+ ExInitializeFastMutex( &Vcb->FcbTableMutex );
+ ExInitializeFastMutex( &Vcb->FcbSecurityMutex );
+ ExInitializeFastMutex( &Vcb->CheckpointMutex );
+
+ KeInitializeEvent( &Vcb->CheckpointNotifyEvent, NotificationEvent, TRUE );
+
+ //
+ // Initialize the Fcb Table
+ //
+
+ RtlInitializeGenericTable( &Vcb->FcbTable,
+ NtfsFcbTableCompare,
+ NtfsAllocateFcbTableEntry,
+ NtfsFreeFcbTableEntry,
+ NULL );
+
+ //
+ // Initialize the list head and mutex for the dir notify Irps.
+ // Also the rename resource.
+ //
+
+ InitializeListHead( &Vcb->DirNotifyList );
+ FsRtlNotifyInitializeSync( &Vcb->NotifySync );
+
+ //
+ // Allocate and initialize struct array for performance data. This
+ // attempt to allocate could raise STATUS_INSUFFICIENT_RESOURCES.
+ //
+
+ NumberProcessors = **((PCHAR *)&KeNumberProcessors);
+ Vcb->Statistics = NtfsAllocatePool( NonPagedPool,
+ sizeof(FILESYSTEM_STATISTICS) * NumberProcessors );
+
+ RtlZeroMemory( Vcb->Statistics, sizeof(FILESYSTEM_STATISTICS) * NumberProcessors );
+
+ for (i = 0; i < NumberProcessors; i += 1) {
+ Vcb->Statistics[i].FileSystemType = FILESYSTEM_STATISTICS_TYPE_NTFS;
+ Vcb->Statistics[i].Version = 1;
+ }
+
+ //
+ // Initialize the property tunneling structure
+ //
+
+ FsRtlInitializeTunnelCache(&Vcb->Tunnel);
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsInitializeVcb -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsDeleteVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PVCB *Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine removes the Vcb record from Ntfs's in-memory data
+ structures.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to be removed
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PVOLUME_DEVICE_OBJECT VolDo;
+ BOOLEAN AcquiredFcb;
+ PSCB Scb;
+ PFCB Fcb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( *Vcb );
+
+ ASSERTMSG("Cannot delete Vcb ", !FlagOn((*Vcb)->VcbState, VCB_STATE_VOLUME_MOUNTED));
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteVcb, *Vcb = %08lx\n", *Vcb) );
+
+ //
+ // Make sure that we can really delete the vcb
+ //
+
+ ASSERT( (*Vcb)->CloseCount == 0 );
+
+#ifdef _CAIRO_
+ NtOfsPurgeSecurityCache( *Vcb );
+#endif
+
+ //
+ // If the Vcb log file object is present then we need to
+ // dereference it and uninitialize it through the cache.
+ //
+
+ if ((*Vcb)->LogFileObject != NULL) {
+
+ CACHE_UNINITIALIZE_EVENT UninitializeCompleteEvent;
+ NTSTATUS WaitStatus;
+
+ KeInitializeEvent( &UninitializeCompleteEvent.Event,
+ SynchronizationEvent,
+ FALSE);
+
+ CcUninitializeCacheMap( (*Vcb)->LogFileObject,
+ &NtfsLarge0,
+ &UninitializeCompleteEvent );
+
+ //
+ // Now wait for the cache manager to finish purging the file.
+ // This will garentee that Mm gets the purge before we
+ // delete the Vcb.
+ //
+
+ WaitStatus = KeWaitForSingleObject( &UninitializeCompleteEvent.Event,
+ Executive,
+ KernelMode,
+ FALSE,
+ NULL);
+
+ ASSERT( NT_SUCCESS( WaitStatus ) );
+
+ (*Vcb)->LogFileObject->FsContext = NULL;
+ (*Vcb)->LogFileObject->FsContext2 = NULL;
+
+ ObDereferenceObject( (*Vcb)->LogFileObject );
+ (*Vcb)->LogFileObject = NULL;
+ }
+
+ //
+ // Uninitialize the Mcb's for the deallocated cluster Mcb's.
+ //
+
+ if ((*Vcb)->PriorDeallocatedClusters != NULL) {
+
+ FsRtlUninitializeLargeMcb( &(*Vcb)->PriorDeallocatedClusters->Mcb );
+ (*Vcb)->PriorDeallocatedClusters = NULL;
+ }
+
+ if ((*Vcb)->ActiveDeallocatedClusters != NULL) {
+
+ FsRtlUninitializeLargeMcb( &(*Vcb)->ActiveDeallocatedClusters->Mcb );
+ (*Vcb)->ActiveDeallocatedClusters = NULL;
+ }
+
+ //
+ // Clean up the Root Lcb if present.
+ //
+
+ if ((*Vcb)->RootLcb != NULL) {
+
+ //
+ // Cleanup the Lcb so the DeleteLcb routine won't look at any
+ // other structures.
+ //
+
+ InitializeListHead( &(*Vcb)->RootLcb->ScbLinks );
+ InitializeListHead( &(*Vcb)->RootLcb->FcbLinks );
+ ClearFlag( (*Vcb)->RootLcb->LcbState,
+ LCB_STATE_EXACT_CASE_IN_TREE | LCB_STATE_IGNORE_CASE_IN_TREE );
+
+ NtfsDeleteLcb( IrpContext, &(*Vcb)->RootLcb );
+ (*Vcb)->RootLcb = NULL;
+ }
+
+ //
+ // Make sure the Fcb table is completely emptied. It is possible that an occasional Fcb
+ // (along with its Scb) will not be deleted when the file object closes come in.
+ //
+
+ while (TRUE) {
+
+ PVOID RestartKey;
+
+ //
+ // Always reinitialize the search so we get the first element in the tree.
+ //
+
+ RestartKey = NULL;
+ NtfsAcquireFcbTable( IrpContext, *Vcb );
+ Fcb = NtfsGetNextFcbTableEntry( *Vcb, &RestartKey );
+ NtfsReleaseFcbTable( IrpContext, *Vcb );
+
+ if (Fcb == NULL) { break; }
+
+ while ((Scb = NtfsGetNextChildScb( Fcb, NULL )) != NULL) {
+
+ NtfsDeleteScb( IrpContext, &Scb );
+ }
+
+ NtfsAcquireFcbTable( IrpContext, *Vcb );
+ NtfsDeleteFcb( IrpContext, &Fcb, &AcquiredFcb );
+ }
+
+ //
+ // Free the upcase table and attribute definitions. The upcase
+ // table only gets freed if it is not the global table.
+ //
+
+ if (((*Vcb)->UpcaseTable != NULL) && ((*Vcb)->UpcaseTable != NtfsData.UpcaseTable)) {
+
+ NtfsFreePool( (*Vcb)->UpcaseTable );
+ }
+
+ (*Vcb)->UpcaseTable = NULL;
+
+ if (((*Vcb)->AttributeDefinitions != NULL) &&
+ ((*Vcb)->AttributeDefinitions != NtfsAttributeDefinitions)) {
+
+ NtfsFreePool( (*Vcb)->AttributeDefinitions );
+ (*Vcb)->AttributeDefinitions = NULL;
+ }
+
+ //
+ // Free the device name string if present.
+ //
+
+ if ((*Vcb)->DeviceName.Buffer != NULL) {
+
+ NtfsFreePool( (*Vcb)->DeviceName.Buffer );
+ (*Vcb)->DeviceName.Buffer = NULL;
+ }
+
+ FsRtlNotifyUninitializeSync( &(*Vcb)->NotifySync );
+
+ //
+ // We will free the structure allocated for the Lfs handle.
+ //
+
+ LfsDeleteLogHandle( (*Vcb)->LogHandle );
+ (*Vcb)->LogHandle = NULL;
+
+ //
+ // Delete the vcb resource and also free the restart tables
+ //
+
+ NtfsFreeRestartTable( &(*Vcb)->OpenAttributeTable );
+ NtfsFreeRestartTable( &(*Vcb)->TransactionTable );
+
+ //
+ // The Vpb in the Vcb may be a temporary Vpb and we should free it here.
+ //
+
+ if (FlagOn( (*Vcb)->VcbState, VCB_STATE_TEMP_VPB )) {
+
+ NtfsFreePool( (*Vcb)->Vpb );
+ (*Vcb)->Vpb = NULL;
+ }
+
+ ExDeleteResource( &(*Vcb)->Resource );
+
+ //
+ // Delete the space used to store performance counters.
+ //
+
+ if ((*Vcb)->Statistics != NULL) {
+ NtfsFreePool( (*Vcb)->Statistics );
+ (*Vcb)->Statistics = NULL;
+ }
+
+ //
+ // Tear down the file property tunneling structure
+ //
+
+ FsRtlDeleteTunnelCache(&(*Vcb)->Tunnel);
+
+#ifdef NTFS_CHECK_BITMAP
+ if ((*Vcb)->BitmapCopy != NULL) {
+
+ ULONG Count = 0;
+
+ while (Count < (*Vcb)->BitmapPages) {
+
+ if (((*Vcb)->BitmapCopy + Count)->Buffer != NULL) {
+
+ NtfsFreePool( ((*Vcb)->BitmapCopy + Count)->Buffer );
+ }
+
+ Count += 1;
+ }
+
+ NtfsFreePool( (*Vcb)->BitmapCopy );
+ (*Vcb)->BitmapCopy = NULL;
+ }
+#endif
+
+ //
+ // Return the Vcb (i.e., the VolumeDeviceObject) to pool and null out
+ // the input pointer to be safe
+ //
+
+ VolDo = CONTAINING_RECORD(*Vcb, VOLUME_DEVICE_OBJECT, Vcb);
+ IoDeleteDevice( (PDEVICE_OBJECT)VolDo );
+
+ *Vcb = NULL;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteVcb -> VOID\n") );
+
+ return;
+}
+
+
+PFCB
+NtfsCreateRootFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates, initializes, and inserts a new root FCB record
+ into the in memory data structure. It also creates the necessary Root LCB
+ record and inserts the root name into the prefix table.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to associate with the new root Fcb and Lcb
+
+Return Value:
+
+ PFCB - returns pointer to the newly allocated root FCB.
+
+--*/
+
+{
+ PFCB RootFcb;
+ PLCB RootLcb;
+
+ //
+ // The following variables are only used for abnormal termination
+ //
+
+ PVOID UnwindStorage = NULL;
+ PERESOURCE UnwindResource = NULL;
+ PFAST_MUTEX UnwindFastMutex = NULL;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ DebugTrace( +1, Dbg, ("NtfsCreateRootFcb, Vcb = %08lx\n", Vcb) );
+
+ try {
+
+ //
+ // Allocate a new fcb and zero it out. We use Fcb locally so we
+ // don't have to continually go through the Vcb
+ //
+
+ RootFcb =
+ UnwindStorage = (PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbIndexLookasideList );
+
+ RtlZeroMemory( RootFcb, sizeof(FCB_INDEX) );
+
+ //
+ // Set the proper node type code and byte size
+ //
+
+ RootFcb->NodeTypeCode = NTFS_NTC_FCB;
+ RootFcb->NodeByteSize = sizeof(FCB);
+
+ SetFlag( RootFcb->FcbState, FCB_STATE_COMPOUND_INDEX );
+
+ //
+ // Initialize the Lcb queue and point back to our Vcb.
+ //
+
+ InitializeListHead( &RootFcb->LcbQueue );
+
+ RootFcb->Vcb = Vcb;
+
+ //
+ // File Reference
+ //
+
+ NtfsSetSegmentNumber( &RootFcb->FileReference,
+ 0,
+ ROOT_FILE_NAME_INDEX_NUMBER );
+ RootFcb->FileReference.SequenceNumber = ROOT_FILE_NAME_INDEX_NUMBER;
+
+ //
+ // Initialize the Scb
+ //
+
+ InitializeListHead( &RootFcb->ScbQueue );
+
+ //
+ // Allocate and initialize the resource variable
+ //
+
+ UnwindResource = RootFcb->Resource = NtfsAllocateEresource();
+
+ //
+ // Allocate and initialize the Fcb fast mutex.
+ //
+
+ UnwindFastMutex =
+ RootFcb->FcbMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
+ ExInitializeFastMutex( UnwindFastMutex );
+
+ //
+ // Insert this new fcb into the fcb table
+ //
+
+ NtfsInsertFcbTableEntry( IrpContext, Vcb, RootFcb, RootFcb->FileReference );
+ SetFlag( RootFcb->FcbState, FCB_STATE_IN_FCB_TABLE );
+
+ //
+ // Now insert this new root fcb into it proper position in the graph with a
+ // root lcb. First allocate an initialize the root lcb and then build the
+ // lcb/scb graph.
+ //
+
+ {
+ //
+ // Use the root Lcb within the Fcb.
+ //
+
+ RootLcb = Vcb->RootLcb = (PLCB) &((PFCB_INDEX) RootFcb)->Lcb;
+
+ RootLcb->NodeTypeCode = NTFS_NTC_LCB;
+ RootLcb->NodeByteSize = sizeof(LCB);
+
+ //
+ // Insert the root lcb into the Root Fcb's queue
+ //
+
+ InsertTailList( &RootFcb->LcbQueue, &RootLcb->FcbLinks );
+ RootLcb->Fcb = RootFcb;
+
+ //
+ // Use the embedded file name attribute.
+ //
+
+ RootLcb->FileNameAttr = (PFILE_NAME) &RootLcb->ParentDirectory;
+
+ RootLcb->FileNameAttr->ParentDirectory = RootFcb->FileReference;
+ RootLcb->FileNameAttr->FileNameLength = 1;
+ RootLcb->FileNameAttr->Flags = FILE_NAME_NTFS | FILE_NAME_DOS;
+
+ RootLcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &RootLcb->FileNameAttr->FileName;
+
+ RootLcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( RootLcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( 2 ));
+
+ RootLcb->ExactCaseLink.LinkName.MaximumLength =
+ RootLcb->ExactCaseLink.LinkName.Length =
+ RootLcb->IgnoreCaseLink.LinkName.MaximumLength =
+ RootLcb->IgnoreCaseLink.LinkName.Length = 2;
+
+ RootLcb->ExactCaseLink.LinkName.Buffer[0] =
+ RootLcb->IgnoreCaseLink.LinkName.Buffer[0] = L'\\';
+
+ SetFlag( RootLcb->FileNameAttr->Flags, FILE_NAME_NTFS | FILE_NAME_DOS );
+
+ //
+ // Initialize both the ccb.
+ //
+
+ InitializeListHead( &RootLcb->CcbQueue );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateRootFcb );
+
+ if (AbnormalTermination()) {
+
+ if (UnwindResource) { NtfsFreeEresource( UnwindResource ); }
+ if (UnwindStorage) { NtfsFreePool( UnwindStorage ); }
+ if (UnwindFastMutex) { NtfsFreePool( UnwindFastMutex ); }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateRootFcb -> %8lx\n", RootFcb) );
+
+ return RootFcb;
+}
+
+
+PFCB
+NtfsCreateFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN FILE_REFERENCE FileReference,
+ IN BOOLEAN IsPagingFile,
+ IN BOOLEAN LargeFcb,
+ OUT PBOOLEAN ReturnedExistingFcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and initializes a new Fcb record. The record
+ is not placed within the Fcb/Scb graph but is only inserted in the
+ FcbTable.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to associate the new FCB under.
+
+ FileReference - Supplies the file reference to use to identify the
+ Fcb with. We will search the Fcb table for any preexisting
+ Fcb's with the same file reference number.
+
+ IsPagingFile - Indicates if we are creating an FCB for a paging file
+ or some other type of file.
+
+ LargeFcb - Indicates if we should use the larger of the compound Fcb's.
+
+ ReturnedExistingFcb - Optionally indicates to the caller if the
+ returned Fcb already existed
+
+Return Value:
+
+ PFCB - Returns a pointer to the newly allocated FCB
+
+--*/
+
+{
+ FCB_TABLE_ELEMENT Key;
+ PFCB_TABLE_ELEMENT Entry;
+
+ PFCB Fcb;
+
+ BOOLEAN LocalReturnedExistingFcb;
+
+ //
+ // The following variables are only used for abnormal termination
+ //
+
+ PVOID UnwindStorage = NULL;
+ PERESOURCE UnwindResource = NULL;
+ PFAST_MUTEX UnwindFastMutex = NULL;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ DebugTrace( +1, Dbg, ("NtfsCreateFcb\n") );
+
+ if (!ARGUMENT_PRESENT(ReturnedExistingFcb)) { ReturnedExistingFcb = &LocalReturnedExistingFcb; }
+
+ //
+ // First search the FcbTable for a matching fcb
+ //
+
+ Key.FileReference = FileReference;
+ Fcb = NULL;
+
+ if ((Entry = RtlLookupElementGenericTable( &Vcb->FcbTable, &Key )) != NULL) {
+
+ Fcb = Entry->Fcb;
+
+ //
+ // It's possible that this Fcb has been deleted but in truncating and
+ // growing the Mft we are reusing some of the file references.
+ // If this file has been deleted but the Fcb is waiting around for
+ // closes, we will remove it from the Fcb table and create a new Fcb
+ // below.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
+
+ //
+ // Remove it from the Fcb table and remember to create an
+ // Fcb below.
+ //
+
+ NtfsDeleteFcbTableEntry( Fcb->Vcb,
+ Fcb->FileReference );
+
+ ClearFlag( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE );
+
+ Fcb = NULL;
+
+ } else {
+
+ *ReturnedExistingFcb = TRUE;
+ }
+ }
+
+ //
+ // Now check if we have an Fcb.
+ //
+
+ if (Fcb == NULL) {
+
+ *ReturnedExistingFcb = FALSE;
+
+ try {
+
+ //
+ // Allocate a new FCB and zero it out.
+ //
+
+ if (IsPagingFile ||
+ NtfsSegmentNumber( &FileReference ) <= MASTER_FILE_TABLE2_NUMBER ||
+ NtfsSegmentNumber( &FileReference ) == BAD_CLUSTER_FILE_NUMBER ||
+ NtfsSegmentNumber( &FileReference ) == BIT_MAP_FILE_NUMBER) {
+
+ Fcb = UnwindStorage = NtfsAllocatePoolWithTag( NonPagedPool,
+ sizeof(FCB),
+ 'fftN' );
+ RtlZeroMemory( Fcb, sizeof(FCB) );
+
+ if (IsPagingFile) {
+
+ SetFlag( Fcb->FcbState, FCB_STATE_PAGING_FILE );
+
+ //
+ // We don't want to dismount this volume now that
+ // we have a pagefile open on it.
+ //
+
+ SetFlag( Vcb->VcbState, VCB_STATE_DISALLOW_DISMOUNT );
+ }
+
+ SetFlag( Fcb->FcbState, FCB_STATE_NONPAGED );
+
+ } else {
+
+ if (LargeFcb) {
+
+ Fcb = UnwindStorage =
+ (PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbIndexLookasideList );
+
+ RtlZeroMemory( Fcb, sizeof( FCB_INDEX ));
+ SetFlag( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX );
+
+ } else {
+
+ Fcb = UnwindStorage =
+ (PFCB)ExAllocateFromPagedLookasideList( &NtfsFcbDataLookasideList );
+
+ RtlZeroMemory( Fcb, sizeof( FCB_DATA ));
+ SetFlag( Fcb->FcbState, FCB_STATE_COMPOUND_DATA );
+ }
+ }
+
+ //
+ // Set the proper node type code and byte size
+ //
+
+ Fcb->NodeTypeCode = NTFS_NTC_FCB;
+ Fcb->NodeByteSize = sizeof(FCB);
+
+ //
+ // Initialize the Lcb queue and point back to our Vcb, and indicate
+ // that we are a directory
+ //
+
+ InitializeListHead( &Fcb->LcbQueue );
+
+ Fcb->Vcb = Vcb;
+
+ //
+ // File Reference
+ //
+
+ Fcb->FileReference = FileReference;
+
+ //
+ // Initialize the Scb
+ //
+
+ InitializeListHead( &Fcb->ScbQueue );
+
+ //
+ // Allocate and initialize the resource variable
+ //
+
+ UnwindResource = Fcb->Resource = NtfsAllocateEresource();
+
+ //
+ // Allocate and initialize fast mutex for the Fcb.
+ //
+
+ UnwindFastMutex = Fcb->FcbMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
+ ExInitializeFastMutex( UnwindFastMutex );
+
+ //
+ // Insert this new fcb into the fcb table
+ //
+
+ NtfsInsertFcbTableEntry( IrpContext, Vcb, Fcb, FileReference );
+ SetFlag( Fcb->FcbState, FCB_STATE_IN_FCB_TABLE );
+
+ //
+ // Set the flag to indicate if this is a system file.
+ //
+
+ if (NtfsSegmentNumber( &FileReference ) <= FIRST_USER_FILE_NUMBER) {
+
+ SetFlag( Fcb->FcbState, FCB_STATE_SYSTEM_FILE );
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCreateFcb );
+
+ if (AbnormalTermination()) {
+
+ if (UnwindFastMutex) { NtfsFreePool( UnwindFastMutex ); }
+ if (UnwindResource) { NtfsFreeEresource( UnwindResource ); }
+ if (UnwindStorage) { NtfsFreePool( UnwindStorage ); }
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateFcb -> %08lx\n", Fcb) );
+
+ return Fcb;
+}
+
+
+VOID
+NtfsDeleteFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PFCB *Fcb,
+ OUT PBOOLEAN AcquiredFcbTable
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocates and removes an FCB record from all Ntfs's in-memory
+ data structures. It assumes that it does not have anything Scb children nor
+ does it have any lcb edges going into it at the time of the call.
+
+Arguments:
+
+ Fcb - Supplies the FCB to be removed
+
+ AcquiredFcbTable - Set to FALSE when this routine releases the
+ FcbTable.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( *Fcb );
+ ASSERT( IsListEmpty(&(*Fcb)->ScbQueue) );
+ ASSERT( (NodeType(*Fcb) == NTFS_NTC_FCB) );
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteFcb, *Fcb = %08lx\n", *Fcb) );
+
+ //
+ // First free any possible Scb snapshots.
+ //
+
+ NtfsFreeSnapshotsForFcb( IrpContext, *Fcb );
+
+ //
+ // This Fcb may be in the ExclusiveFcb list of the IrpContext.
+ // If it is (The Flink is not NULL), we remove it.
+ // And release the global resource.
+ //
+
+ if ((*Fcb)->ExclusiveFcbLinks.Flink != NULL) {
+
+ RemoveEntryList( &(*Fcb)->ExclusiveFcbLinks );
+ }
+
+ //
+ // Clear the IrpContext field for any request which may own the paging
+ // IO resource for this Fcb.
+ //
+
+ if (IrpContext->FcbWithPagingExclusive == *Fcb) {
+
+ IrpContext->FcbWithPagingExclusive = NULL;
+
+ } else if (IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == *Fcb) {
+
+ IrpContext->TopLevelIrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ //
+ // Deallocate the resources protecting the Fcb
+ //
+
+ ASSERT((*Fcb)->Resource->NumberOfSharedWaiters == 0);
+ ASSERT((*Fcb)->Resource->NumberOfExclusiveWaiters == 0);
+
+#ifdef NTFSDBG
+
+ //
+ // Either we own the FCB or nobody should own it. The extra acquire
+ // here does not matter since we will free the resource below.
+ //
+
+ ASSERT(ExAcquireResourceExclusiveLite( (*Fcb)->Resource, FALSE ));
+#endif
+
+ NtfsFreeEresource( (*Fcb)->Resource );
+
+ if ( (*Fcb)->PagingIoResource != NULL ) {
+
+ if (IrpContext->FcbWithPagingExclusive == *Fcb) {
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ NtfsFreeEresource( (*Fcb)->PagingIoResource );
+ }
+
+ //
+ // Deallocate the fast mutex.
+ //
+
+ if ((*Fcb)->FcbMutex != NULL) {
+
+ NtfsFreePool( (*Fcb)->FcbMutex );
+ }
+
+ //
+ // Remove the fcb from the fcb table if present.
+ //
+
+ if (FlagOn( (*Fcb)->FcbState, FCB_STATE_IN_FCB_TABLE )) {
+
+ NtfsDeleteFcbTableEntry( (*Fcb)->Vcb, (*Fcb)->FileReference );
+ ClearFlag( (*Fcb)->FcbState, FCB_STATE_IN_FCB_TABLE );
+ }
+
+ NtfsReleaseFcbTable( IrpContext, (*Fcb)->Vcb );
+ *AcquiredFcbTable = FALSE;
+
+ //
+ // Dereference and possibly deallocate the security descriptor if present.
+ //
+
+ NtfsAcquireFcbSecurity( (*Fcb)->Vcb );
+
+ if ((*Fcb)->SharedSecurity != NULL) {
+
+ NtfsDereferenceSharedSecurity( *Fcb );
+ }
+
+ //
+ // Now check and free if we have a security descriptor for children.
+ //
+
+ if ((*Fcb)->ChildSharedSecurity != NULL) {
+
+ (*Fcb)->ChildSharedSecurity->ReferenceCount -= 1;
+ (*Fcb)->ChildSharedSecurity->ParentFcb = NULL;
+
+ //
+ // We can deallocate the structure if we are the last
+ // reference to this structure.
+ //
+
+ if ((*Fcb)->ChildSharedSecurity->ReferenceCount == 0) {
+
+ NtfsFreePool( (*Fcb)->ChildSharedSecurity );
+ }
+
+ (*Fcb)->ChildSharedSecurity = NULL;
+
+ }
+
+ NtfsReleaseFcbSecurity( (*Fcb)->Vcb );
+
+#ifdef _CAIRO_
+
+ //
+ // Release the quota control block.
+ //
+
+ if (NtfsPerformQuotaOperation( *Fcb )) {
+ NtfsDereferenceQuotaControlBlock( (*Fcb)->Vcb, &(*Fcb)->QuotaControl );
+ }
+
+#endif // _CAIRO_
+
+ //
+ // Deallocate the Fcb itself
+ //
+
+ if (FlagOn( (*Fcb)->FcbState, FCB_STATE_NONPAGED )) {
+
+ NtfsFreePool( *Fcb );
+
+ } else {
+
+ if (FlagOn( (*Fcb)->FcbState, FCB_STATE_COMPOUND_INDEX )) {
+
+ ExFreeToPagedLookasideList( &NtfsFcbIndexLookasideList, *Fcb );
+
+ } else {
+
+ ExFreeToPagedLookasideList( &NtfsFcbDataLookasideList, *Fcb );
+ }
+ }
+
+ //
+ // Zero out the input pointer
+ //
+
+ *Fcb = NULL;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteFcb -> VOID\n") );
+
+ return;
+}
+
+
+PFCB
+NtfsGetNextFcbTableEntry (
+ IN PVCB Vcb,
+ IN PVOID *RestartKey
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will enumerate through all of the fcb's for the given
+ vcb
+
+Arguments:
+
+ Vcb - Supplies the Vcb used in this operation
+
+ RestartKey - This value is used by the table package to maintain
+ its position in the enumeration. It is initialized to NULL
+ for the first search.
+
+Return Value:
+
+ PFCB - A pointer to the next fcb or NULL if the enumeration is
+ completed
+
+--*/
+
+{
+ PFCB Fcb;
+
+ PAGED_CODE();
+
+ Fcb = (PFCB) RtlEnumerateGenericTableWithoutSplaying( &Vcb->FcbTable, RestartKey );
+
+ if (Fcb != NULL) {
+
+ Fcb = ((PFCB_TABLE_ELEMENT)(Fcb))->Fcb;
+ }
+
+ return Fcb;
+}
+
+
+PSCB
+NtfsCreateScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName,
+ IN BOOLEAN ReturnExistingOnly,
+ OUT PBOOLEAN ReturnedExistingScb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates, initializes, and inserts a new Scb record into
+ the in memory data structures, provided one does not already exist
+ with the identical attribute record.
+
+Arguments:
+
+ Fcb - Supplies the Fcb to associate the new SCB under.
+
+ AttributeTypeCode - Supplies the attribute type code for the new Scb
+
+ AttributeName - Supplies the attribute name for the new Scb, with
+ AttributeName->Length == 0 if there is no name.
+
+ ReturnExistingOnly - If specified as TRUE then only an existing Scb
+ will be returned. If no matching Scb exists then NULL is returned.
+
+ ReturnedExistingScb - Indicates if this procedure found an existing
+ Scb with the identical attribute record (variable is set to TRUE)
+ or if this procedure needed to create a new Scb (variable is set to
+ FALSE).
+
+Return Value:
+
+ PSCB - Returns a pointer to the newly allocated SCB or NULL if there is
+ no Scb and ReturnExistingOnly is TRUE.
+
+--*/
+
+{
+ PSCB Scb;
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+ BOOLEAN LocalReturnedExistingScb;
+ BOOLEAN PagingIoResource = FALSE;
+#ifdef SYSCACHE
+ BOOLEAN SyscacheFile = FALSE;
+#endif
+
+ //
+ // The following variables are only used for abnormal termination
+ //
+
+ PVOID UnwindStorage[4] = { NULL, NULL, NULL, NULL };
+ POPLOCK UnwindOplock = NULL;
+ PNTFS_MCB UnwindMcb = NULL;
+
+ PLARGE_MCB UnwindAddedClustersMcb = NULL;
+ PLARGE_MCB UnwindRemovedClustersMcb = NULL;
+
+ BOOLEAN UnwindFromQueue = FALSE;
+
+ BOOLEAN Nonpaged = FALSE;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_FCB( Fcb );
+
+ ASSERT( AttributeTypeCode >= $STANDARD_INFORMATION );
+
+ DebugTrace( +1, Dbg, ("NtfsCreateScb\n") );
+
+ if (!ARGUMENT_PRESENT(ReturnedExistingScb)) { ReturnedExistingScb = &LocalReturnedExistingScb; }
+
+ //
+ // Search the scb queue of the fcb looking for a matching
+ // attribute type code and attribute name
+ //
+
+ NtfsLockFcb( IrpContext, Fcb );
+
+ Scb = NULL;
+ while ((Scb = NtfsGetNextChildScb( Fcb, Scb )) != NULL) {
+
+ ASSERT_SCB( Scb );
+
+ //
+ // For every scb already in the fcb's queue check for a matching
+ // type code and name. If we find a match we return from this
+ // procedure right away.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED) &&
+ (AttributeTypeCode == Scb->AttributeTypeCode) &&
+ NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable, &Scb->AttributeName, AttributeName, FALSE )) {
+
+ *ReturnedExistingScb = TRUE;
+
+ NtfsUnlockFcb( IrpContext, Fcb );
+
+ if (NtfsIsExclusiveScb(Scb)) {
+
+ NtfsSnapshotScb( IrpContext, Scb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", Scb) );
+
+ return Scb;
+ }
+ }
+
+ //
+ // If the user only wanted an existing Scb then return NULL.
+ //
+
+ if (ReturnExistingOnly) {
+
+ NtfsUnlockFcb( IrpContext, Fcb );
+ DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", NULL) );
+ return NULL;
+ }
+
+ //
+ // We didn't find it so we are not going to be returning an existing Scb
+ //
+
+ *ReturnedExistingScb = FALSE;
+
+ try {
+
+ //
+ // Decide the node type and size of the Scb. Also decide if it will be
+ // allocated from paged or non-paged pool.
+ //
+
+ if (AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ if (NtfsSegmentNumber( &Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) {
+ NodeTypeCode = NTFS_NTC_SCB_ROOT_INDEX;
+ } else {
+ NodeTypeCode = NTFS_NTC_SCB_INDEX;
+ }
+
+ NodeByteSize = SIZEOF_SCB_INDEX;
+
+ } else if (NtfsSegmentNumber( &Fcb->FileReference ) <= MASTER_FILE_TABLE2_NUMBER
+ && (AttributeTypeCode == $DATA)) {
+
+ NodeTypeCode = NTFS_NTC_SCB_MFT;
+ NodeByteSize = SIZEOF_SCB_MFT;
+
+ } else {
+
+ NodeTypeCode = NTFS_NTC_SCB_DATA;
+ NodeByteSize = SIZEOF_SCB_DATA;
+
+ //
+ // If this is a user data stream then remember that we need
+ // a paging IO resource.
+ //
+
+ if (((NtfsSegmentNumber( &Fcb->FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) ||
+ (NtfsSegmentNumber( &Fcb->FileReference ) == VOLUME_DASD_NUMBER) ||
+ (NtfsSegmentNumber( &Fcb->FileReference ) == BIT_MAP_FILE_NUMBER) ||
+#ifdef _CAIRO_
+ (NtfsSegmentNumber( &Fcb->FileReference ) == QUOTA_TABLE_NUMBER) ||
+#endif
+ (NtfsSegmentNumber( &Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER))
+
+ && (NtfsIsTypeCodeUserData( AttributeTypeCode ) ||
+ (AttributeTypeCode == $EA) ||
+ (AttributeTypeCode >= $FIRST_USER_DEFINED_ATTRIBUTE))) {
+
+ //
+ // Remember that this stream needs a paging io resource.
+ //
+
+ PagingIoResource = TRUE;
+ }
+ }
+
+ //
+ // The scb will come from non-paged if the Fcb is non-paged or
+ // it is an attribute list.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_NONPAGED ) ||
+ (AttributeTypeCode == $ATTRIBUTE_LIST)) {
+
+ Scb = UnwindStorage[0] = NtfsAllocatePoolWithTag( NonPagedPool, NodeByteSize, 'nftN' );
+ Nonpaged = TRUE;
+
+ } else if (AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ //
+ // If the Fcb is an INDEX Fcb and the Scb is unused, then
+ // use that. Otherwise allocate from the lookaside list.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
+ (SafeNodeType( &((PFCB_INDEX) Fcb)->Scb ) == 0)) {
+
+ Scb = (PSCB) &((PFCB_INDEX) Fcb)->Scb;
+
+ } else {
+
+ Scb = UnwindStorage[0] = (PSCB)NtfsAllocatePoolWithTag( PagedPool, SIZEOF_SCB_INDEX, 'SftN' );
+ }
+
+ } else {
+
+ //
+ // We can use the Scb field in the Fcb in all cases if it is
+ // unused. We will only use it for a data stream since
+ // it will have the longest life.
+ //
+
+ if ((AttributeTypeCode == $DATA) &&
+ (SafeNodeType( &((PFCB_INDEX) Fcb)->Scb ) == 0)) {
+
+ Scb = (PSCB) &((PFCB_INDEX) Fcb)->Scb;
+
+ } else {
+
+ Scb = UnwindStorage[0] = (PSCB)ExAllocateFromPagedLookasideList( &NtfsScbDataLookasideList );
+ }
+
+#ifdef SYSCACHE
+ if (FsRtlIsSyscacheFile(IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->FileObject)) {
+
+ SyscacheFile = TRUE;
+ }
+#endif
+ }
+
+ //
+ // Store the Scb address and zero it out.
+ //
+
+ RtlZeroMemory( Scb, NodeByteSize );
+
+#ifdef SYSCACHE
+ if (SyscacheFile) {
+ SetFlag( Scb->ScbState, SCB_STATE_SYSCACHE_FILE );
+ }
+#endif
+
+ //
+ // Set the proper node type code and byte size
+ //
+
+ Scb->Header.NodeTypeCode = NodeTypeCode;
+ Scb->Header.NodeByteSize = NodeByteSize;
+
+ //
+ // Set a back pointer to the resource we will be using
+ //
+
+ Scb->Header.Resource = Fcb->Resource;
+
+ //
+ // Decide if we will be using the PagingIoResource
+ //
+
+ if (PagingIoResource) {
+
+ //
+ // Initialize it in the Fcb if it is not already there, and
+ // setup the pointer and flag in the Scb.
+ //
+
+ if (Fcb->PagingIoResource == NULL) {
+
+ Fcb->PagingIoResource = NtfsAllocateEresource();
+ }
+
+ Scb->Header.PagingIoResource = Fcb->PagingIoResource;
+ }
+
+ //
+ // Insert this Scb into our parents scb queue, and point back to
+ // our parent fcb, and vcb
+ //
+
+ InsertTailList( &Fcb->ScbQueue, &Scb->FcbLinks );
+ UnwindFromQueue = TRUE;
+
+ Scb->Fcb = Fcb;
+ Scb->Vcb = Fcb->Vcb;
+
+ //
+ // If the attribute name exists then allocate a buffer for the
+ // attribute name and iniitalize it.
+ //
+
+ if (AttributeName->Length != 0) {
+
+ //
+ // The typical case is the $I30 string. If this matches then
+ // point to a common string.
+ //
+
+ if ((AttributeName->Length == NtfsFileNameIndex.Length) &&
+ (RtlEqualMemory( AttributeName->Buffer,
+ NtfsFileNameIndex.Buffer,
+ AttributeName->Length ) )) {
+
+ Scb->AttributeName = NtfsFileNameIndex;
+
+ } else {
+
+ Scb->AttributeName.Length = AttributeName->Length;
+ Scb->AttributeName.MaximumLength = (USHORT)(AttributeName->Length + 2);
+
+ Scb->AttributeName.Buffer = UnwindStorage[1] = NtfsAllocatePool(PagedPool, AttributeName->Length + 2 );
+
+ RtlCopyMemory( Scb->AttributeName.Buffer, AttributeName->Buffer, AttributeName->Length );
+ Scb->AttributeName.Buffer[AttributeName->Length / 2] = L'\0';
+ }
+ }
+
+ //
+ // Set the attribute Type Code
+ //
+
+ Scb->AttributeTypeCode = AttributeTypeCode;
+ if (NtfsIsTypeCodeSubjectToQuota( AttributeTypeCode )){
+ SetFlag( Scb->ScbState, SCB_STATE_SUBJECT_TO_QUOTA );
+ }
+
+ //
+ // If this is an Mft Scb then initialize the cluster Mcb's.
+ //
+
+ if (NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.AddedClusters, NonPagedPool );
+ UnwindAddedClustersMcb = &Scb->ScbType.Mft.AddedClusters;
+
+ FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.RemovedClusters, NonPagedPool );
+ UnwindRemovedClustersMcb = &Scb->ScbType.Mft.RemovedClusters;
+ }
+
+ //
+ // Get the mutex for the Scb. We may be able to use the one in the Fcb.
+ // We can if the Scb is paged.
+ //
+
+ if (Nonpaged) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_NONPAGED );
+ UnwindStorage[3] =
+ Scb->Header.FastMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
+ ExInitializeFastMutex( Scb->Header.FastMutex );
+
+ } else {
+
+ Scb->Header.FastMutex = Fcb->FcbMutex;
+ }
+
+ //
+ // Allocate the Nonpaged portion of the Scb.
+ //
+
+ Scb->NonpagedScb =
+ UnwindStorage[2] = (PSCB_NONPAGED)ExAllocateFromNPagedLookasideList( &NtfsScbNonpagedLookasideList );
+
+ RtlZeroMemory( Scb->NonpagedScb, sizeof( SCB_NONPAGED ));
+
+ Scb->NonpagedScb->NodeTypeCode = NTFS_NTC_SCB_NONPAGED;
+ Scb->NonpagedScb->NodeByteSize = sizeof( SCB_NONPAGED );
+ Scb->NonpagedScb->Vcb = Scb->Vcb;
+
+ //
+ // Fill in the advanced fields
+ //
+
+ SetFlag( Scb->Header.Flags, FSRTL_FLAG_ADVANCED_HEADER );
+ Scb->Header.PendingEofAdvances = &Scb->EofListHead;
+ InitializeListHead( &Scb->EofListHead );
+ Scb->Header.SectionObjectPointers = &Scb->NonpagedScb->SegmentObject;
+
+ NtfsInitializeNtfsMcb( &Scb->Mcb,
+ &Scb->Header,
+ &Scb->McbStructs,
+ FlagOn( Scb->ScbState, SCB_STATE_NONPAGED )
+ ? NonPagedPool : PagedPool);
+
+ UnwindMcb = &Scb->Mcb;
+
+ //
+ // Do that data stream specific initialization.
+ //
+
+ if (NodeTypeCode == NTFS_NTC_SCB_DATA) {
+
+ FsRtlInitializeOplock( &Scb->ScbType.Data.Oplock );
+ UnwindOplock = &Scb->ScbType.Data.Oplock;
+
+ } else {
+
+ //
+ // There is a deallocated queue for indexes and the Mft.
+ //
+
+ InitializeListHead( &Scb->ScbType.Index.RecentlyDeallocatedQueue );
+
+ //
+ // Initialize index-specific fields.
+ //
+
+ if (AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ InitializeListHead( &Scb->ScbType.Index.LcbQueue );
+ }
+ }
+
+ //
+ // If this Scb should be marked as containing Lsn's or
+ // Update Sequence Arrays, do so now.
+ //
+
+ NtfsCheckScbForCache( Scb );
+
+ } finally {
+
+ DebugUnwind( NtfsCreateScb );
+
+ NtfsUnlockFcb( IrpContext, Fcb );
+
+ if (AbnormalTermination()) {
+
+ if (UnwindFromQueue) { RemoveEntryList( &Scb->FcbLinks ); }
+ if (UnwindMcb != NULL) { NtfsUninitializeNtfsMcb( UnwindMcb ); }
+
+ if (UnwindAddedClustersMcb != NULL) { FsRtlUninitializeLargeMcb( UnwindAddedClustersMcb ); }
+ if (UnwindRemovedClustersMcb != NULL) { FsRtlUninitializeLargeMcb( UnwindRemovedClustersMcb ); }
+ if (UnwindOplock != NULL) { FsRtlUninitializeOplock( UnwindOplock ); }
+ if (UnwindStorage[0]) { NtfsFreePool( UnwindStorage[0] );
+ } else if (Scb != NULL) { Scb->Header.NodeTypeCode = 0; }
+ if (UnwindStorage[1]) { NtfsFreePool( UnwindStorage[1] ); }
+ if (UnwindStorage[2]) { NtfsFreePool( UnwindStorage[2] ); }
+ if (UnwindStorage[3]) { NtfsFreePool( UnwindStorage[3] ); }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateScb -> %08lx\n", Scb) );
+
+ return Scb;
+}
+
+
+PSCB
+NtfsCreatePrerestartScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_REFERENCE FileReference,
+ IN ATTRIBUTE_TYPE_CODE AttributeTypeCode,
+ IN PUNICODE_STRING AttributeName OPTIONAL,
+ IN ULONG BytesPerIndexBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates, initializes, and inserts a new Scb record into
+ the in memory data structures, provided one does not already exist
+ with the identical attribute record. It does this on the FcbTable
+ off of the Vcb. If necessary this routine will also create the fcb
+ if one does not already exist for the indicated file reference.
+
+Arguments:
+
+ Vcb - Supplies the Vcb to associate the new SCB under.
+
+ FileReference - Supplies the file reference for the new SCB this is
+ used to identify/create a new lookaside Fcb.
+
+ AttributeTypeCode - Supplies the attribute type code for the new SCB
+
+ AttributeName - Supplies the optional attribute name of the SCB
+
+ BytesPerIndexBuffer - For index Scbs, this must specify the bytes per
+ index buffer.
+
+Return Value:
+
+ PSCB - Returns a pointer to the newly allocated SCB
+
+--*/
+
+{
+ PSCB Scb;
+ PFCB Fcb;
+
+ NODE_TYPE_CODE NodeTypeCode;
+ NODE_BYTE_SIZE NodeByteSize;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+ ASSERT( AttributeTypeCode >= $STANDARD_INFORMATION );
+
+ DebugTrace( +1, Dbg, ("NtfsCreatePrerestartScb\n") );
+
+ //
+ // First make sure we have an Fcb of the proper file reference
+ // and indicate that it is from prerestart
+ //
+
+ Fcb = NtfsCreateFcb( IrpContext,
+ Vcb,
+ *FileReference,
+ FALSE,
+ TRUE,
+ NULL );
+
+ //
+ // Search the child scbs of this fcb for a matching Scb (based on
+ // attribute type code and attribute name) if one is not found then
+ // we'll create a new scb. When we exit the following loop if the
+ // scb pointer to not null then we've found a preexisting scb.
+ //
+
+ Scb = NULL;
+ while ((Scb = NtfsGetNextChildScb(Fcb, Scb)) != NULL) {
+
+ ASSERT_SCB( Scb );
+
+ //
+ // The the attribute type codes match and if supplied the name also
+ // matches then we got our scb
+ //
+
+ if (Scb->AttributeTypeCode == AttributeTypeCode) {
+
+ if (!ARGUMENT_PRESENT( AttributeName )) {
+
+ if (Scb->AttributeName.Length == 0) {
+
+ break;
+ }
+
+ } else if (AttributeName->Length == 0
+ && Scb->AttributeName.Length == 0) {
+
+ break;
+
+ } else if (NtfsAreNamesEqual( IrpContext->Vcb->UpcaseTable,
+ AttributeName,
+ &Scb->AttributeName,
+ FALSE )) { // Ignore Case
+
+ break;
+ }
+ }
+ }
+
+ //
+ // If scb now null then we need to create a minimal scb. We always allocate
+ // these out of non-paged pool.
+ //
+
+ if (Scb == NULL) {
+
+ BOOLEAN ShareScb = FALSE;
+
+ //
+ // Allocate new scb and zero it out and set the node type code and byte size.
+ //
+
+ if (AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ if (NtfsSegmentNumber( FileReference ) == ROOT_FILE_NAME_INDEX_NUMBER) {
+
+ NodeTypeCode = NTFS_NTC_SCB_ROOT_INDEX;
+ } else {
+ NodeTypeCode = NTFS_NTC_SCB_INDEX;
+ }
+
+ NodeByteSize = SIZEOF_SCB_INDEX;
+
+ } else if (NtfsSegmentNumber( FileReference ) <= MASTER_FILE_TABLE2_NUMBER
+ && (AttributeTypeCode == $DATA)) {
+
+ NodeTypeCode = NTFS_NTC_SCB_MFT;
+ NodeByteSize = SIZEOF_SCB_MFT;
+
+ } else {
+
+ NodeTypeCode = NTFS_NTC_SCB_DATA;
+ NodeByteSize = SIZEOF_SCB_DATA;
+ }
+
+ Scb = NtfsAllocatePoolWithTag( NonPagedPool, NodeByteSize, 'tftN' );
+
+ RtlZeroMemory( Scb, NodeByteSize );
+
+ //
+ // Fill in the node type code and size.
+ //
+
+ Scb->Header.NodeTypeCode = NodeTypeCode;
+ Scb->Header.NodeByteSize = NodeByteSize;
+
+ //
+ // Show that all of the Scb's are from nonpaged pool.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_NONPAGED );
+
+ //
+ // Set a back pointer to the resource we will be using
+ //
+
+ Scb->Header.Resource = Fcb->Resource;
+
+ //
+ // Insert this scb into our parents scb queue and point back to our
+ // parent fcb and vcb
+ //
+
+ InsertTailList( &Fcb->ScbQueue, &Scb->FcbLinks );
+
+ Scb->Fcb = Fcb;
+ Scb->Vcb = Vcb;
+
+ //
+ // If the attribute name is present and the name length is greater than 0
+ // then allocate a buffer for the attribute name and initialize it.
+ //
+
+ if (ARGUMENT_PRESENT( AttributeName ) && (AttributeName->Length != 0)) {
+
+ //
+ // The typical case is the $I30 string. If this matches then
+ // point to a common string.
+ //
+
+ if ((AttributeName->Length == NtfsFileNameIndex.Length) &&
+ (RtlEqualMemory( AttributeName->Buffer,
+ NtfsFileNameIndex.Buffer,
+ AttributeName->Length ) )) {
+
+ Scb->AttributeName = NtfsFileNameIndex;
+
+ } else {
+
+ Scb->AttributeName.Length = AttributeName->Length;
+ Scb->AttributeName.MaximumLength = (USHORT)(AttributeName->Length + 2);
+
+ Scb->AttributeName.Buffer = NtfsAllocatePool(PagedPool, AttributeName->Length + 2 );
+
+ RtlCopyMemory( Scb->AttributeName.Buffer, AttributeName->Buffer, AttributeName->Length );
+ Scb->AttributeName.Buffer[AttributeName->Length/2] = L'\0';
+ }
+ }
+
+ //
+ // Set the attribute type code recently deallocated information structures.
+ //
+
+ Scb->AttributeTypeCode = AttributeTypeCode;
+
+ //
+ // If this is an Mft Scb then initialize the cluster Mcb's.
+ //
+
+ if (NodeTypeCode == NTFS_NTC_SCB_MFT) {
+
+ FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.AddedClusters, NonPagedPool );
+
+ FsRtlInitializeLargeMcb( &Scb->ScbType.Mft.RemovedClusters, NonPagedPool );
+ }
+
+ Scb->NonpagedScb = (PSCB_NONPAGED)ExAllocateFromNPagedLookasideList( &NtfsScbNonpagedLookasideList );
+
+ RtlZeroMemory( Scb->NonpagedScb, sizeof( SCB_NONPAGED ));
+
+ Scb->NonpagedScb->NodeTypeCode = NTFS_NTC_SCB_NONPAGED;
+ Scb->NonpagedScb->NodeByteSize = sizeof( SCB_NONPAGED );
+ Scb->NonpagedScb->Vcb = Vcb;
+
+ //
+ // Fill in the advanced fields
+ //
+
+ SetFlag( Scb->Header.Flags, FSRTL_FLAG_ADVANCED_HEADER );
+ Scb->Header.PendingEofAdvances = &Scb->EofListHead;
+ InitializeListHead( &Scb->EofListHead );
+ Scb->Header.SectionObjectPointers = &Scb->NonpagedScb->SegmentObject;
+ Scb->Header.FastMutex = NtfsAllocatePool( NonPagedPool, sizeof( FAST_MUTEX ));
+ ExInitializeFastMutex( Scb->Header.FastMutex );
+
+ NtfsInitializeNtfsMcb( &Scb->Mcb, &Scb->Header, &Scb->McbStructs, NonPagedPool );
+
+ //
+ // Do that data stream specific initialization.
+ //
+
+ if (NodeTypeCode == NTFS_NTC_SCB_DATA) {
+
+ FsRtlInitializeOplock( &Scb->ScbType.Data.Oplock );
+
+ } else {
+
+ //
+ // There is a deallocated queue for indexes and the Mft.
+ //
+
+ InitializeListHead( &Scb->ScbType.Index.RecentlyDeallocatedQueue );
+
+ //
+ // Initialize index-specific fields.
+ //
+
+ if (AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ Scb->ScbType.Index.BytesPerIndexBuffer = BytesPerIndexBuffer;
+
+ InitializeListHead( &Scb->ScbType.Index.LcbQueue );
+ }
+ }
+
+ //
+ // If this Scb should be marked as containing Lsn's or
+ // Update Sequence Arrays, do so now.
+ //
+
+ NtfsCheckScbForCache( Scb );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreatePrerestartScb -> %08lx\n", Scb) );
+
+ return Scb;
+}
+
+
+VOID
+NtfsDeleteScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PSCB *Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocates and removes an Scb record
+ from Ntfs's in-memory data structures. It assume that is does not have
+ any children lcb emanating from it.
+
+Arguments:
+
+ Scb - Supplies the SCB to be removed
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb;
+ PFCB Fcb;
+ POPEN_ATTRIBUTE_ENTRY AttributeEntry;
+ USHORT ThisNodeType;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( *Scb );
+ ASSERT( (*Scb)->CleanupCount == 0 );
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteScb, *Scb = %08lx\n", *Scb) );
+
+ Fcb = (*Scb)->Fcb;
+ Vcb = Fcb->Vcb;
+
+ RemoveEntryList( &(*Scb)->FcbLinks );
+
+ ThisNodeType = SafeNodeType( *Scb );
+
+ //
+ // If this is a bitmap Scb for a directory then make sure the record
+ // allocation structure is uninitialized. Otherwise we will leave a
+ // stale pointer for the record allocation package.
+ //
+
+ if (((*Scb)->AttributeTypeCode == $BITMAP) &&
+ IsDirectory( &Fcb->Info)) {
+
+ PLIST_ENTRY Links;
+ PSCB IndexAllocationScb;
+
+ Links = Fcb->ScbQueue.Flink;
+
+ while (Links != &Fcb->ScbQueue) {
+
+ IndexAllocationScb = CONTAINING_RECORD( Links, SCB, FcbLinks );
+
+ if (IndexAllocationScb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ NtfsUninitializeRecordAllocation( IrpContext,
+ &IndexAllocationScb->ScbType.Index.RecordAllocationContext );
+
+ IndexAllocationScb->ScbType.Index.AllocationInitialized = FALSE;
+
+ break;
+ }
+
+ Links = Links->Flink;
+ }
+ }
+
+ //
+ // Delete the write mask, if one is being maintained.
+ //
+
+#ifdef SYSCACHE
+ if ((ThisNodeType == NTFS_NTC_SCB_DATA) &&
+ ((*Scb)->ScbType.Data.WriteMask != (PULONG)NULL)) {
+
+ NtfsFreePool((*Scb)->ScbType.Data.WriteMask);
+ }
+#endif
+
+ //
+ // Mark our entry in the Open Attribute Table as free,
+ // although it will not be deleted until some future
+ // checkpoint. Log this change as well, as long as the
+ // log file is active.
+ //
+
+ if ((*Scb)->NonpagedScb->OpenAttributeTableIndex != 0) {
+
+ NtfsAcquireSharedRestartTable( &Vcb->OpenAttributeTable, TRUE );
+ AttributeEntry = GetRestartEntryFromIndex( &Vcb->OpenAttributeTable,
+ (*Scb)->NonpagedScb->OpenAttributeTableIndex );
+ AttributeEntry->Overlay.Scb = NULL;
+ NtfsReleaseRestartTable( &Vcb->OpenAttributeTable );
+
+ //
+ // "Steal" the name, and let it belong to the Open Attribute Table
+ // entry and deallocate it only during checkpoints.
+ //
+
+ (*Scb)->AttributeName.Buffer = NULL;
+ }
+
+ //
+ // Uninitialize the file lock and oplock variables if this
+ // a data Scb. For the index case make sure that the lcb queue
+ // is empty. If this is for an Mft Scb then uninitialize the
+ // allocation Mcb's.
+ //
+
+ NtfsUninitializeNtfsMcb( &(*Scb)->Mcb );
+
+ if (ThisNodeType == NTFS_NTC_SCB_DATA ) {
+
+ FsRtlUninitializeOplock( &(*Scb)->ScbType.Data.Oplock );
+
+ if ((*Scb)->ScbType.Data.FileLock != NULL) {
+
+ FsRtlUninitializeFileLock( (*Scb)->ScbType.Data.FileLock );
+ ExFreeToNPagedLookasideList( &NtfsFileLockLookasideList, (*Scb)->ScbType.Data.FileLock );
+ }
+
+ } else if (ThisNodeType != NTFS_NTC_SCB_MFT) {
+
+ ASSERT(IsListEmpty(&(*Scb)->ScbType.Index.LcbQueue));
+
+ if ((*Scb)->ScbType.Index.NormalizedName.Buffer != NULL) {
+
+ NtfsFreePool( (*Scb)->ScbType.Index.NormalizedName.Buffer );
+ (*Scb)->ScbType.Index.NormalizedName.Buffer = NULL;
+ }
+
+ } else {
+
+ FsRtlUninitializeLargeMcb( &(*Scb)->ScbType.Mft.AddedClusters );
+ FsRtlUninitializeLargeMcb( &(*Scb)->ScbType.Mft.RemovedClusters );
+ }
+
+ //
+ // Show there is no longer a snapshot Scb, if there is a snapshot.
+ // We rely on the snapshot package to correctly recognize the
+ // the case where the Scb field is gone.
+ //
+
+ if ((*Scb)->ScbSnapshot != NULL) {
+
+ (*Scb)->ScbSnapshot->Scb = NULL;
+ }
+
+ //
+ // Deallocate the fast mutex if not in the Fcb.
+ //
+
+ if ((*Scb)->Header.FastMutex != (*Scb)->Fcb->FcbMutex) {
+
+ NtfsFreePool( (*Scb)->Header.FastMutex );
+ }
+
+ //
+ // Deallocate the non-paged scb.
+ //
+
+ ExFreeToNPagedLookasideList( &NtfsScbNonpagedLookasideList, (*Scb)->NonpagedScb );
+
+ //
+ // Deallocate the attribute name and the scb itself
+ //
+
+ if (((*Scb)->AttributeName.Buffer != NULL) &&
+ ((*Scb)->AttributeName.Buffer != NtfsFileNameIndexName)) {
+
+ NtfsFreePool( (*Scb)->AttributeName.Buffer );
+ DebugDoit( (*Scb)->AttributeName.Buffer = NULL );
+ }
+
+#ifdef _CAIRO_
+ //
+ // See if CollationData is to be deleted.
+ //
+
+ if (FlagOn((*Scb)->ScbState, SCB_STATE_DELETE_COLLATION_DATA)) {
+ NtfsFreePool((*Scb)->ScbType.Index.CollationData);
+ }
+#endif _CAIRO_
+
+ //
+ // Always directly free the Mft and non-paged Scb's.
+ //
+
+ if (FlagOn( (*Scb)->ScbState, SCB_STATE_NONPAGED ) ||
+ (ThisNodeType == NTFS_NTC_SCB_MFT)) {
+
+ NtfsFreePool( *Scb );
+
+ } else {
+
+ //
+ // Free any final reserved clusters for data Scb's.
+ //
+
+
+ if (ThisNodeType == NTFS_NTC_SCB_DATA) {
+
+ //
+ // Free any reserved clusters directly into the Vcb
+ //
+
+ if (((*Scb)->ScbType.Data.TotalReserved != 0) &&
+ FlagOn((*Scb)->ScbState, SCB_STATE_WRITE_ACCESS_SEEN)) {
+
+#ifdef SYSCACHE
+ if (!FlagOn((*Scb)->Header.Flags, FSRTL_FLAG_USER_MAPPED_FILE)) {
+ DbgPrint( "Freeing final reserved clusters for Scb\n" );
+ }
+#endif
+ NtfsFreeFinalReservedClusters( Vcb,
+ LlClustersFromBytes(Vcb, (*Scb)->ScbType.Data.TotalReserved) );
+ }
+
+ //
+ // Free the reserved bitmap if present.
+ //
+
+ if ((*Scb)->ScbType.Data.ReservedBitMap != NULL) {
+
+ NtfsFreePool( (*Scb)->ScbType.Data.ReservedBitMap );
+ }
+ }
+
+ //
+ // Now free the Scb itself.
+ //
+ // Check if this is an embedded Scb. This could be part of either an INDEX_FCB
+ // or a DATA_FCB. We depend on the fact that the Scb would be in the same
+ // location in either case.
+ //
+
+ if ((*Scb) == (PSCB) &((PFCB_DATA) (*Scb)->Fcb)->Scb) {
+
+ (*Scb)->Header.NodeTypeCode = 0;
+
+ } else if (SafeNodeType( *Scb ) == NTFS_NTC_SCB_DATA) {
+
+ ExFreeToPagedLookasideList( &NtfsScbDataLookasideList, *Scb );
+
+ } else {
+
+ NtfsFreePool( *Scb );
+ }
+ }
+
+ //
+ // Zero out the input pointer
+ //
+
+ *Scb = NULL;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteScb -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsUpdateNormalizedName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB ParentScb,
+ IN PSCB Scb,
+ IN PFILE_NAME FileName OPTIONAL,
+ IN BOOLEAN CheckBufferSizeOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the normalized name in an IndexScb.
+ This name will be the path from the root without any short name components.
+ This routine will append the given name if present provided this is not a
+ DOS only name. In any other case this routine will go to the disk to
+ find the name. This routine will handle the case where there is an existing buffer
+ and the data will fit, as well as the case where the buffer doesn't exist
+ or is too small.
+
+Arguments:
+
+ ParentScb - Supplies the parent of the current Scb. The name for the target
+ scb is appended to the name in this Scb.
+
+ Scb - Supplies the target Scb to add the name to.
+
+ FileName - If present this is a filename attribute for this Scb. We check
+ that it is not a DOS-only name.
+
+ CheckBufferSizeOnly - Indicates that we don't want to change the name yet. Just
+ verify that the buffer is the correct size.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT Context;
+ BOOLEAN CleanupContext = FALSE;
+ BOOLEAN Found;
+ ULONG Length;
+ PVOID NewBuffer = Scb->ScbType.Index.NormalizedName.Buffer;
+ PVOID OldBuffer = NULL;
+ PCHAR NextChar;
+
+ PAGED_CODE();
+
+ ASSERT( NodeType( Scb ) == NTFS_NTC_SCB_INDEX );
+ ASSERT( NodeType( ParentScb ) == NTFS_NTC_SCB_INDEX ||
+ NodeType( ParentScb ) == NTFS_NTC_SCB_ROOT_INDEX );
+ ASSERT( ParentScb->ScbType.Index.NormalizedName.Buffer != NULL );
+
+ //
+ // Use a try-finally to clean up the attribute context.
+ //
+
+ try {
+
+ //
+ // If the filename isn't present or is a DOS-only name then go to
+ // disk to find another name for this Scb.
+ //
+
+ if (!ARGUMENT_PRESENT( FileName ) ||
+ (FileName->Flags == FILE_NAME_DOS)) {
+
+ NtfsInitializeAttributeContext( &Context );
+ CleanupContext = TRUE;
+
+ //
+ // Walk through the names for this entry. There better
+ // be one which is not a DOS-only name.
+ //
+
+ Found = NtfsLookupAttributeByCode( IrpContext,
+ Scb->Fcb,
+ &Scb->Fcb->FileReference,
+ $FILE_NAME,
+ &Context );
+
+ while (Found) {
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &Context ));
+
+ if (FileName->Flags != FILE_NAME_DOS) {
+
+ break;
+ }
+
+ Found = NtfsLookupNextAttributeByCode( IrpContext,
+ Scb->Fcb,
+ $FILE_NAME,
+ &Context );
+ }
+
+ //
+ // We should have found the entry.
+ //
+
+ if (!Found) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Scb->Fcb );
+ }
+ }
+
+ //
+ // Now that we have the file name attribute allocate the paged pool
+ // for the name.
+ //
+
+ Length = ParentScb->ScbType.Index.NormalizedName.Length +
+ ((FileName->FileNameLength + 1) * sizeof( WCHAR ));
+
+ //
+ // If the parent is the root then we don't need an extra separator.
+ //
+
+ if (ParentScb == ParentScb->Vcb->RootIndexScb) {
+
+ Length -= sizeof( WCHAR );
+ }
+
+ //
+ // If the current buffer is insufficient then allocate a new one.
+ //
+
+ if ((NewBuffer == NULL) ||
+ (Scb->ScbType.Index.NormalizedName.MaximumLength < Length)) {
+
+ OldBuffer = NewBuffer;
+ NewBuffer = NtfsAllocatePool( PagedPool, Length );
+
+ if (OldBuffer != NULL) {
+
+ RtlCopyMemory( NewBuffer,
+ OldBuffer,
+ Scb->ScbType.Index.NormalizedName.MaximumLength );
+ }
+
+ //
+ // Now swap the buffers.
+ //
+
+ Scb->ScbType.Index.NormalizedName.Buffer = NewBuffer;
+ Scb->ScbType.Index.NormalizedName.MaximumLength = (USHORT) Length;
+ }
+
+ //
+ // If we are setting the buffer sizes only then make sure we set the length
+ // to zero if there is nothing in the buffer.
+ //
+
+ if (CheckBufferSizeOnly) {
+
+ if (OldBuffer == NULL) {
+
+ Scb->ScbType.Index.NormalizedName.Length = 0;
+ }
+
+ } else {
+
+ Scb->ScbType.Index.NormalizedName.Length = (USHORT) Length;
+ NextChar = (PCHAR) Scb->ScbType.Index.NormalizedName.Buffer;
+
+ //
+ // Now copy the name in. Don't forget to add the separator if the parent isn't
+ // the root.
+ //
+
+ RtlCopyMemory( NextChar,
+ ParentScb->ScbType.Index.NormalizedName.Buffer,
+ ParentScb->ScbType.Index.NormalizedName.Length );
+
+ NextChar += ParentScb->ScbType.Index.NormalizedName.Length;
+
+ if (ParentScb != ParentScb->Vcb->RootIndexScb) {
+
+ *((PWCHAR) NextChar) = L'\\';
+ NextChar += sizeof( WCHAR );
+ }
+
+ //
+ // Now append this name to the parent name.
+ //
+
+ RtlCopyMemory( NextChar,
+ FileName->FileName,
+ FileName->FileNameLength * sizeof( WCHAR ));
+ }
+
+ if (OldBuffer != NULL) {
+
+ NtfsFreePool( OldBuffer );
+ }
+
+ } finally {
+
+ if (CleanupContext) {
+
+ NtfsCleanupAttributeContext( &Context );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsBuildNormalizedName (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ OUT PUNICODE_STRING PathName
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to build a normalized name for an scb by looking
+ up the file name attributes up to the root directory.
+
+Arguments:
+
+ Scb - Supplies the starting point.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB ThisFcb = Scb->Fcb;
+ PFCB NextFcb;
+ PSCB NextScb;
+ PLCB NextLcb;
+ BOOLEAN AcquiredNextFcb = FALSE;
+ BOOLEAN AcquiredThisFcb = FALSE;
+
+ BOOLEAN AcquiredFcbTable = FALSE;
+
+ USHORT NewMaximumLength;
+ PWCHAR NewBuffer;
+ UNICODE_STRING NormalizedName;
+ UNICODE_STRING ComponentName;
+
+ BOOLEAN FoundEntry = TRUE;
+ BOOLEAN CleanupAttrContext = FALSE;
+ PFILE_NAME FileName;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ PAGED_CODE();
+
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_RESOURCE( &Scb->Vcb->Resource );
+
+ NormalizedName.Buffer = NULL;
+ NormalizedName.MaximumLength =
+ NormalizedName.Length = 0;
+
+ //
+ // Special case the root directory.
+ //
+
+ if (ThisFcb == ThisFcb->Vcb->RootIndexScb->Fcb) {
+
+ NormalizedName.Buffer = NtfsAllocatePool( PagedPool, sizeof( WCHAR ) );
+ NormalizedName.Buffer[0] = L'\\';
+ NormalizedName.MaximumLength =
+ NormalizedName.Length = sizeof( WCHAR );
+
+ *PathName = NormalizedName;
+ return;
+ }
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ while (TRUE) {
+
+ //
+ // Find a non-dos name for the current Scb. There better be one.
+ //
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ FoundEntry = NtfsLookupAttributeByCode( IrpContext,
+ ThisFcb,
+ &ThisFcb->FileReference,
+ $FILE_NAME,
+ &AttrContext );
+
+ while (FoundEntry) {
+
+ FileName = (PFILE_NAME) NtfsAttributeValue( NtfsFoundAttribute( &AttrContext ));
+
+ if (FileName->Flags != FILE_NAME_DOS ) { break; }
+
+ FoundEntry = NtfsLookupNextAttributeByCode( IrpContext,
+ ThisFcb,
+ $FILE_NAME,
+ &AttrContext );
+ }
+
+ if (!FoundEntry) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, NextFcb );
+ }
+
+ //
+ // Add the name from the filename attribute to the buffer we are building up.
+ // We always allow space for the leadingseparator. If we need to grow the buffer then
+ // always round up to reduce the number of allocations we make.
+ //
+
+ NewMaximumLength = NormalizedName.Length +
+ ((1 + FileName->FileNameLength) * sizeof( WCHAR ));
+
+ if (NormalizedName.MaximumLength < NewMaximumLength) {
+
+ //
+ // Round up to a pool block boundary, allow for the pool header as well.
+ //
+
+ NewMaximumLength = ((NewMaximumLength + 8 + 0x40 - 1) & ~(0x40 - 1)) - 8;
+
+ NewBuffer = NtfsAllocatePool( PagedPool, NewMaximumLength );
+
+ //
+ // Copy over the existing part of the name to the correct location.
+ //
+
+ if (NormalizedName.Length != 0) {
+
+ RtlCopyMemory( (NewBuffer + FileName->FileNameLength + 1),
+ NormalizedName.Buffer,
+ NormalizedName.Length );
+
+ NtfsFreePool( NormalizedName.Buffer );
+ }
+
+ NormalizedName.Buffer = NewBuffer;
+ NormalizedName.MaximumLength = NewMaximumLength;
+
+ } else {
+
+ //
+ // Move the existing data down in the buffer.
+ //
+
+ RtlMoveMemory( &NormalizedName.Buffer[FileName->FileNameLength + 1],
+ NormalizedName.Buffer,
+ NormalizedName.Length );
+
+ }
+
+ //
+ // Update the length of the normalized name.
+ //
+
+ NormalizedName.Length += (1 + FileName->FileNameLength) * sizeof( WCHAR );
+
+ //
+ // Now copy over the new component name along with a preceding backslash.
+ //
+
+ NormalizedName.Buffer[0] = L'\\';
+
+ RtlCopyMemory( &NormalizedName.Buffer[1],
+ FileName->FileName,
+ FileName->FileNameLength * sizeof( WCHAR ));
+
+ //
+ // Now get the parent for the current component. Acquire the Fcb for synchronization.
+ // We can either walk up the Lcb chain or look it up in the Fcb table. It
+ // must be for the same name as the file name since there is only one path
+ // up the tree for a directory.
+ //
+
+ if (!IsListEmpty( &ThisFcb->LcbQueue )) {
+
+ NextLcb = (PLCB) CONTAINING_RECORD( ThisFcb->LcbQueue.Flink, LCB, FcbLinks );
+ NextScb = NextLcb->Scb;
+ NextFcb = NextScb->Fcb;
+
+ NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, TRUE, FALSE );
+ AcquiredNextFcb = TRUE;
+
+ ASSERT( NtfsEqualMftRef( &FileName->ParentDirectory, &NextFcb->FileReference ));
+
+ } else {
+
+ NtfsAcquireFcbTable( IrpContext, Scb->Vcb );
+ AcquiredFcbTable = TRUE;
+
+ NextFcb = NtfsCreateFcb( IrpContext,
+ Scb->Vcb,
+ FileName->ParentDirectory,
+ FALSE,
+ TRUE,
+ NULL );
+
+ NextFcb->ReferenceCount -= 1;
+
+ //
+ // Try to do an unsafe acquire. Otherwise we must drop the Fcb table
+ // and acquire the Fcb and then reacquire the Fcb table.
+ //
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, TRUE, TRUE )) {
+
+ NtfsReleaseFcbTable( IrpContext, Scb->Vcb );
+ NtfsAcquireExclusiveFcb( IrpContext, NextFcb, NULL, TRUE, FALSE );
+ NtfsAcquireFcbTable( IrpContext, Scb->Vcb );
+ }
+
+ NextFcb->ReferenceCount -= 1;
+ NtfsReleaseFcbTable( IrpContext, Scb->Vcb );
+ AcquiredFcbTable = FALSE;
+ AcquiredNextFcb = TRUE;
+
+ NextScb = NtfsCreateScb( IrpContext,
+ NextFcb,
+ $INDEX_ALLOCATION,
+ &NtfsFileNameIndex,
+ FALSE,
+ NULL );
+
+ ComponentName.Buffer = FileName->FileName;
+ ComponentName.MaximumLength =
+ ComponentName.Length = FileName->FileNameLength * sizeof( WCHAR );
+
+ NextLcb = NtfsCreateLcb( IrpContext,
+ NextScb,
+ ThisFcb,
+ ComponentName,
+ FileName->Flags,
+ NULL );
+ }
+
+ //
+ // If we reach the root then exit. The preceding backslash is already stored.
+ //
+
+ if (NextScb == NextScb->Vcb->RootIndexScb) { break; }
+
+ //
+ // If we reach an Scb which has a normalized name then prepend it
+ // to the name we have built up and store it into the normalized
+ // name buffer and exit.
+ //
+
+ if ((NextScb->ScbType.Index.NormalizedName.Buffer != NULL) &&
+ (NextScb->ScbType.Index.NormalizedName.Length != 0)) {
+
+ //
+ // Compute the new maximum length value.
+ //
+
+ NewMaximumLength = NextScb->ScbType.Index.NormalizedName.Length + NormalizedName.Length;
+
+ //
+ // Allocate a new buffer if needed.
+ //
+
+ if (NewMaximumLength > NormalizedName.MaximumLength) {
+
+ NewBuffer = NtfsAllocatePool( PagedPool, NewMaximumLength );
+
+ RtlCopyMemory( Add2Ptr( NewBuffer, NextScb->ScbType.Index.NormalizedName.Length ),
+ NormalizedName.Buffer,
+ NormalizedName.Length );
+
+ NtfsFreePool( NormalizedName.Buffer );
+
+ NormalizedName.Buffer = NewBuffer;
+ NormalizedName.MaximumLength = NewMaximumLength;
+
+ } else {
+
+ RtlMoveMemory( Add2Ptr( NormalizedName.Buffer, NextScb->ScbType.Index.NormalizedName.Length ),
+ NormalizedName.Buffer,
+ NormalizedName.Length );
+ }
+
+ //
+ // Now copy over the name from the root to this point.
+ //
+
+ RtlCopyMemory( NormalizedName.Buffer,
+ NextScb->ScbType.Index.NormalizedName.Buffer,
+ NextScb->ScbType.Index.NormalizedName.Length );
+
+ NormalizedName.Length = NewMaximumLength;
+
+ break;
+ }
+
+ //
+ // Release the current Fcb and move up the tree.
+ //
+
+ if (AcquiredThisFcb) { NtfsReleaseFcb( IrpContext, ThisFcb ); }
+
+ ThisFcb = NextFcb;
+ AcquiredThisFcb = TRUE;
+ AcquiredNextFcb = FALSE;
+ }
+
+ //
+ // Now store the normalized name into the callers filename structure.
+ //
+
+ *PathName = NormalizedName;
+ NormalizedName.Buffer = NULL;
+
+ } finally {
+
+ if (AcquiredFcbTable) { NtfsReleaseFcbTable( IrpContext, Scb->Vcb ); }
+ if (AcquiredNextFcb) { NtfsReleaseFcb( IrpContext, NextFcb ); }
+ if (AcquiredThisFcb) { NtfsReleaseFcb( IrpContext, ThisFcb ); }
+
+ if (CleanupAttrContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ if (NormalizedName.Buffer != NULL) {
+
+ NtfsFreePool( NormalizedName.Buffer );
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsSnapshotScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine snapshots necessary Scb data, such as the Scb file sizes,
+ so that they may be correctly restored if the caller's I/O request is
+ aborted for any reason. The restoring of these values and the freeing
+ of any pool involved is automatic.
+
+Arguments:
+
+ Scb - Supplies the current Scb
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSCB_SNAPSHOT ScbSnapshot;
+
+ ASSERT_EXCLUSIVE_SCB(Scb);
+
+ ScbSnapshot = &IrpContext->ScbSnapshot;
+
+ //
+ // Only do the snapshot if the Scb is initialized, we have not done
+ // so already, and it is worth special-casing the bitmap, as it never changes!
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED) &&
+ (Scb->ScbSnapshot == NULL) && (Scb != Scb->Vcb->BitmapScb)) {
+
+ //
+ // If the snapshot structure in the IrpContext is in use, then we have
+ // to allocate one and insert it in the list.
+ //
+
+ if (ScbSnapshot->SnapshotLinks.Flink != NULL) {
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ExAllocateFromNPagedLookasideList( &NtfsScbSnapshotLookasideList );
+
+ InsertTailList( &IrpContext->ScbSnapshot.SnapshotLinks,
+ &ScbSnapshot->SnapshotLinks );
+
+ //
+ // Otherwise we will initialize the listhead to show that the structure
+ // in the IrpContext is in use.
+ //
+
+ } else {
+
+ InitializeListHead( &ScbSnapshot->SnapshotLinks );
+ }
+
+ //
+ // Snapshot the Scb values and point the Scb and snapshot structure
+ // at each other.
+ //
+
+ ScbSnapshot->AllocationSize = Scb->Header.AllocationSize.QuadPart;
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+ ((ULONG)ScbSnapshot->AllocationSize) += 1;
+ }
+
+ ScbSnapshot->FileSize = Scb->Header.FileSize.QuadPart;
+ ScbSnapshot->ValidDataLength = Scb->Header.ValidDataLength.QuadPart;
+ ScbSnapshot->ValidDataToDisk = Scb->ValidDataToDisk;
+ ScbSnapshot->Scb = Scb;
+ ScbSnapshot->LowestModifiedVcn = MAXLONGLONG;
+ ScbSnapshot->HighestModifiedVcn = 0;
+
+ ScbSnapshot->TotalAllocated = Scb->TotalAllocated;
+
+#ifdef _CAIRO_
+
+ ScbSnapshot->ScbState = FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT |
+ SCB_STATE_QUOTA_ENLARGED );
+#endif // _CAIRO_
+
+ Scb->ScbSnapshot = ScbSnapshot;
+
+ //
+ // If this is the Mft Scb then initialize the cluster Mcb structures.
+ //
+
+ if (Scb == Scb->Vcb->MftScb) {
+
+ FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.AddedClusters, 0 );
+ FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.RemovedClusters, 0 );
+
+ Scb->ScbType.Mft.FreeRecordChange = 0;
+ Scb->ScbType.Mft.HoleRecordChange = 0;
+ }
+ }
+}
+
+
+VOID
+NtfsUpdateScbSnapshots (
+ IN PIRP_CONTEXT IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to update the snapshot values for all Scbs,
+ after completing a transaction checkpoint.
+
+Arguments:
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSCB_SNAPSHOT ScbSnapshot;
+ PSCB Scb;
+
+ ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
+
+ PAGED_CODE();
+
+ ScbSnapshot = &IrpContext->ScbSnapshot;
+
+ //
+ // There is no snapshot data to update if the Flink is still NULL.
+ //
+
+ if (ScbSnapshot->SnapshotLinks.Flink != NULL) {
+
+ //
+ // Loop to update first the Scb data from the snapshot in the
+ // IrpContext, and then 0 or more additional snapshots linked
+ // to the IrpContext.
+ //
+
+ do {
+
+ Scb = ScbSnapshot->Scb;
+
+ //
+ // Update the Scb values.
+ //
+
+ if (Scb != NULL) {
+
+ ScbSnapshot->AllocationSize = Scb->Header.AllocationSize.QuadPart;
+ if (FlagOn(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT)) {
+ ((ULONG)ScbSnapshot->AllocationSize) += 1;
+ }
+
+ //
+ // If this is the MftScb then clear out the added/removed
+ // cluster Mcbs.
+ //
+
+ if (Scb == Scb->Vcb->MftScb) {
+
+ FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.AddedClusters, (LONGLONG)0 );
+ FsRtlTruncateLargeMcb( &Scb->ScbType.Mft.RemovedClusters, (LONGLONG)0 );
+
+ Scb->ScbType.Mft.FreeRecordChange = 0;
+ Scb->ScbType.Mft.HoleRecordChange = 0;
+ }
+
+ ScbSnapshot->FileSize = Scb->Header.FileSize.QuadPart;
+ ScbSnapshot->ValidDataLength = Scb->Header.ValidDataLength.QuadPart;
+ ScbSnapshot->ValidDataToDisk = Scb->ValidDataToDisk;
+ ScbSnapshot->TotalAllocated = Scb->TotalAllocated;
+#ifdef _CAIRO_
+
+ ScbSnapshot->ScbState = FlagOn( Scb->ScbState,
+ SCB_STATE_ATTRIBUTE_RESIDENT |
+ SCB_STATE_QUOTA_ENLARGED );
+#endif // _CAIRO_
+
+ }
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+
+ } while (ScbSnapshot != &IrpContext->ScbSnapshot);
+ }
+}
+
+
+VOID
+NtfsRestoreScbSnapshots (
+ IN PIRP_CONTEXT IrpContext,
+ IN BOOLEAN Higher
+ )
+
+/*++
+
+Routine Description:
+
+ This routine restores snapshot Scb data in the event of an aborted request.
+
+Arguments:
+
+ Higher - Specified as TRUE to restore only those Scb values which are
+ higher than current values. Specified as FALSE to restore
+ only those Scb values which are lower (or same!).
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ BOOLEAN UpdateCc;
+ PSCB_SNAPSHOT ScbSnapshot;
+ PSCB Scb;
+ PVCB Vcb = IrpContext->Vcb;
+
+ ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
+
+ ScbSnapshot = &IrpContext->ScbSnapshot;
+
+ //
+ // There is no snapshot data to restore if the Flink is still NULL.
+ //
+
+ if (ScbSnapshot->SnapshotLinks.Flink != NULL) {
+
+ //
+ // Loop to retore first the Scb data from the snapshot in the
+ // IrpContext, and then 0 or more additional snapshots linked
+ // to the IrpContext.
+ //
+
+ do {
+
+ PSECTION_OBJECT_POINTERS SectionObjectPointer;
+ PFILE_OBJECT PseudoFileObject;
+
+ Scb = ScbSnapshot->Scb;
+
+ if (Scb == NULL) {
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+ continue;
+ }
+
+ //
+ // Increment the cleanup count so the Scb won't go away.
+ //
+
+ InterlockedIncrement( &Scb->CleanupCount );
+
+ //
+ // We update the Scb file size in the correct pass. We always do
+ // the extend/truncate pair.
+ //
+ // Only do sizes if our caller was changing these fields, which we can
+ // see from the Fcb PagingIo being acquired exclusive, the Scb is
+ // locked for IoAtEof, or else it is metadata.
+ //
+ // The one unusual case is where we are converting a stream to
+ // nonresident when this is not the stream for the request. We
+ // must restore the Scb for this case as well.
+ //
+
+ UpdateCc = FALSE;
+ if (FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE | SCB_STATE_CONVERT_UNDERWAY) ||
+ (Scb->Fcb == IrpContext->FcbWithPagingExclusive) ||
+ (Scb == (PSCB)IrpContext->FcbWithPagingExclusive)) {
+
+ //
+ // Proceed to restore all values which are in higher or not
+ // higher.
+ //
+ // Note that the low bit of the allocation size being set
+ // can only affect the tests if the sizes were equal anyway,
+ // i.e., sometimes we will execute this code unnecessarily,
+ // when the values did not change.
+ //
+
+ if (Higher == (ScbSnapshot->AllocationSize >=
+ Scb->Header.AllocationSize.QuadPart)) {
+
+ //
+ // If this is the maximize pass, we want to extend the cache section.
+ // In all cases we restore the allocation size in the Scb and
+ // recover the resident bit.
+ //
+
+ Scb->Header.AllocationSize.QuadPart = ScbSnapshot->AllocationSize;
+
+ if (FlagOn(Scb->Header.AllocationSize.LowPart, 1)) {
+
+ Scb->Header.AllocationSize.LowPart -= 1;
+ SetFlag(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT);
+
+ } else {
+
+ ClearFlag(Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT);
+ }
+
+ //
+ // Calculate FastIoPossible
+ //
+
+ if (Scb->CompressionUnit != 0) {
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+ }
+
+ NtfsAcquireFsrtlHeader( Scb );
+ if (Higher == (ScbSnapshot->FileSize >
+ Scb->Header.FileSize.QuadPart)) {
+
+ Scb->Header.FileSize.QuadPart = ScbSnapshot->FileSize;
+
+ //
+ // We really only need to update Cc if FileSize changes,
+ // since he does not look at ValidDataLength, and he
+ // only cares about successfully reached highwatermarks
+ // on AllocationSize (making section big enough).
+ //
+ // Note that setting this flag TRUE also implies we
+ // are correctly synchronized with FileSize!
+ //
+
+ UpdateCc = TRUE;
+ }
+
+ if (Higher == (ScbSnapshot->ValidDataLength >
+ Scb->Header.ValidDataLength.QuadPart)) {
+
+ Scb->Header.ValidDataLength.QuadPart = ScbSnapshot->ValidDataLength;
+ }
+
+ //
+ // If this is the unnamed data attribute, we have to update
+ // some Fcb fields for standard information as well.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.FileSize = Scb->Header.FileSize.QuadPart;
+ }
+
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ if (!Higher) {
+
+ Scb->ValidDataToDisk = ScbSnapshot->ValidDataToDisk;
+
+ //
+ // We always truncate the Mcb to the original allocation size.
+ // If the Mcb has shrunk beyond this, this becomes a noop.
+ // If the file is resident, then we will uninitialize
+ // and reinitialize the Mcb.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // Remove all of the mappings in the Mcb.
+ //
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb, (LONGLONG)0, MAXLONGLONG, FALSE, FALSE );
+
+ //
+ // If we attempted a convert a data attribute to non-
+ // resident and failed then need to nuke the pages in the
+ // section if this is not a user file. This is because for
+ // resident system attributes we always update the attribute
+ // directly and don't want to reference stale data in the
+ // section if we do a convert to non-resident later.
+ //
+
+ if ((Scb->AttributeTypeCode != $DATA) &&
+ (Scb->NonpagedScb->SegmentObject.SharedCacheMap != NULL)) {
+
+ if (!CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
+ NULL,
+ 0,
+ FALSE )) {
+
+ ASSERTMSG( "Failed to purge Scb during restore\n", FALSE );
+ }
+
+ //
+ // If the attribute is for non-user data, then we
+ // want to modify this Scb so it won't be used again.
+ // Set the sizes to zero, mark it as being initialized
+ // and deleted and then change the attribute type code
+ // so we won't ever return it via NtfsCreateScb.
+ //
+
+#ifdef _CAIRO_
+ if (!NtfsIsTypeCodeUserData( Scb->AttributeTypeCode )) {
+#endif // _CAIRO_
+ NtfsAcquireFsrtlHeader( Scb );
+ Scb->Header.AllocationSize =
+ Scb->Header.FileSize =
+ Scb->Header.ValidDataLength = Li0;
+ NtfsReleaseFsrtlHeader( Scb );
+ Scb->ValidDataToDisk = 0;
+
+ SetFlag( Scb->ScbState,
+ SCB_STATE_FILE_SIZE_LOADED |
+ SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_ATTRIBUTE_DELETED );
+
+ Scb->AttributeTypeCode = $UNUSED;
+#ifdef _CAIRO_
+ }
+#endif // _CAIRO_
+ }
+
+ //
+ // If we have modified this Mcb and want to back out any
+ // changes then truncate the Mcb. Don't do the Mft, because
+ // that is handled elsewhere.
+ //
+
+ } else if ((ScbSnapshot->LowestModifiedVcn != MAXLONGLONG) &&
+ (Scb != Vcb->MftScb)) {
+
+ //
+ // Truncate the Mcb.
+ //
+
+ NtfsUnloadNtfsMcbRange( &Scb->Mcb, ScbSnapshot->LowestModifiedVcn, ScbSnapshot->HighestModifiedVcn, FALSE, FALSE );
+ }
+
+ Scb->TotalAllocated = ScbSnapshot->TotalAllocated;
+
+ //
+ // If the compression unit is non-zero then set the flag in the
+ // common header for the Modified page writer.
+ //
+
+ ASSERT( (Scb->CompressionUnit == 0) ||
+ (Scb->AttributeTypeCode == $INDEX_ROOT) ||
+ NtfsIsTypeCodeCompressible( Scb->AttributeTypeCode ));
+
+ } else {
+
+ //
+ // Set the flag to indicate that we're performing a restore on this
+ // Scb. We don't want to write any new log records as a result of
+ // this operation other than the abort records.
+ //
+
+ SetFlag( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY );
+ }
+
+#ifdef _CAIRO_
+
+ ClearFlag( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED );
+ SetFlag( Scb->ScbState, FlagOn( ScbSnapshot->ScbState, SCB_STATE_QUOTA_ENLARGED ));
+
+#endif // _CAIRO_
+
+
+ //
+ // Be sure to update Cache Manager. The interface here uses a file
+ // object but the routine itself only uses the section object pointers.
+ // We put a pointer to the segment object pointers on the stack and
+ // cast some prior value as a file object pointer.
+ //
+
+ PseudoFileObject = (PFILE_OBJECT) CONTAINING_RECORD( &SectionObjectPointer,
+ FILE_OBJECT,
+ SectionObjectPointer );
+ PseudoFileObject->SectionObjectPointer = &Scb->NonpagedScb->SegmentObject;
+
+ //
+ // Now tell the cache manager the sizes.
+ //
+ // If we fail in this call, we definitely want to charge on anyway.
+ // It should only fail if it tries to extend the section and cannot,
+ // in which case we do not care because we cannot need the extended
+ // part to the section anyway. (This is probably the very error that
+ // is causing us to clean up in the first place!)
+ //
+ // We don't need to make this call if the top level request is a
+ // paging Io write.
+ //
+ // We only do this if there is a shared cache map for this stream.
+ // Otherwise CC will cause a flush to happen which could screw up
+ // the transaction and abort logic.
+ //
+
+ if (UpdateCc && CcIsFileCached( PseudoFileObject ) &&
+ (IrpContext->OriginatingIrp == NULL ||
+ IrpContext->OriginatingIrp->Type != IO_TYPE_IRP ||
+ IrpContext->MajorFunction != IRP_MJ_WRITE ||
+ !FlagOn( IrpContext->OriginatingIrp->Flags, IRP_PAGING_IO ))) {
+
+ try {
+
+ CcSetFileSizes( PseudoFileObject,
+ (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ } except(FsRtlIsNtstatusExpected(GetExceptionCode()) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH) {
+ NOTHING;
+ }
+ }
+
+ //
+ // If this is the unnamed data attribute, we have to update
+ // some Fcb fields for standard information as well.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ Scb->Fcb->Info.AllocatedLength = Scb->TotalAllocated;
+ }
+
+ //
+ // We always clear the Scb deleted flag and the deleted flag in the Fcb
+ // unless this was a create new file operation which failed. We recognize
+ // this by looking for the major Irp code in the IrpContext, and the
+ // deleted bit in the Fcb.
+ //
+
+ if (Scb->AttributeTypeCode != $UNUSED &&
+ (IrpContext->MajorFunction != IRP_MJ_CREATE ||
+ !FlagOn( Scb->Fcb->FcbState, FCB_STATE_FILE_DELETED ))) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+ ClearFlag( Scb->Fcb->FcbState, FCB_STATE_FILE_DELETED );
+ }
+
+ //
+ // Clear the flags in the Scb if this Scb is from a create
+ // that failed. We always clear our RESTORE_UNDERWAY flag.
+ //
+ // If this is an Index allocation or Mft bitmap, then we
+ // store MAXULONG in the record allocation context to indicate
+ // that we should reinitialize it.
+ //
+
+ if (!Higher) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY );
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_UNINITIALIZE_ON_RESTORE )) {
+
+ ClearFlag( Scb->ScbState, SCB_STATE_FILE_SIZE_LOADED |
+ SCB_STATE_HEADER_INITIALIZED |
+ SCB_STATE_UNINITIALIZE_ON_RESTORE );
+ }
+
+ //
+ // If this is the MftScb we have several jobs to do.
+ //
+ // - Force the record allocation context to be reinitialized
+ // - Back out the changes to the Vcb->MftFreeRecords field
+ // - Back changes to the Vcb->MftHoleRecords field
+ // - Clear the flag indicating we allocated file record 15
+ // - Clear the flag indicating we reserved a record
+ // - Remove any clusters added to the Scb Mcb
+ // - Restore any clusters removed from the Scb Mcb
+ //
+
+ if (Scb == Vcb->MftScb) {
+
+ ULONG RunIndex;
+ VCN Vcn;
+ LCN Lcn;
+ LONGLONG Clusters;
+
+ Vcb->MftBitmapAllocationContext.CurrentBitmapSize = MAXULONG;
+ (LONG) Vcb->MftFreeRecords -= Scb->ScbType.Mft.FreeRecordChange;
+ (LONG) Vcb->MftHoleRecords -= Scb->ScbType.Mft.HoleRecordChange;
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_15_USED )) {
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_15_USED );
+ ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_15_USED );
+ }
+
+ if (FlagOn( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED )) {
+
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_MFT_RECORD_RESERVED );
+ ClearFlag( Vcb->MftReserveFlags, VCB_MFT_RECORD_RESERVED );
+
+ Scb->ScbType.Mft.ReservedIndex = 0;
+ }
+
+ RunIndex = 0;
+
+ while (FsRtlGetNextLargeMcbEntry( &Scb->ScbType.Mft.AddedClusters,
+ RunIndex,
+ &Vcn,
+ &Lcn,
+ &Clusters )) {
+
+ if (Lcn != UNUSED_LCN) {
+
+ NtfsRemoveNtfsMcbEntry( &Scb->Mcb, Vcn, Clusters );
+ }
+
+ RunIndex += 1;
+ }
+
+ RunIndex = 0;
+
+ while (FsRtlGetNextLargeMcbEntry( &Scb->ScbType.Mft.RemovedClusters,
+ RunIndex,
+ &Vcn,
+ &Lcn,
+ &Clusters )) {
+
+ if (Lcn != UNUSED_LCN) {
+
+ NtfsAddNtfsMcbEntry( &Scb->Mcb, Vcn, Lcn, Clusters, FALSE );
+ }
+
+ RunIndex += 1;
+ }
+
+ } else if (Scb->AttributeTypeCode == $INDEX_ALLOCATION) {
+
+ Scb->ScbType.Index.RecordAllocationContext.CurrentBitmapSize = MAXULONG;
+ }
+ }
+
+ //
+ // Decrement the cleanup count to restore the previous value.
+ //
+
+ InterlockedDecrement( &Scb->CleanupCount );
+
+ ScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+
+ } while (ScbSnapshot != &IrpContext->ScbSnapshot);
+ }
+}
+
+
+VOID
+NtfsFreeSnapshotsForFcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine restores snapshot Scb data in the event of an aborted request.
+
+Arguments:
+
+ Fcb - Fcb for which all snapshots are to be freed, or NULL to free all
+ snapshots.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSCB_SNAPSHOT ScbSnapshot;
+
+ ASSERT(FIELD_OFFSET(SCB_SNAPSHOT, SnapshotLinks) == 0);
+
+ ScbSnapshot = &IrpContext->ScbSnapshot;
+
+ //
+ // There is no snapshot data to free if the Flink is still NULL.
+ // We also don't free the snapshot if this isn't a top-level action.
+ //
+
+ if (ScbSnapshot->SnapshotLinks.Flink != NULL) {
+
+ //
+ // Loop to free first the Scb data from the snapshot in the
+ // IrpContext, and then 0 or more additional snapshots linked
+ // to the IrpContext.
+ //
+
+ do {
+
+ PSCB_SNAPSHOT NextScbSnapshot;
+
+ //
+ // Move to next snapshot before we delete the current one.
+ //
+
+ NextScbSnapshot = (PSCB_SNAPSHOT)ScbSnapshot->SnapshotLinks.Flink;
+
+ //
+ // We are now at a snapshot in the snapshot list. We skip
+ // over this entry if it has an Scb and the Fcb for that
+ // Scb does not match the input Fcb. If there is no
+ // input Fcb we always deal with this snapshot.
+ //
+
+ if (ScbSnapshot->Scb != NULL
+ && Fcb != NULL
+ && ScbSnapshot->Scb->Fcb != Fcb) {
+
+ ScbSnapshot = NextScbSnapshot;
+ continue;
+ }
+
+ //
+ // If there is an Scb, then clear its snapshot pointer.
+ // Always clear the UNINITIALIZE_ON_RESTORE flag, RESTORE_UNDERWAY and
+ // CONVERT_UNDERWAY flags.
+ //
+
+ if (ScbSnapshot->Scb != NULL) {
+
+ if (FlagOn( ScbSnapshot->Scb->ScbState,
+ SCB_STATE_UNINITIALIZE_ON_RESTORE | SCB_STATE_RESTORE_UNDERWAY | SCB_STATE_CONVERT_UNDERWAY )) {
+
+ NtfsAcquireFsrtlHeader( ScbSnapshot->Scb );
+ ClearFlag( ScbSnapshot->Scb->ScbState,
+ SCB_STATE_UNINITIALIZE_ON_RESTORE | SCB_STATE_RESTORE_UNDERWAY | SCB_STATE_CONVERT_UNDERWAY );
+ NtfsReleaseFsrtlHeader( ScbSnapshot->Scb );
+ }
+
+ ScbSnapshot->Scb->ScbSnapshot = NULL;
+ }
+
+ if (ScbSnapshot == &IrpContext->ScbSnapshot) {
+
+ IrpContext->ScbSnapshot.Scb = NULL;
+
+ //
+ // Else delete the snapshot structure
+ //
+
+ } else {
+
+ RemoveEntryList(&ScbSnapshot->SnapshotLinks);
+
+ ExFreeToNPagedLookasideList( &NtfsScbSnapshotLookasideList, ScbSnapshot );
+ }
+
+ ScbSnapshot = NextScbSnapshot;
+
+ } while (ScbSnapshot != &IrpContext->ScbSnapshot);
+ }
+}
+
+
+BOOLEAN
+NtfsCreateFileLock (
+ IN PSCB Scb,
+ IN BOOLEAN RaiseOnError
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to create and initialize a file lock structure.
+ A try-except is used to catch allocation failures if the caller doesn't
+ want the exception raised.
+
+Arguments:
+
+ Scb - Supplies the Scb to attach the file lock to.
+
+ RaiseOnError - If TRUE then don't catch the allocation failure.
+
+Return Value:
+
+ TRUE if the lock is allocated and initialized. FALSE if there is an
+ error and the caller didn't specify RaiseOnError.
+
+--*/
+
+{
+ PFILE_LOCK FileLock = NULL;
+ BOOLEAN Success = TRUE;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-except to catch all errors.
+ //
+
+ try {
+
+ FileLock = (PFILE_LOCK)ExAllocateFromNPagedLookasideList( &NtfsFileLockLookasideList );
+
+ FsRtlInitializeFileLock( FileLock, NULL, NULL );
+
+ //
+ // Use the FsRtl header mutex to synchronize storing
+ // the lock structure, and only store it if no one
+ // else beat us.
+ //
+
+ NtfsAcquireFsrtlHeader(Scb);
+
+ if (Scb->ScbType.Data.FileLock == NULL) {
+ Scb->ScbType.Data.FileLock = FileLock;
+ FileLock = NULL;
+ }
+
+ NtfsReleaseFsrtlHeader(Scb);
+
+ } except( (!FsRtlIsNtstatusExpected( GetExceptionCode() ) || RaiseOnError)
+ ? EXCEPTION_CONTINUE_SEARCH
+ : EXCEPTION_EXECUTE_HANDLER ) {
+
+ Success = FALSE;
+ }
+
+ if (FileLock != NULL) {
+ ExFreeToNPagedLookasideList( &NtfsFileLockLookasideList, FileLock );
+ }
+
+ return Success;
+}
+
+
+PSCB
+NtfsGetNextScb (
+ IN PSCB Scb,
+ IN PSCB TerminationScb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to iterate through Scbs in a tree.
+
+ The rules are:
+
+ . If you have a child, go to it, else
+ . If you have a next sibling, go to it, else
+ . Go to your parent's next sibling.
+
+ If this routine is called with in invalid TerminationScb it will fail,
+ badly.
+
+Arguments:
+
+ Scb - Supplies the current Scb
+
+ TerminationScb - The Scb at which the enumeration should (non-inclusively)
+ stop. Assumed to be a directory.
+
+Return Value:
+
+ The next Scb in the enumeration, or NULL if Scb was the final one.
+
+--*/
+
+{
+ PSCB Results;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsGetNextScb, Scb = %08lx, TerminationScb = %08lx\n", Scb, TerminationScb) );
+
+ //
+ // If this is an index (i.e., not a file) and it has children then return
+ // the scb for the first child
+ //
+ // Scb
+ //
+ // / \.
+ // / \.
+ //
+ // ChildLcb
+ //
+ // |
+ // |
+ //
+ // ChildFcb
+ //
+ // / \.
+ // / \.
+ //
+ // Results
+ //
+
+ if (((SafeNodeType(Scb) == NTFS_NTC_SCB_INDEX) || (SafeNodeType(Scb) == NTFS_NTC_SCB_ROOT_INDEX))
+
+ &&
+
+ !IsListEmpty(&Scb->ScbType.Index.LcbQueue)) {
+
+ PLCB ChildLcb;
+ PFCB ChildFcb;
+
+ //
+ // locate the first lcb out of this scb and also the corresponding fcb
+ //
+
+ ChildLcb = NtfsGetNextChildLcb(Scb, NULL);
+ ChildFcb = ChildLcb->Fcb;
+
+ //
+ // Then as a bookkeeping means for ourselves we will move this
+ // lcb to the head of the fcb's lcb queue that way when we
+ // need to ask which link we went through to get here we will know
+ //
+
+ RemoveEntryList( &ChildLcb->FcbLinks );
+ InsertHeadList( &ChildFcb->LcbQueue, &ChildLcb->FcbLinks );
+
+ //
+ // And our return value is the first scb of this fcb
+ //
+
+ ASSERT( !IsListEmpty(&ChildFcb->ScbQueue) );
+
+ //
+ // Acquire and drop the Fcb in order to look at the Scb list.
+ //
+
+ ExAcquireResourceExclusive( ChildFcb->Resource, TRUE );
+ Results = NtfsGetNextChildScb( ChildFcb, NULL );
+ ExReleaseResource( ChildFcb->Resource );
+
+ //
+ // We could be processing an empty index
+ //
+
+ } else if ( Scb == TerminationScb ) {
+
+ Results = NULL;
+
+ } else {
+
+ PSCB SiblingScb;
+ PFCB ParentFcb;
+ PLCB ParentLcb;
+ PLCB SiblingLcb;
+ PFCB SiblingFcb;
+
+ //
+ // Acquire and drop the Fcb in order to look at the Scb list.
+ //
+
+ ExAcquireResourceExclusive( Scb->Fcb->Resource, TRUE );
+ SiblingScb = NtfsGetNextChildScb( Scb->Fcb, Scb );
+ ExReleaseResource( Scb->Fcb->Resource );
+
+ while (TRUE) {
+
+ //
+ // If there is a sibling scb to the input scb then return it
+ //
+ // Fcb
+ //
+ // / \.
+ // / \.
+ //
+ // Scb Sibling
+ // Scb
+ //
+
+ if (SiblingScb != NULL) {
+
+ Results = SiblingScb;
+ break;
+ }
+
+ //
+ // The scb doesn't have any more siblings. See if our fcb has a sibling
+ //
+ // S
+ //
+ // / \.
+ // / \.
+ //
+ // ParentLcb SiblingLcb
+ //
+ // | |
+ // | |
+ //
+ // ParentFcb SiblingFcb
+ //
+ // / / \.
+ // / / \.
+ //
+ // Scb Results
+ //
+ // It's possible that the SiblingFcb has already been traversed.
+ // Consider the case where there are multiple links between the
+ // same Scb and Fcb. We want to ignore this case or else face
+ // an infinite loop by moving the Lcb to the beginning of the
+ // Fcb queue and then later finding an Lcb that we have already
+ // traverse. We use the fact that we haven't modified the
+ // ordering of the Lcb off the parent Scb. When we find a
+ // candidate for the next Fcb, we walk backwards through the
+ // list of Lcb's off the Scb to make sure this is not a
+ // duplicate Fcb.
+ //
+
+ ParentFcb = Scb->Fcb;
+
+ ParentLcb = NtfsGetNextParentLcb(ParentFcb, NULL);
+
+ //
+ // Try to find a sibling Lcb which does not point to an Fcb
+ // we've already visited.
+ //
+
+ SiblingLcb = ParentLcb;
+
+ while ((SiblingLcb = NtfsGetNextChildLcb( ParentLcb->Scb, SiblingLcb)) != NULL) {
+
+ PLCB PrevChildLcb;
+ PFCB PotentialSiblingFcb;
+
+ //
+ // Now walk through the child Lcb's of the Scb which we have
+ // already visited.
+ //
+
+ PrevChildLcb = SiblingLcb;
+ PotentialSiblingFcb = SiblingLcb->Fcb;
+
+ //
+ // Skip this Lcb if the Fcb has no children.
+ //
+
+ if (IsListEmpty( &PotentialSiblingFcb->ScbQueue )) {
+
+ continue;
+ }
+
+ while ((PrevChildLcb = NtfsGetPrevChildLcb( ParentLcb->Scb, PrevChildLcb )) != NULL) {
+
+ //
+ // If the parent Fcb and the Fcb for this Lcb are the same,
+ // then we have already returned the Scb's for this Fcb.
+ //
+
+ if (PrevChildLcb->Fcb == PotentialSiblingFcb) {
+
+ break;
+ }
+ }
+
+ //
+ // If we don't have a PrevChildLcb, that means that we have a valid
+ // sibling Lcb. We will ignore any sibling Lcb's whose
+ // Fcb's don't have any Scb's.
+ //
+
+ if (PrevChildLcb == NULL) {
+
+ break;
+ }
+ }
+
+ if (SiblingLcb != NULL) {
+
+ SiblingFcb = SiblingLcb->Fcb;
+
+ //
+ // Then as a bookkeeping means for ourselves we will move this
+ // lcb to the head of the fcb's lcb queue that way when we
+ // need to ask which link we went through to get here we will know
+ //
+
+ RemoveEntryList( &SiblingLcb->FcbLinks );
+ InsertHeadList( &SiblingFcb->LcbQueue, &SiblingLcb->FcbLinks );
+
+ //
+ // And our return value is the first scb of this fcb
+ //
+
+ ASSERT( !IsListEmpty(&SiblingFcb->ScbQueue) );
+
+ //
+ // Acquire and drop the Fcb in order to look at the Scb list.
+ //
+
+ ExAcquireResourceExclusive( SiblingFcb->Resource, TRUE );
+ Results = NtfsGetNextChildScb( SiblingFcb, NULL );
+ ExReleaseResource( SiblingFcb->Resource );
+ break;
+ }
+
+ //
+ // The Fcb has no sibling so bounce up one and see if we
+ // have reached our termination scb yet
+ //
+ // NewScb
+ //
+ // /
+ // /
+ //
+ // ParentLcb
+ //
+ // |
+ // |
+ //
+ // ParentFcb
+ //
+ // /
+ // /
+ //
+ // Scb
+ //
+ //
+
+ Scb = ParentLcb->Scb;
+
+ if (Scb == TerminationScb) {
+
+ Results = NULL;
+ break;
+ }
+
+ //
+ // Acquire and drop the Fcb in order to look at the Scb list.
+ //
+
+ ExAcquireResourceExclusive( Scb->Fcb->Resource, TRUE );
+ SiblingScb = NtfsGetNextChildScb( Scb->Fcb, Scb );
+ ExReleaseResource( Scb->Fcb->Resource );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsGetNextScb -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+PLCB
+NtfsCreateLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN UNICODE_STRING LastComponentFileName,
+ IN UCHAR FileNameFlags,
+ OUT PBOOLEAN ReturnedExistingLcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and creates a new lcb between an
+ existing scb and fcb. If a component of the exact
+ name already exists we return that one instead of creating
+ a new lcb
+
+Arguments:
+
+ Scb - Supplies the parent scb to use
+
+ Fcb - Supplies the child fcb to use
+
+ LastComponentFileName - Supplies the last component of the
+ path that this link represents
+
+ FileNameFlags - Indicates if this is an NTFS, DOS or hard link
+
+ ReturnedExistingLcb - Optionally tells the caller if the
+ lcb returned already existed
+
+Return Value:
+
+ LCB - returns a pointer the newly created lcb.
+
+--*/
+
+{
+ PLCB Lcb = NULL;
+ BOOLEAN LocalReturnedExistingLcb;
+
+ //
+ // The following variables are only used for abnormal termination
+ //
+
+ PVOID UnwindStorage[2] = { NULL, NULL };
+
+ PAGED_CODE();
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_FCB( Fcb );
+ ASSERT(NodeType(Scb) != NTFS_NTC_SCB_DATA);
+
+ DebugTrace( +1, Dbg, ("NtfsCreateLcb...\n") );
+
+ if (!ARGUMENT_PRESENT(ReturnedExistingLcb)) { ReturnedExistingLcb = &LocalReturnedExistingLcb; }
+
+ //
+ // Search the lcb children of the input Scb to see if we have an Lcb that matches
+ // this one. We match if the Lcb points to the same fcb and the last component file name
+ // and flags match. We ignore any Lcb's that indicate links that have been
+ // removed.
+ //
+
+ Lcb = NULL;
+
+ while ((Lcb = NtfsGetNextParentLcb(Fcb, Lcb)) != NULL) {
+
+ ASSERT_LCB( Lcb );
+
+ if ((Scb == Lcb->Scb)
+
+ &&
+
+ (!FlagOn( Lcb->LcbState, LCB_STATE_LINK_IS_GONE ))
+
+ &&
+
+ (FileNameFlags == Lcb->FileNameAttr->Flags)
+
+ &&
+
+ (LastComponentFileName.Length == Lcb->ExactCaseLink.LinkName.Length)
+
+ &&
+
+ (RtlEqualMemory( LastComponentFileName.Buffer,
+ Lcb->ExactCaseLink.LinkName.Buffer,
+ LastComponentFileName.Length ) )) {
+
+ *ReturnedExistingLcb = TRUE;
+
+ DebugTrace( -1, Dbg, ("NtfsCreateLcb -> %08lx\n", Lcb) );
+
+ return Lcb;
+ }
+ }
+
+ *ReturnedExistingLcb = FALSE;
+
+ try {
+
+ UCHAR MaxNameLength;
+
+ //
+ // Allocate a new lcb, zero it out and set the node type information
+ // Check if we can allocate the Lcb out of a compound Fcb. Check here if
+ // we can use the embedded name as well.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_DATA) &&
+ (SafeNodeType( &((PFCB_DATA) Fcb)->Lcb ) == 0)) {
+
+ Lcb = (PLCB) &((PFCB_DATA) Fcb)->Lcb;
+ MaxNameLength = MAX_DATA_FILE_NAME;
+
+ } else if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
+ (SafeNodeType( &((PFCB_INDEX) Fcb)->Lcb ) == 0)) {
+
+ Lcb = (PLCB) &((PFCB_INDEX) Fcb)->Lcb;
+ MaxNameLength = MAX_INDEX_FILE_NAME;
+
+ } else {
+
+ Lcb = UnwindStorage[0] = ExAllocateFromPagedLookasideList( &NtfsLcbLookasideList );
+ MaxNameLength = 0;
+ }
+
+ RtlZeroMemory( Lcb, sizeof(LCB) );
+
+ Lcb->NodeTypeCode = NTFS_NTC_LCB;
+ Lcb->NodeByteSize = sizeof(LCB);
+
+ //
+ // Check if we will have to allocate a separate filename attr.
+ //
+
+ if (MaxNameLength < (USHORT) (LastComponentFileName.Length / sizeof( WCHAR ))) {
+
+ //
+ // Allocate the last component part of the lcb and copy over the data.
+ // Check if there is space in the Fcb for this.
+ //
+
+ Lcb->FileNameAttr =
+ UnwindStorage[1] = NtfsAllocatePool(PagedPool, LastComponentFileName.Length +
+ NtfsFileNameSizeFromLength( LastComponentFileName.Length ));
+
+ MaxNameLength = LastComponentFileName.Length / sizeof( WCHAR );
+
+ } else {
+
+ Lcb->FileNameAttr = (PFILE_NAME) &Lcb->ParentDirectory;
+ }
+
+ Lcb->FileNameAttr->ParentDirectory = Scb->Fcb->FileReference;
+ Lcb->FileNameAttr->FileNameLength = (USHORT) LastComponentFileName.Length / sizeof( WCHAR );
+ Lcb->FileNameAttr->Flags = FileNameFlags;
+
+ Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
+
+ Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( MaxNameLength * sizeof( WCHAR )));
+
+ Lcb->ExactCaseLink.LinkName.Length =
+ Lcb->IgnoreCaseLink.LinkName.Length = LastComponentFileName.Length;
+
+ Lcb->ExactCaseLink.LinkName.MaximumLength =
+ Lcb->IgnoreCaseLink.LinkName.MaximumLength = MaxNameLength * sizeof( WCHAR );
+
+ RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
+ LastComponentFileName.Buffer,
+ LastComponentFileName.Length );
+
+ RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
+ LastComponentFileName.Buffer,
+ LastComponentFileName.Length );
+
+ NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
+ IrpContext->Vcb->UpcaseTableSize,
+ &Lcb->IgnoreCaseLink.LinkName );
+
+ //
+ // Now put this Lcb into the queues for the scb and the fcb
+ //
+
+ InsertTailList( &Scb->ScbType.Index.LcbQueue, &Lcb->ScbLinks );
+ Lcb->Scb = Scb;
+
+ InsertTailList( &Fcb->LcbQueue, &Lcb->FcbLinks );
+ Lcb->Fcb = Fcb;
+
+ //
+ // Now initialize the ccb queue.
+ //
+
+ InitializeListHead( &Lcb->CcbQueue );
+
+ } finally {
+
+ DebugUnwind( NtfsCreateLcb );
+
+ if (AbnormalTermination()) {
+
+ if (UnwindStorage[0]) { NtfsFreePool( UnwindStorage[0] );
+ } else if (Lcb != NULL) { Lcb->NodeTypeCode = 0; }
+ if (UnwindStorage[1]) { NtfsFreePool( UnwindStorage[1] ); }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCreateLcb -> %08lx\n", Lcb) );
+
+ return Lcb;
+}
+
+
+VOID
+NtfsDeleteLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN OUT PLCB *Lcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocated and removes the lcb record from Ntfs's in-memory
+ data structures. It assumes that the ccb queue is empty. We also assume
+ that this is not the root lcb that we are trying to delete.
+
+Arguments:
+
+ Lcb - Supplise the Lcb to be removed
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PCCB Ccb;
+ PLIST_ENTRY Links;
+
+ PAGED_CODE();
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteLcb, *Lcb = %08lx\n", *Lcb) );
+
+ //
+ // Get rid of any prefixes that might still be attached to us
+ //
+
+ NtfsRemovePrefix( (*Lcb) );
+
+ //
+ // Walk through the Ccb's for this link and clear the Lcb
+ // pointer. This can only be for Ccb's which there is no
+ // more user handle.
+ //
+
+ Links = (*Lcb)->CcbQueue.Flink;
+
+ while (Links != &(*Lcb)->CcbQueue) {
+
+ Ccb = CONTAINING_RECORD( Links,
+ CCB,
+ LcbLinks );
+
+ Links = Links->Flink;
+ NtfsUnlinkCcbFromLcb( IrpContext, Ccb );
+ }
+
+ //
+ //
+ // Now remove ourselves from our scb and fcb
+ //
+
+ RemoveEntryList( &(*Lcb)->ScbLinks );
+ RemoveEntryList( &(*Lcb)->FcbLinks );
+
+ //
+ // Free up the last component part and then free ourselves
+ //
+
+ if ((*Lcb)->FileNameAttr != (PFILE_NAME) &(*Lcb)->ParentDirectory) {
+
+ NtfsFreePool( (*Lcb)->FileNameAttr );
+ DebugDoit( (*Lcb)->FileNameAttr = NULL );
+ }
+
+ //
+ // Check if we are part of an embedded structure otherwise free back to the
+ // lookaside list
+ //
+
+ if (((*Lcb) == (PLCB) &((PFCB_DATA) (*Lcb)->Fcb)->Lcb) ||
+ ((*Lcb) == (PLCB) &((PFCB_INDEX) (*Lcb)->Fcb)->Lcb)) {
+
+ (*Lcb)->NodeTypeCode = 0;
+
+ } else {
+
+ ExFreeToPagedLookasideList( &NtfsLcbLookasideList, *Lcb );
+ }
+
+ //
+ // And for safety sake null out the pointer
+ //
+
+ *Lcb = NULL;
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteLcb -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsMoveLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB Lcb,
+ IN PSCB Scb,
+ IN PFCB Fcb,
+ IN PUNICODE_STRING TargetDirectoryName,
+ IN PUNICODE_STRING LastComponentName,
+ IN UCHAR FileNameFlags,
+ IN BOOLEAN CheckBufferSizeOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine completely moves the input lcb to join different fcbs and
+ scbs. It hasIt uses the target directory
+ file object to supply the complete new name to use.
+
+Arguments:
+
+ Lcb - Supplies the Lcb being moved.
+
+ Scb - Supplies the new parent scb
+
+ Fcb - Supplies the new child fcb
+
+ TargetDirectoryName - This is the path used to reach the new parent directory
+ for this Lcb. It will only be from the root.
+
+ LastComponentName - This is the last component name to store in this relocated Lcb.
+
+ FileNameFlags - Indicates if this is an NTFS, DOS or hard link
+
+ CheckBufferSizeOnly - If TRUE we just want to pass through and verify that
+ the buffer sizes of the various structures will be large enough for the
+ new name.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+ ULONG BytesNeeded;
+ PVOID NewAllocation;
+ PCHAR NextChar;
+
+ PSCB NextScb;
+ PSCB ChildScb;
+ PLCB TempLcb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_LCB( Lcb );
+ ASSERT_SCB( Scb );
+ ASSERT_FCB( Fcb );
+ ASSERT( NodeType( Scb ) != NTFS_NTC_SCB_DATA );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsMoveLcb, Lcb = %08lx\n", Lcb) );
+
+ //
+ // Use a try finally to cleanup any new allocation.
+ //
+
+ try {
+
+ //
+ // If we're not just checking sizes then remove entries from the prefix table
+ // and the normalized name for descendents of the current scb.
+ //
+
+ if (!CheckBufferSizeOnly) {
+
+ //
+ // Clear the index offset pointer so we will look this up again.
+ //
+
+ Lcb->QuickIndex.BufferOffset = 0;
+
+ //
+ // Get rid of any prefixes that might still be attached to us
+ //
+
+ NtfsRemovePrefix( Lcb );
+
+ //
+ // And then traverse the graph underneath our fcb and remove all prefixes
+ // also used there. For each child scb under the fcb we will traverse all of
+ // its descendant Scb children and for each lcb we encounter we will remove its prefixes.
+ //
+
+ ChildScb = NULL;
+ while ((ChildScb = NtfsGetNextChildScb( Lcb->Fcb, ChildScb )) != NULL) {
+
+ //
+ // Loop through his Lcb's and remove the prefix entries.
+ //
+
+ TempLcb = NULL;
+ while ((TempLcb = NtfsGetNextChildLcb(ChildScb, TempLcb)) != NULL) {
+
+ NtfsRemovePrefix( TempLcb );
+ }
+
+ //
+ // Now we have to descend into this Scb subtree, if it exists.
+ // Then remove the prefix entries on all of the links found.
+ //
+
+ NextScb = ChildScb;
+ while ((NextScb = NtfsGetNextScb( NextScb, ChildScb )) != NULL) {
+
+ //
+ // If this is an index Scb with a normalized name, then free
+ // the normalized name.
+ //
+
+ if ((SafeNodeType( NextScb ) == NTFS_NTC_SCB_INDEX) &&
+ (NextScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsFreePool( NextScb->ScbType.Index.NormalizedName.Buffer );
+
+ NextScb->ScbType.Index.NormalizedName.Buffer = NULL;
+ NextScb->ScbType.Index.NormalizedName.Length = 0;
+ }
+
+ TempLcb = NULL;
+ while ((TempLcb = NtfsGetNextChildLcb(NextScb, TempLcb)) != NULL) {
+
+ NtfsRemovePrefix( TempLcb );
+ }
+ }
+ }
+ }
+
+ //
+ // Remember the number of bytes needed for the last component.
+ //
+
+ BytesNeeded = LastComponentName->Length;
+
+ //
+ // Check if we need to allocate a new filename attribute. If so allocate
+ // it and store it into the new allocation buffer.
+ //
+
+ if (Lcb->ExactCaseLink.LinkName.MaximumLength < BytesNeeded) {
+
+ NewAllocation = NtfsAllocatePool( PagedPool,
+ BytesNeeded + NtfsFileNameSizeFromLength( BytesNeeded ));
+
+ //
+ // Set up the existing names into the new buffer. That way if we have an allocation
+ // failure below with the Ccb's the Lcb is still in a valid state.
+ //
+
+ RtlCopyMemory( NewAllocation,
+ Lcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( Lcb->ExactCaseLink.LinkName.MaximumLength ));
+
+ RtlCopyMemory( Add2Ptr( NewAllocation, NtfsFileNameSizeFromLength( BytesNeeded )),
+ Lcb->IgnoreCaseLink.LinkName.Buffer,
+ Lcb->IgnoreCaseLink.LinkName.MaximumLength );
+
+ if (Lcb->FileNameAttr != (PFILE_NAME) &Lcb->ParentDirectory) {
+
+ NtfsFreePool( Lcb->FileNameAttr );
+ }
+
+ Lcb->FileNameAttr = NewAllocation;
+
+ Lcb->ExactCaseLink.LinkName.MaximumLength =
+ Lcb->IgnoreCaseLink.LinkName.MaximumLength = (USHORT) BytesNeeded;
+
+ Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
+ Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( BytesNeeded ));
+ }
+
+ //
+ // Compute the full length of the name for the CCB, assume we will need a
+ // separator.
+ //
+
+ BytesNeeded += (TargetDirectoryName->Length + sizeof( WCHAR ));
+
+ //
+ // Now for every ccb attached to us we need to check if we need a new
+ // filename buffer.
+ //
+
+ Ccb = NULL;
+ while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
+
+ //
+ // Check if the name buffer currently in the Ccb is large enough.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLEANUP )) {
+
+ if (Ccb->FullFileName.MaximumLength < BytesNeeded) {
+
+ //
+ // Allocate a new file name buffer and copy the existing data back into it.
+ //
+
+ NewAllocation = NtfsAllocatePool( PagedPool, BytesNeeded );
+
+ RtlCopyMemory( NewAllocation,
+ Ccb->FullFileName.Buffer,
+ Ccb->FullFileName.Length );
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME )) {
+
+ NtfsFreePool( Ccb->FullFileName.Buffer );
+ }
+
+ Ccb->FullFileName.Buffer = NewAllocation;
+ Ccb->FullFileName.MaximumLength = (USHORT) BytesNeeded;
+
+ SetFlag( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME );
+ }
+ }
+ }
+
+ //
+ // Now update the Lcb with the new values if we are to rewrite the buffers.
+ //
+
+ if (!CheckBufferSizeOnly) {
+
+ Lcb->FileNameAttr->ParentDirectory = Scb->Fcb->FileReference;
+ Lcb->FileNameAttr->FileNameLength = (USHORT) LastComponentName->Length / sizeof( WCHAR );
+ Lcb->FileNameAttr->Flags = FileNameFlags;
+
+ Lcb->ExactCaseLink.LinkName.Length =
+ Lcb->IgnoreCaseLink.LinkName.Length = (USHORT) LastComponentName->Length;
+
+ RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
+ LastComponentName->Buffer,
+ LastComponentName->Length );
+
+ RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
+ LastComponentName->Buffer,
+ LastComponentName->Length );
+
+ NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
+ IrpContext->Vcb->UpcaseTableSize,
+ &Lcb->IgnoreCaseLink.LinkName );
+
+ //
+ // Now for every ccb attached to us we need to munge it file object name by
+ // copying over the entire new name
+ //
+
+ Ccb = NULL;
+ while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
+
+ //
+ // We ignore any Ccb's which are associated with open by File Id
+ // file objects or their file objects have gone through cleanup.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLEANUP )) {
+
+ Ccb->FullFileName.Length = (USHORT) BytesNeeded;
+ NextChar = (PCHAR) Ccb->FullFileName.Buffer;
+
+ RtlCopyMemory( NextChar,
+ TargetDirectoryName->Buffer,
+ TargetDirectoryName->Length );
+
+ NextChar += TargetDirectoryName->Length;
+
+ if (TargetDirectoryName->Length != sizeof( WCHAR )) {
+
+ *((PWCHAR) NextChar) = L'\\';
+ NextChar += sizeof( WCHAR );
+
+ } else {
+
+ Ccb->FullFileName.Length -= sizeof( WCHAR );
+ }
+
+ RtlCopyMemory( NextChar,
+ LastComponentName->Buffer,
+ LastComponentName->Length );
+
+ Ccb->LastFileNameOffset = (USHORT) (Ccb->FullFileName.Length - LastComponentName->Length);
+ }
+ }
+
+ //
+ // Now dequeue ourselves from our old scb and fcb and put us in the
+ // new fcb and scb queues.
+ //
+
+ RemoveEntryList( &Lcb->ScbLinks );
+ RemoveEntryList( &Lcb->FcbLinks );
+
+ InsertTailList( &Scb->ScbType.Index.LcbQueue, &Lcb->ScbLinks );
+ Lcb->Scb = Scb;
+
+ InsertTailList( &Fcb->LcbQueue, &Lcb->FcbLinks );
+ Lcb->Fcb = Fcb;
+ }
+
+ } finally {
+
+ DebugTrace( -1, Dbg, ("NtfsMoveLcb -> VOID\n") );
+ }
+
+ //
+ // And return to our caller
+ //
+
+
+ return;
+}
+
+
+VOID
+NtfsRenameLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB Lcb,
+ IN PUNICODE_STRING LastComponentFileName,
+ IN UCHAR FileNameFlags,
+ IN BOOLEAN CheckBufferSizeOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the last component name of the input lcb
+ It also walks through the opened ccb and munges their names and
+ also removes the lcb from the prefix table
+
+Arguments:
+
+ Lcb - Supplies the Lcb being renamed
+
+ LastComponentFileName - Supplies the new last component to use
+ for the lcb name
+
+ FileNameFlags - Indicates if this is an NTFS, DOS or hard link
+
+ CheckBufferSizeOnly - If TRUE we just want to pass through and verify that
+ the buffer sizes of the various structures will be large enough for the
+ new name.
+
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Lcb->Fcb->Vcb;
+ ULONG BytesNeeded;
+ PVOID NewAllocation;
+
+ PSCB ChildScb;
+ PLCB TempLcb;
+ PSCB NextScb;
+ PCCB Ccb;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_LCB( Lcb );
+
+ PAGED_CODE();
+
+ //
+ // If we're not just checking sizes then remove entries from the prefix table
+ // and the normalized name for descendents of the current scb.
+ //
+
+ if (!CheckBufferSizeOnly) {
+
+ //
+ // Clear the index offset pointer so we will look this up again.
+ //
+
+ Lcb->QuickIndex.BufferOffset = 0;
+
+ //
+ // Get rid of any prefixes that might still be attached to us
+ //
+
+ NtfsRemovePrefix( Lcb );
+
+ //
+ // And then traverse the graph underneath our fcb and remove all prefixes
+ // also used there. For each child scb under the fcb we will traverse all of
+ // its descendant Scb children and for each lcb we encounter we will remove its prefixes.
+ //
+
+ ChildScb = NULL;
+ while ((ChildScb = NtfsGetNextChildScb( Lcb->Fcb, ChildScb )) != NULL) {
+
+ //
+ // Loop through his Lcb's and remove the prefix entries.
+ //
+
+ TempLcb = NULL;
+ while ((TempLcb = NtfsGetNextChildLcb(ChildScb, TempLcb)) != NULL) {
+
+ NtfsRemovePrefix( TempLcb );
+ }
+
+ //
+ // Now we have to descend into this Scb subtree, if it exists.
+ // Then remove the prefix entries on all of the links found.
+ //
+
+ NextScb = ChildScb;
+ while ((NextScb = NtfsGetNextScb(NextScb, ChildScb)) != NULL) {
+
+ //
+ // If this is an index Scb with a normalized name, then free
+ // the normalized name.
+ //
+
+ if ((SafeNodeType( NextScb ) == NTFS_NTC_SCB_INDEX) &&
+ (NextScb->ScbType.Index.NormalizedName.Buffer != NULL)) {
+
+ NtfsFreePool( NextScb->ScbType.Index.NormalizedName.Buffer );
+ NextScb->ScbType.Index.NormalizedName.Buffer = NULL;
+ NextScb->ScbType.Index.NormalizedName.Length = 0;
+ }
+
+ TempLcb = NULL;
+ while ((TempLcb = NtfsGetNextChildLcb(NextScb, TempLcb)) != NULL) {
+
+ NtfsRemovePrefix( TempLcb );
+ }
+ }
+ }
+ }
+
+ //
+ // Remember the number of bytes needed for the last component.
+ //
+
+ BytesNeeded = LastComponentFileName->Length;
+
+ //
+ // Check if we need to allocate a new filename attribute. If so allocate
+ // it and store it into the new allocation buffer.
+ //
+
+ if (Lcb->ExactCaseLink.LinkName.MaximumLength < BytesNeeded) {
+
+ NewAllocation = NtfsAllocatePool( PagedPool,
+ BytesNeeded + NtfsFileNameSizeFromLength( BytesNeeded ));
+
+ //
+ // Set up the existing names into the new buffer. That way if we have an allocation
+ // failure below with the Ccb's the Lcb is still in a valid state.
+ //
+
+ RtlCopyMemory( NewAllocation,
+ Lcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( Lcb->ExactCaseLink.LinkName.MaximumLength ));
+
+ RtlCopyMemory( Add2Ptr( NewAllocation, NtfsFileNameSizeFromLength( BytesNeeded )),
+ Lcb->IgnoreCaseLink.LinkName.Buffer,
+ Lcb->IgnoreCaseLink.LinkName.MaximumLength );
+
+ if (Lcb->FileNameAttr != (PFILE_NAME) &Lcb->ParentDirectory) {
+
+ NtfsFreePool( Lcb->FileNameAttr );
+ }
+
+ Lcb->FileNameAttr = NewAllocation;
+
+ Lcb->ExactCaseLink.LinkName.MaximumLength =
+ Lcb->IgnoreCaseLink.LinkName.MaximumLength = (USHORT) BytesNeeded;
+
+ Lcb->ExactCaseLink.LinkName.Buffer = (PWCHAR) &Lcb->FileNameAttr->FileName;
+ Lcb->IgnoreCaseLink.LinkName.Buffer = Add2Ptr( Lcb->FileNameAttr,
+ NtfsFileNameSizeFromLength( BytesNeeded ));
+ }
+
+ //
+ // Now for every ccb attached to us we need to check if we need a new
+ // filename buffer.
+ //
+
+ Ccb = NULL;
+ while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
+
+ //
+ // If the Ccb last component length is zero, this Ccb is for a
+ // file object that was opened by File Id. We won't to any
+ // work for the name in the fileobject for this. Otherwise we
+ // compute the length of the new name and see if we have enough space
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLEANUP )) {
+
+ BytesNeeded = Ccb->LastFileNameOffset + LastComponentFileName->Length;
+
+ if ((ULONG)Ccb->FullFileName.MaximumLength < BytesNeeded) {
+
+ //
+ // Allocate a new file name buffer and copy the existing data back into it.
+ //
+
+ NewAllocation = NtfsAllocatePool( PagedPool, BytesNeeded );
+
+ RtlCopyMemory( NewAllocation,
+ Ccb->FullFileName.Buffer,
+ Ccb->FullFileName.Length );
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME )) {
+
+ NtfsFreePool( Ccb->FullFileName.Buffer );
+ }
+
+ Ccb->FullFileName.Buffer = NewAllocation;
+ Ccb->FullFileName.MaximumLength = (USHORT) BytesNeeded;
+
+ SetFlag( Ccb->Flags, CCB_FLAG_ALLOCATED_FILE_NAME );
+ }
+ }
+ }
+
+ //
+ // Now update the Lcb and Ccb's with the new values if we are to rewrite the buffers.
+ //
+
+ if (!CheckBufferSizeOnly) {
+
+ BytesNeeded = LastComponentFileName->Length;
+
+ Lcb->FileNameAttr->FileNameLength = (USHORT) BytesNeeded / sizeof( WCHAR );
+ Lcb->FileNameAttr->Flags = FileNameFlags;
+
+ Lcb->ExactCaseLink.LinkName.Length =
+ Lcb->IgnoreCaseLink.LinkName.Length = (USHORT) LastComponentFileName->Length;
+
+ RtlCopyMemory( Lcb->ExactCaseLink.LinkName.Buffer,
+ LastComponentFileName->Buffer,
+ BytesNeeded );
+
+ RtlCopyMemory( Lcb->IgnoreCaseLink.LinkName.Buffer,
+ LastComponentFileName->Buffer,
+ BytesNeeded );
+
+ NtfsUpcaseName( IrpContext->Vcb->UpcaseTable,
+ IrpContext->Vcb->UpcaseTableSize,
+ &Lcb->IgnoreCaseLink.LinkName );
+
+ //
+ // Now for every ccb attached to us we need to munge it file object name by
+ // copying over the entire new name
+ //
+
+ Ccb = NULL;
+ while ((Ccb = NtfsGetNextCcb(Lcb, Ccb)) != NULL) {
+
+ //
+ // We ignore any Ccb's which are associated with open by File Id
+ // file objects. We also ignore any Ccb's which don't have a file
+ // object pointer.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_OPEN_BY_FILE_ID | CCB_FLAG_CLEANUP )) {
+
+ RtlCopyMemory( &Ccb->FullFileName.Buffer[ Ccb->LastFileNameOffset / sizeof( WCHAR ) ],
+ LastComponentFileName->Buffer,
+ BytesNeeded );
+
+ Ccb->FullFileName.Length = Ccb->LastFileNameOffset + (USHORT) BytesNeeded;
+ }
+ }
+ }
+
+ return;
+}
+
+
+VOID
+NtfsCombineLcbs (
+ IN PIRP_CONTEXT IrpContext,
+ IN PLCB PrimaryLcb,
+ IN PLCB AuxLcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called for the case where we have multiple Lcb's for a
+ file which connect to the same Scb. We are performing a link rename
+ operation which causes the links to be combined and we need to
+ move all of the Ccb's to the same Lcb. This routine will be called only
+ after the names have been munged so that they are identical.
+ (i.e. call NtfsRenameLcb first)
+
+Arguments:
+
+ PrimaryLcb - Supplies the Lcb to receive all the Ccb's and Pcb's.
+
+ AuxLcb - Supplies the Lcb to strip.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PLIST_ENTRY Links;
+ PCCB NextCcb;
+
+ DebugTrace( +1, Dbg, ("NtfsCombineLcbs: Entered\n") );
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_LCB( PrimaryLcb );
+ ASSERT_LCB( AuxLcb );
+
+ PAGED_CODE();
+
+ //
+ // Move all of the Ccb's first.
+ //
+
+ for (Links = AuxLcb->CcbQueue.Flink;
+ Links != &AuxLcb->CcbQueue;
+ Links = AuxLcb->CcbQueue.Flink) {
+
+ NextCcb = CONTAINING_RECORD( Links, CCB, LcbLinks );
+ NtfsUnlinkCcbFromLcb( IrpContext, NextCcb );
+ NtfsLinkCcbToLcb( IrpContext, NextCcb, PrimaryLcb );
+ }
+
+ //
+ // Now do the prefix entries.
+ //
+
+ NtfsRemovePrefix( AuxLcb );
+
+ //
+ // Finally we need to transfer the unclean counts from the
+ // Lcb being merged to the primary Lcb.
+ //
+
+ PrimaryLcb->CleanupCount += AuxLcb->CleanupCount;
+
+ DebugTrace( -1, Dbg, ("NtfsCombineLcbs: Entered\n") );
+
+ return;
+}
+
+
+PLCB
+NtfsLookupLcbByFlags (
+ IN PFCB Fcb,
+ IN UCHAR FileNameFlags
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to find a split primary link by the file flag
+ only.
+
+Arguments:
+
+ Fcb - This is the Fcb for the file.
+
+ FileNameFlags - This is the file flag to search for. We will return
+ a link which matches this exactly.
+
+Return Value:
+
+ PLCB - The Lcb which has the desired flag, NULL otherwise.
+
+--*/
+
+{
+ PLCB Lcb;
+
+ PLIST_ENTRY Links;
+ PLCB ThisLcb;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsLookupLcbByFlags: Entered\n") );
+
+ Lcb = NULL;
+
+ //
+ // Walk through the Lcb's for the file, looking for an exact match.
+ //
+
+ for (Links = Fcb->LcbQueue.Flink; Links != &Fcb->LcbQueue; Links = Links->Flink) {
+
+ ThisLcb = CONTAINING_RECORD( Links, LCB, FcbLinks );
+
+ if (ThisLcb->FileNameAttr->Flags == FileNameFlags) {
+
+ Lcb = ThisLcb;
+ break;
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsLookupLcbByFlags: Exit\n") );
+
+ return Lcb;
+}
+
+
+
+ULONG
+NtfsLookupNameLengthViaLcb (
+ IN PFCB Fcb,
+ OUT PBOOLEAN LeadingBackslash
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to find the length of the file name by walking
+ backwards through the Lcb links.
+
+Arguments:
+
+ Fcb - This is the Fcb for the file.
+
+ LeadingBackslash - On return, indicates whether this chain begins with a
+ backslash.
+
+Return Value:
+
+ ULONG This is the length of the bytes found in the Lcb chain.
+
+--*/
+
+{
+ ULONG NameLength;
+
+ DebugTrace( +1, Dbg, ("NtfsLookupNameLengthViaLcb: Entered\n") );
+
+ //
+ // Initialize the return values.
+ //
+
+ NameLength = 0;
+ *LeadingBackslash = FALSE;
+
+ //
+ // If there is no Lcb we are done.
+ //
+
+ if (!IsListEmpty( &Fcb->LcbQueue )) {
+
+ PLCB ThisLcb;
+ BOOLEAN FirstComponent;
+
+ //
+ // Walk up the list of Lcb's and count the name elements.
+ //
+
+ FirstComponent = TRUE;
+
+ ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ //
+ // Loop until we have reached the root or there are no more Lcb's.
+ //
+
+ while (TRUE) {
+
+ if (ThisLcb == Fcb->Vcb->RootLcb) {
+
+ NameLength += sizeof( WCHAR );
+ *LeadingBackslash = TRUE;
+ break;
+ }
+
+ //
+ // If this is not the first component, we add room for a separating
+ // forward slash.
+ //
+
+ if (!FirstComponent) {
+
+ NameLength += sizeof( WCHAR );
+
+ } else {
+
+ FirstComponent = FALSE;
+ }
+
+ NameLength += ThisLcb->ExactCaseLink.LinkName.Length;
+
+ //
+ // If the next Fcb has no Lcb we exit.
+ //
+
+ Fcb = ((PSCB) ThisLcb->Scb)->Fcb;
+
+ if (IsListEmpty( &Fcb->LcbQueue)) {
+
+ break;
+ }
+
+ ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+ }
+
+ //
+ // If this is a system file we use the hard coded name.
+ //
+
+ } else if (NtfsSegmentNumber( &Fcb->FileReference ) <= UPCASE_TABLE_NUMBER) {
+
+ NameLength = NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length;
+ *LeadingBackslash = TRUE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsLookupNameLengthViaLcb: Exit - %08lx\n", NameLength) );
+ return NameLength;
+}
+
+
+VOID
+NtfsFileNameViaLcb (
+ IN PFCB Fcb,
+ IN PWCHAR FileName,
+ ULONG Length,
+ ULONG BytesToCopy
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to fill a buffer with the generated filename. The name
+ is constructed by walking backwards through the Lcb chain from the current Fcb.
+
+Arguments:
+
+ Fcb - This is the Fcb for the file.
+
+ FileName - This is the buffer to fill with the name.
+
+ Length - This is the length of the name. Already calculated by calling
+ NtfsLookupNameLengthViaLcb.
+
+ BytesToCopy - This indicates the number of bytes we are to copy. We drop
+ any characters out of the trailing Lcb's to only insert the beginning
+ of the path.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ULONG BytesToDrop;
+
+ PWCHAR ThisName;
+ DebugTrace( +1, Dbg, ("NtfsFileNameViaLcb: Entered\n") );
+
+ //
+ // If there is no Lcb or there are no bytes to copy we are done.
+ //
+
+ if (BytesToCopy) {
+
+ if (!IsListEmpty( &Fcb->LcbQueue )) {
+
+ PLCB ThisLcb;
+ BOOLEAN FirstComponent;
+
+ //
+ // Walk up the list of Lcb's and count the name elements.
+ //
+
+ FirstComponent = TRUE;
+
+ ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+
+ //
+ // Loop until we have reached the root or there are no more Lcb's.
+ //
+
+ while (TRUE) {
+
+ if (ThisLcb == Fcb->Vcb->RootLcb) {
+
+ *FileName = L'\\';
+ break;
+ }
+
+ //
+ // If this is not the first component, we add room for a separating
+ // forward slash.
+ //
+
+ if (!FirstComponent) {
+
+ Length -= sizeof( WCHAR );
+ ThisName = (PWCHAR) Add2Ptr( FileName,
+ Length );
+
+ if (Length < BytesToCopy) {
+
+ *ThisName = L'\\';
+ }
+
+ } else {
+
+ FirstComponent = FALSE;
+ }
+
+ //
+ // Length is current pointing just beyond where the next
+ // copy will end. If we are beyond the number of bytes to copy
+ // then we will truncate the copy.
+ //
+
+ if (Length > BytesToCopy) {
+
+ BytesToDrop = Length - BytesToCopy;
+
+ } else {
+
+ BytesToDrop = 0;
+ }
+
+ Length -= ThisLcb->ExactCaseLink.LinkName.Length;
+
+ ThisName = (PWCHAR) Add2Ptr( FileName,
+ Length );
+
+ //
+ // Only perform the copy if we are in the range of bytes to copy.
+ //
+
+ if (Length < BytesToCopy) {
+
+ RtlCopyMemory( ThisName,
+ ThisLcb->ExactCaseLink.LinkName.Buffer,
+ ThisLcb->ExactCaseLink.LinkName.Length - BytesToDrop );
+ }
+
+ //
+ // If the next Fcb has no Lcb we exit.
+ //
+
+ Fcb = ((PSCB) ThisLcb->Scb)->Fcb;
+
+ if (IsListEmpty( &Fcb->LcbQueue)) {
+
+ break;
+ }
+
+ ThisLcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+ }
+
+ //
+ // If this is a system file, we use the hard coded name.
+ //
+
+ } else if (NtfsSegmentNumber(&Fcb->FileReference) <= UPCASE_TABLE_NUMBER) {
+
+ if (BytesToCopy > NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length) {
+
+ BytesToCopy = NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Length;
+ }
+
+ RtlCopyMemory( FileName,
+ NtfsSystemFiles[NtfsSegmentNumber( &Fcb->FileReference )].Buffer,
+ BytesToCopy );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsFileNameViaLcb: Exit\n") );
+ return;
+}
+
+
+PCCB
+NtfsCreateCcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN BOOLEAN Indexed,
+ IN USHORT EaModificationCount,
+ IN ULONG Flags,
+ IN UNICODE_STRING FileName,
+ IN ULONG LastFileNameOffset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates a new CCB record
+
+Arguments:
+
+ Fcb - This is the Fcb for the file. We will check if we can allocate
+ the Ccb from an embedded structure.
+
+ Indexed - Indicates if we need an index Ccb.
+
+ EaModificationCount - This is the current modification count in the
+ Fcb for this file.
+
+ Flags - Informational flags for this Ccb.
+
+ FileName - Full path used to open this file.
+
+ LastFileNameOffset - Supplies the offset (in bytes) of the last component
+ for the name that the user is opening. If this is the root
+ directory it should denote "\" and all other ones should not
+ start with a backslash.
+
+Return Value:
+
+ CCB - returns a pointer to the newly allocate CCB
+
+--*/
+
+{
+ PCCB Ccb;
+
+ PAGED_CODE();
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ DebugTrace( +1, Dbg, ("NtfsCreateCcb\n") );
+
+ //
+ // Allocate a new CCB Record. If the Fcb is nonpaged then we must allocate
+ // a non-paged ccb. Then test if we can allocate this out of the Fcb.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_NONPAGED )) {
+
+ if (Indexed) {
+
+ Ccb = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(CCB), 'CftN' );
+
+ } else {
+
+ Ccb = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(CCB_DATA), 'cftN' );
+ }
+
+ } else if (FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_INDEX ) &&
+ (SafeNodeType( &((PFCB_INDEX) Fcb)->Ccb ) == 0)) {
+
+ Ccb = (PCCB) &((PFCB_INDEX) Fcb)->Ccb;
+
+ } else if (!Indexed &&
+ FlagOn( Fcb->FcbState, FCB_STATE_COMPOUND_DATA ) &&
+ (SafeNodeType( &((PFCB_DATA) Fcb)->Ccb ) == 0)) {
+
+ Ccb = (PCCB) &((PFCB_DATA) Fcb)->Ccb;
+
+ } else {
+
+ if (Indexed) {
+
+ Ccb = (PCCB)ExAllocateFromPagedLookasideList( &NtfsCcbLookasideList );
+
+ } else {
+
+ Ccb = (PCCB)ExAllocateFromPagedLookasideList( &NtfsCcbDataLookasideList );
+ }
+ }
+
+ //
+ // Zero and initialize the correct structure.
+ //
+
+ if (Indexed) {
+
+ RtlZeroMemory( Ccb, sizeof(CCB) );
+
+ //
+ // Set the proper node type code and node byte size
+ //
+
+ Ccb->NodeTypeCode = NTFS_NTC_CCB_INDEX;
+ Ccb->NodeByteSize = sizeof(CCB);
+
+ } else {
+
+ RtlZeroMemory( Ccb, sizeof(CCB_DATA) );
+
+ //
+ // Set the proper node type code and node byte size
+ //
+
+ Ccb->NodeTypeCode = NTFS_NTC_CCB_DATA;
+ Ccb->NodeByteSize = sizeof(CCB_DATA);
+ }
+
+ //
+ // Copy the Ea modification count.
+ //
+
+ Ccb->EaModificationCount = EaModificationCount;
+
+ //
+ // Copy the flags field
+ //
+
+ Ccb->Flags = Flags;
+
+ //
+ // Set the file object and last file name offset fields
+ //
+
+ Ccb->FullFileName = FileName;
+ Ccb->LastFileNameOffset = (USHORT)LastFileNameOffset;
+
+ //
+ // Initialize the Lcb queue.
+ //
+
+ InitializeListHead( &Ccb->LcbLinks );
+
+#ifdef _CAIRO_
+
+ if (FlagOn( Fcb->Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED )) {
+
+ //
+ // CAIROBUG: Consider doing this if VCB_QUOTA_TRACKING_REQUESTED
+ // is on too.
+ //
+
+ //
+ // Get the owner id of the calling thread. This must be done at
+ // create time since that is the only time the owner is valid.
+ //
+
+ Ccb->OwnerId = NtfsGetCallersUserId( IrpContext );
+ }
+
+#endif _CAIRO_
+
+ DebugTrace( -1, Dbg, ("NtfsCreateCcb -> %08lx\n", Ccb) );
+
+ return Ccb;
+}
+
+
+VOID
+NtfsDeleteCcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN OUT PCCB *Ccb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocates the specified CCB record.
+
+Arguments:
+
+ Fcb - This is the Fcb for the file. We will check if we can allocate
+ the Ccb from an embedded structure.
+
+ Ccb - Supplies the CCB to remove
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_CCB( *Ccb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsDeleteCcb, Ccb = %08lx\n", Ccb) );
+
+ //
+ // Deallocate any structures the Ccb is pointing to. The following
+ // are only in index Ccb.
+ //
+
+ if (SafeNodeType( *Ccb ) == NTFS_NTC_CCB_INDEX) {
+
+ if ((*Ccb)->QueryBuffer != NULL) { NtfsFreePool( (*Ccb)->QueryBuffer ); }
+ if ((*Ccb)->IndexEntry != NULL) { NtfsFreePool( (*Ccb)->IndexEntry ); }
+
+ if ((*Ccb)->IndexContext != NULL) {
+
+ PINDEX_CONTEXT IndexContext;
+
+ if ((*Ccb)->IndexContext->Base != (*Ccb)->IndexContext->LookupStack) {
+ NtfsFreePool( (*Ccb)->IndexContext->Base );
+ }
+
+ //
+ // Copy the IndexContext pointer into the stack so we don't dereference the
+ // paged Ccb while holding a spinlock.
+ //
+
+ IndexContext = (*Ccb)->IndexContext;
+ ExFreeToPagedLookasideList( &NtfsIndexContextLookasideList, IndexContext );
+ }
+ }
+
+ if (FlagOn( (*Ccb)->Flags, CCB_FLAG_ALLOCATED_FILE_NAME )) {
+
+ NtfsFreePool( (*Ccb)->FullFileName.Buffer );
+ }
+
+ //
+ // Deallocate the Ccb simply clear the flag in the Ccb header.
+ //
+
+ if ((*Ccb == (PCCB) &((PFCB_DATA) Fcb)->Ccb) ||
+ (*Ccb == (PCCB) &((PFCB_INDEX) Fcb)->Ccb)) {
+
+ (*Ccb)->NodeTypeCode = 0;
+
+ } else {
+
+ if (SafeNodeType( *Ccb ) == NTFS_NTC_CCB_INDEX) {
+
+ ExFreeToPagedLookasideList( &NtfsCcbLookasideList, *Ccb );
+
+ } else {
+
+ ExFreeToPagedLookasideList( &NtfsCcbDataLookasideList, *Ccb );
+ }
+ }
+
+ //
+ // Zero out the input pointer
+ //
+
+ *Ccb = NULL;
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsDeleteCcb -> VOID\n") );
+
+ return;
+}
+
+
+PIRP_CONTEXT
+NtfsCreateIrpContext (
+ IN PIRP Irp OPTIONAL,
+ IN BOOLEAN Wait
+ )
+
+/*++
+
+Routine Description:
+
+ This routine creates a new IRP_CONTEXT record
+
+Arguments:
+
+ Irp - Supplies the originating Irp. Won't be present if this is a defrag
+ operation.
+
+ Wait - Supplies the wait value to store in the context
+
+Return Value:
+
+ PIRP_CONTEXT - returns a pointer to the newly allocate IRP_CONTEXT Record
+
+--*/
+
+{
+ PIRP_CONTEXT IrpContext = NULL;
+ PIO_STACK_LOCATION IrpSp;
+ PVCB Vcb = NULL;
+ BOOLEAN AllocateFromPool = FALSE;
+
+ ASSERT_OPTIONAL_IRP( Irp );
+
+ // DebugTrace( +1, Dbg, ("NtfsCreateIrpContext\n") );
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // If we were called with our file system device object instead of a
+ // volume device object and this is not a mount, the request is illegal.
+ //
+
+ if ((IrpSp->DeviceObject->Size == (USHORT)sizeof(DEVICE_OBJECT)) &&
+ (IrpSp->FileObject != NULL)) {
+
+ ExRaiseStatus( STATUS_INVALID_DEVICE_REQUEST );
+ }
+
+ }
+
+ //
+ // Allocate an IrpContext from zone if available, otherwise from
+ // non-paged pool.
+ //
+
+ DebugDoit( NtfsFsdEntryCount += 1);
+
+ IrpContext = (PIRP_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIrpContextLookasideList );
+
+ RtlZeroMemory( IrpContext, sizeof(IRP_CONTEXT) );
+
+ //
+ // Set the proper node type code and node byte size
+ //
+
+ IrpContext->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext->NodeByteSize = sizeof(IRP_CONTEXT);
+
+ //
+ // Set the originating Irp field
+ //
+
+ IrpContext->OriginatingIrp = Irp;
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ //
+ // Copy RealDevice for workque algorithms, and also set WriteThrough
+ // if there is a file object.
+ //
+
+ if (IrpSp->FileObject != NULL) {
+
+ PVOLUME_DEVICE_OBJECT VolumeDeviceObject;
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+
+ IrpContext->RealDevice = FileObject->DeviceObject;
+
+ //
+ // Locate the volume device object and Vcb that we are trying to access
+ // so we can see if the request is WriteThrough. We ignore the
+ // write-through flag for close and cleanup.
+ //
+
+ VolumeDeviceObject = (PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject;
+ Vcb = &VolumeDeviceObject->Vcb;
+ if (IsFileWriteThrough( FileObject, Vcb )) {
+
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH);
+ }
+
+ //
+ // We would still like to find out the Vcb in all cases except for
+ // mount.
+ //
+
+ } else if (IrpSp->DeviceObject != NULL) {
+
+ Vcb = &((PVOLUME_DEVICE_OBJECT)IrpSp->DeviceObject)->Vcb;
+ }
+
+ //
+ // Major/Minor Function codes
+ //
+
+ IrpContext->MajorFunction = IrpSp->MajorFunction;
+ IrpContext->MinorFunction = IrpSp->MinorFunction;
+ }
+
+ //
+ // Set the Vcb we found (or NULL).
+ //
+
+ IrpContext->Vcb = Vcb;
+
+ //
+ // Set the wait parameter
+ //
+
+ if (Wait) { SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT); }
+
+ //
+ // Initialize the recently deallocated record queue and exclusive Scb queue
+ //
+
+ InitializeListHead( &IrpContext->RecentlyDeallocatedQueue );
+ InitializeListHead( &IrpContext->ExclusiveFcbList );
+
+ //
+ // Set up LogFull testing
+ //
+
+ DebugDoit( IrpContext->CurrentFailCount = IrpContext->NextFailCount = NtfsFailCheck );
+
+ //
+ // return and tell the caller
+ //
+
+ // DebugTrace( -1, Dbg, ("NtfsCreateIrpContext -> %08lx\n", IrpContext) );
+
+ return IrpContext;
+}
+
+
+VOID
+NtfsDeleteIrpContext (
+ IN OUT PIRP_CONTEXT *IrpContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine deallocates and removes the specified IRP_CONTEXT record
+ from the Ntfs in memory data structures. It should only be called
+ by NtfsCompleteRequest.
+
+Arguments:
+
+ IrpContext - Supplies the IRP_CONTEXT to remove
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PFCB Fcb;
+
+ ASSERT_IRP_CONTEXT( *IrpContext );
+
+ // DebugTrace( +1, Dbg, ("NtfsDeleteIrpContext, *IrpContext = %08lx\n", *IrpContext) );
+
+ if (!IsListEmpty( &(*IrpContext)->RecentlyDeallocatedQueue )) {
+
+ NtfsDeallocateRecordsComplete( *IrpContext );
+ }
+
+ //
+ // Just in case we somehow get here with a transaction ID, clear
+ // it here so we do not loop forever.
+ //
+
+ ASSERT((*IrpContext)->TransactionId == 0);
+
+ (*IrpContext)->TransactionId = 0;
+
+ //
+ // Free any exclusive paging I/O resource, or IoAtEof condition,
+ // this field is overlayed, minimally in write.c.
+ //
+
+ Fcb = (*IrpContext)->FcbWithPagingExclusive;
+ if (Fcb != NULL) {
+
+ if (Fcb->NodeTypeCode == NTFS_NTC_FCB) {
+
+ NtfsReleasePagingIo((*IrpContext), Fcb );
+
+ } else {
+
+ FsRtlUnlockFsRtlHeader( (PFSRTL_ADVANCED_FCB_HEADER) Fcb );
+ (*IrpContext)->FcbWithPagingExclusive = NULL;
+ }
+ }
+
+ //
+ // Finally, now that we have written the forget record, we can free
+ // any exclusive Scbs that we have been holding.
+ //
+
+ while (!IsListEmpty(&(*IrpContext)->ExclusiveFcbList)) {
+
+ Fcb = (PFCB)CONTAINING_RECORD((*IrpContext)->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks );
+
+ NtfsReleaseFcb( *IrpContext, Fcb );
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's for transactions.
+ //
+
+ if ((*IrpContext)->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( *IrpContext );
+ }
+
+ //
+ // Make sure there are no Scb snapshots left.
+ //
+
+ NtfsFreeSnapshotsForFcb( *IrpContext, NULL );
+
+ //
+ // If we can delete this Irp Context do so now.
+ //
+
+ if (!FlagOn( (*IrpContext)->Flags, IRP_CONTEXT_FLAG_DONT_DELETE )) {
+
+ //
+ // If there is an Io context pointer in the irp context and it is not
+ // on the stack, then free it.
+ //
+
+ if (FlagOn( (*IrpContext)->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT )
+ && ((*IrpContext)->Union.NtfsIoContext != NULL)) {
+
+ ExFreeToNPagedLookasideList( &NtfsIoContextLookasideList, (*IrpContext)->Union.NtfsIoContext );
+ }
+
+ //
+ // If we have captured the subject context then free it now.
+ //
+
+ if (FlagOn( (*IrpContext)->Flags, IRP_CONTEXT_FLAG_ALLOC_SECURITY )
+ && (*IrpContext)->Union.SubjectContext != NULL) {
+
+ SeReleaseSubjectContext( (*IrpContext)->Union.SubjectContext );
+
+ NtfsFreePool( (*IrpContext)->Union.SubjectContext );
+ }
+
+ //
+ // Return the IRP context record to the lookaside or to pool depending
+ // how much is currently in the lookaside
+ //
+
+ ExFreeToNPagedLookasideList( &NtfsIrpContextLookasideList, *IrpContext );
+
+ //
+ // Zero out the input pointer
+ //
+
+ *IrpContext = NULL;
+
+ } else {
+
+ //
+ // Set up the Irp Context for retry.
+ //
+
+ ClearFlag( (*IrpContext)->Flags, IRP_CONTEXT_FLAGS_CLEAR_ON_RETRY );
+ (*IrpContext)->DeallocatedClusters = 0;
+ (*IrpContext)->FreeClusterChange = 0;
+ }
+
+ //
+ // And return to our caller
+ //
+
+ // DebugTrace( -1, Dbg, ("NtfsDeleteIrpContext -> VOID\n") );
+
+ return;
+}
+
+
+VOID
+NtfsTeardownStructures (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVOID FcbOrScb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN CheckForAttributeTable,
+ IN BOOLEAN DontWaitForAcquire,
+ OUT PBOOLEAN RemovedFcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to start the teardown process on a node in
+ the Fcb/Scb tree. We will attempt to remove this node and then
+ move up the tree removing any nodes held by this node.
+
+ This routine deals with the case where a single node may be holding
+ multiple parents in memory. If we are passed an input Lcb we will
+ use that to walk up the tree. If the Vcb is held exclusively we
+ will try to trim any nodes that have no open files on them.
+
+ This routine takes the following steps:
+
+ Remove as many Scb's and file objects from the starting
+ Fcb.
+
+ If the Fcb can't go away but has multiple links then remove
+ whatever links possible. If we have the Vcb we can
+ do all of them but we will leave a single link behind
+ to optimize prefix lookups. Otherwise we will traverse the
+ single link we were given.
+
+ If the Fcb can go away then we should have the Vcb if there are
+ multiple links to remove. Otherwise we only remove the link
+ we were given if there are multiple links. In the single link
+ case just remove that link.
+
+Arguments:
+
+ FcbOrScb - Supplies either an Fcb or an Scb as the start of the
+ teardown point. The Fcb for this element must be held exclusively.
+
+ Lcb - If specified, this is the path up the tree to perform the
+ teardown.
+
+ CheckForAttributeTable - Indicates that we should not teardown an
+ Scb which is in the attribute table. Instead we will attempt
+ to put an entry on the async close queue. This will be TRUE
+ if we may need the Scb to abort the current transaction.
+
+ DontWaitForAcquire - Indicates whether we should abort the teardown when
+ we can't acquire a parent. When called from some path where we may
+ hold the MftScb or another resource in another path up the tree.
+
+ RemovedFcb - Address to store TRUE if we delete the starting Fcb.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSCB StartingScb = NULL;
+ PFCB Fcb;
+ BOOLEAN FcbCanBeRemoved;
+ BOOLEAN RemovedLcb;
+ BOOLEAN LocalRemovedFcb = FALSE;
+ PLIST_ENTRY Links;
+ PLIST_ENTRY NextLink;
+
+ PAGED_CODE();
+
+ //
+ // If this is a recursive call to TearDownStructures we return immediately
+ // doing no operation.
+ //
+
+ if (FlagOn( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_IN_TEARDOWN )) {
+
+ DebugTrace( 0, Dbg, ("Recursive teardown call\n") );
+ DebugTrace( -1, Dbg, ("NtfsTeardownStructures -> VOID\n") );
+
+ return;
+ }
+
+ if (SafeNodeType(FcbOrScb) == NTFS_NTC_FCB) {
+
+ Fcb = FcbOrScb;
+
+ } else {
+
+ StartingScb = FcbOrScb;
+ FcbOrScb = Fcb = StartingScb->Fcb;
+ }
+
+ SetFlag( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_IN_TEARDOWN );
+
+ //
+ // Use a try-finally to clear the top level irp field.
+ //
+
+ try {
+
+ //
+ // Use our local boolean if the caller didn't supply one.
+ //
+
+ if (!ARGUMENT_PRESENT( RemovedFcb )) {
+
+ RemovedFcb = &LocalRemovedFcb;
+ }
+
+ //
+ // Check this Fcb for removal. Remember if all of the Scb's
+ // and file objects are gone. We will try to remove the Fcb
+ // if the cleanup count is zero or if we are walking up
+ // one directory path of a mult-link file. If the Fcb has
+ // a non-zero cleanup count but the current Scb has a zero
+ // cleanup count then try to delete the Scb at the very least.
+ //
+
+ FcbCanBeRemoved = FALSE;
+
+ if (Fcb->CleanupCount == 0) {
+
+ FcbCanBeRemoved = NtfsPrepareFcbForRemoval( IrpContext,
+ Fcb,
+ StartingScb,
+ CheckForAttributeTable );
+
+ } else if (ARGUMENT_PRESENT( StartingScb ) &&
+ (StartingScb->CleanupCount == 0) &&
+ (StartingScb->AttributeTypeCode != $ATTRIBUTE_LIST)) {
+
+ NtfsRemoveScb( IrpContext, StartingScb, CheckForAttributeTable, &RemovedLcb );
+ }
+
+ //
+ // There is a single link (typical case) we either try to
+ // remove that link or we simply return.
+ //
+
+ if (Fcb->LcbQueue.Flink == Fcb->LcbQueue.Blink) {
+
+ if (FcbCanBeRemoved) {
+
+ NtfsTeardownFromLcb( IrpContext,
+ Fcb->Vcb,
+ Fcb,
+ CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks ),
+ CheckForAttributeTable,
+ DontWaitForAcquire,
+ &RemovedLcb,
+ &LocalRemovedFcb );
+ }
+
+ try_return( NOTHING );
+
+ //
+ // If there are multiple links we will try to either remove
+ // them all or all but one (if the Fcb is not going away) if
+ // we own the Vcb. We will try to delete the one we were
+ // given otherwise.
+ //
+
+ } else {
+
+ //
+ // If we have the Vcb we will remove all if the Fcb can
+ // go away. Otherwise we will leave one.
+ //
+
+ if (NtfsIsExclusiveVcb( Fcb->Vcb )) {
+
+ Links = Fcb->LcbQueue.Flink;
+
+ while (TRUE) {
+
+ //
+ // Remember the next entry in case the current link
+ // goes away.
+ //
+
+ NextLink = Links->Flink;
+
+ RemovedLcb = FALSE;
+
+ NtfsTeardownFromLcb( IrpContext,
+ Fcb->Vcb,
+ Fcb,
+ CONTAINING_RECORD( Links, LCB, FcbLinks ),
+ CheckForAttributeTable,
+ FALSE,
+ &RemovedLcb,
+ &LocalRemovedFcb );
+
+ //
+ // If couldn't remove this link then munge the
+ // boolean indicating if the Fcb can be removed
+ // to make it appear we need to remove all of
+ // the Lcb's.
+ //
+
+ if (!RemovedLcb) {
+
+ FcbCanBeRemoved = TRUE;
+ }
+
+ //
+ // If the Fcb has been removed then we exit.
+ // If the next link is the beginning of the
+ // Lcb queue then we also exit.
+ // If the next link is the last entry and
+ // we want to leave a single entry then we
+ // exit.
+ //
+
+ if (LocalRemovedFcb ||
+ (NextLink == &Fcb->LcbQueue) ||
+ (!FcbCanBeRemoved &&
+ (NextLink->Flink == &Fcb->LcbQueue))) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // Move to the next link.
+ //
+
+ Links = NextLink;
+ }
+
+ //
+ // If we have an Lcb just move up that path.
+ //
+
+ } else if (ARGUMENT_PRESENT( Lcb )) {
+
+ NtfsTeardownFromLcb( IrpContext,
+ Fcb->Vcb,
+ Fcb,
+ Lcb,
+ CheckForAttributeTable,
+ DontWaitForAcquire,
+ &RemovedLcb,
+ &LocalRemovedFcb );
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsTeardownStructures );
+
+ //
+ // If we removed the Fcb then set the caller's value now.
+ //
+
+ if (LocalRemovedFcb) {
+
+ *RemovedFcb = TRUE;
+ }
+
+ ClearFlag( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_IN_TEARDOWN );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsIncrementCleanupCounts (
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN NonCachedHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine increments the cleanup counts for the associated data structures
+
+Arguments:
+
+ Scb - Supplies the Scb used in this operation
+
+ Lcb - Optionally supplies the Lcb used in this operation
+
+ NonCachedHandle - Indicates this handle is for a user non-cached handle.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+
+ //
+ // This is really a pretty light weight procedure but having it be a procedure
+ // really helps in debugging the system and keeping track of who increments
+ // and decrements cleanup counts
+ //
+
+ if (ARGUMENT_PRESENT(Lcb)) { Lcb->CleanupCount += 1; }
+
+ InterlockedIncrement( &Scb->CleanupCount );
+ Scb->Fcb->CleanupCount += 1;
+
+ if (NonCachedHandle) {
+
+ Scb->NonCachedCleanupCount += 1;
+ }
+
+ InterlockedIncrement( &Vcb->CleanupCount );
+
+ return;
+}
+
+
+VOID
+NtfsIncrementCloseCounts (
+ IN PSCB Scb,
+ IN BOOLEAN SystemFile,
+ IN BOOLEAN ReadOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine increments the close counts for the associated data structures
+
+Arguments:
+
+ Scb - Supplies the Scb used in this operation
+
+ SystemFile - Indicates if the Scb is for a system file (if so then
+ the Vcb system file close count in also incremented)
+
+ ReadOnly - Indicates if the Scb is opened readonly. (if so then the
+ Vcb Read Only close count is also incremented)
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+
+ //
+ // This is really a pretty light weight procedure but having it be a procedure
+ // really helps in debugging the system and keeping track of who increments
+ // and decrements close counts
+ //
+
+ InterlockedIncrement( &Scb->CloseCount );
+ InterlockedIncrement( &Scb->Fcb->CloseCount );
+
+ InterlockedIncrement( &Vcb->CloseCount );
+
+ if (SystemFile) {
+
+ InterlockedIncrement( &Vcb->SystemFileCloseCount );
+ }
+
+ if (ReadOnly) {
+
+ InterlockedIncrement( &Vcb->ReadOnlyCloseCount );
+ }
+
+ //
+ // We will always clear the delay close flag in this routine.
+ //
+
+ ClearFlag( Scb->ScbState, SCB_STATE_DELAY_CLOSE );
+ return;
+}
+
+
+VOID
+NtfsDecrementCleanupCounts (
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN NonCachedHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This procedure decrements the cleanup counts for the associated data structures
+ and if necessary it also start to cleanup associated internal attribute streams
+
+Arguments:
+
+ Scb - Supplies the Scb used in this operation
+
+ Lcb - Optionally supplies the Lcb used in this operation
+
+ NonCachedHandle - Indicates this handle is for a user non-cached handle.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PVCB Vcb = Scb->Vcb;
+
+ ASSERT_SCB( Scb );
+ ASSERT_FCB( Scb->Fcb );
+ ASSERT_VCB( Scb->Fcb->Vcb );
+ ASSERT_OPTIONAL_LCB( Lcb );
+
+ //
+ // First we decrement the appropriate cleanup counts
+ //
+
+ if (ARGUMENT_PRESENT(Lcb)) { Lcb->CleanupCount -= 1; }
+
+ InterlockedDecrement( &Scb->CleanupCount );
+ Scb->Fcb->CleanupCount -= 1;
+
+ if (NonCachedHandle) {
+
+ Scb->NonCachedCleanupCount -= 1;
+ }
+
+ InterlockedDecrement( &Vcb->CleanupCount );
+
+ //
+ // Now if the Fcb's cleanup count is zero that indicates that we are
+ // done with this Fcb from a user handle standpoint and we should
+ // now scan through all of the Scb's that are opened under this
+ // Fcb and shutdown any internal attributes streams we have open.
+ // For example, EAs and ACLs. We only need to do one. The domino effect
+ // will take of the rest.
+ //
+
+ if (Scb->Fcb->CleanupCount == 0) {
+
+ PSCB NextScb;
+
+ //
+ // Remember if we are dealing with a system file and return immediately.
+ //
+
+ if (NtfsSegmentNumber( &Scb->Fcb->FileReference ) < FIRST_USER_FILE_NUMBER &&
+ NtfsSegmentNumber( &Scb->Fcb->FileReference ) != ROOT_FILE_NAME_INDEX_NUMBER) {
+
+ return;
+ }
+
+ for (NextScb = CONTAINING_RECORD(Scb->Fcb->ScbQueue.Flink, SCB, FcbLinks);
+ &NextScb->FcbLinks != &Scb->Fcb->ScbQueue;
+ NextScb = CONTAINING_RECORD( NextScb->FcbLinks.Flink, SCB, FcbLinks )) {
+
+ //
+ // Skip the root index on the volume.
+ //
+
+ if (SafeNodeType( NextScb ) == NTFS_NTC_SCB_ROOT_INDEX) {
+
+ continue;
+ }
+
+ //
+ // If we have an index with children then break out.
+ //
+
+ if ((SafeNodeType( NextScb ) == NTFS_NTC_SCB_INDEX) &&
+ !IsListEmpty( &NextScb->ScbType.Index.LcbQueue )) {
+
+ break;
+ }
+
+ //
+ // If there is an internal stream then dereference it and get out.
+ //
+
+ if (NextScb->FileObject != NULL) {
+
+ NtfsDeleteInternalAttributeStream( NextScb,
+ (BOOLEAN) (Scb->Fcb->LinkCount == 0) );
+ break;
+ }
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+VOID
+NtfsDecrementCloseCounts (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PLCB Lcb OPTIONAL,
+ IN BOOLEAN SystemFile,
+ IN BOOLEAN ReadOnly,
+ IN BOOLEAN DecrementCountsOnly
+ )
+
+/*++
+
+Routine Description:
+
+ This routine decrements the close counts for the associated data structures
+ and if necessary it will teardown structures that are no longer in use
+
+Arguments:
+
+ Scb - Supplies the Scb used in this operation
+
+ Lcb - Used if calling teardown to know which path to take.
+
+ SystemFile - Indicates if the Scb is for a system file
+
+ ReadOnly - Indicates if the Scb was opened readonly
+
+ DecrementCountsOnly - Indicates if this operation should only modify the
+ count fields.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFCB Fcb = Scb->Fcb;
+ PVCB Vcb = Scb->Vcb;
+
+ ASSERT_SCB( Scb );
+ ASSERT_FCB( Fcb );
+ ASSERT_VCB( Fcb->Vcb );
+
+ //
+ // Decrement the close counts
+ //
+
+ InterlockedDecrement( &Scb->CloseCount );
+ InterlockedDecrement( &Fcb->CloseCount );
+
+ InterlockedDecrement( &Vcb->CloseCount );
+
+ if (SystemFile) {
+
+ InterlockedDecrement( &Vcb->SystemFileCloseCount );
+ }
+
+ if (ReadOnly) {
+
+ InterlockedDecrement( &Vcb->ReadOnlyCloseCount );
+ }
+
+ //
+ // Now if the scb's close count is zero then we are ready to tear
+ // it down
+ //
+
+ if (!DecrementCountsOnly) {
+
+ //
+ // We want to try to start a teardown from this Scb if
+ //
+ // - The close count is zero
+ //
+ // or the following are all true
+ //
+ // - The cleanup count is zero
+ // - There is a file object in the Scb
+ // - It is a data Scb or an empty index Scb
+ // - It is not an Ntfs system file
+ //
+ // The teardown will be noopted if this is a recursive call.
+ //
+
+ if (Scb->CloseCount == 0
+
+ ||
+
+ (Scb->CleanupCount == 0
+ && Scb->FileObject != NULL
+ && (NtfsSegmentNumber( &Scb->Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER)
+ && ((SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA)
+ || (SafeNodeType( Scb ) == NTFS_NTC_SCB_MFT)
+ || IsListEmpty( &Scb->ScbType.Index.LcbQueue )))) {
+
+ NtfsTeardownStructures( IrpContext,
+ Scb,
+ Lcb,
+ FALSE,
+ FALSE,
+ NULL );
+ }
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+PERESOURCE
+NtfsAllocateEresource (
+ )
+{
+ KIRQL _SavedIrql;
+ PERESOURCE Eresource;
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &_SavedIrql );
+ if (NtfsData.FreeEresourceSize > 0) {
+ Eresource = NtfsData.FreeEresourceArray[--NtfsData.FreeEresourceSize];
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ } else {
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ Eresource = NtfsAllocatePoolWithTag( NonPagedPool, sizeof(ERESOURCE), 'rftN' );
+ ExInitializeResource( Eresource );
+ NtfsData.FreeEresourceMiss += 1;
+ }
+
+ return Eresource;
+}
+
+VOID
+NtfsFreeEresource (
+ IN PERESOURCE Eresource
+ )
+{
+ KIRQL _SavedIrql;
+
+ //
+ // Do an unsafe test to see if we should put this on our list.
+ // We want to reinitialize this before adding to the list so
+ // we don't have a bunch of resources which appear to be held.
+ //
+
+ if (NtfsData.FreeEresourceSize < NtfsData.FreeEresourceTotal) {
+
+ ExReinitializeResourceLite( Eresource );
+
+ //
+ // Now acquire the spinlock and do a real test.
+ //
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &_SavedIrql );
+ if (NtfsData.FreeEresourceSize < NtfsData.FreeEresourceTotal) {
+ NtfsData.FreeEresourceArray[NtfsData.FreeEresourceSize++] = Eresource;
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ } else {
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ ExDeleteResource( Eresource );
+ NtfsFreePool( Eresource );
+ }
+
+ } else {
+
+ ExDeleteResource( Eresource );
+ NtfsFreePool( Eresource );
+ }
+
+ return;
+}
+
+
+PVOID
+NtfsAllocateFcbTableEntry (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN CLONG ByteSize
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine to allocate memory
+
+Arguments:
+
+ FcbTable - Supplies the generic table being used
+
+ ByteSize - Supplies the number of bytes to allocate
+
+Return Value:
+
+ PVOID - Returns a pointer to the allocated data
+
+--*/
+
+{
+ KIRQL _SavedIrql;
+ PVOID FcbTableEntry;
+
+ UNREFERENCED_PARAMETER( FcbTable );
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &_SavedIrql );
+ if (NtfsData.FreeFcbTableSize > 0) {
+ FcbTableEntry = NtfsData.FreeFcbTableArray[--NtfsData.FreeFcbTableSize];
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ } else {
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ FcbTableEntry = NtfsAllocatePool( PagedPool, ByteSize );
+ }
+
+ return FcbTableEntry;
+}
+
+VOID
+NtfsFreeFcbTableEntry (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine that deallocates memory
+
+Arguments:
+
+ FcbTable - Supplies the generic table being used
+
+ Buffer - Supplies the buffer being deallocated
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ KIRQL _SavedIrql;
+
+ UNREFERENCED_PARAMETER( FcbTable );
+
+ KeAcquireSpinLock( &NtfsData.StrucSupSpinLock, &_SavedIrql );
+ if (NtfsData.FreeFcbTableSize < FREE_FCB_TABLE_SIZE) {
+ NtfsData.FreeFcbTableArray[NtfsData.FreeFcbTableSize++] = Buffer;
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ } else {
+ KeReleaseSpinLock( &NtfsData.StrucSupSpinLock, _SavedIrql );
+ NtfsFreePool( Buffer );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsCheckScbForCache (
+ IN OUT PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine checks if the Scb has blocks contining
+ Lsn's or Update sequence arrays and set the appropriate
+ bit in the Scb state word.
+
+ The Scb is Update sequence aware if it the Data Attribute for the
+ Mft or the Data Attribute for the log file or any index allocation
+ stream.
+
+ The Lsn aware Scb's are the ones above without the Log file.
+
+Arguments:
+
+ Scb - Supplies the current Scb
+
+Return Value:
+
+ The next Scb in the enumeration, or NULL if Scb was the final one.
+
+--*/
+
+{
+ //
+ // Temporarily either sequence 0 or 1 is ok.
+ //
+
+ FILE_REFERENCE MftTemp = {0,0,1};
+
+ PAGED_CODE();
+
+ //
+ // Check for Update Sequence Array files first.
+ //
+
+ if ((Scb->AttributeTypeCode == $INDEX_ALLOCATION)
+
+ ||
+
+ (Scb->AttributeTypeCode == $DATA
+ && Scb->AttributeName.Length == 0
+ && (NtfsEqualMftRef( &Scb->Fcb->FileReference, &MftFileReference )
+ || NtfsEqualMftRef( &Scb->Fcb->FileReference, &MftTemp )
+ || NtfsEqualMftRef( &Scb->Fcb->FileReference, &Mft2FileReference )
+ || NtfsEqualMftRef( &Scb->Fcb->FileReference, &LogFileReference )))) {
+
+ SetFlag( Scb->ScbState, SCB_STATE_USA_PRESENT );
+ }
+
+ return;
+}
+
+
+//
+// Local support routine.
+//
+
+BOOLEAN
+NtfsRemoveScb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN BOOLEAN CheckForAttributeTable,
+ OUT PBOOLEAN HeldByStream
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will try to remove an Scb from the Fcb/Scb tree.
+ It deals with the case where we can make no attempt to remove
+ the Scb, the case where we start the process but can't complete
+ it, and finally the case where we remove the Scb entirely.
+
+ The following conditions prevent us from removing the Scb at all.
+
+ The open count is greater than 1.
+ It is the root directory.
+ It is an index Scb with no stream file and an outstanding close.
+ It is a data file with a non-zero close count.
+
+ We start the teardown under the following conditions.
+
+ It is an index with an open count of 1, and a stream file object.
+
+ We totally remove the Scb when the open count is zero.
+
+Arguments:
+
+ Scb - Supplies the Scb to test
+
+ CheckForAttributeTable - Indicates that we don't want to remove this
+ Scb in this thread if it is in the open attribute table. We will
+ queue an async close in this case. This is to prevent us from
+ deleting an Scb which may be needed in the abort path.
+
+ HeldByStream - Indicates that this Scb was held by a stream file which
+ we started the close on.
+
+Return Value:
+
+ BOOLEAN - TRUE if the Scb was removed, FALSE otherwise. We return FALSE for
+ the case where we start the process but don't finish.
+
+--*/
+
+{
+ BOOLEAN ScbRemoved;
+
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsRemoveScb: Entered\n") );
+ DebugTrace( 0, Dbg, ("Scb -> %08lx\n", Scb) );
+
+ ScbRemoved = FALSE;
+ *HeldByStream = FALSE;
+
+ //
+ // If the Scb is not the root Scb and the count is less than two,
+ // then this Scb is a candidate for removal.
+ //
+
+ if (SafeNodeType( Scb ) != NTFS_NTC_SCB_ROOT_INDEX
+ && Scb->CleanupCount == 0) {
+
+ //
+ //
+ // If this is a data file or it is an index without children,
+ // we can get rid of the Scb if there are no children. If
+ // there is one open count and it is the file object, we
+ // can start the cleanup on the file object.
+ //
+
+ if ((SafeNodeType( Scb ) == NTFS_NTC_SCB_DATA) ||
+ (SafeNodeType( Scb ) == NTFS_NTC_SCB_MFT) ||
+ IsListEmpty( &Scb->ScbType.Index.LcbQueue )) {
+
+ //
+ // Check if we need to post a request to the async queue.
+ //
+
+ if (CheckForAttributeTable &&
+ (Scb->NonpagedScb->OpenAttributeTableIndex != 0)) {
+
+ NtfsAddScbToFspClose( IrpContext, Scb, FALSE );
+
+ } else {
+
+ if (Scb->CloseCount == 0) {
+
+ NtfsDeleteScb( IrpContext, &Scb );
+ ScbRemoved = TRUE;
+
+ //
+ // Else we know the open count is 1 or 2. If there is a stream
+ // file, we discard it (but not for the special system
+ // files) that get removed on dismount
+ //
+
+ } else if (((Scb->FileObject != NULL) || (Scb->Header.FileObjectC != NULL)) &&
+ (NtfsSegmentNumber( &Scb->Fcb->FileReference ) >= FIRST_USER_FILE_NUMBER)) {
+
+ NtfsDeleteInternalAttributeStream( Scb,
+ FALSE );
+ *HeldByStream = TRUE;
+ }
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsRemoveScb: Exit -> %04x\n", ScbRemoved) );
+
+ return ScbRemoved;
+}
+
+
+//
+// Local support routine
+//
+
+BOOLEAN
+NtfsPrepareFcbForRemoval (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB StartingScb OPTIONAL,
+ IN BOOLEAN CheckForAttributeTable
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will attempt to prepare the Fcb for removal from the Fcb/Scb
+ tree. It will try to remove all of the Scb's and test finally if
+ all of the close count has gone to zero. NOTE the close count is incremented
+ by routines to reference this Fcb to keep it from being torn down. An empty
+ Scb list isn't enough to insure that the Fcb can be removed.
+
+Arguments:
+
+ Fcb - This is the Fcb to remove.
+
+ StartingScb - This is the Scb to remove first.
+
+ CheckForAttributeTable - Indicates that we should not teardown an
+ Scb which is in the attribute table. Instead we will attempt
+ to put an entry on the async close queue. This will be TRUE
+ if we may need the Scb to abort the current transaction.
+
+Return Value:
+
+ BOOLEAN - TRUE if the Fcb can be removed, FALSE otherwise.
+
+--*/
+
+{
+ PSCB Scb;
+ BOOLEAN HeldByStream;
+
+ PAGED_CODE();
+
+ //
+ // Try to remove each Scb in the Fcb queue.
+ //
+
+ while (TRUE) {
+
+ if (IsListEmpty( &Fcb->ScbQueue )) {
+
+ if (Fcb->CloseCount == 0) {
+
+ return TRUE;
+
+ } else {
+
+ return FALSE;
+ }
+ }
+
+ if (ARGUMENT_PRESENT( StartingScb )) {
+
+ Scb = StartingScb;
+ StartingScb = NULL;
+
+ } else {
+
+ Scb = CONTAINING_RECORD( Fcb->ScbQueue.Flink,
+ SCB,
+ FcbLinks );
+ }
+
+ //
+ // Try to remove this Scb. If the call to remove didn't succeed
+ // but the close count has gone to zero, it means that a recursive
+ // close was generated which removed a stream file. In that
+ // case we can delete the Scb now.
+ //
+
+ if (!NtfsRemoveScb( IrpContext, Scb, CheckForAttributeTable, &HeldByStream )) {
+
+ if (HeldByStream &&
+ Scb->CloseCount == 0) {
+
+ NtfsDeleteScb( IrpContext, &Scb );
+
+ //
+ // Return FALSE to indicate the Fcb can't go away.
+ //
+
+ } else {
+
+ return FALSE;
+ }
+ }
+ }
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+NtfsTeardownFromLcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFCB StartingFcb,
+ IN PLCB StartingLcb,
+ IN BOOLEAN CheckForAttributeTable,
+ IN BOOLEAN DontWaitForAcquire,
+ OUT PBOOLEAN RemovedStartingLcb,
+ OUT PBOOLEAN RemovedStartingFcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to remove a link and continue moving up the
+ tree looking for more elements to remove. We will check that the
+ link is unreferenced. NOTE this Lcb must point up to a directory
+ so that other than our starting Lcb no Lcb we encounter will
+ have multiple parents.
+
+Arguments:
+
+ Vcb - Vcb for this volume.
+
+ StartingFcb - This is the Fcb whose link we are trying to remove.
+
+ StartingLcb - This is the Lcb to walk up through. Note that
+ this may be a bogus pointer. It is only valid if there
+ is at least one Fcb in the queue.
+
+ CheckForAttributeTable - Indicates that we should not teardown an
+ Scb which is in the attribute table. Instead we will attempt
+ to put an entry on the async close queue. This will be TRUE
+ if we may need the Scb to abort the current transaction.
+
+ DontWaitForAcquire - Indicates whether we should abort the teardown when
+ we can't acquire a parent. When called from some path where we may
+ hold the MftScb or another resource in another path up the tree.
+
+ RemovedStartingLcb - Address to store TRUE if we remove the
+ starting Lcb.
+
+ RemovedStartingFcb - Address to store TRUE if we remove the
+ starting Fcb.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PSCB ParentScb;
+ BOOLEAN AcquiredParentScb = FALSE;
+ BOOLEAN AcquiredFcb = FALSE;
+ BOOLEAN LastAccessInFcb;
+ BOOLEAN FcbUpdateDuplicate;
+ BOOLEAN AcquiredFcbTable = FALSE;
+
+ PLCB Lcb;
+ PFCB Fcb = StartingFcb;
+
+ PAGED_CODE();
+
+ //
+ // Use a try-finally to free any resources held.
+ //
+
+ try {
+
+ if (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED | FCB_STATE_SYSTEM_FILE ) &&
+ !FlagOn( StartingLcb->LcbState, LCB_STATE_LINK_IS_GONE ) &&
+ (FlagOn( Fcb->Vcb->VcbState,
+ VCB_STATE_VOL_PURGE_IN_PROGRESS | VCB_STATE_VOLUME_MOUNTED ) == VCB_STATE_VOLUME_MOUNTED) &&
+ (IrpContext->TopLevelIrpContext->ExceptionStatus == STATUS_SUCCESS)) {
+
+ FcbUpdateDuplicate = TRUE;
+
+ } else {
+
+ FcbUpdateDuplicate = FALSE;
+ }
+
+ //
+ // Check if the correct last access is stored in the Fcb.
+ //
+
+ if (Fcb->CurrentLastAccess != Fcb->Info.LastAccessTime) {
+
+ LastAccessInFcb = FALSE;
+
+ } else {
+
+ LastAccessInFcb = TRUE;
+ }
+
+ while (TRUE) {
+
+ ParentScb = NULL;
+
+ //
+ // Look through all of the Lcb's for this Fcb.
+ //
+
+ while (!IsListEmpty( &Fcb->LcbQueue )) {
+
+ if (Fcb == StartingFcb) {
+
+ Lcb = StartingLcb;
+
+ } else {
+
+ Lcb = CONTAINING_RECORD( Fcb->LcbQueue.Flink,
+ LCB,
+ FcbLinks );
+ }
+
+ if (Lcb->CleanupCount != 0) {
+
+ try_return( NOTHING );
+ }
+
+ ParentScb = Lcb->Scb;
+
+ //
+ // Try to acquire the parent but check whether we
+ // should wait.
+ //
+
+ if (!NtfsAcquireExclusiveFcb( IrpContext,
+ ParentScb->Fcb,
+ ParentScb,
+ TRUE,
+ DontWaitForAcquire )) {
+
+ try_return( NOTHING );
+ }
+
+ if (FlagOn( ParentScb->ScbState, SCB_STATE_FILE_SIZE_LOADED )) {
+
+ NtfsSnapshotScb( IrpContext, ParentScb );
+ }
+
+ AcquiredParentScb = TRUE;
+
+ if (Lcb->ReferenceCount != 0) {
+
+ try_return( NOTHING );
+ }
+
+ //
+ // This Lcb may be removed. Check first if we need
+ // to update the duplicate information for this
+ // entry.
+ //
+
+ if (FcbUpdateDuplicate &&
+ (FlagOn( (Fcb->InfoFlags | Lcb->InfoFlags), FCB_INFO_DUPLICATE_FLAGS ) ||
+ !LastAccessInFcb)) {
+
+ if (!LastAccessInFcb) {
+
+ Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
+ SetFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ SetFlag( Fcb->InfoFlags, FCB_INFO_CHANGED_LAST_ACCESS );
+ LastAccessInFcb = TRUE;
+ }
+
+ //
+ // Use a try-except, we ignore errors here.
+ //
+
+ try {
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO )) {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+ }
+
+ NtfsUpdateDuplicateInfo( IrpContext, Fcb, Lcb, ParentScb );
+ NtfsUpdateLcbDuplicateInfo( Fcb, Lcb );
+
+ } except( FsRtlIsNtstatusExpected( GetExceptionCode() ) ?
+ EXCEPTION_EXECUTE_HANDLER :
+ EXCEPTION_CONTINUE_SEARCH ) {
+
+ NOTHING;
+ }
+
+ Fcb->InfoFlags = 0;
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ //
+ // Now remove the Lcb. Remember if this is our original
+ // Lcb.
+ //
+
+ if (Lcb == StartingLcb) {
+
+ *RemovedStartingLcb = TRUE;
+ }
+
+ NtfsDeleteLcb( IrpContext, &Lcb );
+
+ //
+ // If this is the first Fcb then exit the loop.
+ //
+
+ if (Fcb == StartingFcb) {
+
+ break;
+ }
+ }
+
+ //
+ // If we get here it means we removed all of the Lcb's we
+ // could for the current Fcb. If the list is empty we
+ // can remove the Fcb itself.
+ //
+
+ if (IsListEmpty( &Fcb->LcbQueue )) {
+
+ //
+ // If this is a directory that was opened by Id it is
+ // possible that we still have an update to perform
+ // for the duplicate information and possibly for
+ // standard information.
+ //
+
+ if (FcbUpdateDuplicate &&
+ (!LastAccessInFcb || FlagOn( Fcb->InfoFlags, FCB_INFO_DUPLICATE_FLAGS ))) {
+
+ if (!LastAccessInFcb) {
+
+ Fcb->Info.LastAccessTime = Fcb->CurrentLastAccess;
+ }
+
+ //
+ // Use a try-except, we ignore errors here.
+ //
+
+ try {
+
+ NtfsUpdateStandardInformation( IrpContext, Fcb );
+
+ NtfsUpdateDuplicateInfo( IrpContext, Fcb, NULL, NULL );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ NOTHING;
+ }
+
+ Fcb->InfoFlags = 0;
+ ClearFlag( Fcb->FcbState, FCB_STATE_UPDATE_STD_INFO );
+ }
+
+ //
+ // Our worst nightmare has come true. We had to create an Scb
+ // and a stream in order to write out the duplicate information.
+ // This will happen if we have a non-resident attribute list.
+ //
+
+ if (!IsListEmpty( &Fcb->ScbQueue)) {
+
+ PSCB Scb;
+
+ Scb = CONTAINING_RECORD( Fcb->ScbQueue.Flink,
+ SCB,
+ FcbLinks );
+
+ NtfsDeleteInternalAttributeStream( Scb, TRUE );
+ }
+
+ //
+ // If the list is now empty then check the reference count.
+ //
+
+ if (IsListEmpty( &Fcb->ScbQueue)) {
+
+ //
+ // Now we are ready to remove the current Fcb. We need to
+ // do a final check of the reference count to make sure
+ // it isn't being referenced in an open somewhere.
+ //
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = TRUE;
+
+ if (Fcb->ReferenceCount == 0) {
+
+ if (Fcb == StartingFcb) {
+
+ *RemovedStartingFcb = TRUE;
+ }
+
+ NtfsDeleteFcb( IrpContext, &Fcb, &AcquiredFcbTable );
+ AcquiredFcb = FALSE;
+
+ } else {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ AcquiredFcbTable = FALSE;
+ }
+ }
+ }
+
+ //
+ // Move to the Fcb for the ParentScb.
+ //
+
+ if (ParentScb == NULL) {
+
+ try_return( NOTHING );
+ }
+
+ Fcb = ParentScb->Fcb;
+ AcquiredFcb = TRUE;
+ AcquiredParentScb = FALSE;
+
+ //
+ // Check if this Fcb can be removed.
+ //
+
+ if (!NtfsPrepareFcbForRemoval( IrpContext, Fcb, NULL, CheckForAttributeTable )) {
+
+ try_return( NOTHING );
+ }
+
+ if (!FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED ) &&
+ (FlagOn( Fcb->Vcb->VcbState,
+ VCB_STATE_VOL_PURGE_IN_PROGRESS | VCB_STATE_VOLUME_MOUNTED ) == VCB_STATE_VOLUME_MOUNTED) &&
+ (IrpContext->TopLevelIrpContext->ExceptionStatus == STATUS_SUCCESS)) {
+
+ FcbUpdateDuplicate = TRUE;
+
+ } else {
+
+ FcbUpdateDuplicate = FALSE;
+ }
+
+ //
+ // Check if the last access time for this Fcb is out of date.
+ //
+
+ if (Fcb->CurrentLastAccess != Fcb->Info.LastAccessTime) {
+
+ LastAccessInFcb = FALSE;
+
+ } else {
+
+ LastAccessInFcb = TRUE;
+ }
+ }
+
+ try_exit: NOTHING;
+ } finally {
+
+ DebugUnwind( NtfsTeardownFromLcb );
+
+ if (AcquiredFcbTable) {
+
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+ }
+
+ if (AcquiredFcb) {
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+
+ if (AcquiredParentScb) {
+
+ NtfsReleaseScb( IrpContext, ParentScb );
+ }
+ }
+
+ return;
+}
+
+
+//
+// Local support routine
+//
+
+RTL_GENERIC_COMPARE_RESULTS
+NtfsFcbTableCompare (
+ IN PRTL_GENERIC_TABLE FcbTable,
+ IN PVOID FirstStruct,
+ IN PVOID SecondStruct
+ )
+
+/*++
+
+Routine Description:
+
+ This is a generic table support routine to compare two fcb table elements
+
+Arguments:
+
+ FcbTable - Supplies the generic table being queried
+
+ FirstStruct - Supplies the first fcb table element to compare
+
+ SecondStruct - Supplies the second fcb table element to compare
+
+Return Value:
+
+ RTL_GENERIC_COMPARE_RESULTS - The results of comparing the two
+ input structures
+
+--*/
+
+{
+ LONGLONG UNALIGNED *Key1 = (PLONGLONG) &((PFCB_TABLE_ELEMENT)FirstStruct)->FileReference;
+ LONGLONG UNALIGNED *Key2 = (PLONGLONG) &((PFCB_TABLE_ELEMENT)SecondStruct)->FileReference;
+
+ UNREFERENCED_PARAMETER( FcbTable );
+
+ PAGED_CODE();
+
+ //
+ // Use also the sequence number for all compares so file references in the
+ // fcb table are unique over time and space. If we want to ignore sequence
+ // numbers we can zero out the sequence number field, but then we will also
+ // need to delete the Fcbs from the table during cleanup and not when the
+ // fcb really gets deleted. Otherwise we cannot reuse file records.
+ //
+
+ if (*Key1 < *Key2) {
+
+ return GenericLessThan;
+ }
+
+ if (*Key1 > *Key2) {
+
+ return GenericGreaterThan;
+ }
+
+ return GenericEqual;
+}
diff --git a/private/ntos/cntfs/tests/makefile b/private/ntos/cntfs/tests/makefile
new file mode 100644
index 000000000..fae3aa32c
--- /dev/null
+++ b/private/ntos/cntfs/tests/makefile
@@ -0,0 +1,9 @@
+############################################################################
+#
+# Copyright (C) 1992, Microsoft Corporation.
+#
+# All rights reserved.
+#
+############################################################################
+
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/ntos/cntfs/tests/proptest.cxx b/private/ntos/cntfs/tests/proptest.cxx
new file mode 100644
index 000000000..d07c9d99e
--- /dev/null
+++ b/private/ntos/cntfs/tests/proptest.cxx
@@ -0,0 +1,1891 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ proptest.c
+
+Abstract:
+
+ This module contains tests for Ntfs Property support.
+
+--*/
+
+
+extern "C" {
+#include <nt.h>
+#include <ntioapi.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+}
+
+#include <windows.h>
+
+#include <stdio.h>
+
+#include <ddeml.h> // for CP_WINUNICODE
+
+#include <objidl.h>
+
+extern "C"
+{
+#include <propapi.h>
+}
+
+#include <stgprop.h>
+
+#include <stgvar.hxx>
+#include <propstm.hxx>
+#include <align.hxx>
+#include <sstream.hxx>
+
+#include <propvar.h>
+
+#include <ntfsprop.h>
+
+//
+// Task allocators
+//
+
+CCoTaskAllocator g_CoTaskAllocator;
+
+void *
+CCoTaskAllocator::Allocate(ULONG cbSize)
+{
+ return(CoTaskMemAlloc(cbSize));
+}
+
+void
+CCoTaskAllocator::Free(void *pv)
+{
+ CoTaskMemFree(pv);
+}
+
+#define NEW(a,t,c) ((t *) (a)->Allocate( sizeof( t ) * (c)))
+
+//
+// Stuff pirated from ntfsproc.h
+//
+
+#define LongAlign(P) ( \
+ ((((ULONG)(P)) + 3) & 0xfffffffc) \
+)
+
+#define WordAlign(P) ( \
+ ((((ULONG)(P)) + 1) & 0xfffffffe) \
+)
+
+#define Add2Ptr(P,I) ((PVOID)((PUCHAR)(P) + (I)))
+
+#define PtrOffset(B,O) ((ULONG)((ULONG)(O) - (ULONG)(B)))
+
+#define SetFlag(F,SF) { \
+ (F) |= (SF); \
+}
+
+#define FlagOn(F,SF) ( \
+ (((F) & (SF))) \
+)
+
+
+//
+// Simple wrapper for NtCreateFile
+//
+
+NTSTATUS
+OpenObject (
+ WCHAR const *pwszFile,
+ ULONG CreateOptions,
+ ULONG DesiredAccess,
+ ULONG ShareAccess,
+ ULONG CreateDisposition,
+ HANDLE *ph)
+{
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES oa;
+ UNICODE_STRING str;
+ IO_STATUS_BLOCK isb;
+
+ RtlDosPathNameToNtPathName_U(pwszFile, &str, NULL, NULL);
+
+ InitializeObjectAttributes(
+ &oa,
+ &str,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL);
+
+ Status = NtCreateFile(
+ ph,
+ DesiredAccess | SYNCHRONIZE,
+ &oa,
+ &isb,
+ NULL, // pallocationsize (none!)
+ FILE_ATTRIBUTE_NORMAL,
+ ShareAccess,
+ CreateDisposition,
+ CreateOptions,
+ NULL, // EA buffer (none!)
+ 0);
+
+ RtlFreeHeap(RtlProcessHeap(), 0, str.Buffer);
+ return(Status);
+}
+
+
+void
+SzToWsz (
+ OUT WCHAR *Unicode,
+ IN char *Ansi
+ )
+{
+ while (*Unicode++ = *Ansi++)
+ ;
+}
+
+void
+DumpBufferAsULong (
+ IN PULONG Buffer,
+ IN ULONG Count
+ )
+{
+ ULONG i;
+
+ for (i = 0; i < Count; i++) {
+ if ((i % 4) == 0) {
+ if (i != 0) {
+ printf( "\n" );
+ }
+ printf( "%02d: ", Buffer + i );
+ }
+ printf( " %08x", Buffer[i] );
+ }
+
+ if (Count != 0) {
+ printf( "\n" );
+ }
+}
+
+
+//
+// Marshall Ids into PropertyIds
+//
+
+ULONG
+MarshallIds (
+ IN ULONG Count,
+ IN PROPID *Ids,
+ IN PVOID InBuffer,
+ IN ULONG InBufferLength
+ )
+{
+ PPROPERTY_IDS PropertyIds;
+ ULONG i;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ //
+ // Verify that there's enough room in the buffer for the Id array.
+ //
+
+ if (InBuffer != NULL && PROPERTY_IDS_SIZE( Count ) <= InBufferLength) {
+ //
+ // Build the propid array
+ //
+
+ PropertyIds = (PPROPERTY_IDS) InBuffer;
+ PropertyIds->Count = Count;
+ for (i = 0; i < Count; i++) {
+ PropertyIds->PropertyIds[i] = Ids[i];
+ }
+ }
+
+ return PROPERTY_IDS_SIZE( Count );
+}
+
+
+//
+// UnMarshall PropertyIds into Ids
+//
+
+ULONG
+UnmarshallPropertyIds (
+ IN PVOID InBuffer,
+ PMemoryAllocator *Allocator,
+ OUT PULONG Count,
+ OUT PROPID **PropId
+ )
+{
+ PPROPERTY_IDS PropertyIds = (PPROPERTY_IDS) InBuffer;
+ ULONG i;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ *Count = PropertyIds->Count;
+ *PropId = NEW( Allocator, PROPID, PropertyIds->Count );
+
+ for (i = 0; i < PropertyIds->Count; i++) {
+ (*PropId)[i] = PROPERTY_ID( PropertyIds, i );
+ }
+
+ return PROPERTY_IDS_SIZE( *Count );
+}
+
+//
+// Free Ids
+//
+
+VOID
+FreeIds (
+ IN PROPID *Ids,
+ IN PMemoryAllocator *Allocator
+ )
+{
+ Allocator->Free( Ids );
+}
+
+//
+// Dump IDs out
+//
+
+VOID
+DumpIds (
+ IN ULONG Count,
+ IN PROPID *Ids
+ )
+{
+ printf( "Ids (%x):\n", Count );
+ for (ULONG i = 0; i < Count; i++) {
+ printf( " [%02d] %08x\n", i, Ids[i] );
+ }
+}
+
+//
+// Marshall PropSpec into PropertySpecifications
+//
+
+ULONG
+MarshallPropSpec (
+ IN ULONG Count,
+ IN PROPSPEC *PropSpec,
+ IN PVOID InBuffer,
+ IN ULONG InBufferLength
+ )
+{
+ PPROPERTY_SPECIFICATIONS PropertySpecifications;
+ ULONG i;
+ ULONG InBufferUsed;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ //
+ // Build the propspec array. We lay out the table and point to where
+ // the names begin. We have to walk the array even if we run out of
+ // space since we have to account for the length of all the strings.
+ //
+
+ PropertySpecifications = (PPROPERTY_SPECIFICATIONS) InBuffer;
+
+ InBufferUsed = PROPERTY_SPECIFICATIONS_SIZE( Count );
+ if (InBufferUsed > InBufferLength) {
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertySpecifications->Count = Count;
+ }
+
+ for (i = 0; i < Count; i++) {
+
+ //
+ // Even if we have overflowed, we need to accrue InBufferUsed
+ //
+
+ if (PropSpec[i].ulKind == PRSPEC_LPWSTR) {
+ PCOUNTED_STRING Dest = (PCOUNTED_STRING) Add2Ptr( InBuffer, InBufferUsed );
+ USHORT Length = wcslen( PropSpec[i].lpwstr ) * sizeof( WCHAR );
+
+ InBufferUsed += COUNTED_STRING_SIZE( Length );
+ if (InBufferUsed > InBufferLength) {
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertySpecifications->Specifiers[i].Variant = PropSpec[i].ulKind;
+ PropertySpecifications->Specifiers[i].NameOffset =
+ PtrOffset( PropertySpecifications, Dest );
+
+ ASSERT( Dest == (PCOUNTED_STRING)WordAlign( Dest ) );
+ Dest->Length = Length;
+ RtlCopyMemory( &Dest->Text[0], PropSpec[i].lpwstr, Length );
+ }
+ } else {
+ if (InBuffer != NULL) {
+ PropertySpecifications->Specifiers[i].Variant = PropSpec[i].ulKind;
+ PropertySpecifications->Specifiers[i].Id = PropSpec[i].propid;
+ }
+ }
+
+ }
+
+ //
+ // Set up property spec length
+ //
+
+ if (InBuffer != NULL) {
+ PropertySpecifications->Length =
+ InBufferUsed - PtrOffset( InBuffer, PropertySpecifications );
+ }
+
+ return InBufferUsed;
+}
+
+
+
+//
+// Marshall name array into PropertyNames
+//
+
+ULONG
+MarshallNames (
+ IN ULONG Count,
+ IN LPWSTR *Names,
+ IN PVOID InBuffer,
+ IN ULONG InBufferLength
+ )
+{
+ PPROPERTY_NAMES PropertyNames;
+ ULONG i;
+ ULONG InBufferUsed;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ //
+ // Build the names array. We lay out the table and point to where
+ // the names begin. We have to walk the array even if we run out of
+ // space since we have to account for the length of all the strings.
+ //
+
+ PropertyNames = (PPROPERTY_NAMES) InBuffer;
+
+ InBufferUsed = PROPERTY_NAMES_SIZE( Count );
+ if (InBufferUsed > InBufferLength) {
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertyNames->Count = Count;
+ }
+
+ for (i = 0; i < Count; i++) {
+ PVOID Dest = Add2Ptr( InBuffer, InBufferUsed );
+ ULONG Length = wcslen( Names[i] ) * sizeof( WCHAR );
+
+ InBufferUsed += Length;
+ if (InBufferUsed > InBufferLength) {
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertyNames->PropertyNameOffset[i] = PtrOffset( PropertyNames, Dest);
+ ASSERT( Dest == (PVOID)WordAlign( Dest ));
+ RtlCopyMemory( Dest, Names[i], Length );
+ }
+ }
+
+ //
+ // Set up PROPERTY_VALUES length
+ //
+
+ if (InBuffer != NULL) {
+ PropertyNames->PropertyNameOffset[i] = InBufferUsed;
+ PropertyNames->Length = InBufferUsed;
+ }
+
+ return InBufferUsed;
+}
+
+
+//
+// Unmarshall PropertyNames into Names
+//
+
+ULONG
+UnmarshallPropertyNames (
+ IN PVOID InBuffer,
+ PMemoryAllocator *Allocator,
+ OUT PULONG Count,
+ OUT LPWSTR **Names
+ )
+{
+ PPROPERTY_NAMES PropertyNames = (PPROPERTY_NAMES) InBuffer;
+ ULONG i;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ *Count = PropertyNames->Count;
+ *Names = NEW( Allocator, LPWSTR, PropertyNames->Count );
+
+ for (i = 0; i < PropertyNames->Count; i++) {
+ ULONG Length = PROPERTY_NAME_LENGTH( PropertyNames, i );
+ (*Names)[i] = NEW( Allocator, WCHAR, Length / sizeof( WCHAR ) + 1 );
+ RtlCopyMemory( (*Names)[i], PROPERTY_NAME( PropertyNames, i ), Length);
+ (*Names)[i][Length / sizeof( WCHAR )] = L'\0';
+ }
+
+ return PropertyNames->Length;
+}
+
+//
+// Free Names
+//
+
+VOID
+FreeNames (
+ IN ULONG Count,
+ IN LPWSTR *Names,
+ IN PMemoryAllocator *Allocator
+ )
+{
+ for (ULONG i = 0; i < Count; i++) {
+ Allocator->Free( Names[i] );
+ }
+
+ Allocator->Free( Names );
+}
+
+//
+// DumpNames
+//
+
+VOID
+DumpNames (
+ IN ULONG Count,
+ IN LPWSTR *Names
+ )
+{
+ printf( "Names (%x):\n", Count );
+ for (ULONG i = 0; i < Count; i++) {
+ printf( " [%02d] '%ws'\n", i, Names[i] );
+ }
+
+}
+
+//
+// Marshall PropVariants into PropertyValues
+//
+
+ULONG
+MarshallPropVariants (
+ IN ULONG Count,
+ IN PROPVARIANT *Variants,
+ IN PVOID InBuffer,
+ IN ULONG InBufferLength
+ )
+{
+ PPROPERTY_VALUES PropertyValues;
+ ULONG InBufferUsed;
+ ULONG i;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ PropertyValues = (PPROPERTY_VALUES) InBuffer;
+
+ //
+ // Make sure there's enough room for a bare table
+ //
+
+ InBufferUsed = PROPERTY_VALUES_SIZE( Count );
+ if (InBufferUsed > InBufferLength) {
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertyValues->Count = Count;
+ }
+
+ //
+ // Build table
+ //
+
+ for (i = 0; i < Count; i++) {
+ SERIALIZEDPROPERTYVALUE *Dest = (SERIALIZEDPROPERTYVALUE *) Add2Ptr( InBuffer, InBufferUsed );
+ ULONG Room;
+
+ if (InBuffer != NULL) {
+ PropertyValues->PropertyValueOffset[i] = PtrOffset( PropertyValues, Dest );
+ }
+
+ Room = InBuffer == NULL ? 0 : InBufferLength - InBufferUsed;
+
+ ASSERT( Dest == (SERIALIZEDPROPERTYVALUE *)LongAlign( Dest ));
+
+ Dest = RtlConvertVariantToProperty(
+ &Variants[i],
+ CP_WINUNICODE, // UNICODE CODE PAGE 1200
+ Dest,
+ &Room,
+ PID_ILLEGAL,
+ FALSE,
+ NULL);
+
+ ASSERT( Room == LongAlign( Room ));
+ InBufferUsed += LongAlign( Room );
+
+ if (InBufferUsed > InBufferLength || (Dest == NULL && InBuffer != NULL)) {
+ InBuffer = NULL;
+ }
+
+ }
+
+ //
+ // Set up PROPERTY_VALUES length
+ //
+
+ if (InBuffer != NULL) {
+ PropertyValues->PropertyValueOffset[i] = InBufferUsed;
+ PropertyValues->Length = InBufferUsed;
+ }
+
+ return InBufferUsed;
+}
+
+//
+// UnmarshallPropertyValues
+//
+
+ULONG
+UnmarshallPropertyValues (
+ IN PVOID InBuffer,
+ IN PMemoryAllocator *Allocator,
+ OUT PULONG Count,
+ OUT PROPVARIANT **Variants
+ )
+{
+ PPROPERTY_VALUES Values = (PPROPERTY_VALUES) InBuffer;
+ ULONG i;
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ *Count = Values->Count;
+ *Variants = NEW( Allocator, PROPVARIANT, Values->Count );
+
+ for (i = 0; i < Values->Count; i++) {
+ RtlConvertPropertyToVariant(
+ PROPERTY_VALUE( Values, i),
+ CP_WINUNICODE,
+ &(*Variants)[i],
+ Allocator );
+ }
+
+ return Values->Length;
+}
+
+//
+// FreeVariants
+//
+
+VOID
+FreeVariants (
+ IN ULONG Count,
+ IN PROPVARIANT *Variants,
+ IN PMemoryAllocator *Allocator
+ )
+{
+ FreePropVariantArray( Count, Variants );
+ Allocator->Free( Variants );
+}
+
+
+//
+// DumpVariants
+//
+
+VOID
+DumpVariants (
+ IN ULONG Count,
+ IN PROPVARIANT *Variants
+ )
+{
+ printf( "Variants (%x):\n", Count );
+ for (ULONG i = 0; i < Count; i++) {
+ printf( " [%02d]: type %04x\n", i, Variants[i].vt );
+ }
+}
+
+//
+// ReadAction
+//
+
+NTSTATUS
+ReadAction (
+ IN HANDLE Handle,
+ IN READ_CONTROL_OPERATION Op,
+ IN ULONG Count,
+ IN PROPSPEC *Specs OPTIONAL,
+ IN PROPID *Ids OPTIONAL,
+ IN PMemoryAllocator *Allocator,
+ OUT PROPID **IdsOut OPTIONAL,
+ OUT LPWSTR **Names OPTIONAL,
+ OUT PROPVARIANT **Variants OPTIONAL,
+ OUT PULONG ReturnedCount OPTIONAL,
+ char *Caller
+ )
+{
+ NTSTATUS Status;
+ PCHAR InBuffer;
+ ULONG InBufferLength;
+ ULONG InBufferUsed;
+ PCHAR OutBuffer;
+ ULONG OutBufferLength;
+ PPROPERTY_READ_CONTROL PropertyReadControl;
+ IO_STATUS_BLOCK Iosb;
+
+ //
+ // Serialze this into:
+ //
+ // PROPERTY_READ_CONTROL <op>
+ // PROPERTY_SPEC
+ // PROPERTY_IDS
+ //
+ // Start out by assuming a 1K buffer. If this is insufficient, we will
+ // release the buffer, accrue the total size and try again.
+ //
+
+
+ InBufferLength = 1;
+
+ while (TRUE) {
+ //
+ // Allocate a marshalling buffer
+ //
+
+ InBuffer = new CHAR [InBufferLength];
+ InBufferUsed = 0;
+
+ //
+ // Stick in the PROPERTY_READ_CONTROL
+ //
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ PropertyReadControl = (PPROPERTY_READ_CONTROL) Add2Ptr( InBuffer, InBufferUsed );
+
+ InBufferUsed += sizeof( PROPERTY_READ_CONTROL );
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertyReadControl->Op = Op;
+ }
+
+ if (Specs != NULL) {
+
+ ASSERT( Op == PRC_READ_PROP );
+
+ InBufferUsed +=
+ LongAlign( MarshallPropSpec (
+ Count,
+ Specs,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed ));
+
+ ASSERT( InBufferUsed == LongAlign( InBufferUsed ));
+
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+ }
+
+ if (Ids != NULL) {
+
+ ASSERT( Op == PRC_READ_NAME );
+
+ InBufferUsed +=
+ MarshallIds (
+ Count,
+ Ids,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed );
+
+ ASSERT( InBufferUsed == LongAlign( InBufferUsed ));
+
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ }
+
+
+ //
+ // If we have finished with a buffer, get out of the loop and
+ // send the FsCtl
+ //
+
+ if (InBuffer != NULL) {
+ break;
+ }
+
+ //
+ // We no longer have a buffer because we've exhausted the size we guessed.
+ // Allocate one of the recalculated size and try again.
+ //
+
+ InBufferLength = InBufferUsed;
+ }
+
+ //
+ // Set up for the output buffer. We have a similar loop to try to determine the
+ // correct size of the buffer.
+ //
+
+ OutBufferLength = 4;
+
+ while (TRUE) {
+ OutBuffer = new CHAR [OutBufferLength];
+
+ //
+ // Send the FsCtl to establish the new properties
+ //
+
+ Status = NtFsControlFile(
+ Handle,
+ NULL,
+ NULL,
+ NULL,
+ &Iosb,
+ FSCTL_READ_PROPERTY_DATA,
+ InBuffer,
+ InBufferUsed,
+ OutBuffer,
+ OutBufferLength);
+
+ printf( "%s: %x\n", Caller, Status );
+
+
+ //
+ // If we had an unexpected error, cleanup and return
+ //
+
+ if (Status != STATUS_BUFFER_OVERFLOW)
+ break;
+
+ //
+ // We did not allocate a sufficient buffer for this operation.
+ // Get the correct size, free up the current buffer, and retry
+ //
+
+ OutBufferLength = *(PULONG) OutBuffer;
+ delete [] OutBuffer;
+ }
+
+ if (Status == STATUS_SUCCESS) {
+ PVOID NextOutput = OutBuffer;
+ ULONG TmpCount;
+ ULONG i;
+
+ if (Op == PRC_READ_ALL) {
+ NextOutput =
+ Add2Ptr( NextOutput,
+ UnmarshallPropertyIds( NextOutput, Allocator, ReturnedCount, IdsOut ));
+
+ DumpIds( *ReturnedCount, *IdsOut );
+ }
+
+ if (Op == PRC_READ_NAME || Op == PRC_READ_ALL) {
+ NextOutput =
+ Add2Ptr( NextOutput,
+ LongAlign( UnmarshallPropertyNames( NextOutput, Allocator, &TmpCount, Names )));
+
+ DumpNames( TmpCount, *Names );
+ }
+
+ if (Op == PRC_READ_PROP || Op == PRC_READ_ALL) {
+ NextOutput =
+ Add2Ptr( NextOutput,
+ UnmarshallPropertyValues( NextOutput, Allocator, &TmpCount, Variants ));
+
+ DumpVariants( TmpCount, *Variants );
+ }
+ }
+
+ delete [] OutBuffer;
+ delete [] InBuffer;
+
+ return Status;
+}
+
+
+//
+// Read some specific properties
+//
+
+NTSTATUS
+ReadProperties (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN PROPSPEC *Specifiers, // which properties to read
+ IN PMemoryAllocator *Allocator, // memory allocator
+ OUT PROPVARIANT **Variants // values
+ )
+{
+ return ReadAction( Handle,
+ PRC_READ_PROP,
+ Count,
+ Specifiers,
+ NULL,
+ Allocator,
+ NULL,
+ NULL,
+ Variants,
+ NULL,
+ "ReadProperties" );
+}
+
+
+//
+// Read some specific names
+//
+
+NTSTATUS
+ReadNames (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN PROPID *Ids, // which properties to read
+ IN PMemoryAllocator *Allocator, // memory allocator
+ OUT LPWSTR **Names // Names
+ )
+{
+ return ReadAction( Handle,
+ PRC_READ_NAME,
+ Count,
+ NULL,
+ Ids,
+ Allocator,
+ NULL,
+ Names,
+ NULL,
+ NULL,
+ "ReadNames" );
+}
+
+
+//
+// Read all
+//
+
+NTSTATUS
+ReadAll (
+ IN HANDLE Handle,
+ IN PMemoryAllocator *Allocator, // memory allocator
+ OUT PULONG Count,
+ OUT PROPID **Ids,
+ OUT LPWSTR **Names,
+ OUT PROPVARIANT **Variants
+ )
+{
+ return ReadAction( Handle,
+ PRC_READ_ALL,
+ 0,
+ NULL,
+ NULL,
+ Allocator,
+ Ids,
+ Names,
+ Variants,
+ Count,
+ "ReadAll" );
+}
+
+
+//
+// WriteAction
+//
+
+NTSTATUS
+WriteAction (
+ IN HANDLE Handle,
+ IN WRITE_CONTROL_OPERATION Op,
+ IN ULONG Count,
+ IN PROPID NextId,
+ IN PROPSPEC *Specs OPTIONAL,
+ IN PROPID *Ids OPTIONAL,
+ IN LPWSTR *Names OPTIONAL,
+ IN PROPVARIANT *Variants OPTIONAL,
+ IN PMemoryAllocator *Allocator OPTIONAL,
+ OUT PROPID **IdsOut OPTIONAL,
+ OUT char IndirectStuff,
+ char *Caller
+ )
+{
+ NTSTATUS Status;
+ PCHAR InBuffer;
+ ULONG InBufferLength;
+ ULONG InBufferUsed;
+ PCHAR OutBuffer;
+ ULONG OutBufferLength;
+ PPROPERTY_WRITE_CONTROL PropertyWriteControl;
+ IO_STATUS_BLOCK Iosb;
+
+ //
+ // Serialze this into:
+ //
+ // PROPERTY_WRITE_CONTROL <op>
+ // PROPERTY_SPEC
+ // PROPERTY_IDS
+ // PROPERTY_NAMES
+ // PROPERTY_VALUES
+ //
+ // Start out by assuming a 1K buffer. If this is insufficient, we will
+ // release the buffer, accrue the total size and try again.
+ //
+
+ InBufferLength = 1;
+
+ while (TRUE) {
+ //
+ // Allocate a marshalling buffer
+ //
+
+ InBuffer = new CHAR [InBufferLength];
+ InBufferUsed = 0;
+
+ //
+ // Stick in the PROPERTY_READ_CONTROL
+ //
+
+ ASSERT( InBuffer == (PVOID)LongAlign( InBuffer ));
+
+ PropertyWriteControl = (PPROPERTY_WRITE_CONTROL) Add2Ptr( InBuffer, InBufferUsed );
+
+ InBufferUsed += sizeof( PROPERTY_WRITE_CONTROL );
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ if (InBuffer != NULL) {
+ PropertyWriteControl->Op = Op;
+ PropertyWriteControl->NextPropertyId = NextId;
+ }
+
+ if (Specs != NULL) {
+
+ ASSERT( Op == PWC_WRITE_PROP ||
+ Op == PWC_DELETE_PROP );
+
+ InBufferUsed +=
+ LongAlign( MarshallPropSpec (
+ Count,
+ Specs,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed ));
+
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ }
+
+ if (Ids != NULL) {
+
+ ASSERT( Op == PWC_WRITE_NAME ||
+ Op == PWC_DELETE_NAME ||
+ Op == PWC_WRITE_ALL );
+
+ InBufferUsed +=
+ MarshallIds (
+ Count,
+ Ids,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed );
+
+ ASSERT( InBufferUsed == LongAlign( InBufferUsed ));
+
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ }
+
+ if (Names != NULL) {
+
+ ASSERT( Op == PWC_WRITE_NAME ||
+ Op == PWC_WRITE_ALL );
+
+ InBufferUsed +=
+ MarshallNames (
+ Count,
+ Names,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed );
+
+ ASSERT( InBufferUsed == WordAlign( InBufferUsed ));
+
+ if (Op == PWC_WRITE_ALL) {
+ InBufferUsed = LongAlign( InBufferUsed );
+ }
+
+ if (InBufferUsed > InBufferLength) {
+ delete [] InBuffer;
+ InBuffer = NULL;
+ }
+
+ }
+
+ if (Variants != NULL) {
+
+ ASSERT( Op == PWC_WRITE_PROP ||
+ Op == PWC_WRITE_ALL );
+
+ InBufferUsed +=
+ MarshallPropVariants (
+ Count,
+ Variants,
+ InBuffer == NULL ? NULL : Add2Ptr( InBuffer, InBufferUsed ),
+ InBufferLength - InBufferUsed );
+
+ ASSERT( InBufferUsed == LongAlign( InBufferUsed ));
+
+ if (InBufferUsed > InBufferLength) {
+ free( InBuffer );
+ InBuffer = NULL;
+ }
+ }
+
+
+ //
+ // If we have finished with a buffer, get out of the loop and
+ // send the FsCtl
+ //
+
+ if (InBuffer != NULL) {
+ break;
+ }
+
+ //
+ // We no longer have a buffer because we've exhausted the size we guessed.
+ // Allocate one of the recalculated size and try again.
+ //
+
+ InBufferLength = InBufferUsed;
+ }
+
+ //
+ // Set up for the output buffer if one is specified
+ //
+
+ if (Op == PWC_WRITE_PROP) {
+ OutBufferLength = 4;
+ } else {
+ OutBufferLength = 0;
+ }
+
+
+ //
+ // Loop while we provide a too-small buffer
+ //
+
+ while (TRUE) {
+
+ //
+ // Allocate an appropriately sized buffer
+ //
+
+ OutBuffer = new CHAR [OutBufferLength];
+
+ //
+ // Send the FsCtl to establish the new properties
+ //
+
+ Status = NtFsControlFile(
+ Handle,
+ NULL,
+ NULL,
+ NULL,
+ &Iosb,
+ FSCTL_WRITE_PROPERTY_DATA,
+ InBuffer,
+ InBufferUsed,
+ OutBuffer,
+ OutBufferLength);
+
+ //
+ // If we had success and are retrieving the values, dump into output buffer
+ //
+
+ printf( "%s: %x\n", Caller, Status );
+
+ //
+ // If we had an unexpected error, cleanup and return
+ //
+
+ if (Status != STATUS_BUFFER_OVERFLOW)
+ break;
+
+ //
+ // We did not allocate a sufficient buffer for this operation.
+ // Get the correct size, free up the current buffer, and retry
+ //
+
+ ASSERT( OutBuffer != NULL );
+
+ OutBufferLength = *(PULONG) OutBuffer;
+ delete [] OutBuffer;
+
+ }
+
+ if (Status == STATUS_SUCCESS) {
+ PVOID NextOutput = OutBuffer;
+ ULONG i;
+ ULONG TmpCount;
+ LPWSTR *TmpNames;
+ PROPVARIANT *TmpVariants;
+
+ if (Op == PWC_WRITE_PROP) {
+ NextOutput =
+ Add2Ptr( NextOutput,
+ UnmarshallPropertyIds( NextOutput, Allocator, &TmpCount, IdsOut ));
+
+ DumpIds( TmpCount, *IdsOut );
+ }
+
+ if (Op == PWC_WRITE_PROP) {
+ // Indirect stuff
+ }
+ }
+
+
+ delete [] OutBuffer;
+ delete [] InBuffer;
+
+ return Status;
+}
+
+//
+// Write some specific properties
+//
+
+NTSTATUS
+WriteProperties (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN ULONG NextId, // next ID to allocate
+ IN PROPSPEC *Specifiers, // which properties to read
+ IN PROPVARIANT *Variants, // values
+ IN PMemoryAllocator *Allocator,
+ OUT PROPID **Ids,
+ OUT char IndirectSomethings
+ )
+{
+ return WriteAction( Handle,
+ PWC_WRITE_PROP,
+ Count,
+ NextId,
+ Specifiers,
+ NULL,
+ NULL,
+ Variants,
+ Allocator,
+ Ids,
+ IndirectSomethings,
+ "WriteProperties" );
+}
+
+
+//
+// Delete some specific properties
+//
+
+NTSTATUS
+DeleteProperties (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN PROPSPEC *Specifiers
+ )
+{
+ return WriteAction( Handle,
+ PWC_DELETE_PROP,
+ Count,
+ PID_ILLEGAL,
+ Specifiers,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "DeleteProperties" );
+}
+
+
+//
+// Write some specific names
+//
+
+NTSTATUS
+WriteNames (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN PROPID *Ids,
+ IN LPWSTR *Names
+ )
+{
+ return WriteAction( Handle,
+ PWC_WRITE_NAME,
+ Count,
+ PID_ILLEGAL,
+ NULL,
+ Ids,
+ Names,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "WriteNames" );
+}
+
+//
+// Delete some specific names
+//
+
+NTSTATUS
+DeleteNames (
+ IN HANDLE Handle,
+ IN ULONG Count, // count of properties
+ IN PROPID *Ids
+ )
+{
+ return WriteAction( Handle,
+ PWC_DELETE_NAME,
+ Count,
+ PID_ILLEGAL,
+ NULL,
+ Ids,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ "DeleteNames" );
+}
+
+
+//
+// Write all properties
+//
+
+NTSTATUS
+WriteAll (
+ IN HANDLE Handle,
+ IN ULONG Count,
+ IN PROPID *Ids,
+ IN LPWSTR *Names,
+ IN PROPVARIANT *Variants
+ )
+{
+ return WriteAction( Handle,
+ PWC_WRITE_ALL,
+ Count,
+ PID_ILLEGAL,
+ NULL,
+ Ids,
+ Names,
+ Variants,
+ NULL,
+ NULL,
+ NULL,
+ "WriteAll" );
+}
+
+
+//
+// Status = NtFsControlFile(
+// IN HANDLE FileHandle,
+// IN HANDLE Event OPTIONAL,
+// IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
+// IN PVOID ApcContext OPTIONAL,
+// OUT PIO_STATUS_BLOCK IoStatusBlock,
+// IN ULONG IoControlCode,
+// IN PVOID InputBuffer OPTIONAL,
+// IN ULONG InputBufferLength,
+// OUT PVOID OutputBuffer OPTIONAL,
+// IN ULONG OutputBufferLength);
+//
+//
+
+int __cdecl
+main (
+ int argc,
+ char **argv)
+{
+ NTSTATUS Status;
+ HANDLE Handle;
+ WCHAR FileName[MAX_PATH];
+ char InputBuffer[10];
+ char OutputBuffer[200];
+ IO_STATUS_BLOCK Iosb;
+
+ if (argc == 2 && !strcmp (argv[1], "-reload")) {
+ //
+ // Enable load/unload privilege
+ //
+
+ HANDLE Handle;
+
+ if (!OpenThreadToken( GetCurrentThread( ),
+ TOKEN_ADJUST_PRIVILEGES,
+ TRUE,
+ &Handle )
+ ) {
+
+ if (!OpenProcessToken( GetCurrentProcess( ),
+ TOKEN_ADJUST_PRIVILEGES,
+ &Handle )
+ ) {
+
+ printf( "Unable to open current thread token %d\n", GetLastError( ));
+ exit( 1 );
+ }
+ }
+
+ TOKEN_PRIVILEGES NewState;
+
+ NewState.PrivilegeCount = 1;
+
+ if (!LookupPrivilegeValue( NULL,
+ SE_LOAD_DRIVER_NAME,
+ &NewState.Privileges[0].Luid )
+ ) {
+
+ printf( "Unable to look up SE_LOAD_DRIVER_NAME %d\n", GetLastError( ));
+ exit( 1 );
+ }
+
+ NewState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ if (!AdjustTokenPrivileges( Handle, // token handle
+ FALSE, // no disable all
+ &NewState, // new privilege to set
+ 0, // previous buffer is empty
+ NULL, // previous buffer is NULL
+ NULL ) // no size to return
+ ) {
+
+ printf( "Unable to AdjustTokenPrivilege %d\n", GetLastError ( ));
+ exit( 1 );
+ }
+
+ CloseHandle( Handle );
+
+ UNICODE_STRING DriverName;
+ RtlInitUnicodeString( &DriverName, L"\\registry\\machine\\system\\currentcontrolset\\services\\views");
+ Status = NtUnloadDriver( &DriverName );
+ printf( "NtUnloadDriver returned %x\n", Status );
+ Status = NtLoadDriver( &DriverName );
+ printf( "NtLoadDriver returned %x\n", Status );
+ exit (0);
+ }
+
+
+ SzToWsz( FileName, argv[1] );
+ wcscat( FileName, L":PropertySet:$PROPERTY_SET" );
+
+ Status = OpenObject( FileName,
+ FILE_SYNCHRONOUS_IO_NONALERT,
+ FILE_READ_DATA | FILE_WRITE_DATA,
+ FALSE,
+ FILE_OPEN_IF,
+ &Handle );
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to open %s - %x\n", argv[1], Status );
+ }
+
+ //
+ // Initialize the property set
+ //
+
+ Status = NtFsControlFile(
+ Handle,
+ NULL,
+ NULL,
+ NULL,
+ &Iosb,
+ FSCTL_INITIALIZE_PROPERTY_DATA,
+ NULL, 0,
+ NULL, 0);
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to send initialize %x\n", Status);
+ }
+
+ //
+ // Dump out property set
+ //
+
+ Status = NtFsControlFile(
+ Handle,
+ NULL,
+ NULL,
+ NULL,
+ &Iosb,
+ FSCTL_DUMP_PROPERTY_DATA,
+ NULL, 0,
+ NULL, 0);
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to dump %x\n", Status);
+ }
+
+
+ //
+ // ReadProperties on empty property set
+ //
+
+ {
+ PROPSPEC InSpec[4];
+ PROPVARIANT *Variants = NULL;
+
+ InSpec[0].ulKind = PRSPEC_PROPID;
+ InSpec[0].propid = 42;
+
+ InSpec[1].ulKind = PRSPEC_LPWSTR;
+ InSpec[1].lpwstr = L"Date Saved";
+
+ InSpec[2].ulKind = PRSPEC_LPWSTR;
+ InSpec[2].lpwstr = L"Author";
+
+ InSpec[3].ulKind = PRSPEC_LPWSTR;
+ InSpec[3].lpwstr = L"Paper Title";
+
+ Status = ReadProperties( Handle, 4, InSpec, &g_CoTaskAllocator, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to readproperties on empty property set %x\n", Status );
+ } else {
+
+ FreeVariants( 4, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+ //
+ // ReadNames on empty property set
+ //
+
+ {
+ PROPID Ids[4];
+ LPWSTR *Names;
+
+ Ids[0] = 2;
+ Ids[1] = 4;
+ Ids[2] = 3;
+ Ids[3] = 5;
+
+ Status = ReadNames( Handle, 4, Ids, &g_CoTaskAllocator, &Names );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to readnames on empty property set %x\n", Status );
+ } else {
+ FreeNames( 4, Names, &g_CoTaskAllocator );
+ }
+
+ }
+
+ //
+ // ReadAll on empty property set
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll on empty property set %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+
+ }
+
+ //
+ // Write properties by name and id
+ //
+
+ {
+ PROPVARIANT Var[4];
+ PROPSPEC InSpec[4];
+ PROPID *Ids;
+
+ //
+ // Set a few named properties
+ //
+
+ Var[0].vt = VT_I4;
+ Var[0].ulVal = 0x12345678;
+ InSpec[0].ulKind = PRSPEC_PROPID;
+ InSpec[0].propid = 42;
+
+ Var[1].vt = VT_LPWSTR;
+ Var[1].pwszVal = L"Thomas Jefferson";
+ InSpec[1].ulKind = PRSPEC_LPWSTR;
+ InSpec[1].lpwstr = L"Author"; // 1024
+
+ Var[2].vt = VT_LPWSTR;
+ Var[2].pwszVal = L"Federalist Papers";
+ InSpec[2].ulKind = PRSPEC_LPWSTR;
+ InSpec[2].lpwstr = L"Paper Title"; // 1025
+
+ Var[3].vt = VT_I4;
+ Var[3].ulVal = 1776;
+ InSpec[3].ulKind = PRSPEC_LPWSTR;
+ InSpec[3].lpwstr = L"Date Saved"; // 1026
+
+ Status = WriteProperties( Handle, 4, 1024, InSpec, Var, &g_CoTaskAllocator, &Ids, NULL );
+ if (!NT_SUCCESS( Status )) {
+ printf ("WriteProperties status %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ }
+
+ // 42
+ // 1024 Author
+ // 1025 Paper Title
+ // 1026 Date Saved
+
+ }
+
+ //
+ // ReadProperties
+ //
+
+ {
+ PROPVARIANT *Variants;
+ PROPSPEC InSpec[6];
+
+ InSpec[0].ulKind = PRSPEC_PROPID;
+ InSpec[0].propid = 42;
+
+ InSpec[1].ulKind = PRSPEC_LPWSTR;
+ InSpec[1].lpwstr = L"Date Saved";
+
+ InSpec[2].ulKind = PRSPEC_LPWSTR;
+ InSpec[2].lpwstr = L"Author";
+
+ InSpec[3].ulKind = PRSPEC_LPWSTR;
+ InSpec[3].lpwstr = L"Paper Title";
+
+ InSpec[4].ulKind = PRSPEC_LPWSTR;
+ InSpec[4].lpwstr = L"Not Found";
+
+ InSpec[5].ulKind = PRSPEC_PROPID;
+ InSpec[5].propid = 17;
+
+ Status = ReadProperties( Handle, 6, InSpec, &g_CoTaskAllocator, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadProperties %x\n", Status );
+ } else {
+
+ FreeVariants( 6, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+
+ }
+
+
+ //
+ // Read names
+ //
+
+ {
+ PROPID Ids[5];
+ LPWSTR *Names;
+
+ Ids[0] = 42;
+ Ids[1] = 1025;
+ Ids[2] = 1024;
+ Ids[3] = 1026;
+ Ids[4] = 17;
+
+ Status = ReadNames( Handle, 5, Ids, &g_CoTaskAllocator, &Names );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadNames %x\n", Status );
+ } else {
+ FreeNames( 5, Names, &g_CoTaskAllocator );
+ }
+
+ }
+
+ //
+ // Delete properties
+ //
+
+ {
+ PROPSPEC InSpec[3];
+
+ InSpec[0].ulKind = PRSPEC_PROPID;
+ InSpec[0].propid = 42;
+
+ InSpec[1].ulKind = PRSPEC_LPWSTR;
+ InSpec[1].lpwstr = L"Date Saved";
+
+ InSpec[2].ulKind = PRSPEC_PROPID;
+ InSpec[2].propid = 17;
+
+ Status = DeleteProperties( Handle, 3, InSpec );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to DeleteProperties %x\n", Status );
+ }
+
+ // 1024 Author
+ // 1025 Paper Title
+ }
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+
+ //
+ // WriteNames
+ //
+
+ {
+ PROPID Ids[2] = { 43, 1024 };
+ LPWSTR Names[2] = { L"FortyThree", L"The Real Author" };
+
+ Status = WriteNames( Handle, 2, Ids, Names );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to WriteNames %x\n", Status );
+ }
+
+ // 43 FortyThree
+ // 1024 The Real Author
+ // 1025 Paper Title
+ }
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+
+ }
+
+
+ //
+ // DeleteNames
+ //
+
+ {
+ PROPID Id = 1025;
+
+ Status = DeleteNames( Handle, 1, &Id );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to DeleteNames %x\n", Status );
+ }
+
+ // 43 FortyThree
+ // 1024 The Real Author
+ // 1025
+}
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+
+ //
+ // Write all properties
+ //
+
+ {
+ PROPID Ids[4];
+ LPWSTR Names[4];
+ PROPVARIANT Var[4];
+
+ //
+ // Set a few named properties
+ //
+
+ Ids[0] = 10;
+ Names[0] = L"";
+ Var[0].vt = VT_I4;
+ Var[0].ulVal = 0x12345678;
+
+ Ids[1] = 12;
+ Names[1] = L"Some Random President";
+ Var[1].vt = VT_LPWSTR;
+ Var[1].pwszVal = L"Thomas Jefferson";
+
+ Ids[2] = 11;
+ Names[2] = L"Scurrilous Drivel";
+ Var[2].vt = VT_LPWSTR;
+ Var[2].pwszVal = L"Federalist Papers";
+
+ Ids[3] = 13;
+ Names[3] = L"";
+ Var[3].vt = VT_I4;
+ Var[3].ulVal = 1776;
+
+ Status = WriteAll( Handle, 4, Ids, Names, Var );
+ if (!NT_SUCCESS( Status )) {
+ printf ("WriteAll status %x\n", Status);
+ }
+
+ // 10
+ // 11 Scurrilous Drivel
+ // 12 Some Random President
+ // 13
+ }
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+ //
+ // Expand the heap and Id table by adding many properties
+ //
+
+ {
+ PROPVARIANT Variant;
+ PROPSPEC Spec;
+ char Name[30];
+ WCHAR WName[30];
+ PROPID *Ids;
+
+ for (ULONG i = 0; i < 1000; i++) {
+
+ Variant.vt = VT_UI4;
+ Variant.ulVal = i;
+ sprintf (Name, "Prop %d", i);
+ SzToWsz( WName, Name );
+
+ Spec.ulKind = PRSPEC_LPWSTR;
+ Spec.lpwstr = WName;
+
+ Status = WriteProperties( Handle, 1, 0x4D5A, &Spec, &Variant, &g_CoTaskAllocator, &Ids, NULL );
+ if (!NT_SUCCESS( Status )) {
+ printf ("WriteProperties status %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ }
+ }
+ }
+
+ //
+ // ReadAll
+ //
+
+ {
+ ULONG Count;
+ PROPID *Ids;
+ LPWSTR *Names;
+ PROPVARIANT *Variants;
+
+ Status = ReadAll( Handle, &g_CoTaskAllocator, &Count, &Ids, &Names, &Variants );
+ if (!NT_SUCCESS( Status )) {
+ printf( "Unable to ReadAll %x\n", Status);
+ } else {
+ FreeIds( Ids, &g_CoTaskAllocator );
+ FreeNames( Count, Names, &g_CoTaskAllocator );
+ FreeVariants( Count, Variants, &g_CoTaskAllocator );
+ }
+ }
+
+ return 0;
+}
+
+
+
diff --git a/private/ntos/cntfs/tests/quota.c b/private/ntos/cntfs/tests/quota.c
new file mode 100644
index 000000000..5ca95a38b
--- /dev/null
+++ b/private/ntos/cntfs/tests/quota.c
@@ -0,0 +1,781 @@
+#include <nt.h>
+#include <ntrtl.h>
+#include <nturtl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <windows.h>
+
+#define QuadAlign(n) (((n) + (sizeof(LONGLONG) - 1)) & ~(sizeof(LONGLONG) - 1))
+#define DwordAlign(n)(((n) + (sizeof(ULONG) - 1)) & ~(sizeof(ULONG) - 1))
+
+#define STRUCT_COUNT(n, type, name_length) \
+ ((((n) * QuadAlign(sizeof(type)) + ((name_length) * sizeof(WCHAR))) + \
+ sizeof(type) - 1) / \
+ sizeof(type))
+
+#define SID_MAX_LENGTH \
+ (FIELD_OFFSET(SID, SubAuthority) + sizeof(ULONG) * SID_MAX_SUB_AUTHORITIES)
+
+#define DISK_EVENT_MODULE "System"
+
+#define IO_FILE_QUOTA_THRESHOLD ((NTSTATUS)0x40040024L)
+#define IO_FILE_QUOTA_LIMIT ((NTSTATUS)0x80040025L)
+
+VOID
+DumpQuota (
+ IN PFILE_QUOTA_INFORMATION pfqi,
+ IN PCHAR SeverName
+ );
+
+CHAR *
+FileTimeToString(
+ FILETIME *pft
+ );
+
+VOID
+PrintError(
+ ULONG ErrorCode
+ );
+
+VOID
+Usage();
+
+BOOLEAN QuickSid;
+
+VOID
+main(
+ int Argc,
+ char *Argv[]
+ )
+
+{
+ HANDLE FileHandle;
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES ObjectAttributes;
+ ANSI_STRING DiskName;
+ UNICODE_STRING NameString;
+ IO_STATUS_BLOCK IoStatus;
+ ULONG BufferSize;
+ LONG i;
+ PWCHAR Wstr;
+ PEVENTLOGRECORD EventLogRecord;
+ FILE_QUOTA_INFORMATION QuotaInfo[STRUCT_COUNT(400, FILE_QUOTA_INFORMATION, 32)];
+ FILE_QUOTA_INFORMATION TempQuotaInfo[STRUCT_COUNT(1, FILE_QUOTA_INFORMATION, 32)];
+ PFILE_QUOTA_INFORMATION QuotaInfoPtr;
+ FILE_FS_CONTROL_INFORMATION ControlInfo;
+ FILE_FS_CONTROL_INFORMATION TempControlInfo;
+ PCHAR ServerName = NULL;
+ SID_NAME_USE SidNameUse;
+ LARGE_INTEGER TempLArgeInt;
+ ULONG ErrorCode;
+ ULONG DomainLength;
+ CHAR Domain[100];
+ PCHAR TempPtr;
+ BOOLEAN UserGiven = 0;
+ BOOLEAN DriveLetter = 0;
+ BOOLEAN EventLog = 0;
+ BOOLEAN SettingDefault = 0;
+ BOOLEAN DefaultGiven = 0;
+
+ struct {
+ UCHAR DefaultLimit : 1;
+ UCHAR DefaultThreshold : 1;
+ UCHAR Flags : 1;
+ } DefaultFlags = { 0, 0, 0 };
+
+ if (Argc < 2) {
+ Usage();
+ exit(1);
+ }
+
+ RtlZeroMemory(&QuotaInfo, sizeof(QuotaInfo));
+ QuotaInfoPtr = QuotaInfo;
+
+ RtlInitString( &DiskName, "\\DosDevices\\d:" );
+ RtlAnsiStringToUnicodeString( &NameString, &DiskName, TRUE );
+
+ // Look for the d and repleace it with the requested Argument.
+
+ for (Wstr = NameString.Buffer; *Wstr != L'd'; Wstr++);
+
+ for (i = 1; i < Argc; i++) {
+
+ if (*Argv[i] != '-') {
+
+ if (DriveLetter || !isalpha(*Argv[i])) {
+ Usage();
+ exit(1);
+ }
+
+ TempPtr = Argv[i];
+ *Wstr = RtlAnsiCharToUnicodeChar( &TempPtr );
+ DriveLetter++;
+ continue;
+ }
+
+ switch (Argv[i][1]) {
+ case 'd':
+
+ DefaultGiven = 1;
+ SettingDefault = 1;
+ break;
+
+ case 'e':
+
+ if (EventLog) {
+ Usage();
+ exit(1);
+ }
+
+ if (Argv[i][2] == '\0') {
+ i++;
+ if (i < Argc && Argv[i][0] == '\\') {
+ ServerName = Argv[i];
+ }
+
+ } else {
+
+ ServerName = &Argv[i][2];
+ }
+
+ EventLog++;
+ break;
+
+ case 'u':
+
+ QuotaInfoPtr = (PFILE_QUOTA_INFORMATION) ((PCHAR) QuotaInfoPtr +
+ QuotaInfoPtr->NextEntryOffset);
+
+ if (Argv[i][2] == '\0') {
+ i++;
+ if (i >= Argc) {
+ printf("%s: Missing user name\n", Argv[0] );
+ exit(1);
+ }
+
+ TempPtr = Argv[i];
+
+ } else {
+
+ TempPtr = Argv[i];
+ TempPtr += 2;
+ }
+
+ QuotaInfoPtr->SidLength = SID_MAX_LENGTH;
+ DomainLength = sizeof(Domain);
+
+ if (!LookupAccountName( NULL,
+ TempPtr,
+ &QuotaInfoPtr->Sid,
+ &QuotaInfoPtr->SidLength,
+ Domain,
+ &DomainLength,
+ &SidNameUse)) {
+
+ printf("%s: Bad acccount name %s. Error = %d\n",
+ Argv[0],
+ TempPtr,
+ ErrorCode = GetLastError());
+
+ PrintError( ErrorCode );
+ exit(1);
+ }
+
+ //
+ // Initialize the values to something resonable.
+ //
+
+ QuotaInfoPtr->QuotaThreshold.QuadPart = ~0I64;
+ QuotaInfoPtr->QuotaLimit.QuadPart = ~0I64;
+
+ QuotaInfoPtr->NextEntryOffset = sizeof(FILE_QUOTA_INFORMATION) +
+ QuadAlign(QuotaInfoPtr->SidLength);
+
+ SettingDefault = 0;
+ UserGiven++;
+ break;
+
+ case 't':
+
+ if (Argv[i][2] == '\0') {
+ i++;
+ if (i >= Argc) {
+ printf("%s: Missing Argument\n", Argv[0] );
+ exit(1);
+ }
+
+ TempPtr = Argv[i];
+
+ } else {
+
+ TempPtr = Argv[i];
+ TempPtr += 2;
+ }
+
+
+ if (!sscanf( TempPtr, "%I64i", &TempLArgeInt)) {
+ printf("%s: Missing threshold value\n", Argv[0] );
+ exit(1);
+ }
+
+ if (SettingDefault) {
+ ControlInfo.DefaultQuotaThreshold = TempLArgeInt;
+ DefaultFlags.DefaultThreshold = TRUE;
+
+ } else {
+ QuotaInfoPtr->QuotaThreshold = TempLArgeInt;
+ }
+
+ break;
+
+ case 'l':
+
+ if (Argv[i][2] == '\0') {
+ i++;
+ if (i >= Argc) {
+ printf("%s: Missing limit value\n", Argv[0] );
+ exit(1);
+ }
+
+ TempPtr = Argv[i];
+
+ } else {
+
+ TempPtr = Argv[i];
+ TempPtr += 2;
+ }
+
+ if (!sscanf( TempPtr, "%I64i", &TempLArgeInt)) {
+ printf("%s: Missing value\n", Argv[0] );
+ exit(1);
+ }
+
+ if (SettingDefault) {
+ ControlInfo.DefaultQuotaLimit = TempLArgeInt;
+ DefaultFlags.DefaultLimit = TRUE;
+
+ } else {
+ QuotaInfoPtr->QuotaLimit = TempLArgeInt;
+ }
+
+ break;
+
+ case 'q':
+ QuickSid++;
+ break;
+
+ case 'f':
+
+ if (Argv[i][2] == '\0') {
+ i++;
+ if (i >= Argc) {
+ printf("%s: Missing flag setting\n", Argv[0] );
+ exit(1);
+ }
+
+ TempPtr = Argv[i];
+
+ } else {
+
+ TempPtr = Argv[i];
+ TempPtr += 2;
+ }
+
+ switch (*TempPtr) {
+ case 'e':
+ ControlInfo.FileSystemControlFlags |= FILE_VC_QUOTA_ENFORCE;
+ break;
+
+ case 't':
+ ControlInfo.FileSystemControlFlags |= FILE_VC_QUOTA_TRACK;
+ break;
+
+ case 'd':
+ ControlInfo.FileSystemControlFlags &= ~(FILE_VC_QUOTA_MASK |
+ FILE_VC_LOG_QUOTA_LIMIT |
+ FILE_VC_LOG_QUOTA_THRESHOLD);
+ break;
+
+ default:
+
+ printf("%s: Invalid or missing flag setting.\n", Argv[0] );
+ Usage();
+ exit(1);
+ }
+
+ while (*++TempPtr != '\0') {
+ switch (*TempPtr) {
+ case 'l':
+ ControlInfo.FileSystemControlFlags |= FILE_VC_LOG_QUOTA_LIMIT;
+ break;
+ case 't':
+ ControlInfo.FileSystemControlFlags |= FILE_VC_LOG_QUOTA_THRESHOLD;
+ break;
+ default:
+
+ printf("%s: Invalid flag setting.\n", Argv[0] );
+ Usage();
+ exit(1);
+
+ }
+
+ }
+
+ DefaultGiven = 1;
+ DefaultFlags.Flags = TRUE;
+ break;
+
+ default:
+ printf("%s: Invalid or missing flag setting.\n", Argv[0] );
+
+ case '?':
+ Usage();
+ exit(1);
+ break;
+
+ }
+ }
+
+ if (DriveLetter == 0 && EventLog == 0 ) {
+ printf("%s: Missing drive-letter\n", Argv[0] );
+ }
+
+ if (EventLog &&
+ (DriveLetter || UserGiven)) {
+ Usage();
+ exit(1);
+ }
+
+ if (EventLog) {
+
+ //
+ // Open the event log and read andy events.
+ //
+
+ FileHandle = OpenEventLog( ServerName, DISK_EVENT_MODULE );
+
+ if (FileHandle == NULL) {
+ printf("%s: Event log open failed. %s. Error = %d\n",
+ Argv[0],
+ ServerName == NULL ? "Local machine" : ServerName,
+ ErrorCode = GetLastError());
+
+ PrintError( ErrorCode );
+ exit(1);
+ }
+
+ while (ReadEventLog( FileHandle,
+ EVENTLOG_SEQUENTIAL_READ | EVENTLOG_FORWARDS_READ,
+ 0,
+ QuotaInfo,
+ sizeof(QuotaInfo),
+ &BufferSize,
+ &i )) {
+
+ if (BufferSize == 0) {
+ break;
+ }
+
+ for (EventLogRecord = (PEVENTLOGRECORD) QuotaInfo;
+ (PCHAR) EventLogRecord < (PCHAR) QuotaInfo + BufferSize;
+ EventLogRecord = (PEVENTLOGRECORD)((PCHAR) EventLogRecord +
+ EventLogRecord->Length)) {
+
+ if (EventLogRecord->EventID == IO_FILE_QUOTA_THRESHOLD) {
+ printf( "Quota threshold event at: %s",
+ ctime( &EventLogRecord->TimeGenerated ));
+ } else if (EventLogRecord->EventID == IO_FILE_QUOTA_LIMIT) {
+ printf( "Quota limit event at: %s",
+ ctime( &EventLogRecord->TimeGenerated ));
+ } else {
+ continue;
+ }
+
+ //
+ // Look for the device name. It is the second string.
+ //
+
+ TempPtr = ((PCHAR) EventLogRecord +
+ EventLogRecord->StringOffset);
+
+ printf( " on device %s\n", TempPtr );
+
+ TempPtr = ((PCHAR) EventLogRecord +
+ EventLogRecord->DataOffset +
+ FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ));
+
+ //
+ // Need to align the buffer.
+ //
+
+ RtlCopyMemory( TempQuotaInfo,
+ TempPtr,
+ EventLogRecord->DataLength -
+ FIELD_OFFSET( IO_ERROR_LOG_PACKET, DumpData ));
+
+ DumpQuota( TempQuotaInfo, ServerName );
+ }
+ }
+
+ ErrorCode = GetLastError();
+
+ if (ErrorCode =! ERROR_HANDLE_EOF) {
+ printf("%s: Event log read failed. Error = %d\n",
+ Argv[0],
+ ErrorCode);
+
+ PrintError( ErrorCode );
+ }
+
+ CloseEventLog( FileHandle );
+
+ exit(0);
+ }
+
+ //
+ // Terminate the list.
+ //
+
+ QuotaInfoPtr->NextEntryOffset = 0;
+
+
+ InitializeObjectAttributes( &ObjectAttributes,
+ &NameString,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL );
+
+ Status = NtOpenFile( &FileHandle,
+ FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
+ &ObjectAttributes,
+ &IoStatus,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_SYNCHRONOUS_IO_ALERT );
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Error opening input file %S; error was: %lx\n", NameString.Buffer, Status );
+ exit(1);
+ }
+
+
+ Status = NtQueryVolumeInformationFile( FileHandle,
+ &IoStatus,
+ &TempControlInfo,
+ sizeof( FILE_FS_CONTROL_INFORMATION ),
+ FileFsControlInformation );
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Error NtQueryVolumeInformationFile; error was %lx\n", Status );
+ PrintError( Status );
+ exit(1);
+ }
+
+ printf( "FileSystemControlFlags = %8lx\n", TempControlInfo.FileSystemControlFlags);
+ if ((TempControlInfo.FileSystemControlFlags & FILE_VC_QUOTA_MASK) ==
+ FILE_VC_QUOTA_NONE) {
+
+ TempPtr = "Quotas are disabled on this volume";
+ } else if ((TempControlInfo.FileSystemControlFlags & FILE_VC_QUOTA_MASK) ==
+ FILE_VC_QUOTA_TRACK) {
+
+ TempPtr = "Quota tracking is enabled on this volume";
+
+ }else if (TempControlInfo.FileSystemControlFlags & FILE_VC_QUOTA_ENFORCE) {
+
+ TempPtr = "Quota tracking and enforment is enabled on this volume";
+
+ }
+
+ printf("%s.\n", TempPtr);
+
+ switch (TempControlInfo.FileSystemControlFlags &
+ (FILE_VC_LOG_QUOTA_LIMIT | FILE_VC_LOG_QUOTA_THRESHOLD)) {
+ case FILE_VC_LOG_QUOTA_LIMIT:
+ printf("Logging enable for quota limits.\n");
+ break;
+ case FILE_VC_LOG_QUOTA_THRESHOLD:
+ printf("Logging enable for quota thresholds.\n");
+ break;
+ case FILE_VC_LOG_QUOTA_LIMIT | FILE_VC_LOG_QUOTA_THRESHOLD:
+ printf("Logging enable for quota limits and threshold.\n");
+ break;
+ case 0:
+ printf("Logging for quota events is not enabled.\n");
+ break;
+ }
+
+ if (TempControlInfo.FileSystemControlFlags & FILE_VC_QUOTA_MASK) {
+ if (TempControlInfo.FileSystemControlFlags & FILE_VC_QUOTAS_INCOMPLETE) {
+ TempPtr = "The quota values are incomplete.\n";
+ } else
+ {
+ TempPtr = "The quota values are up to date.\n";
+ }
+
+ printf(TempPtr);
+ }
+
+ printf("Default Quota Threshold = %16I64x\n", TempControlInfo.DefaultQuotaThreshold.QuadPart);
+ printf("Default Quota Limit = %16I64x\n\n", TempControlInfo.DefaultQuotaLimit.QuadPart);
+
+ if (DefaultGiven) {
+
+ if (DefaultFlags.Flags) {
+ TempControlInfo.FileSystemControlFlags &= ~FILE_VC_QUOTA_MASK;
+ TempControlInfo.FileSystemControlFlags |=
+ ControlInfo.FileSystemControlFlags;
+ }
+
+ if (DefaultFlags.DefaultLimit) {
+ TempControlInfo.DefaultQuotaLimit = ControlInfo.DefaultQuotaLimit;
+ }
+
+ if (DefaultFlags.DefaultThreshold) {
+ TempControlInfo.DefaultQuotaThreshold = ControlInfo.DefaultQuotaThreshold;
+ }
+
+ Status = NtSetVolumeInformationFile( FileHandle,
+ &IoStatus,
+ &TempControlInfo,
+ sizeof( FILE_FS_CONTROL_INFORMATION ),
+ FileFsControlInformation );
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Error NtSetVolumeInformationFile; error was %lx\n", Status );
+ PrintError( Status );
+ exit(1);
+ }
+ }
+
+ if (UserGiven) {
+
+ Status = NtSetVolumeInformationFile( FileHandle,
+ &IoStatus,
+ QuotaInfo,
+ sizeof(QuotaInfo),
+ FileFsQuotaSetInformation );
+
+ if (!NT_SUCCESS( Status )) {
+ printf( "Error NtSetVolumeInformationFile; error was %lx\n", Status );
+ PrintError( Status );
+ exit(1);
+ }
+
+ }
+
+ Status = NtQueryVolumeInformationFile( FileHandle,
+ &IoStatus,
+ QuotaInfo,
+ sizeof(QuotaInfo),
+ FileFsQuotaQueryInformation );
+
+ if (!NT_SUCCESS( Status ) && Status != STATUS_BUFFER_OVERFLOW) {
+ printf( "Error NtQueryVolumeInformationFile; error was %lx\n", Status );
+ PrintError( Status );
+ exit(1);
+ }
+
+ QuotaInfoPtr = QuotaInfo;
+
+ while (TRUE) {
+
+ DumpQuota( QuotaInfoPtr, ServerName );
+
+ if (QuotaInfoPtr->NextEntryOffset == 0) {
+ break;
+ }
+
+ QuotaInfoPtr = (PFILE_QUOTA_INFORMATION) ((PCHAR) QuotaInfoPtr +
+ QuotaInfoPtr->NextEntryOffset);
+
+ }
+
+ NtClose( FileHandle );
+}
+
+VOID
+DumpQuota (
+ IN PFILE_QUOTA_INFORMATION FileQuotaInfo,
+ IN PCHAR ServerName
+ )
+{
+ SID_NAME_USE SidNameUse;
+ ULONG AccountLength, DomainLength;
+ ULONG ErrorCode;
+ char AccountName[128];
+ char DomainName[128];
+ UNICODE_STRING String;
+ NTSTATUS Status;
+
+ AccountLength = sizeof(AccountName) - 1;
+ DomainLength = sizeof(DomainName) - 1;
+
+ if (FileQuotaInfo->SidLength == 0) {
+
+ printf( "Default quota values \n" );
+
+ } else if (QuickSid) {
+
+ String.Buffer = (PWCHAR) AccountName;
+ String.MaximumLength = sizeof( AccountName );
+ String.Length = 0;
+
+ Status = RtlConvertSidToUnicodeString( &String,
+ &FileQuotaInfo->Sid,
+ FALSE );
+
+ if (!NT_SUCCESS(Status)) {
+ printf("DumpQuota: RtlConvertSidToUnicodeString failed. Error = %d\n",
+ Status);
+ PrintError( Status );
+ } else {
+ printf( "SID Value = %S\n", String.Buffer );
+ }
+
+ } else if (LookupAccountSidA(
+ ServerName,
+ &FileQuotaInfo->Sid,
+ AccountName,
+ &AccountLength,
+ DomainName,
+ &DomainLength,
+ &SidNameUse))
+ {
+ char *String;
+
+ AccountName[AccountLength] = '\0';
+ DomainName[DomainLength] = '\0';
+ switch (SidNameUse)
+ {
+ case SidTypeUser: String = "User"; break;
+ case SidTypeGroup: String = "Group"; break;
+ case SidTypeDomain: String = "Domain"; break;
+ case SidTypeAlias: String = "Alias"; break;
+ case SidTypeWellKnownGroup: String = "WellKnownGroup"; break;
+ case SidTypeDeletedAccount: String = "DeletedAccount"; break;
+ case SidTypeInvalid: String = "Invalid"; break;
+ default: String = "Unknown"; break;
+ }
+ printf(
+ "SID Name = %s\\%s (%s)\n",
+ DomainName,
+ AccountName,
+ String);
+
+ } else {
+
+ printf("DumpQuota: Bad acccount SID. Error = %d\n",
+ ErrorCode = GetLastError());
+ PrintError( ErrorCode );
+
+ }
+
+ printf("Change time = %s\n", FileTimeToString((PFILETIME) &FileQuotaInfo->ChangeTime));
+ printf("Quota Used = %16I64x\n", FileQuotaInfo->QuotaUsed.QuadPart);
+ printf("Quota Threshold = %16I64x\n", FileQuotaInfo->QuotaThreshold.QuadPart);
+ printf("Quota Limit = %16I64x\n\n", FileQuotaInfo->QuotaLimit.QuadPart);
+
+}
+
+char *Days[] =
+{
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+char *Months[] =
+{
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+CHAR *
+FileTimeToString(FILETIME *FileTime)
+{
+ FILETIME LocalFileTime;
+ SYSTEMTIME SystemTime;
+ static char Buffer[32];
+
+ Buffer[0] = '\0';
+ if (FileTime->dwHighDateTime != 0 || FileTime->dwLowDateTime != 0)
+ {
+ if (!FileTimeToLocalFileTime(FileTime, &LocalFileTime) ||
+ !FileTimeToSystemTime(&LocalFileTime, &SystemTime))
+ {
+ return("Time???");
+ }
+ sprintf(
+ Buffer,
+ "%s %s %2d %2d:%02d:%02d %4d",
+ Days[SystemTime.wDayOfWeek],
+ Months[SystemTime.wMonth - 1],
+ SystemTime.wDay,
+ SystemTime.wHour,
+ SystemTime.wMinute,
+ SystemTime.wSecond,
+ SystemTime.wYear);
+ }
+ return(Buffer);
+}
+
+VOID
+PrintError(ULONG ErrorCode)
+{
+ UCHAR ErrorBuffer[80];
+ ULONG Count;
+ HMODULE FileHandle = NULL;
+ ULONG Flags = FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (ErrorCode > MAXLONG) {
+ Flags = FORMAT_MESSAGE_FROM_HMODULE;
+ FileHandle = LoadLibrary( "ntdll" );
+ if (FileHandle == NULL) {
+ ULONG ErrorCode;
+
+ printf("PrintError: LoadLibrary filed. Error = %d\n",
+ ErrorCode = GetLastError());
+
+ PrintError( ErrorCode );
+ }
+
+ }
+
+ Count = FormatMessage(Flags,
+ FileHandle,
+ ErrorCode,
+ 0,
+ ErrorBuffer,
+ sizeof(ErrorBuffer),
+ NULL
+ );
+
+ if (Count != 0) {
+ printf("Error was: %s\n", ErrorBuffer);
+ } else {
+ printf("Format message failed. Error: %d\n", GetLastError());
+ }
+
+ if (FileHandle != NULL) {
+ FreeLibrary( FileHandle );
+ }
+}
+
+VOID
+Usage()
+{
+ printf( "Usage: %s -e [\\ServerName] | drive-letter [-q ] [ -f e|t|d [lt] ] [-d | -u account-name -t Threshold -l Limit] \n", __argv[0] );
+ printf( " -e [\\ServerName] Print quota events from specified server default is local.\n");
+ printf( " [-q] Quick print Sids. \n");
+ printf( " [-f e|t|d[lt] ] Set volume quota flags (For example -f elt ): \n");
+ printf( " [e]nforce quota limits.\n" );
+ printf( " [t]rack quota usage.\n" );
+ printf( " [d]isable quotas.\n" );
+ printf( " [l]imit events should be logged.\n" );
+ printf( " [t]threhold events should be logged.\n" );
+ printf( " [-d] Set default user quota values.\n");
+ printf( " [-u AccountName] Set quota values for user. \n");
+ printf( " [-l Limit] Set 64-Bit limit value preivously specified user. \n");
+ printf( " A limit of -2 indicates a defunct user can be removed. \n");
+ printf( " [-t Threshold] Set 64-Bit threshold value preivously specified user. \n\n");
+ printf( " Example:\n %s d -f elt -d -t 4194304 -l 5242880 -u administrators -l 0xffffffffffffffff -t 0xffffffffffffffff\n", __argv[0] );
+}
diff --git a/private/ntos/cntfs/tests/sources b/private/ntos/cntfs/tests/sources
new file mode 100644
index 000000000..6d8926bfc
--- /dev/null
+++ b/private/ntos/cntfs/tests/sources
@@ -0,0 +1,45 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ MarkZ 3/29/96
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+MAJORCOMP=cntfs
+MINORCOMP=tests
+
+INCLUDES=$(INCLUDES);$(BASEDIR)\private\inc;$(BASEDIR)\dcomidl
+
+
+TARGETNAME=ntfstest
+TARGETPATH=obj
+TARGETTYPE=LIBRARY
+
+SOURCES=
+
+UMTYPE=console
+UMAPPL=proptest*quota
+
+UMLIBS= $(NTLIBS) \
+ $(BASEDIR)\public\sdk\lib\cairo\*\coruuid.lib \
+ $(BASEDIR)\public\sdk\lib\*\ntdll.lib \
+ $(BASEDIR)\public\sdk\lib\*\ole32.lib
+
+C_DEFINES=$(C_DEFINES) -D_DCOM_
diff --git a/private/ntos/cntfs/up/makefile b/private/ntos/cntfs/up/makefile
new file mode 100644
index 000000000..6ee4f43fa
--- /dev/null
+++ b/private/ntos/cntfs/up/makefile
@@ -0,0 +1,6 @@
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT OS/2
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/ntos/cntfs/up/ntfs.prf b/private/ntos/cntfs/up/ntfs.prf
new file mode 100644
index 000000000..80099c9b7
--- /dev/null
+++ b/private/ntos/cntfs/up/ntfs.prf
@@ -0,0 +1,407 @@
+NtfsFsdCreate@8
+NtfsFreeSnapshotsForFcb@8
+NtfsCompleteRequest@12
+NtfsDeleteIrpContext@4
+NtfsSetTopLevelIrp@12
+NtfsCreateIrpContext@8
+NtfsCommonCleanup@8
+NtfsSnapshotScb@8
+NtfsReleaseFcb@8
+NtfsIncrementCloseCounts@12
+_abnormal_termination
+NtfsAcquireSharedVcb@12
+NtfsCreateScb@24
+NtfsFsdCleanup@8
+NtfsAcquireExclusiveFcb@20
+NtfsSetFileObject@16
+NtfsCommonCreate@12
+NtfsPingVolume@8
+NtfsMapStream@28
+NtfsReadMftRecord@24
+NtfsMapUserBuffer@4
+NtfsCollateNames@24
+NtfsReadFileRecord@28
+NtfsFindInFileRecord@32
+NtfsCleanupAttributeContext@4
+NtfsLookupInFileRecord@40
+NtfsDecrementCleanupCounts@12
+NtfsFsdClose@8
+NtfsAcquireExclusiveScb@8
+NtfsDeleteCcb@12
+NtfsAccessCheck@24
+NtfsCreateCcb@32
+NtfsDecrementCloseCounts@24
+NtfsIncrementCleanupCounts@12
+NtfsCommonClose@40
+NtfsReleaseVcbCheckDelete@16
+NtfsOpenAttribute@60
+NtfsFindPrefix@36
+NtfsUpcaseName@12
+NtfsAcquireFcbWithPaging@12
+ReadIndexBuffer@24
+NtfsFileUpcaseValue@12
+NtfsInitializeIndexContext@4
+NtfsIsFileNameValid@8
+NtfsCleanupIndexContext@8
+NtfsFileCompareValues@24
+NtfsFileIsEqual@16
+FindFirstIndexEntry@16
+BinarySearchIndex@16
+FindNextIndexEntry@32
+NtfsFindNameLink@8
+NtfsTeardownStructures@24
+NtfsRemoveScb@16
+NtfsPrepareFcbForRemoval@16
+NtfsCheckValidAttributeAccess@44
+NtfsOpenExistingAttr@60
+NtfsUpdateScbFromFileObject@16
+NtfsCheckExistingFile@20
+NtfsOpenExistingPrefixFcb@60
+NtfsOpenAttributeInExistingFile@56
+NtfsFinishIoAtEof@4
+NtfsFileContainsWildcards@4
+NtfsAcquireSharedFcb@16
+NtfsReinitializeIndexContext@8
+NtfsFastQueryNetworkOpenInfo@20
+NtfsQueryDirectory@20
+NtfsReleaseScbWithPaging@8
+NtfsFullCompareNames@8
+NtfsCommonDirectoryControl@8
+NtfsCleanupAfterEnumeration@8
+NtfsMdlReadA@28
+NtfsFsdDirectoryControl@8
+NtfsRestartIndexEnumeration@32
+NtfsRemoveClose@8
+NtfsQueueClose@8
+NtfsFspClose@4
+NtfsFindIndexEntry@28
+NtfsLookupEntry@36
+NtfsCreateNewFile@84
+FindMoveableIndexRoot@12
+NtfsFileIsInExpression@16
+NtfsNetworkOpenCreate@12
+NtfsContinueIndexEnumeration@20
+NtfsCopyReadA@32
+NtfsWaitSync@4
+NtfsMcbLookupArrayIndex@12
+NtfsLookupNtfsMcbEntry@36
+_allshr
+NtfsSingleAsync@24
+NtfsSingleSyncCompletionRoutine@12
+NtfsLookupAllocation@32
+NtfsNonCachedIo@28
+NtfsPrepareBuffers@32
+NtfsDeallocateCompressionBuffer@8
+NtfsLockUserBuffer@16
+NtfsDeleteMdlAndBuffer@8
+NtfsCommitCurrentTransaction@4
+NtfsFreeRestartTable@4
+LfsResetUndoTotal@12
+LfsQueryLastLsn@4
+InitializeNewTable@12
+NtfsInitializeRestartTable@12
+NtfsGetFirstRestartTable@4
+LfsAllocateLbcb@8
+LfsWriteLfsRestart@12
+LfsPinOrMapData@40
+NtfsCheckpointVolume@28
+LfsWriteLogRecordIntoLogPage@52
+NtfsFreeRestartTableIndex@8
+LfsPrepareLfcbForLogRecord@8
+LfsWrite@44
+LfsCurrentAvailSpace@12
+NtfsWriteLog@56
+LfsTransferLogBytes@28
+NtfsAllocateRestartTableIndex@4
+LfsVerifyLogSpaceAvail@20
+NtfsVolumeCheckpointDpc@16
+NtfsCheckpointAllVolumes@4
+LfsWriteRestartArea@16
+NtfsFreeReservedBuffer@4
+NtfsFsdWrite@8
+NtfsCreateMdlAndBuffer@24
+NtfsReleaseFileForCcFlush@8
+NtfsCommonWrite@8
+LfsFlushLfcb@8
+LfsFlushLbcb@8
+LfsSetBaseLsnPriv@16
+NtfsGetNextRestartTable@8
+NtfsTransformUsaBlock@16
+LfsFindFirstIo@40
+NtfsAcquireFileForCcFlush@8
+LfsDeallocateLbcb@8
+NtfsReleaseScbFromLazyWrite@4
+NtfsAcquireScbForLazyWrite@8
+LookupLcns@28
+DirtyPageRoutine@28
+LfsNextLogPageOffset@20
+LfsGetLbcb@4
+NtfsFsdRead@8
+NtfsCommonRead@12
+NtfsFsdFileSystemControl@8
+NtfsCommonFileSystemControl@8
+NtfsUserFsRequest@8
+NtfsDeallocateRecordsComplete@4
+NtfsPinMappedData@24
+NtfsCheckpointCurrentTransaction@4
+NtfsUpdateScbSnapshots@4
+NtfsRestartChangeValue@28
+NtfsUpdateLcbDuplicateInfo@8
+NtfsRestartUpdateFileName@8
+NtfsOplockRequest@8
+NtfsUpdateFileNameInIndex@20
+NtfsBreakBatchOplock@32
+NtfsChangeAttributeValue@40
+NtfsUpdateStandardInformation@8
+NtfsOpenAttributeCheck@20
+NtfsAcquireSharedScbForTransaction@8
+NtfsReleaseSharedResources@4
+NtfsPrepareForUpdateDuplicate@20
+NtfsUpdateDuplicateInfo@16
+NtfsFsdSetInformation@8
+NtfsCommonSetInformation@8
+NtfsWriteFileSizes@20
+NtfsSetEndOfFileInfo@24
+NtfsSetBasicInfo@20
+NtfsUpdateFileDupInfo@12
+NtfsCopyWriteA@32
+_allmul
+LfsFindOldestClientLsn@12
+_aullshr
+LfsFindCurrentAvail@4
+_allshl
+NtfsFsdQueryVolumeInformation@8
+NtfsCommonQueryVolumeInfo@8
+NtfsFastQueryStdInfo@20
+NtfsAddNtfsMcbEntry@32
+NtfsDefineNtfsMcbRange@24
+NtfsVerifyAndRevertUsaBlock@24
+NtfsUpdateScbFromAttribute@12
+NtfsCreateInternalStreamCommon@16
+NtfsUpdateFcbInfoFromDisk@20
+NtfsFcbTableCompare@12
+NtfsAllocateFcbTableEntry@8
+NtfsUpdateFcbSecurity@20
+NtfsAllocateEresource@0
+SeValidSecurityDescriptor@8
+NtfsCreateFcb@28
+NtfsInitializeNtfsMcb@16
+NtfsCheckScbForCache@4
+NtfsMapAttributeValue@24
+NtfsDeleteInternalAttributeStream@8
+NtfsNumberOfRunsInRange@12
+NtfsPreloadAllocation@24
+NtfsMapOrPinPageInBitmap@32
+NtfsGetNextNtfsMcbEntry@24
+NtfsPinStream@28
+NtfsUnloadNtfsMcbRange@28
+NtfsInsertPrefix@8
+NtfsOpenFile@92
+NtfsInsertNameLink@8
+NtfsCreateLcb@28
+NtfsFspDispatch@4
+NtfsAddToWorkque@8
+NtfsFreeEresource@4
+NtfsFreeFcbTableEntry@8
+NtfsDeleteScb@8
+NtfsDeleteFcb@12
+NtfsUninitializeNtfsMcb@4
+NtfsTeardownFromLcb@32
+NtfsGetNextCachedFreeRun@24
+NtfsAddCachedRun@28
+NtfsDeleteIndexEntry@16
+DeleteFromIndex@12
+NtfsPinMftRecord@28
+NtfsRemovePrefix@4
+PruneIndex@16
+NtfsFreeBitmapRun@24
+NtfsDeallocateClusters@32
+NtfsDeleteAllocationInternal@32
+NtfsDeleteAllocation@36
+DeleteSimple@16
+NtfsRemoveNtfsMcbEntry@20
+NtfsUpdateFcb@4
+NtfsRestartClearBitsInBitMap@16
+NtfsRestartDeleteSimpleAllocation@8
+NtfsUpdateIndexScbFromAttribute@8
+NtfsAddScbToFspClose@12
+NtfsUpdateScbFromMemory@8
+AddToIndex@20
+NtfsScanMcbForRealClusterCount@24
+NtfsRestartChangeMapping@28
+NtfsAllocateBitmapRun@24
+NtfsGetSpaceForAttribute@16
+NtfsGetNextScb@8
+NtfsRenameLinkInDir@36
+NtfsDeleteAttributeAllocation@24
+NtfsAddIndexEntry@24
+NtfsIsMftIndexInHole@16
+NtfsChangeAttributeSize@16
+NtfsRenameLcb@20
+NtfsBuildMappingPairs@20
+NtfsAddAllocation@32
+NtfsCreateAttributeWithValue@40
+NtfsCreateNonresidentWithValue@44
+NtfsInitializeMftRecord@24
+NtfsAllocateAttribute@40
+NtfsFindTargetElements@24
+NtfsOpenTargetDirectory@44
+NtfsAddLink@36
+NtfsAddNameToParent@36
+NtfsAllocateClusters@36
+NtfsRestartRemoveAttribute@12
+NtfsGetHighestVcn@16
+NtfsConvertToNonresident@20
+NtfsInitializeFcbAndStdInfo@24
+NtfsRestartSetBitsInBitMap@16
+NtfsAllocateRecord@16
+NtfsSetRenameInfo@24
+NtfsIsFatNameValid@8
+InsertSimpleAllocation@24
+NtfsGetNextHoleToFill@36
+NtfsCreateAttributeWithAllocation@32
+NtfsCreateAttribute@32
+NtfsRestartInsertAttribute@28
+NtfsAllocateMftRecord@12
+NtfsOpenNewAttr@56
+NtfsRestartInsertSimpleAllocation@12
+MmCanFileBeTruncated@8
+NtfsStoreSecurityDescriptor@12
+NtfsLookupLastNtfsMcbEntry@12
+NtfsDeleteAttributeRecord@20
+NtfsGetSizeForMappingPairs@24
+NtfsAssignSecurity@36
+NtfsAddAttributeAllocation@20
+NtfsReadAheadCachedBitmap@16
+NtfsCheckIndexForAddOrDelete@12
+NtfsRemoveLink@24
+NtfsAddDeallocatedRecords@16
+RtlFindLastBackwardRunClear@12
+NtfsUpdateNormalizedName@20
+NtfsOplockPrePostIrp@8
+NtfsDeallocateMftRecord@12
+NtfsDeleteLcb@8
+NtfsDeallocateRecord@16
+NtfsDeleteFile@16
+NtfsOplockComplete@8
+NtfsDereferenceSharedSecurity@4
+NtfsDeleteAllocationFromRecord@16
+NtfsPrepareMdlWriteA@28
+NtfsAcquireScbForReadAhead@8
+NtfsReleaseScbFromReadAhead@4
+NtfsQueryFsSizeInfo@16
+NtfsOpenSubdirectory@32
+NtfsReleaseFcbWithPaging@8
+NtfsFastUnlockSingle@28
+InsertSimpleRoot@20
+NtfsCreateFileLock@8
+NtfsRestartDeleteSimpleRoot@16
+NtfsRestartInsertSimpleRoot@20
+NtfsFastLock@36
+NtfsRestartChangeAttributeSize@16
+NtfsFastIoCheckIfPossible@32
+MmSetAddressRangeModified@8
+LfsFlushToLsn@12
+LfsFlushToLsnPriv@12
+NtfsExtendRestartTable@12
+NtfsLoadSecurityDescriptor@12
+NtfsAcquireForCreateSection@4
+NtfsReleaseForCreateSection@4
+NtfsBackoutFailedOpens@20
+NtfsGetReservedBuffer@16
+NtfsAddRecentlyDeallocated@16
+NtfsFindFreeBitmapRun@32
+NtfsIsLcnInCachedFreeRun@24
+NtfsCreateIndex@40
+NtfsMultipleAsync@20
+NtfsMultiSyncCompletionRoutine@12
+NtfsInitializeRecordAllocation@28
+NtfsPreparePinWriteStream@32
+GetIndexBuffer@16
+PushIndexRoot@12
+NtfsWaitForIoAtEof@16
+NtfsLockNtfsMcb@4
+NtfsMcbCleanupLruQueue@4
+NtfsUnlockNtfsMcb@4
+RtlFindNextForwardRunClear@12
+NtfsQueryFsAttributeInfo@16
+NtfsCheckRecordStackUsage@4
+NtfsScanEntireBitmap@12
+NtfsAcquireExclusiveGlobal@4
+NtfsGetNextFcbTableEntry@8
+NtfsAcquireExclusiveVcb@12
+NtfsPrePostIrp@8
+NtfsGetDiskGeometry@16
+NtfsRaiseStatus@16
+RtlUnwind@16
+NtfsProcessException@12
+NtfsExceptionFilter@8
+_except_handler3
+NtfsInitializeVcb@16
+_local_unwind2
+_global_unwind2
+NtfsMountVolume@8
+NtfsPostRequest@8
+NtfsDeviceIoControl@28
+NtfsCommonVolumeOpen@8
+NtfsRestartVolume@8
+NtfsStartLogFile@8
+LfsReadRestart@48
+LfsUpdateRestartAreaFromLfcb@8
+ReleaseRestartState@16
+LfsUpdateLfcbFromNoRestart@32
+NtfsScanMftBitmap@8
+NtfsOpenRootDirectory@8
+NtfsFlushUserStream@16
+NtfsCheckFileRecord@12
+NtfsFsdDeviceControl@8
+NtfsSetAndGetVolumeTimes@12
+InitializeRestartState@20
+LfsRemoveClientFromList@12
+NtfsCreateRootFcb@8
+LfsIsRestartPageHeaderValid@16
+NtfsInitializeCachedBitmap@8
+LfsRestartLogFile@24
+LfsUpdateLfcbFromPgHeader@24
+NtfsCreatePrerestartScb@24
+LfsAddClientToList@12
+NtfsRestoreScbSnapshots@8
+LfsNormalizeBasicLogFile@12
+LfsInitializeLogFilePriv@24
+NtfsDismountVolume@8
+NtfsCommonDeviceControl@8
+NtfsGetVolumeLabel@12
+LfsIsRestartAreaValid@8
+LfsInitializeLogFileService@0
+LfsAllocateLfcb@0
+NtfsInitializeClusterAllocation@8
+NtfsFlushVolume@20
+NtfsIsBootSectorNtfs@8
+NtfsCheckAttributeRecord@16
+NtfsSetExtendedDasdIo@8
+NtfsCloseAttributesFromRestart@8
+NtfsOpenSystemFile@32
+NtfsVolumeDasdIo@24
+NtfsGet8dot3NameStatus@12
+DriverEntry@8
+NtfsInitializeNtfsData@4
+_alldiv
+LfsOpenLogFile@36
+NtfsReadBootSector@20
+LfsReadRestartArea@16
+NtfsAbortTransaction@12
+NtfsReplaceFileEas@12
+NtfsReplaceAttribute@28
+NtfsOverwriteAttr@56
+NtfsRemoveDataAttributes@24
+NtfsAddEa@24
+NtfsQueryNameInfo@24
+NtfsCommonQueryInformation@8
+NtfsFsdQueryInformation@8
+NtfsReserveMftRecord@12
+NtfsDeleteVcb@8
+LfsDeleteLogHandle@4
+NtfsReleaseAllFiles@12
+NtfsAcquireAllFiles@16
+NtfsPerformDismountOnVcb@12
+NtfsStopLogFile@4
diff --git a/private/ntos/cntfs/up/sources b/private/ntos/cntfs/up/sources
new file mode 100644
index 000000000..43960dd93
--- /dev/null
+++ b/private/ntos/cntfs/up/sources
@@ -0,0 +1,30 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+UP_DRIVER=yes
+
+TARGETPATH=obj
+TARGETLIBS=..\..\lfs\up\obj\*\lfs.lib
+
+!include ..\sources.inc
diff --git a/private/ntos/cntfs/vattrsup.c b/private/ntos/cntfs/vattrsup.c
new file mode 100644
index 000000000..b5d51cc11
--- /dev/null
+++ b/private/ntos/cntfs/vattrsup.c
@@ -0,0 +1,627 @@
+/*++
+
+Copyright (c) 1996 Microsoft Corporation
+
+Module Name:
+
+ VAttrSup.c
+
+Abstract:
+
+ This module implements the attribute routines for NtOfs
+
+Author:
+
+ Tom Miller [TomM] 10-Apr-1996
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('vFtN')
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtOfsCreateAttribute)
+#endif
+
+
+NTFSAPI
+NTSTATUS
+NtOfsCreateAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN UNICODE_STRING Name,
+ IN CREATE_OPTIONS CreateOptions,
+ IN ULONG LogNonresidentToo,
+ OUT PSCB *ReturnScb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to create / open a named data attribute
+ within a given file, which may or may not be recoverable.
+
+Arguments:
+
+ Fcb - File in which the attribute is to be created. It is acquired exclusive
+
+ Name - Name of the attribute for all related Scbs and attributes on disk.
+
+ CreateOptions - Standard create flags.
+
+ LogNonresidentToo - Supplies nonzero if updates to the attribute should
+ be logged.
+
+ ReturnScb - Returns an Scb as handle for the attribute.
+
+Return Value:
+
+ STATUS_OBJECT_NAME_COLLISION -- if CreateNew and attribute already exists
+ STATUS_OBJECT_NAME_NOT_FOUND -- if OpenExisting and attribute does not exist
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ BOOLEAN FoundAttribute;
+ NTSTATUS Status = STATUS_SUCCESS;
+ PSCB Scb = NULL;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT( NtfsIsExclusiveFcb( Fcb ));
+
+ PAGED_CODE();
+
+ //
+ // Now, just create the Data Attribute.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ try {
+
+ //
+ // First see if the attribute already exists, by searching for the root
+ // attribute.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $DATA,
+ &Name,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ //
+ // If it is not there, and the CreateOptions allow, then let's create
+ // the attribute root now. (First cleaning up the attribute context from
+ // the lookup).
+ //
+
+ if (!FoundAttribute && (CreateOptions <= CREATE_OR_OPEN)) {
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $DATA,
+ &Name,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ //
+ // If the attribute is already there, and we were asked to create it, then
+ // return an error.
+ //
+
+ } else if (FoundAttribute && (CreateOptions == CREATE_NEW)) {
+
+ Status = STATUS_OBJECT_NAME_COLLISION;
+ leave;
+
+ //
+ // If the attribute is not there, and we were supposed to open existing, then
+ // return an error.
+ //
+
+ } else if (!FoundAttribute) {
+
+ Status = STATUS_OBJECT_NAME_NOT_FOUND;
+ leave;
+ }
+
+ //
+ // Otherwise create/find the Scb and reference it.
+ //
+
+ Scb = NtfsCreateScb( IrpContext, Fcb, $DATA, &Name, FALSE, &FoundAttribute );
+
+ //
+ // Make sure things are correctly reference counted
+ //
+
+ NtfsIncrementCloseCounts( Scb, TRUE, FALSE );
+
+ //
+ // Make sure the stream can be mapped internally
+ //
+
+ if (Scb->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+ }
+
+ //
+ // If we created the Scb, then get the no modified write set correctly.
+ //
+
+ ASSERT( !FoundAttribute ||
+ (LogNonresidentToo == BooleanFlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE)) );
+
+ if (!FoundAttribute && LogNonresidentToo) {
+ SetFlag( Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
+ }
+
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NtfsFoundAttribute(&LocalContext) );
+
+ NtfsExpandQuotaToAllocationSize( IrpContext, Scb );
+
+ } finally {
+
+ if (AbnormalTermination( )) {
+ if (Scb != NULL) {
+ NtOfsCloseAttribute( IrpContext, Scb );
+ }
+ }
+
+ NtfsCleanupAttributeContext( &LocalContext );
+ }
+
+ *ReturnScb = Scb;
+
+ return Status;
+}
+
+
+NTFSAPI
+VOID
+NtOfsCloseAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to close a previously returned handle on an attribute.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this attribute.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ ASSERT( NtfsIsExclusiveFcb( Scb->Fcb ));
+
+ NtfsDecrementCloseCounts( IrpContext, Scb, NULL, TRUE, FALSE, TRUE );
+}
+
+
+NTFSAPI
+VOID
+NtOfsDeleteAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete an attribute.
+
+Arguments:
+
+ Fcb - Supplies an Fcb as the previously returned object handle for the file
+
+ Scb - Supplies an Scb as the previously returned handle for this attribute.
+
+Return Value:
+
+ None (Deleting a nonexistant index is benign).
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ BOOLEAN FoundAttribute;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ ASSERT( NtfsIsExclusiveFcb( Fcb ));
+
+ try {
+
+ //
+ // First see if there is some attribute allocation, and if so truncate it
+ // away allowing this operation to be broken up.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ if (NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $DATA,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &LocalContext )
+
+ &&
+
+ !NtfsIsAttributeResident(NtfsFoundAttribute(&LocalContext))) {
+
+ ASSERT(Scb->FileObject != NULL);
+
+ NtfsDeleteAllocation( IrpContext, NULL, Scb, 0, MAXLONGLONG, TRUE, TRUE );
+ }
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ //
+ // Initialize the attribute context on each trip through the loop.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ //
+ // Now there should be a single attribute record, so look it up and delete it.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $DATA,
+ &Scb->AttributeName,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ ASSERT(FlagOn( Scb->ScbState, SCB_STATE_QUOTA_ENLARGED ));
+
+ NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &LocalContext );
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ }
+}
+
+
+NTFSAPI
+LONGLONG
+NtOfsQueryLength (
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to query the Length (FileSize) of an attribute.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this attribute.
+
+ Length - Returns the current Length of the attribute.
+
+Return Value:
+
+ None (Deleting a nonexistant index is benign).
+
+--*/
+
+{
+ LONGLONG Length;
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ Length = Scb->Header.FileSize.QuadPart;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ return Length;
+}
+
+NTFSAPI
+VOID
+NtOfsSetLength (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN LONGLONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to set the Length (FileSize) of an attribute.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this attribute.
+
+ Length - Supplies the new Length for the attribute.
+
+Return Value:
+
+ None (Deleting a nonexistant index is benign).
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+
+ EOF_WAIT_BLOCK EofWaitBlock;
+ PFILE_OBJECT FileObject = Scb->FileObject;
+ PFCB Fcb = Scb->Fcb;
+ BOOLEAN DoingIoAtEof = FALSE;
+ BOOLEAN Truncating = FALSE;
+ BOOLEAN CleanupAttrContext = FALSE;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT( NtfsIsExclusiveScb( Scb ));
+
+ ASSERT(FileObject != NULL);
+
+ PAGED_CODE();
+
+ try {
+
+ //
+ // If this is a resident attribute we will try to keep it resident.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // If the new file size is larger than a file record then convert
+ // to non-resident and use the non-resident code below. Otherwise
+ // call ChangeAttributeValue which may also convert to nonresident.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttrContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ //
+ // Either convert or change the attribute value.
+ //
+
+ if (Length >= Scb->Vcb->BytesPerFileRecordSegment) {
+
+ NtfsConvertToNonresident( IrpContext,
+ Fcb,
+ NtfsFoundAttribute( &AttrContext ),
+ FALSE,
+ &AttrContext );
+
+ } else {
+
+ ULONG AttributeOffset;
+
+ //
+ // We are sometimes called by MM during a create section, so
+ // for right now the best way we have of detecting a create
+ // section is whether or not the requestor mode is kernel.
+ //
+
+ if ((ULONG)Length > Scb->Header.FileSize.LowPart) {
+
+ AttributeOffset = Scb->Header.ValidDataLength.LowPart;
+
+ } else {
+
+ AttributeOffset = (ULONG) Length;
+ }
+
+ //
+ // ****TEMP Ideally we would do this simple case by hand.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ AttributeOffset,
+ NULL,
+ (ULONG)Length - AttributeOffset,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+
+ Scb->Header.FileSize.QuadPart = Length;
+
+ //
+ // If the file went non-resident, then the allocation size in
+ // the Scb is correct. Otherwise we quad-align the new file size.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ Scb->Header.AllocationSize.LowPart = QuadAlign( Scb->Header.FileSize.LowPart );
+ Scb->Header.ValidDataLength.QuadPart = Length;
+
+ Scb->TotalAllocated = Scb->Header.AllocationSize.QuadPart;
+
+ } else {
+
+ SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // Now update Cc.
+ //
+
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ //
+ // ****TEMP**** This hack is awaiting our actually doing this change
+ // in CcSetFileSizes.
+ //
+
+ *((PLONGLONG)(Scb->NonpagedScb->SegmentObject.SharedCacheMap) + 5) = Length;
+
+ leave;
+ }
+ }
+
+ //
+ // Nonresident path
+ //
+ // Now determine where the new file size lines up with the
+ // current file layout. The two cases we need to consider are
+ // where the new file size is less than the current file size and
+ // valid data length, in which case we need to shrink them.
+ // Or we new file size is greater than the current allocation,
+ // in which case we need to extend the allocation to match the
+ // new file size.
+ //
+
+ if (Length > Scb->Header.AllocationSize.QuadPart) {
+
+ //
+ // Add the allocation.
+ //
+
+ NtfsAddAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes( Scb->Vcb, Scb->Header.AllocationSize.QuadPart ),
+ LlClustersFromBytes(Scb->Vcb, (Length - Scb->Header.AllocationSize.QuadPart)),
+ FALSE );
+
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ Scb->Header.FileSize.QuadPart = Length;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+
+ //
+ // Otherwise see if we have to knock these numbers down...
+ //
+
+ } else {
+
+ ExAcquireFastMutex( Scb->Header.FastMutex );
+ if (Length < Scb->Header.ValidDataLength.QuadPart) {
+
+ Scb->Header.ValidDataLength.QuadPart = Length;
+ }
+
+ if (Length < Scb->ValidDataToDisk) {
+
+ Scb->ValidDataToDisk = Length;
+ }
+ Scb->Header.FileSize.QuadPart = Length;
+ ExReleaseFastMutex( Scb->Header.FastMutex );
+ }
+
+
+ //
+ // Call our common routine to modify the file sizes. We are now
+ // done with Length and NewValidDataLength, and we have
+ // PagingIo + main exclusive (so no one can be working on this Scb).
+ // NtfsWriteFileSizes uses the sizes in the Scb, and this is the
+ // one place where in Ntfs where we wish to use a different value
+ // for ValidDataLength. Therefore, we save the current ValidData
+ // and plug it with our desired value and restore on return.
+ //
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &Scb->Header.ValidDataLength.QuadPart,
+ FALSE,
+ TRUE );
+
+ //
+ // Now update Cc.
+ //
+
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Scb->Header.AllocationSize );
+
+ } finally {
+
+ if (CleanupAttrContext) {
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ }
+}
+
+NTFSAPI
+VOID
+NtOfsFlushAttribute (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ULONG Purge
+ )
+
+/*++
+
+Routine Description:
+
+ This routine flushes the specified attribute, and optionally purges it from the cache.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this attribute.
+
+ Purge - Supplies TRUE if the attribute is to be purged.
+
+Return Value:
+
+ None (Deleting a nonexistant index is benign).
+
+--*/
+
+{
+ if (Purge) {
+ NtfsFlushAndPurgeScb( IrpContext, Scb, NULL );
+ } else {
+ NtfsFlushUserStream( IrpContext, Scb, NULL, 0 );
+ }
+}
diff --git a/private/ntos/cntfs/verfysup.c b/private/ntos/cntfs/verfysup.c
new file mode 100644
index 000000000..869249dbc
--- /dev/null
+++ b/private/ntos/cntfs/verfysup.c
@@ -0,0 +1,1779 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ VerfySup.c
+
+Abstract:
+
+ This module implements the Ntfs Verify volume and fcb support
+ routines
+
+Author:
+
+ Gary Kimura [GaryKi] 30-Jan-1992
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The Debug trace level for this module
+//
+
+#define Dbg (DEBUG_TRACE_VERFYSUP)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('VFtN')
+
+extern BOOLEAN NtfsCheckQuota;
+
+//
+// Local procedure prototypes
+//
+
+VOID
+NtfsPerformVerifyDiskRead (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PVOID Buffer,
+ IN LONGLONG Offset,
+ IN ULONG NumberOfBytesToRead
+ );
+
+NTSTATUS
+NtfsVerifyReadCompletionRoutine(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ );
+
+NTSTATUS
+NtfsDeviceIoControlAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN ULONG IoCtl
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCheckpointAllVolumes)
+#pragma alloc_text(PAGE, NtfsMarkVolumeDirty)
+#pragma alloc_text(PAGE, NtfsPerformVerifyOperation)
+#pragma alloc_text(PAGE, NtfsPingVolume)
+#pragma alloc_text(PAGE, NtfsUpdateVersionNumber)
+#pragma alloc_text(PAGE, NtfsVerifyOperationIsLegal)
+#endif
+
+
+BOOLEAN
+NtfsPerformVerifyOperation (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to force a verification of the volume. It assumes
+ that everything might be resource/mutex locked so it cannot take out
+ any resources. It will read in the boot sector and the dasd file record
+ and from those determine if the volume is okay. This routine is called
+ whenever the real device has started rejecting I/O requests with
+ VERIFY_REQUIRED.
+
+ If the volume verifies okay then we will return TRUE otherwise we will
+ return FALSE.
+
+ It does not alter the Vcb state.
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried.
+
+Return Value:
+
+ BOOLEAN - TRUE if the volume verified okay, and FALSE otherwise.
+
+--*/
+
+{
+ BOOLEAN Results;
+
+ PPACKED_BOOT_SECTOR BootSector;
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ LONGLONG Offset;
+
+ PSTANDARD_INFORMATION StandardInformation;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPerformVerifyOperation, Vcb = %08lx\n", Vcb) );
+
+ BootSector = NULL;
+ FileRecord = NULL;
+
+ try {
+
+ //
+ // Allocate a buffer for the boot sector, read it in, and then check if
+ // it some of the fields still match. The starting lcn is zero and the
+ // size is the size of a disk sector.
+ //
+
+ BootSector = NtfsAllocatePool( NonPagedPool,
+ ROUND_TO_PAGES( Vcb->BytesPerSector ));
+
+ NtfsPerformVerifyDiskRead( IrpContext, Vcb, BootSector, (LONGLONG)0, Vcb->BytesPerSector );
+
+ //
+ // For now we will only check that the serial numbers, mft lcn's and
+ // number of sectors match up with what they use to be.
+ //
+
+ if ((BootSector->SerialNumber != Vcb->VolumeSerialNumber) ||
+ (BootSector->MftStartLcn != Vcb->MftStartLcn) ||
+ (BootSector->Mft2StartLcn != Vcb->Mft2StartLcn) ||
+ (BootSector->NumberSectors != Vcb->NumberSectors)) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // Allocate a buffer for the dasd file record, read it in, and then check
+ // if some of the fields still match. The size of the record is the number
+ // of bytes in a file record segment, and because the dasd file record is
+ // known to be contiguous with the start of the mft we can compute the starting
+ // lcn as the base of the mft plus the dasd number mulitplied by the clusters
+ // per file record segment.
+ //
+
+ FileRecord = NtfsAllocatePool( NonPagedPoolCacheAligned,
+ ROUND_TO_PAGES( Vcb->BytesPerFileRecordSegment ));
+
+ Offset = LlBytesFromClusters(Vcb, Vcb->MftStartLcn) +
+ (VOLUME_DASD_NUMBER * Vcb->BytesPerFileRecordSegment);
+
+ NtfsPerformVerifyDiskRead( IrpContext, Vcb, FileRecord, Offset, Vcb->BytesPerFileRecordSegment );
+
+ //
+ // Given a pointer to a file record we want the value of the first attribute which
+ // will be the standard information attribute. Then we will check the
+ // times stored in the standard information attribute against the times we
+ // have saved in the vcb. Note that last access time will be modified if
+ // the disk was moved and mounted on a different system without doing a dismount
+ // on this system.
+ //
+
+ StandardInformation = NtfsGetValue(((PATTRIBUTE_RECORD_HEADER)Add2Ptr( FileRecord,
+ FileRecord->FirstAttributeOffset )));
+
+ if ((StandardInformation->CreationTime != Vcb->VolumeCreationTime) ||
+ (StandardInformation->LastModificationTime != Vcb->VolumeLastModificationTime) ||
+ (StandardInformation->LastChangeTime != Vcb->VolumeLastChangeTime) ||
+ (StandardInformation->LastAccessTime != Vcb->VolumeLastAccessTime)) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // If the device is not writable we won't remount it.
+ //
+
+ if (NtfsDeviceIoControlAsync( IrpContext,
+ Vcb->TargetDeviceObject,
+ IOCTL_DISK_IS_WRITABLE ) == STATUS_MEDIA_WRITE_PROTECTED) {
+
+ try_return( Results = FALSE );
+ }
+
+ //
+ // At this point we believe that the disk has not changed so can return true and
+ // let our caller reenable the device
+ //
+
+ Results = TRUE;
+
+ try_exit: NOTHING;
+ } finally {
+
+ if (BootSector != NULL) { NtfsFreePool( BootSector ); }
+ if (FileRecord != NULL) { NtfsFreePool( FileRecord ); }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsPerformVerifyOperation -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+VOID
+NtfsPerformDismountOnVcb (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN BOOLEAN DoCompleteDismount
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to start the dismount process on a vcb.
+ It marks the Vcb as not mounted and dereferences all opened stream
+ file objects, and gets the Vcb out of the Vpb's mounted volume
+ structures.
+
+Arguments:
+
+ Vcb - Supplies the Vcb being dismounted
+
+ DoCompleteDismount - Indicates if we are to actually mark the volume
+ as dismounted or if we are simply to stop the logfile and close
+ the internal attribute streams.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFCB Fcb;
+ PSCB Scb;
+ PVOID RestartKey;
+
+ BOOLEAN Restart;
+
+ PVPB NewVpb;
+
+ DebugTrace( +1, Dbg, ("NtfsPerformDismountOnVcb, Vcb = %08lx\n", Vcb) );
+
+ //
+ // Blow away our delayed close file object.
+ //
+
+ if (!IsListEmpty( &NtfsData.AsyncCloseList ) ||
+ !IsListEmpty( &NtfsData.DelayedCloseList )) {
+
+ NtfsFspClose( Vcb );
+ }
+
+ //
+ // Commit any current transaction before we start tearing down the volume.
+ //
+
+ NtfsCommitCurrentTransaction( IrpContext );
+
+ //
+ // Acquire all files exclusively to make dismount atomic.
+ //
+
+ NtfsAcquireAllFiles( IrpContext, Vcb, TRUE, FALSE );
+
+ //
+ // Use a try-finally to facilitate cleanup.
+ //
+
+ try {
+
+ //
+ // Get rid of Cairo Files
+ //
+
+#ifdef _CAIRO_
+ if (Vcb->QuotaTableScb != NULL) {
+ NtOfsCloseIndex( IrpContext, Vcb->QuotaTableScb );
+ Vcb->QuotaTableScb = NULL;
+ }
+#endif _CAIRO_
+
+ //
+ // Stop the log file.
+ //
+
+ NtfsStopLogFile( Vcb );
+
+ //
+ // Now for every file Scb with an opened stream file we will delete
+ // the internal attribute stream
+ //
+
+ RestartKey = NULL;
+ while (TRUE) {
+
+ NtfsAcquireFcbTable( IrpContext, Vcb );
+ Fcb = NtfsGetNextFcbTableEntry(Vcb, &RestartKey);
+ NtfsReleaseFcbTable( IrpContext, Vcb );
+
+ if (Fcb == NULL) {
+
+ break;
+ }
+
+ ASSERT_FCB( Fcb );
+
+ Scb = NULL;
+ while ((Fcb != NULL) && ((Scb = NtfsGetNextChildScb(Fcb, Scb)) != NULL)) {
+
+ ASSERT_SCB( Scb );
+
+ if (Scb->FileObject != NULL) {
+
+ //
+ // Assume we want to start from the beginning of the Fcb table.
+ //
+
+ Restart = TRUE;
+
+ //
+ // For the VolumeDasdScb and bad cluster file, we simply decrement
+ // the counts that we incremented.
+ //
+
+ if ((Scb == Vcb->VolumeDasdScb) ||
+ (Scb == Vcb->BadClusterFileScb)) {
+
+ Scb->FileObject = NULL;
+
+ NtfsDecrementCloseCounts( IrpContext,
+ Scb,
+ NULL,
+ TRUE,
+ FALSE,
+ FALSE );
+
+ //
+ // Dereference the file object in the Scb unless it is the one in
+ // the Vcb for the Log File. This routine may not be able to
+ // dereference file object because of synchronization problems (there
+ // can be a lazy writer callback in process which owns the paging
+ // io resource). In that case we don't want to go back to the beginning
+ // of Fcb table or we will loop indefinitely.
+ //
+
+ } else if (Scb->FileObject != Vcb->LogFileObject) {
+
+ Restart = NtfsDeleteInternalAttributeStream( Scb, TRUE );
+
+ //
+ // This is the file object for the Log file. Remove our
+ // extra reference on the logfile Scb.
+ //
+
+ } else if (Scb->FileObject != NULL) {
+
+ //
+ // Remember the log file object so we can defer the dereference.
+ //
+
+ NtfsDecrementCloseCounts( IrpContext,
+ Vcb->LogFileScb,
+ NULL,
+ TRUE,
+ FALSE,
+ TRUE );
+
+ Scb->FileObject = NULL;
+ }
+
+ if (Restart) {
+
+ if (Scb == Vcb->MftScb) { Vcb->MftScb = NULL; }
+ if (Scb == Vcb->Mft2Scb) { Vcb->Mft2Scb = NULL; }
+ if (Scb == Vcb->LogFileScb) { Vcb->LogFileScb = NULL; }
+ if (Scb == Vcb->VolumeDasdScb) { Vcb->VolumeDasdScb = NULL; }
+ if (Scb == Vcb->AttributeDefTableScb) { Vcb->AttributeDefTableScb = NULL; }
+ if (Scb == Vcb->UpcaseTableScb) { Vcb->UpcaseTableScb = NULL; }
+ if (Scb == Vcb->RootIndexScb) { Vcb->RootIndexScb = NULL; }
+ if (Scb == Vcb->BitmapScb) { Vcb->BitmapScb = NULL; }
+ if (Scb == Vcb->BadClusterFileScb) { Vcb->BadClusterFileScb = NULL; }
+ if (Scb == Vcb->QuotaTableScb) { Vcb->QuotaTableScb = NULL; }
+ if (Scb == Vcb->MftBitmapScb) { Vcb->MftBitmapScb = NULL; }
+
+#if defined (_CAIRO_)
+ if (Scb == Vcb->SecurityIdIndex) { Vcb->SecurityIdIndex = NULL; }
+ if (Scb == Vcb->SecurityDescriptorHashIndex)
+ { Vcb->SecurityDescriptorHashIndex = NULL; }
+ if (Scb == Vcb->SecurityDescriptorStream)
+ { Vcb->SecurityDescriptorStream = NULL; }
+#endif // defined (_CAIRO_)
+
+ //
+ // Now zero out the enumerations so we will start all over again because
+ // our call to Delete Internal Attribute Stream just messed up our
+ // enumeration.
+ //
+
+ Scb = NULL;
+ Fcb = NULL;
+ RestartKey = NULL;
+ }
+ }
+ }
+ }
+
+ //
+ // Mark the volume as not mounted.
+ //
+
+ ClearFlag( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED );
+
+ //
+ // Now only really dismount the volume if that's what our caller wants
+ //
+
+ if (DoCompleteDismount) {
+
+ PREVENT_MEDIA_REMOVAL Prevent;
+ KIRQL SavedIrql;
+
+ //
+ // Attempt to unlock any removable media, ignoring status.
+ //
+
+ Prevent.PreventMediaRemoval = FALSE;
+ (PVOID)NtfsDeviceIoControl( IrpContext,
+ Vcb->TargetDeviceObject,
+ IOCTL_DISK_MEDIA_REMOVAL,
+ &Prevent,
+ sizeof(PREVENT_MEDIA_REMOVAL),
+ NULL,
+ 0 );
+
+ //
+ // Remove this voldo from the mounted disk structures
+ //
+ // Assume we will need a new Vpb. Deallocate it later if not needed.
+ //
+
+ NewVpb = NtfsAllocatePoolWithTag( NonPagedPoolMustSucceed, sizeof( VPB ), 'VftN' );
+ RtlZeroMemory( NewVpb, sizeof( VPB ) );
+
+ IoAcquireVpbSpinLock( &SavedIrql );
+
+ //
+ // If there are no file objects and no reference counts in the
+ // Vpb then we can use the existing Vpb.
+ //
+
+ if ((Vcb->CloseCount == 0) &&
+ (Vcb->Vpb->ReferenceCount == 0)) {
+
+ Vcb->Vpb->DeviceObject = NULL;
+ ClearFlag( Vcb->Vpb->Flags, VPB_MOUNTED );
+
+ //
+ // Otherwise we will swap out the Vpb.
+ //
+
+ } else {
+
+ NewVpb->Type = IO_TYPE_VPB;
+ NewVpb->Size = sizeof( VPB );
+ NewVpb->RealDevice = Vcb->Vpb->RealDevice;
+ Vcb->Vpb->RealDevice->Vpb = NewVpb;
+
+ SetFlag( Vcb->VcbState, VCB_STATE_TEMP_VPB );
+
+ NewVpb = NULL;
+ }
+
+ IoReleaseVpbSpinLock( SavedIrql );
+
+ SetFlag( Vcb->VcbState, VCB_STATE_PERFORMED_DISMOUNT );
+
+ //
+ // Free the temp Vpb if not used.
+ //
+
+ if (NewVpb) {
+
+ NtfsFreePool( NewVpb );
+ }
+ }
+
+ } finally {
+
+ NtfsReleaseAllFiles( IrpContext, Vcb, FALSE );
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsPerformDismountOnVcb -> VOID\n") );
+ }
+
+ return;
+}
+
+
+BOOLEAN
+NtfsPingVolume (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine will ping the volume to see if the device needs to
+ be verified. It is used for create operations to see if the
+ create should proceed or if we should complete the create Irp
+ with a remount status.
+
+Arguments:
+
+ Vcb - Supplies the Vcb being pinged
+
+Return Value:
+
+ BOOLEAN - TRUE if the volume is fine and the operation should
+ proceed and FALSE if the volume needs to be verified
+
+--*/
+
+{
+ BOOLEAN Results;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsPingVolume, Vcb = %08lx\n", Vcb) );
+
+ //
+ // If the media is removable and the verify volume flag in the
+ // device object is not set then we want to ping the device
+ // to see if it needs to be verified.
+ //
+ // Note that we only force this ping for create operations.
+ // For others we take a sporting chance. If in the end we
+ // have to physically access the disk, the right thing will happen.
+ //
+
+ if ( FlagOn(Vcb->VcbState, VCB_STATE_REMOVABLE_MEDIA) &&
+ !FlagOn(Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME) ) {
+
+ PIRP Irp;
+ KEVENT Event;
+ PDEVICE_OBJECT TargetDevice;
+ IO_STATUS_BLOCK Iosb;
+ NTSTATUS Status;
+ LONGLONG ThirtySeconds = (-10 * 1000 * 1000) * 30;
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+ TargetDevice = Vcb->TargetDeviceObject;
+
+ Irp = IoBuildDeviceIoControlRequest( IOCTL_DISK_CHECK_VERIFY,
+ TargetDevice,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ FALSE,
+ &Event,
+ &Iosb );
+
+ if (Irp == NULL) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ Status = IoCallDriver( TargetDevice, Irp );
+
+
+ if (Status == STATUS_PENDING) {
+
+ Status = KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)&ThirtySeconds );
+
+ ASSERT( Status == STATUS_SUCCESS );
+
+ if ( !NT_SUCCESS(Iosb.Status) ) {
+
+ NtfsRaiseStatus( IrpContext, Iosb.Status, NULL, NULL );
+ }
+
+ } else {
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ NtfsRaiseStatus( IrpContext, Status, NULL, NULL );
+ }
+ }
+ }
+
+ if (FlagOn( Vcb->Vpb->RealDevice->Flags, DO_VERIFY_VOLUME)) {
+
+ Results = FALSE;
+
+ } else {
+
+ Results = TRUE;
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsPingVolume -> %08lx\n", Results) );
+
+ return Results;
+}
+
+
+VOID
+NtfsVolumeCheckpointDpc (
+ IN PKDPC Dpc,
+ IN PVOID DeferredContext,
+ IN PVOID SystemArgument1,
+ IN PVOID SystemArgument2
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is dispatched every 5 seconds when disk structure is being
+ modified. It had the ExWorker thread to volume checkpoints.
+
+Arguments:
+
+ DeferredContext - Not Used
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ TIMER_STATUS TimerStatus;
+
+ UNREFERENCED_PARAMETER( SystemArgument1 );
+ UNREFERENCED_PARAMETER( SystemArgument2 );
+ UNREFERENCED_PARAMETER( DeferredContext );
+ UNREFERENCED_PARAMETER( Dpc );
+
+ //
+ // Atomic reset of status indicating the timer is currently fired. This
+ // synchronizes with NtfsSetDirtyBcb. After NtfsSetDirtyBcb dirties
+ // a Bcb, it sees if it should enable this timer routine.
+ //
+ // If the status indicates that a timer is active, it does nothing. In this
+ // case it is guaranteed that when the timer fires, it causes a checkpoint (to
+ // force out the dirty Bcb data).
+ //
+ // If there is no timer active, it enables it, thus queueing a checkpoint later.
+ //
+ // If the timer routine actually fires between the dirtying of the Bcb and the
+ // testing of the status then a single extra checkpoint is generated. This
+ // extra checkpoint is not considered harmful.
+ //
+
+ //
+ // Atomically reset status and get previous value
+ //
+
+ TimerStatus = InterlockedExchange( &NtfsData.TimerStatus, TIMER_NOT_SET );
+
+ //
+ // We have only one instance of the work queue item. It can only be
+ // queued once. In a slow system, this checkpoint item may not be processed
+ // by the time this timer routine fires again.
+ //
+
+ if (NtfsData.VolumeCheckpointItem.List.Flink == NULL) {
+ ExQueueWorkItem( &NtfsData.VolumeCheckpointItem, CriticalWorkQueue );
+ }
+}
+
+
+VOID
+NtfsCheckpointAllVolumes (
+ PVOID Parameter
+ )
+
+/*++
+
+Routine Description:
+
+ This routine searches all of the vcbs for Ntfs and tries to clean
+ them. If the vcb is good and dirty but not almost clean then
+ we set it almost clean. If the Vcb is good and dirty and almost clean
+ then we clean it.
+
+Arguments:
+
+ Parameter - Not Used.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ IRP_CONTEXT LocalIrpContext;
+ IRP LocalIrp;
+
+ PIRP_CONTEXT IrpContext;
+
+ PLIST_ENTRY Links;
+ PVCB Vcb;
+
+ BOOLEAN AcquiredGlobal = FALSE;
+ BOOLEAN StartTimer = FALSE;
+
+ TIMER_STATUS TimerStatus;
+
+ UNREFERENCED_PARAMETER( Parameter );
+
+ PAGED_CODE();
+
+ RtlZeroMemory( &LocalIrpContext, sizeof(LocalIrpContext) );
+ RtlZeroMemory( &LocalIrp, sizeof(LocalIrp) );
+
+ IrpContext = &LocalIrpContext;
+ IrpContext->NodeTypeCode = NTFS_NTC_IRP_CONTEXT;
+ IrpContext->NodeByteSize = sizeof(IRP_CONTEXT);
+ IrpContext->OriginatingIrp = &LocalIrp;
+ SetFlag(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT);
+ InitializeListHead( &IrpContext->ExclusiveFcbList );
+
+ //
+ // Make sure we don't get any pop-ups
+ //
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, TRUE, FALSE );
+ ASSERT( ThreadTopLevelContext == &TopLevelContext );
+
+ (VOID) ExAcquireResourceShared( &NtfsData.Resource, TRUE );
+ AcquiredGlobal = TRUE;
+
+ try {
+
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ try {
+
+ for (Links = NtfsData.VcbQueue.Flink;
+ Links != &NtfsData.VcbQueue;
+ Links = Links->Flink) {
+
+ Vcb = CONTAINING_RECORD(Links, VCB, VcbLinks);
+
+ IrpContext->Vcb = Vcb;
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ NtfsCheckpointVolume( IrpContext, Vcb, FALSE, FALSE, TRUE, Li0 );
+
+ //
+ // Check to see whether this was not a clean checkpoint.
+ //
+
+ if (!FlagOn( Vcb->CheckpointFlags, VCB_LAST_CHECKPOINT_CLEAN )) {
+
+ StartTimer = TRUE;
+ }
+
+ NtfsCommitCurrentTransaction( IrpContext );
+#ifdef _CAIRO_
+ if (NtfsCheckQuota && Vcb->QuotaTableScb != NULL) {
+ NtfsPostRepairQuotaIndex( IrpContext, Vcb );
+ }
+#endif // _CAIRO_
+
+ }
+ }
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NOTHING;
+ }
+
+ //
+ // Synchronize with the checkpoint timer and other instances of this routine.
+ //
+ // Perform an interlocked exchange to indicate that a timer is being set.
+ //
+ // If the previous value indicates that no timer was set, then we
+ // enable the volume checkpoint timer. This will guarantee that a checkpoint
+ // will occur to flush out the dirty Bcb data.
+ //
+ // If the timer was set previously, then it is guaranteed that a checkpoint
+ // will occur without this routine having to reenable the timer.
+ //
+ // If the timer and checkpoint occurred between the dirtying of the Bcb and
+ // the setting of the timer status, then we will be queueing a single extra
+ // checkpoint on a clean volume. This is not considered harmful.
+ //
+
+ //
+ // Atomically set the timer status to indicate a timer is being set and
+ // retrieve the previous value.
+ //
+
+ if (StartTimer) {
+
+ TimerStatus = InterlockedExchange( &NtfsData.TimerStatus, TIMER_SET );
+
+ //
+ // If the timer is not currently set then we must start the checkpoint timer
+ // to make sure the above dirtying is flushed out.
+ //
+
+ if (TimerStatus == TIMER_NOT_SET) {
+
+ LONGLONG FiveSecondsFromNow = -5*1000*1000*10;
+
+ KeSetTimer( &NtfsData.VolumeCheckpointTimer,
+ *(PLARGE_INTEGER) &FiveSecondsFromNow,
+ &NtfsData.VolumeCheckpointDpc );
+ }
+ }
+
+ } finally {
+
+ if (AcquiredGlobal) {
+ ExReleaseResource( &NtfsData.Resource );
+ }
+
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+VOID
+NtfsVerifyOperationIsLegal (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine determines is the requested operation should be allowed to
+ continue. It either returns to the user if the request is Okay, or
+ raises an appropriate status.
+
+Arguments:
+
+ Irp - Supplies the Irp to check
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PFILE_OBJECT FileObject;
+ PIO_STACK_LOCATION IrpSp;
+
+ PAGED_CODE();
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+ FileObject = IrpSp->FileObject;
+
+ //
+ // We may be called during a clean all volumes operation. In this case
+ // we have a dummy Irp that we created. If the Irp stack
+ // count is zero then there is nothing to do here.
+ //
+
+ if (Irp->StackCount == 0) {
+
+ return;
+ }
+
+ //
+ // If there is not a file object, we cannot continue.
+ //
+
+ if (FileObject == NULL) {
+
+ return;
+ }
+
+ //
+ // If we are trying to do any other operation than close on a file
+ // object marked for delete, raise STATUS_DELETE_PENDING.
+ //
+
+ if (FileObject->DeletePending == TRUE
+ && IrpContext->MajorFunction != IRP_MJ_CLEANUP
+ && IrpContext->MajorFunction != IRP_MJ_CLOSE ) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DELETE_PENDING, NULL, NULL );
+ }
+
+ //
+ // If we are doing a create, and there is a related file objects, and
+ // it it is marked for delete, raise STATUS_DELETE_PENDING.
+ //
+
+ if (IrpContext->MajorFunction == IRP_MJ_CREATE) {
+
+ PFILE_OBJECT RelatedFileObject;
+
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ RelatedFileObject = FileObject->RelatedFileObject;
+
+ NtfsDecodeFileObject( IrpContext,
+ RelatedFileObject,
+ &Vcb,
+ &Fcb,
+ &Scb,
+ &Ccb,
+ TRUE );
+
+ if (RelatedFileObject != NULL
+ && Fcb->LinkCount == 0) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DELETE_PENDING, NULL, NULL );
+ }
+ }
+
+ //
+ // If the file object has already been cleaned up, and
+ //
+ // A) This request is a paging io read or write, or
+ // B) This request is a close operation, or
+ // C) This request is a set or query info call (for Lou)
+ // D) This is an MDL complete
+ //
+ // let it pass, otherwise return STATUS_FILE_CLOSED.
+ //
+
+ if ( FlagOn(FileObject->Flags, FO_CLEANUP_COMPLETE) ) {
+
+ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ if ( (FlagOn(Irp->Flags, IRP_PAGING_IO)) ||
+ (IrpSp->MajorFunction == IRP_MJ_CLOSE ) ||
+ (IrpSp->MajorFunction == IRP_MJ_SET_INFORMATION) ||
+ (IrpSp->MajorFunction == IRP_MJ_QUERY_INFORMATION) ||
+ ( ( (IrpSp->MajorFunction == IRP_MJ_READ) ||
+ (IrpSp->MajorFunction == IRP_MJ_WRITE) ) &&
+ FlagOn(IrpSp->MinorFunction, IRP_MN_COMPLETE) ) ) {
+
+ NOTHING;
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CLOSED, NULL, NULL );
+ }
+ }
+
+ return;
+}
+
+
+//
+// Local Support routine
+//
+
+VOID
+NtfsPerformVerifyDiskRead (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PVOID Buffer,
+ IN LONGLONG Offset,
+ IN ULONG NumberOfBytesToRead
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to read in a range of bytes from the disk. It
+ bypasses all of the caching and regular I/O logic, and builds and issues
+ the requests itself. It does this operation overriding the verify
+ volume flag in the device object.
+
+Arguments:
+
+ Vcb - Supplies the Vcb denoting the device for this operation
+
+ Buffer - Supplies the buffer that will recieve the results of this operation
+
+ Offset - Supplies the offset of where to start reading
+
+ NumberOfBytesToRead - Supplies the number of bytes to read, this must
+ be in multiple of bytes units acceptable to the disk driver.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ KEVENT Event;
+ PIRP Irp;
+ NTSTATUS Status;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ //
+ // Initialize the event we're going to use
+ //
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+
+ //
+ // Build the irp for the operation and also set the overrride flag
+ //
+ // Note that we may be at APC level, so do this asyncrhonously and
+ // use an event for synchronization normal request completion
+ // cannot occur at APC level.
+ //
+
+ Irp = IoBuildAsynchronousFsdRequest( IRP_MJ_READ,
+ Vcb->TargetDeviceObject,
+ Buffer,
+ NumberOfBytesToRead,
+ (PLARGE_INTEGER)&Offset,
+ NULL );
+
+ if ( Irp == NULL ) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ SetFlag( IoGetNextIrpStackLocation( Irp )->Flags, SL_OVERRIDE_VERIFY_VOLUME );
+
+ //
+ // Set up the completion routine
+ //
+
+ IoSetCompletionRoutine( Irp,
+ NtfsVerifyReadCompletionRoutine,
+ &Event,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Call the device to do the write and wait for it to finish.
+ //
+
+ try {
+
+ (VOID)IoCallDriver( Vcb->TargetDeviceObject, Irp );
+ (VOID)KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL );
+
+ //
+ // Grab the Status.
+ //
+
+ Status = Irp->IoStatus.Status;
+
+ } finally {
+
+ //
+ // If there is an MDL (or MDLs) associated with this I/O
+ // request, Free it (them) here. This is accomplished by
+ // walking the MDL list hanging off of the IRP and deallocating
+ // each MDL encountered.
+ //
+
+ while (Irp->MdlAddress != NULL) {
+
+ PMDL NextMdl;
+
+ NextMdl = Irp->MdlAddress->Next;
+
+ MmUnlockPages( Irp->MdlAddress );
+
+ IoFreeMdl( Irp->MdlAddress );
+
+ Irp->MdlAddress = NextMdl;
+ }
+
+ IoFreeIrp( Irp );
+ }
+
+ //
+ // If it doesn't succeed then raise the error
+ //
+
+ if (!NT_SUCCESS(Status)) {
+
+ NtfsNormalizeAndRaiseStatus( IrpContext,
+ Status,
+ STATUS_UNEXPECTED_IO_ERROR );
+ }
+
+ //
+ // And return to our caller
+ //
+
+ return;
+}
+
+
+NTSTATUS
+NtfsIoCallSelf (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFILE_OBJECT FileObject,
+ IN UCHAR MajorFunction
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to call ourselves for a simple function. Note that
+ if more use is found for this routine than the few current uses, its interface
+ may be easily expanded.
+
+Arguments:
+
+ FileObject - FileObject for request.
+
+ MajorFunction - function to be performed.
+
+Return Value:
+
+ Status code resulting from the driver call
+
+--*/
+
+{
+ KEVENT Event;
+ PIRP Irp;
+ PDEVICE_OBJECT DeviceObject;
+ PIO_STACK_LOCATION IrpSp;
+ NTSTATUS Status;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ //
+ // Initialize the event we're going to use
+ //
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+ DeviceObject = IoGetRelatedDeviceObject( FileObject );
+
+ //
+ // Reference the file object here so that no special checks need be made
+ // in I/O completion to determine whether or not to dereference the file
+ // object.
+ //
+
+ if (MajorFunction != IRP_MJ_CLOSE) {
+ ObReferenceObject( FileObject );
+ }
+
+ //
+ // Build the irp for the operation and also set the overrride flag
+ //
+ // Note that we may be at APC level, so do this asyncrhonously and
+ // use an event for synchronization normal request completion
+ // cannot occur at APC level.
+ //
+
+
+ Irp = IoBuildAsynchronousFsdRequest( IRP_MJ_SHUTDOWN,
+ DeviceObject,
+ NULL,
+ 0,
+ NULL,
+ NULL );
+
+ if (Irp == NULL) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ //
+ // Fill in a few remaining items
+ //
+
+ Irp->Tail.Overlay.OriginalFileObject = FileObject;
+
+ IrpSp = IoGetNextIrpStackLocation(Irp);
+ IrpSp->MajorFunction = MajorFunction;
+ IrpSp->FileObject = FileObject;
+
+ //
+ // Set up the completion routine
+ //
+
+ IoSetCompletionRoutine( Irp,
+ NtfsVerifyReadCompletionRoutine,
+ &Event,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Call the device to do the write and wait for it to finish.
+ //
+
+ try {
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+ (VOID)KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL );
+
+ //
+ // Grab the Status.
+ //
+
+ Status = Irp->IoStatus.Status;
+
+ } finally {
+
+ //
+ // There should never be an MDL here.
+ //
+
+ ASSERT(Irp->MdlAddress == NULL);
+
+ IoFreeIrp( Irp );
+ }
+
+ //
+ // If it doesn't succeed then raise the error
+ //
+ // And return to our caller
+ //
+
+ return Status;
+}
+
+BOOLEAN
+NtfsLogEvent (
+ IN PIRP_CONTEXT IrpContext,
+ IN PQUOTA_USER_DATA UserData OPTIONAL,
+ IN NTSTATUS LogCode,
+ IN NTSTATUS FinalStatus
+ )
+
+/*++
+
+Routine Description:
+
+ This routine logs an io event. If UserData is supplied then the
+ data logged is a FILE_QUOTA_INFORMATION structure; otherwise the
+ volume label is included.
+
+Arguments:
+
+ UserData - Supplies the optional quota user data index entry.
+
+ LogCode - Supplies the Io Log code to use for the ErrorCode field.
+
+ FinalStauts - Supplies the final status of the operation.
+
+Return Value:
+
+ True - if the event was successfully logged.
+
+--*/
+
+{
+ PIO_ERROR_LOG_PACKET ErrorLogEntry;
+ PFILE_QUOTA_INFORMATION FileQuotaInfo;
+ ULONG SidLength;
+ ULONG DumpDataLength = 0;
+ ULONG LabelLength = 0;
+
+ if (IrpContext->Vcb == NULL) {
+ return FALSE;
+ }
+
+ if (UserData == NULL) {
+
+ LabelLength = IrpContext->Vcb->Vpb->VolumeLabelLength + sizeof(WCHAR);
+
+#ifdef _CAIRO_
+
+ } else {
+
+ //
+ // Calculate the required length of the Sid.
+ //
+
+ SidLength = RtlLengthSid( &UserData->QuotaSid );
+
+ DumpDataLength = SidLength +
+ FIELD_OFFSET( FILE_QUOTA_INFORMATION, Sid );
+
+#endif // _CAIRO_
+
+ }
+
+ ErrorLogEntry = (PIO_ERROR_LOG_PACKET)
+ IoAllocateErrorLogEntry( IrpContext->Vcb->TargetDeviceObject,
+ (UCHAR) (DumpDataLength +
+ LabelLength +
+ sizeof(IO_ERROR_LOG_PACKET)) );
+
+ if (ErrorLogEntry == NULL) {
+ return FALSE;
+ }
+
+ ErrorLogEntry->ErrorCode = LogCode;
+ ErrorLogEntry->FinalStatus = FinalStatus;
+
+ ErrorLogEntry->SequenceNumber = IrpContext->TransactionId;
+ ErrorLogEntry->MajorFunctionCode = IrpContext->MajorFunction;
+ ErrorLogEntry->RetryCount = 0;
+ ErrorLogEntry->DumpDataSize = (USHORT) DumpDataLength;
+
+ if (UserData == NULL) {
+
+ PWCHAR String;
+
+ //
+ // The label string at the end of the error log entry.
+ //
+
+ String = (PWCHAR) (ErrorLogEntry + 1);
+
+ ErrorLogEntry->NumberOfStrings = 1;
+ ErrorLogEntry->StringOffset = sizeof( IO_ERROR_LOG_PACKET );
+ RtlCopyMemory( String,
+ IrpContext->Vcb->Vpb->VolumeLabel,
+ IrpContext->Vcb->Vpb->VolumeLabelLength );
+
+ //
+ // Make sure the string is null terminated.
+ //
+
+ String += IrpContext->Vcb->Vpb->VolumeLabelLength / sizeof( WCHAR );
+
+ *String = L'\0';
+
+#ifdef _CAIRO_
+
+ } else {
+
+ FileQuotaInfo = (PFILE_QUOTA_INFORMATION) ErrorLogEntry->DumpData;
+
+ FileQuotaInfo->NextEntryOffset = 0;
+ FileQuotaInfo->SidLength = SidLength;
+ FileQuotaInfo->ChangeTime.QuadPart = UserData->QuotaChangeTime;
+ FileQuotaInfo->QuotaUsed.QuadPart = UserData->QuotaUsed;
+ FileQuotaInfo->QuotaThreshold.QuadPart = UserData->QuotaThreshold;
+ FileQuotaInfo->QuotaLimit.QuadPart = UserData->QuotaLimit;
+ RtlCopyMemory( &FileQuotaInfo->Sid,
+ &UserData->QuotaSid,
+ SidLength );
+
+#endif // _CAIRO
+
+ }
+
+ IoWriteErrorLogEntry( ErrorLogEntry );
+
+ return TRUE;
+}
+
+
+VOID
+NtfsPostVcbIsCorrupt (
+ IN PIRP_CONTEXT IrpContext,
+ IN NTSTATUS Status OPTIONAL,
+ IN PFILE_REFERENCE FileReference OPTIONAL,
+ IN PFCB Fcb OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to mark the volume dirty and possibly raise a hard error.
+
+Arguments:
+
+ Status - If not zero, then this is the error code for the popup.
+
+ FileReference - If specified, then this is the file reference for the corrupt file.
+
+ Fcb - If specified, then this is the Fcb for the corrupt file.
+
+Return Value:
+
+ None
+
+--*/
+{
+ PVCB Vcb = IrpContext->Vcb;
+
+#ifdef NTFS_CORRUPT
+{
+ KdPrint(("NTFS: Post Vcb is corrupt\n", 0));
+ DbgBreakPoint();
+}
+#endif
+
+ //
+ // Set this flag to keep the volume from ever getting set clean.
+ //
+
+ if (Vcb != NULL) {
+
+ NtfsMarkVolumeDirty( IrpContext, Vcb );
+
+ //
+ // This would be the appropriate place to raise a hard error popup,
+ // ala the code in FastFat. We should do it after marking the volume
+ // dirty so that if anything goes wrong with the popup, the volume is
+ // already marked anyway.
+ //
+
+ if (Status != 0) {
+
+ NtfsRaiseInformationHardError( IrpContext,
+ Status,
+ FileReference,
+ Fcb );
+ }
+ }
+}
+
+
+VOID
+NtfsMarkVolumeDirty (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called any time the Mft is open to mark the volume
+ dirty.
+
+Arguments:
+
+ Vcb - Vcb for volume to mark dirty
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+ PVOLUME_INFORMATION VolumeInformation;
+
+ PAGED_CODE();
+
+#if DBG
+ KdPrint(("NTFS: Marking volume dirty, Vcb: %08lx\n", Vcb));
+#endif
+
+#ifdef NTFS_CORRUPT
+{
+ KdPrint(("NTFS: Marking volume dirty\n", 0));
+ DbgBreakPoint();
+}
+#endif
+
+ // if (FlagOn(*(PULONG)NtGlobalFlag, 0x00080000)) {
+ // KdPrint(("NTFS: marking volume dirty\n", 0));
+ // DbgBreakPoint();
+ // }
+
+ //
+ // Return if the volume is already marked dirty. This also prevents
+ // endless recursion if the volume file itself is corrupt.
+ //
+
+ if (FlagOn(Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED_DIRTY)) {
+ return;
+ }
+
+ SetFlag(Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED_DIRTY);
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ try {
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $VOLUME_INFORMATION,
+ &AttributeContext )) {
+
+ VolumeInformation =
+ (PVOLUME_INFORMATION)NtfsAttributeValue( NtfsFoundAttribute( &AttributeContext ));
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, &AttributeContext );
+
+ SetFlag(VolumeInformation->VolumeFlags, VOLUME_DIRTY);
+
+ CcSetDirtyPinnedData( NtfsFoundBcb(&AttributeContext), NULL );
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ NtfsLogEvent( IrpContext,
+ NULL,
+ IO_FILE_SYSTEM_CORRUPT,
+ STATUS_DISK_CORRUPT_ERROR );
+}
+
+
+VOID
+NtfsUpdateVersionNumber (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN UCHAR MajorVersion,
+ IN UCHAR MinorVersion
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to update the version number on disk, without
+ going through the logging package.
+
+Arguments:
+
+ Vcb - Vcb for volume.
+
+ MajorVersion - This is the Major Version number to write out.
+
+ MinorVersion - This is the Minor Version number to write out.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+ PVOLUME_INFORMATION VolumeInformation;
+
+ PAGED_CODE();
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ try {
+
+ //
+ // Lookup the volume information attribute.
+ //
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $VOLUME_INFORMATION,
+ &AttributeContext )) {
+
+ VolumeInformation =
+ (PVOLUME_INFORMATION)NtfsAttributeValue( NtfsFoundAttribute( &AttributeContext ));
+
+ NtfsPinMappedAttribute( IrpContext, Vcb, &AttributeContext );
+
+ //
+ // Now update the version number fields.
+ //
+
+ VolumeInformation->MajorVersion = MajorVersion;
+ VolumeInformation->MinorVersion = MinorVersion;
+
+ CcSetDirtyPinnedData( NtfsFoundBcb(&AttributeContext), NULL );
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+
+ //
+ // Now flush it out, so the new version numbers will be present on
+ // the next mount.
+ //
+
+ CcFlushCache( Vcb->MftScb->FileObject->SectionObjectPointer,
+ (PLARGE_INTEGER)&AttributeContext.FoundAttribute.MftFileOffset,
+ Vcb->BytesPerFileRecordSegment,
+ NULL );
+ }
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+}
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsVerifyReadCompletionRoutine(
+ IN PDEVICE_OBJECT DeviceObject,
+ IN PIRP Irp,
+ IN PVOID Contxt
+ )
+
+{
+ //
+ // Set the event so that our call will wake up.
+ //
+
+ KeSetEvent( (PKEVENT)Contxt, 0, FALSE );
+
+ UNREFERENCED_PARAMETER( DeviceObject );
+ UNREFERENCED_PARAMETER( Irp );
+
+ return STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+//
+// Local support routine
+//
+
+NTSTATUS
+NtfsDeviceIoControlAsync (
+ IN PIRP_CONTEXT IrpContext,
+ IN PDEVICE_OBJECT DeviceObject,
+ IN ULONG IoCtl
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is used to perform an IoCtl when we may be at the APC level
+ and calling NtfsDeviceIoControl could be unsafe.
+
+Arguments:
+
+ DeviceObject - Supplies the device object to which to send the ioctl.
+
+ IoCtl - Supplies the I/O control code.
+
+Return Value:
+
+ Status.
+
+--*/
+
+{
+ KEVENT Event;
+ PIRP Irp;
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ //
+ // We'll be leaving the event on the stack, so let's be sure
+ // that we've done an FsRtlEnterFileSystem before we got here.
+ // I claim that we're never going to end up here in the fast
+ // io path, but let's make certain.
+ //
+
+ ASSERTMSG( "Didn't FsRtlEnterFileSystem", KeGetCurrentThread()->KernelApcDisable != 0 );
+
+ //
+ // Initialize the event we're going to use
+ //
+
+ KeInitializeEvent( &Event, NotificationEvent, FALSE );
+
+ //
+ // Build the irp for the operation and also set the overrride flag
+ //
+ // Note that we may be at APC level, so do this asyncrhonously and
+ // use an event for synchronization normal request completion
+ // cannot occur at APC level.
+ //
+
+ // **** Change this irp_mj_ back after Jeff or Peter changes io so
+ // **** that we can pass in the right irp_mj_ and no buffers.
+
+ Irp = IoBuildAsynchronousFsdRequest( IRP_MJ_FLUSH_BUFFERS, // **** IRP_MJ_DEVICE_CONTROL,
+ DeviceObject,
+ NULL,
+ 0,
+ NULL,
+ NULL );
+
+ if ( Irp == NULL ) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES, NULL, NULL );
+ }
+
+ IrpSp = IoGetNextIrpStackLocation( Irp );
+ SetFlag( IrpSp->Flags, SL_OVERRIDE_VERIFY_VOLUME );
+
+ IrpSp->Parameters.DeviceIoControl.IoControlCode = IoCtl;
+
+ // **** Remove this, too.
+
+ IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;
+
+ //
+ // Set up the completion routine.
+ //
+
+ IoSetCompletionRoutine( Irp,
+ NtfsVerifyReadCompletionRoutine,
+ &Event,
+ TRUE,
+ TRUE,
+ TRUE );
+
+ //
+ // Call the device to do the io and wait for it to finish.
+ //
+
+ (VOID)IoCallDriver( DeviceObject, Irp );
+ (VOID)KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL );
+
+ //
+ // Grab the Status.
+ //
+
+ Status = Irp->IoStatus.Status;
+
+ IoFreeIrp( Irp );
+
+ //
+ // And return to our caller.
+ //
+
+ return Status;
+}
+
+
diff --git a/private/ntos/cntfs/views/check.c b/private/ntos/cntfs/views/check.c
new file mode 100644
index 000000000..c07b71acd
--- /dev/null
+++ b/private/ntos/cntfs/views/check.c
@@ -0,0 +1,282 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ check.c
+
+Abstract:
+
+ This module contains the property set check support
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+
+VOID
+CheckPropertySet (
+ IN PPROPERTY_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs validates the syntax of the property set
+ stream.
+
+Arguments:
+
+ Context - context of call
+
+Return Value:
+
+ Nothing. May raise if object is corrupt.
+
+--*/
+
+{
+ PPROPERTY_SET_HEADER Header = Context->Header;
+ PPROPERTY_ID_TABLE IdTable = Context->IdTable;
+ PPROPERTY_HEAP_HEADER HeapHeader = Context->HeapHeader;
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+ ULONG Length = (ULONG) Context->Attribute->Header.FileSize.QuadPart ;
+ ULONG i;
+
+ if (
+ //
+ // Verify initial length
+ //
+
+ (Length < sizeof( PROPERTY_SET_HEADER )
+ DebugDoit( && PROPASSERT( !"Not enough room for header" ))) ||
+
+ //
+ // Verify header of attribute. Check the signature and format stamp.
+ //
+
+ (Header->wByteOrder != 0xFFFE
+ DebugDoit( && PROPASSERT( !"Byte order invalid" ))) ||
+ (Header->wFormat != PSH_FORMAT_VERSION
+ DebugDoit( && PROPASSERT( !"Format version invalid" ))) ||
+ ((HIWORD( Header->dwOSVer ) > 2 ||
+ LOBYTE( LOWORD( Header->dwOSVer )) > 5)
+ DebugDoit( && PROPASSERT( !"dwOSVer invalid" ))) ||
+
+ //
+ // Verify offsets of table and heap are valid.
+ //
+
+ (Header->IdTableOffset >= Length
+ DebugDoit( && PROPASSERT( !"IdTable offset invalid" ))) ||
+ (Header->IdTableOffset != LongAlign( Header->IdTableOffset )
+ DebugDoit( && PROPASSERT( !"IdTable misaligned" ))) ||
+ (Header->ValueHeapOffset >= Length
+ DebugDoit( && PROPASSERT( !"ValueHeap offset invalid" ))) ||
+ (Header->ValueHeapOffset != LongAlign( Header->ValueHeapOffset )
+ DebugDoit( && PROPASSERT( !"ValueHeap misaligned" ))) ||
+
+ //
+ // Verify that the table fits below the value heap
+ //
+
+ (PROPERTY_ID_TABLE_SIZE( IdTable->MaximumPropertyCount ) >
+ Header->ValueHeapOffset - Header->IdTableOffset
+ DebugDoit( && PROPASSERT( !"IdTable overlaps ValueHeap" ))) ||
+
+ //
+ // Verify that the heap is within the stream
+ //
+
+ (Header->ValueHeapOffset + HeapHeader->PropertyHeapLength > Length
+ DebugDoit( && PROPASSERT( !"ValueHeap beyond end of stream" ))) ||
+
+ //
+ // Verify table counts are correct
+ //
+
+ (IdTable->PropertyCount > IdTable->MaximumPropertyCount
+ DebugDoit( && PROPASSERT( !"IdTable counts are incorrect" )))
+
+ ) {
+
+ NtfsRaiseStatus( Context->IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Context->Object );
+ }
+
+ for (i = 0; i < IdTable->PropertyCount; i++) {
+ PPROPERTY_TABLE_ENTRY Entry = (PPROPERTY_TABLE_ENTRY) &IdTable->Entry[i];
+ if (
+ //
+ // Verify sorting
+ //
+
+ (i > 1 && Entry[-1].PropertyId >= Entry[0].PropertyId
+ DebugDoit( && PROPASSERT( !"IdTable entry sort invalid" ))) ||
+ (i < IdTable->PropertyCount - 1 && Entry[0].PropertyId >= Entry[1].PropertyId
+ DebugDoit( && PROPASSERT( !"IdTable entry sort invalid 2" ))) ||
+
+ //
+ // Verify offset points within heap
+ //
+
+ (Entry[0].PropertyValueOffset >= HeapHeader->PropertyHeapLength
+ DebugDoit( && PROPASSERT( !"IdTable entry offset invalid" ))) ||
+
+ //
+ // Verify the back pointer matches
+ //
+
+ (Entry[0].PropertyId != GET_HEAP_ENTRY( HeapHeader, Entry[0].PropertyValueOffset )->PropertyId
+ DebugDoit( && PROPASSERT( !"Backpointer invalid" )))
+ ) {
+
+ NtfsRaiseStatus( Context->IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Context->Object );
+ }
+ }
+
+ HeapEntry = FIRST_HEAP_ENTRY( HeapHeader );
+ while (!IS_LAST_HEAP_ENTRY( HeapHeader, HeapEntry )) {
+
+ if (HeapEntry->PropertyId != PID_ILLEGAL) {
+ ULONG Index =
+ BinarySearchIdTable( Context, HeapEntry->PropertyId );
+
+ if (
+ //
+ // Verify length is aligned
+ //
+
+ (HeapEntry->PropertyValueLength != LongAlign( HeapEntry->PropertyValueLength )
+ DebugDoit( && PROPASSERT( !"Property length misaligned" ))) ||
+ //
+ // Verify backpointer works
+ //
+
+ (Index >= IdTable->PropertyCount
+ DebugDoit( && PROPASSERT( !"Backpointer after end of table" ))) ||
+
+ //
+ // Backpointer Id agrees
+ //
+
+ (IdTable->Entry[Index].PropertyId != HeapEntry->PropertyId
+ DebugDoit( && PROPASSERT( !"Backpointer not found in table" ))) ||
+
+ //
+ // Backpointer offset agrees
+ //
+
+ (IdTable->Entry[Index].PropertyValueOffset != HeapOffset( Context, HeapEntry)
+ DebugDoit( && PROPASSERT( !"Backpointer not found in table" ))) ||
+
+ //
+ // Name length is word aligned
+ //
+
+ (HeapEntry->PropertyNameLength != WordAlign( HeapEntry->PropertyNameLength )
+ DebugDoit( && PROPASSERT( !"Name is odd number of bytes" ))) ||
+
+ //
+ // Verify property is entirely in heap
+ //
+
+ (HeapOffset( Context, NEXT_HEAP_ENTRY( HeapEntry)) > HeapHeader->PropertyHeapLength
+ DebugDoit( && PROPASSERT( !"Property Value overlaps end of heap" )))
+
+ ) {
+
+ NtfsRaiseStatus( Context->IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Context->Object );
+ }
+ }
+
+ HeapEntry = NEXT_HEAP_ENTRY( HeapEntry );
+ }
+}
+
+
+VOID
+DumpPropertyData (
+ IN PPROPERTY_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs validates the syntax of the property set
+ stream.
+
+Arguments:
+
+ Context - context of call
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+
+ ULONG i;
+
+ //
+ // Map attribute for full size
+ //
+
+ MapPropertyContext( Context );
+
+ //
+ // Verify the property set has valid contents.
+ //
+
+ CheckPropertySet( Context );
+
+ //
+ // Dump out contents of property set
+ //
+
+ DebugTrace( 0, Dbg, ("Property set dump\n") );
+ DebugTrace( 0, Dbg, ("wByteOrder %04x wFormat %04x dwOSVer %08x\n",
+ Context->Header->wByteOrder,
+ Context->Header->wFormat,
+ Context->Header->dwOSVer) );
+ DebugTrace( 0, Dbg, ("IdTableOffset %08x ValueHeapOffset %08x\n",
+ Context->Header->IdTableOffset,
+ Context->Header->ValueHeapOffset) );
+
+ DebugTrace( 0, Dbg, ("IdTable %x/%x entries used\n",
+ Context->IdTable->PropertyCount,
+ Context->IdTable->MaximumPropertyCount) );
+ for (i = 0; i < Context->IdTable->PropertyCount; i++) {
+ DebugTrace( 0, Dbg, (" Entry[%d].PropertyId %08x .Header %08x\n",
+ i,
+ Context->IdTable->Entry[i].PropertyId,
+ Context->IdTable->Entry[i].PropertyValueOffset) );
+ }
+
+
+ DebugTrace( 0, Dbg, ("PropertyHeapLength %08x\n",
+ Context->HeapHeader->PropertyHeapLength) );
+
+ HeapEntry = FIRST_HEAP_ENTRY( Context->HeapHeader );
+ while (!IS_LAST_HEAP_ENTRY( Context->HeapHeader, HeapEntry )) {
+ DebugTrace( 0, Dbg, (" Heap[%08x].Length %08x .PropertyId %08x .PropertyNameLength %04x\n",
+ HeapOffset( Context, HeapEntry ),
+ HeapEntry->PropertyValueLength,
+ HeapEntry->PropertyId,
+ HeapEntry->PropertyNameLength) );
+ DebugTrace( 0, Dbg, (" .PropertyName '%.*ws'\n",
+ HeapEntry->PropertyNameLength / sizeof( WCHAR ),
+ HeapEntry->PropertyName) );
+ HeapEntry = NEXT_HEAP_ENTRY( HeapEntry );
+ }
+
+}
+
diff --git a/private/ntos/cntfs/views/driver.c b/private/ntos/cntfs/views/driver.c
new file mode 100644
index 000000000..f1b24b06c
--- /dev/null
+++ b/private/ntos/cntfs/views/driver.c
@@ -0,0 +1,287 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ driver.h
+
+Abstract:
+
+ This module contains the user FsCtls for the Ntfs Property support.
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#if defined( NTFSDBG ) || defined( DBG )
+LONG NtfsDebugTraceIndent = 0;
+LONG NtfsDebugTraceLevel = DEBUG_TRACE_PROP_FSCTL;
+#endif
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+//
+// DefaultEmptyProperty is used in place of any property that is truly not found
+//
+
+NOT_FOUND_PROPERTY DefaultEmptyProperty =
+ { sizeof( DefaultEmptyProperty ), // PropertyValueLength
+ PID_ILLEGAL, // PropertyId
+ 0, // PropertyNameLength
+ 0, // PropertyName[0] (alignment pad when name length == 0)
+ VT_EMPTY }; // dwType
+
+LONGLONG Views0 = 0i64;
+
+
+NTSTATUS
+ViewFileSystemControl (
+ IN PIRP_CONTEXT IrpContext,
+ IN OBJECT_HANDLE Object,
+ IN ATTRIBUTE_HANDLE Attribute,
+ IN ULONG FsControlCode,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT PULONG OutBufferLength,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine dispatches the file system control call to workers within
+ VIEWS. This routines does not complete or post the IRP; that function
+ is reserved for the FsCtl dispatcher in Ntfs.
+
+ Its job is to verify that the FsControlCode, the parameters and the buffers
+ are appropriate.
+
+Arguments:
+
+ IrpContext - IrpContext for the call
+
+ Object - FCB of the file where the View/Property call is directed
+ *
+ Attribute - SCB of the property set stream.
+
+ FsControlCode - Control code directing the action to take place
+
+ InBufferLength - Length of the command buffer
+
+ InBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally.
+
+ OutBufferLength - pointer to ULONG length of output buffer. This value
+ is set on return to indicate the total size of data within the output
+ buffer.
+
+ OutBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ PROPERTY_CONTEXT Context;
+
+ DebugTrace( 0, Dbg, ("ViewFileSystemControl: IrpContext %08x\n", IrpContext));
+ DebugTrace( 0, Dbg, (" Object %08x\n", Object));
+ DebugTrace( 0, Dbg, (" Attribute %08x\n", Attribute));
+ DebugTrace( 0, Dbg, (" FsControlCode %08x\n", FsControlCode));
+ DebugTrace( 0, Dbg, (" InBufferLength %08x\n", InBufferLength));
+ DebugTrace( 0, Dbg, (" InBuffer %08x\n", InBuffer));
+ DebugTrace( 0, Dbg, (" *OutBufferLength %08x\n", *OutBufferLength));
+ DebugTrace( 0, Dbg, (" OutBuffer %08x\n", OutBuffer));
+
+
+ //
+ // Acquire object according to r/w needs of worker
+ //
+
+ switch (FsControlCode) {
+ case FSCTL_READ_PROPERTY_DATA:
+ case FSCTL_DUMP_PROPERTY_DATA:
+ NtfsAcquireSharedScb( IrpContext, Attribute );
+ break;
+
+ case FSCTL_WRITE_PROPERTY_DATA:
+ case FSCTL_INITIALIZE_PROPERTY_DATA:
+ NtfsAcquireExclusiveScb( IrpContext, Attribute );
+ break;
+ default:
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ if (!FlagOn( Attribute->ScbState, SCB_STATE_MODIFIED_NO_WRITE )) {
+
+ DbgPrint( "Enabling NO_WRITE behaviour on stream\n" );
+ SetFlag( Attribute->ScbState, SCB_STATE_MODIFIED_NO_WRITE );
+
+ }
+
+
+ try {
+
+ //
+ // Make sure that the workers can map the stream
+ //
+
+ if (Attribute->FileObject == NULL) {
+ NtfsCreateInternalAttributeStream( IrpContext, Attribute, FALSE );
+ }
+
+ //
+ // Set up context for workers.
+ //
+
+ InitializePropertyContext( &Context, IrpContext, Object, Attribute );
+
+
+ //
+ // Verify that we have a property set to begin with
+ //
+
+ if (Attribute->AttributeTypeCode != $PROPERTY_SET) {
+ ExRaiseStatus( STATUS_PROPSET_NOT_FOUND );
+ }
+
+ switch (FsControlCode) {
+ case FSCTL_READ_PROPERTY_DATA:
+ ReadPropertyData( &Context,
+ InBufferLength, InBuffer,
+ OutBufferLength, OutBuffer );
+ break;
+
+ case FSCTL_WRITE_PROPERTY_DATA:
+ WritePropertyData( &Context,
+ InBufferLength, InBuffer,
+ OutBufferLength, OutBuffer );
+ break;
+
+ case FSCTL_INITIALIZE_PROPERTY_DATA:
+ InitializePropertyData( &Context );
+ break;
+
+ case FSCTL_DUMP_PROPERTY_DATA:
+ DumpPropertyData( &Context );
+ break;
+
+ }
+ } except ( EXCEPTION_EXECUTE_HANDLER ) {
+ //
+ // Remap the status code
+ //
+
+ Status = GetExceptionCode( );
+ if (Status == STATUS_ACCESS_VIOLATION) {
+ Status = STATUS_INVALID_USER_BUFFER;
+ }
+ }
+
+ //
+ // Clean up any mapped context
+ //
+
+ CleanupPropertyContext( &Context );
+
+ //
+ // Release Resource
+ //
+
+ NtfsReleaseScb( IrpContext, Attribute );
+
+ if (Status != STATUS_SUCCESS) {
+ ExRaiseStatus( Status );
+ }
+
+ DebugTrace( 0, Dbg, ("ViewFileSystemControl returned status %x, length %x\n", Status, *OutBufferLength) );
+
+ return STATUS_SUCCESS;
+}
+
+
+/*++
+
+Routine Description:
+
+ This is the unload routine for the Views/properties file
+ system device driver. This routine deregisters with ntfs.
+
+Arguments:
+
+ DriverObject - Pointer to driver object created by the system.
+
+Return Value:
+
+ None.
+
+--*/
+
+VOID
+DriverUnload (
+ IN struct _DRIVER_OBJECT *DriverObject
+ )
+{
+ NTSTATUS Status = NtOfsRegisterCallBacks( Views, NULL );
+
+ UNREFERENCED_PARAMETER( DriverObject );
+
+ DebugTrace( 0, Dbg, ("DriverUnload: NtOfsRegisterCallBacks returned %x\n", Status) );
+}
+
+/*++
+
+Routine Description:
+
+ This is the initialization routine for the Views/properties file
+ system device driver. This routine registers with ntfs and registers
+ an unload routine.
+
+
+Arguments:
+
+ DriverObject - Pointer to driver object created by the system.
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+VIEW_CALL_BACK ViewCallBackTable = {
+ VIEW_CURRENT_INTERFACE_VERSION,
+ ViewFileSystemControl
+ };
+
+NTSTATUS
+DriverEntry (
+ IN PDRIVER_OBJECT DriverObject,
+ IN PUNICODE_STRING pstrRegistry
+ )
+{
+ NTSTATUS Status;
+
+ UNREFERENCED_PARAMETER( DriverObject );
+ UNREFERENCED_PARAMETER( pstrRegistry );
+
+ Status = NtOfsRegisterCallBacks( Views, &ViewCallBackTable );
+
+ DriverObject->DriverUnload = DriverUnload;
+
+ DbgPrint( "Ntfs views: Register call backs Status = 0x%lx\n", Status );
+ return Status;
+}
+
+
+#if DBG == 1
+PDRIVER_INITIALIZE pdi = DriverEntry; // type check for changed prototype
+#endif
+
+
diff --git a/private/ntos/cntfs/views/heap.c b/private/ntos/cntfs/views/heap.c
new file mode 100644
index 000000000..170650e5f
--- /dev/null
+++ b/private/ntos/cntfs/views/heap.c
@@ -0,0 +1,506 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ heap.c
+
+Abstract:
+
+ This module contains the code to manipulate the value heap.
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+//
+// Global illegal value
+//
+
+PROPID IllegalId = PID_ILLEGAL;
+
+
+ULONG
+FindStringInHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PCOUNTED_STRING Name
+ )
+
+/*++
+
+Routine Description:
+
+ This routines looks up a property by name in the property set heap.
+
+Arguments:
+
+ Context - Context of heap to scan
+
+ Name - UNICODE string property to search for
+
+Return Value:
+
+ Offset to property value. If name is not found, 0 is returned
+
+--*/
+
+{
+ PPROPERTY_HEAP_ENTRY HeapEntry = FIRST_HEAP_ENTRY( Context->HeapHeader );
+ UNICODE_STRING First, Second;
+
+ First.Buffer = COUNTED_STRING_TEXT( Name );
+ First.Length = Second.Length = COUNTED_STRING_LENGTH( Name );
+
+ //
+ // While there are more heap records
+ //
+
+ while (!IS_LAST_HEAP_ENTRY( Context->HeapHeader, HeapEntry )) {
+
+ //
+ // if a name doesn't have the same length, skip it
+ //
+
+ if (HeapEntry->PropertyNameLength != Name->Length) {
+
+ NOTHING;
+
+ //
+ // Same length, test for case-insignificant match
+ //
+
+ } else {
+ Second.Buffer = &HeapEntry->PropertyName[0];
+
+ if (RtlCompareUnicodeString( &First, &Second, TRUE ) == 0) {
+
+ //
+ // return pointer to name
+ //
+
+ DebugTrace( 0, Dbg, ("FindStringInHeap %.*ws %x\n",
+ COUNTED_STRING_LENGTH( Name ) / sizeof( WCHAR ),
+ COUNTED_STRING_TEXT( Name ),
+ HeapOffset( Context, HeapEntry )) );
+
+ return HeapOffset( Context, HeapEntry );
+ }
+ }
+
+ //
+ // advance to next heap record
+ //
+
+ HeapEntry = NEXT_HEAP_ENTRY( HeapEntry );
+ }
+
+ //
+ // return not-found
+ //
+
+ DebugTrace( 0, Dbg, ("FindStringInHeap %.*ws not found\n",
+ COUNTED_STRING_LENGTH( Name ) / sizeof( WCHAR ),
+ COUNTED_STRING_TEXT( Name )) );
+
+ return 0;
+}
+
+
+//
+// Local support routine
+//
+
+PPROPERTY_HEAP_ENTRY GrowHeap (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routines grows the heap to accommodate a value of a particular length.
+ There is no guarantee about any "extra space" present.
+
+Arguments:
+
+ Context - context of property set
+
+ Length - minimum growth needed
+
+
+Return Value:
+
+ Pointer to new free item at end of heap
+
+--*/
+
+{
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+
+ //
+ // Determine true growth. For now, we allocate 2X the size of the property
+ // up to a max of 1K. After that, we just allocate the requested size
+ //
+
+ if (Length <= 512) {
+ Length = max( 2 * Length, Length + PROPERTY_HEAP_ENTRY_SIZE( 10, 8 ));
+ }
+
+ //
+ // Resize stream to account for new growth
+ //
+
+
+ DebugTrace( 0, Dbg, ("Growing heap to length %x\n", Length + NtOfsQueryLength( Context->Attribute )));
+
+ NtOfsSetLength (
+ Context->IrpContext,
+ Context->Attribute,
+ Length + NtOfsQueryLength( Context->Attribute ));
+
+ //
+ // Set up new header for new growth, Id and Length.
+ //
+
+ HeapEntry = (PPROPERTY_HEAP_ENTRY) Add2Ptr( Context->HeapHeader, Context->HeapHeader->PropertyHeapLength );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyId ),
+ sizeof( PROPID ),
+ &IllegalId );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyValueLength ),
+ sizeof( Length ),
+ &Length );
+
+ //
+ // Resize heap
+ //
+
+ Length+= Context->HeapHeader->PropertyHeapLength;
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->HeapHeader->PropertyHeapLength ),
+ sizeof( Length ),
+ &Length );
+
+ return HeapEntry;
+}
+
+VOID
+SetValueInHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_HEAP_ENTRY HeapEntry,
+ IN PROPID Id,
+ IN USHORT NameLength,
+ IN PWCHAR Name,
+ IN ULONG ValueLength,
+ IN SERIALIZEDPROPERTYVALUE *Value
+ )
+
+/*++
+
+Routine Description:
+
+ This routines sets a specific value in the heap.
+
+Arguments:
+
+ Context - context of property set
+
+ HeapEntry - pointer to header that will be modified
+
+ Id - PROPID to set
+
+ NameLength - length of name in BYTES
+
+ Name - pointer to name, may be NULL
+
+ ValueLength - count of bytes in value
+
+ Value - Serialized property value
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PROPASSERT( Id != PID_ILLEGAL );
+
+ //
+ // Set PropertyId
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyId ),
+ sizeof( PROPID ),
+ &Id );
+
+ //
+ // Set name length
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyNameLength ),
+ sizeof( USHORT ),
+ &NameLength );
+
+ //
+ // Set name if present
+ //
+
+ if (Name != NULL) {
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyName[0] ),
+ NameLength,
+ Name );
+ }
+
+ //
+ // Set property value
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, PROPERTY_HEAP_ENTRY_VALUE( HeapEntry )),
+ ValueLength,
+ Value );
+
+}
+
+
+ULONG
+AddValueToHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id,
+ IN ULONG Length,
+ IN USHORT NameLength,
+ IN PWCHAR Name OPTIONAL,
+ IN ULONG ValueLength,
+ IN SERIALIZEDPROPERTYVALUE *Value
+ )
+
+/*++
+
+Routine Description:
+
+ This routines adds a value to the heap. We walk the heap looking for a free
+ header (PID_ILLEGAL) whose size is sufficient to contain the name and value.
+ If one is not found, the heap and attribute are grown.
+
+Arguments:
+
+ Context - context of property set
+
+ Id - PROPID to add
+
+ Length - length of entire propery value (including name) in bytes
+
+ NameLength - length of name in BYTES
+
+ Name - pointer to name, may be NULL
+
+ ValueLength - count of bytes in value
+
+ Value - Serialized property value
+
+Return Value:
+
+ Offset to property value stored in heap
+
+--*/
+
+{
+ PPROPERTY_HEAP_ENTRY HeapEntry = FIRST_HEAP_ENTRY( Context->HeapHeader );
+
+ PROPASSERT( Id != PID_ILLEGAL );
+
+ DebugTrace( +1, Dbg, ("AddValueToHeap(%x '%.*ws' %x len %x)\n",
+ Id, NameLength / sizeof( WCHAR ), Name, Value, ValueLength) );
+ DebugTrace( 0, Dbg, ("property value length is %x\n", Length) );
+
+ //
+ // Walk through the heap until we reach the end or until we have
+ // a free block that contains enough room for this property. We also
+ // will break out if the block we find has enough room for the property
+ // and can be split to allow for a property that has 5 chars of name
+ // and 8 bytes of serialized value.
+ //
+
+ while (TRUE) {
+ //
+ // If this is not a valid header, break
+ //
+
+ if (IS_LAST_HEAP_ENTRY( Context->HeapHeader, HeapEntry )) {
+ break;
+ }
+
+ //
+ // if this is a free header
+ //
+
+ if (HeapEntry->PropertyId == PID_ILLEGAL) {
+
+ //
+ // If the value length matches exactly, break
+ //
+
+ if (HeapEntry->PropertyValueLength == Length) {
+ break;
+ }
+
+ //
+ // If the value length allows for an extra value, break
+ //
+
+ if (HeapEntry->PropertyValueLength >=
+ Length + PROPERTY_HEAP_ENTRY_SIZE( 10, 8 )) {
+ break;
+ }
+ }
+
+ //
+ // Go to next block
+ //
+
+ HeapEntry = NEXT_HEAP_ENTRY( HeapEntry );
+ }
+
+ //
+ // If we are at the end of the heap, then we must grow the heap. In this case
+ // we grow the heap by a large block and set thing up as if we had found
+ // a free block of the right size. We then fall into the normal reuse-free-
+ // block code.
+ //
+
+ if (IS_LAST_HEAP_ENTRY( Context->HeapHeader, HeapEntry )) {
+ HeapEntry = GrowHeap( Context, Length );
+ }
+
+ //
+ // HeapEntry points to a free heap block that is large enough to contain
+ // the new length, possibly splitting. First, split if necessary.
+ //
+
+ if (HeapEntry->PropertyValueLength != Length) {
+ PPROPERTY_HEAP_ENTRY SecondHeader;
+ ULONG SecondLength = HeapEntry->PropertyValueLength - Length;
+
+ PROPASSERT( Length + PROPERTY_HEAP_ENTRY_SIZE( 10, 8 ) <= HeapEntry->PropertyValueLength);
+
+ //
+ // Get address of second block header
+ //
+
+ SecondHeader = (PPROPERTY_HEAP_ENTRY) Add2Ptr( HeapEntry, Length );
+
+ //
+ // set up second block header: length and propid
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &SecondHeader->PropertyId ),
+ sizeof( PROPID ),
+ &IllegalId );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &SecondHeader->PropertyValueLength ),
+ sizeof( SecondLength ),
+ &SecondLength );
+
+
+ //
+ // set up value header length to reflect split
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyValueLength ),
+ sizeof( Length ),
+ &Length );
+
+ }
+
+ //
+ // HeapEntry points at a free block with exactly the right amount of space
+ // for this name/value
+ //
+
+ PROPASSERT( Length == HeapEntry->PropertyValueLength );
+
+ SetValueInHeap( Context, HeapEntry, Id, NameLength, Name, ValueLength, Value );
+
+ DebugTrace( -1, Dbg, ("value offset is at %x\n",
+ HeapOffset( Context, HeapEntry )) );
+
+ return HeapOffset( Context, HeapEntry );
+}
+
+
+VOID
+DeleteFromHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_HEAP_ENTRY HeapEntry
+ )
+
+/*++
+
+Routine Description:
+
+ This routines deletes a value from the heap. The present implementation
+ marks the header as being for PID_ILLEGAL.
+
+Arguments:
+
+ Context - Property set context
+
+ HeapEntry - entry to be deleted
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ DebugTrace( 0, Dbg, ("DeleteFromHeap at %x\n", HeapEntry) );
+
+ //
+ // Delete by setting the value to be PID_ILLEGAL
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &HeapEntry->PropertyId),
+ sizeof( IllegalId ),
+ &IllegalId );
+}
+
diff --git a/private/ntos/cntfs/views/initprop.c b/private/ntos/cntfs/views/initprop.c
new file mode 100644
index 000000000..c1ab2066a
--- /dev/null
+++ b/private/ntos/cntfs/views/initprop.c
@@ -0,0 +1,122 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ initprop.h
+
+Abstract:
+
+ This module contains the initialization user FsCtls for the Ntfs Property
+ support.
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+
+VOID
+InitializePropertyData (
+ IN PPROPERTY_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine initializes a blank stream for property access.
+
+ We set up the initial size, lay out an empty table and empty header.
+
+
+Arguments:
+
+ Context - Property Context for the call
+
+Return Value:
+
+ Nothing
+
+--*/
+{
+ PROPERTY_SET_HEADER PropertySetHeader;
+ PROPERTY_ID_TABLE IdTable;
+ PROPERTY_HEAP_HEADER HeapHeader;
+
+ //
+ // Set up header
+ //
+
+ PropertySetHeader.wByteOrder = 0xFFFE;
+ PropertySetHeader.wFormat = PSH_FORMAT_VERSION;
+ PropertySetHeader.dwOSVer = PSH_DWOSVER;
+ RtlZeroMemory( &PropertySetHeader.clsid, sizeof( CLSID ));
+ PropertySetHeader.reserved = 2; // BUGBUG ???
+ PropertySetHeader.IdTableOffset = LongAlign( sizeof( PROPERTY_SET_HEADER ));
+ PropertySetHeader.ValueHeapOffset = PropertySetHeader.IdTableOffset;
+
+ //
+ // Set up Id table
+ //
+
+ IdTable.PropertyCount = 0;
+ IdTable.MaximumPropertyCount = PIT_PROPERTY_DELTA;
+ PropertySetHeader.ValueHeapOffset +=
+ LongAlign( PROPERTY_ID_TABLE_SIZE( PIT_PROPERTY_DELTA ));
+
+ //
+ // Set up Heap header
+ //
+
+ HeapHeader.PropertyHeapLength = PHH_INITIAL_SIZE;
+ HeapHeader.PropertyHeapEntry[0].PropertyValueLength = PHH_INITIAL_SIZE - PROPERTY_HEAP_HEADER_SIZE;
+ HeapHeader.PropertyHeapEntry[0].PropertyId = PID_ILLEGAL;
+ HeapHeader.PropertyHeapEntry[0].PropertyNameLength = 0;
+
+ //
+ // Set the new size of the stream
+ //
+
+ NtOfsSetLength( Context->IrpContext, Context->Attribute,
+ PropertySetHeader.ValueHeapOffset + PHH_INITIAL_SIZE );
+
+
+ //
+ // Write out the header
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ 0,
+ sizeof( PROPERTY_SET_HEADER ),
+ &PropertySetHeader );
+
+
+ //
+ // Write out the table
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ PropertySetHeader.IdTableOffset,
+ sizeof( PROPERTY_ID_TABLE ),
+ &IdTable );
+
+ //
+ // Write out the heap and set the stream size
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData( Context->IrpContext,
+ Context->Attribute,
+ PropertySetHeader.ValueHeapOffset,
+ sizeof( PROPERTY_HEAP_HEADER ),
+ &HeapHeader );
+}
+
diff --git a/private/ntos/cntfs/views/makefile b/private/ntos/cntfs/views/makefile
new file mode 100644
index 000000000..0e21f828e
--- /dev/null
+++ b/private/ntos/cntfs/views/makefile
@@ -0,0 +1,13 @@
+############################################################################
+#
+# Copyright (C) 1992, Microsoft Corporation.
+#
+# All rights reserved.
+#
+############################################################################
+#
+# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source
+# file to this component. This file merely indirects to the real make file
+# that is shared by all the components of NT
+#
+!INCLUDE $(NTMAKEENV)\makefile.def
diff --git a/private/ntos/cntfs/views/readprop.c b/private/ntos/cntfs/views/readprop.c
new file mode 100644
index 000000000..859a39ece
--- /dev/null
+++ b/private/ntos/cntfs/views/readprop.c
@@ -0,0 +1,802 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ read.h
+
+Abstract:
+
+ This module contains the implementation of the read property data FsCtl
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+
+PPROPERTY_INFO
+BuildPropertyInfoFromPropSpec (
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_SPECIFICATIONS Specs,
+ IN PVOID InBufferEnd,
+ IN PROPID NextId
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and builds PROPERTY_INFO from the input
+ specifications. It looks up the names or id's in the property set
+ and assigns Ids if necessary.
+
+Arguments:
+
+ Context - property set context
+
+ Specs - input property specifications
+
+ InBufferEnd - end of input buffer for bounds testing Specs
+
+ NextId - next property Id to assign when a name is not found
+
+Return Value:
+
+ PPROPERTY_INFO pointer to allocated and initialized structure.
+
+--*/
+{
+ PPROPERTY_INFO Info;
+ ULONG i;
+ ULONG ValueOffset;
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+ PROPID Id;
+
+ Info = NtfsAllocatePool( PagedPool, PROPERTY_INFO_SIZE( Specs->Count ));
+
+ //
+ // Set up header and running totals
+ //
+
+ Info->Count = Specs->Count;
+ Info->TotalIdsSize = PROPERTY_IDS_SIZE( Info->Count );
+ Info->TotalValuesSize = PROPERTY_VALUES_SIZE( Info->Count );
+ Info->TotalNamesSize = PROPERTY_NAMES_SIZE( Info->Count );
+
+ //
+ // Indirect information is not determined until we finish
+ // scanning the array
+ //
+
+ Info->IndirectCount = 0;
+ Info->TotalIndirectSize = 0;
+
+ for (i = 0; i < Info->Count; i++) {
+
+ //
+ // If the spec is an ID, then look up the Id in the table
+ //
+
+ if (Specs->Specifiers[i].Variant == PRSPEC_PROPID) {
+ ValueOffset = FindIdInTable( Context, PROPERTY_SPECIFIER_ID( Specs, i ));
+
+ //
+ // Otherwise, find the name string, verify it's within the buffer
+ // and look it up in the heap.
+ //
+
+ } else {
+ PCOUNTED_STRING Name = PROPERTY_SPECIFIER_COUNTED_STRING( Specs, i );
+
+ if (InBufferEnd < Add2Ptr(Name, COUNTED_STRING_SIZE( Name->Length ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ ValueOffset = FindStringInHeap( Context, Name );
+ }
+
+ //
+ // Convert offset to pointer to header
+ //
+
+ HeapEntry =
+ ValueOffset == 0 ? EMPTY_PROPERTY
+ : GET_HEAP_ENTRY( Context->HeapHeader, ValueOffset );
+
+ //
+ // Gather up the size of this output
+ //
+
+ // TotalIds is already set up
+ Info->TotalValuesSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ Info->TotalNamesSize += HeapEntry->PropertyNameLength;
+ if (IS_INDIRECT_VALUE( HeapEntry )) {
+ Info->IndirectCount++;
+ Info->TotalIndirectSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ }
+
+ //
+ // Set up the Entry pointer and Id. If we did not find a property, then
+ // attempt to assign and Id.
+ //
+
+ Info->Entries[i].Heap = HeapEntry;
+ if (HeapEntry == EMPTY_PROPERTY) {
+
+ //
+ // No property was found. If a propid was specified, go use it
+ //
+
+ if (Specs->Specifiers[i].Variant == PRSPEC_PROPID) {
+
+ Id = PROPERTY_SPECIFIER_ID( Specs, i );
+
+ //
+ // No Id was specified, assign one if we are supposed to. We could
+ // probe the table until we don't find and Id, but better to let
+ // the table tell us where the next hole is.
+ //
+
+ } else if (NextId != PID_ILLEGAL) {
+ Id = FindFreeIdInTable( Context, NextId );
+ NextId = Id + 1;
+ } else {
+ Id = PID_ILLEGAL;
+ }
+ } else {
+ Id = HeapEntry->PropertyId;
+ }
+ Info->Entries[i].Id = Id;
+ }
+
+ Info->TotalIndirectSize += INDIRECT_PROPERTIES_SIZE( Info->IndirectCount );
+
+ Context->Info = Info;
+
+ return Info;
+}
+
+
+PPROPERTY_INFO
+BuildPropertyInfoFromIds (
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_IDS Ids
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and builds PROPERTY_INFO from the input
+ ids.
+
+Arguments:
+
+ Context - property set context
+
+ Ids - input property Ids
+
+Return Value:
+
+ PPROPERTY_INFO pointer to allocated and initialized structure.
+
+--*/
+{
+ PPROPERTY_INFO Info;
+ ULONG i;
+ ULONG ValueOffset;
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+
+ Info = NtfsAllocatePool( PagedPool, PROPERTY_INFO_SIZE( Ids->Count ));
+
+ //
+ // Set up header and running totals
+ //
+
+ Info->Count = Ids->Count;
+ Info->TotalIdsSize = PROPERTY_IDS_SIZE( Info->Count );
+ Info->TotalValuesSize = PROPERTY_VALUES_SIZE( Info->Count );
+ Info->TotalNamesSize = PROPERTY_NAMES_SIZE( Info->Count );
+
+ //
+ // Indirect information is not determined until we finish
+ // scanning the array
+ //
+
+ Info->IndirectCount = 0;
+ Info->TotalIndirectSize = 0;
+
+ for (i = 0; i < Info->Count; i++) {
+
+ ValueOffset = FindIdInTable( Context, PROPERTY_ID( Ids, i ));
+
+ //
+ // Convert offset to pointer to header
+ //
+
+ HeapEntry =
+ ValueOffset == 0 ? EMPTY_PROPERTY
+ : GET_HEAP_ENTRY( Context->HeapHeader, ValueOffset );
+
+ //
+ // Gather up the size of this output
+ //
+
+ // TotalIds is already set up
+ Info->TotalValuesSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ Info->TotalNamesSize += HeapEntry->PropertyNameLength;
+ if (IS_INDIRECT_VALUE( HeapEntry )) {
+ Info->IndirectCount++;
+ Info->TotalIndirectSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ }
+
+ //
+ // Set up the Entry pointer and Id. If we did not find a property, then
+ // attempt to assign and Id.
+ //
+
+ Info->Entries[i].Heap = HeapEntry;
+ Info->Entries[i].Id = PROPERTY_ID( Ids, i );
+ }
+
+ Info->TotalIndirectSize += INDIRECT_PROPERTIES_SIZE( Info->IndirectCount );
+
+ Context->Info = Info;
+
+ return Info;
+}
+
+
+PPROPERTY_INFO
+BuildPropertyInfoFromIdTable (
+ IN PPROPERTY_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine allocates and builds PROPERTY_INFO from the property Id
+ table
+
+Arguments:
+
+ Context - property set context
+
+Return Value:
+
+ PPROPERTY_INFO pointer to allocated and initialized structure.
+
+--*/
+{
+ PPROPERTY_INFO Info;
+ ULONG i;
+ ULONG ValueOffset;
+ PPROPERTY_HEAP_ENTRY HeapEntry;
+
+ Info = NtfsAllocatePool( PagedPool, PROPERTY_INFO_SIZE( Context->IdTable->PropertyCount ));
+
+ //
+ // Set up header and running totals
+ //
+
+ Info->Count = Context->IdTable->PropertyCount;
+ Info->TotalIdsSize = PROPERTY_IDS_SIZE( Info->Count );
+ Info->TotalValuesSize = PROPERTY_VALUES_SIZE( Info->Count );
+ Info->TotalNamesSize = PROPERTY_NAMES_SIZE( Info->Count );
+
+ //
+ // Indirect information is not determined until we finish
+ // scanning the array
+ //
+
+ Info->IndirectCount = 0;
+ Info->TotalIndirectSize = 0;
+
+ for (i = 0; i < Info->Count; i++) {
+
+ HeapEntry =
+ GET_HEAP_ENTRY( Context->HeapHeader,
+ Context->IdTable->Entry[i].PropertyValueOffset );
+
+ //
+ // Gather up the size of this output
+ //
+
+ // TotalIds is already set up
+ Info->TotalValuesSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ Info->TotalNamesSize += HeapEntry->PropertyNameLength;
+ if (IS_INDIRECT_VALUE( HeapEntry )) {
+ Info->IndirectCount++;
+ Info->TotalIndirectSize += PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ }
+
+ //
+ // Set up the Entry pointer and Id. If we did not find a property, then
+ // attempt to assign and Id.
+ //
+
+ Info->Entries[i].Heap = HeapEntry;
+ Info->Entries[i].Id = HeapEntry->PropertyId;
+ }
+
+ Info->TotalIndirectSize += INDIRECT_PROPERTIES_SIZE( Info->IndirectCount );
+
+ Context->Info = Info;
+
+ return Info;
+}
+
+
+PVOID
+BuildPropertyIds (
+ IN PPROPERTY_INFO Info,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds a PROPERTY_IDS structure in the output
+ buffer from the array of value headers.
+
+Arguments:
+
+ Info - pointers to the properties
+
+ OutBuffer - pointer to the unverified user command buffer. Since we
+ address this directly, we may raise if the buffer becomes invalid.
+ The caller must have already verified the size.
+
+Return Value:
+
+ PVOID pointer to next output
+
+--*/
+{
+ ULONG i;
+
+ //
+ // Build Ids
+ //
+
+ PPROPERTY_IDS Ids = (PPROPERTY_IDS) OutBuffer;
+
+ PROPASSERT( OutBuffer == (PVOID)LongAlign( OutBuffer ));
+
+ Ids->Count = Info->Count;
+
+ for (i = 0; i < Info->Count; i++) {
+ Ids->PropertyIds[i] = PROPERTY_INFO_ID( Info, i );
+ }
+
+ return (PVOID) Add2Ptr( Ids, PROPERTY_IDS_SIZE( Info->Count ));
+}
+
+
+PVOID
+BuildPropertyNames (
+ IN PPROPERTY_INFO Info,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds a PROPERTY_NAMES structure in the output
+ buffer from the array of value headers.
+
+Arguments:
+
+ Info - pointers to the properties
+
+ OutBuffer - pointer to the unverified user command buffer. Since we
+ address this directly, we may raise if the buffer becomes invalid.
+ The caller must have already verified the size.
+
+Return Value:
+
+ PVOID pointer to next output
+
+--*/
+{
+ ULONG i;
+
+ //
+ // Build property names
+ //
+
+ PPROPERTY_NAMES Names = (PPROPERTY_NAMES) OutBuffer;
+
+ PROPASSERT( OutBuffer == (PVOID)LongAlign( OutBuffer ));
+
+ Names->Count = Info->Count;
+ OutBuffer = Add2Ptr( Names, PROPERTY_NAMES_SIZE( Info->Count ));
+
+ //
+ // For each property found, copy the name into the buffer and
+ // advance for next output.
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ ULONG Length = PROPERTY_INFO_HEAP_ENTRY( Info, i )->PropertyNameLength;
+ Names->PropertyNameOffset[i] = PtrOffset( Names, OutBuffer );
+
+ PROPASSERT( OutBuffer == (PVOID)WordAlign( OutBuffer ));
+
+ RtlCopyMemory( OutBuffer,
+ &PROPERTY_INFO_HEAP_ENTRY( Info, i )->PropertyName[0],
+ Length);
+ OutBuffer = Add2Ptr( OutBuffer, Length);
+ }
+
+ //
+ // Set last pointer and length to reflect total size of the
+ // buffer.
+ //
+
+ Names->Length = Names->PropertyNameOffset[i] = PtrOffset( Names, OutBuffer );
+
+ return OutBuffer;
+}
+
+
+PVOID
+BuildPropertyValues (
+ IN PPROPERTY_INFO Info,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine builds a PROPERTY_VALUES structure in the output
+ buffer from the array of value headers.
+
+Arguments:
+
+ Info - pointer to properties
+
+ OutBuffer - pointer to the unverified user command buffer. Since we
+ address this directly, we may raise if the buffer becomes invalid.
+ The caller must have already verified the size.
+
+Return Value:
+
+ PVOID pointer to next output
+
+--*/
+{
+ ULONG i;
+
+ //
+ // Build property values header
+ //
+
+ PPROPERTY_VALUES Values = (PPROPERTY_VALUES) OutBuffer;
+ Values->Count = Info->Count;
+
+ PROPASSERT( OutBuffer == (PVOID)LongAlign( OutBuffer ));
+
+ OutBuffer = Add2Ptr( Values, PROPERTY_VALUES_SIZE( Info->Count ));
+
+ //
+ // For each property found, copy the value into the buffer and
+ // advance for next output.
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ ULONG Length = PROPERTY_HEAP_ENTRY_VALUE_LENGTH( PROPERTY_INFO_HEAP_ENTRY( Info, i ));
+ Values->PropertyValueOffset[i] = PtrOffset( Values, OutBuffer );
+
+ PROPASSERT( OutBuffer == (PVOID)LongAlign( OutBuffer ));
+
+ RtlCopyMemory( OutBuffer,
+ PROPERTY_HEAP_ENTRY_VALUE( PROPERTY_INFO_HEAP_ENTRY( Info, i )),
+ Length);
+ OutBuffer = Add2Ptr( OutBuffer, Length);
+ }
+
+ //
+ // Set last pointer and length to reflect total size of the
+ // buffer.
+ //
+
+ Values->PropertyValueOffset[i] = Values->Length = PtrOffset( Values, OutBuffer );
+
+ return OutBuffer;
+}
+
+
+VOID
+ReadPropertyData (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT PULONG OutBufferLength,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs property read functions.
+
+ We verify the header format and perform the read operation.
+
+Arguments:
+
+ Context - Property Context for the call
+
+ InBufferLength - Length of the command buffer
+
+ InBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally.
+
+ OutBufferLength - pointer to ULONG length of output buffer. This value
+ is set on return to indicate the total size of data within the output
+ buffer.
+
+ OutBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally
+
+Note:
+
+ The Io system does no validation whatsoever on either buffer. This means that
+ we need to probe for readability and for being a valid user address.
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PVOID InBufferEnd = Add2Ptr( InBuffer, InBufferLength );
+ PPROPERTY_INFO Info = NULL;
+
+ try {
+ PPROPERTY_READ_CONTROL Control = (PPROPERTY_READ_CONTROL) InBuffer;
+ ULONG ValueOffset;
+ PVOID NextOutput;
+ ULONG TotalLength;
+ ULONG Count;
+ ULONG i;
+
+ //
+ // Buffers must be long aligned
+ //
+
+ if (
+ //
+ // Long alignment of all buffers
+ //
+ InBuffer != (PVOID)LongAlign( InBuffer ) ||
+ OutBuffer != (PVOID)LongAlign( OutBuffer ) ||
+
+ //
+ // Room for control at least
+ //
+ InBufferEnd < (PVOID)(Control + 1)
+
+ ) {
+
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+
+ }
+
+
+ if (KeGetPreviousMode() != KernelMode) {
+ //
+ // Verify that the input buffer is readable
+ //
+
+ ProbeForRead( InBuffer, InBufferLength, sizeof( ULONG ));
+
+ //
+ // Verify that the output buffer is readable
+ //
+
+ ProbeForWrite( OutBuffer, *OutBufferLength, sizeof( ULONG ));
+ }
+
+ //
+ // Map attribute for full size
+ //
+
+ MapPropertyContext( Context );
+
+
+ //
+ // Verify the property set has valid contents.
+ //
+
+ CheckPropertySet( Context );
+
+ if (Control->Op == PRC_READ_PROP) {
+ PPROPERTY_SPECIFICATIONS Specs;
+
+ //
+ // The input buffer is:
+ // PROPERTY_READ_CONTROL
+ // PROPERTY_SPECIFICATIONS
+ //
+ // The output buffer will be:
+ // PROPERTY_VALUES
+ //
+
+ //
+ // Build value headers array from specifiers
+ //
+
+ Specs = (PPROPERTY_SPECIFICATIONS) (Control + 1);
+ if (
+ //
+ // Room for specifications header
+ //
+
+ InBufferEnd < (PVOID)(Specs + 1) ||
+
+ //
+ // Room for full body
+ //
+ InBufferEnd < Add2Ptr( Specs, PROPERTY_SPECIFICATIONS_SIZE( Specs->Count ))
+
+ ) {
+
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+
+ }
+
+ Info = BuildPropertyInfoFromPropSpec( Context,
+ Specs,
+ InBufferEnd,
+ PID_ILLEGAL );
+
+
+ //
+ // Check for enough room on output
+ //
+
+ TotalLength = Info->TotalValuesSize;
+
+ if (TotalLength > *OutBufferLength) {
+ PULONG ReturnLength = OutBuffer;
+
+ *ReturnLength = TotalLength;
+ *OutBufferLength = sizeof( ULONG );
+ ExRaiseStatus( STATUS_BUFFER_OVERFLOW );
+ }
+
+ //
+ // Build property values header
+ //
+
+ NextOutput = BuildPropertyValues( Info, OutBuffer );
+
+ PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PRC_READ_NAME) {
+ PPROPERTY_IDS Ids;
+
+ //
+ // The input buffer is:
+ // PROPERTY_READ_CONTROL
+ // PROPERTY_IDS
+ //
+ // The output buffer will be:
+ // PROPERTY_NAMES
+ //
+
+ //
+ // Build offsets array from Ids
+ //
+
+ Ids = (PPROPERTY_IDS) (Control + 1);
+
+ if (InBufferEnd < Add2Ptr( Ids, PROPERTY_IDS_SIZE( 0 )) ||
+ InBufferEnd < Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Info = BuildPropertyInfoFromIds( Context, Ids );
+
+ //
+ // Check for enough room on output
+ //
+
+ TotalLength = Info->TotalNamesSize;
+
+ if (TotalLength > *OutBufferLength) {
+ PULONG ReturnLength = OutBuffer;
+
+ *ReturnLength = TotalLength;
+ *OutBufferLength = sizeof( ULONG );
+ ExRaiseStatus( STATUS_BUFFER_OVERFLOW );
+ }
+
+
+ //
+ // Build property names
+ //
+
+ NextOutput = BuildPropertyNames( Info, OutBuffer );
+
+ PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PRC_READ_ALL) {
+ ULONG TotalIds;
+ ULONG TotalNames;
+ ULONG TotalValues;
+ //
+ // The input buffer is NULL
+ //
+ // The output buffer will be:
+ // PROPERTY_IDS
+ // PROPERTY_NAMES
+ // PROPERTY_VALUES
+ //
+
+ Info = BuildPropertyInfoFromIdTable( Context );
+
+ //
+ // Check for enough room on output
+ //
+
+ TotalLength = Info->TotalIdsSize +
+ LongAlign( Info->TotalNamesSize ) +
+ Info->TotalValuesSize;
+
+ if (TotalLength > *OutBufferLength) {
+ PULONG ReturnLength = OutBuffer;
+
+ *ReturnLength = TotalLength;
+ *OutBufferLength = sizeof( ULONG );
+ ExRaiseStatus( STATUS_BUFFER_OVERFLOW );
+ }
+
+ //
+ // Build Ids
+ //
+
+ NextOutput = BuildPropertyIds( Info, OutBuffer );
+ PROPASSERT( NextOutput == (PVOID)LongAlign( NextOutput ));
+
+ //
+ // Build property names
+ //
+
+ NextOutput = BuildPropertyNames( Info, NextOutput );
+ PROPASSERT( NextOutput == (PVOID)WordAlign( NextOutput ));
+
+ //
+ // Build property values header
+ //
+
+ NextOutput = BuildPropertyValues( Info, (PVOID)LongAlign( NextOutput ));
+ PROPASSERT( NextOutput == (PVOID)LongAlign( NextOutput ));
+
+ PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ *OutBufferLength = TotalLength;
+
+ } finally {
+
+ if (Info != NULL) {
+ NtfsFreePool( Info );
+ }
+
+ }
+}
+
diff --git a/private/ntos/cntfs/views/sources b/private/ntos/cntfs/views/sources
new file mode 100644
index 000000000..9cb0eb303
--- /dev/null
+++ b/private/ntos/cntfs/views/sources
@@ -0,0 +1,56 @@
+!IF 0
+
+Copyright (c) 1989 Microsoft Corporation
+
+Module Name:
+
+ sources.
+
+Abstract:
+
+ This file specifies the target component being built and the list of
+ sources files needed to build that component. Also specifies optional
+ compiler switches and libraries that are unique for the component being
+ built.
+
+
+Author:
+
+ Steve Wood (stevewo) 12-Apr-1990
+
+NOTE: Commented description of this file is in \nt\bak\bin\sources.tpl
+
+!ENDIF
+
+SYNCHRONIZE_DRAIN=1
+SYNCHRONIZE_BLOCK=1
+
+MAJORCOMP=ntfs
+MINORCOMP=fs
+
+TARGETNAME=views
+TARGETPATH=obj
+TARGETTYPE=DRIVER
+
+INCLUDES=$(INCLUDES);.;..\..\inc;..
+
+C_DEFINES=$(C_DEFINES) -D_NTSYSTEM_ -D_NTIFS_
+
+CAIRO_PRODUCT=1
+
+SOURCES=driver.c \
+ check.c \
+ heap.c \
+ initprop.c \
+ readprop.c \
+ table.c \
+ writprop.c
+
+TARGETLIBS= \
+ $(BASEDIR)\public\sdk\lib\cairo\*\ntfs.lib
+
+PRECOMPILED_INCLUDE=viewprop.h
+PRECOMPILED_PCH=viewprop.pch
+PRECOMPILED_OBJ=viewprop.obj
+
+
diff --git a/private/ntos/cntfs/views/table.c b/private/ntos/cntfs/views/table.c
new file mode 100644
index 000000000..eae79f15f
--- /dev/null
+++ b/private/ntos/cntfs/views/table.c
@@ -0,0 +1,543 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ table.c
+
+Abstract:
+
+ This module contains the routines to manipulate the property id table in
+ a property set.
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+
+ULONG
+BinarySearchIdTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID PropertyId
+ )
+
+/*++
+
+Routine Description:
+
+ This routines performs a binary search on the Id table
+
+Arguments:
+
+ Context - property context of table
+
+ PropertyId - PROPID to find
+
+Return Value:
+
+ Entry index where the specified property Id exists or where it should be
+ inserted
+
+--*/
+
+{
+ int Hi, Lo;
+
+ //
+ // Set up for binary search. Entries between 0 and count-1 are eligible
+ //
+
+ Lo = 0;
+ Hi = (int) Context->IdTable->PropertyCount - 1;
+
+ //
+ // While we have a valid range to search
+ //
+
+ while (Lo <= Hi) {
+ int Mid;
+
+ //
+ // Determine midpoint where we'll test
+ //
+
+ Mid = (Lo + Hi) / 2;
+
+ //
+ // If we've found the item then return it
+ //
+
+ if (PropertyId == Context->IdTable->Entry[Mid].PropertyId) {
+
+ PROPASSERT( Mid >= 0);
+
+ return (ULONG) Mid;
+
+ //
+ // If the property is in the upper range then move
+ // the lower boundary upward
+ //
+
+ } else if (PropertyId > Context->IdTable->Entry[Mid].PropertyId) {
+
+ Lo = Mid + 1;
+
+ //
+ // The property is in the lower range. Move the upper boundary
+ // downward
+ //
+
+ } else {
+
+ Hi = Mid - 1;
+
+ }
+ }
+
+ //
+ // No exact match was found. Lo is the point before where the property Id
+ // must be inserted.
+ //
+
+ PROPASSERT( Lo >= 0 && Lo <= (int) Context->IdTable->PropertyCount );
+
+ return (ULONG) Lo;
+}
+
+
+ULONG
+FindIdInTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID PropertyId
+ )
+
+/*++
+
+Routine Description:
+
+ This routines looks up a property by Id in the property set heap.
+
+Arguments:
+
+ Context - property context of table
+
+ PropertyId - PROPID to find
+
+Return Value:
+
+ Offset to property value in heap. 0 if not found.
+
+--*/
+
+{
+ ULONG Index;
+
+ DebugTrace( +1, Dbg, ("FindInTable(%x)\n", PropertyId) );
+
+ //
+ // Binary search table for Id
+ //
+
+ Index = BinarySearchIdTable( Context, PropertyId );
+
+ //
+ // If found entry is legal and it matches the Id then return
+ // pointer to value header
+ //
+
+ if (Index < Context->IdTable->PropertyCount &&
+ Context->IdTable->Entry[Index].PropertyId == PropertyId) {
+
+ DebugTrace( -1, Dbg, ("FindIdInTable: return offset %x (found)\n",
+ Context->IdTable->Entry[Index].PropertyValueOffset ));
+
+ return Context->IdTable->Entry[Index].PropertyValueOffset;
+ }
+
+ //
+ // entry not found, return
+ //
+
+ DebugTrace( -1, Dbg, ("FindIdInTable: not found\n") );
+
+ return 0;
+}
+
+
+PROPID
+FindFreeIdInTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id
+ )
+
+/*++
+
+Routine Description:
+
+ This routines looks in the table to find the next propid beginning at Id
+ that is not allocated
+
+Arguments:
+
+ Context - property context of table
+
+ Id - PROPID to being free searc at
+
+Return Value:
+
+ PROPID of free Id
+
+--*/
+
+{
+ ULONG Index;
+
+ //
+ // Find location in table to begin search
+ //
+
+ Index = BinarySearchIdTable( Context, Id );
+
+ //
+ // while location is valid and Id is the same
+ //
+
+ while (Index < Context->IdTable->PropertyCount &&
+ Context->IdTable->Entry[Index].PropertyId == Id) {
+ //
+ // advance location and Id
+ //
+
+ Index ++;
+ Id ++;
+ }
+
+ return Id;
+}
+
+
+//
+// Local support routine
+//
+
+VOID
+GrowIdTable (
+ IN PPROPERTY_CONTEXT Context
+ )
+
+/*++
+
+Routine Description:
+
+ This routine grows the Id table in the property set. It handles
+ resizing the attribute and moving the property heap. This means
+ resetting the cached pointers inside of Context
+
+Arguments:
+
+ Context - property context for this action.
+
+Return Value:
+
+ Nothing.
+
+--*/
+
+{
+ ULONG Count = Context->IdTable->PropertyCount;
+ ULONG Offset = Context->Header->ValueHeapOffset;
+
+ //
+ // We grow the property heap by max (PIT_PROPERTY_DELTA, Count / 16)
+ // entries
+ //
+
+ ULONG Delta = max( PIT_PROPERTY_DELTA, Count / 16 );
+
+ ULONG NewSize = (ULONG) NtOfsQueryLength( Context->Attribute ) +
+ Delta * sizeof( PROPERTY_TABLE_ENTRY );
+
+ DebugTrace( +1, Dbg, ("GrowIdTable growing by %x\n", Delta) );
+
+ //
+ // Check for growing attribute too much.
+ // BUGBUG - define a better status code.
+ //
+
+ if (NewSize > VACB_MAPPING_GRANULARITY) {
+ ExRaiseStatus( STATUS_DISK_FULL );
+ }
+
+ //
+ // Resize the attribute
+ //
+
+ DebugTrace( 0, Dbg, ("Setting size to %x\n", Context->Attribute->Header.FileSize.QuadPart +
+ Delta * sizeof( PROPERTY_TABLE_ENTRY )) );
+
+ NtOfsSetLength(
+ Context->IrpContext,
+ Context->Attribute,
+ Context->Attribute->Header.FileSize.QuadPart +
+ Delta * sizeof( PROPERTY_TABLE_ENTRY )
+ );
+
+ //
+ // Move the heap upwards by Delta
+ //
+
+ DebugTrace( 0, Dbg, ("Moving heap from %x to %x\n",
+ ContextOffset( Context, Context->HeapHeader ),
+ ContextOffset( Context, Context->HeapHeader ) +
+ Delta * sizeof( PROPERTY_TABLE_ENTRY )) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, Context->HeapHeader ) +
+ Delta * sizeof( PROPERTY_TABLE_ENTRY ),
+ Context->HeapHeader->PropertyHeapLength,
+ Context->HeapHeader
+ );
+
+ Offset += Delta * sizeof( PROPERTY_TABLE_ENTRY );
+
+ DebugTrace( 0, Dbg, ("Setting ValueHeapOffset to %x\n", Offset) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->Header->ValueHeapOffset ),
+ sizeof( ULONG ),
+ &Offset
+ );
+
+ Context->HeapHeader = PROPERTY_HEAP_HEADER( Context->Header );
+
+ //
+ // Update the max size by the delta
+ //
+
+ Count = Context->IdTable->MaximumPropertyCount + Delta;
+
+ DebugTrace( 0, Dbg, ("Setting max table count to %x\n", Count) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->MaximumPropertyCount ),
+ sizeof( ULONG ),
+ &Count
+ );
+
+ //
+ // Rebase the entry pointers in the propinfo if any
+ //
+
+ if (Context->Info != NULL) {
+ ULONG i;
+
+ for (i = 0; i < Context->Info->Count; i++) {
+ if (Context->Info->Entries[i].Heap != EMPTY_PROPERTY) {
+ Context->Info->Entries[i].Heap =
+ Add2Ptr( Context->Info->Entries[i].Heap, Delta );
+ }
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("") );
+}
+
+
+VOID
+ChangeTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id,
+ IN ULONG Offset
+ )
+
+/*++
+
+Routine Description:
+
+ This routine changes the table based on the new offset
+
+Arguments:
+
+ Context - property context for this action.
+
+ Id - PROPID to find
+
+ Offset - New property value offset
+
+Return Value:
+
+ Nothing.
+
+--*/
+
+{
+ ULONG Count = Context->IdTable->PropertyCount;
+
+ //
+ // Binary search table for Id
+ //
+
+ ULONG Index = BinarySearchIdTable( Context, Id );
+
+
+ //
+ // If the offset is zero, then we are deleting the entry
+ //
+
+ if (Offset == 0) {
+
+ //
+ // Make sure the returned value makes sense
+ //
+
+ ASSERT ( Index < Count && Context->IdTable->Entry[Index].PropertyId == Id );
+
+ //
+ // We move all entries Index+1..PropertyCount down to Index. Special case
+ // moving the last item.
+ //
+
+ if (Index != Count - 1) {
+
+ DebugTrace( 0, Dbg, ("Ripple copy %x to %x length %x\n",
+ ContextOffset( Context, &Context->IdTable->Entry[Index + 1] ),
+ ContextOffset( Context, &Context->IdTable->Entry[Index] ),
+ sizeof( PROPERTY_TABLE_ENTRY ) * (Count - (Index + 1))) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->Entry[Index] ),
+ sizeof( PROPERTY_TABLE_ENTRY ) * (Count - (Index + 1)),
+ &Context->IdTable->Entry[Index + 1]
+ );
+ }
+
+ //
+ // Change the count in use
+ //
+
+ Count--;
+
+ DebugTrace( 0, Dbg, ("New count is %x\n", Count) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->PropertyCount ),
+ sizeof( ULONG ),
+ &Count
+ );
+
+ } else {
+
+ //
+ // if we found the propertyid in the table
+ //
+
+ if (Index < Count && Context->IdTable->Entry[Index].PropertyId == Id) {
+ PROPERTY_TABLE_ENTRY Entry = { Id, Offset };
+
+ //
+ // Replace the entry in the table with the new entry
+ //
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->Entry[Index] ),
+ sizeof( PROPERTY_TABLE_ENTRY ),
+ &Entry
+ );
+
+ } else {
+
+ PROPERTY_TABLE_ENTRY Entry = { Id, Offset };
+
+ //
+ // Add the new entry to the table
+ //
+
+ //
+ // If there is no more room in the table for a new Id then
+ // grow the IdTable
+ //
+
+ if (Count == Context->IdTable->MaximumPropertyCount) {
+ GrowIdTable( Context );
+ }
+
+ //
+ // Index is the point where the insertion must occur. We leave
+ // alone elements 0..Index-1, and ripple-copy elements Index..PropertyCount-1
+ // to Index+1. We skip this case when we are simply appending a propid at the
+ // end.
+ //
+
+ if (Index < Count) {
+
+ DebugTrace( 0, Dbg, ("Ripple copy table from %x to %x length %x\n",
+ PtrOffset( Context->IdTable, &Context->IdTable->Entry[Index] ),
+ PtrOffset( Context->IdTable, &Context->IdTable->Entry[Index + 1]),
+ sizeof( PROPERTY_TABLE_ENTRY ) * (Count - Index)) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->Entry[Index + 1] ),
+ sizeof( PROPERTY_TABLE_ENTRY ) * (Count - Index),
+ &Context->IdTable->Entry[Index]
+ );
+ }
+
+ //
+ // Stick in the new property entry
+ //
+
+ DebugTrace( 0, Dbg, ("new entry %x:%x\n", Entry.PropertyId, Entry.PropertyValueOffset) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->Entry[Index] ),
+ sizeof( PROPERTY_TABLE_ENTRY ),
+ &Entry
+ );
+
+ //
+ // Increment the usage count and log it
+ //
+
+ Count++;
+
+ DebugTrace( 0, Dbg, ("new count in table is %x\n", Count) );
+
+ LogFileFullFailCheck( Context->IrpContext );
+ NtOfsPutData(
+ Context->IrpContext,
+ Context->Attribute,
+ ContextOffset( Context, &Context->IdTable->PropertyCount ),
+ sizeof( ULONG ),
+ &Count
+ );
+ }
+ }
+}
diff --git a/private/ntos/cntfs/views/viewprop.h b/private/ntos/cntfs/views/viewprop.h
new file mode 100644
index 000000000..2f681ad0b
--- /dev/null
+++ b/private/ntos/cntfs/views/viewprop.h
@@ -0,0 +1,600 @@
+/*++
+
+Copyright (c) 1995 Microsoft Corporation
+
+Module Name:
+
+ ViewProp.h
+
+Abstract:
+
+ This module defines the routines used in ViewProp.Sys to service view and
+ property requests.
+
+ *********************************
+ *No other clients are supported.*
+ *********************************
+
+Author:
+
+ Mark Zbikowski [MarkZ] 21-Dec-95
+
+Revision History:
+
+
+--*/
+
+#include <ntfsproc.h>
+
+#include <ntrtl.h>
+#include <nturtl.h> // needs ntrtl.h
+#include <objidl.h> // needs nturtl.h
+#include <propset.h> // needs objidl.h
+#include "ntfsprop.h" // needs objidl.h
+
+//
+// Big picture of Views implementation
+//
+// Directories are the only objects that contain views. Views' purpose is
+// to provide an up-to-date sorted list of objects in a directory. This is
+// only single-level containment as we don't do transitive closure.
+//
+// A view is simply an index whose entries are an ordered list of property
+// values. Each view has a description that lists the sources of each
+// property value.
+//
+// All views of a directory are stored as index attributes of the directory
+// itself. All view descriptsions are stored in a single data sttribute of
+// the directory.
+//
+// Creation of a view consists of:
+// Dispatched via FsCtl
+// Acquiring the directory
+// Creating/opening the view description attribute
+// Adding a record describing the view (what properties are included,
+// which ones are sorted, and whether the sort is up or down). This
+// is a logged operation.
+// Creating the view index
+// Releasing the directory
+//
+// Deleting a view consists of:
+// Dispatched via FsCtl
+// Acquiring the directory
+// Opening the view description attribute
+// Finding/deleting the record describing the view. This is a logged
+// operation
+// Deleting the view index
+// Releasing the directory
+//
+// Updating a view consists of:
+// Dispatched via property change call, security change call,
+// DUPLICATED_INFO change, or STAT_INFO change
+// Acquiring the object
+// Acquiring the parent directory
+// Opening the view description
+// For each view that contains a property that is being changed
+// From the object, build the old row and build the new row
+// Open the view
+// Delete the old row
+// Insert the new row
+// Releasing the parent
+// Releasing the object
+//
+
+
+#if DBG
+#define PROPASSERT(exp) \
+ ((exp) ? TRUE : \
+ (DbgPrint( "%s:%d %s\n",__FILE__,__LINE__,#exp ), \
+ DbgBreakPoint(), \
+ TRUE))
+#else // DBG
+#define PROPASSERT(exp)
+#endif
+
+//
+// Property set storage format
+//
+// Each property set is stored within a single stream and is limited to
+// VACB_MAPPING_GRANULARITY in size. The storage format is optimized for
+// the following operations:
+//
+// Write all properties. (written via save/save-all/copy/restore)
+// Write in place.
+// Write and extend length of variable length property.
+// Add one or several new properties.
+// Delete all properties.
+// Delete one or several properties.
+//
+// Read all properties. (open/copy/backup)
+// Read one or several properties.
+//
+// Name via ID.
+// Name via string.
+//
+// Each property set is comprised of three pieces: a fixed-size header,
+// a property ID table and a property-value heap.
+//
+// The header describes the sizes and offsets of the table and heap within the
+// stream. The header is always at offset 0i64. Planning for a future where
+// this format might be exposed to user-space code, the header is included from
+// the OLE property set format and has several additional fields.
+//
+
+typedef struct _PROPERTY_SET_HEADER {
+
+ //
+ // Header from OLE describing version number and containing fields
+ // for format and class Id
+ //
+
+ PROPERTYSETHEADER;
+
+ //
+ // Offset to PropertyIdTable
+ //
+
+ ULONG IdTableOffset;
+
+ //
+ // Offset to PropertyValueHeap
+ //
+
+ ULONG ValueHeapOffset;
+
+} PROPERTY_SET_HEADER, *PPROPERTY_SET_HEADER;
+typedef const PROPERTY_SET_HEADER *PCPROPERTY_SET_HEADER;
+
+#define PSH_FORMAT_VERSION 2
+#define PSH_DWOSVER 0x00020005
+
+#define PROPERTY_ID_TABLE(psh) \
+ ((PPROPERTY_ID_TABLE)Add2Ptr( (psh), (psh)->IdTableOffset ))
+#define PROPERTY_HEAP_HEADER(psh) \
+ ((PPROPERTY_HEAP_HEADER)Add2Ptr( (psh), (psh)->ValueHeapOffset ))
+
+
+//
+// Following the header in the stream is the PropertyIdTable.
+//
+// The Property Id Table allows for a quick mapping of PropertyId to offset
+// within the Property Heap. The table is a sorted (on PropertyId) array of
+// Id/Offset pairs. The table is allowed to contain some extra slots so that
+// insertion of a new element can often be made without shuffling the heap.
+//
+// As an implementation efficiency, the Entry array is addressed in the code in
+// 1-based fashion. The array, however, will always occupy entry [0].
+//
+
+typedef struct _PROPERTY_TABLE_ENTRY {
+
+ //
+ // Property ID used for sorting
+ //
+
+ ULONG PropertyId;
+
+ //
+ // Offset within the property heap of the value of this property
+ //
+
+ ULONG PropertyValueOffset;
+
+} PROPERTY_TABLE_ENTRY, *PPROPERTY_TABLE_ENTRY;
+
+
+typedef struct _PROPERTY_ID_TABLE {
+
+ //
+ // Number of entries that are currently in the table
+ //
+
+ ULONG PropertyCount;
+
+ //
+ // Maximum number of entries that the table could contain
+ //
+
+ ULONG MaximumPropertyCount;
+
+ //
+ // Beginning of the table itself
+ //
+
+ PROPERTY_TABLE_ENTRY Entry[1];
+
+} PROPERTY_ID_TABLE, *PPROPERTY_ID_TABLE;
+
+typedef const PROPERTY_ID_TABLE *PCPROPERTY_ID_TABLE;
+
+#define PIT_PROPERTY_DELTA 0x10
+
+#define PROPERTY_ID_TABLE_SIZE(c) \
+ (VARIABLE_STRUCTURE_SIZE( PROPERTY_ID_TABLE, PROPERTY_TABLE_ENTRY, c ))
+#define PROPERTY_ID_ENTRY(p,i) \
+ ((p)->Entry[i])
+
+
+//
+// Following the PropertyIdTable in the stream is the PropertyValueHeap
+//
+// The PropertyValueHeap is a boundary-tagged heap. The contents of each
+// heap element contains the Length of the element, PropertyId of the element,
+// the unicode string name, if one has been assigned, and the serialized property
+// value of the property.
+//
+// The length of the element may be larger than the data contained within it. This
+// enables replacement of long values with shorter ones without forcing
+// reallocation. When reallocation must take place, the existing heap item is
+// marked with an invalid property Id and a new item is allocated at the end of the
+// heap.
+//
+// Over time, this may result in unused space within the heap. When writing a
+// property set for the first time, the total amount of free space is calculated
+// and stored in the SCB. If that amount is either > 4K or >20% of the size of the
+// stream, a compaction is done.
+//
+// The serialized format of property values is dictated by OLE. This results in a
+// common set of source to manipulate the serialized formats.
+//
+
+typedef struct _PROPERTY_HEAP_ENTRY {
+
+ //
+ // Length of this value in bytes
+ //
+
+ ULONG PropertyValueLength;
+
+ //
+ // Property Id for this heap item. This is used for updating the Property
+ // Table during compaction.
+ //
+
+ ULONG PropertyId;
+
+ //
+ // Length in bytes of the string name. If zero, then no name
+ // is assigned.
+ //
+
+ USHORT PropertyNameLength;
+
+ //
+ // Name, if present
+ //
+
+ WCHAR PropertyName[1];
+
+ //
+ // Following the name, on a DWORD boundary is the SERIALIZEDPROPERTYVALUE.
+ //
+
+} PROPERTY_HEAP_ENTRY, *PPROPERTY_HEAP_ENTRY;
+
+#define PROPERTY_HEAP_ENTRY_SIZE(n,v) \
+ (LongAlign( sizeof( PROPERTY_HEAP_ENTRY ) + (n)) + (v))
+#define PROPERTY_HEAP_ENTRY_VALUE(p) \
+ ((SERIALIZEDPROPERTYVALUE *) LongAlign( Add2Ptr( &(p)->PropertyName[0], (p)->PropertyNameLength )))
+#define PROPERTY_HEAP_ENTRY_VALUE_LENGTH(p) \
+ ((p)->PropertyValueLength - PtrOffset( (p), PROPERTY_HEAP_ENTRY_VALUE( p )))
+#define IS_INDIRECT_VALUE(p) \
+ (IsIndirectVarType( PROPERTY_HEAP_ENTRY_VALUE( (p) )->dwType ))
+
+
+
+//
+// The heap has a header as well, that contains the total size of the heap
+//
+
+typedef struct _PROPERTY_HEAP_HEADER {
+
+ //
+ // Length of the heap, including this structure, in bytes. This must
+ // never span beyond the end of data in the stream.
+ //
+
+ ULONG PropertyHeapLength;
+
+ //
+ // First PropertyHeapEntry
+ //
+
+ PROPERTY_HEAP_ENTRY PropertyHeapEntry[1];
+
+} PROPERTY_HEAP_HEADER, *PPROPERTY_HEAP_HEADER;
+typedef const PROPERTY_HEAP_HEADER *PCPROPERTY_HEAP_HEADER;
+
+#define PROPERTY_HEAP_HEADER_SIZE \
+ (sizeof( PROPERTY_HEAP_HEADER ) - sizeof( PROPERTY_HEAP_ENTRY ))
+
+#define PHH_INITIAL_SIZE 0x80
+
+#define GET_HEAP_ENTRY(phh,off) \
+ ((PPROPERTY_HEAP_ENTRY) Add2Ptr( (phh), (off) ))
+#define FIRST_HEAP_ENTRY(phh) \
+ (&(phh)->PropertyHeapEntry[0])
+#define NEXT_HEAP_ENTRY(pvh) \
+ ((PPROPERTY_HEAP_ENTRY) Add2Ptr( (pvh), (pvh)->PropertyValueLength ))
+#define IS_LAST_HEAP_ENTRY(phh,pvh) \
+ ((PCHAR)(pvh) >= (PCHAR)(phh) + (phh)->PropertyHeapLength)
+
+
+//
+// Debug levels for printing
+//
+
+#define DEBUG_TRACE_PROP_FSCTL 0x00000001
+#define DEBUG_TRACE_READ_PROPERTY 0x00000002
+#define DEBUG_TRACE_WRITE_PROPERTY 0x00000004
+
+
+
+//
+// Runtime structures.
+//
+
+//
+// PROPERTY_INFO is a structure built out of the input, either from
+// PROPERTY_SPECIFIERS or PROPERTY_IDS.
+//
+
+typedef struct _PROPERTY_INFO_ENTRY {
+ PPROPERTY_HEAP_ENTRY Heap;
+ PROPID Id;
+} PROPERTY_INFO_ENTRY;
+
+typedef struct _PROPERTY_INFO {
+ ULONG Count;
+ ULONG TotalIdsSize;
+ ULONG TotalValuesSize;
+ ULONG TotalNamesSize;
+ ULONG TotalIndirectSize;
+ ULONG IndirectCount;
+ PROPERTY_INFO_ENTRY Entries[1];
+} PROPERTY_INFO, *PPROPERTY_INFO;
+
+#define PROPERTY_INFO_SIZE(c) \
+ (VARIABLE_STRUCTURE_SIZE( PROPERTY_INFO, PROPERTY_INFO_ENTRY, c ))
+
+#define PROPERTY_INFO_HEAP_ENTRY(p,i) \
+ ((p)->Entries[(i)].Heap)
+#define PROPERTY_INFO_ID(p,i) \
+ ((p)->Entries[(i)].Id)
+
+
+//
+// PROPERTY_CONTEXT is used to pass a large group of related parameters around.
+//
+
+typedef struct _PROPERTY_CONTEXT {
+ PIRP_CONTEXT IrpContext;
+ OBJECT_HANDLE Object;
+ ATTRIBUTE_HANDLE Attribute;
+ MAP_HANDLE Map;
+ PPROPERTY_SET_HEADER Header;
+ PPROPERTY_ID_TABLE IdTable;
+ PPROPERTY_HEAP_HEADER HeapHeader;
+ PPROPERTY_INFO Info;
+} PROPERTY_CONTEXT, *PPROPERTY_CONTEXT;
+
+#define InitializePropertyContext(C,I,O,A) \
+ { \
+ (C)->IrpContext = (I); \
+ (C)->Object = (O); \
+ (C)->Attribute = (A); \
+ NtOfsInitializeMapHandle( &(C)->Map ); \
+ DebugDoit( (C)->Header = NULL ); \
+ DebugDoit( (C)->IdTable = NULL ); \
+ DebugDoit( (C)->HeapHeader = NULL ); \
+ DebugDoit( (C)->Info = NULL ); \
+ }
+
+#define MapPropertyContext(C) \
+ (NtOfsMapAttribute( \
+ (C)->IrpContext, \
+ (C)->Attribute, \
+ Views0, \
+ (ULONG)(C)->Attribute->Header.FileSize.QuadPart, \
+ &(C)->Header, \
+ &(C)->Map ), \
+ SetPropertyContextPointersFromMap(C))
+
+#define SetPropertyContextPointersFromMap(C) \
+ ((C)->IdTable = PROPERTY_ID_TABLE( (C)->Header ), \
+ (C)->HeapHeader = PROPERTY_HEAP_HEADER( (C)->Header ))
+
+#define CleanupPropertyContext(C) \
+ NtOfsReleaseMap( (C)->IrpContext, &(C)->Map )
+
+#define ContextOffset(C,P) \
+ (PtrOffset( (C)->Map.Buffer, (P) ))
+#define HeapOffset(C,P) \
+ (PtrOffset( (C)->HeapHeader, (P) ))
+
+
+//
+// Default not-found property value header
+//
+
+typedef struct _NOT_FOUND_PROPERTY {
+ //
+ // BEGINNING OF PROPERY_VALUE_HEADER
+ //
+
+ ULONG PropertyValueLength;
+ ULONG PropertyId;
+ USHORT PropertyNameLength;
+ // NO NAME
+ USHORT PadToDWord;
+
+ //
+ // BEGINNING OF SERIALIZED VALUE
+ //
+
+ DWORD dwType;
+} NOT_FOUND_PROPERTY;
+
+extern NOT_FOUND_PROPERTY DefaultEmptyProperty;
+
+#define EMPTY_PROPERTY ((PPROPERTY_HEAP_ENTRY) &DefaultEmptyProperty)
+
+extern LONGLONG Views0;
+
+
+//
+// Function prototypes
+//
+
+//
+// check.c
+//
+
+VOID
+CheckPropertySet (
+ IN PPROPERTY_CONTEXT Context
+ );
+
+VOID
+DumpPropertyData (
+ IN PPROPERTY_CONTEXT Context
+ );
+
+
+//
+// heap.c
+//
+
+ULONG
+FindStringInHeap (
+ IN PPROPERTY_CONTEXT Context,
+ IN PCOUNTED_STRING Name
+ );
+
+VOID
+SetValueInHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_HEAP_ENTRY HeapEntry,
+ IN PROPID Id,
+ IN USHORT NameLength,
+ IN PWCHAR Name,
+ IN ULONG ValueLength,
+ IN SERIALIZEDPROPERTYVALUE *Value
+ );
+
+ULONG
+AddValueToHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id,
+ IN ULONG Length,
+ IN USHORT NameLength,
+ IN PWCHAR Name OPTIONAL,
+ IN ULONG ValueLength,
+ IN SERIALIZEDPROPERTYVALUE *Value
+ );
+
+VOID
+DeleteFromHeap(
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_HEAP_ENTRY HeapEntry
+ );
+
+ULONG
+ChangeHeap (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG HeapEntryOffset,
+ IN PROPID Id,
+ IN USHORT NameLength,
+ IN PWCHAR Name,
+ IN ULONG ValueLength,
+ IN SERIALIZEDPROPERTYVALUE *Value
+ );
+
+
+//
+// initprop.c
+//
+
+VOID
+InitializePropertyData (
+ IN PPROPERTY_CONTEXT Context
+ );
+
+
+//
+// readprop.c
+//
+
+PPROPERTY_INFO
+BuildPropertyInfoFromPropSpec (
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_SPECIFICATIONS Specs,
+ IN PVOID InBufferEnd,
+ IN PROPID NextId
+ );
+
+PPROPERTY_INFO
+BuildPropertyInfoFromIds (
+ IN PPROPERTY_CONTEXT Context,
+ IN PPROPERTY_IDS Ids
+ );
+
+PVOID
+BuildPropertyIds (
+ IN PPROPERTY_INFO Info,
+ OUT PVOID OutBuffer
+ );
+
+VOID
+ReadPropertyData (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT PULONG OutBufferLength,
+ OUT PVOID OutBuffer
+ );
+
+
+//
+// table.c
+//
+
+ULONG
+BinarySearchIdTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID PropertyId
+ );
+
+ULONG
+FindIdInTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID PropertyId
+ );
+
+PROPID
+FindFreeIdInTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id
+ );
+
+VOID
+ChangeTable (
+ IN PPROPERTY_CONTEXT Context,
+ IN PROPID Id,
+ IN ULONG Offset
+ );
+
+
+//
+// writprop.c
+//
+
+VOID
+WritePropertyData (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT PULONG OutBufferLength,
+ OUT PVOID OutBuffer
+ );
+
diff --git a/private/ntos/cntfs/views/views.reg b/private/ntos/cntfs/views/views.reg
new file mode 100644
index 000000000..815fcf3d7
--- /dev/null
+++ b/private/ntos/cntfs/views/views.reg
@@ -0,0 +1,7 @@
+\registry\machine\system\currentcontrolset\services\views
+ Type = REG_DWORD 0x00000002
+ Start = REG_DWORD 0x00000004
+ Group = File system
+ ErrorControl = REG_DWORD 0x00000001
+
+
diff --git a/private/ntos/cntfs/views/writprop.c b/private/ntos/cntfs/views/writprop.c
new file mode 100644
index 000000000..0e4b5489f
--- /dev/null
+++ b/private/ntos/cntfs/views/writprop.c
@@ -0,0 +1,700 @@
+/*++
+
+Copyright (c) 1989-1997 Microsoft Corporation
+
+Module Name:
+
+ writprop.h
+
+Abstract:
+
+ This module contains the write user FsCtl for the Ntfs Property support.
+
+
+--*/
+
+#include <viewprop.h> // needs propset.h and ntfsprop.h
+
+#define Dbg DEBUG_TRACE_PROP_FSCTL
+
+
+VOID
+WritePropertyData (
+ IN PPROPERTY_CONTEXT Context,
+ IN ULONG InBufferLength,
+ IN PVOID InBuffer,
+ OUT PULONG OutBufferLength,
+ OUT PVOID OutBuffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs property write functions.
+
+ We verify the header format and perform the write operation.
+
+
+Arguments:
+
+ Context - Property Context for the call
+
+ InBufferLength - Length of the command buffer
+
+ InBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally.
+
+ OutBufferLength - pointer to ULONG length of output buffer. This value
+ is set on return to indicate the total size of data within the output
+ buffer.
+
+ OutBuffer - pointer to the unverified user command buffer. All access
+ to this needs to be wrapped in try/finally
+
+Return Value:
+
+ Nothing
+
+--*/
+
+{
+ PVOID InBufferEnd = Add2Ptr( InBuffer, InBufferLength );
+ PPROPERTY_INFO Info = NULL;
+
+ try {
+ PPROPERTY_WRITE_CONTROL Control = (PPROPERTY_WRITE_CONTROL) InBuffer;
+ PVOID NextOutput = OutBuffer;
+ ULONG TotalLength = 0;
+ ULONG Count;
+ ULONG i;
+ ULONG Offset;
+ KPROCESSOR_MODE requestorMode;
+
+ //
+ // Map attribute for full size
+ //
+
+ MapPropertyContext( Context );
+
+
+ //
+ // Verify the property set has valid contents.
+ //
+
+ CheckPropertySet( Context );
+
+ //
+ // Simple sanity check of input buffer
+ //
+
+ if (
+ //
+ // Long alignment of all buffers
+ //
+ InBuffer != (PVOID)LongAlign( InBuffer ) ||
+ OutBuffer != (PVOID)LongAlign( OutBuffer ) ||
+
+ //
+ // Room for control at least
+ //
+ InBufferEnd < (PVOID)(Control + 1)
+
+ ) {
+
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+
+ }
+
+ requestorMode = KeGetPreviousMode( );
+ if (requestorMode != KernelMode) {
+
+ //
+ // Verify that the input buffer is readable
+ //
+
+ ProbeForRead( InBuffer, InBufferLength, sizeof( ULONG ));
+ }
+
+ if (Control->Op == PWC_WRITE_PROP) {
+ PPROPERTY_SPECIFICATIONS InSpecs;
+ PPROPERTY_VALUES Values;
+ PROPID NextId = Control->NextPropertyId;
+
+ //
+ // The input buffer is:
+ // PROPERTY_WRITE_CONTROL
+ // PROPERTY_SPECIFICATIONS
+ // PROPERTY_VALUES
+ //
+ // The output buffer will be:
+ // PROPERTY_IDS
+ // INDIRECT_PROPERTIES
+ //
+
+
+ if (requestorMode != KernelMode) {
+ //
+ // Verify that the output buffer is readable
+ //
+
+ ProbeForWrite( OutBuffer, *OutBufferLength, sizeof( ULONG ));
+ }
+
+ //
+ // Build value headers array from specifiers. Calculate size of
+ // property Ids and indirect output.
+ //
+
+ InSpecs = (PPROPERTY_SPECIFICATIONS) (Control + 1);
+ if (InBufferEnd < (PVOID)(InSpecs + 1) ||
+ InBufferEnd < Add2Ptr( InSpecs, PROPERTY_SPECIFICATIONS_SIZE( InSpecs->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Values = (PPROPERTY_VALUES) Add2Ptr( InSpecs, LongAlign( InSpecs->Length ));
+ if (InBufferEnd < (PVOID)(Values + 1) ||
+ InBufferEnd < Add2Ptr( Values, Values->Length ) ||
+ InSpecs->Count != Values->Count) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Info = BuildPropertyInfoFromPropSpec( Context,
+ InSpecs,
+ Values, // End of InSpecs
+ NextId );
+
+ //
+ // Check for enough room on output
+ //
+
+ TotalLength = Info->TotalIdsSize + Info->TotalIndirectSize;
+ if (TotalLength > *OutBufferLength) {
+ PULONG ReturnLength = OutBuffer;
+
+ *ReturnLength = TotalLength;
+ *OutBufferLength = sizeof( ULONG );
+ ExRaiseStatus( STATUS_BUFFER_OVERFLOW );
+ }
+
+
+ //
+ // Build property Ids
+ //
+
+ NextOutput = BuildPropertyIds( Info, OutBuffer );
+
+ //
+ // BUGBUG - build indirect properties
+ //
+
+ //
+ // Walk through the headers array and set the new values, saving away
+ // the new offsets
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ ULONG SerializedValueLength;
+ SERIALIZEDPROPERTYVALUE *SerializedValue;
+ USHORT NameLength;
+ PWCHAR Name;
+ ULONG Length;
+ PPROPERTY_HEAP_ENTRY HeapEntry = PROPERTY_INFO_HEAP_ENTRY( Info, i );
+
+ //
+ // The new values are found in the PROPERTY_VALUES input
+ //
+
+ SerializedValueLength = PROPERTY_VALUE_LENGTH( Values, i );
+ SerializedValue = PROPERTY_VALUE( Values, i );
+
+ //
+ // BUGBUG - handle duplicate sets
+ //
+
+ //
+ // If we've previously found a header, then we are replacing one that
+ // exists previously.
+ //
+
+ if (HeapEntry != EMPTY_PROPERTY) {
+ //
+ // if this property was named just by an Id, then use the name
+ // from the heap entry
+ //
+
+ if (InSpecs->Specifiers[i].Variant == PRSPEC_PROPID) {
+
+ NameLength = HeapEntry->PropertyNameLength;
+ Name = &HeapEntry->PropertyName[0];
+
+ //
+ // Use the name from the property specification
+ //
+
+ } else {
+
+ NameLength = PROPERTY_SPECIFIER_NAME_LENGTH( InSpecs, i );
+ Name = PROPERTY_SPECIFIER_NAME( InSpecs, i );
+ }
+
+ Length = PROPERTY_HEAP_ENTRY_SIZE( NameLength,
+ SerializedValueLength );
+
+ //
+ // If the new length matches that of the original header, then
+ // we can adjust set the values in place
+ //
+
+ if (Length == HeapEntry->PropertyValueLength) {
+ SetValueInHeap( Context, HeapEntry,
+ PROPERTY_INFO_ID( Info, i),
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+
+ //
+ // The lengths don't match. Since we may be using the name
+ // in-place we have to add before deleting. Also, the property
+ // Id needs to be accessed before deleting.
+ //
+
+ } else {
+ Offset =
+ AddValueToHeap( Context, PROPERTY_INFO_ID( Info, i),
+ Length,
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+ ChangeTable( Context, PROPERTY_INFO_ID( Info, i), Offset );
+ DeleteFromHeap( Context, HeapEntry );
+
+ //
+ // update header
+ //
+
+ Info->Entries[i].Heap =
+ GET_HEAP_ENTRY( Context->HeapHeader, Offset );
+ }
+
+ //
+ // We are adding a new property.
+ //
+
+ } else {
+
+ //
+ // If the property was named by an Id, then there is no name to
+ // create
+ //
+
+ if (InSpecs->Specifiers[i].Variant == PRSPEC_PROPID) {
+ NameLength = 0;
+ Name = NULL;
+
+ //
+ // Otherwise, use the specified name and generate an Id
+ //
+
+ } else {
+ NameLength = PROPERTY_SPECIFIER_NAME_LENGTH( InSpecs, i );
+ Name = PROPERTY_SPECIFIER_NAME( InSpecs, i );
+ }
+
+ //
+ // Add the new value to the heap and table
+ //
+
+ Length = PROPERTY_HEAP_ENTRY_SIZE( NameLength,
+ SerializedValueLength );
+ Offset =
+ AddValueToHeap( Context, PROPERTY_INFO_ID( Info, i ), Length,
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+ ChangeTable( Context, PROPERTY_INFO_ID( Info, i ), Offset );
+
+ //
+ // Set new header value
+ //
+
+ Info->Entries[i].Heap = GET_HEAP_ENTRY( Context->HeapHeader, Offset );
+ }
+ }
+
+ // PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PWC_DELETE_PROP) {
+ PPROPERTY_SPECIFICATIONS InSpecs;
+
+ //
+ // The input buffer is:
+ // PROPERTY_WRITE_CONTROL
+ // PROPERTY_SPECIFICATIONS
+ //
+ // The output buffer will be NULL
+ //
+
+ //
+ // Build value headers array from specifiers. Calculate size of
+ // property Ids and indirect output.
+ //
+
+ InSpecs = (PPROPERTY_SPECIFICATIONS) (Control + 1);
+ if (InBufferEnd < (PVOID)(InSpecs + 1) ||
+ InBufferEnd < Add2Ptr( InSpecs, PROPERTY_SPECIFICATIONS_SIZE( InSpecs->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Info = BuildPropertyInfoFromPropSpec( Context,
+ InSpecs,
+ InBufferEnd,
+ PID_ILLEGAL );
+
+ //
+ // No output is necessary
+ //
+
+ TotalLength = 0;
+
+ //
+ // Walk through the headers array and delete the values
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ PPROPERTY_HEAP_ENTRY HeapEntry = PROPERTY_INFO_HEAP_ENTRY( Info, i );
+
+ //
+ // If we found a heap entry then delete it from the heap and from
+ // the IdTable
+ //
+
+ if (HeapEntry != EMPTY_PROPERTY) {
+ ChangeTable( Context, PROPERTY_INFO_ID( Info, i), 0 );
+ DeleteFromHeap( Context, HeapEntry );
+
+ //
+ // update header
+ //
+
+ Info->Entries[i].Heap = EMPTY_PROPERTY;
+ }
+ }
+
+ // PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PWC_WRITE_NAME) {
+ PPROPERTY_IDS Ids;
+ PPROPERTY_NAMES Names;
+
+ //
+ // The input buffer is:
+ // PROPERTY_WRITE_CONTROL
+ // PROPERTY_IDS
+ // PROPERTY_NAMES
+ //
+ // The output buffer will be NULL
+ //
+
+ //
+ // Build value headers array from Ids.
+ //
+
+ Ids = (PPROPERTY_IDS) (Control + 1);
+ if (InBufferEnd < (PVOID)(Ids + 1) ||
+ InBufferEnd < Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Names = (PPROPERTY_NAMES) Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ));
+ if (InBufferEnd < (PVOID)(Names + 1) ||
+ InBufferEnd < Add2Ptr( Names, Names->Length ) ||
+ Ids->Count != Names->Count) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Info = BuildPropertyInfoFromIds( Context, Ids );
+
+ //
+ // No output is necessary
+ //
+
+ TotalLength = 0;
+
+
+ //
+ // Walk through the headers array and set the new names, saving away
+ // the new offsets
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ ULONG SerializedValueLength;
+ SERIALIZEDPROPERTYVALUE *SerializedValue;
+ USHORT NameLength;
+ PWCHAR Name;
+ ULONG Length;
+ PPROPERTY_HEAP_ENTRY HeapEntry =
+ PROPERTY_INFO_HEAP_ENTRY( Info, i );
+
+ //
+ // The old values are found in the heap entry itself
+ //
+
+ SerializedValueLength =
+ PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ SerializedValue = PROPERTY_HEAP_ENTRY_VALUE( HeapEntry );
+
+ //
+ // The new name is found in the input
+ //
+
+ NameLength = (USHORT) PROPERTY_NAME_LENGTH( Names, i );
+ Name = PROPERTY_NAME( Names, i );
+
+ //
+ // Get new length of heap entry
+ //
+
+ Length = PROPERTY_HEAP_ENTRY_SIZE( NameLength,
+ SerializedValueLength );
+
+ //
+ // BUGBUG - handle duplicate sets
+ //
+
+ //
+ // If the new length matches that of the original header, then
+ // we can adjust set the values in place. We do this only if the
+ // property is not the EMPTY_PROPERTY (i.e., no one has set
+ // a value). If someone does specify a property that does not yet
+ // exist, one is created with the empty value.
+ //
+
+ if (HeapEntry != EMPTY_PROPERTY &&
+ Length == HeapEntry->PropertyValueLength) {
+
+ SetValueInHeap( Context, HeapEntry,
+ PROPERTY_INFO_ID( Info, i),
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+
+ //
+ // The lengths don't match. Since we may be using the name
+ // in-place we have to add before deleting.
+ //
+
+ } else {
+ Offset =
+ AddValueToHeap( Context, PROPERTY_INFO_ID( Info, i),
+ Length,
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+ ChangeTable( Context, PROPERTY_INFO_ID( Info, i), Offset );
+ if (HeapEntry != EMPTY_PROPERTY) {
+ DeleteFromHeap( Context, HeapEntry );
+ }
+
+ //
+ // update header
+ //
+
+ Info->Entries[i].Heap =
+ GET_HEAP_ENTRY( Context->HeapHeader, Offset );
+ }
+ }
+
+ // PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PWC_DELETE_NAME) {
+ PPROPERTY_IDS Ids;
+
+ //
+ // The input buffer is:
+ // PROPERTY_WRITE_CONTROL
+ // PROPERTY_IDS
+ //
+ // The output buffer will be NULL
+ //
+
+ //
+ // Build value headers array from Ids.
+ //
+
+ Ids = (PPROPERTY_IDS) (Control + 1);
+ if (InBufferEnd < (PVOID)(Ids + 1) ||
+ InBufferEnd < Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Info = BuildPropertyInfoFromIds( Context, Ids );
+
+ //
+ // No output is necessary
+ //
+
+ TotalLength = 0;
+
+
+ //
+ // Walk through the headers array and delete the names
+ //
+
+ for (i = 0; i < Info->Count; i++) {
+ ULONG SerializedValueLength;
+ SERIALIZEDPROPERTYVALUE *SerializedValue;
+ ULONG Length;
+ PPROPERTY_HEAP_ENTRY HeapEntry = PROPERTY_INFO_HEAP_ENTRY( Info, i );
+
+ //
+ // The old values are found in the heap entry itself
+ //
+
+ SerializedValueLength = PROPERTY_HEAP_ENTRY_VALUE_LENGTH( HeapEntry );
+ SerializedValue = PROPERTY_HEAP_ENTRY_VALUE( HeapEntry );
+
+ //
+ // Get new length of heap entry
+ //
+
+ Length = PROPERTY_HEAP_ENTRY_SIZE( 0, SerializedValueLength );
+
+ //
+ // If the new length matches that of the original header, then
+ // we are deleting a name that isn't there. This is a NOP.
+ //
+
+ if (Length == HeapEntry->PropertyValueLength) {
+
+ NOTHING;
+
+ //
+ // The lengths don't match.
+ //
+
+ } else {
+ Offset =
+ AddValueToHeap( Context, PROPERTY_INFO_ID( Info, i),
+ Length,
+ 0, NULL,
+ SerializedValueLength, SerializedValue );
+ ChangeTable( Context, PROPERTY_INFO_ID( Info, i), Offset );
+ if (HeapEntry != EMPTY_PROPERTY) {
+ DeleteFromHeap( Context, HeapEntry );
+ }
+
+ //
+ // update header
+ //
+
+ Info->Entries[i].Heap =
+ GET_HEAP_ENTRY( Context->HeapHeader, Offset );
+ }
+ }
+
+ // PROPASSERT( PtrOffset( OutBuffer, NextOutput ) == TotalLength );
+
+ } else if (Control->Op == PWC_WRITE_ALL) {
+ PPROPERTY_IDS Ids;
+ PPROPERTY_NAMES Names;
+ PPROPERTY_VALUES Values;
+
+ //
+ // The input buffer is:
+ // PROPERTY_WRITE_CONTROL
+ // PROPERTY_IDS
+ // PROPERTY_NAMES
+ // PROPERTY_VALUES
+ //
+ // The output buffer will be NULL
+ //
+
+ //
+ // Get and validate pointers to data blocks. We still need to validate
+ // pointers and offsets as we read the data out.
+ //
+
+ Ids = (PPROPERTY_IDS) (Control + 1);
+ if (InBufferEnd < (PVOID)(Ids + 1) ||
+ InBufferEnd < Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ))) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Names = (PPROPERTY_NAMES) Add2Ptr( Ids, PROPERTY_IDS_SIZE( Ids->Count ));
+ if (InBufferEnd < (PVOID)(Names + 1) ||
+ InBufferEnd < Add2Ptr( Names, Names->Length ) ||
+ Ids->Count != Names->Count) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ Values = (PPROPERTY_VALUES) LongAlign( Add2Ptr( Names, Names->Length ));
+ if (InBufferEnd < (PVOID)(Values + 1) ||
+ InBufferEnd < Add2Ptr( Values, Values->Length ) ||
+ Ids->Count != Values->Count) {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ //
+ // Initialize the property set
+ //
+
+ InitializePropertyData( Context );
+ SetPropertyContextPointersFromMap( Context );
+
+
+ for (i = 0; i < Ids->Count; i++) {
+ ULONG SerializedValueLength;
+ SERIALIZEDPROPERTYVALUE *SerializedValue;
+ USHORT NameLength;
+ PWCHAR Name;
+ ULONG Length;
+
+ //
+ // The values are found in the PROPERTY_VALUES input
+ //
+
+ SerializedValueLength = PROPERTY_VALUE_LENGTH( Values, i );
+ SerializedValue = PROPERTY_VALUE( Values, i );
+
+ //
+ // The name is found in the PROPERTY_NAMES input
+ //
+
+ NameLength = (USHORT) PROPERTY_NAME_LENGTH( Names, i);
+ Name = PROPERTY_NAME( Names, i);
+
+ Length = PROPERTY_HEAP_ENTRY_SIZE( NameLength,
+ SerializedValueLength );
+
+ //
+ // BUGBUG - handle duplicate sets
+ //
+
+ //
+ // add id name and value
+ //
+
+ Offset =
+ AddValueToHeap( Context, PROPERTY_ID( Ids, i ), Length,
+ NameLength, Name,
+ SerializedValueLength, SerializedValue );
+ ChangeTable( Context, PROPERTY_ID( Ids, i ), Offset );
+ }
+
+ //
+ // No output
+ //
+
+ TotalLength = 0;
+
+
+ } else {
+ ExRaiseStatus( STATUS_INVALID_USER_BUFFER );
+ }
+
+ *OutBufferLength = TotalLength;
+
+ } finally {
+
+ if (Info != NULL) {
+ NtfsFreePool( Info );
+ }
+ }
+}
+
diff --git a/private/ntos/cntfs/viewsup.c b/private/ntos/cntfs/viewsup.c
new file mode 100644
index 000000000..ce9e0d4c8
--- /dev/null
+++ b/private/ntos/cntfs/viewsup.c
@@ -0,0 +1,2293 @@
+/*++
+
+Copyright (c) 1996 Microsoft Corporation
+
+Module Name:
+
+ ViewSup.c
+
+Abstract:
+
+ This module implements the Index management routines for NtOfs
+
+Author:
+
+ Tom Miller [TomM] 5-Jan-1996
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+#include "Index.h"
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('vFtN')
+
+//
+// Temporary definitions for test
+//
+
+BOOLEAN NtOfsDoIndexTest = TRUE;
+BOOLEAN NtOfsLeaveTestIndex = FALSE;
+extern ATTRIBUTE_DEFINITION_COLUMNS NtfsAttributeDefinitions[];
+
+//
+// Define a context for NtOfsReadRecords, which is primarily an IndexContext
+// and a copy of the last Key returned.
+//
+
+typedef struct _READ_CONTEXT {
+
+ //
+ // IndexContext (cursor) for the enumeration.
+ //
+
+ INDEX_CONTEXT IndexContext;
+
+ //
+ // The last key returned is allocated from paged pool. We have to
+ // separately record how much is allocated, and how long the current
+ // key is using, the latter being in the KeyLength field of IndexKey.
+ // SmallKeyBuffer will store a small key in this structure without going
+ // to pool.
+ //
+
+ INDEX_KEY LastReturnedKey;
+ ULONG AllocatedKeyLength;
+ ULONG SmallKeyBuffer[3];
+
+} READ_CONTEXT, *PREAD_CONTEXT;
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtOfsCreateIndex)
+#pragma alloc_text(PAGE, NtOfsCloseIndex)
+#pragma alloc_text(PAGE, NtOfsDeleteIndex)
+#pragma alloc_text(PAGE, NtOfsFindRecord)
+#pragma alloc_text(PAGE, NtOfsAddRecords)
+#pragma alloc_text(PAGE, NtOfsDeleteRecords)
+#pragma alloc_text(PAGE, NtOfsUpdateRecord)
+#pragma alloc_text(PAGE, NtOfsReadRecords)
+#pragma alloc_text(PAGE, NtOfsFreeReadContext)
+#pragma alloc_text(PAGE, NtOfsCollateUlong)
+#pragma alloc_text(PAGE, NtOfsCollateUnicode)
+#pragma alloc_text(PAGE, NtOfsMatchAll)
+#pragma alloc_text(PAGE, NtOfsMatchUlongExact)
+#pragma alloc_text(PAGE, NtOfsMatchUnicodeExpression)
+#pragma alloc_text(PAGE, NtOfsMatchUnicodeString)
+#endif
+
+
+NTFSAPI
+NTSTATUS
+NtOfsCreateIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN UNICODE_STRING Name,
+ IN CREATE_OPTIONS CreateOptions,
+ IN ULONG DeleteCollationData,
+ IN PCOLLATION_FUNCTION CollationFunction,
+ IN PVOID CollationData OPTIONAL,
+ OUT PSCB *Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to create / open a view index
+ within a given file for a given CollationRule.
+
+Arguments:
+
+ Fcb - File in which the index is to be created.
+
+ Name - Name of the index for all related Scbs and attributes on disk.
+
+ CreateOptions - Standard create flags.
+
+ DeleteCollationData - Specifies 1 if the NtfsFreePool should be called
+ for CollationData when no longer required, or 0
+ if NtfsFreePool should never be called.
+
+ CollationFunction - Function to be called to collate the index.
+
+ CollationData - Data pointer to be passed to CollationFunction.
+
+ Scb - Returns an Scb as handle for the index.
+
+Return Value:
+
+ STATUS_OBJECT_NAME_COLLISION -- if CreateNew and index already exists
+ STATUS_OBJECT_NAME_NOT_FOUND -- if OpenExisting and index does not exist
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ ULONG idx;
+ BOOLEAN FoundAttribute;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ struct {
+ INDEX_ROOT IndexRoot;
+ INDEX_ENTRY EndEntry;
+ } R;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ PAGED_CODE();
+
+ //
+ // First we will initialize the Index Root structure which is the value
+ // of the attribute we need to create.
+ //
+
+ RtlZeroMemory( &R, sizeof(R) );
+
+ R.IndexRoot.BytesPerIndexBuffer = NTOFS_VIEW_INDEX_BUFFER_SIZE;
+
+ R.IndexRoot.BlocksPerIndexBuffer = (UCHAR)ClustersFromBytes( Fcb->Vcb,
+ NTOFS_VIEW_INDEX_BUFFER_SIZE );
+
+ if (NTOFS_VIEW_INDEX_BUFFER_SIZE < Fcb->Vcb->BytesPerCluster) {
+
+ R.IndexRoot.BlocksPerIndexBuffer = NTOFS_VIEW_INDEX_BUFFER_SIZE / DEFAULT_INDEX_BLOCK_SIZE;
+ }
+
+ R.IndexRoot.IndexHeader.FirstIndexEntry = QuadAlign(sizeof(INDEX_HEADER));
+ R.IndexRoot.IndexHeader.FirstFreeByte =
+ R.IndexRoot.IndexHeader.BytesAvailable = QuadAlign(sizeof(INDEX_HEADER)) +
+ QuadAlign(sizeof(INDEX_ENTRY));
+
+ //
+ // Now we need to put in the special End entry.
+ //
+
+ R.EndEntry.Length = sizeof(INDEX_ENTRY);
+ SetFlag( R.EndEntry.Flags, INDEX_ENTRY_END );
+
+
+ //
+ // Now, just create the Index Root Attribute.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ NtfsAcquireExclusiveFcb( IrpContext, Fcb, NULL, FALSE, FALSE );
+
+ try {
+
+ //
+ // First see if the index already exists, by searching for the root
+ // attribute.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $INDEX_ROOT,
+ &Name,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ //
+ // If it is not there, and the CreateOptions allow, then let's create
+ // the index root now. (First cleaning up the attribute context from
+ // the lookup).
+ //
+
+ if (!FoundAttribute && (CreateOptions <= CREATE_OR_OPEN)) {
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Fcb,
+ $INDEX_ROOT,
+ &Name,
+ &R,
+ sizeof(R),
+ 0,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ //
+ // If the index is already there, and we were asked to create it, then
+ // return an error.
+ //
+
+ } else if (FoundAttribute && (CreateOptions == CREATE_NEW)) {
+
+ try_return( Status = STATUS_OBJECT_NAME_COLLISION );
+
+ //
+ // If the index is not there, and we were supposed to open existing, then
+ // return an error.
+ //
+
+ } else if (!FoundAttribute && (CreateOptions == OPEN_EXISTING)) {
+
+ try_return( Status = STATUS_OBJECT_NAME_NOT_FOUND );
+ }
+
+ //
+ // Otherwise create/find the Scb and reference it.
+ //
+
+ *Scb = NtfsCreateScb( IrpContext, Fcb, $INDEX_ALLOCATION, &Name, FALSE, NULL );
+ SetFlag( (*Scb)->ScbState, SCB_STATE_VIEW_INDEX );
+ (*Scb)->ScbType.Index.CollationFunction = CollationFunction;
+
+ //
+ // Handle the case where CollationData is to be deleted.
+ //
+
+ if (DeleteCollationData) {
+ SetFlag((*Scb)->ScbState, SCB_STATE_DELETE_COLLATION_DATA);
+ if ((*Scb)->ScbType.Index.CollationData != NULL) {
+ NtfsFreePool(CollationData);
+ } else {
+ (*Scb)->ScbType.Index.CollationData = CollationData;
+ }
+
+ //
+ // Otherwise just jam the pointer the caller passed.
+ //
+
+ } else {
+ (*Scb)->ScbType.Index.CollationData = CollationData;
+ }
+
+ NtfsIncrementCloseCounts( *Scb, TRUE, FALSE );
+
+ try_exit: NOTHING;
+
+ } finally {
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ NtfsReleaseFcb( IrpContext, Fcb );
+ }
+
+ return Status;
+}
+
+
+NTFSAPI
+VOID
+NtOfsCloseIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to close a previously returned handle on a view index.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ NtfsAcquireExclusiveFcb( IrpContext, Scb->Fcb, NULL, TRUE, FALSE );
+ NtfsDecrementCloseCounts( IrpContext, Scb, NULL, TRUE, FALSE, FALSE );
+ NtfsReleaseFcb( IrpContext, Scb->Fcb );
+}
+
+
+NTFSAPI
+VOID
+NtOfsDeleteIndex (
+ IN PIRP_CONTEXT IrpContext,
+ IN PFCB Fcb,
+ IN PSCB Scb
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete an index.
+
+Arguments:
+
+ Fcb - Supplies an Fcb as the previously returned object handle for the file
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+Return Value:
+
+ None (Deleting a nonexistant index is benign).
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT LocalContext;
+ ATTRIBUTE_TYPE_CODE AttributeTypeCode;
+ BOOLEAN FoundAttribute;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ ASSERT(($BITMAP - $INDEX_ALLOCATION) == ($INDEX_ALLOCATION - $INDEX_ROOT));
+
+ PAGED_CODE();
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // First see if there is some index allocation, and if so truncate it
+ // away allowing this operation to be broken up.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ if (NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ $INDEX_ALLOCATION,
+ &Scb->AttributeName,
+ NULL,
+ FALSE,
+ &LocalContext )) {
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, TRUE );
+
+ NtfsDeleteAllocation( IrpContext, NULL, Scb, 0, MAXLONGLONG, TRUE, TRUE );
+ }
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ for (AttributeTypeCode = $INDEX_ROOT;
+ AttributeTypeCode <= $BITMAP;
+ AttributeTypeCode += ($INDEX_ALLOCATION - $INDEX_ROOT)) {
+
+ //
+ // Initialize the attribute context on each trip through the loop.
+ //
+
+ NtfsInitializeAttributeContext( &LocalContext );
+
+ //
+ // First see if the index already exists, by searching for the root
+ // attribute.
+ //
+
+ FoundAttribute = NtfsLookupAttributeByName( IrpContext,
+ Fcb,
+ &Fcb->FileReference,
+ AttributeTypeCode,
+ &Scb->AttributeName,
+ NULL,
+ TRUE,
+ &LocalContext );
+
+ //
+ // Loop while we see the right records.
+ //
+
+ while (FoundAttribute) {
+
+ NtfsDeleteAttributeRecord( IrpContext, Fcb, TRUE, FALSE, &LocalContext );
+
+ FoundAttribute = NtfsLookupNextAttributeByName( IrpContext,
+ Fcb,
+ AttributeTypeCode,
+ &Scb->AttributeName,
+ TRUE,
+ &LocalContext );
+ }
+
+ NtfsCleanupAttributeContext( &LocalContext );
+ }
+
+ SetFlag( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED );
+
+ } finally {
+
+ NtfsCleanupAttributeContext( &LocalContext );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+}
+
+
+NTFSAPI
+NTSTATUS
+NtOfsFindRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_KEY IndexKey,
+ OUT PINDEX_ROW IndexRow,
+ OUT PMAP_HANDLE MapHandle,
+ IN OUT PQUICK_INDEX_HINT QuickIndexHint OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to find the first occurrence of a key in an index,
+ and return cached information which may can accelerate the update on the data
+ for that key if the index buffer is not changed.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ IndexKey - Supplies the key to find.
+
+ IndexRow - Returns a description of the Key and Data *in place*, for read-only
+ access, valid only until the Bcb is unpinned. (Neither key nor
+ data may be modified in place!)
+
+ MapHandle - Returns a map handle for accessing the key and data directly.
+
+ QuickIndexHint - Supplies a previously returned hint, or all zeros on first use.
+ Returns location information which may be held an arbitrary
+ amount of time, which can accelerate a subsequent call to
+ NtOfsUpdateRecord for the data in this key, iff changes to
+ the index do not prohibit use of this hint.
+
+Return Value:
+
+ STATUS_SUCCESS -- if operation was successful.
+ STATUS_NO_MATCH -- if the specified key does not exist.
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PINDEX_LOOKUP_STACK Sp;
+ PINDEX_ENTRY IndexEntry;
+ NTSTATUS Status;
+ PQUICK_INDEX QuickIndex = (PQUICK_INDEX)QuickIndexHint;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ Status = STATUS_SUCCESS;
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ ASSERT_SHARED_SCB( Scb );
+
+ try {
+
+ //
+ // Use the second location in the index context to perform the
+ // read.
+ //
+
+ Sp =
+ IndexContext.Current = IndexContext.Base + 1;
+
+ //
+ // If the index entry for this filename hasn't moved we can go
+ // directly to the location in the buffer. For this to be the case the
+ // following must be true.
+ //
+ // - The entry must already be in an index buffer (BufferOffset test)
+ // - The index stream may not have been truncated (ChangeCount test)
+ // - The Lsn in the page can't have changed
+ //
+
+ if (ARGUMENT_PRESENT( QuickIndexHint ) &&
+ (QuickIndex->BufferOffset != 0) &&
+ (QuickIndex->ChangeCount == Scb->ScbType.Index.ChangeCount)) {
+
+ ReadIndexBuffer( IrpContext,
+ Scb,
+ QuickIndex->IndexBlock,
+ FALSE,
+ Sp );
+
+ //
+ // If the Lsn matches then we can use this buffer directly.
+ //
+
+ if (QuickIndex->CapturedLsn.QuadPart == Sp->CapturedLsn.QuadPart) {
+
+ Sp->IndexEntry = (PINDEX_ENTRY) Add2Ptr( Sp->StartOfBuffer,
+ QuickIndex->BufferOffset );
+
+ //
+ // Otherwise we need to reinitialize the index context and take
+ // the long path below.
+ //
+
+ } else {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ NtfsInitializeIndexContext( &IndexContext );
+ }
+ }
+
+ //
+ // If we did not get the index entry via the hint, get it now.
+ //
+
+ if (Sp->Bcb == NULL) {
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ IndexKey,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (!FindNextIndexEntry( IrpContext,
+ Scb,
+ IndexKey,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ try_return( Status = STATUS_NO_MATCH );
+ }
+ }
+
+ //
+ // If we found the key in the base, then return the Bcb from the
+ // attribute context and return no hint (BufferOffset = 0).
+ //
+
+ if (IndexContext.Current == IndexContext.Base) {
+
+ MapHandle->Buffer = NULL;
+ MapHandle->Bcb = NtfsFoundBcb(&IndexContext.AttributeContext);
+ NtfsFoundBcb(&IndexContext.AttributeContext) = NULL;
+
+ if (ARGUMENT_PRESENT( QuickIndexHint )) {
+ QuickIndex->BufferOffset = 0;
+ }
+
+ //
+ // If we found the key in an index buffer, then return the Bcb from
+ // the lookup stack, and record the hint for the caller.
+ //
+
+ } else {
+
+ Sp = IndexContext.Current;
+
+ MapHandle->Buffer = Sp->StartOfBuffer;
+ MapHandle->Bcb = Sp->Bcb;
+ Sp->Bcb = NULL;
+
+ if (ARGUMENT_PRESENT( QuickIndexHint )) {
+ QuickIndex->ChangeCount = Scb->ScbType.Index.ChangeCount;
+ QuickIndex->BufferOffset = PtrOffset( Sp->StartOfBuffer, Sp->IndexEntry );
+ QuickIndex->CapturedLsn = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->Lsn;
+ QuickIndex->IndexBlock = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->ThisBlock;
+ }
+ }
+
+ //
+ // Complete the MapHandle to disallow pinning.
+ //
+
+ MapHandle->FileOffset = MAXLONGLONG;
+ MapHandle->Length = MAXULONG;
+
+ //
+ // Return the IndexRow described directly in the buffer.
+ //
+
+ IndexEntry = IndexContext.Current->IndexEntry;
+ IndexRow->KeyPart.Key = (IndexEntry + 1);
+ IndexRow->KeyPart.KeyLength = IndexEntry->AttributeLength;
+ IndexRow->DataPart.Data = Add2Ptr( IndexEntry, IndexEntry->DataOffset );
+ IndexRow->DataPart.DataLength = IndexEntry->DataLength;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+
+ }
+
+ return Status;
+}
+
+
+NTFSAPI
+NTSTATUS
+NtOfsFindLastRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN PINDEX_KEY MaxIndexKey,
+ OUT PINDEX_ROW IndexRow,
+ OUT PMAP_HANDLE MapHandle
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to find the highest key in an index.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ MaxIndexKey - Supplies the maximum possible key value (such as MAXULONG, etc.),
+ and this key must not actually be in use!
+
+ IndexRow - Returns a description of the Key and Data *in place*, for read-only
+ access, valid only until the Bcb is unpinned. (Neither key nor
+ data may be modified in place!)
+
+ MapHandle - Returns a map handle for accessing the key and data directly.
+
+Return Value:
+
+ STATUS_SUCCESS -- if operation was successful.
+ STATUS_NO_MATCH -- if the specified key does not exist (index is empty).
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PINDEX_LOOKUP_STACK Sp;
+ PINDEX_ENTRY IndexEntry, NextIndexEntry;
+ NTSTATUS Status;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ Status = STATUS_SUCCESS;
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // Slide down the "right" side of the tree.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ MaxIndexKey,
+ &IndexContext );
+
+ //
+ // If this happens, the index must be empty.
+ //
+
+ Sp = IndexContext.Current;
+ IndexEntry = NtfsFirstIndexEntry(Sp->IndexHeader);
+ if (FlagOn(IndexEntry->Flags, INDEX_ENTRY_END)) {
+ try_return( Status = STATUS_NO_MATCH );
+ }
+
+ //
+ // If we found the key in the base, then return the Bcb from the
+ // attribute context and return no hint (BufferOffset = 0).
+ //
+
+ if (IndexContext.Current == IndexContext.Base) {
+
+ MapHandle->Bcb = NtfsFoundBcb(&IndexContext.AttributeContext);
+ NtfsFoundBcb(&IndexContext.AttributeContext) = NULL;
+
+ //
+ // If we found the key in an index buffer, then return the Bcb from
+ // the lookup stack, and record the hint for the caller.
+ //
+
+ } else {
+
+
+ MapHandle->Bcb = Sp->Bcb;
+ Sp->Bcb = NULL;
+ }
+
+ //
+ // Complete the MapHandle to disallow pinning.
+ //
+
+ MapHandle->FileOffset = MAXLONGLONG;
+ MapHandle->Length = MAXULONG;
+ MapHandle->Buffer = NULL;
+
+ //
+ // Now rescan the last buffer to return the second to last index entry,
+ // if there is one.
+ //
+
+ NextIndexEntry = IndexEntry;
+ do {
+ IndexEntry = NextIndexEntry;
+ NextIndexEntry = NtfsNextIndexEntry(IndexEntry);
+ } while (!FlagOn(NextIndexEntry->Flags, INDEX_ENTRY_END));
+
+ //
+ // Return the IndexRow described directly in the buffer.
+ //
+
+ IndexRow->KeyPart.Key = (IndexEntry + 1);
+ IndexRow->KeyPart.KeyLength = IndexEntry->AttributeLength;
+ IndexRow->DataPart.Data = Add2Ptr( IndexEntry, IndexEntry->DataOffset );
+ IndexRow->DataPart.DataLength = IndexEntry->DataLength;
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ return Status;
+}
+
+
+NTFSAPI
+VOID
+NtOfsAddRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ULONG Count,
+ IN PINDEX_ROW IndexRow,
+ IN ULONG SequentialInsertMode
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to add one or more records to an index.
+
+ If SequentialInsertMode is nonzero, this is a hint to the index package
+ to keep all BTree buffers as full as possible, by splitting as close to
+ the end of the buffer as possible. If specified as zero, random inserts
+ are assumed, and buffers are always split in the middle for better balance.
+
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ Count - Supplies the number of records being added.
+
+ IndexRow - Supplies an array of Count entries, containing the Keys and Data to add.
+
+ SequentialInsertMode - If specified as nozero, the implementation may choose to
+ split all index buffers at the end for maximum fill.
+
+Return Value:
+
+ None.
+
+Raises:
+
+ STATUS_DUPLICATE_NAME -- if the specified key already exists.
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ struct {
+ INDEX_ENTRY IndexEntry;
+ PVOID Key;
+ PVOID Data;
+ } IE;
+ ULONG i;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ UNREFERENCED_PARAMETER(SequentialInsertMode);
+
+ PAGED_CODE();
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // Loop to add all entries
+ //
+
+ for (i = 0; i < Count; i++) {
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ &IndexRow->KeyPart,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ &IndexRow->KeyPart,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DUPLICATE_NAME, NULL, NULL );
+ }
+
+ //
+ // Initialize the Index Entry in pointer form.
+ //
+ // Note that the final index entry ends up looking like this:
+ //
+ // (IndexEntry)(Key)(Data)
+ //
+ // where all fields are long-aligned and:
+ //
+ // Key is at IndexEntry + sizeof(INDEX_ENTRY), and of length AttributeLength
+ // Data is at IndexEntry + DataOffset and of length DataLength
+ //
+
+ IE.IndexEntry.AttributeLength = (USHORT)IndexRow->KeyPart.KeyLength;
+
+ IE.IndexEntry.DataOffset = (USHORT)(sizeof(INDEX_ENTRY) +
+ LongAlign(IndexRow->KeyPart.KeyLength));
+
+ IE.IndexEntry.DataLength = (USHORT)IndexRow->DataPart.DataLength;
+ IE.IndexEntry.ReservedForZero = 0;
+
+ IE.IndexEntry.Length = (USHORT)(QuadAlign(IE.IndexEntry.DataOffset +
+ IndexRow->DataPart.DataLength));
+
+ IE.IndexEntry.Flags = INDEX_ENTRY_POINTER_FORM;
+ IE.IndexEntry.Reserved = 0;
+ IE.Key = IndexRow->KeyPart.Key;
+ IE.Data = IndexRow->DataPart.Data;
+
+ //
+ // Now add it to the index. We can only add to a leaf, so force our
+ // position back to the correct spot in a leaf first.
+ //
+
+ IndexContext.Current = IndexContext.Top;
+ AddToIndex( IrpContext, Scb, (PINDEX_ENTRY)&IE, &IndexContext, NULL );
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ IndexRow += 1;
+ }
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+}
+
+
+NTFSAPI
+VOID
+NtOfsDeleteRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ULONG Count,
+ IN PINDEX_KEY IndexKey
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to delete one or more records from an index.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ Count - Supplies the number of records being deleted.
+
+ IndexKey - Supplies an array of Count entries, containing the Keys to be deleted.
+
+Return Value:
+
+ None. (This call is benign if any records do not exist.)
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ ULONG i;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // Loop to add all entries
+ //
+
+ for (i = 0; i < Count; i++) {
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ IndexKey,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ IndexKey,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ //
+ // Delete it.
+ //
+
+ DeleteFromIndex( IrpContext, Scb, &IndexContext );
+ }
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ IndexKey += 1;
+ }
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+}
+
+
+NTFSAPI
+VOID
+NtOfsUpdateRecord (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN ULONG Count,
+ IN PINDEX_ROW IndexRow,
+ IN OUT PQUICK_INDEX_HINT QuickIndexHint OPTIONAL,
+ IN OUT PMAP_HANDLE MapHandle OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to update the data portion of a record in an index.
+
+ If QuickIndexHint is specified, then the update may occur by directly accessing
+ the buffer containing the specified key, iff other changes to the index do not
+ prevent that. If changes prevent the quick update, then the record is looked
+ up by key in order to perform the data update.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ Count - Supplies the count of updates described in IndexRow. For counts
+ greater than 1, QuickIndexHint and MapHandle must not be supplied.
+
+ IndexRow - Supplies the key to be updated and the new data for that key.
+
+ QuickIndexHint - Supplies a optional quick index for this row returned from a previous
+ call to NtOfsFindRecord, updated on return.
+
+ MapHandle - Supplies an optional MapHandle to accompany the QuickIndex. If MapHandle
+ is supplied, then the QuickIndexHint must be guaranteed valid. MapHandle
+ is updated (pinned) on return.
+
+ MapHandle is ignored if QuickIndexHint is not specified.
+
+Return Value:
+
+ None.
+
+Raises:
+
+ STATUS_INFO_LENGTH_MISMATCH -- if the specified data is a different length from the
+ data in the key.
+ STATUS_NO_MATCH -- if the specified key does not exist.
+
+--*/
+
+{
+ INDEX_CONTEXT IndexContext;
+ PQUICK_INDEX QuickIndex = (PQUICK_INDEX)QuickIndexHint;
+ PVOID DataInIndex;
+ PINDEX_ENTRY IndexEntry;
+ PVCB Vcb = Scb->Vcb;
+ PINDEX_LOOKUP_STACK Sp;
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+ ASSERT_SHARED_SCB( Scb );
+
+ ASSERT(Count != 0);
+
+ PAGED_CODE();
+
+ try {
+
+ //
+ // If the index entry for this filename hasn't moved we can go
+ // directly to the location in the buffer. For this to be the case the
+ // following must be true.
+ //
+ // - The entry must already be in an index buffer (BufferOffset test)
+ // - The index stream may not have been truncated (ChangeCount test)
+ // - The Lsn in the page can't have changed
+ //
+
+ if (ARGUMENT_PRESENT( QuickIndexHint ) &&
+ (QuickIndex->BufferOffset != 0) &&
+ (QuickIndex->ChangeCount == Scb->ScbType.Index.ChangeCount)) {
+
+ ASSERT(Count == 1);
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ //
+ // Use the top location in the index context to perform the
+ // read.
+ //
+
+ Sp = IndexContext.Base;
+
+ //
+ // If we have a MapHandle already, we do not need to read the
+ // IndexBuffer.
+ //
+
+ if (ARGUMENT_PRESENT(MapHandle)) {
+
+ IndexBuffer = MapHandle->Buffer;
+ Sp->Bcb = MapHandle->Bcb;
+ MapHandle->Bcb = NULL;
+ Sp->CapturedLsn.QuadPart = QuickIndex->CapturedLsn.QuadPart;
+
+ } else {
+
+ ReadIndexBuffer( IrpContext,
+ Scb,
+ QuickIndex->IndexBlock,
+ FALSE,
+ Sp );
+
+ IndexBuffer = Sp->StartOfBuffer;
+ }
+
+ //
+ // If the Lsn matches then we can use this buffer directly.
+ //
+
+ if (QuickIndex->CapturedLsn.QuadPart == Sp->CapturedLsn.QuadPart) {
+
+ IndexEntry = (PINDEX_ENTRY) Add2Ptr( IndexBuffer, QuickIndex->BufferOffset );
+
+ if (IndexEntry->DataLength < IndexRow->DataPart.DataLength) {
+ NtfsRaiseStatus( IrpContext, STATUS_INFO_LENGTH_MISMATCH, NULL, NULL );
+ }
+
+ DataInIndex = Add2Ptr( IndexEntry, IndexEntry->DataOffset );
+
+ //
+ // Pin the index buffer
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ //
+ // Write the log record, but do not update the IndexBuffer Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // Indexbuffer->Lsn =
+ //
+
+ // ASSERT(Scb->ScbType.Index.ClustersPerIndexBuffer != 0);
+
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ UpdateRecordDataAllocation,
+ IndexRow->DataPart.Data,
+ IndexRow->DataPart.DataLength,
+ UpdateRecordDataAllocation,
+ DataInIndex,
+ IndexRow->DataPart.DataLength,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ QuickIndex->BufferOffset,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtOfsRestartUpdateDataInIndex( IndexEntry,
+ IndexRow->DataPart.Data,
+ IndexRow->DataPart.DataLength );
+
+ //
+ // If there is a MapHandle, we must update the Bcb pointer.
+ //
+
+ if (ARGUMENT_PRESENT(MapHandle)) {
+
+ MapHandle->Bcb = Sp->Bcb;
+ Sp->Bcb = NULL;
+ }
+
+ try_return( NOTHING );
+
+ //
+ // Otherwise we need to unpin the Bcb and take
+ // the long path below.
+ //
+
+ } else {
+
+ ASSERT(!ARGUMENT_PRESENT(MapHandle));
+ NtfsUnpinBcb( &Sp->Bcb );
+ }
+ }
+
+ //
+ // Loop to apply all updates.
+ //
+
+ do {
+
+ NtfsInitializeIndexContext( &IndexContext );
+
+ //
+ // Position to first possible match.
+ //
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ &IndexRow->KeyPart,
+ &IndexContext );
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (FindNextIndexEntry( IrpContext,
+ Scb,
+ &IndexRow->KeyPart,
+ FALSE,
+ FALSE,
+ &IndexContext,
+ FALSE,
+ NULL )) {
+
+ //
+ // Point to the index entry and the data within it.
+ //
+
+ IndexEntry = IndexContext.Current->IndexEntry;
+
+ if (IndexEntry->DataLength < IndexRow->DataPart.DataLength) {
+ NtfsRaiseStatus( IrpContext, STATUS_INFO_LENGTH_MISMATCH, NULL, NULL );
+ }
+
+ DataInIndex = Add2Ptr( IndexEntry, IndexEntry->DataOffset );
+
+ //
+ // Now pin the entry.
+ //
+
+ if (IndexContext.Current == IndexContext.Base) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ PATTRIBUTE_ENUMERATION_CONTEXT Context = &IndexContext.AttributeContext;
+
+ //
+ // Pin the root
+ //
+
+ NtfsPinMappedAttribute( IrpContext,
+ Vcb,
+ Context );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ FileRecord = NtfsContainingFileRecord(Context);
+ Attribute = NtfsFoundAttribute(Context);
+
+ //
+ // Write the log record, but do not update the FileRecord Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // FileRecord->Lsn =
+ //
+
+ NtfsWriteLog( IrpContext,
+ Vcb->MftScb,
+ NtfsFoundBcb(Context),
+ UpdateRecordDataRoot,
+ IndexRow->DataPart.Data,
+ IndexRow->DataPart.DataLength,
+ UpdateRecordDataRoot,
+ DataInIndex,
+ IndexRow->DataPart.DataLength,
+ NtfsMftOffset( Context ),
+ (PCHAR)Attribute - (PCHAR)FileRecord,
+ (PCHAR)IndexEntry - (PCHAR)Attribute,
+ Vcb->BytesPerFileRecordSegment );
+
+ if (ARGUMENT_PRESENT( QuickIndexHint )) {
+
+ ASSERT( Count == 1 );
+ QuickIndex->BufferOffset = 0;
+ }
+
+ } else {
+
+ PINDEX_ALLOCATION_BUFFER IndexBuffer;
+
+ Sp = IndexContext.Current;
+ IndexBuffer = (PINDEX_ALLOCATION_BUFFER)Sp->StartOfBuffer;
+
+ //
+ // Pin the index buffer
+ //
+
+ NtfsPinMappedData( IrpContext,
+ Scb,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ Scb->ScbType.Index.BytesPerIndexBuffer,
+ &Sp->Bcb );
+
+ //
+ // Write a log record to change our ParentIndexEntry.
+ //
+
+ //
+ // Write the log record, but do not update the IndexBuffer Lsn,
+ // since nothing moved and we don't want to force index contexts
+ // to have to rescan.
+ //
+ // Indexbuffer->Lsn =
+ //
+
+ NtfsWriteLog( IrpContext,
+ Scb,
+ Sp->Bcb,
+ UpdateRecordDataAllocation,
+ IndexRow->DataPart.Data,
+ IndexRow->DataPart.DataLength,
+ UpdateRecordDataAllocation,
+ DataInIndex,
+ IndexRow->DataPart.DataLength,
+ LlBytesFromIndexBlocks( IndexBuffer->ThisBlock, Scb->ScbType.Index.IndexBlockByteShift ),
+ 0,
+ (PCHAR)Sp->IndexEntry - (PCHAR)IndexBuffer,
+ Scb->ScbType.Index.BytesPerIndexBuffer );
+
+ if (ARGUMENT_PRESENT( QuickIndexHint )) {
+
+ ASSERT( Count == 1 );
+ QuickIndex->ChangeCount = Scb->ScbType.Index.ChangeCount;
+ QuickIndex->BufferOffset = PtrOffset( Sp->StartOfBuffer, Sp->IndexEntry );
+ QuickIndex->CapturedLsn = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->Lsn;
+ QuickIndex->IndexBlock = ((PINDEX_ALLOCATION_BUFFER) Sp->StartOfBuffer)->ThisBlock;
+ }
+ }
+
+ //
+ // Now call the Restart routine to do it.
+ //
+
+ NtOfsRestartUpdateDataInIndex( IndexEntry,
+ IndexRow->DataPart.Data,
+ IndexRow->DataPart.DataLength );
+
+ //
+ // If the file name is not in the index, this is a bad file.
+ //
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_NO_MATCH, NULL, NULL );
+ }
+
+ //
+ // Get ready for the next pass through.
+ //
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+ IndexRow += 1;
+
+ } while (--Count);
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, &IndexContext );
+
+ }
+}
+
+
+NTFSAPI
+NTSTATUS
+NtOfsReadRecords (
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb,
+ IN OUT PREAD_CONTEXT *ReadContext,
+ IN PINDEX_KEY IndexKey OPTIONAL,
+ IN PMATCH_FUNCTION MatchFunction,
+ IN PVOID MatchData,
+ IN OUT ULONG *Count,
+ OUT PINDEX_ROW Rows,
+ IN ULONG BufferLength,
+ OUT PVOID Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine may be called to enumerate rows in an index, in collated
+ order. It only returns records accepted by the match function.
+
+ IndexKey may be specified at any time to start a new search from IndexKey,
+ and IndexKey must be specified on the first call for a given IrpContext
+ (and *ReadContext must be NULL).
+
+ The read terminates when either *Count records have been returned, or
+ BufferLength has been exhausted, or there are no more matching records.
+
+ NtOfsReadRecords will seek to the appropriate point in the BTree (as defined
+ by the IndexKey or saved position and the CollateFunction) and begin calling
+ MatchFunction for each record. It continues doing this while MatchFunction
+ returns STATUS_SUCCESS. If MatchFunction returns STATUS_NO_MORE_MATCHES,
+ NtOfsReadRecords will cache this result and not call MatchFunction again until
+ called with a non-NULL IndexKey.
+
+ Note that this call is self-synchronized, such that successive calls to
+ the routine are guaranteed to make progress through the index and to return
+ items in Collation order, in spite of Add and Delete record calls being
+ interspersed with Read records calls.
+
+Arguments:
+
+ Scb - Supplies an Scb as the previously returned handle for this index.
+
+ ReadContext - On the first call this must supply a pointer to NULL. On
+ return a pointer to a private context structure is returned,
+ which must then be supplied on all subsequent calls. This
+ structure must be eventually be freed via NtOfsFreeReadContext.
+
+ IndexKey - If specified, supplies the key from which the enumeration is to
+ start/resume. It must be specified on the first call when *ReadContext
+ is NULL.
+
+ MatchFunction - Supplies the MatchFunction to be called to determine which
+ rows to return.
+
+ MatchData - Supplies the MatchData to be specified on each call to the MatchFunction.
+
+ Count - Supplies the count of how many rows may be received, and returns the
+ number of rows actually being returned.
+
+ Rows - Returns the Count row descriptions.
+
+ BufferLength - Supplies the length of the buffer in bytes, into which the
+ row keys and data are copied upon return.
+
+ Buffer - Supplies the buffer into which the rows may be copied.
+
+Return Value:
+
+ STATUS_SUCCESS -- if operation was successful.
+ STATUS_NO_MATCH -- if there is no match for the specified IndexKey.
+ STATUS_NO_MORE_MATCHES -- if a match is returned or previously returned,
+ but there are no more matches.
+
+--*/
+
+{
+ PINDEX_CONTEXT IndexContext;
+ PINDEX_ENTRY IndexEntry;
+ ULONG LengthToCopy;
+ BOOLEAN MustRestart;
+ ULONG BytesRemaining = BufferLength;
+ ULONG ReturnCount = 0;
+ NTSTATUS Status;
+ BOOLEAN NextFlag;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_SCB( Scb );
+
+ PAGED_CODE();
+
+ //
+ // On the first lookup, their must be a key.
+ //
+
+ ASSERT((IndexKey != NULL) || (*ReadContext != NULL));
+
+ //
+ // Everything must be Ulong aligned and sized.
+ //
+
+ ASSERT(((ULONG)Buffer & (sizeof(ULONG) - 1)) == 0);
+ ASSERT((BufferLength & (sizeof(ULONG) - 1)) == 0);
+
+ Status = STATUS_SUCCESS;
+ NextFlag = FALSE;
+
+ //
+ // Pick up the IndexContext, allocating one if we need to.
+ //
+
+ if (*ReadContext == NULL) {
+ *ReadContext = NtfsAllocatePool(PagedPool, sizeof(READ_CONTEXT) );
+ NtfsInitializeIndexContext( &(*ReadContext)->IndexContext );
+ (*ReadContext)->LastReturnedKey.Key = &(*ReadContext)->SmallKeyBuffer[0];
+ (*ReadContext)->LastReturnedKey.KeyLength = 0;
+ (*ReadContext)->AllocatedKeyLength = sizeof(READ_CONTEXT) -
+ FIELD_OFFSET(READ_CONTEXT, SmallKeyBuffer[0]);
+ }
+
+ IndexContext = &(*ReadContext)->IndexContext;
+
+ //
+ // Store the MatchFunction and Data in the IndexContext, for the enumerations.
+ //
+
+ IndexContext->MatchFunction = MatchFunction;
+ IndexContext->MatchData = MatchData;
+
+ NtfsAcquireSharedScb( IrpContext, Scb );
+
+ try {
+
+ //
+ // If a Key was passed, position to the first possible match.
+ //
+
+ if (ARGUMENT_PRESENT(IndexKey)) {
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ IndexKey,
+ IndexContext );
+
+ //
+ // Otherwise return here if we hit the end of the matches last time.
+ //
+
+ } else if ((*ReadContext)->LastReturnedKey.KeyLength == 0) {
+
+ try_return( Status = STATUS_NO_MORE_MATCHES );
+ }
+
+ //
+ // Loop while we still have space to store rows.
+ //
+
+ while (ReturnCount <= *Count) {
+
+ //
+ // See if there is an actual match.
+ //
+
+ if (!FindNextIndexEntry( IrpContext,
+ Scb,
+ NULL, // Not needed because of Match Function
+ TRUE,
+ FALSE,
+ IndexContext,
+ NextFlag,
+ &MustRestart )) {
+
+ //
+ // First handle the restart case by resuming from the last
+ // key returned, and skip that one.
+ //
+
+ if (MustRestart) {
+
+ ASSERT(!ARGUMENT_PRESENT(IndexKey));
+
+ NtfsReinitializeIndexContext( IrpContext, IndexContext );
+
+ FindFirstIndexEntry( IrpContext,
+ Scb,
+ &(*ReadContext)->LastReturnedKey,
+ IndexContext );
+
+ //
+ // Set NextFlag to TRUE, so we can go back and skip
+ // the key we resumed on.
+ //
+
+ NextFlag = TRUE;
+ continue;
+ }
+
+ //
+ // No (more) entries - remember that the enumeration is done.
+ //
+
+ (*ReadContext)->LastReturnedKey.KeyLength = 0;
+
+ //
+ // Return the appropriate code based on whether we have returned
+ // any matches yet or not.
+ //
+
+ if ((ReturnCount == 0) && ARGUMENT_PRESENT(IndexKey)) {
+ Status = STATUS_NO_MATCH;
+ } else {
+ Status = STATUS_NO_MORE_MATCHES;
+ }
+
+ try_return(Status);
+ }
+
+ //
+ // We always need to go one beyond the one we can return to keep
+ // all resume cases the same, so now is the time to get out if the
+ // count is finished.
+ //
+
+ if (ReturnCount == *Count) {
+ break;
+ }
+
+ //
+ // Now we must always move to the next.
+ //
+
+ NextFlag = TRUE;
+
+ //
+ // First try to copy the key.
+ //
+
+ IndexEntry = IndexContext->Current->IndexEntry;
+
+ LengthToCopy = IndexEntry->AttributeLength;
+ if (LengthToCopy > BytesRemaining) {
+ break;
+ }
+
+ RtlCopyMemory( Buffer, IndexEntry + 1, LengthToCopy );
+ Rows->KeyPart.Key = Buffer;
+ Rows->KeyPart.KeyLength = LengthToCopy;
+ LengthToCopy = LongAlign(LengthToCopy);
+ Buffer = Add2Ptr( Buffer, LengthToCopy );
+ BytesRemaining -= LengthToCopy;
+
+ //
+ // Now try to copy the data.
+ //
+
+ LengthToCopy = IndexEntry->DataLength;
+ if (LengthToCopy > BytesRemaining) {
+ break;
+ }
+
+ RtlCopyMemory( Buffer, Add2Ptr(IndexEntry, IndexEntry->DataOffset), LengthToCopy );
+ Rows->DataPart.Data = Buffer;
+ Rows->DataPart.DataLength = LengthToCopy;
+ LengthToCopy = LongAlign(LengthToCopy);
+ Buffer = Add2Ptr( Buffer, LengthToCopy );
+ BytesRemaining -= LengthToCopy;
+
+ //
+ // Capture this key before looping back.
+ //
+ // First see if there is enough space.
+ //
+
+ if (Rows->KeyPart.KeyLength > (*ReadContext)->AllocatedKeyLength) {
+
+ PVOID NewBuffer;
+
+ //
+ // Allocate a new buffer.
+ //
+
+ LengthToCopy = LongAlign(Rows->KeyPart.KeyLength + 16);
+ NewBuffer = NtfsAllocatePool(PagedPool, LengthToCopy );
+
+ //
+ // Delete old key buffer?
+ //
+
+ if ((*ReadContext)->LastReturnedKey.Key != &(*ReadContext)->SmallKeyBuffer[0]) {
+ NtfsFreePool( (*ReadContext)->LastReturnedKey.Key );
+ }
+
+ (*ReadContext)->LastReturnedKey.Key = NewBuffer;
+ (*ReadContext)->AllocatedKeyLength = LengthToCopy;
+ }
+
+ RtlCopyMemory( (*ReadContext)->LastReturnedKey.Key,
+ Rows->KeyPart.Key,
+ Rows->KeyPart.KeyLength );
+
+ (*ReadContext)->LastReturnedKey.KeyLength = Rows->KeyPart.KeyLength;
+
+ Rows += 1;
+ ReturnCount += 1;
+ }
+
+ try_exit: NOTHING;
+
+ } finally {
+
+ NtfsCleanupIndexContext( IrpContext, IndexContext );
+
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ *Count = ReturnCount;
+
+ //
+ // If we are already returning something, but we got an error, change it
+ // to success to return what we have. Then we may or may not get this error
+ // again anyway when we are called back. This loop is currently not designed
+ // to resume correctly in all cases if there are already items returned.
+ //
+
+ if (ReturnCount != 0) {
+ Status = STATUS_SUCCESS;
+ }
+
+ return Status;
+}
+
+
+NTFSAPI
+VOID
+NtOfsFreeReadContext (
+ IN PREAD_CONTEXT ReadContext
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to free an ReadContext created by NtOfsReadRecords.
+
+Arguments:
+
+ ReadContext - Supplies the context to free.
+
+Return Value:
+
+ STATUS_SUCCESS -- if operation was successful.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ if (ReadContext->LastReturnedKey.Key != NULL &&
+ ReadContext->LastReturnedKey.Key != &ReadContext->SmallKeyBuffer[0]) {
+ NtfsFreePool( ReadContext->LastReturnedKey.Key );
+ }
+ NtfsFreePool( ReadContext );
+}
+
+
+/*++
+
+Routine Descriptions:
+
+ Standard collation routines for creating simple indices.
+
+Arguments:
+
+ Key1 - First key to compare.
+
+ Key2 - Second key to compare.
+
+ CollationData - Optional data to support the collation.
+
+Return Value:
+
+ LessThan, EqualTo, or Greater than, for how Key1 compares
+ with Key2.
+
+--*/
+
+FSRTL_COMPARISON_RESULT
+NtOfsCollateUlong (
+ IN PINDEX_KEY Key1,
+ IN PINDEX_KEY Key2,
+ IN PVOID CollationData
+ )
+
+{
+ ULONG u1, u2;
+
+ UNREFERENCED_PARAMETER(CollationData);
+
+ ASSERT( Key1->KeyLength == 4 );
+ ASSERT( Key2->KeyLength == 4 );
+
+ u1 = *(PULONG)Key1->Key;
+ u2 = *(PULONG)Key2->Key;
+
+ if (u1 > u2) {
+ return GreaterThan;
+ } else if (u1 < u2) {
+ return LessThan;
+ }
+ return EqualTo;
+}
+
+FSRTL_COMPARISON_RESULT
+NtOfsCollateSid (
+ IN PINDEX_KEY Key1,
+ IN PINDEX_KEY Key2,
+ IN PVOID CollationData
+ )
+
+{
+ LONG Compare;
+
+ UNREFERENCED_PARAMETER(CollationData);
+
+ //
+ // The length of a valid SID is imbedded in the data
+ // so the function will mismatch be for the data runs out.
+ //
+
+ Compare = memcmp( Key1->Key, Key2->Key, Key1->KeyLength );
+
+ if (Compare > 0) {
+ return GreaterThan;
+ } else if (Compare < 0) {
+ return LessThan;
+ }
+
+ return EqualTo;
+}
+
+FSRTL_COMPARISON_RESULT
+NtOfsCollateUnicode (
+ IN PINDEX_KEY Key1,
+ IN PINDEX_KEY Key2,
+ IN PVOID CollationData
+ )
+
+{
+ UNICODE_STRING String1, String2;
+
+ PAGED_CODE();
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ String1.Length =
+ String1.MaximumLength = (USHORT)Key1->KeyLength;
+ String1.Buffer = Key1->Key;
+
+ String2.Length =
+ String2.MaximumLength = (USHORT)Key2->KeyLength;
+ String2.Buffer = Key2->Key;
+
+ return NtfsCollateNames( ((PUPCASE_TABLE_AND_KEY)CollationData)->UpcaseTable,
+ ((PUPCASE_TABLE_AND_KEY)CollationData)->UpcaseTableSize,
+ &String1,
+ &String2,
+ LessThan,
+ TRUE );
+}
+
+
+/*++
+
+Routine Descriptions:
+
+ Standard match routines for find / enumerate in simple indices.
+
+Arguments:
+
+ IndexRow - Row to check for a match.
+
+ MatchData - Optional data for determining a match.
+
+Return Value:
+
+ STATUS_SUCCESS if the IndexRow matches
+ STATUS_NO_MATCH if the IndexRow does not match, but the enumeration should
+ continue
+ STATUS_NO_MORE_MATCHES if the IndexRow does not match, and the enumeration
+ should terminate
+
+--*/
+
+NTSTATUS
+NtOfsMatchAll (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ )
+
+{
+ UNREFERENCED_PARAMETER(IndexRow);
+ UNREFERENCED_PARAMETER(MatchData);
+
+ return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NtOfsMatchUlongExact (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ )
+
+{
+ ULONG u1, u2;
+
+ ASSERT( IndexRow->KeyPart.KeyLength == 4 );
+
+ u1 = *(PULONG)IndexRow->KeyPart.Key;
+ u2 = *(PULONG)((PINDEX_KEY)MatchData)->Key;
+
+ if (u1 == u2) {
+ return STATUS_SUCCESS;
+ } else if (u1 < u2) {
+ return STATUS_NO_MATCH;
+ }
+ return STATUS_NO_MORE_MATCHES;
+}
+
+NTSTATUS
+NtOfsMatchUnicodeExpression (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ )
+
+{
+ UNICODE_STRING MatchString, IndexString;
+ FSRTL_COMPARISON_RESULT BlindResult;
+ PUPCASE_TABLE_AND_KEY UpcaseTableAndKey = (PUPCASE_TABLE_AND_KEY)MatchData;
+
+ PAGED_CODE();
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ MatchString.Length =
+ MatchString.MaximumLength = (USHORT)UpcaseTableAndKey->Key.KeyLength;
+ MatchString.Buffer = UpcaseTableAndKey->Key.Key;
+
+ IndexString.Length =
+ IndexString.MaximumLength = (USHORT)IndexRow->KeyPart.KeyLength;
+ IndexString.Buffer = IndexRow->KeyPart.Key;
+
+ if (NtfsIsNameInExpression( UpcaseTableAndKey->UpcaseTable, &MatchString, &IndexString, TRUE )) {
+
+ return STATUS_SUCCESS;
+
+ } else if ((BlindResult = NtfsCollateNames(UpcaseTableAndKey->UpcaseTable,
+ UpcaseTableAndKey->UpcaseTableSize,
+ &MatchString,
+ &IndexString,
+ GreaterThan,
+ TRUE)) != LessThan) {
+
+ return STATUS_NO_MATCH;
+
+ } else {
+
+ return STATUS_NO_MORE_MATCHES;
+ }
+}
+
+NTSTATUS
+NtOfsMatchUnicodeString (
+ IN PINDEX_ROW IndexRow,
+ IN OUT PVOID MatchData
+ )
+
+{
+ UNICODE_STRING MatchString, IndexString;
+ FSRTL_COMPARISON_RESULT BlindResult;
+ PUPCASE_TABLE_AND_KEY UpcaseTableAndKey = (PUPCASE_TABLE_AND_KEY)MatchData;
+
+ PAGED_CODE();
+
+ //
+ // Build the unicode strings and call namesup.
+ //
+
+ MatchString.Length =
+ MatchString.MaximumLength = (USHORT)UpcaseTableAndKey->Key.KeyLength;
+ MatchString.Buffer = UpcaseTableAndKey->Key.Key;
+
+ IndexString.Length =
+ IndexString.MaximumLength = (USHORT)IndexRow->KeyPart.KeyLength;
+ IndexString.Buffer = IndexRow->KeyPart.Key;
+
+ if (NtfsAreNamesEqual( UpcaseTableAndKey->UpcaseTable, &MatchString, &IndexString, TRUE )) {
+
+ return STATUS_SUCCESS;
+
+ } else if ((BlindResult = NtfsCollateNames(UpcaseTableAndKey->UpcaseTable,
+ UpcaseTableAndKey->UpcaseTableSize,
+ &MatchString,
+ &IndexString,
+ GreaterThan,
+ TRUE)) != LessThan) {
+
+ return STATUS_NO_MATCH;
+
+ } else {
+
+ return STATUS_NO_MORE_MATCHES;
+ }
+}
+
+
+#ifdef TOMM
+VOID
+NtOfsIndexTest (
+ PIRP_CONTEXT IrpContext,
+ PFCB TestFcb
+ )
+
+{
+ PSCB AdScb;
+ NTSTATUS Status;
+ ULONG i;
+ MAP_HANDLE MapHandle;
+ ULONG Count;
+ UPCASE_TABLE_AND_KEY UpcaseTableAndKey;
+ QUICK_INDEX_HINT QuickHint;
+ INDEX_KEY IndexKey;
+ INDEX_ROW IndexRow[6];
+ UCHAR Buffer[6*160];
+ PREAD_CONTEXT ReadContext = NULL;
+ UNICODE_STRING IndexName = {sizeof(L"$Test") - 2, sizeof(L"$Test") - 2, L"$Test"};
+ USHORT MaxKey = MAXUSHORT;
+ USHORT MinKey = 0;
+
+ DbgPrint("NtOfs Make NtOfsDoIndexTest FALSE to suppress test\n");
+ DbgPrint("NtOfs Make NtOfsLeaveTestIndex TRUE to leave test index\n");
+
+ DbgBreakPoint();
+
+ if (!NtOfsDoIndexTest) {
+ return;
+ }
+ NtOfsDoIndexTest = FALSE;
+
+ UpcaseTableAndKey.UpcaseTable = TestFcb->Vcb->UpcaseTable;
+ UpcaseTableAndKey.UpcaseTableSize = TestFcb->Vcb->UpcaseTableSize;
+ UpcaseTableAndKey.Key.Key = NULL;
+ UpcaseTableAndKey.Key.KeyLength = 0;
+
+ //
+ // Create Test Index
+ //
+
+ DbgPrint("NtOfs creating test index\n");
+ NtOfsCreateIndex( IrpContext,
+ TestFcb,
+ IndexName,
+ CREATE_NEW,
+ 0,
+ &NtOfsCollateUnicode,
+ &UpcaseTableAndKey,
+ &AdScb );
+
+ DbgPrint("NtOfs created Test Index Scb %08lx\n", AdScb);
+
+ //
+ // Access empty index
+ //
+
+ DbgPrint("NtOfs lookup last in empty index\n");
+ IndexKey.Key = &MaxKey;
+ IndexKey.KeyLength = sizeof(MaxKey);
+ Status = NtOfsFindLastRecord( IrpContext, AdScb, &IndexKey, &IndexRow[0], &MapHandle );
+
+ ASSERT(!NT_SUCCESS(Status));
+
+ //
+ // Add some keys!
+ //
+
+ DbgPrint("NtOfs adding keys to index\n");
+ for (i = 0; i < $EA/0x10; i++) {
+
+ IndexRow[0].KeyPart.Key = &NtfsAttributeDefinitions[i].AttributeName;
+ IndexRow[0].KeyPart.KeyLength = 0x80;
+ IndexRow[0].DataPart.Data = (PCHAR)IndexRow[0].KeyPart.Key + 0x80;
+ IndexRow[0].DataPart.DataLength = sizeof(ATTRIBUTE_DEFINITION_COLUMNS) - 0x84;
+
+ NtOfsAddRecords( IrpContext, AdScb, 1, &IndexRow[0], 0 );
+ }
+
+ //
+ // Now find the last key
+ //
+
+ DbgPrint("NtOfs checkin last key in index\n");
+ IndexKey.Key = &MaxKey;
+ IndexKey.KeyLength = sizeof(MaxKey);
+ Status = NtOfsFindLastRecord( IrpContext, AdScb, &IndexKey, &IndexRow[0], &MapHandle );
+
+ ASSERT(NT_SUCCESS(Status));
+ ASSERT(RtlCompareMemory(IndexRow[0].KeyPart.Key, L"$VOLUME_NAME", sizeof(L"$VOLUME_NAME") - 2) ==
+ (sizeof(L"$VOLUME_NAME") - 2));
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+
+ //
+ // See if they are all there.
+ //
+
+ DbgPrint("NtOfs looking up all keys in index\n");
+ for (i = 0; i < $EA/0x10; i++) {
+
+ IndexKey.Key = &NtfsAttributeDefinitions[i].AttributeName;
+ IndexKey.KeyLength = 0x80;
+
+ Status = NtOfsFindRecord( IrpContext, AdScb, &IndexKey, &IndexRow[0], &MapHandle, NULL );
+
+ if (!NT_SUCCESS(Status)) {
+ DbgPrint("NtOfsIterationFailure with i = %08lx, Status = %08lx\n", i, Status);
+ }
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ }
+
+ //
+ // Now enumerate the entire index
+ //
+
+ IndexKey.Key = &MinKey;
+ IndexKey.KeyLength = sizeof(MinKey);
+ Count = 6;
+
+ DbgPrint("NtOfs enumerating index:\n\n");
+ while (NT_SUCCESS(Status = NtOfsReadRecords( IrpContext,
+ AdScb,
+ &ReadContext,
+ (ReadContext == NULL) ? &IndexKey : NULL,
+ &NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ sizeof(Buffer),
+ Buffer ))) {
+
+ for (i = 0; i < Count; i++) {
+ DbgPrint( "IndexKey = %ws, AttributeTypeCode = %lx\n",
+ IndexRow[i].KeyPart.Key,
+ *(PULONG)IndexRow[i].DataPart.Data );
+ }
+ DbgPrint( "\n" );
+ }
+
+ NtOfsFreeReadContext( ReadContext );
+ ReadContext = NULL;
+
+ //
+ // Loop to update all records.
+ //
+
+ DbgPrint("NtOfs updating up all keys in index\n");
+ for (i = 0; i < $EA/0x10; i++) {
+
+ IndexKey.Key = &NtfsAttributeDefinitions[i].AttributeName;
+ IndexKey.KeyLength = 0x80;
+
+ RtlZeroMemory( &QuickHint, sizeof(QUICK_INDEX_HINT) );
+
+ NtOfsFindRecord( IrpContext, AdScb, &IndexKey, &IndexRow[0], &MapHandle, &QuickHint );
+
+ //
+ // Copy and update the data.
+ //
+
+ RtlCopyMemory( Buffer, IndexRow[0].DataPart.Data, IndexRow[0].DataPart.DataLength );
+ *(PULONG)Buffer += 0x100;
+ IndexRow[0].DataPart.Data = Buffer;
+
+ //
+ // Perform update with all valid combinations of hint and map handle.
+ //
+
+ NtOfsUpdateRecord( IrpContext,
+ AdScb,
+ 1,
+ &IndexRow[0],
+ (i <= $FILE_NAME/0x10) ? NULL : &QuickHint,
+ (i < $INDEX_ROOT/0x10) ? NULL : &MapHandle );
+
+ NtOfsReleaseMap( IrpContext, &MapHandle );
+ }
+
+ //
+ // Now enumerate the entire index again to see the updates.
+ //
+
+ IndexKey.Key = &MinKey;
+ IndexKey.KeyLength = sizeof(MinKey);
+ Count = 6;
+
+ DbgPrint("NtOfs enumerating index after updates:\n\n");
+ while (NT_SUCCESS(Status = NtOfsReadRecords( IrpContext,
+ AdScb,
+ &ReadContext,
+ (ReadContext == NULL) ? &IndexKey : NULL,
+ &NtOfsMatchAll,
+ NULL,
+ &Count,
+ IndexRow,
+ sizeof(Buffer),
+ Buffer ))) {
+
+ for (i = 0; i < Count; i++) {
+ DbgPrint( "IndexKey = %ws, AttributeTypeCode = %lx\n",
+ IndexRow[i].KeyPart.Key,
+ *(PULONG)IndexRow[i].DataPart.Data );
+ }
+ DbgPrint( "\n" );
+ }
+
+ NtOfsFreeReadContext( ReadContext );
+ ReadContext = NULL;
+
+
+ //
+ // Now delete the keys
+ //
+
+ if (!NtOfsLeaveTestIndex) {
+
+ DbgPrint("NtOfs deleting all keys in index:\n\n");
+ for (i = 0; i < $EA/0x10; i++) {
+
+ IndexKey.Key = &NtfsAttributeDefinitions[i].AttributeName;
+ IndexKey.KeyLength = 0x80;
+
+ NtOfsDeleteRecords( IrpContext, AdScb, 1, &IndexKey );
+ }
+
+ //
+ // Access empty index
+ //
+
+ DbgPrint("NtOfs lookup last key in empty index:\n\n");
+ IndexKey.Key = &MaxKey;
+ IndexKey.KeyLength = sizeof(MaxKey);
+ Status = NtOfsFindLastRecord( IrpContext, AdScb, &IndexKey, &IndexRow[0], &MapHandle );
+
+ ASSERT(!NT_SUCCESS(Status));
+
+ DbgPrint("NtOfs deleting index:\n");
+ NtOfsDeleteIndex( IrpContext, TestFcb, AdScb );
+ }
+
+ DbgPrint("NtOfs closing index:\n");
+ NtOfsCloseIndex( IrpContext, AdScb );
+
+ DbgPrint("NtOfs test complete!\n\n");
+
+ return;
+
+ //
+ // Make sure these at least compile until we have some real callers.
+ //
+
+ {
+ MAP_HANDLE M;
+ PVOID B;
+ LONGLONG O;
+ ULONG L;
+ LSN Lsn;
+
+ NtOfsInitializeMapHandle( &M );
+ NtOfsMapAttribute( IrpContext, AdScb, O, L, &B, &M );
+ NtOfsPreparePinWrite( IrpContext, AdScb, O, L, &B, &M );
+ NtOfsPinRead( IrpContext, AdScb, O, L, &M );
+ NtOfsDirty( IrpContext, &M, &Lsn );
+ NtOfsReleaseMap( IrpContext, &M );
+ NtOfsPutData( IrpContext, AdScb, O, L, &B );
+
+ }
+}
+
+#endif TOMM
diff --git a/private/ntos/cntfs/volinfo.c b/private/ntos/cntfs/volinfo.c
new file mode 100644
index 000000000..d081632bf
--- /dev/null
+++ b/private/ntos/cntfs/volinfo.c
@@ -0,0 +1,1353 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ VolInfo.c
+
+Abstract:
+
+ This module implements the set and query volume information routines for
+ Ntfs called by the dispatch driver.
+
+Author:
+
+ Your Name [Email] dd-Mon-Year
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_VOLINFO)
+
+//
+// Local procedure prototypes
+//
+
+NTSTATUS
+NtfsQueryFsVolumeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_VOLUME_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryFsSizeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_SIZE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryFsDeviceInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_DEVICE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryFsAttributeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_ATTRIBUTE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsQueryFsControlInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION Buffer,
+ IN OUT PULONG Length
+ );
+
+NTSTATUS
+NtfsSetFsLabelInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_LABEL_INFORMATION Buffer
+ );
+
+NTSTATUS
+NtfsSetFsControlInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION Buffer
+ );
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsCommonQueryVolumeInfo)
+#pragma alloc_text(PAGE, NtfsCommonSetVolumeInfo)
+#pragma alloc_text(PAGE, NtfsFsdQueryVolumeInformation)
+#pragma alloc_text(PAGE, NtfsFsdSetVolumeInformation)
+#pragma alloc_text(PAGE, NtfsQueryFsAttributeInfo)
+#pragma alloc_text(PAGE, NtfsQueryFsDeviceInfo)
+#pragma alloc_text(PAGE, NtfsQueryFsSizeInfo)
+#pragma alloc_text(PAGE, NtfsQueryFsVolumeInfo)
+#pragma alloc_text(PAGE, NtfsQueryFsControlInfo)
+#pragma alloc_text(PAGE, NtfsSetFsLabelInfo)
+#pragma alloc_text(PAGE, NtfsSetFsControlInfo)
+#endif
+
+
+NTSTATUS
+NtfsFsdQueryVolumeInformation (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of query Volume Information.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdQueryVolumeInformation\n") );
+
+ //
+ // Call the common query Volume Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonQueryVolumeInfo( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdQueryVolumeInformation -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsFsdSetVolumeInformation (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of set Volume Information.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, ("NtfsFsdSetVolumeInformation\n") );
+
+ //
+ // Call the common set Volume Information routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ Status = NtfsCommonSetVolumeInfo( IrpContext, Irp );
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ Status = NtfsProcessException( IrpContext, Irp, GetExceptionCode() );
+ }
+
+ } while (Status == STATUS_CANT_WAIT ||
+ Status == STATUS_LOG_FILE_FULL);
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdSetVolumeInformation -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonQueryVolumeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for query Volume Information called by both the
+ fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ULONG Length;
+ FS_INFORMATION_CLASS FsInformationClass;
+ PVOID Buffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonQueryVolumeInfo...\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.QueryVolume.Length) );
+ DebugTrace( 0, Dbg, ("FsInformationClass = %08lx\n", IrpSp->Parameters.QueryVolume.FsInformationClass) );
+ DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ Length = IrpSp->Parameters.QueryVolume.Length;
+ FsInformationClass = IrpSp->Parameters.QueryVolume.FsInformationClass;
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Extract and decode the file object to get the Vcb, we don't really
+ // care what the type of open is.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // We need exclusive access to the Vcb because we are going to verify
+ // it. After we verify the vcb we'll convert our access to shared
+ //
+
+ NtfsAcquireSharedVcb( IrpContext, Vcb, FALSE );
+
+ try {
+
+ //
+ // Based on the information class we'll do different actions. Each
+ // of the procedures that we're calling fills up the output buffer
+ // if possible and returns true if it successfully filled the buffer
+ // and false if it couldn't wait for any I/O to complete.
+ //
+
+ switch (FsInformationClass) {
+
+ case FileFsVolumeInformation:
+
+ Status = NtfsQueryFsVolumeInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+ case FileFsSizeInformation:
+
+ Status = NtfsQueryFsSizeInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+ case FileFsDeviceInformation:
+
+ Status = NtfsQueryFsDeviceInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+ case FileFsAttributeInformation:
+
+ Status = NtfsQueryFsAttributeInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+#ifdef _CAIRO_
+ case FileFsControlInformation:
+
+ Status = NtfsQueryFsControlInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+
+ case FileFsQuotaQueryInformation:
+
+ Status = NtfsFsQuotaQueryInfo( IrpContext, Vcb, Buffer, &Length );
+ break;
+
+#endif // _CAIRO_
+
+ default:
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ //
+ // Set the information field to the number of bytes actually filled in
+ //
+
+ Irp->IoStatus.Information = IrpSp->Parameters.QueryVolume.Length - Length;
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonQueryVolumeInfo );
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonQueryVolumeInfo -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonSetVolumeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for set Volume Information called by both the
+ fsd and fsp threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ ULONG Length;
+ FS_INFORMATION_CLASS FsInformationClass;
+ PVOID Buffer;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ PAGED_CODE();
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonSetVolumeInfo\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+ DebugTrace( 0, Dbg, ("Length = %08lx\n", IrpSp->Parameters.SetVolume.Length) );
+ DebugTrace( 0, Dbg, ("FsInformationClass = %08lx\n", IrpSp->Parameters.SetVolume.FsInformationClass) );
+ DebugTrace( 0, Dbg, ("Buffer = %08lx\n", Irp->AssociatedIrp.SystemBuffer) );
+
+ //
+ // Reference our input parameters to make things easier
+ //
+
+ Length = IrpSp->Parameters.SetVolume.Length;
+ FsInformationClass = IrpSp->Parameters.SetVolume.FsInformationClass;
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ //
+ // Extract and decode the file object to get the Vcb, we don't really
+ // care what the type of open is.
+ //
+
+ FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ if (TypeOfOpen != UserVolumeOpen) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_ACCESS_DENIED );
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetVolumeInfo -> STATUS_ACCESS_DENIED\n") );
+
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // Acquire exclusive access to the Vcb
+ //
+
+ NtfsAcquireExclusiveVcb( IrpContext, Vcb, TRUE );
+
+ try {
+
+ //
+ // Proceed only if the volume is mounted.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ //
+ // Based on the information class we'll do different actions. Each
+ // of the procedures that we're calling performs the action if
+ // possible and returns true if it successful and false if it couldn't
+ // wait for any I/O to complete.
+ //
+
+ switch (FsInformationClass) {
+
+ case FileFsLabelInformation:
+
+ Status = NtfsSetFsLabelInfo( IrpContext, Vcb, Buffer );
+ break;
+
+#ifdef _CAIRO_
+
+ case FileFsQuotaSetInformation:
+
+ Status = NtfsFsQuotaSetInfo( IrpContext, Vcb, Buffer, Length );
+ break;
+
+ case FileFsControlInformation:
+
+ Status = NtfsSetFsControlInfo( IrpContext, Vcb, Buffer );
+ break;
+
+#endif // _CAIRO_
+
+ default:
+
+ Status = STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ } else {
+
+ Status = STATUS_FILE_INVALID;
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+
+ } finally {
+
+ DebugUnwind( NtfsCommonSetVolumeInfo );
+
+ NtfsReleaseVcb( IrpContext, Vcb );
+
+ if (!AbnormalTermination()) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonSetVolumeInfo -> %08lx\n", Status) );
+ }
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryFsVolumeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_VOLUME_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the query volume info call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried
+
+ Buffer - Supplies a pointer to the output buffer where the information
+ is to be returned
+
+ Length - Supplies the length of the buffer in byte. This variable
+ upon return recieves the remaining bytes free in the buffer
+
+Return Value:
+
+ NTSTATUS - Returns the status for the query
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ ULONG BytesToCopy;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsQueryFsVolumeInfo...\n") );
+
+ //
+ // Get the volume creation time from the Vcb.
+ //
+
+ Buffer->VolumeCreationTime.QuadPart = Vcb->VolumeCreationTime;
+
+ //
+ // Fill in the serial number and indicate that we support objects
+ //
+
+ Buffer->VolumeSerialNumber = Vcb->Vpb->SerialNumber;
+ Buffer->SupportsObjects = TRUE;
+
+ Buffer->VolumeLabelLength = Vcb->Vpb->VolumeLabelLength;
+
+ //
+ // Update the length field with how much we have filled in so far.
+ //
+
+ *Length -= FIELD_OFFSET(FILE_FS_VOLUME_INFORMATION, VolumeLabel[0]);
+
+ //
+ // See how many bytes of volume label we can copy
+ //
+
+ if (*Length >= (ULONG)Vcb->Vpb->VolumeLabelLength) {
+
+ Status = STATUS_SUCCESS;
+
+ BytesToCopy = Vcb->Vpb->VolumeLabelLength;
+
+ } else {
+
+ Status = STATUS_BUFFER_OVERFLOW;
+
+ BytesToCopy = *Length;
+ }
+
+ //
+ // Copy over the volume label (if there is one).
+ //
+
+ RtlCopyMemory( &Buffer->VolumeLabel[0],
+ &Vcb->Vpb->VolumeLabel[0],
+ BytesToCopy);
+
+ //
+ // Update the buffer length by the amount we copied.
+ //
+
+ *Length -= BytesToCopy;
+
+ return Status;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryFsSizeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_SIZE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the query size information call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried
+
+ Buffer - Supplies a pointer to the output buffer where the information
+ is to be returned
+
+ Length - Supplies the length of the buffer in byte. This variable
+ upon return recieves the remaining bytes free in the buffer
+
+Return Value:
+
+ NTSTATUS - Returns the status for the query
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsQueryFsSizeInfo...\n") );
+
+ //
+ // Make sure the buffer is large enough and zero it out
+ //
+
+ if (*Length < sizeof(FILE_FS_SIZE_INFORMATION)) {
+
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ RtlZeroMemory( Buffer, sizeof(FILE_FS_SIZE_INFORMATION) );
+
+ //
+ // Check if we need to rescan the bitmap. Don't try this
+ // if we have started to teardown the volume.
+ //
+
+ if (FlagOn( Vcb->VcbState, VCB_STATE_RELOAD_FREE_CLUSTERS ) &&
+ FlagOn( Vcb->VcbState, VCB_STATE_VOLUME_MOUNTED )) {
+
+ //
+ // Acquire the volume bitmap shared to rescan the bitmap.
+ //
+
+ NtfsAcquireExclusiveScb( IrpContext, Vcb->BitmapScb );
+
+ try {
+
+ NtfsScanEntireBitmap( IrpContext, Vcb, TRUE );
+
+ } finally {
+
+ NtfsReleaseScb( IrpContext, Vcb->BitmapScb );
+ }
+ }
+
+ //
+ // Set the output buffer
+ //
+
+ Buffer->TotalAllocationUnits.QuadPart = Vcb->TotalClusters;
+ Buffer->AvailableAllocationUnits.QuadPart = Vcb->FreeClusters;
+ Buffer->SectorsPerAllocationUnit = Vcb->BytesPerCluster / Vcb->BytesPerSector;
+ Buffer->BytesPerSector = Vcb->BytesPerSector;
+
+
+#ifdef _CAIRO_
+
+ //
+ // If quota enforcement is enabled then the availalble allocation
+ // units. must be reduced by the available quota.
+ //
+
+ if (FlagOn(Vcb->QuotaFlags, QUOTA_FLAG_TRACKING_ENABLED)) {
+ PCCB Ccb;
+
+ //
+ // Go grab the ccb out of the Irp.
+ //
+
+ Ccb = (PCCB) (IoGetCurrentIrpStackLocation(IrpContext->OriginatingIrp)->
+ FileObject->FsContext2);
+
+ if (Ccb != NULL && Ccb->OwnerId != 0) {
+ ULONGLONG Quota;
+
+ NtfsGetRemainingQuota( IrpContext, Ccb->OwnerId, &Quota, NULL );
+
+ Quota = LlClustersFromBytesTruncate( Vcb, Quota );
+
+ if (Quota < (ULONGLONG) Vcb->FreeClusters) {
+
+ Buffer->AvailableAllocationUnits.QuadPart = Quota;
+
+ }
+ }
+ }
+
+#endif // _CAIRO_
+
+
+ //
+ // Adjust the length variable
+ //
+
+ *Length -= sizeof(FILE_FS_SIZE_INFORMATION);
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryFsDeviceInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_DEVICE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the query device information call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried
+
+ Buffer - Supplies a pointer to the output buffer where the information
+ is to be returned
+
+ Length - Supplies the length of the buffer in byte. This variable
+ upon return recieves the remaining bytes free in the buffer
+
+Return Value:
+
+ NTSTATUS - Returns the status for the query
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsQueryFsDeviceInfo...\n") );
+
+ //
+ // Make sure the buffer is large enough and zero it out
+ //
+
+ if (*Length < sizeof(FILE_FS_DEVICE_INFORMATION)) {
+
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ RtlZeroMemory( Buffer, sizeof(FILE_FS_DEVICE_INFORMATION) );
+
+ //
+ // Set the output buffer
+ //
+
+ Buffer->DeviceType = FILE_DEVICE_DISK;
+ Buffer->Characteristics = Vcb->TargetDeviceObject->Characteristics;
+
+ //
+ // Adjust the length variable
+ //
+
+ *Length -= sizeof(FILE_FS_DEVICE_INFORMATION);
+
+ return STATUS_SUCCESS;
+}
+
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryFsAttributeInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_ATTRIBUTE_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the query attribute information call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried
+
+ Buffer - Supplies a pointer to the output buffer where the information
+ is to be returned
+
+ Length - Supplies the length of the buffer in byte. This variable
+ upon return recieves the remaining bytes free in the buffer
+
+Return Value:
+
+ NTSTATUS - Returns the status for the query
+
+--*/
+
+{
+ NTSTATUS Status;
+ ULONG BytesToCopy;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsQueryFsAttributeInfo...\n") );
+
+ //
+ // See how many bytes of the name we can copy.
+ //
+
+ *Length -= FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName[0]);
+
+ if ( *Length >= 8 ) {
+
+ Status = STATUS_SUCCESS;
+
+ BytesToCopy = 8;
+
+ } else {
+
+ Status = STATUS_BUFFER_OVERFLOW;
+
+ BytesToCopy = *Length;
+ }
+
+ //
+ // Set the output buffer
+ //
+
+ Buffer->FileSystemAttributes = FILE_CASE_SENSITIVE_SEARCH |
+ FILE_CASE_PRESERVED_NAMES |
+ FILE_UNICODE_ON_DISK |
+ FILE_FILE_COMPRESSION |
+ FILE_PERSISTENT_ACLS;
+
+ //
+ // Clear the compression flag if we don't allow compression on this drive
+ // (i.e. large clusters)
+ //
+
+ if (!FlagOn( Vcb->AttributeFlagsMask, ATTRIBUTE_FLAG_COMPRESSION_MASK )) {
+
+ ClearFlag( Buffer->FileSystemAttributes, FILE_FILE_COMPRESSION );
+ }
+
+ Buffer->MaximumComponentNameLength = 255;
+ Buffer->FileSystemNameLength = BytesToCopy;;
+ RtlCopyMemory( &Buffer->FileSystemName[0], L"NTFS", BytesToCopy );
+
+ //
+ // Adjust the length variable
+ //
+
+ *Length -= BytesToCopy;
+
+ return Status;
+}
+
+
+#ifdef _CAIRO_
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsQueryFsControlInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION Buffer,
+ IN OUT PULONG Length
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the query control information call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being queried
+
+ Buffer - Supplies a pointer to the output buffer where the information
+ is to be returned
+
+ Length - Supplies the length of the buffer in byte. This variable
+ upon return recieves the remaining bytes free in the buffer
+
+Return Value:
+
+ NTSTATUS - Returns the status for the query
+
+--*/
+
+{
+ INDEX_ROW IndexRow;
+ INDEX_KEY IndexKey;
+ QUOTA_USER_DATA QuotaBuffer;
+ PQUOTA_USER_DATA UserData;
+ ULONG OwnerId;
+ ULONG Count = 1;
+ PREAD_CONTEXT ReadContext = NULL;
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsQueryFsControlInfo...\n") );
+
+ RtlZeroMemory( Buffer, sizeof( FILE_FS_CONTROL_INFORMATION ));
+
+ PAGED_CODE();
+
+ try {
+
+ //
+ // Fill in the quota information if quotas are running.
+ //
+
+ if (Vcb->QuotaTableScb != NULL) {
+
+ OwnerId = QUOTA_DEFAULTS_ID;
+ IndexKey.KeyLength = sizeof( OwnerId );
+ IndexKey.Key = &OwnerId;
+
+ Status = NtOfsReadRecords( IrpContext,
+ Vcb->QuotaTableScb,
+ &ReadContext,
+ &IndexKey,
+ NtOfsMatchUlongExact,
+ &IndexKey,
+ &Count,
+ &IndexRow,
+ sizeof( QuotaBuffer ),
+ &QuotaBuffer );
+
+
+ if (NT_SUCCESS( Status )) {
+
+ UserData = IndexRow.DataPart.Data;
+
+ Buffer->DefaultQuotaThreshold.QuadPart =
+ UserData->QuotaThreshold;
+ Buffer->DefaultQuotaLimit.QuadPart =
+ UserData->QuotaLimit;
+
+ //
+ // If the quota info is corrupt or has not been rebuilt
+ // yet then indicate the information is incomplete.
+ //
+
+ if (FlagOn( Vcb->QuotaFlags, QUOTA_FLAG_OUT_OF_DATE |
+ QUOTA_FLAG_CORRUPT )) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_QUOTAS_INCOMPLETE );
+ }
+
+ if ((Vcb->QuotaState & VCB_QUOTA_REPAIR_RUNNING) >
+ VCB_QUOTA_REPAIR_POSTED ) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_QUOTAS_REBUILDING );
+ }
+
+ //
+ // Set the quota information basied on where we want
+ // to be rather than where we are.
+ //
+
+ if (FlagOn( UserData->QuotaFlags,
+ QUOTA_FLAG_ENFORCEMENT_ENABLED )) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_QUOTA_ENFORCE );
+
+ } else if (FlagOn( UserData->QuotaFlags,
+ QUOTA_FLAG_TRACKING_REQUESTED )) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_QUOTA_TRACK );
+ }
+
+ if (FlagOn( UserData->QuotaFlags, QUOTA_FLAG_LOG_LIMIT)) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_LOG_QUOTA_LIMIT );
+
+ }
+
+ if (FlagOn( UserData->QuotaFlags, QUOTA_FLAG_LOG_THRESHOLD)) {
+
+ SetFlag( Buffer->FileSystemControlFlags,
+ FILE_VC_LOG_QUOTA_THRESHOLD );
+
+ }
+ }
+ }
+
+ } finally {
+
+ if (ReadContext != NULL) {
+ NtOfsFreeReadContext( ReadContext );
+ }
+
+ }
+
+ //
+ // Adjust the length variable
+ //
+
+ *Length -= sizeof( FILE_FS_CONTROL_INFORMATION );
+
+ return Status;
+}
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetFsControlInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_CONTROL_INFORMATION Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the set label call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being altered
+
+ Buffer - Supplies a pointer to the input buffer containing the new label
+
+Return Value:
+
+ NTSTATUS - Returns the status for the operation
+
+--*/
+
+{
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ if (Vcb->QuotaTableScb == NULL) {
+ return( STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // Process the quota part of the control structure.
+ //
+
+ NtfsUpdateQuotaDefaults( IrpContext, Vcb, Buffer );
+
+ return STATUS_SUCCESS;
+}
+#endif // _CAIRO_
+
+//
+// Internal Support Routine
+//
+
+NTSTATUS
+NtfsSetFsLabelInfo (
+ IN PIRP_CONTEXT IrpContext,
+ IN PVCB Vcb,
+ IN PFILE_FS_LABEL_INFORMATION Buffer
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the set label call
+
+Arguments:
+
+ Vcb - Supplies the Vcb being altered
+
+ Buffer - Supplies a pointer to the input buffer containing the new label
+
+Return Value:
+
+ NTSTATUS - Returns the status for the operation
+
+--*/
+
+{
+ ATTRIBUTE_ENUMERATION_CONTEXT AttributeContext;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_VCB( Vcb );
+
+ PAGED_CODE();
+
+ DebugTrace( 0, Dbg, ("NtfsSetFsLabelInfo...\n") );
+
+ //
+ // Check that the volume label length is supported by the system.
+ //
+
+ if (Buffer->VolumeLabelLength > MAXIMUM_VOLUME_LABEL_LENGTH) {
+
+ return STATUS_INVALID_VOLUME_LABEL;
+ }
+
+ try {
+
+ //
+ // Initialize the attribute context and then lookup the volume name
+ // attribute for on the volume dasd file
+ //
+
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ if (NtfsLookupAttributeByCode( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ &Vcb->VolumeDasdScb->Fcb->FileReference,
+ $VOLUME_NAME,
+ &AttributeContext )) {
+
+ //
+ // We found the volume name so now simply update the label
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ 0,
+ &Buffer->VolumeLabel[0],
+ Buffer->VolumeLabelLength,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttributeContext );
+
+ } else {
+
+ //
+ // We didn't find the volume name so now create a new label
+ //
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ NtfsInitializeAttributeContext( &AttributeContext );
+
+ NtfsCreateAttributeWithValue( IrpContext,
+ Vcb->VolumeDasdScb->Fcb,
+ $VOLUME_NAME,
+ NULL,
+ &Buffer->VolumeLabel[0],
+ Buffer->VolumeLabelLength,
+ 0, // Attributeflags
+ NULL,
+ TRUE,
+ &AttributeContext );
+ }
+
+ Vcb->Vpb->VolumeLabelLength = (USHORT)Buffer->VolumeLabelLength;
+
+ if ( Vcb->Vpb->VolumeLabelLength > MAXIMUM_VOLUME_LABEL_LENGTH) {
+
+ Vcb->Vpb->VolumeLabelLength = MAXIMUM_VOLUME_LABEL_LENGTH;
+ }
+
+ RtlCopyMemory( &Vcb->Vpb->VolumeLabel[0],
+ &Buffer->VolumeLabel[0],
+ Vcb->Vpb->VolumeLabelLength );
+
+ } finally {
+
+ DebugUnwind( NtfsSetFsLabelInfo );
+
+ NtfsCleanupAttributeContext( &AttributeContext );
+ }
+
+ //
+ // and return to our caller
+ //
+
+ return STATUS_SUCCESS;
+}
+
diff --git a/private/ntos/cntfs/workque.c b/private/ntos/cntfs/workque.c
new file mode 100644
index 000000000..dc14550c1
--- /dev/null
+++ b/private/ntos/cntfs/workque.c
@@ -0,0 +1,433 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ WorkQue.c
+
+Abstract:
+
+ This module implements the Work queue routines for the Ntfs File
+ system.
+
+Author:
+
+ Gary Kimura [GaryKi] 21-May-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The following constant is the maximum number of ExWorkerThreads that we
+// will allow to be servicing a particular target device at any one time.
+//
+
+#define FSP_PER_DEVICE_THRESHOLD (2)
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, NtfsOplockComplete)
+#endif
+
+
+VOID
+NtfsOplockComplete (
+ IN PVOID Context,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called by the oplock package when an oplock break has
+ completed, allowing an Irp to resume execution. If the status in
+ the Irp is STATUS_SUCCESS, then we queue the Irp to the Fsp queue.
+ Otherwise we complete the Irp with the status in the Irp.
+
+Arguments:
+
+ Context - Pointer to the IrpContext to be queued to the Fsp
+
+ Irp - I/O Request Packet.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PAGED_CODE();
+
+ //
+ // Check on the return value in the Irp.
+ //
+
+ if (Irp->IoStatus.Status == STATUS_SUCCESS) {
+
+ //
+ // Insert the Irp context in the workqueue.
+ //
+
+ NtfsAddToWorkque( (PIRP_CONTEXT) Context, Irp );
+
+ //
+ // Otherwise complete the request.
+ //
+
+ } else {
+
+ NtfsCompleteRequest( ((PIRP_CONTEXT *)&Context), &Irp, Irp->IoStatus.Status );
+ }
+
+ return;
+}
+
+
+VOID
+NtfsPrePostIrp (
+ IN PVOID Context,
+ IN PIRP Irp OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine performs any neccessary work before STATUS_PENDING is
+ returned with the Fsd thread. This routine is called within the
+ filesystem and by the oplock package.
+
+Arguments:
+
+ Context - Pointer to the IrpContext to be queued to the Fsp
+
+ Irp - I/O Request Packet (or FileObject in special close path)
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIRP_CONTEXT IrpContext;
+ PFCB Fcb;
+ PIO_STACK_LOCATION IrpSp = NULL;
+
+ IrpContext = (PIRP_CONTEXT) Context;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+
+ //
+ // Make sure if we are posting the request, which may be
+ // because of log file full, that we free any Fcbs or PagingIo
+ // resources which were acquired.
+ //
+
+ //
+ // Just in case we somehow get here with a transaction ID, clear
+ // it here so we do not loop forever.
+ //
+
+ ASSERT(IrpContext->TransactionId == 0);
+
+ IrpContext->TransactionId = 0;
+
+ //
+ // Free any exclusive paging I/O resource, or IoAtEof condition,
+ // this field is overlayed, minimally in write.c.
+ //
+
+ Fcb = IrpContext->FcbWithPagingExclusive;
+ if (Fcb != NULL) {
+
+ if (Fcb->NodeTypeCode == NTFS_NTC_FCB) {
+
+ NtfsReleasePagingIo(IrpContext, Fcb );
+
+ } else {
+
+ FsRtlUnlockFsRtlHeader( (PFSRTL_ADVANCED_FCB_HEADER) Fcb );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+
+ while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+ //
+ // Go through and free any Scb's in the queue of shared Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+ IrpContext->OriginatingIrp = Irp;
+
+ //
+ // Note that close.c uses a trick where the "Irp" is really
+ // a file object.
+ //
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ if (Irp->Type == IO_TYPE_IRP) {
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // We need to lock the user's buffer, unless this is an MDL-read,
+ // in which case there is no user buffer.
+ //
+ // **** we need a better test than non-MDL (read or write)!
+
+ if (IrpContext->MajorFunction == IRP_MJ_READ
+ || IrpContext->MajorFunction == IRP_MJ_WRITE) {
+
+ ClearFlag(IrpContext->MinorFunction, IRP_MN_DPC);
+
+ //
+ // Lock the user's buffer if this is not an Mdl request.
+ //
+
+ if (!FlagOn( IrpContext->MinorFunction, IRP_MN_MDL )) {
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ (IrpContext->MajorFunction == IRP_MJ_READ) ?
+ IoWriteAccess : IoReadAccess,
+ IrpSp->Parameters.Write.Length );
+ }
+
+ //
+ // We also need to check whether this is a query directory operation.
+ //
+
+ } else if (IrpContext->MajorFunction == IRP_MJ_DIRECTORY_CONTROL
+ && IrpContext->MinorFunction == IRP_MN_QUERY_DIRECTORY) {
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ IoWriteAccess,
+ IrpSp->Parameters.QueryDirectory.Length );
+
+ //
+ // We also need to check whether this is a query ea operation.
+ //
+
+ } else if (IrpContext->MajorFunction == IRP_MJ_QUERY_EA) {
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ IoWriteAccess,
+ IrpSp->Parameters.QueryEa.Length );
+
+ //
+ // We also need to check whether this is a set ea operation.
+ //
+
+ } else if (IrpContext->MajorFunction == IRP_MJ_SET_EA) {
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ IoReadAccess,
+ IrpSp->Parameters.SetEa.Length );
+
+ //
+ // These two FSCTLs use neither I/O, so check for them.
+ //
+
+ } else if ((IrpContext->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL) &&
+ (IrpContext->MinorFunction == IRP_MN_USER_FS_REQUEST) &&
+ ((IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_GET_VOLUME_BITMAP) ||
+ (IrpSp->Parameters.FileSystemControl.FsControlCode == FSCTL_GET_RETRIEVAL_POINTERS))) {
+
+ NtfsLockUserBuffer( IrpContext,
+ Irp,
+ IoWriteAccess,
+ IrpSp->Parameters.FileSystemControl.OutputBufferLength );
+ }
+
+ //
+ // Mark that we've already returned pending to the user
+ //
+
+ IoMarkIrpPending( Irp );
+ }
+ }
+
+ return;
+}
+
+
+NTSTATUS
+NtfsPostRequest(
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine enqueues the request packet specified by IrpContext to the
+ work queue associated with the FileSystemDeviceObject. This is a FSD
+ routine.
+
+Arguments:
+
+ IrpContext - Pointer to the IrpContext to be queued to the Fsp
+
+ Irp - I/O Request Packet (or FileObject in special close path)
+
+Return Value:
+
+ STATUS_PENDING
+
+
+--*/
+
+{
+ //
+ // Before posting, free any Scb snapshots. Note that if someone
+ // is calling this routine directly to post, then he better not
+ // have changed any disk structures, and thus we should have no
+ // work to do. On the other hand, if someone raised a status
+ // (like STATUS_CANT_WAIT), then we do both a transaction abort
+ // and restore of these Scb values.
+ //
+
+ ASSERT( !ARGUMENT_PRESENT( Irp )
+ || !FlagOn( Irp->Flags, IRP_PAGING_IO )
+ || (IrpContext->MajorFunction != IRP_MJ_READ
+ && IrpContext->MajorFunction != IRP_MJ_WRITE));
+
+ NtfsFreeSnapshotsForFcb( IrpContext, NULL );
+
+ RtlZeroMemory( &IrpContext->ScbSnapshot, sizeof(SCB_SNAPSHOT) );
+
+ NtfsPrePostIrp( IrpContext, Irp );
+
+ NtfsAddToWorkque( IrpContext, Irp );
+
+ //
+ // And return to our caller
+ //
+
+ return STATUS_PENDING;
+}
+
+
+//
+// Local support routine.
+//
+
+VOID
+NtfsAddToWorkque (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine is called to acually store the posted Irp to the Fsp
+ workque.
+
+Arguments:
+
+ IrpContext - Pointer to the IrpContext to be queued to the Fsp
+
+ Irp - I/O Request Packet.
+
+Return Value:
+
+ None.
+
+--*/
+
+{
+ PIO_STACK_LOCATION IrpSp;
+
+
+ if (ARGUMENT_PRESENT( Irp )) {
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ //
+ // Check if this request has an associated file object, and thus volume
+ // device object.
+ //
+
+ if ( IrpSp->FileObject != NULL ) {
+
+ KIRQL SavedIrql;
+ PVOLUME_DEVICE_OBJECT Vdo;
+
+ Vdo = CONTAINING_RECORD( IrpSp->DeviceObject,
+ VOLUME_DEVICE_OBJECT,
+ DeviceObject );
+
+ //
+ // Check to see if this request should be sent to the overflow
+ // queue. If not, then send it off to an exworker thread.
+ //
+
+ ExAcquireSpinLock( &Vdo->OverflowQueueSpinLock, &SavedIrql );
+
+ if ( Vdo->PostedRequestCount > FSP_PER_DEVICE_THRESHOLD) {
+
+ //
+ // We cannot currently respond to this IRP so we'll just enqueue it
+ // to the overflow queue on the volume.
+ //
+
+ InsertTailList( &Vdo->OverflowQueue,
+ &IrpContext->WorkQueueItem.List );
+
+ Vdo->OverflowQueueCount += 1;
+
+ ExReleaseSpinLock( &Vdo->OverflowQueueSpinLock, SavedIrql );
+
+ return;
+
+ } else {
+
+ //
+ // We are going to send this Irp to an ex worker thread so up
+ // the count.
+ //
+
+ Vdo->PostedRequestCount += 1;
+
+ ExReleaseSpinLock( &Vdo->OverflowQueueSpinLock, SavedIrql );
+ }
+ }
+ }
+
+ //
+ // Send it off.....
+ //
+
+ ExInitializeWorkItem( &IrpContext->WorkQueueItem,
+ NtfsFspDispatch,
+ (PVOID)IrpContext );
+
+ ExQueueWorkItem( &IrpContext->WorkQueueItem, CriticalWorkQueue );
+
+ return;
+}
+
diff --git a/private/ntos/cntfs/write.c b/private/ntos/cntfs/write.c
new file mode 100644
index 000000000..c4fbc8ef1
--- /dev/null
+++ b/private/ntos/cntfs/write.c
@@ -0,0 +1,2769 @@
+/*++
+
+Copyright (c) 1991 Microsoft Corporation
+
+Module Name:
+
+ Write.c
+
+Abstract:
+
+ This module implements the File Write routine for Ntfs called by the
+ dispatch driver.
+
+Author:
+
+ Brian Andrew BrianAn 19-Aug-1991
+
+Revision History:
+
+--*/
+
+#include "NtfsProc.h"
+
+//
+// The local debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_WRITE)
+
+//
+// Define a tag for general pool allocations from this module
+//
+
+#undef MODULE_POOL_TAG
+#define MODULE_POOL_TAG ('WFtN')
+
+#define OVERFLOW_WRITE_THRESHHOLD (0x1a00)
+
+#define CollectWriteStats(VCB,OPEN_TYPE,SCB,FCB,BYTE_COUNT,IRP_CONTEXT,TLIC) { \
+ PFILESYSTEM_STATISTICS FsStats = &(VCB)->Statistics[KeGetCurrentProcessorNumber()]; \
+ if (!FlagOn( (FCB)->FcbState, FCB_STATE_SYSTEM_FILE )) { \
+ if (NtfsIsTypeCodeUserData( (SCB)->AttributeTypeCode )) { \
+ FsStats->UserFileWrites += 1; \
+ FsStats->UserFileWriteBytes += (ULONG)(BYTE_COUNT); \
+ } else { \
+ FsStats->Ntfs.UserIndexWrites += 1; \
+ FsStats->Ntfs.UserIndexWriteBytes += (ULONG)(BYTE_COUNT); \
+ } \
+ } else { \
+ if ((SCB) != (VCB)->LogFileScb) { \
+ FsStats->MetaDataWrites += 1; \
+ FsStats->MetaDataWriteBytes += (ULONG)(BYTE_COUNT); \
+ } \
+ \
+ if ((SCB) == (VCB)->MftScb) { \
+ FsStats->Ntfs.MftWrites += 1; \
+ FsStats->Ntfs.MftWriteBytes += (ULONG)(BYTE_COUNT); \
+ \
+ if ((IRP_CONTEXT) == (TLIC)) { \
+ FsStats->Ntfs.MftWritesLazyWriter += 1; \
+ } else if ((TLIC)->LastRestartArea.QuadPart != 0) { \
+ FsStats->Ntfs.MftWritesFlushForLogFileFull += 1; \
+ } else { \
+ FsStats->Ntfs.MftWritesUserRequest += 1; \
+ \
+ switch ((TLIC)->MajorFunction) { \
+ case IRP_MJ_WRITE: \
+ FsStats->Ntfs.MftWritesUserLevel.Write += 1; \
+ break; \
+ case IRP_MJ_CREATE: \
+ FsStats->Ntfs.MftWritesUserLevel.Create += 1; \
+ break; \
+ case IRP_MJ_SET_INFORMATION: \
+ FsStats->Ntfs.MftWritesUserLevel.SetInfo += 1; \
+ break; \
+ case IRP_MJ_FLUSH_BUFFERS: \
+ FsStats->Ntfs.MftWritesUserLevel.Flush += 1; \
+ break; \
+ default: \
+ break; \
+ } \
+ } \
+ } else if ((SCB) == (VCB)->Mft2Scb) { \
+ FsStats->Ntfs.Mft2Writes += 1; \
+ FsStats->Ntfs.Mft2WriteBytes += (ULONG)(BYTE_COUNT); \
+ \
+ if ((IRP_CONTEXT) == (TLIC)) { \
+ FsStats->Ntfs.Mft2WritesLazyWriter += 1; \
+ } else if ((TLIC)->LastRestartArea.QuadPart != 0) { \
+ FsStats->Ntfs.Mft2WritesFlushForLogFileFull += 1; \
+ } else { \
+ FsStats->Ntfs.Mft2WritesUserRequest += 1; \
+ \
+ switch ((TLIC)->MajorFunction) { \
+ case IRP_MJ_WRITE: \
+ FsStats->Ntfs.Mft2WritesUserLevel.Write += 1; \
+ break; \
+ case IRP_MJ_CREATE: \
+ FsStats->Ntfs.Mft2WritesUserLevel.Create += 1; \
+ break; \
+ case IRP_MJ_SET_INFORMATION: \
+ FsStats->Ntfs.Mft2WritesUserLevel.SetInfo += 1; \
+ break; \
+ case IRP_MJ_FLUSH_BUFFERS: \
+ FsStats->Ntfs.Mft2WritesUserLevel.Flush += 1; \
+ break; \
+ default: \
+ break; \
+ } \
+ } \
+ } else if ((SCB) == (VCB)->RootIndexScb) { \
+ FsStats->Ntfs.RootIndexWrites += 1; \
+ FsStats->Ntfs.RootIndexWriteBytes += (ULONG)(BYTE_COUNT); \
+ } else if ((SCB) == (VCB)->BitmapScb) { \
+ FsStats->Ntfs.BitmapWrites += 1; \
+ FsStats->Ntfs.BitmapWriteBytes += (ULONG)(BYTE_COUNT); \
+ \
+ if ((IRP_CONTEXT) == (TLIC)) { \
+ FsStats->Ntfs.BitmapWritesLazyWriter += 1; \
+ } else if ((TLIC)->LastRestartArea.QuadPart != 0) { \
+ FsStats->Ntfs.BitmapWritesFlushForLogFileFull += 1; \
+ } else { \
+ FsStats->Ntfs.BitmapWritesUserRequest += 1; \
+ \
+ switch ((TLIC)->MajorFunction) { \
+ case IRP_MJ_WRITE: \
+ FsStats->Ntfs.BitmapWritesUserLevel.Write += 1; \
+ break; \
+ case IRP_MJ_CREATE: \
+ FsStats->Ntfs.BitmapWritesUserLevel.Create += 1; \
+ break; \
+ case IRP_MJ_SET_INFORMATION: \
+ FsStats->Ntfs.BitmapWritesUserLevel.SetInfo += 1; \
+ break; \
+ default: \
+ break; \
+ } \
+ } \
+ } else if ((SCB) == (VCB)->MftBitmapScb) { \
+ FsStats->Ntfs.MftBitmapWrites += 1; \
+ FsStats->Ntfs.MftBitmapWriteBytes += (ULONG)(BYTE_COUNT); \
+ \
+ if ((IRP_CONTEXT) == (TLIC)) { \
+ FsStats->Ntfs.MftBitmapWritesLazyWriter += 1; \
+ } else if ((TLIC)->LastRestartArea.QuadPart != 0) { \
+ FsStats->Ntfs.MftBitmapWritesFlushForLogFileFull += 1; \
+ } else { \
+ FsStats->Ntfs.MftBitmapWritesUserRequest += 1; \
+ \
+ switch ((TLIC)->MajorFunction) { \
+ case IRP_MJ_WRITE: \
+ FsStats->Ntfs.MftBitmapWritesUserLevel.Write += 1; \
+ break; \
+ case IRP_MJ_CREATE: \
+ FsStats->Ntfs.MftBitmapWritesUserLevel.Create += 1; \
+ break; \
+ case IRP_MJ_SET_INFORMATION: \
+ FsStats->Ntfs.MftBitmapWritesUserLevel.SetInfo += 1; \
+ break; \
+ default: \
+ break; \
+ } \
+ } \
+ } \
+ } \
+}
+
+#define WriteToEof (StartingVbo < 0)
+
+
+NTSTATUS
+NtfsFsdWrite (
+ IN PVOLUME_DEVICE_OBJECT VolumeDeviceObject,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This routine implements the FSD part of Write.
+
+Arguments:
+
+ VolumeDeviceObject - Supplies the volume device object where the
+ file exists
+
+ Irp - Supplies the Irp being processed
+
+Return Value:
+
+ NTSTATUS - The FSD status for the IRP
+
+--*/
+
+{
+ TOP_LEVEL_CONTEXT TopLevelContext;
+ PTOP_LEVEL_CONTEXT ThreadTopLevelContext;
+
+ NTSTATUS Status = STATUS_SUCCESS;
+ PIRP_CONTEXT IrpContext = NULL;
+
+ UNREFERENCED_PARAMETER( VolumeDeviceObject );
+ ASSERT_IRP( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsFsdWrite\n") );
+
+ //
+ // Call the common Write routine
+ //
+
+ FsRtlEnterFileSystem();
+
+ ThreadTopLevelContext = NtfsSetTopLevelIrp( &TopLevelContext, FALSE, FALSE );
+
+ do {
+
+ try {
+
+
+ //
+ // We are either initiating this request or retrying it.
+ //
+
+ if (IrpContext == NULL) {
+
+ IrpContext = NtfsCreateIrpContext( Irp, CanFsdWait( Irp ) );
+ NtfsUpdateIrpContextWithTopLevel( IrpContext, ThreadTopLevelContext );
+
+ if (ThreadTopLevelContext->ScbBeingHotFixed != NULL) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_HOTFIX_UNDERWAY );
+ }
+
+ //
+ // If this is an MDL_WRITE then the Mdl in the Irp should
+ // be NULL.
+ //
+
+ if (FlagOn( IrpContext->MinorFunction, IRP_MN_MDL ) &&
+ !FlagOn( IrpContext->MinorFunction, IRP_MN_COMPLETE )) {
+
+ Irp->MdlAddress = NULL;
+ }
+
+ } else if (Status == STATUS_LOG_FILE_FULL) {
+
+ NtfsCheckpointForLogFileFull( IrpContext );
+ }
+
+ //
+ // If this is an Mdl complete request, don't go through
+ // common write.
+ //
+
+ ASSERT(!FlagOn( IrpContext->MinorFunction, IRP_MN_DPC ));
+
+ if (FlagOn( IrpContext->MinorFunction, IRP_MN_COMPLETE )) {
+
+ DebugTrace( 0, Dbg, ("Calling NtfsCompleteMdl\n") );
+ Status = NtfsCompleteMdl( IrpContext, Irp );
+
+ //
+ // Identify write requests which can't wait and post them to the
+ // Fsp.
+ //
+
+ } else {
+
+ //
+ // Capture the auxiliary buffer and clear its address if it
+ // is not supposed to be deleted by the I/O system on I/O completion.
+ //
+
+ if (Irp->Tail.Overlay.AuxiliaryBuffer != NULL) {
+
+ IrpContext->Union.AuxiliaryBuffer =
+ (PFSRTL_AUXILIARY_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
+
+ if (!FlagOn(IrpContext->Union.AuxiliaryBuffer->Flags,
+ FSRTL_AUXILIARY_FLAG_DEALLOCATE)) {
+
+ Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
+ }
+ }
+
+ Status = NtfsCommonWrite( IrpContext, Irp );
+ }
+
+ break;
+
+ } except(NtfsExceptionFilter( IrpContext, GetExceptionInformation() )) {
+
+ NTSTATUS ExceptionCode;
+
+ //
+ // We had some trouble trying to perform the requested
+ // operation, so we'll abort the I/O request with
+ // the error status that we get back from the
+ // execption code
+ //
+
+ ExceptionCode = GetExceptionCode();
+
+ if (ExceptionCode == STATUS_FILE_DELETED) {
+
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_SUCCESS;
+
+ } else if (ExceptionCode == STATUS_VOLUME_DISMOUNTED) {
+
+ IrpContext->ExceptionStatus = ExceptionCode = STATUS_SUCCESS;
+ }
+
+ Status = NtfsProcessException( IrpContext,
+ Irp,
+ ExceptionCode );
+ }
+
+ } while ((Status == STATUS_CANT_WAIT || Status == STATUS_LOG_FILE_FULL) &&
+ (ThreadTopLevelContext == &TopLevelContext));
+
+ if (ThreadTopLevelContext == &TopLevelContext) {
+ NtfsRestoreTopLevelIrp( ThreadTopLevelContext );
+ }
+
+ FsRtlExitFileSystem();
+
+ //
+ // And return to our caller
+ //
+
+ DebugTrace( -1, Dbg, ("NtfsFsdWrite -> %08lx\n", Status) );
+
+ return Status;
+}
+
+
+NTSTATUS
+NtfsCommonWrite (
+ IN PIRP_CONTEXT IrpContext,
+ IN PIRP Irp
+ )
+
+/*++
+
+Routine Description:
+
+ This is the common routine for Write called by both the fsd and fsp
+ threads.
+
+Arguments:
+
+ Irp - Supplies the Irp to process
+
+Return Value:
+
+ NTSTATUS - The return status for the operation
+
+--*/
+
+{
+ NTSTATUS Status;
+ PIO_STACK_LOCATION IrpSp;
+ PFILE_OBJECT FileObject;
+ PFILE_OBJECT UserFileObject;
+
+ TYPE_OF_OPEN TypeOfOpen;
+ PVCB Vcb;
+ PFCB Fcb;
+ PSCB Scb;
+ PCCB Ccb;
+
+ EOF_WAIT_BLOCK EofWaitBlock;
+ PFSRTL_ADVANCED_FCB_HEADER Header;
+
+ BOOLEAN OplockPostIrp = FALSE;
+ BOOLEAN PostIrp = FALSE;
+
+ PVOID SystemBuffer = NULL;
+ PVOID SafeBuffer = NULL;
+
+ BOOLEAN CalledByLazyWriter = FALSE;
+ BOOLEAN RecursiveWriteThrough = FALSE;
+ BOOLEAN ScbAcquired = FALSE;
+ BOOLEAN PagingIoResourceAcquired = FALSE;
+
+ BOOLEAN UpdateMft = FALSE;
+ BOOLEAN DoingIoAtEof = FALSE;
+ BOOLEAN SetWriteSeen = FALSE;
+
+ BOOLEAN CcFileSizeChangeDue = FALSE;
+
+ BOOLEAN Wait;
+ BOOLEAN OriginalWait;
+ BOOLEAN PagingIo;
+ BOOLEAN NonCachedIo;
+ BOOLEAN SynchronousIo;
+
+ NTFS_IO_CONTEXT LocalContext;
+
+ VBO StartingVbo;
+ LONGLONG ByteCount;
+ LONGLONG ByteRange;
+ LONGLONG OldFileSize;
+
+ PCOMPRESSED_DATA_INFO CompressedDataInfo;
+ ULONG EngineMatches;
+ ULONG CompressionUnitSize, ChunkSize;
+
+ PVOID NewBuffer;
+ PMDL NewMdl;
+ PMDL OriginalMdl;
+ PVOID OriginalBuffer;
+ ULONG TempLength;
+
+ PATTRIBUTE_RECORD_HEADER Attribute;
+ ATTRIBUTE_ENUMERATION_CONTEXT AttrContext;
+ BOOLEAN CleanupAttributeContext = FALSE;
+
+ LONGLONG LlTemp1;
+ LONGLONG LlTemp2;
+
+ ASSERT_IRP_CONTEXT( IrpContext );
+ ASSERT_IRP( Irp );
+
+ //
+ // Get the current Irp stack location
+ //
+
+ IrpSp = IoGetCurrentIrpStackLocation( Irp );
+
+ DebugTrace( +1, Dbg, ("NtfsCommonWrite\n") );
+ DebugTrace( 0, Dbg, ("IrpContext = %08lx\n", IrpContext) );
+ DebugTrace( 0, Dbg, ("Irp = %08lx\n", Irp) );
+
+ //
+ // Extract and decode the file object
+ //
+
+ UserFileObject = FileObject = IrpSp->FileObject;
+ TypeOfOpen = NtfsDecodeFileObject( IrpContext, FileObject, &Vcb, &Fcb, &Scb, &Ccb, TRUE );
+
+ //
+ // Let's kill invalid write requests.
+ //
+
+ if (TypeOfOpen != UserFileOpen &&
+ TypeOfOpen != UserVolumeOpen &&
+ TypeOfOpen != StreamFileOpen ) {
+
+ DebugTrace( 0, Dbg, ("Invalid file object for write\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite: Exit -> %08lx\n", STATUS_INVALID_DEVICE_REQUEST) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_INVALID_DEVICE_REQUEST );
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ //
+ // If this is a recursive request which has already failed then
+ // complete this request with STATUS_FILE_LOCK_CONFLICT. Always let the
+ // log file requests go through though since Cc won't get a chance to
+ // retry.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY ) &&
+ !NT_SUCCESS( IrpContext->TopLevelIrpContext->ExceptionStatus ) &&
+ (Scb != Scb->Vcb->LogFileScb)) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_FILE_LOCK_CONFLICT );
+ return STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ //
+ // Check if this volume has already been shut down. If it has, fail
+ // this write request.
+ //
+
+ //**** ASSERT( !FlagOn(Vcb->VcbState, VCB_STATE_FLAG_SHUTDOWN) );
+
+ if (FlagOn(Vcb->VcbState, VCB_STATE_FLAG_SHUTDOWN)) {
+
+ Irp->IoStatus.Information = 0;
+
+ DebugTrace( 0, Dbg, ("Write for volume that is already shutdown.\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite: Exit -> %08lx\n", STATUS_TOO_LATE) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_TOO_LATE );
+ return STATUS_TOO_LATE;
+ }
+
+ //
+ // Initialize the appropriate local variables.
+ //
+
+ OriginalWait =
+ Wait = BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ PagingIo = BooleanFlagOn(Irp->Flags, IRP_PAGING_IO);
+ NonCachedIo = BooleanFlagOn(Irp->Flags,IRP_NOCACHE);
+ SynchronousIo = BooleanFlagOn(FileObject->Flags, FO_SYNCHRONOUS_IO);
+
+ DebugTrace( 0, Dbg, ("PagingIo -> %04x\n", PagingIo) );
+ DebugTrace( 0, Dbg, ("NonCachedIo -> %04x\n", NonCachedIo) );
+ DebugTrace( 0, Dbg, ("SynchronousIo -> %04x\n", SynchronousIo) );
+
+ ASSERT( PagingIo || FileObject->WriteAccess );
+
+ //
+ // Extract starting Vbo and offset.
+ //
+
+ StartingVbo = IrpSp->Parameters.Write.ByteOffset.QuadPart;
+ ByteCount = (LONGLONG) IrpSp->Parameters.Write.Length;
+ ByteRange = StartingVbo + ByteCount;
+
+ DebugTrace( 0, Dbg, ("StartingVbo -> %016I64x\n", StartingVbo) );
+
+ //
+ // Check for a null request, and return immediately
+ //
+
+ if ((ULONG)ByteCount == 0) {
+
+ Irp->IoStatus.Information = 0;
+
+ DebugTrace( 0, Dbg, ("No bytes to write\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite: Exit -> %08lx\n", STATUS_SUCCESS) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // If this is async Io to a compressed stream
+ // then we will make this look synchronous.
+ //
+
+ if (Scb->CompressionUnit != 0) {
+
+ Wait = TRUE;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ }
+
+ //
+ // See if we have to defer the write.
+ //
+
+ if (!PagingIo &&
+ (!NonCachedIo || (Scb->CompressionUnit != 0)) &&
+ !CcCanIWrite(FileObject,
+ (ULONG)ByteCount,
+ (BOOLEAN)(FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) &&
+ !FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_IN_FSP)),
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_DEFERRED_WRITE))) {
+
+ BOOLEAN Retrying = BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_DEFERRED_WRITE);
+
+ NtfsPrePostIrp( IrpContext, Irp );
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_DEFERRED_WRITE );
+
+ CcDeferWrite( FileObject,
+ (PCC_POST_DEFERRED_WRITE)NtfsAddToWorkque,
+ IrpContext,
+ Irp,
+ (ULONG)ByteCount,
+ Retrying );
+
+ return STATUS_PENDING;
+ }
+
+ //
+ // Use a local pointer to the Scb header for convenience.
+ //
+
+ Header = &Scb->Header;
+
+ //
+ // Make sure there is an initialized NtfsIoContext block.
+ //
+
+ if (TypeOfOpen == UserVolumeOpen
+ || NonCachedIo) {
+
+ //
+ // If there is a context pointer, we need to make sure it was
+ // allocated and not a stale stack pointer.
+ //
+
+ if (IrpContext->Union.NtfsIoContext == NULL
+ || !FlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT )) {
+
+ //
+ // If we can wait, use the context on the stack. Otherwise
+ // we need to allocate one.
+ //
+
+ if (Wait) {
+
+ IrpContext->Union.NtfsIoContext = &LocalContext;
+ ClearFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ } else {
+
+ IrpContext->Union.NtfsIoContext = (PNTFS_IO_CONTEXT)ExAllocateFromNPagedLookasideList( &NtfsIoContextLookasideList );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+ }
+ }
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ //
+ // Store whether we allocated this context structure in the structure
+ // itself.
+ //
+
+ IrpContext->Union.NtfsIoContext->AllocatedContext =
+ BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ if (Wait) {
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+
+ } else {
+
+ IrpContext->Union.NtfsIoContext->PagingIo = PagingIo;
+ IrpContext->Union.NtfsIoContext->Wait.Async.ResourceThreadId =
+ ExGetCurrentResourceThread();
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.RequestedByteCount =
+ (ULONG)ByteCount;
+ }
+ }
+
+ DebugTrace( 0, Dbg, ("PagingIo -> %04x\n", PagingIo) );
+ DebugTrace( 0, Dbg, ("NonCachedIo -> %04x\n", NonCachedIo) );
+ DebugTrace( 0, Dbg, ("SynchronousIo -> %04x\n", SynchronousIo) );
+ DebugTrace( 0, Dbg, ("WriteToEof -> %04x\n", WriteToEof) );
+
+ //
+ // Handle volume Dasd here.
+ //
+
+ if (TypeOfOpen == UserVolumeOpen) {
+
+ //
+ // If the caller has not asked for extended DASD IO access then
+ // limit with the volume size.
+ //
+
+ if (!FlagOn( Ccb->Flags, CCB_FLAG_ALLOW_XTENDED_DASD_IO )) {
+
+ //
+ // If this is a volume file, we cannot write past the current
+ // end of file (volume). We check here now before continueing.
+ //
+ // If the starting vbo is past the end of the volume, we are done.
+ //
+
+ if (WriteToEof || (Header->FileSize.QuadPart <= StartingVbo)) {
+
+ DebugTrace( 0, Dbg, ("No bytes to write\n") );
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite: Exit -> %08lx\n", STATUS_SUCCESS) );
+
+ NtfsCompleteRequest( &IrpContext, &Irp, STATUS_SUCCESS );
+ return STATUS_SUCCESS;
+
+ //
+ // If the write extends beyond the end of the volume, truncate the
+ // bytes to write.
+ //
+
+ } else if (Header->FileSize.QuadPart < ByteRange) {
+
+ ByteCount = Header->FileSize.QuadPart - StartingVbo;
+ }
+ }
+
+ SetFlag( UserFileObject->Flags, FO_FILE_MODIFIED );
+ Status = NtfsVolumeDasdIo( IrpContext,
+ Irp,
+ Vcb,
+ StartingVbo,
+ (ULONG)ByteCount );
+
+ //
+ // If the volume was opened for Synchronous IO, update the current
+ // file position.
+ //
+
+ if (SynchronousIo && !PagingIo && NT_SUCCESS(Status)) {
+
+ UserFileObject->CurrentByteOffset.QuadPart = StartingVbo + (LONGLONG) Irp->IoStatus.Information;
+ }
+
+ DebugTrace( 0, Dbg, ("Complete with %08lx bytes written\n", Irp->IoStatus.Information) );
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite: Exit -> %08lx\n", Status) );
+
+ if (Wait) {
+
+ NtfsCompleteRequest( &IrpContext, &Irp, Status );
+ }
+
+ return Status;
+ }
+
+ //
+ // If this is a paging file, just send it to the device driver.
+ // We assume Mm is a good citizen.
+ //
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_PAGING_FILE )
+ && FlagOn( Scb->ScbState, SCB_STATE_UNNAMED_DATA )) {
+
+ if (FlagOn( Fcb->FcbState, FCB_STATE_FILE_DELETED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL );
+ }
+
+ //
+ // Do the usual STATUS_PENDING things.
+ //
+
+ IoMarkIrpPending( Irp );
+
+ //
+ // Perform the actual IO, it will be completed when the io finishes.
+ //
+
+ NtfsPagingFileIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ (ULONG)ByteCount );
+
+ //
+ // We, nor anybody else, need the IrpContext any more.
+ //
+
+ NtfsCompleteRequest( &IrpContext, NULL, 0 );
+
+ return STATUS_PENDING;
+ }
+
+ //
+ // Accumulate interesting statistics.
+ //
+
+ if (PagingIo) {
+ CollectWriteStats( Vcb, TypeOfOpen, Scb, Fcb, ByteCount, IrpContext,
+ IrpContext->TopLevelIrpContext );
+ }
+
+ //
+ // Use a try-finally to free Scb and buffers on the way out.
+ // At this point we can treat all requests identically since we
+ // have a usable Scb for each of them. (Volume, User or Stream file)
+ //
+
+ Status = STATUS_SUCCESS;
+
+ try {
+
+ //
+ // If this is a noncached transfer and is not a paging I/O, and
+ // the file has been opened cached, then we will do a flush here
+ // to avoid stale data problems. Note that we must flush before
+ // acquiring the Fcb shared since the write may try to acquire
+ // it exclusive.
+ //
+ // CcFlushCache may not raise.
+ //
+ // The Purge following the flush will guarantee cache coherency.
+ //
+
+ if (NonCachedIo &&
+ !PagingIo &&
+ (TypeOfOpen != StreamFileOpen) &&
+ (FileObject->SectionObjectPointer->DataSectionObject != NULL)) {
+
+ //
+ // Acquire the paging io resource to test the compression state. If the
+ // file is compressed this will add serialization up to the point where
+ // CcCopyWrite flushes the data, but those flushes will be serialized
+ // anyway. Uncompressed files will need the paging io resource
+ // exclusive to do the flush.
+ //
+
+ ExAcquireResourceExclusive( Header->PagingIoResource, TRUE );
+ PagingIoResourceAcquired = TRUE;
+
+ if (Scb->CompressionUnit == 0) {
+
+ if (WriteToEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB) Scb;
+ }
+
+ CcFlushCache( &Scb->NonpagedScb->SegmentObject,
+ WriteToEof ? &Header->FileSize : (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ &Irp->IoStatus );
+
+ if (WriteToEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ //
+ // Make sure there was no error in the flush path.
+ //
+
+ if (!NT_SUCCESS( IrpContext->TopLevelIrpContext->ExceptionStatus ) ||
+ !NT_SUCCESS( Irp->IoStatus.Status )) {
+
+ NtfsNormalizeAndCleanupTransaction( IrpContext,
+ &Irp->IoStatus.Status,
+ TRUE,
+ STATUS_UNEXPECTED_IO_ERROR );
+ }
+
+ //
+ // Now purge the data for this range.
+ //
+
+ NtfsDeleteInternalAttributeStream( Scb, FALSE );
+
+ CcPurgeCacheSection( &Scb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ FALSE );
+ }
+
+ //
+ // Convert to shared but don't release the resource. This will synchronize
+ // this operation with defragging.
+ //
+
+ ExConvertExclusiveToSharedLite( Header->PagingIoResource );
+ }
+
+ if (PagingIo) {
+
+ //
+ // For all paging I/O, the correct resource has already been
+ // acquired shared - PagingIoResource if it exists, or else
+ // main Resource. In some rare cases this is not currently
+ // true (shutdown & segment dereference thread), so we acquire
+ // shared here, but we starve exclusive in these rare cases
+ // to be a little more resilient to deadlocks! Most of the
+ // time all we do is the test.
+ //
+
+ if ((Header->PagingIoResource != NULL) &&
+ !ExIsResourceAcquiredShared(Header->PagingIoResource) &&
+ !ExIsResourceAcquiredShared(Header->Resource)) {
+
+ ExAcquireSharedStarveExclusive( Header->PagingIoResource, TRUE );
+ PagingIoResourceAcquired = TRUE;
+ }
+
+ //
+ // Note that the lazy writer must not be allowed to try and
+ // acquire the resource exclusive. This is not a problem since
+ // the lazy writer is paging IO and thus not allowed to extend
+ // file size, and is never the top level guy, thus not able to
+ // extend valid data length.
+ //
+
+ if ((Scb->LazyWriteThread[0] == PsGetCurrentThread()) ||
+ (Scb->LazyWriteThread[1] == PsGetCurrentThread())) {
+
+ DebugTrace( 0, Dbg, ("Lazy writer generated write\n") );
+ CalledByLazyWriter = TRUE;
+
+ //
+ // If the temporary bit is set in the Scb then set the temporary
+ // bit in the file object. In case the temporary bit has changed
+ // in the Scb, this is a good file object to fix it in!
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_TEMPORARY )) {
+ SetFlag( FileObject->Flags, FO_TEMPORARY_FILE );
+ } else {
+ ClearFlag( FileObject->Flags, FO_TEMPORARY_FILE );
+ }
+
+ //
+ // Test if we are the result of a recursive flush in the write path. In
+ // that case we won't have to update valid data.
+ //
+
+ } else {
+
+ //
+ // Check if we are recursing into write from a write via the
+ // cache manager.
+ //
+
+ if (FlagOn( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_SEEN )) {
+
+ RecursiveWriteThrough = TRUE;
+
+ //
+ // If the top level request is a write to the same file object
+ // then set the write-through flag in the current Scb. We
+ // know the current request is not top-level because some
+ // other write has already set the bit in the top IrpContext.
+ //
+
+ if ((IrpContext->TopLevelIrpContext->MajorFunction == IRP_MJ_WRITE) &&
+ (IrpContext->TopLevelIrpContext->OriginatingIrp != NULL) &&
+ (FileObject->FsContext ==
+ IoGetCurrentIrpStackLocation( IrpContext->TopLevelIrpContext->OriginatingIrp )->FileObject->FsContext)) {
+
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH );
+ }
+
+ //
+ // Otherwise set the flag in the top level IrpContext showing that
+ // we have entered write.
+ //
+
+ } else {
+
+ SetFlag(IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_SEEN);
+ SetWriteSeen = TRUE;
+
+ //
+ // This is could be someone who extends valid data,
+ // like the Mapped Page Writer or a flush, so we have to
+ // duplicate code from below to serialize this guy with I/O
+ // at the end of the file. We do not extend valid data for
+ // metadata streams and need to eliminate them to avoid deadlocks
+ // later.
+ //
+
+ if (!FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE)) {
+
+ ASSERT(!WriteToEof);
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped.
+ //
+
+ if (ByteRange > Header->ValidDataLength.QuadPart) {
+
+ //
+ // Our caller may already be synchronized with EOF.
+ // The FcbWithPaging field in the top level IrpContext
+ // will have either the current Fcb/Scb if so.
+ //
+
+ if ((IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == Fcb) ||
+ (IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == (PFCB) Scb)) {
+
+ DoingIoAtEof = TRUE;
+ OldFileSize = Header->FileSize.QuadPart;
+
+ } else {
+
+ //
+ // We can change FileSize and ValidDataLength if either, no one
+ // else is now, or we are still extending after waiting.
+ // We won't block the mapped page writer on IoAtEof. Test
+ // the original state of the wait flag to know this.
+ //
+
+ if (FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE )) {
+
+ if (!OriginalWait) {
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ try_return( Status = STATUS_FILE_LOCK_CONFLICT );
+ }
+
+ DoingIoAtEof = NtfsWaitForIoAtEof( Header, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, &EofWaitBlock );
+
+ } else {
+
+ DoingIoAtEof = TRUE;
+ }
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Store this in the IrpContext until commit or post
+ //
+
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+
+ OldFileSize = Header->FileSize.QuadPart;
+ }
+ }
+
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+ }
+ }
+
+ //
+ // If are paging io, then we do not want
+ // to write beyond end of file. If the base is beyond Eof, we will just
+ // Noop the call. If the transfer starts before Eof, but extends
+ // beyond, we will truncate the transfer to the last sector
+ // boundary.
+ //
+ // Just in case this is paging io, limit write to file size.
+ // Otherwise, in case of write through, since Mm rounds up
+ // to a page, we might try to acquire the resource exclusive
+ // when our top level guy only acquired it shared. Thus, =><=.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+ if (ByteRange > Header->FileSize.QuadPart) {
+
+ if (StartingVbo >= Header->FileSize.QuadPart) {
+ DebugTrace( 0, Dbg, ("PagingIo started beyond EOF.\n") );
+
+ Irp->IoStatus.Information = 0;
+
+ //
+ // Make sure we do not advance ValidDataLength!
+ //
+
+ ByteRange = Header->ValidDataLength.QuadPart;
+
+ ExReleaseFastMutex( Header->FastMutex );
+ try_return( Status = STATUS_SUCCESS );
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("PagingIo extending beyond EOF.\n") );
+
+ ByteCount = Header->FileSize.QuadPart - StartingVbo;
+ ByteRange = Header->FileSize.QuadPart;
+ }
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // If not paging I/O, then we must acquire a resource, and do some
+ // other initialization.
+ //
+
+ } else {
+
+ if (!PagingIoResourceAcquired &&
+ !ExAcquireSharedWaitForExclusive( Scb->Header.PagingIoResource, Wait )) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+ PagingIoResourceAcquired = TRUE;
+
+ //
+ // Check if we have already gone through cleanup on this handle.
+ //
+
+ if (FlagOn( Ccb->Flags, CCB_FLAG_CLEANUP )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CLOSED, NULL, NULL );
+ }
+
+ //
+ // Now synchronize with the FsRtl Header
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+
+ //
+ // Now see if we will change FileSize. We have to do it now
+ // so that our reads are not nooped.
+ //
+
+ if ((ByteRange > Header->ValidDataLength.QuadPart) || WriteToEof) {
+
+ //
+ // We expect this routine to be top level or, for the
+ // future, our caller is not already serialized.
+ //
+
+ ASSERT( IrpContext->TopLevelIrpContext->FcbWithPagingExclusive == NULL );
+
+ DoingIoAtEof = !FlagOn( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE ) ||
+ NtfsWaitForIoAtEof( Header, (PLARGE_INTEGER)&StartingVbo, (ULONG)ByteCount, &EofWaitBlock );
+
+ //
+ // Set the Flag if we are changing FileSize or ValidDataLength,
+ // and save current values.
+ //
+
+ if (DoingIoAtEof) {
+
+ SetFlag( Header->Flags, FSRTL_FLAG_EOF_ADVANCE_ACTIVE );
+
+ //
+ // Store this in the IrpContext until commit or post
+ //
+
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+
+ OldFileSize = Header->FileSize.QuadPart;
+
+ //
+ // Check for writing to end of File. If we are, then we have to
+ // recalculate the byte range.
+ //
+
+ if (WriteToEof) {
+
+ StartingVbo = Header->FileSize.QuadPart;
+ ByteRange = StartingVbo + ByteCount;
+ }
+ }
+ }
+
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // We cannot handle user noncached I/Os to compressed files, so we always
+ // divert them through the cache with write through.
+ //
+ // The reason that we always handle the user requests through the cache,
+ // is that there is no other safe way to deal with alignment issues, for
+ // the frequent case where the user noncached I/O is not an integral of
+ // the Compression Unit. We cannot, for example, read the rest of the
+ // compression unit into a scratch buffer, because we are not synchronized
+ // with anyone mapped to the file and modifying the other data. If we
+ // try to assemble the data in the cache in the noncached path, to solve
+ // the above problem, then we have to somehow purge these pages away
+ // to solve cache coherency problems, but then the pages could be modified
+ // by a file mapper and that would be wrong, too.
+ //
+ // Bottom line is we can only really support cached writes to compresed
+ // files.
+ //
+
+ if ((Scb->CompressionUnit != 0) && NonCachedIo) {
+
+ NonCachedIo = FALSE;
+
+ if (Scb->FileObject == NULL) {
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+
+ FileObject = Scb->FileObject;
+ SetFlag( FileObject->Flags, FO_WRITE_THROUGH );
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH );
+ }
+
+ if (!Wait && NonCachedIo) {
+
+ //
+ // Make sure we haven't exceeded our threshold for async requests
+ // on this thread.
+ //
+
+ if (ExIsResourceAcquiredShared( Header->PagingIoResource ) > MAX_SCB_ASYNC_ACQUIRE) {
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ IrpContext->Union.NtfsIoContext->Wait.Async.Resource = Header->PagingIoResource;
+ }
+
+ //
+ // Set the flag in our IrpContext to indicate that we have entered
+ // write.
+ //
+
+ ASSERT( !FlagOn( IrpContext->TopLevelIrpContext->Flags,
+ IRP_CONTEXT_FLAG_WRITE_SEEN ));
+
+ SetFlag( IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_SEEN );
+ SetWriteSeen = TRUE;
+ }
+
+ //
+ // Now check if the attribute has been deleted or is on a dismounted volume.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED | SCB_STATE_VOLUME_DISMOUNTED)) {
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_DELETED )) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_DELETED, NULL, NULL );
+
+ } else {
+
+ NtfsRaiseStatus( IrpContext, STATUS_VOLUME_DISMOUNTED, NULL, NULL );
+ }
+ }
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ // We skip this step for a $INDEX_ALLOCATION stream. We need to
+ // protect ourselves in the case where an $INDEX_ALLOCATION
+ // stream was created and deleted in an aborted transaction.
+ // In that case we may get a lazy-writer call which will
+ // naturally be nooped below since the valid data length
+ // in the Scb is 0.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ if (Scb->AttributeTypeCode != $INDEX_ALLOCATION) {
+
+ DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
+
+ //
+ // Acquire and drop the Scb when doing this.
+ //
+
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+ ScbAcquired = TRUE;
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ ExReleaseResource( Scb->Header.Resource );
+ ScbAcquired = FALSE;
+
+ } else {
+
+ ASSERT( Header->ValidDataLength.QuadPart == Li0.QuadPart );
+ }
+ }
+
+ //
+ // We assert that Paging Io writes will never WriteToEof.
+ //
+
+ ASSERT( !WriteToEof || !PagingIo );
+
+ //
+ // We assert that we never get a non-cached io call for a non-$DATA,
+ // resident attribute.
+ //
+
+ ASSERTMSG( "Non-cached I/O call on resident system attribute\n",
+ NtfsIsTypeCodeUserData( Scb->AttributeTypeCode ) ||
+ !NonCachedIo ||
+ !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ));
+
+ //
+ // Here is the deal with ValidDataLength and FileSize:
+ //
+ // Rule 1: PagingIo is never allowed to extend file size.
+ //
+ // Rule 2: Only the top level requestor may extend Valid
+ // Data Length. This may be paging IO, as when a
+ // a user maps a file, but will never be as a result
+ // of cache lazy writer writes since they are not the
+ // top level request.
+ //
+ // Rule 3: If, using Rules 1 and 2, we decide we must extend
+ // file size or valid data, we take the Fcb exclusive.
+ //
+
+ //
+ // Now see if we are writing beyond valid data length, and thus
+ // maybe beyond the file size. If so, then we must
+ // release the Fcb and reacquire it exclusive. Note that it is
+ // important that when not writing beyond EOF that we check it
+ // while acquired shared and keep the FCB acquired, in case some
+ // turkey truncates the file. Note that for paging Io we will
+ // already have acquired the file correctly.
+ //
+
+ if (DoingIoAtEof) {
+
+ //
+ // If this was a non-cached asynchronous operation we will
+ // convert it to synchronous. This is to allow the valid
+ // data length change to go out to disk and to fix the
+ // problem of the Fcb being in the exclusive Fcb list.
+ //
+
+ if (!Wait && NonCachedIo) {
+
+ Wait = TRUE;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+
+ RtlZeroMemory( IrpContext->Union.NtfsIoContext, sizeof( NTFS_IO_CONTEXT ));
+
+ //
+ // Store whether we allocated this context structure in the structure
+ // itself.
+ //
+
+ IrpContext->Union.NtfsIoContext->AllocatedContext =
+ BooleanFlagOn( IrpContext->Flags, IRP_CONTEXT_FLAG_ALLOC_CONTEXT );
+
+ KeInitializeEvent( &IrpContext->Union.NtfsIoContext->Wait.SyncEvent,
+ NotificationEvent,
+ FALSE );
+
+ //
+ // If this is async Io to a compressed stream
+ // then we will make this look synchronous.
+ //
+
+ } else if (Scb->CompressionUnit != 0) {
+
+ Wait = TRUE;
+ SetFlag( IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT );
+ }
+
+ //
+ // If the Scb is uninitialized, we initialize it now.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_HEADER_INITIALIZED )) {
+
+ DebugTrace( 0, Dbg, ("Initializing Scb -> %08lx\n", Scb) );
+ //
+ // Acquire and drop the Scb when doing this.
+ //
+
+ //
+ // Acquire and drop the Scb when doing this.
+ //
+
+ ExAcquireResourceShared( Scb->Header.Resource, TRUE );
+ ScbAcquired = TRUE;
+ NtfsUpdateScbFromAttribute( IrpContext, Scb, NULL );
+
+ ExReleaseResource( Scb->Header.Resource );
+ ScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // We check whether we can proceed based on the state of the file oplocks.
+ //
+
+ if (!PagingIo && (TypeOfOpen == UserFileOpen)) {
+
+ Status = FsRtlCheckOplock( &Scb->ScbType.Data.Oplock,
+ Irp,
+ IrpContext,
+ NtfsOplockComplete,
+ NtfsPrePostIrp );
+
+ if (Status != STATUS_SUCCESS) {
+
+ OplockPostIrp = TRUE;
+ PostIrp = TRUE;
+ try_return( NOTHING );
+ }
+
+ //
+ // This oplock call can affect whether fast IO is possible.
+ // We may have broken an oplock to no oplock held. If the
+ // current state of the file is FastIoIsNotPossible then
+ // recheck the fast IO state.
+ //
+
+ if (Header->IsFastIoPossible == FastIoIsNotPossible) {
+
+ NtfsAcquireFsrtlHeader( Scb );
+ Header->IsFastIoPossible = NtfsIsFastIoPossible( Scb );
+ NtfsReleaseFsrtlHeader( Scb );
+ }
+
+ //
+ // We have to check for write access according to the current
+ // state of the file locks, and set FileSize from the Fcb.
+ //
+
+ if (!PagingIo &&
+ (Scb->ScbType.Data.FileLock != NULL) &&
+ !FsRtlCheckLockForWriteAccess( Scb->ScbType.Data.FileLock, Irp )) {
+
+ try_return( Status = STATUS_FILE_LOCK_CONFLICT );
+ }
+ }
+
+ // ASSERT( Header->ValidDataLength.QuadPart <= Header->FileSize.QuadPart);
+
+ //
+ // If the ByteRange now exceeds our maximum value, then
+ // return an error.
+ //
+
+ if (ByteRange < StartingVbo) {
+
+ try_return( Status = STATUS_INVALID_PARAMETER );
+ }
+
+ //
+ // If we are extending a file size, we may have to extend the allocation.
+ // For a non-resident attribute, this is a call to the add allocation
+ // routine. For a resident attribute it depends on whether we
+ // can use the change attribute routine to automatically extend
+ // the attribute.
+ //
+
+ if (DoingIoAtEof) {
+
+ //
+ // EXTENDING THE FILE
+ //
+
+ //
+ // If the write goes beyond the allocation size, add some
+ // file allocation.
+ //
+
+ if (ByteRange > Header->AllocationSize.QuadPart) {
+
+ BOOLEAN NonResidentPath;
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ //
+ // We have to deal with both the resident and non-resident
+ // case. For the resident case we do the work here
+ // only if the new size is too large for the change attribute
+ // value routine.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ PFILE_RECORD_SEGMENT_HEADER FileRecord;
+
+ NonResidentPath = FALSE;
+
+ //
+ // Now call the attribute routine to change the value, remembering
+ // the values up to the current valid data length.
+ //
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttributeContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ FileRecord = NtfsContainingFileRecord( &AttrContext );
+ Attribute = NtfsFoundAttribute( &AttrContext );
+ LlTemp1 = (LONGLONG) (Vcb->BytesPerFileRecordSegment
+ - FileRecord->FirstFreeByte
+ + QuadAlign( Attribute->Form.Resident.ValueLength ));
+
+ //
+ // If the new attribute size will not fit then we have to be
+ // prepared to go non-resident. If the byte range takes more
+ // more than 32 bits or this attribute is big enough to move
+ // then it will go non-resident. Otherwise we simply may
+ // end up moving another attribute or splitting the file
+ // record.
+ //
+
+ //
+ // Note, there is an infinitesimal chance that before the Lazy Writer
+ // writes the data for an attribute which is extending, but fits
+ // when we check it here, that some other attribute will grow,
+ // and this attribute no longer fits. If in addition, the disk
+ // is full, then the Lazy Writer will fail to allocate space
+ // for the data when it gets around to writing. This is
+ // incredibly unlikely, and not fatal; the Lazy Writer gets an
+ // error rather than the user. What we are trying to avoid is
+ // having to update the attribute every time on small writes
+ // (also see comments below in NONCACHED RESIDENT ATTRIBUTE case).
+ //
+
+ if (ByteRange > LlTemp1) {
+
+ //
+ // Go ahead and convert this attribute to non-resident.
+ // Then take the non-resident path below. There is a chance
+ // that there was a more suitable candidate to move non-resident
+ // but we don't want to change the file size until we copy
+ // the user's data into the cache in case the buffer is
+ // corrupt.
+ //
+
+ NtfsConvertToNonresident( IrpContext,
+ Fcb,
+ Attribute,
+ NonCachedIo,
+ &AttrContext );
+
+ NonResidentPath = TRUE;
+
+ //
+ // If there is room for the data, we will write a zero
+ // to the last byte to reserve the space since the
+ // Lazy Writer cannot grow the attribute with shared
+ // access.
+ //
+
+ } else {
+
+ //
+ // The attribute will stay resident because we
+ // have already checked that it will fit. It will
+ // not update the file size and valid data size in
+ // the Scb.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ (ULONG) ByteRange,
+ NULL,
+ 0,
+ TRUE,
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+
+ Header->AllocationSize.LowPart = QuadAlign( (ULONG)ByteRange );
+ Scb->TotalAllocated = Header->AllocationSize.QuadPart;
+ }
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ CleanupAttributeContext = FALSE;
+
+ } else {
+
+ NonResidentPath = TRUE;
+ }
+
+ //
+ // Note that we may have gotten all the space we need when
+ // we converted to nonresident above, so we have to check
+ // again if we are extending.
+ //
+
+ if (NonResidentPath &&
+ ByteRange > Header->AllocationSize.QuadPart) {
+
+ //
+ // Assume we are allocating contiguously from AllocationSize.
+ //
+
+ LlTemp1 = Header->AllocationSize.QuadPart;
+
+ //
+ // If the file is compressed, we want to limit how far we are
+ // willing to go beyond ValidDataLength, because we would just
+ // have to throw that space away anyway in NtfsZeroData. If
+ // we would have to zero more than two compression units (same
+ // limit as NtfsZeroData), then just allocate space where we
+ // need it.
+ //
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_COMPRESSED) &&
+ ((StartingVbo - Header->ValidDataLength.QuadPart)
+ > (LONGLONG) (Scb->CompressionUnit * 2))) {
+
+ LlTemp1 = StartingVbo;
+ (ULONG)LlTemp1 &= ~(Scb->CompressionUnit - 1);
+ }
+
+ LlTemp2 = ByteRange - LlTemp1;
+
+ //
+ // This will add the allocation and modify the allocation
+ // size in the Scb.
+ //
+
+ NtfsAddAllocation( IrpContext,
+ FileObject,
+ Scb,
+ LlClustersFromBytes( Vcb, LlTemp1 ),
+ LlClustersFromBytes( Vcb, LlTemp2 ),
+ TRUE );
+
+ //
+ // Assert that the allocation worked
+ //
+
+ ASSERT( Header->AllocationSize.QuadPart >= ByteRange ||
+ (Scb->CompressionUnit != 0));
+
+ SetFlag(Scb->ScbState, SCB_STATE_TRUNCATE_ON_CLOSE);
+
+ }
+
+ //
+ // Now that we have grown the attribute, it is important to
+ // checkpoint the current transaction and free all main resources
+ // to avoid the tc type deadlocks. Note that the extend is ok
+ // to stand in its own right, and the stream will be truncated
+ // on close anyway.
+ //
+
+ NtfsCheckpointCurrentTransaction( IrpContext );
+
+ //
+ // Growing allocation can change file size (in ChangeAttributeValue).
+ // Make sure we know the correct value for file size to restore.
+ //
+
+ OldFileSize = Header->FileSize.QuadPart;
+ while (!IsListEmpty(&IrpContext->ExclusiveFcbList)) {
+
+ NtfsReleaseFcb( IrpContext,
+ (PFCB)CONTAINING_RECORD(IrpContext->ExclusiveFcbList.Flink,
+ FCB,
+ ExclusiveFcbLinks ));
+ }
+
+#ifdef _CAIRO_
+ //
+ // Go through and free any Scb's in the queue of shared
+ // Scb's for transactions.
+ //
+
+ if (IrpContext->SharedScb != NULL) {
+
+ NtfsReleaseSharedResources( IrpContext );
+ }
+
+#endif // _CAIRO_
+
+ ScbAcquired = FALSE;
+ }
+
+ //
+ // Now synchronize with the FsRtl Header and set FileSize
+ // now so that our reads will not get truncated.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+ if (ByteRange > Header->FileSize.QuadPart) {
+ ASSERT( ByteRange <= Header->AllocationSize.QuadPart );
+ Header->FileSize.QuadPart = ByteRange;
+ SetFlag( UserFileObject->Flags, FO_FILE_SIZE_CHANGED );
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+
+ //
+ // Extend the cache map, letting mm knows the new file size.
+ //
+
+ if (CcIsFileCached(FileObject) && !PagingIo) {
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Header->AllocationSize );
+ } else {
+ CcFileSizeChangeDue = TRUE;
+ }
+ }
+
+
+ //
+ // HANDLE THE NONCACHED RESIDENT ATTRIBUTE CASE
+ //
+ // We let the cached case take the normal path for the following
+ // reasons:
+ //
+ // o To insure data coherency if a user maps the file
+ // o To get a page in the cache to keep the Fcb around
+ // o So the data can be accessed via the Fast I/O path
+ // o To reduce the number of calls to NtfsChangeAttributeValue,
+ // to infrequent calls from the Lazy Writer. Calls to CcCopyWrite
+ // are much cheaper. With any luck, if the attribute actually stays
+ // resident, we will only have to update it (and log it) once
+ // when the Lazy Writer gets around to the data.
+ //
+ // The disadvantage is the overhead to fault the data in the
+ // first time, but we may be able to do this with asynchronous
+ // read ahead.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT | SCB_STATE_CONVERT_UNDERWAY )
+ && NonCachedIo) {
+
+ //
+ // The attribute is already resident and we have already tested
+ // if we are going past the end of the file.
+ //
+
+ DebugTrace( 0, Dbg, ("Resident attribute write\n") );
+
+ //
+ // If this buffer is not in system space then we can't
+ // trust it. In that case we will allocate a temporary buffer
+ // and copy the user's data to it.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ if (!PagingIo && (Irp->RequestorMode != KernelMode)) {
+
+ SafeBuffer = NtfsAllocatePool( NonPagedPool,
+ (ULONG) ByteCount );
+
+ try {
+
+ RtlCopyMemory( SafeBuffer, SystemBuffer, (ULONG)ByteCount );
+
+ } except( EXCEPTION_EXECUTE_HANDLER ) {
+
+ try_return( Status = STATUS_INVALID_USER_BUFFER );
+ }
+
+ SystemBuffer = SafeBuffer;
+ }
+
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+
+ //
+ // Now see if the file is still resident, and if not
+ // fall through below.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ //
+ // If this Scb is for an $EA attribute which is now resident then
+ // we don't want to write the data into the attribute. All resident
+ // EA's are modified directly.
+ //
+
+ if (Scb->AttributeTypeCode != $EA) {
+
+ NtfsInitializeAttributeContext( &AttrContext );
+ CleanupAttributeContext = TRUE;
+
+ NtfsLookupAttributeForScb( IrpContext,
+ Scb,
+ NULL,
+ &AttrContext );
+
+ Attribute = NtfsFoundAttribute( &AttrContext );
+
+ //
+ // The attribute should already be optionally extended,
+ // just write the data to it now.
+ //
+
+ NtfsChangeAttributeValue( IrpContext,
+ Fcb,
+ ((ULONG)StartingVbo),
+ SystemBuffer,
+ (ULONG)ByteCount,
+ (BOOLEAN)((((ULONG)StartingVbo) + (ULONG)ByteCount) >
+ Attribute->Form.Resident.ValueLength),
+ FALSE,
+ FALSE,
+ FALSE,
+ &AttrContext );
+ }
+
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+
+ try_return( Status = STATUS_SUCCESS );
+
+ //
+ // Gee, someone else made the file nonresident, so we can just
+ // free the resource and get on with life.
+ //
+
+ } else {
+ NtfsReleaseScb( IrpContext, Scb );
+ ScbAcquired = FALSE;
+ }
+ }
+
+ //
+ // HANDLE THE NON-CACHED CASE
+ //
+
+ if (NonCachedIo) {
+
+ ULONG SectorSize;
+ ULONG BytesToWrite;
+
+ //
+ // Get the sector size
+ //
+
+ SectorSize = Vcb->BytesPerSector;
+
+ //
+ // Round up to a sector boundry
+ //
+
+ BytesToWrite = ((ULONG)ByteCount + (SectorSize - 1))
+ & ~(SectorSize - 1);
+
+ //
+ // All requests should be well formed and
+ // make sure we don't wipe out any data
+ //
+
+ if ((((ULONG)StartingVbo) & (SectorSize - 1))
+
+ || ((BytesToWrite != (ULONG)ByteCount)
+ && ByteRange < Header->ValidDataLength.QuadPart )) {
+
+ //**** we only reach this path via fast I/O and by returning not implemented we
+ //**** force it to return to use via slow I/O
+
+ DebugTrace( 0, Dbg, ("NtfsCommonWrite -> STATUS_NOT_IMPLEMENTED\n") );
+
+ try_return( Status = STATUS_NOT_IMPLEMENTED );
+ }
+
+ //
+ // If this noncached transfer is at least one sector beyond
+ // the current ValidDataLength in the Scb, then we have to
+ // zero the sectors in between. This can happen if the user
+ // has opened the file noncached, or if the user has mapped
+ // the file and modified a page beyond ValidDataLength. It
+ // *cannot* happen if the user opened the file cached, because
+ // ValidDataLength in the Fcb is updated when he does the cached
+ // write (we also zero data in the cache at that time), and
+ // therefore, we will bypass this action when the data
+ // is ultimately written through (by the Lazy Writer).
+ //
+ // For the paging file we don't care about security (ie.
+ // stale data), do don't bother zeroing.
+ //
+ // We can actually get writes wholly beyond valid data length
+ // from the LazyWriter because of paging Io decoupling.
+ //
+ // We drop this zeroing on the floor in any case where this
+ // request is a recursive write caused by a flush from a higher level write.
+ //
+
+ if (!CalledByLazyWriter &&
+ !RecursiveWriteThrough &&
+ (StartingVbo > Header->ValidDataLength.QuadPart)) {
+
+ if (!NtfsZeroData( IrpContext,
+ Scb,
+ FileObject,
+ Header->ValidDataLength.QuadPart,
+ StartingVbo - Header->ValidDataLength.QuadPart )) {
+
+ //
+ // The zeroing didn't complete but we might have moved
+ // valid data length up and committed. We don't want
+ // to set the file size below this value.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+ if (OldFileSize < Header->ValidDataLength.QuadPart) {
+
+ OldFileSize = Header->ValidDataLength.QuadPart;
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Data was zeroed up to the StartingVbo. Update our old file
+ // size to that point.
+ //
+
+ OldFileSize = StartingVbo;
+ }
+
+ //
+ // If this Scb uses update sequence protection, we need to transform
+ // the blocks to a protected version. We first allocate an auxilary
+ // buffer and Mdl. Then we copy the data to this buffer and
+ // transform it. Finally we attach this Mdl to the Irp and use
+ // it to perform the Io.
+ //
+
+ if (FlagOn( Scb->ScbState, SCB_STATE_USA_PRESENT )) {
+
+ TempLength = BytesToWrite;
+
+ //
+ // Find the system buffer for this request and initialize the
+ // local state.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ OriginalMdl = Irp->MdlAddress;
+ OriginalBuffer = Irp->UserBuffer;
+ NewBuffer = NULL;
+
+ //
+ // Protect this operation with a try-finally.
+ //
+
+ try {
+
+ //
+ // If this is the Mft Scb and the range of bytes falls into
+ // the range for the Mirror Mft, we generate a write to
+ // the mirror as well.
+ //
+
+ if ((Scb == Vcb->MftScb)
+ && StartingVbo < Vcb->Mft2Scb->Header.FileSize.QuadPart) {
+
+ LlTemp1 = Vcb->Mft2Scb->Header.FileSize.QuadPart - StartingVbo;
+
+ if ((ULONG)LlTemp1 > BytesToWrite) {
+
+ (ULONG)LlTemp1 = BytesToWrite;
+ }
+
+ CcCopyWrite( Vcb->Mft2Scb->FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)LlTemp1,
+ TRUE,
+ SystemBuffer );
+
+ //
+ // Now flush this to disk.
+ //
+
+ CcFlushCache( &Vcb->Mft2Scb->NonpagedScb->SegmentObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)LlTemp1,
+ &Irp->IoStatus );
+
+ NtfsCleanupTransaction( IrpContext, Irp->IoStatus.Status, TRUE );
+ }
+
+ //
+ // Start by allocating buffer and Mdl.
+ //
+
+ NtfsCreateMdlAndBuffer( IrpContext,
+ Scb,
+ 0,
+ &TempLength,
+ &NewMdl,
+ &NewBuffer );
+
+ //
+ // Now transform and write out the original stream.
+ //
+
+ RtlCopyMemory( NewBuffer, SystemBuffer, BytesToWrite );
+
+ //
+ // Now increment the sequence number in both the original
+ // and copied buffer, and transform the copied buffer.
+ //
+
+ NtfsTransformUsaBlock( Scb,
+ SystemBuffer,
+ NewBuffer,
+ BytesToWrite );
+
+ //
+ // We copy our Mdl into the Irp and then perform the Io.
+ //
+
+ Irp->MdlAddress = NewMdl;
+ Irp->UserBuffer = NewBuffer;
+
+ ASSERT( Wait );
+ NtfsNonCachedIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ BytesToWrite,
+ FALSE );
+
+ } finally {
+
+ //
+ // In all cases we restore the user's Mdl and cleanup
+ // our Mdl and buffer.
+ //
+
+ if (NewBuffer != NULL) {
+
+ Irp->MdlAddress = OriginalMdl;
+ Irp->UserBuffer = OriginalBuffer;
+
+ NtfsDeleteMdlAndBuffer( NewMdl, NewBuffer );
+ }
+ }
+
+ //
+ // Otherwise we simply perform the Io.
+ //
+
+ } else {
+
+ Status = NtfsNonCachedIo( IrpContext,
+ Irp,
+ Scb,
+ StartingVbo,
+ BytesToWrite,
+ (FileObject->SectionObjectPointer != &Scb->NonpagedScb->SegmentObject) );
+
+#ifdef SYSCACHE
+ if ((NodeType(Scb) == NTFS_NTC_SCB_DATA) &&
+ FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE)) {
+
+ PULONG WriteMask;
+ ULONG Len;
+ ULONG Off = (ULONG)StartingVbo;
+
+ if (FlagOn(Scb->ScbState, SCB_STATE_SYSCACHE_FILE)) {
+
+ FsRtlVerifySyscacheData( FileObject,
+ MmGetSystemAddressForMdl(Irp->MdlAddress),
+ BytesToWrite,
+ (ULONG)StartingVbo );
+ }
+
+ WriteMask = Scb->ScbType.Data.WriteMask;
+ if (WriteMask == NULL) {
+ WriteMask = NtfsAllocatePool( NonPagedPool, (((0x2000000) / PAGE_SIZE) / 8) );
+ Scb->ScbType.Data.WriteMask = WriteMask;
+ RtlZeroMemory(WriteMask, (((0x2000000) / PAGE_SIZE) / 8));
+ }
+
+ if (Off < 0x2000000) {
+ Len = BytesToWrite;
+ if ((Off + Len) > 0x2000000) {
+ Len = 0x2000000 - Off;
+ }
+ while (Len != 0) {
+ WriteMask[(Off / PAGE_SIZE)/32] |= (1 << ((Off / PAGE_SIZE) % 32));
+
+ Off += PAGE_SIZE;
+ if (Len <= PAGE_SIZE) {
+ break;
+ }
+ Len -= PAGE_SIZE;
+ }
+ }
+ }
+#endif
+
+ if (Status == STATUS_PENDING) {
+
+ IrpContext->Union.NtfsIoContext = NULL;
+ PagingIoResourceAcquired = FALSE;
+ Irp = NULL;
+
+ try_return( Status );
+ }
+
+ //
+ // On successful uncompressed writes, take this opportunity to
+ // update ValidDataToDisk. Unfortunately this field is
+ // synchronized by the main resource, but this resource should
+ // be fairly available for uncompressed streams anyway.
+ //
+
+ if ((Scb->CompressionUnit == 0) &&
+ !FlagOn(Scb->ScbState, SCB_STATE_MODIFIED_NO_WRITE) &&
+ NT_SUCCESS(Status)) {
+ LlTemp1 = StartingVbo + BytesToWrite;
+ ExAcquireResourceExclusive( Header->Resource, TRUE );
+ if (Scb->ValidDataToDisk < LlTemp1) {
+ Scb->ValidDataToDisk = LlTemp1;
+ }
+ ExReleaseResource( Header->Resource );
+ }
+ }
+
+ //
+ // Show that we want to immediately update the Mft.
+ //
+
+ UpdateMft = TRUE;
+
+ //
+ // If the call didn't succeed, raise the error status
+ //
+
+ if (!NT_SUCCESS( Status = Irp->IoStatus.Status )) {
+
+ NtfsNormalizeAndRaiseStatus( IrpContext, Status, STATUS_UNEXPECTED_IO_ERROR );
+
+ } else {
+
+ //
+ // Else set the context block to reflect the entire write
+ // Also assert we got how many bytes we asked for.
+ //
+
+ ASSERT( Irp->IoStatus.Information == BytesToWrite );
+
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+ }
+
+ //
+ // The transfer is either complete, or the Iosb contains the
+ // appropriate status.
+ //
+
+ try_return( Status );
+
+ } // if No Intermediate Buffering
+
+
+ //
+ // HANDLE THE CACHED CASE
+ //
+
+ ASSERT( !PagingIo );
+
+ //
+ // We delay setting up the file cache until now, in case the
+ // caller never does any I/O to the file, and thus
+ // FileObject->PrivateCacheMap == NULL.
+ //
+
+ if (FileObject->PrivateCacheMap == NULL) {
+
+ DebugTrace( 0, Dbg, ("Initialize cache mapping.\n") );
+
+ //
+ // Get the file allocation size, and if it is less than
+ // the file size, raise file corrupt error.
+ //
+
+ if (Header->FileSize.QuadPart > Header->AllocationSize.QuadPart) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_FILE_CORRUPT_ERROR, NULL, Fcb );
+ }
+
+ //
+ // Now initialize the cache map. Notice that we may extending
+ // the ValidDataLength with this write call. At this point
+ // we haven't updated the ValidDataLength in the Scb header.
+ // This way we will get a call from the cache manager
+ // when the lazy writer writes out the data.
+ //
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ CcInitializeCacheMap( FileObject,
+ (PCC_FILE_SIZES)&Header->AllocationSize,
+ FALSE,
+ &NtfsData.CacheManagerCallbacks,
+ Scb );
+
+ if (CcFileSizeChangeDue) {
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Header->AllocationSize );
+ }
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+
+ CcSetReadAheadGranularity( FileObject, READ_AHEAD_GRANULARITY );
+ }
+
+ //
+ // Remember if we need to update the Mft.
+ //
+
+ if (!FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT )) {
+
+ UpdateMft = BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_THROUGH);
+ }
+
+ //
+ // If this write is beyond valid data length, then we
+ // must zero the data in between.
+ //
+
+ LlTemp1 = StartingVbo - Header->ValidDataLength.QuadPart;
+
+ if (LlTemp1 > 0) {
+
+ //
+ // If the caller is writing zeros way beyond ValidDataLength,
+ // then noop it.
+ //
+
+ if (LlTemp1 > PAGE_SIZE &&
+ ByteCount <= sizeof(LARGE_INTEGER) &&
+ (RtlEqualMemory( NtfsMapUserBuffer( Irp ),
+ &Li0,
+ (ULONG)ByteCount ) )) {
+
+ ByteRange = Header->ValidDataLength.QuadPart;
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+ try_return( Status = STATUS_SUCCESS );
+ }
+
+ //
+ // Call the Cache Manager to zero the data.
+ //
+
+ if (!NtfsZeroData( IrpContext,
+ Scb,
+ FileObject,
+ Header->ValidDataLength.QuadPart,
+ LlTemp1 )) {
+
+ //
+ // The zeroing didn't complete but we might have moved
+ // valid data length up and committed. We don't want
+ // to set the file size below this value.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+ if (OldFileSize < Scb->Header.ValidDataLength.QuadPart) {
+
+ OldFileSize = Scb->Header.ValidDataLength.QuadPart;
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+ }
+
+ //
+ // Data was zeroed up to the StartingVbo. Update our old file
+ // size to that point.
+ //
+
+ OldFileSize = StartingVbo;
+ }
+
+
+ //
+ // For a compressed stream, we must first reserve the space.
+ //
+
+ if (FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK) &&
+ !FlagOn(Scb->ScbState, SCB_STATE_REALLOCATE_ON_WRITE) &&
+ !NtfsReserveClusters(IrpContext, Scb, StartingVbo, (ULONG)ByteCount)) {
+
+ NtfsRaiseStatus( IrpContext, STATUS_DISK_FULL, NULL, NULL );
+ }
+
+ //
+ // We need to go through the cache for this
+ // file object. First handle the noncompressed calls.
+ //
+
+
+#ifdef _CAIRO_
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_COMPRESSED)) {
+#endif _CAIRO_
+
+ //
+ // DO A NORMAL CACHED WRITE, if the MDL bit is not set,
+ //
+
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) {
+
+ DebugTrace( 0, Dbg, ("Cached write.\n") );
+
+ //
+ // Get hold of the user's buffer.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+
+ //
+ // Do the write, possibly writing through
+ //
+
+ if (!CcCopyWrite( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ BooleanFlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT),
+ SystemBuffer )) {
+
+ DebugTrace( 0, Dbg, ("Cached Write could not wait\n") );
+
+ NtfsRaiseStatus( IrpContext, STATUS_CANT_WAIT, NULL, NULL );
+
+ } else if (!NT_SUCCESS( IrpContext->ExceptionStatus )) {
+
+ NtfsRaiseStatus( IrpContext, IrpContext->ExceptionStatus, NULL, NULL );
+ }
+
+ Irp->IoStatus.Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = (ULONG)ByteCount;
+
+ try_return( Status = STATUS_SUCCESS );
+
+ } else {
+
+ //
+ // DO AN MDL WRITE
+ //
+
+ DebugTrace( 0, Dbg, ("MDL write.\n") );
+
+ ASSERT( FlagOn(IrpContext->Flags, IRP_CONTEXT_FLAG_WAIT) );
+
+ //
+ // If we got this far and then hit a log file full the Mdl will
+ // already be present.
+ //
+
+ ASSERT(Irp->MdlAddress == NULL);
+
+ CcPrepareMdlWrite( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ &Irp->MdlAddress,
+ &Irp->IoStatus );
+
+ Status = Irp->IoStatus.Status;
+
+ ASSERT( NT_SUCCESS( Status ));
+
+ try_return( Status );
+ }
+
+ //
+ // Handle the compressed calls.
+ //
+
+#ifdef _CAIRO_
+ } else {
+
+ ASSERT((StartingVbo & (NTFS_CHUNK_SIZE - 1)) == 0);
+
+ if ((Header->FileObjectC == NULL) ||
+ (Header->FileObjectC->PrivateCacheMap == NULL)) {
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ NtfsCreateInternalCompressedStream( IrpContext, Scb, FALSE );
+
+ if (CcFileSizeChangeDue) {
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Header->AllocationSize );
+ }
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+
+ //
+ // Assume success.
+ //
+
+ Irp->IoStatus.Status = Status = STATUS_SUCCESS;
+ Irp->IoStatus.Information = (ULONG)(ByteRange - StartingVbo);
+
+ //
+ // Based on the Mdl minor function, set up the appropriate
+ // parameters for the call below. (NewMdl is not exactly the
+ // right type, so it is cast...)
+ //
+
+ if (!FlagOn(IrpContext->MinorFunction, IRP_MN_MDL)) {
+
+ //
+ // Get hold of the user's buffer.
+ //
+
+ SystemBuffer = NtfsMapUserBuffer( Irp );
+ NewMdl = NULL;
+
+ } else {
+
+ //
+ // We will deliver the Mdl directly to the Irp.
+ //
+
+ SystemBuffer = NULL;
+ NewMdl = (PMDL)&Irp->MdlAddress;
+ }
+
+ CompressedDataInfo = (PCOMPRESSED_DATA_INFO)IrpContext->Union.AuxiliaryBuffer->Buffer;
+
+ //
+ // Calculate the compression unit and chunk sizes.
+ //
+
+ CompressionUnitSize = 1 << CompressedDataInfo->CompressionUnitShift;
+ ChunkSize = 1 << CompressedDataInfo->ChunkShift;
+
+ //
+ // See if the engine matches, so we can pass that on to the
+ // compressed write routine.
+ //
+
+ EngineMatches =
+ ((CompressedDataInfo->CompressionFormatAndEngine == ((Scb->AttributeFlags & ATTRIBUTE_FLAG_COMPRESSION_MASK) + 1)) &&
+ (CompressedDataInfo->CompressionUnitShift == (Scb->CompressionUnitShift + Vcb->ClusterShift)) &&
+ (CompressedDataInfo->ChunkShift == NTFS_CHUNK_SHIFT));
+
+ //
+ // Do the compressed write in common code with the Fast Io path.
+ // We do it from a loop because we may need to create the other
+ // data stream.
+ //
+
+ while (TRUE) {
+
+ Status = NtfsCompressedCopyWrite( FileObject,
+ (PLARGE_INTEGER)&StartingVbo,
+ (ULONG)ByteCount,
+ SystemBuffer,
+ (PMDL *)NewMdl,
+ CompressedDataInfo,
+ IoGetRelatedDeviceObject(FileObject),
+ Header,
+ CompressionUnitSize,
+ ChunkSize,
+ EngineMatches );
+
+ //
+ // On successful Mdl requests we hang on to the PagingIo resource.
+ //
+
+ if ((NewMdl != NULL) && NT_SUCCESS(Status)) {
+ PagingIoResourceAcquired = FALSE;
+ }
+
+ //
+ // Check for the status that says we need to create the normal
+ // data stream, else we are done.
+ //
+
+ if (Status != STATUS_NOT_MAPPED_DATA) {
+ break;
+ }
+
+ //
+ // Create the normal data stream and loop back to try again.
+ //
+
+ ASSERT(Scb->FileObject == NULL);
+
+ //
+ // Make sure we are serialized with the FileSizes, and
+ // will remove this condition if we abort.
+ //
+
+ if (!DoingIoAtEof) {
+ FsRtlLockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = (PFCB)Scb;
+ }
+
+ NtfsCreateInternalAttributeStream( IrpContext, Scb, FALSE );
+
+ if (CcFileSizeChangeDue) {
+ CcSetFileSizes( FileObject, (PCC_FILE_SIZES)&Header->AllocationSize );
+ }
+
+ if (!DoingIoAtEof) {
+ FsRtlUnlockFsRtlHeader( Header );
+ IrpContext->FcbWithPagingExclusive = NULL;
+ }
+ }
+ }
+#endif _CAIRO_
+
+
+ try_exit: NOTHING;
+
+ if (Irp) {
+
+ if (PostIrp) {
+
+ //
+ // If we acquired this Scb exclusive, we won't need to release
+ // the Scb. That is done in the oplock post request.
+ //
+
+ if (OplockPostIrp) {
+
+ ScbAcquired = FALSE;
+ }
+
+ //
+ // If we didn't post the Irp, we may have written some bytes to the
+ // file. We report the number of bytes written and update the
+ // file object for synchronous writes.
+ //
+
+ } else {
+
+ DebugTrace( 0, Dbg, ("Completing request with status = %08lx\n", Status) );
+
+ DebugTrace( 0, Dbg, (" Information = %08lx\n",
+ Irp->IoStatus.Information));
+
+ //
+ // Record the total number of bytes actually written
+ //
+
+ LlTemp1 = Irp->IoStatus.Information;
+
+ //
+ // If the file was opened for Synchronous IO, update the current
+ // file position.
+ //
+
+ if (SynchronousIo && !PagingIo) {
+
+ UserFileObject->CurrentByteOffset.QuadPart = StartingVbo + LlTemp1;
+ }
+
+ //
+ // The following are things we only do if we were successful
+ //
+
+ if (NT_SUCCESS( Status )) {
+
+ //
+ // Mark that the modify time needs to be updated on close.
+ // Note that only the top level User requests will generate
+ // correct
+
+ if (!PagingIo) {
+
+ //
+ // Set the flag in the file object to know we modified this file.
+ //
+
+ SetFlag( UserFileObject->Flags, FO_FILE_MODIFIED );
+
+ //
+ // On successful paging I/O to a compressed data stream which is
+ // not mapped, we free any reserved space for the stream.
+ //
+
+ } else {
+
+ if (FlagOn(Scb->AttributeFlags, ATTRIBUTE_FLAG_COMPRESSION_MASK)) {
+
+ if (!FlagOn(Header->Flags, FSRTL_FLAG_USER_MAPPED_FILE) &&
+ (Header->NodeTypeCode == NTFS_NTC_SCB_DATA)) {
+
+ NtfsFreeReservedClusters( Scb,
+ StartingVbo,
+ Irp->IoStatus.Information );
+ }
+ }
+ }
+
+ //
+ // If we extended the file size and we are meant to
+ // immediately update the dirent, do so. (This flag is
+ // set for either WriteThrough or noncached, because
+ // in either case the data and any necessary zeros are
+ // actually written to the file.) Note that a flush of
+ // a user-mapped file could cause VDL to get updated the
+ // first time because we never had a cached write, so we
+ // have to be sure to update VDL here in that case as well.
+ //
+
+ if (DoingIoAtEof) {
+
+ //
+ // If we know this has gone to disk we update the Mft.
+ // This variable should never be set for a resident
+ // attribute.
+ //
+
+ if (UpdateMft && !FlagOn( Scb->ScbState, SCB_STATE_RESTORE_UNDERWAY )) {
+
+ ASSERTMSG( "Scb should be non-resident\n", !FlagOn( Scb->ScbState, SCB_STATE_ATTRIBUTE_RESIDENT ));
+
+ //
+ // We may not have the Scb.
+ //
+
+ if (!ScbAcquired) {
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+ }
+
+ //
+ // Start by capturing any file size changes.
+ //
+
+ NtfsUpdateScbFromFileObject( IrpContext, UserFileObject, Scb, FALSE );
+
+ //
+ // Write a log entry to update these sizes.
+ //
+
+ NtfsWriteFileSizes( IrpContext,
+ Scb,
+ &ByteRange,
+ TRUE,
+ TRUE );
+
+ //
+ // Clear the check attribute size flag.
+ //
+
+ ExAcquireFastMutex( Header->FastMutex );
+ ClearFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+
+ //
+ // Otherwise we set the flag indicating that we need to
+ // update the attribute size.
+ //
+
+ } else {
+
+ ExAcquireFastMutex( Header->FastMutex );
+ SetFlag( Scb->ScbState, SCB_STATE_CHECK_ATTRIBUTE_SIZE );
+ }
+
+ //
+ // Now is the time to update valid data length.
+ // The Eof condition will be freed when we commit.
+ //
+
+ if (ByteRange > Header->ValidDataLength.QuadPart) {
+ Header->ValidDataLength.QuadPart = ByteRange;
+ }
+ DoingIoAtEof = FALSE;
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+ }
+
+ //
+ // Abort transaction on error by raising.
+ //
+
+ NtfsCleanupTransaction( IrpContext, Status, FALSE );
+ }
+ }
+
+ } finally {
+
+ DebugUnwind( NtfsCommonWrite );
+
+ if (CleanupAttributeContext) {
+
+ NtfsCleanupAttributeContext( &AttrContext );
+ }
+
+ if (SafeBuffer) {
+
+ NtfsFreePool( SafeBuffer );
+ }
+
+ //
+ // Now is the time to restore FileSize on errors.
+ // The Eof condition will be freed when we commit.
+ //
+
+ if (DoingIoAtEof) {
+
+ //
+ // Acquire the main resource to knock valid data to disk back.
+ //
+
+ if (!ScbAcquired) {
+ NtfsAcquireExclusiveScb( IrpContext, Scb );
+ ScbAcquired = TRUE;
+ }
+
+ if (Scb->ValidDataToDisk > OldFileSize) {
+
+ Scb->ValidDataToDisk = OldFileSize;
+ }
+
+ ExAcquireFastMutex( Header->FastMutex );
+ Header->FileSize.QuadPart = OldFileSize;
+
+ if (FileObject->SectionObjectPointer->SharedCacheMap != NULL) {
+
+ CcGetFileSizePointer(FileObject)->QuadPart = OldFileSize;
+ }
+ ExReleaseFastMutex( Header->FastMutex );
+ }
+
+ //
+ // If the Scb or PagingIo resource has been acquired, release it.
+ //
+
+ if (PagingIoResourceAcquired) {
+ ExReleaseResource( Header->PagingIoResource );
+ }
+
+ if (Irp) {
+
+ if (ScbAcquired) {
+ NtfsReleaseScb( IrpContext, Scb );
+ }
+
+ //
+ // Now remember to clear the WriteSeen flag if we set it. We only
+ // do this if there is still an Irp. It is possible for the current
+ // Irp to be posted or asynchronous. In that case this is a top
+ // level request and the cleanup happens elsewhere. For synchronous
+ // recursive cases the Irp will still be here.
+ //
+
+ if (SetWriteSeen) {
+ ClearFlag(IrpContext->TopLevelIrpContext->Flags, IRP_CONTEXT_FLAG_WRITE_SEEN);
+ }
+ }
+
+ //
+ // Complete the request if we didn't post it and no exception
+ //
+ // Note that FatCompleteRequest does the right thing if either
+ // IrpContext or Irp are NULL
+ //
+
+ if (!AbnormalTermination()) {
+
+ if (!PostIrp) {
+
+ NtfsCompleteRequest( &IrpContext,
+ Irp ? &Irp : NULL,
+ Status );
+
+ } else if (!OplockPostIrp) {
+
+ Status = NtfsPostRequest( IrpContext, Irp );
+ }
+ }
+
+ DebugTrace( -1, Dbg, ("NtfsCommonWrite -> %08lx\n", Status) );
+ }
+
+ return Status;
+}