3 * Copyright (C) 2002, 2014 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
30 /* INCLUDES *****************************************************************/
37 /* FUNCTIONS ****************************************************************/
40 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
42 PNTFS_ATTR_CONTEXT Context
;
44 Context
= ExAllocatePoolWithTag(NonPagedPool
,
45 sizeof(NTFS_ATTR_CONTEXT
),
49 DPRINT1("Error: Unable to allocate memory for context!\n");
53 // Allocate memory for a copy of the attribute
54 Context
->pRecord
= ExAllocatePoolWithTag(NonPagedPool
, AttrRecord
->Length
, TAG_NTFS
);
57 DPRINT1("Error: Unable to allocate memory for attribute record!\n");
58 ExFreePoolWithTag(Context
, TAG_NTFS
);
63 RtlCopyMemory(Context
->pRecord
, AttrRecord
, AttrRecord
->Length
);
65 if (AttrRecord
->IsNonResident
)
67 LONGLONG DataRunOffset
;
68 ULONGLONG DataRunLength
;
69 ULONGLONG NextVBN
= 0;
70 PUCHAR DataRun
= (PUCHAR
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->NonResident
.MappingPairsOffset
);
72 Context
->CacheRun
= DataRun
;
73 Context
->CacheRunOffset
= 0;
74 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
75 Context
->CacheRunLength
= DataRunLength
;
76 if (DataRunOffset
!= -1)
79 Context
->CacheRunStartLCN
=
80 Context
->CacheRunLastLCN
= DataRunOffset
;
85 Context
->CacheRunStartLCN
= -1;
86 Context
->CacheRunLastLCN
= 0;
88 Context
->CacheRunCurrentOffset
= 0;
90 // Convert the data runs to a map control block
91 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun
, &Context
->DataRunsMCB
, &NextVBN
)))
93 DPRINT1("Unable to convert data runs to MCB!\n");
94 ExFreePoolWithTag(Context
->pRecord
, TAG_NTFS
);
95 ExFreePoolWithTag(Context
, TAG_NTFS
);
105 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
107 if (Context
->pRecord
->IsNonResident
)
109 FsRtlUninitializeLargeMcb(&Context
->DataRunsMCB
);
113 ExFreePoolWithTag(Context
->pRecord
, TAG_NTFS
);
115 ExFreePoolWithTag(Context
, TAG_NTFS
);
120 * @name FindAttribute
123 * Searches a file record for an attribute matching the given type and name.
126 * Optional pointer to a ULONG that will receive the offset of the found attribute
127 * from the beginning of the record. Can be set to NULL.
130 FindAttribute(PDEVICE_EXTENSION Vcb
,
131 PFILE_RECORD_HEADER MftRecord
,
135 PNTFS_ATTR_CONTEXT
* AttrCtx
,
140 FIND_ATTR_CONTXT Context
;
141 PNTFS_ATTR_RECORD Attribute
;
143 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
, Offset
);
146 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
147 while (NT_SUCCESS(Status
))
149 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
155 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
156 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
157 if (RtlCompareMemory(AttrName
, Name
, NameLength
* sizeof(WCHAR
)) == (NameLength
* sizeof(WCHAR
)))
169 /* Found it, fill up the context and return. */
170 DPRINT("Found context\n");
171 *AttrCtx
= PrepareAttributeContext(Attribute
);
173 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
176 *Offset
= Context
.Offset
;
178 FindCloseAttribute(&Context
);
179 return STATUS_SUCCESS
;
183 Status
= FindNextAttribute(&Context
, &Attribute
);
186 FindCloseAttribute(&Context
);
187 return STATUS_OBJECT_NAME_NOT_FOUND
;
192 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
194 if (AttrRecord
->IsNonResident
)
195 return AttrRecord
->NonResident
.AllocatedSize
;
197 return ALIGN_UP_BY(AttrRecord
->Resident
.ValueLength
, ATTR_RECORD_ALIGNMENT
);
202 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
204 if (AttrRecord
->IsNonResident
)
205 return AttrRecord
->NonResident
.DataSize
;
207 return AttrRecord
->Resident
.ValueLength
;
211 * @name IncreaseMftSize
214 * Increases the size of the master file table on a volume, increasing the space available for file records.
217 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
221 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
222 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
225 * STATUS_SUCCESS on success.
226 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
227 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
228 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
231 * Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
232 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
233 * This function will wait for exlusive access to the volume fcb.
236 IncreaseMftSize(PDEVICE_EXTENSION Vcb
, BOOLEAN CanWait
)
238 PNTFS_ATTR_CONTEXT BitmapContext
;
239 LARGE_INTEGER BitmapSize
;
240 LARGE_INTEGER DataSize
;
241 LONGLONG BitmapSizeDifference
;
242 ULONG DataSizeDifference
= Vcb
->NtfsInfo
.BytesPerFileRecord
* 8;
245 ULONGLONG BitmapBytes
;
246 ULONGLONG NewBitmapSize
;
251 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb
, CanWait
? "TRUE" : "FALSE");
253 // We need exclusive access to the mft while we change its size
254 if (!ExAcquireResourceExclusiveLite(&(Vcb
->DirResource
), CanWait
))
256 return STATUS_CANT_WAIT
;
259 // Find the bitmap attribute of master file table
260 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, &BitmapOffset
);
261 if (!NT_SUCCESS(Status
))
263 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
264 ExReleaseResourceLite(&(Vcb
->DirResource
));
268 // Get size of Bitmap Attribute
269 BitmapSize
.QuadPart
= AttributeDataLength(BitmapContext
->pRecord
);
271 // Calculate the new mft size
272 DataSize
.QuadPart
= AttributeDataLength(Vcb
->MFTContext
->pRecord
) + DataSizeDifference
;
274 // Determine how many bytes will make up the bitmap
275 BitmapBytes
= DataSize
.QuadPart
/ Vcb
->NtfsInfo
.BytesPerFileRecord
/ 8;
277 // Determine how much we need to adjust the bitmap size (it's possible we don't)
278 BitmapSizeDifference
= BitmapBytes
- BitmapSize
.QuadPart
;
279 NewBitmapSize
= max(BitmapSize
.QuadPart
+ BitmapSizeDifference
, BitmapSize
.QuadPart
);
281 // Allocate memory for the bitmap
282 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, NewBitmapSize
, TAG_NTFS
);
285 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
286 ExReleaseResourceLite(&(Vcb
->DirResource
));
287 ReleaseAttributeContext(BitmapContext
);
288 return STATUS_INSUFFICIENT_RESOURCES
;
291 // Zero the bytes we'll be adding
292 RtlZeroMemory(BitmapBuffer
, NewBitmapSize
);
294 // Read the bitmap attribute
295 BytesRead
= ReadAttribute(Vcb
,
300 if (BytesRead
!= BitmapSize
.LowPart
)
302 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
303 ExReleaseResourceLite(&(Vcb
->DirResource
));
304 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
305 ReleaseAttributeContext(BitmapContext
);
306 return STATUS_INVALID_PARAMETER
;
309 // Increase the mft size
310 Status
= SetNonResidentAttributeDataLength(Vcb
, Vcb
->MFTContext
, Vcb
->MftDataOffset
, Vcb
->MasterFileTable
, &DataSize
);
311 if (!NT_SUCCESS(Status
))
313 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
314 ExReleaseResourceLite(&(Vcb
->DirResource
));
315 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
316 ReleaseAttributeContext(BitmapContext
);
320 // If the bitmap grew
321 if (BitmapSizeDifference
> 0)
323 // Set the new bitmap size
324 BitmapSize
.QuadPart
+= BitmapSizeDifference
;
325 if (BitmapContext
->pRecord
->IsNonResident
)
326 Status
= SetNonResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
328 Status
= SetResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
330 if (!NT_SUCCESS(Status
))
332 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
333 ExReleaseResourceLite(&(Vcb
->DirResource
));
334 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
335 ReleaseAttributeContext(BitmapContext
);
340 //NtfsDumpFileAttributes(Vcb, FileRecord);
342 // Update the file record with the new attribute sizes
343 Status
= UpdateFileRecord(Vcb
, Vcb
->VolumeFcb
->MFTIndex
, Vcb
->MasterFileTable
);
344 if (!NT_SUCCESS(Status
))
346 DPRINT1("ERROR: Failed to update $MFT file record!\n");
347 ExReleaseResourceLite(&(Vcb
->DirResource
));
348 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
349 ReleaseAttributeContext(BitmapContext
);
353 // Write out the new bitmap
354 Status
= WriteAttribute(Vcb
, BitmapContext
, BitmapOffset
, BitmapBuffer
, BitmapSize
.LowPart
, &LengthWritten
, Vcb
->MasterFileTable
);
355 if (!NT_SUCCESS(Status
))
357 ExReleaseResourceLite(&(Vcb
->DirResource
));
358 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
359 ReleaseAttributeContext(BitmapContext
);
360 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
365 ExReleaseResourceLite(&(Vcb
->DirResource
));
366 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
367 ReleaseAttributeContext(BitmapContext
);
369 return STATUS_SUCCESS
;
373 * @name MoveAttributes
376 * Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
377 * and every attribute after that will be moved to MoveTo.
380 * Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
382 * @param FirstAttributeToMove
383 * Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
385 * @param FirstAttributeOffset
386 * Offset of FirstAttributeToMove relative to the beginning of the file record.
389 * ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
392 * The new location of the final attribute (i.e. AttributeEnd marker).
395 MoveAttributes(PDEVICE_EXTENSION DeviceExt
,
396 PNTFS_ATTR_RECORD FirstAttributeToMove
,
397 ULONG FirstAttributeOffset
,
400 // Get the size of all attributes after this one
401 ULONG MemBlockSize
= 0;
402 PNTFS_ATTR_RECORD CurrentAttribute
= FirstAttributeToMove
;
403 ULONG CurrentOffset
= FirstAttributeOffset
;
404 PNTFS_ATTR_RECORD FinalAttribute
;
406 while (CurrentAttribute
->Type
!= AttributeEnd
&& CurrentOffset
< DeviceExt
->NtfsInfo
.BytesPerFileRecord
)
408 CurrentOffset
+= CurrentAttribute
->Length
;
409 MemBlockSize
+= CurrentAttribute
->Length
;
410 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
413 FinalAttribute
= (PNTFS_ATTR_RECORD
)(MoveTo
+ MemBlockSize
);
414 MemBlockSize
+= sizeof(ULONG
) * 2; // Add the AttributeEnd and file record end
416 ASSERT(MemBlockSize
% ATTR_RECORD_ALIGNMENT
== 0);
418 // Move the attributes after this one
419 RtlMoveMemory((PCHAR
)MoveTo
, FirstAttributeToMove
, MemBlockSize
);
421 return FinalAttribute
;
425 InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt
,
426 PNTFS_ATTR_CONTEXT AttrContext
,
427 PFILE_RECORD_HEADER FileRecord
,
431 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
432 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
433 PNTFS_ATTR_RECORD FinalAttribute
;
434 ULONG OldAttributeLength
= Destination
->Length
;
435 ULONG NextAttributeOffset
;
437 DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt
, AttrContext
, FileRecord
, AttrOffset
, DataSize
);
439 ASSERT(!AttrContext
->pRecord
->IsNonResident
);
441 // Update ValueLength Field
442 Destination
->Resident
.ValueLength
= DataSize
;
444 // Calculate the record length and end marker offset
445 Destination
->Length
= ALIGN_UP_BY(DataSize
+ AttrContext
->pRecord
->Resident
.ValueOffset
, ATTR_RECORD_ALIGNMENT
);
446 NextAttributeOffset
= AttrOffset
+ Destination
->Length
;
448 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
449 ASSERT(NextAttributeOffset
% ATTR_RECORD_ALIGNMENT
== 0);
451 // Will the new attribute be larger than the old one?
452 if (Destination
->Length
> OldAttributeLength
)
454 // Free the old copy of the attribute in the context, as it will be the wrong length
455 ExFreePoolWithTag(AttrContext
->pRecord
, TAG_NTFS
);
457 // Create a new copy of the attribute record for the context
458 AttrContext
->pRecord
= ExAllocatePoolWithTag(NonPagedPool
, Destination
->Length
, TAG_NTFS
);
459 if (!AttrContext
->pRecord
)
461 DPRINT1("Unable to allocate memory for attribute!\n");
462 return STATUS_INSUFFICIENT_RESOURCES
;
464 RtlZeroMemory((PVOID
)((ULONG_PTR
)AttrContext
->pRecord
+ OldAttributeLength
), Destination
->Length
- OldAttributeLength
);
465 RtlCopyMemory(AttrContext
->pRecord
, Destination
, OldAttributeLength
);
468 // Are there attributes after this one that need to be moved?
469 if (NextAttribute
->Type
!= AttributeEnd
)
471 // Move the attributes after this one
472 FinalAttribute
= MoveAttributes(DeviceExt
, NextAttribute
, NextAttributeOffset
, (ULONG_PTR
)Destination
+ Destination
->Length
);
476 // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
477 FinalAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)NextAttribute
- OldAttributeLength
+ Destination
->Length
);
480 // Update pRecord's length
481 AttrContext
->pRecord
->Length
= Destination
->Length
;
482 AttrContext
->pRecord
->Resident
.ValueLength
= DataSize
;
484 // set the file record end
485 SetFileRecordEnd(FileRecord
, FinalAttribute
, FILE_RECORD_END
);
487 //NtfsDumpFileRecord(DeviceExt, FileRecord);
489 return STATUS_SUCCESS
;
493 * @parameter FileRecord
494 * Pointer to a file record. Must be a full record at least
495 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
498 SetAttributeDataLength(PFILE_OBJECT FileObject
,
500 PNTFS_ATTR_CONTEXT AttrContext
,
502 PFILE_RECORD_HEADER FileRecord
,
503 PLARGE_INTEGER DataSize
)
505 NTSTATUS Status
= STATUS_SUCCESS
;
507 DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n",
515 // are we truncating the file?
516 if (DataSize
->QuadPart
< AttributeDataLength(AttrContext
->pRecord
))
518 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
520 DPRINT1("Can't truncate a memory-mapped file!\n");
521 return STATUS_USER_MAPPED_FILE
;
525 if (AttrContext
->pRecord
->IsNonResident
)
527 Status
= SetNonResidentAttributeDataLength(Fcb
->Vcb
,
535 // resident attribute
536 Status
= SetResidentAttributeDataLength(Fcb
->Vcb
,
543 if (!NT_SUCCESS(Status
))
545 DPRINT1("ERROR: Failed to set size of attribute!\n");
549 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
551 // write the updated file record back to disk
552 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
554 if (NT_SUCCESS(Status
))
556 if (AttrContext
->pRecord
->IsNonResident
)
557 Fcb
->RFCB
.AllocationSize
.QuadPart
= AttrContext
->pRecord
->NonResident
.AllocatedSize
;
559 Fcb
->RFCB
.AllocationSize
= *DataSize
;
560 Fcb
->RFCB
.FileSize
= *DataSize
;
561 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
562 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
565 return STATUS_SUCCESS
;
569 * @name SetFileRecordEnd
572 * This small function sets a new endpoint for the file record. It set's the final
573 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
576 * Pointer to the file record whose endpoint (length) will be set.
579 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
580 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
583 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
584 * a file record, it preserves the final ULONG that previously ended the record, even though this
585 * value is (to my knowledge) never used. We emulate this behavior.
589 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
590 PNTFS_ATTR_RECORD AttrEnd
,
593 // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
594 ASSERT(((ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
) % ATTR_RECORD_ALIGNMENT
== 0);
596 // mark the end of attributes
597 AttrEnd
->Type
= AttributeEnd
;
599 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
600 AttrEnd
->Length
= EndMarker
;
602 // recalculate bytes in use
603 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
607 * @name SetNonResidentAttributeDataLength
610 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
613 * Pointer to a DEVICE_EXTENSION describing the target disk.
616 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
619 * Offset, from the beginning of the record, of the attribute being sized.
622 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
623 * not just the header.
626 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
629 * STATUS_SUCCESS on success;
630 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
631 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
634 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
635 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
636 * any associated files. Synchronization is the callers responsibility.
639 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
640 PNTFS_ATTR_CONTEXT AttrContext
,
642 PFILE_RECORD_HEADER FileRecord
,
643 PLARGE_INTEGER DataSize
)
645 NTSTATUS Status
= STATUS_SUCCESS
;
646 ULONG BytesPerCluster
= Vcb
->NtfsInfo
.BytesPerCluster
;
647 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
648 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
649 ULONG ExistingClusters
= AttrContext
->pRecord
->NonResident
.AllocatedSize
/ BytesPerCluster
;
651 ASSERT(AttrContext
->pRecord
->IsNonResident
);
653 // do we need to increase the allocation size?
654 if (AttrContext
->pRecord
->NonResident
.AllocatedSize
< AllocationSize
)
656 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
657 LARGE_INTEGER LastClusterInDataRun
;
658 ULONG NextAssignedCluster
;
659 ULONG AssignedClusters
;
661 if (ExistingClusters
== 0)
663 LastClusterInDataRun
.QuadPart
= 0;
667 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
668 (LONGLONG
)AttrContext
->pRecord
->NonResident
.HighestVCN
,
669 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
675 DPRINT1("Error looking up final large MCB entry!\n");
677 // Most likely, HighestVCN went above the largest mapping
678 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->pRecord
->NonResident
.HighestVCN
);
679 return STATUS_INVALID_PARAMETER
;
683 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
684 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->pRecord
->NonResident
.HighestVCN
);
686 while (ClustersNeeded
> 0)
688 Status
= NtfsAllocateClusters(Vcb
,
689 LastClusterInDataRun
.LowPart
+ 1,
691 &NextAssignedCluster
,
694 if (!NT_SUCCESS(Status
))
696 DPRINT1("Error: Unable to allocate requested clusters!\n");
700 // now we need to add the clusters we allocated to the data run
701 Status
= AddRun(Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
702 if (!NT_SUCCESS(Status
))
704 DPRINT1("Error: Unable to add data run!\n");
708 ClustersNeeded
-= AssignedClusters
;
709 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
712 else if (AttrContext
->pRecord
->NonResident
.AllocatedSize
> AllocationSize
)
714 // shrink allocation size
715 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
716 Status
= FreeClusters(Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
719 // TODO: is the file compressed, encrypted, or sparse?
721 AttrContext
->pRecord
->NonResident
.AllocatedSize
= AllocationSize
;
722 AttrContext
->pRecord
->NonResident
.DataSize
= DataSize
->QuadPart
;
723 AttrContext
->pRecord
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
725 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
726 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
727 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
729 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
735 * @name SetResidentAttributeDataLength
738 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
741 * Pointer to a DEVICE_EXTENSION describing the target disk.
744 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
747 * Offset, from the beginning of the record, of the attribute being sized.
750 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
751 * not just the header.
754 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
757 * STATUS_SUCCESS on success;
758 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
759 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
760 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
761 * last attribute listed in the file record.
764 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
765 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
766 * any associated files. Synchronization is the callers responsibility.
769 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
770 PNTFS_ATTR_CONTEXT AttrContext
,
772 PFILE_RECORD_HEADER FileRecord
,
773 PLARGE_INTEGER DataSize
)
777 // find the next attribute
778 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->pRecord
->Length
;
779 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
781 ASSERT(!AttrContext
->pRecord
->IsNonResident
);
783 //NtfsDumpFileAttributes(Vcb, FileRecord);
785 // Do we need to increase the data length?
786 if (DataSize
->QuadPart
> AttrContext
->pRecord
->Resident
.ValueLength
)
788 // There's usually padding at the end of a record. Do we need to extend past it?
789 ULONG MaxValueLength
= AttrContext
->pRecord
->Length
- AttrContext
->pRecord
->Resident
.ValueOffset
;
790 if (MaxValueLength
< DataSize
->LowPart
)
792 // If this is the last attribute, we could move the end marker to the very end of the file record
793 MaxValueLength
+= Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
795 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
797 // convert attribute to non-resident
798 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
799 PNTFS_ATTR_RECORD NewRecord
;
800 LARGE_INTEGER AttribDataSize
;
802 ULONG NewRecordLength
;
803 ULONG EndAttributeOffset
;
806 DPRINT1("Converting attribute to non-resident.\n");
808 AttribDataSize
.QuadPart
= AttrContext
->pRecord
->Resident
.ValueLength
;
810 // Is there existing data we need to back-up?
811 if (AttribDataSize
.QuadPart
> 0)
813 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
814 if (AttribData
== NULL
)
816 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
817 return STATUS_INSUFFICIENT_RESOURCES
;
820 // read data to temp buffer
821 Status
= ReadAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
822 if (!NT_SUCCESS(Status
))
824 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
825 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
830 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
832 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
833 NewRecordLength
= ALIGN_UP_BY(0x41 + (AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
)), ATTR_RECORD_ALIGNMENT
);
835 // Create a new attribute record that will store the 0-length, non-resident attribute
836 NewRecord
= ExAllocatePoolWithTag(NonPagedPool
, NewRecordLength
, TAG_NTFS
);
838 // Zero out the NonResident structure
839 RtlZeroMemory(NewRecord
, NewRecordLength
);
841 // Copy the data that's common to both non-resident and resident attributes
842 RtlCopyMemory(NewRecord
, AttrContext
->pRecord
, FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.ValueLength
));
845 if (AttrContext
->pRecord
->NameLength
!= 0)
848 // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
849 RtlCopyMemory((PCHAR
)((ULONG_PTR
)NewRecord
+ 0x40),
850 (PCHAR
)((ULONG_PTR
)AttrContext
->pRecord
+ 0x18),
851 AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
));
854 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
855 NewRecord
->NonResident
.MappingPairsOffset
= 0x40 + (AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
));
857 // update the end of the file record
858 // calculate position of end markers (1 byte for empty data run)
859 EndAttributeOffset
= AttrOffset
+ NewRecord
->NonResident
.MappingPairsOffset
+ 1;
860 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, ATTR_RECORD_ALIGNMENT
);
863 NewRecord
->Length
= EndAttributeOffset
- AttrOffset
;
865 ASSERT(NewRecord
->Length
== NewRecordLength
);
867 // Copy the new attribute record into the file record
868 RtlCopyMemory(Destination
, NewRecord
, NewRecord
->Length
);
870 // Update the file record end
871 SetFileRecordEnd(FileRecord
,
872 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
875 // Initialize the MCB, potentially catch an exception
878 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
880 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
882 DPRINT1("Unable to create LargeMcb!\n");
883 if (AttribDataSize
.QuadPart
> 0)
884 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
885 _SEH2_YIELD(return _SEH2_GetExceptionCode());
888 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
889 NewRecord
->IsNonResident
= Destination
->IsNonResident
= 1;
891 // Update file record on disk
892 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
893 if (!NT_SUCCESS(Status
))
895 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
896 if (AttribDataSize
.QuadPart
> 0)
897 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
901 // Now we need to free the old copy of the attribute record in the context and replace it with the new one
902 ExFreePoolWithTag(AttrContext
->pRecord
, TAG_NTFS
);
903 AttrContext
->pRecord
= NewRecord
;
905 // Now we can treat the attribute as non-resident and enlarge it normally
906 Status
= SetNonResidentAttributeDataLength(Vcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
907 if (!NT_SUCCESS(Status
))
909 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
910 if (AttribDataSize
.QuadPart
> 0)
911 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
915 // restore the back-up attribute, if we made one
916 if (AttribDataSize
.QuadPart
> 0)
918 Status
= WriteAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
, FileRecord
);
919 if (!NT_SUCCESS(Status
))
921 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
922 // TODO: Reverse migration so no data is lost
923 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
927 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
933 // set the new length of the resident attribute (if we didn't migrate it)
934 if (!AttrContext
->pRecord
->IsNonResident
)
935 return InternalSetResidentAttributeLength(Vcb
, AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
937 return STATUS_SUCCESS
;
941 ReadAttribute(PDEVICE_EXTENSION Vcb
,
942 PNTFS_ATTR_CONTEXT Context
,
949 LONGLONG DataRunOffset
;
950 ULONGLONG DataRunLength
;
951 LONGLONG DataRunStartLCN
;
952 ULONGLONG CurrentOffset
;
960 if (!Context
->pRecord
->IsNonResident
)
962 // We need to truncate Offset to a ULONG for pointer arithmetic
963 // The check below should ensure that Offset is well within the range of 32 bits
964 ULONG LittleOffset
= (ULONG
)Offset
;
966 // Ensure that offset isn't beyond the end of the attribute
967 if (Offset
> Context
->pRecord
->Resident
.ValueLength
)
969 if (Offset
+ Length
> Context
->pRecord
->Resident
.ValueLength
)
970 Length
= (ULONG
)(Context
->pRecord
->Resident
.ValueLength
- Offset
);
972 RtlCopyMemory(Buffer
, (PVOID
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->Resident
.ValueOffset
+ LittleOffset
), Length
);
977 * Non-resident attribute
981 * I. Find the corresponding start data run.
986 // FIXME: Cache seems to be non-working. Disable it for now
987 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
990 DataRun
= Context
->CacheRun
;
991 LastLCN
= Context
->CacheRunLastLCN
;
992 DataRunStartLCN
= Context
->CacheRunStartLCN
;
993 DataRunLength
= Context
->CacheRunLength
;
994 CurrentOffset
= Context
->CacheRunCurrentOffset
;
999 ULONG UsedBufferSize
;
1000 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1005 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1006 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1008 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1011 DataRun
= TempBuffer
;
1015 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1016 if (DataRunOffset
!= -1)
1018 /* Normal data run. */
1019 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1020 LastLCN
= DataRunStartLCN
;
1024 /* Sparse data run. */
1025 DataRunStartLCN
= -1;
1028 if (Offset
>= CurrentOffset
&&
1029 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1039 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1044 * II. Go through the run list and read the data
1047 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1048 if (DataRunStartLCN
== -1)
1050 RtlZeroMemory(Buffer
, ReadLength
);
1051 Status
= STATUS_SUCCESS
;
1055 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
1056 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
1058 Vcb
->NtfsInfo
.BytesPerSector
,
1062 if (NT_SUCCESS(Status
))
1064 Length
-= ReadLength
;
1065 Buffer
+= ReadLength
;
1066 AlreadyRead
+= ReadLength
;
1068 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1070 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1071 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1072 if (DataRunOffset
!= (ULONGLONG
)-1)
1074 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1075 LastLCN
= DataRunStartLCN
;
1078 DataRunStartLCN
= -1;
1083 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1084 if (DataRunStartLCN
== -1)
1085 RtlZeroMemory(Buffer
, ReadLength
);
1088 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
1089 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1091 Vcb
->NtfsInfo
.BytesPerSector
,
1094 if (!NT_SUCCESS(Status
))
1098 Length
-= ReadLength
;
1099 Buffer
+= ReadLength
;
1100 AlreadyRead
+= ReadLength
;
1102 /* We finished this request, but there still data in this data run. */
1103 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1107 * Go to next run in the list.
1112 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1113 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1114 if (DataRunOffset
!= -1)
1116 /* Normal data run. */
1117 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1118 LastLCN
= DataRunStartLCN
;
1122 /* Sparse data run. */
1123 DataRunStartLCN
= -1;
1130 if (Context
->pRecord
->IsNonResident
)
1131 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1133 Context
->CacheRun
= DataRun
;
1134 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
1135 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1136 Context
->CacheRunLength
= DataRunLength
;
1137 Context
->CacheRunLastLCN
= LastLCN
;
1138 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1145 * @name WriteAttribute
1148 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1149 * and it still needs more documentation / cleaning up.
1152 * Volume Control Block indicating which volume to write the attribute to
1155 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1158 * Offset, in bytes, from the beginning of the attribute indicating where to start
1162 * The data that's being written to the device
1165 * How much data will be written, in bytes
1167 * @param RealLengthWritten
1168 * Pointer to a ULONG which will receive how much data was written, in bytes
1171 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
1172 * being written to. Can be NULL, in which case the file record will be read from disk.
1173 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord
1174 * will be updated with the newly-written attribute before the function returns.
1177 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1178 * writing to a sparse file.
1180 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1181 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1186 WriteAttribute(PDEVICE_EXTENSION Vcb
,
1187 PNTFS_ATTR_CONTEXT Context
,
1189 const PUCHAR Buffer
,
1191 PULONG RealLengthWritten
,
1192 PFILE_RECORD_HEADER FileRecord
)
1196 LONGLONG DataRunOffset
;
1197 ULONGLONG DataRunLength
;
1198 LONGLONG DataRunStartLCN
;
1199 ULONGLONG CurrentOffset
;
1202 PUCHAR SourceBuffer
= Buffer
;
1203 LONGLONG StartingOffset
;
1204 BOOLEAN FileRecordAllocated
= FALSE
;
1210 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
, FileRecord
);
1212 *RealLengthWritten
= 0;
1214 // is this a resident attribute?
1215 if (!Context
->pRecord
->IsNonResident
)
1217 ULONG AttributeOffset
;
1218 PNTFS_ATTR_CONTEXT FoundContext
;
1219 PNTFS_ATTR_RECORD Destination
;
1221 // Ensure requested data is within the bounds of the attribute
1222 ASSERT(Offset
+ Length
<= Context
->pRecord
->Resident
.ValueLength
);
1224 if (Offset
+ Length
> Context
->pRecord
->Resident
.ValueLength
)
1226 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1227 return STATUS_INVALID_PARAMETER
;
1230 // Do we need to read the file record?
1231 if (FileRecord
== NULL
)
1233 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1236 DPRINT1("Error: Couldn't allocate file record!\n");
1237 return STATUS_NO_MEMORY
;
1240 FileRecordAllocated
= TRUE
;
1242 // read the file record
1243 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1246 // find where to write the attribute data to
1247 Status
= FindAttribute(Vcb
, FileRecord
,
1248 Context
->pRecord
->Type
,
1249 (PCWSTR
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->NameOffset
),
1250 Context
->pRecord
->NameLength
,
1254 if (!NT_SUCCESS(Status
))
1256 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1257 if(FileRecordAllocated
)
1258 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1262 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttributeOffset
);
1264 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->pRecord
->Resident
.ValueLength
);
1266 // Will we be writing past the end of the allocated file record?
1267 if (Offset
+ Length
+ AttributeOffset
+ Context
->pRecord
->Resident
.ValueOffset
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
1269 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1270 ReleaseAttributeContext(FoundContext
);
1271 if (FileRecordAllocated
)
1272 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1273 return STATUS_INVALID_PARAMETER
;
1276 // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
1277 RtlCopyMemory((PCHAR
)((ULONG_PTR
)Destination
+ Context
->pRecord
->Resident
.ValueOffset
+ (ULONG
)Offset
), Buffer
, Length
);
1279 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1281 // Update the context's copy of the resident attribute
1282 ASSERT(Context
->pRecord
->Length
== Destination
->Length
);
1283 RtlCopyMemory((PVOID
)Context
->pRecord
, Destination
, Context
->pRecord
->Length
);
1285 ReleaseAttributeContext(FoundContext
);
1286 if (FileRecordAllocated
)
1287 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1289 if (NT_SUCCESS(Status
))
1290 *RealLengthWritten
= Length
;
1295 // This is a non-resident attribute.
1297 // I. Find the corresponding start data run.
1299 // FIXME: Cache seems to be non-working. Disable it for now
1300 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1303 DataRun = Context->CacheRun;
1304 LastLCN = Context->CacheRunLastLCN;
1305 DataRunStartLCN = Context->CacheRunStartLCN;
1306 DataRunLength = Context->CacheRunLength;
1307 CurrentOffset = Context->CacheRunCurrentOffset;
1311 ULONG UsedBufferSize
;
1315 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1316 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1318 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1320 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1323 DataRun
= TempBuffer
;
1327 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1328 if (DataRunOffset
!= -1)
1331 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1332 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1333 LastLCN
= DataRunStartLCN
;
1337 // Sparse data run. We can't support writing to sparse files yet
1338 // (it may require increasing the allocation size).
1339 DataRunStartLCN
= -1;
1340 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1341 return STATUS_NOT_IMPLEMENTED
;
1344 // Have we reached the data run we're trying to write to?
1345 if (Offset
>= CurrentOffset
&&
1346 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1353 // We reached the last assigned cluster
1354 // TODO: assign new clusters to the end of the file.
1355 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1356 // [We can reach here by creating a new file record when the MFT isn't large enough]
1357 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1358 return STATUS_END_OF_FILE
;
1361 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1365 // II. Go through the run list and write the data
1367 /* REVIEWME -- As adapted from NtfsReadAttribute():
1368 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1369 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1371 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1373 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
1375 // Write the data to the disk
1376 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1379 Vcb
->NtfsInfo
.BytesPerSector
,
1380 (PVOID
)SourceBuffer
);
1382 // Did the write fail?
1383 if (!NT_SUCCESS(Status
))
1385 Context
->CacheRun
= DataRun
;
1386 Context
->CacheRunOffset
= Offset
;
1387 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1388 Context
->CacheRunLength
= DataRunLength
;
1389 Context
->CacheRunLastLCN
= LastLCN
;
1390 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1395 Length
-= WriteLength
;
1396 SourceBuffer
+= WriteLength
;
1397 *RealLengthWritten
+= WriteLength
;
1399 // Did we write to the end of the data run?
1400 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1402 // Advance to the next data run
1403 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1404 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1406 if (DataRunOffset
!= (ULONGLONG
)-1)
1408 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1409 LastLCN
= DataRunStartLCN
;
1412 DataRunStartLCN
= -1;
1415 // Do we have more data to write?
1418 // Make sure we don't write past the end of the current data run
1419 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1421 // Are we dealing with a sparse data run?
1422 if (DataRunStartLCN
== -1)
1424 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1425 return STATUS_NOT_IMPLEMENTED
;
1429 // write the data to the disk
1430 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1431 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1433 Vcb
->NtfsInfo
.BytesPerSector
,
1434 (PVOID
)SourceBuffer
);
1435 if (!NT_SUCCESS(Status
))
1439 Length
-= WriteLength
;
1440 SourceBuffer
+= WriteLength
;
1441 *RealLengthWritten
+= WriteLength
;
1443 // We finished this request, but there's still data in this data run.
1444 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1447 // Go to next run in the list.
1451 // that was the last run
1454 // Failed sanity check.
1455 DPRINT1("Encountered EOF before expected!\n");
1456 return STATUS_END_OF_FILE
;
1462 // Advance to the next data run
1463 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1464 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1465 if (DataRunOffset
!= -1)
1468 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1469 LastLCN
= DataRunStartLCN
;
1474 DataRunStartLCN
= -1;
1476 } // end while (Length > 0) [more data to write]
1479 if (Context
->pRecord
->IsNonResident
)
1480 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1482 Context
->CacheRun
= DataRun
;
1483 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1484 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1485 Context
->CacheRunLength
= DataRunLength
;
1486 Context
->CacheRunLastLCN
= LastLCN
;
1487 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1493 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1495 PFILE_RECORD_HEADER file
)
1497 ULONGLONG BytesRead
;
1499 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1501 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1502 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1504 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1505 return STATUS_PARTIAL_COPY
;
1508 /* Apply update sequence array fixups. */
1509 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1510 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1515 * Searches a file's parent directory (given the parent's index in the mft)
1516 * for the given file. Upon finding an index entry for that file, updates
1517 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1519 * (Most of this code was copied from NtfsFindMftRecord)
1522 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1523 ULONGLONG ParentMFTIndex
,
1524 PUNICODE_STRING FileName
,
1526 ULONGLONG NewDataSize
,
1527 ULONGLONG NewAllocationSize
,
1528 BOOLEAN CaseSensitive
)
1530 PFILE_RECORD_HEADER MftRecord
;
1531 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1532 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1534 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1536 ULONG CurrentEntry
= 0;
1538 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1542 DirSearch
? "TRUE" : "FALSE",
1545 CaseSensitive
? "TRUE" : "FALSE");
1547 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1548 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1550 if (MftRecord
== NULL
)
1552 return STATUS_INSUFFICIENT_RESOURCES
;
1555 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1556 if (!NT_SUCCESS(Status
))
1558 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1562 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1563 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1564 if (!NT_SUCCESS(Status
))
1566 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1570 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1571 if (IndexRecord
== NULL
)
1573 ReleaseAttributeContext(IndexRootCtx
);
1574 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1575 return STATUS_INSUFFICIENT_RESOURCES
;
1578 Status
= ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, AttributeDataLength(IndexRootCtx
->pRecord
));
1579 if (!NT_SUCCESS(Status
))
1581 DPRINT1("ERROR: Failed to read Index Root!\n");
1582 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1583 ReleaseAttributeContext(IndexRootCtx
);
1584 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1587 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1588 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1589 // Index root is always resident.
1590 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1592 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1594 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1597 IndexRoot
->SizeOfEntry
,
1608 if (Status
== STATUS_PENDING
)
1610 // we need to write the index root attribute back to disk
1611 ULONG LengthWritten
;
1612 Status
= WriteAttribute(Vcb
, IndexRootCtx
, 0, (PUCHAR
)IndexRecord
, AttributeDataLength(IndexRootCtx
->pRecord
), &LengthWritten
, MftRecord
);
1613 if (!NT_SUCCESS(Status
))
1615 DPRINT1("ERROR: Couldn't update Index Root!\n");
1620 ReleaseAttributeContext(IndexRootCtx
);
1621 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1622 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1628 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1629 * proper index entry.
1630 * (Heavily based on BrowseIndexEntries)
1633 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1634 PFILE_RECORD_HEADER MftRecord
,
1636 ULONG IndexBlockSize
,
1637 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1638 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1639 PUNICODE_STRING FileName
,
1641 PULONG CurrentEntry
,
1643 ULONGLONG NewDataSize
,
1644 ULONGLONG NewAllocatedSize
,
1645 BOOLEAN CaseSensitive
)
1649 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1650 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1651 ULONGLONG IndexAllocationSize
;
1652 PINDEX_BUFFER IndexBuffer
;
1654 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1664 DirSearch
? "TRUE" : "FALSE",
1667 CaseSensitive
? "TRUE" : "FALSE");
1669 // find the index entry responsible for the file we're trying to update
1670 IndexEntry
= FirstEntry
;
1671 while (IndexEntry
< LastEntry
&&
1672 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1674 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > NTFS_FILE_FIRST_USER_FILE
&&
1675 *CurrentEntry
>= *StartEntry
&&
1676 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1677 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
1679 *StartEntry
= *CurrentEntry
;
1680 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1681 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1682 // indicate that the caller will still need to write the structure to the disk
1683 return STATUS_PENDING
;
1686 (*CurrentEntry
) += 1;
1687 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1688 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1691 /* If we're already browsing a subnode */
1692 if (IndexRecord
== NULL
)
1694 return STATUS_OBJECT_PATH_NOT_FOUND
;
1697 /* If there's no subnode */
1698 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1700 return STATUS_OBJECT_PATH_NOT_FOUND
;
1703 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1704 if (!NT_SUCCESS(Status
))
1706 DPRINT("Corrupted filesystem!\n");
1710 IndexAllocationSize
= AttributeDataLength(IndexAllocationCtx
->pRecord
);
1711 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1712 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1714 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1715 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1716 if (!NT_SUCCESS(Status
))
1721 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1722 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1723 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1724 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1725 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1726 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1728 Status
= UpdateIndexEntryFileNameSize(NULL
,
1741 if (Status
== STATUS_PENDING
)
1743 // write the index record back to disk
1746 // first we need to update the fixup values for the index block
1747 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1748 if (!NT_SUCCESS(Status
))
1750 DPRINT1("Error: Failed to update fixup sequence array!\n");
1754 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
, MftRecord
);
1755 if (!NT_SUCCESS(Status
))
1757 DPRINT1("ERROR Performing write!\n");
1761 Status
= STATUS_SUCCESS
;
1764 if (NT_SUCCESS(Status
))
1770 ReleaseAttributeContext(IndexAllocationCtx
);
1775 * @name UpdateFileRecord
1778 * Writes a file record to the master file table, at a given index.
1781 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1784 * Target index in the master file table to store the file record.
1787 * Pointer to the complete file record which will be written to the master file table.
1790 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1794 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1796 PFILE_RECORD_HEADER FileRecord
)
1799 NTSTATUS Status
= STATUS_SUCCESS
;
1801 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1803 // Add the fixup array to prepare the data for writing to disk
1804 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1806 // write the file record to the master file table
1807 Status
= WriteAttribute(Vcb
,
1809 MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
,
1810 (const PUCHAR
)FileRecord
,
1811 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1815 if (!NT_SUCCESS(Status
))
1817 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1820 // remove the fixup array (so the file record pointer can still be used)
1821 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1828 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1829 PNTFS_RECORD_HEADER Record
)
1836 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1837 USANumber
= *(USA
++);
1838 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1839 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1841 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1845 if (*Block
!= USANumber
)
1847 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1848 return STATUS_UNSUCCESSFUL
;
1851 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1855 return STATUS_SUCCESS
;
1859 * @name AddNewMftEntry
1862 * Adds a file record to the master file table of a given device.
1865 * Pointer to a complete file record which will be saved to disk.
1868 * Pointer to the DEVICE_EXTENSION of the target drive.
1870 * @param DestinationIndex
1871 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1874 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1875 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1878 * STATUS_SUCCESS on success.
1879 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1880 * to read the attribute.
1881 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1882 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1885 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1886 PDEVICE_EXTENSION DeviceExt
,
1887 PULONGLONG DestinationIndex
,
1890 NTSTATUS Status
= STATUS_SUCCESS
;
1893 ULONGLONG BitmapDataSize
;
1894 ULONGLONG AttrBytesRead
;
1896 PUCHAR BitmapBuffer
;
1897 ULONG LengthWritten
;
1898 PNTFS_ATTR_CONTEXT BitmapContext
;
1899 LARGE_INTEGER BitmapBits
;
1900 UCHAR SystemReservedBits
;
1902 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord
, DeviceExt
, DestinationIndex
, CanWait
? "TRUE" : "FALSE");
1904 // First, we have to read the mft's $Bitmap attribute
1906 // Find the attribute
1907 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1908 if (!NT_SUCCESS(Status
))
1910 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1914 // Get size of bitmap
1915 BitmapDataSize
= AttributeDataLength(BitmapContext
->pRecord
);
1917 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1918 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
1919 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
+ sizeof(ULONG
), TAG_NTFS
);
1922 ReleaseAttributeContext(BitmapContext
);
1923 return STATUS_INSUFFICIENT_RESOURCES
;
1926 // Get a ULONG-aligned pointer for the bitmap itself
1927 BitmapData
= (PUCHAR
)ALIGN_UP_BY((ULONG_PTR
)BitmapBuffer
, sizeof(ULONG
));
1929 // read $Bitmap attribute
1930 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, (PCHAR
)BitmapData
, BitmapDataSize
);
1932 if (AttrBytesRead
!= BitmapDataSize
)
1934 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1935 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
1936 ReleaseAttributeContext(BitmapContext
);
1937 return STATUS_OBJECT_NAME_NOT_FOUND
;
1940 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
1941 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
1942 SystemReservedBits
= BitmapData
[2];
1943 BitmapData
[2] = 0xff;
1945 // Calculate bit count
1946 BitmapBits
.QuadPart
= AttributeDataLength(DeviceExt
->MFTContext
->pRecord
) /
1947 DeviceExt
->NtfsInfo
.BytesPerFileRecord
;
1948 if (BitmapBits
.HighPart
!= 0)
1950 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
1951 NtfsGlobalData
->EnableWriteSupport
= FALSE
;
1952 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
1953 ReleaseAttributeContext(BitmapContext
);
1954 return STATUS_NOT_IMPLEMENTED
;
1957 // convert buffer into bitmap
1958 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapBits
.LowPart
);
1960 // set next available bit, preferrably after 23rd bit
1961 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
1962 if ((LONG
)MftIndex
== -1)
1964 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1966 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
1967 ReleaseAttributeContext(BitmapContext
);
1969 // Couldn't find a free record in the MFT, add some blank records and try again
1970 Status
= IncreaseMftSize(DeviceExt
, CanWait
);
1971 if (!NT_SUCCESS(Status
))
1973 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1977 return AddNewMftEntry(FileRecord
, DeviceExt
, DestinationIndex
, CanWait
);
1980 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
1982 // update file record with index
1983 FileRecord
->MFTRecordNumber
= MftIndex
;
1985 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1987 // Restore the system reserved bits
1988 BitmapData
[2] = SystemReservedBits
;
1990 // write the bitmap back to the MFT's $Bitmap attribute
1991 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
, FileRecord
);
1992 if (!NT_SUCCESS(Status
))
1994 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1995 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
1996 ReleaseAttributeContext(BitmapContext
);
2000 // update the file record (write it to disk)
2001 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
2003 if (!NT_SUCCESS(Status
))
2005 DPRINT1("ERROR: Unable to write file record!\n");
2006 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2007 ReleaseAttributeContext(BitmapContext
);
2011 *DestinationIndex
= MftIndex
;
2013 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2014 ReleaseAttributeContext(BitmapContext
);
2020 * @name NtfsAddFilenameToDirectory
2023 * Adds a $FILE_NAME attribute to a given directory index.
2026 * Points to the target disk's DEVICE_EXTENSION.
2028 * @param DirectoryMftIndex
2029 * Mft index of the parent directory which will receive the file.
2031 * @param FileReferenceNumber
2032 * File reference of the file to be added to the directory. This is a combination of the
2033 * Mft index and sequence number.
2035 * @param FilenameAttribute
2036 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2038 * @param CaseSensitive
2039 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2040 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2043 * STATUS_SUCCESS on success.
2044 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2045 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2048 * WIP - Can only support a few files in a directory.
2049 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2050 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2051 * get both attributes added to its parent directory.
2054 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt
,
2055 ULONGLONG DirectoryMftIndex
,
2056 ULONGLONG FileReferenceNumber
,
2057 PFILENAME_ATTRIBUTE FilenameAttribute
,
2058 BOOLEAN CaseSensitive
)
2060 NTSTATUS Status
= STATUS_SUCCESS
;
2061 PFILE_RECORD_HEADER ParentFileRecord
;
2062 PNTFS_ATTR_CONTEXT IndexRootContext
;
2063 PINDEX_ROOT_ATTRIBUTE I30IndexRoot
;
2064 ULONG IndexRootOffset
;
2065 ULONGLONG I30IndexRootLength
;
2066 ULONG LengthWritten
;
2067 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
;
2068 ULONG AttributeLength
;
2069 PNTFS_ATTR_RECORD NextAttribute
;
2071 ULONG BtreeIndexLength
;
2072 ULONG MaxIndexRootSize
;
2074 // Allocate memory for the parent directory
2075 ParentFileRecord
= ExAllocatePoolWithTag(NonPagedPool
,
2076 DeviceExt
->NtfsInfo
.BytesPerFileRecord
,
2078 if (!ParentFileRecord
)
2080 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2081 return STATUS_INSUFFICIENT_RESOURCES
;
2084 // Open the parent directory
2085 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2086 if (!NT_SUCCESS(Status
))
2088 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2089 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2094 DPRINT1("Dumping old parent file record:\n");
2095 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
2097 // Find the index root attribute for the directory
2098 Status
= FindAttribute(DeviceExt
,
2105 if (!NT_SUCCESS(Status
))
2107 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2109 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2113 // Find the maximum index size given what the file record can hold
2114 // First, find the max index size assuming index root is the last attribute
2115 MaxIndexRootSize
= DeviceExt
->NtfsInfo
.BytesPerFileRecord
// Start with the size of a file record
2116 - IndexRootOffset
// Subtract the length of everything that comes before index root
2117 - IndexRootContext
->pRecord
->Resident
.ValueOffset
// Subtract the length of the attribute header for index root
2118 - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
) // Subtract the length of the index header for index root
2119 - (sizeof(ULONG
) * 2); // Subtract the length of the file record end marker and padding
2121 // Are there attributes after this one?
2122 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)ParentFileRecord
+ IndexRootOffset
+ IndexRootContext
->pRecord
->Length
);
2123 if (NextAttribute
->Type
!= AttributeEnd
)
2125 // Find the length of all attributes after this one, not counting the end marker
2126 ULONG LengthOfAttributes
= 0;
2127 PNTFS_ATTR_RECORD CurrentAttribute
= NextAttribute
;
2128 while (CurrentAttribute
->Type
!= AttributeEnd
)
2130 LengthOfAttributes
+= CurrentAttribute
->Length
;
2131 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
2134 // Leave room for the existing attributes
2135 MaxIndexRootSize
-= LengthOfAttributes
;
2138 // Allocate memory for the index root data
2139 I30IndexRootLength
= AttributeDataLength(IndexRootContext
->pRecord
);
2140 I30IndexRoot
= ExAllocatePoolWithTag(NonPagedPool
, I30IndexRootLength
, TAG_NTFS
);
2143 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2144 ReleaseAttributeContext(IndexRootContext
);
2145 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2146 return STATUS_INSUFFICIENT_RESOURCES
;
2149 // Read the Index Root
2150 Status
= ReadAttribute(DeviceExt
, IndexRootContext
, 0, (PCHAR
)I30IndexRoot
, I30IndexRootLength
);
2151 if (!NT_SUCCESS(Status
))
2153 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex
);
2154 ReleaseAttributeContext(IndexRootContext
);
2155 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2156 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2160 // Convert the index to a B*Tree
2161 Status
= CreateBTreeFromIndex(DeviceExt
,
2166 if (!NT_SUCCESS(Status
))
2168 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2169 ReleaseAttributeContext(IndexRootContext
);
2170 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2171 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2177 // Insert the key for the file we're adding
2178 Status
= NtfsInsertKey(NewTree
, FileReferenceNumber
, FilenameAttribute
, NewTree
->RootNode
, CaseSensitive
, MaxIndexRootSize
);
2179 if (!NT_SUCCESS(Status
))
2181 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2182 DestroyBTree(NewTree
);
2183 ReleaseAttributeContext(IndexRootContext
);
2184 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2185 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2191 // Convert B*Tree back to Index, starting with the index allocation
2192 Status
= UpdateIndexAllocation(DeviceExt
, NewTree
, I30IndexRoot
->SizeOfEntry
, ParentFileRecord
);
2193 if (!NT_SUCCESS(Status
))
2195 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2196 DestroyBTree(NewTree
);
2197 ReleaseAttributeContext(IndexRootContext
);
2198 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2199 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2203 // Create the Index Root from the B*Tree
2204 Status
= CreateIndexRootFromBTree(DeviceExt
, NewTree
, MaxIndexRootSize
, &NewIndexRoot
, &BtreeIndexLength
);
2205 if (!NT_SUCCESS(Status
))
2207 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2208 DestroyBTree(NewTree
);
2209 ReleaseAttributeContext(IndexRootContext
);
2210 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2211 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2215 // We're done with the B-Tree now
2216 DestroyBTree(NewTree
);
2218 // Write back the new index root attribute to the parent directory file record
2220 // First, we need to resize the attribute.
2221 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2222 // We can't set the size as we normally would, because if we extend past the file record,
2223 // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
2224 // $ATTRIBUTE_LIST's.
2225 AttributeLength
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
2227 if (AttributeLength
!= IndexRootContext
->pRecord
->Resident
.ValueLength
)
2229 // Update the length of the attribute in the file record of the parent directory
2230 Status
= InternalSetResidentAttributeLength(DeviceExt
,
2235 if (!NT_SUCCESS(Status
))
2237 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2238 ReleaseAttributeContext(IndexRootContext
);
2239 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2240 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2241 DPRINT1("ERROR: Unable to set resident attribute length!\n");
2247 NT_ASSERT(ParentFileRecord
->BytesInUse
<= DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
2249 Status
= UpdateFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2250 if (!NT_SUCCESS(Status
))
2252 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex
);
2253 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2254 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2255 ReleaseAttributeContext(IndexRootContext
);
2256 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2260 // Write the new index root to disk
2261 Status
= WriteAttribute(DeviceExt
,
2264 (PUCHAR
)NewIndexRoot
,
2268 if (!NT_SUCCESS(Status
) || LengthWritten
!= AttributeLength
)
2270 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2271 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2272 ReleaseAttributeContext(IndexRootContext
);
2273 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2274 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2278 // re-read the parent file record, so we can dump it
2279 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2280 if (!NT_SUCCESS(Status
))
2282 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2286 DPRINT1("Dumping new parent file record:\n");
2287 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
2291 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2292 ReleaseAttributeContext(IndexRootContext
);
2293 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2294 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2300 AddFixupArray(PDEVICE_EXTENSION Vcb
,
2301 PNTFS_RECORD_HEADER Record
)
2303 USHORT
*pShortToFixUp
;
2304 ULONG ArrayEntryCount
= Record
->UsaCount
- 1;
2305 ULONG Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
2308 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
2310 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
2314 for (i
= 0; i
< ArrayEntryCount
; i
++)
2316 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
2318 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
2319 fixupArray
->Array
[i
] = *pShortToFixUp
;
2320 *pShortToFixUp
= fixupArray
->USN
;
2321 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
2324 return STATUS_SUCCESS
;
2328 ReadLCN(PDEVICE_EXTENSION Vcb
,
2333 LARGE_INTEGER DiskSector
;
2335 DiskSector
.QuadPart
= lcn
;
2337 return NtfsReadSectors(Vcb
->StorageDevice
,
2338 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2339 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2340 Vcb
->NtfsInfo
.BytesPerSector
,
2347 CompareFileName(PUNICODE_STRING FileName
,
2348 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
2350 BOOLEAN CaseSensitive
)
2352 BOOLEAN Ret
, Alloc
= FALSE
;
2353 UNICODE_STRING EntryName
;
2355 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
2357 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
2361 UNICODE_STRING IntFileName
;
2364 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
2369 IntFileName
= *FileName
;
2372 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, !CaseSensitive
, NULL
);
2376 RtlFreeUnicodeString(&IntFileName
);
2383 return (RtlCompareUnicodeString(FileName
, &EntryName
, !CaseSensitive
) == 0);
2390 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
2392 DPRINT1("Entry: %p\n", IndexEntry
);
2393 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
2394 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
2395 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
2396 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
2397 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
2398 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
2399 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
2400 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
2401 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
2402 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
2403 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
2404 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
2405 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
2406 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
2407 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
2408 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
2413 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
2414 PFILE_RECORD_HEADER MftRecord
,
2416 ULONG IndexBlockSize
,
2417 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
2418 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
2419 PUNICODE_STRING FileName
,
2421 PULONG CurrentEntry
,
2423 BOOLEAN CaseSensitive
,
2424 ULONGLONG
*OutMFTIndex
)
2428 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
2429 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
2430 ULONGLONG IndexAllocationSize
;
2431 PINDEX_BUFFER IndexBuffer
;
2433 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2443 DirSearch
? "TRUE" : "FALSE",
2444 CaseSensitive
? "TRUE" : "FALSE",
2447 IndexEntry
= FirstEntry
;
2448 while (IndexEntry
< LastEntry
&&
2449 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
2451 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) >= NTFS_FILE_FIRST_USER_FILE
&&
2452 *CurrentEntry
>= *StartEntry
&&
2453 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
2454 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
2456 *StartEntry
= *CurrentEntry
;
2457 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
2458 return STATUS_SUCCESS
;
2461 (*CurrentEntry
) += 1;
2462 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
2463 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
2466 /* If we're already browsing a subnode */
2467 if (IndexRecord
== NULL
)
2469 return STATUS_OBJECT_PATH_NOT_FOUND
;
2472 /* If there's no subnode */
2473 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
2475 return STATUS_OBJECT_PATH_NOT_FOUND
;
2478 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
2479 if (!NT_SUCCESS(Status
))
2481 DPRINT1("Corrupted filesystem!\n");
2485 IndexAllocationSize
= AttributeDataLength(IndexAllocationCtx
->pRecord
);
2486 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
2487 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
2489 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
2490 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
2491 if (!NT_SUCCESS(Status
))
2496 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
2497 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
2498 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
2499 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
2500 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
2501 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
2503 Status
= BrowseIndexEntries(NULL
,
2515 if (NT_SUCCESS(Status
))
2521 ReleaseAttributeContext(IndexAllocationCtx
);
2526 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
2528 PUNICODE_STRING FileName
,
2531 BOOLEAN CaseSensitive
,
2532 ULONGLONG
*OutMFTIndex
)
2534 PFILE_RECORD_HEADER MftRecord
;
2535 PNTFS_ATTR_CONTEXT IndexRootCtx
;
2536 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
2538 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
2540 ULONG CurrentEntry
= 0;
2542 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2547 DirSearch
? "TRUE" : "FALSE",
2548 CaseSensitive
? "TRUE" : "FALSE",
2551 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
2552 Vcb
->NtfsInfo
.BytesPerFileRecord
,
2554 if (MftRecord
== NULL
)
2556 return STATUS_INSUFFICIENT_RESOURCES
;
2559 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
2560 if (!NT_SUCCESS(Status
))
2562 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2566 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
2567 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
2568 if (!NT_SUCCESS(Status
))
2570 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2574 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
2575 if (IndexRecord
== NULL
)
2577 ReleaseAttributeContext(IndexRootCtx
);
2578 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2579 return STATUS_INSUFFICIENT_RESOURCES
;
2582 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
2583 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
2584 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
2585 /* Index root is always resident. */
2586 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
2587 ReleaseAttributeContext(IndexRootCtx
);
2589 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
2591 Status
= BrowseIndexEntries(Vcb
,
2594 IndexRoot
->SizeOfEntry
,
2604 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2605 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2611 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
2612 PUNICODE_STRING PathName
,
2613 BOOLEAN CaseSensitive
,
2614 PFILE_RECORD_HEADER
*FileRecord
,
2615 PULONGLONG MFTIndex
,
2616 ULONGLONG CurrentMFTIndex
)
2618 UNICODE_STRING Current
, Remaining
;
2620 ULONG FirstEntry
= 0;
2622 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2625 CaseSensitive
? "TRUE" : "FALSE",
2630 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
2632 while (Current
.Length
!= 0)
2634 DPRINT("Current: %wZ\n", &Current
);
2636 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, CaseSensitive
, &CurrentMFTIndex
);
2637 if (!NT_SUCCESS(Status
))
2642 if (Remaining
.Length
== 0)
2645 FsRtlDissectName(Current
, &Current
, &Remaining
);
2648 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2649 if (*FileRecord
== NULL
)
2651 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2652 return STATUS_INSUFFICIENT_RESOURCES
;
2655 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2656 if (!NT_SUCCESS(Status
))
2658 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2659 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2663 *MFTIndex
= CurrentMFTIndex
;
2665 return STATUS_SUCCESS
;
2669 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
2670 PUNICODE_STRING PathName
,
2671 BOOLEAN CaseSensitive
,
2672 PFILE_RECORD_HEADER
*FileRecord
,
2673 PULONGLONG MFTIndex
)
2675 return NtfsLookupFileAt(Vcb
, PathName
, CaseSensitive
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
2679 * @name NtfsDumpFileRecord
2682 * Provides diagnostic information about a file record. Prints a hex dump
2683 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2684 * then prints a dump of each attribute.
2687 * Pointer to a DEVICE_EXTENSION describing the volume.
2690 * Pointer to the file record to be analyzed.
2693 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2694 * in size, and not just the header.
2698 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
2699 PFILE_RECORD_HEADER FileRecord
)
2703 // dump binary data, 8 bytes at a time
2704 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
2706 // display current offset, in hex
2707 DbgPrint("\t%03x\t", i
);
2709 // display hex value of each of the next 8 bytes
2710 for (j
= 0; j
< 8; j
++)
2711 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
2715 NtfsDumpFileAttributes(Vcb
, FileRecord
);
2719 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
2720 PUNICODE_STRING SearchPattern
,
2722 PFILE_RECORD_HEADER
*FileRecord
,
2723 PULONGLONG MFTIndex
,
2724 ULONGLONG CurrentMFTIndex
,
2725 BOOLEAN CaseSensitive
)
2729 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2736 (CaseSensitive
? "TRUE" : "FALSE"));
2738 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, CaseSensitive
, &CurrentMFTIndex
);
2739 if (!NT_SUCCESS(Status
))
2741 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
2745 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2746 if (*FileRecord
== NULL
)
2748 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2749 return STATUS_INSUFFICIENT_RESOURCES
;
2752 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2753 if (!NT_SUCCESS(Status
))
2755 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2756 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2760 *MFTIndex
= CurrentMFTIndex
;
2762 return STATUS_SUCCESS
;