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 *****************************************************************/
33 #include <ntintsafe.h>
38 /* FUNCTIONS ****************************************************************/
41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
43 PNTFS_ATTR_CONTEXT Context
;
45 Context
= ExAllocateFromNPagedLookasideList(&NtfsGlobalData
->AttrCtxtLookasideList
);
48 DPRINT1("Error: Unable to allocate memory for context!\n");
52 // Allocate memory for a copy of the attribute
53 Context
->pRecord
= ExAllocatePoolWithTag(NonPagedPool
, AttrRecord
->Length
, TAG_NTFS
);
56 DPRINT1("Error: Unable to allocate memory for attribute record!\n");
57 ExFreeToNPagedLookasideList(&NtfsGlobalData
->AttrCtxtLookasideList
, Context
);
62 RtlCopyMemory(Context
->pRecord
, AttrRecord
, AttrRecord
->Length
);
64 if (AttrRecord
->IsNonResident
)
66 LONGLONG DataRunOffset
;
67 ULONGLONG DataRunLength
;
68 ULONGLONG NextVBN
= 0;
69 PUCHAR DataRun
= (PUCHAR
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->NonResident
.MappingPairsOffset
);
71 Context
->CacheRun
= DataRun
;
72 Context
->CacheRunOffset
= 0;
73 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
74 Context
->CacheRunLength
= DataRunLength
;
75 if (DataRunOffset
!= -1)
78 Context
->CacheRunStartLCN
=
79 Context
->CacheRunLastLCN
= DataRunOffset
;
84 Context
->CacheRunStartLCN
= -1;
85 Context
->CacheRunLastLCN
= 0;
87 Context
->CacheRunCurrentOffset
= 0;
89 // Convert the data runs to a map control block
90 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun
, &Context
->DataRunsMCB
, &NextVBN
)))
92 DPRINT1("Unable to convert data runs to MCB!\n");
93 ExFreePoolWithTag(Context
->pRecord
, TAG_NTFS
);
94 ExFreeToNPagedLookasideList(&NtfsGlobalData
->AttrCtxtLookasideList
, Context
);
104 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
106 if (Context
->pRecord
)
108 if (Context
->pRecord
->IsNonResident
)
110 FsRtlUninitializeLargeMcb(&Context
->DataRunsMCB
);
113 ExFreePoolWithTag(Context
->pRecord
, TAG_NTFS
);
116 ExFreeToNPagedLookasideList(&NtfsGlobalData
->AttrCtxtLookasideList
, Context
);
121 * @name FindAttribute
124 * Searches a file record for an attribute matching the given type and name.
127 * Optional pointer to a ULONG that will receive the offset of the found attribute
128 * from the beginning of the record. Can be set to NULL.
131 FindAttribute(PDEVICE_EXTENSION Vcb
,
132 PFILE_RECORD_HEADER MftRecord
,
136 PNTFS_ATTR_CONTEXT
* AttrCtx
,
141 FIND_ATTR_CONTXT Context
;
142 PNTFS_ATTR_RECORD Attribute
;
144 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
, Offset
);
147 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
148 while (NT_SUCCESS(Status
))
150 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
156 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
157 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
158 if (RtlCompareMemory(AttrName
, Name
, NameLength
* sizeof(WCHAR
)) == (NameLength
* sizeof(WCHAR
)))
170 /* Found it, fill up the context and return. */
171 DPRINT("Found context\n");
172 *AttrCtx
= PrepareAttributeContext(Attribute
);
174 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
177 *Offset
= Context
.Offset
;
179 FindCloseAttribute(&Context
);
180 return STATUS_SUCCESS
;
184 Status
= FindNextAttribute(&Context
, &Attribute
);
187 FindCloseAttribute(&Context
);
188 return STATUS_OBJECT_NAME_NOT_FOUND
;
193 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
195 if (AttrRecord
->IsNonResident
)
196 return AttrRecord
->NonResident
.AllocatedSize
;
198 return ALIGN_UP_BY(AttrRecord
->Resident
.ValueLength
, ATTR_RECORD_ALIGNMENT
);
203 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
205 if (AttrRecord
->IsNonResident
)
206 return AttrRecord
->NonResident
.DataSize
;
208 return AttrRecord
->Resident
.ValueLength
;
212 * @name IncreaseMftSize
215 * Increases the size of the master file table on a volume, increasing the space available for file records.
218 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
222 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
223 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
226 * STATUS_SUCCESS on success.
227 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
228 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
229 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
232 * Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared,
233 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
234 * This function will wait for exlusive access to the volume fcb.
237 IncreaseMftSize(PDEVICE_EXTENSION Vcb
, BOOLEAN CanWait
)
239 PNTFS_ATTR_CONTEXT BitmapContext
;
240 LARGE_INTEGER BitmapSize
;
241 LARGE_INTEGER DataSize
;
242 LONGLONG BitmapSizeDifference
;
243 ULONG NewRecords
= ATTR_RECORD_ALIGNMENT
* 8; // Allocate one new record for every bit of every byte we'll be adding to the bitmap
244 ULONG DataSizeDifference
= Vcb
->NtfsInfo
.BytesPerFileRecord
* NewRecords
;
247 ULONGLONG BitmapBytes
;
248 ULONGLONG NewBitmapSize
;
249 ULONGLONG FirstNewMftIndex
;
252 PFILE_RECORD_HEADER BlankFileRecord
;
256 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb
, CanWait
? "TRUE" : "FALSE");
258 // We need exclusive access to the mft while we change its size
259 if (!ExAcquireResourceExclusiveLite(&(Vcb
->DirResource
), CanWait
))
261 return STATUS_CANT_WAIT
;
264 // Create a blank file record that will be used later
265 BlankFileRecord
= NtfsCreateEmptyFileRecord(Vcb
);
266 if (!BlankFileRecord
)
268 DPRINT1("Error: Unable to create empty file record!\n");
269 return STATUS_INSUFFICIENT_RESOURCES
;
272 // Clear the flags (file record is not in use)
273 BlankFileRecord
->Flags
= 0;
275 // Find the bitmap attribute of master file table
276 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
277 if (!NT_SUCCESS(Status
))
279 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
280 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
281 ExReleaseResourceLite(&(Vcb
->DirResource
));
285 // Get size of Bitmap Attribute
286 BitmapSize
.QuadPart
= AttributeDataLength(BitmapContext
->pRecord
);
288 // Calculate the new mft size
289 DataSize
.QuadPart
= AttributeDataLength(Vcb
->MFTContext
->pRecord
) + DataSizeDifference
;
291 // Find the index of the first Mft entry that will be created
292 FirstNewMftIndex
= AttributeDataLength(Vcb
->MFTContext
->pRecord
) / Vcb
->NtfsInfo
.BytesPerFileRecord
;
294 // Determine how many bytes will make up the bitmap
295 BitmapBytes
= DataSize
.QuadPart
/ Vcb
->NtfsInfo
.BytesPerFileRecord
/ 8;
296 if ((DataSize
.QuadPart
/ Vcb
->NtfsInfo
.BytesPerFileRecord
) % 8 != 0)
299 // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack
300 BitmapBytes
= ALIGN_UP_BY(BitmapBytes
, ATTR_RECORD_ALIGNMENT
);
302 // Determine how much we need to adjust the bitmap size (it's possible we don't)
303 BitmapSizeDifference
= BitmapBytes
- BitmapSize
.QuadPart
;
304 NewBitmapSize
= max(BitmapSize
.QuadPart
+ BitmapSizeDifference
, BitmapSize
.QuadPart
);
306 // Allocate memory for the bitmap
307 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, NewBitmapSize
, TAG_NTFS
);
310 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
311 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
312 ExReleaseResourceLite(&(Vcb
->DirResource
));
313 ReleaseAttributeContext(BitmapContext
);
314 return STATUS_INSUFFICIENT_RESOURCES
;
317 // Zero the bytes we'll be adding
318 RtlZeroMemory(BitmapBuffer
, NewBitmapSize
);
320 // Read the bitmap attribute
321 BytesRead
= ReadAttribute(Vcb
,
326 if (BytesRead
!= BitmapSize
.LowPart
)
328 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
329 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
330 ExReleaseResourceLite(&(Vcb
->DirResource
));
331 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
332 ReleaseAttributeContext(BitmapContext
);
333 return STATUS_INVALID_PARAMETER
;
336 // Increase the mft size
337 Status
= SetNonResidentAttributeDataLength(Vcb
, Vcb
->MFTContext
, Vcb
->MftDataOffset
, Vcb
->MasterFileTable
, &DataSize
);
338 if (!NT_SUCCESS(Status
))
340 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
341 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
342 ExReleaseResourceLite(&(Vcb
->DirResource
));
343 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
344 ReleaseAttributeContext(BitmapContext
);
348 // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute
349 ReleaseAttributeContext(BitmapContext
);
350 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, &BitmapOffset
);
351 if (!NT_SUCCESS(Status
))
353 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
354 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
355 ExReleaseResourceLite(&(Vcb
->DirResource
));
359 // If the bitmap grew
360 if (BitmapSizeDifference
> 0)
362 // Set the new bitmap size
363 BitmapSize
.QuadPart
= NewBitmapSize
;
364 if (BitmapContext
->pRecord
->IsNonResident
)
365 Status
= SetNonResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
367 Status
= SetResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
369 if (!NT_SUCCESS(Status
))
371 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
372 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
373 ExReleaseResourceLite(&(Vcb
->DirResource
));
374 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
375 ReleaseAttributeContext(BitmapContext
);
380 NtfsDumpFileAttributes(Vcb
, Vcb
->MasterFileTable
);
382 // Update the file record with the new attribute sizes
383 Status
= UpdateFileRecord(Vcb
, Vcb
->VolumeFcb
->MFTIndex
, Vcb
->MasterFileTable
);
384 if (!NT_SUCCESS(Status
))
386 DPRINT1("ERROR: Failed to update $MFT file record!\n");
387 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
388 ExReleaseResourceLite(&(Vcb
->DirResource
));
389 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
390 ReleaseAttributeContext(BitmapContext
);
394 // Write out the new bitmap
395 Status
= WriteAttribute(Vcb
, BitmapContext
, 0, BitmapBuffer
, NewBitmapSize
, &LengthWritten
, Vcb
->MasterFileTable
);
396 if (!NT_SUCCESS(Status
))
398 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
399 ExReleaseResourceLite(&(Vcb
->DirResource
));
400 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
401 ReleaseAttributeContext(BitmapContext
);
402 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
406 // Create blank records for the new file record entries.
407 for (i
= 0; i
< NewRecords
; i
++)
409 Status
= UpdateFileRecord(Vcb
, FirstNewMftIndex
+ i
, BlankFileRecord
);
410 if (!NT_SUCCESS(Status
))
412 DPRINT1("ERROR: Failed to write blank file record!\n");
413 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
414 ExReleaseResourceLite(&(Vcb
->DirResource
));
415 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
416 ReleaseAttributeContext(BitmapContext
);
421 // Update the mft mirror
422 Status
= UpdateMftMirror(Vcb
);
425 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BlankFileRecord
);
426 ExReleaseResourceLite(&(Vcb
->DirResource
));
427 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
428 ReleaseAttributeContext(BitmapContext
);
434 * @name MoveAttributes
437 * Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
438 * and every attribute after that will be moved to MoveTo.
441 * Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
443 * @param FirstAttributeToMove
444 * Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
446 * @param FirstAttributeOffset
447 * Offset of FirstAttributeToMove relative to the beginning of the file record.
450 * ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
453 * The new location of the final attribute (i.e. AttributeEnd marker).
456 MoveAttributes(PDEVICE_EXTENSION DeviceExt
,
457 PNTFS_ATTR_RECORD FirstAttributeToMove
,
458 ULONG FirstAttributeOffset
,
461 // Get the size of all attributes after this one
462 ULONG MemBlockSize
= 0;
463 PNTFS_ATTR_RECORD CurrentAttribute
= FirstAttributeToMove
;
464 ULONG CurrentOffset
= FirstAttributeOffset
;
465 PNTFS_ATTR_RECORD FinalAttribute
;
467 while (CurrentAttribute
->Type
!= AttributeEnd
&& CurrentOffset
< DeviceExt
->NtfsInfo
.BytesPerFileRecord
)
469 CurrentOffset
+= CurrentAttribute
->Length
;
470 MemBlockSize
+= CurrentAttribute
->Length
;
471 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
474 FinalAttribute
= (PNTFS_ATTR_RECORD
)(MoveTo
+ MemBlockSize
);
475 MemBlockSize
+= sizeof(ULONG
) * 2; // Add the AttributeEnd and file record end
477 ASSERT(MemBlockSize
% ATTR_RECORD_ALIGNMENT
== 0);
479 // Move the attributes after this one
480 RtlMoveMemory((PCHAR
)MoveTo
, FirstAttributeToMove
, MemBlockSize
);
482 return FinalAttribute
;
486 InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt
,
487 PNTFS_ATTR_CONTEXT AttrContext
,
488 PFILE_RECORD_HEADER FileRecord
,
492 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
493 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
494 PNTFS_ATTR_RECORD FinalAttribute
;
495 ULONG OldAttributeLength
= Destination
->Length
;
496 ULONG NextAttributeOffset
;
498 DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt
, AttrContext
, FileRecord
, AttrOffset
, DataSize
);
500 ASSERT(!AttrContext
->pRecord
->IsNonResident
);
502 // Update ValueLength Field
503 Destination
->Resident
.ValueLength
= DataSize
;
505 // Calculate the record length and end marker offset
506 Destination
->Length
= ALIGN_UP_BY(DataSize
+ AttrContext
->pRecord
->Resident
.ValueOffset
, ATTR_RECORD_ALIGNMENT
);
507 NextAttributeOffset
= AttrOffset
+ Destination
->Length
;
509 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
510 ASSERT(NextAttributeOffset
% ATTR_RECORD_ALIGNMENT
== 0);
512 // Will the new attribute be larger than the old one?
513 if (Destination
->Length
> OldAttributeLength
)
515 // Free the old copy of the attribute in the context, as it will be the wrong length
516 ExFreePoolWithTag(AttrContext
->pRecord
, TAG_NTFS
);
518 // Create a new copy of the attribute record for the context
519 AttrContext
->pRecord
= ExAllocatePoolWithTag(NonPagedPool
, Destination
->Length
, TAG_NTFS
);
520 if (!AttrContext
->pRecord
)
522 DPRINT1("Unable to allocate memory for attribute!\n");
523 return STATUS_INSUFFICIENT_RESOURCES
;
525 RtlZeroMemory((PVOID
)((ULONG_PTR
)AttrContext
->pRecord
+ OldAttributeLength
), Destination
->Length
- OldAttributeLength
);
526 RtlCopyMemory(AttrContext
->pRecord
, Destination
, OldAttributeLength
);
529 // Are there attributes after this one that need to be moved?
530 if (NextAttribute
->Type
!= AttributeEnd
)
532 // Move the attributes after this one
533 FinalAttribute
= MoveAttributes(DeviceExt
, NextAttribute
, NextAttributeOffset
, (ULONG_PTR
)Destination
+ Destination
->Length
);
537 // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
538 FinalAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)NextAttribute
- OldAttributeLength
+ Destination
->Length
);
541 // Update pRecord's length
542 AttrContext
->pRecord
->Length
= Destination
->Length
;
543 AttrContext
->pRecord
->Resident
.ValueLength
= DataSize
;
545 // set the file record end
546 SetFileRecordEnd(FileRecord
, FinalAttribute
, FILE_RECORD_END
);
548 //NtfsDumpFileRecord(DeviceExt, FileRecord);
550 return STATUS_SUCCESS
;
554 * @parameter FileRecord
555 * Pointer to a file record. Must be a full record at least
556 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
559 SetAttributeDataLength(PFILE_OBJECT FileObject
,
561 PNTFS_ATTR_CONTEXT AttrContext
,
563 PFILE_RECORD_HEADER FileRecord
,
564 PLARGE_INTEGER DataSize
)
566 NTSTATUS Status
= STATUS_SUCCESS
;
568 DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n",
576 // are we truncating the file?
577 if (DataSize
->QuadPart
< AttributeDataLength(AttrContext
->pRecord
))
579 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
581 DPRINT1("Can't truncate a memory-mapped file!\n");
582 return STATUS_USER_MAPPED_FILE
;
586 if (AttrContext
->pRecord
->IsNonResident
)
588 Status
= SetNonResidentAttributeDataLength(Fcb
->Vcb
,
596 // resident attribute
597 Status
= SetResidentAttributeDataLength(Fcb
->Vcb
,
604 if (!NT_SUCCESS(Status
))
606 DPRINT1("ERROR: Failed to set size of attribute!\n");
610 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
612 // write the updated file record back to disk
613 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
615 if (NT_SUCCESS(Status
))
617 if (AttrContext
->pRecord
->IsNonResident
)
618 Fcb
->RFCB
.AllocationSize
.QuadPart
= AttrContext
->pRecord
->NonResident
.AllocatedSize
;
620 Fcb
->RFCB
.AllocationSize
= *DataSize
;
621 Fcb
->RFCB
.FileSize
= *DataSize
;
622 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
623 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
626 return STATUS_SUCCESS
;
630 * @name SetFileRecordEnd
633 * This small function sets a new endpoint for the file record. It set's the final
634 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
637 * Pointer to the file record whose endpoint (length) will be set.
640 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
641 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
644 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
645 * a file record, it preserves the final ULONG that previously ended the record, even though this
646 * value is (to my knowledge) never used. We emulate this behavior.
650 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
651 PNTFS_ATTR_RECORD AttrEnd
,
654 // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
655 ASSERT(((ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
) % ATTR_RECORD_ALIGNMENT
== 0);
657 // mark the end of attributes
658 AttrEnd
->Type
= AttributeEnd
;
660 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
661 AttrEnd
->Length
= EndMarker
;
663 // recalculate bytes in use
664 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
668 * @name SetNonResidentAttributeDataLength
671 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
674 * Pointer to a DEVICE_EXTENSION describing the target disk.
677 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
680 * Offset, from the beginning of the record, of the attribute being sized.
683 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
684 * not just the header.
687 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
690 * STATUS_SUCCESS on success;
691 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
692 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
695 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
696 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
697 * any associated files. Synchronization is the callers responsibility.
700 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
701 PNTFS_ATTR_CONTEXT AttrContext
,
703 PFILE_RECORD_HEADER FileRecord
,
704 PLARGE_INTEGER DataSize
)
706 NTSTATUS Status
= STATUS_SUCCESS
;
707 ULONG BytesPerCluster
= Vcb
->NtfsInfo
.BytesPerCluster
;
708 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
709 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
710 ULONG ExistingClusters
= AttrContext
->pRecord
->NonResident
.AllocatedSize
/ BytesPerCluster
;
712 ASSERT(AttrContext
->pRecord
->IsNonResident
);
714 // do we need to increase the allocation size?
715 if (AttrContext
->pRecord
->NonResident
.AllocatedSize
< AllocationSize
)
717 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
718 LARGE_INTEGER LastClusterInDataRun
;
719 ULONG NextAssignedCluster
;
720 ULONG AssignedClusters
;
722 if (ExistingClusters
== 0)
724 LastClusterInDataRun
.QuadPart
= 0;
728 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
729 (LONGLONG
)AttrContext
->pRecord
->NonResident
.HighestVCN
,
730 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
736 DPRINT1("Error looking up final large MCB entry!\n");
738 // Most likely, HighestVCN went above the largest mapping
739 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->pRecord
->NonResident
.HighestVCN
);
740 return STATUS_INVALID_PARAMETER
;
744 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
745 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->pRecord
->NonResident
.HighestVCN
);
747 while (ClustersNeeded
> 0)
749 Status
= NtfsAllocateClusters(Vcb
,
750 LastClusterInDataRun
.LowPart
+ 1,
752 &NextAssignedCluster
,
755 if (!NT_SUCCESS(Status
))
757 DPRINT1("Error: Unable to allocate requested clusters!\n");
761 // now we need to add the clusters we allocated to the data run
762 Status
= AddRun(Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
763 if (!NT_SUCCESS(Status
))
765 DPRINT1("Error: Unable to add data run!\n");
769 ClustersNeeded
-= AssignedClusters
;
770 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
773 else if (AttrContext
->pRecord
->NonResident
.AllocatedSize
> AllocationSize
)
775 // shrink allocation size
776 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
777 Status
= FreeClusters(Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
780 // TODO: is the file compressed, encrypted, or sparse?
782 AttrContext
->pRecord
->NonResident
.AllocatedSize
= AllocationSize
;
783 AttrContext
->pRecord
->NonResident
.DataSize
= DataSize
->QuadPart
;
784 AttrContext
->pRecord
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
786 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
787 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
788 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
790 // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it.
791 // HACKHACK FIXME: Fix for sparse files; this math won't work in that case.
792 AttrContext
->pRecord
->NonResident
.HighestVCN
= ((ULONGLONG
)AllocationSize
/ Vcb
->NtfsInfo
.BytesPerCluster
) - 1;
793 DestinationAttribute
->NonResident
.HighestVCN
= AttrContext
->pRecord
->NonResident
.HighestVCN
;
795 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
801 * @name SetResidentAttributeDataLength
804 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
807 * Pointer to a DEVICE_EXTENSION describing the target disk.
810 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
813 * Offset, from the beginning of the record, of the attribute being sized.
816 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
817 * not just the header.
820 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
823 * STATUS_SUCCESS on success;
824 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
825 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
826 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
827 * last attribute listed in the file record.
830 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
831 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
832 * any associated files. Synchronization is the callers responsibility.
835 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
836 PNTFS_ATTR_CONTEXT AttrContext
,
838 PFILE_RECORD_HEADER FileRecord
,
839 PLARGE_INTEGER DataSize
)
843 // find the next attribute
844 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->pRecord
->Length
;
845 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
847 ASSERT(!AttrContext
->pRecord
->IsNonResident
);
849 //NtfsDumpFileAttributes(Vcb, FileRecord);
851 // Do we need to increase the data length?
852 if (DataSize
->QuadPart
> AttrContext
->pRecord
->Resident
.ValueLength
)
854 // There's usually padding at the end of a record. Do we need to extend past it?
855 ULONG MaxValueLength
= AttrContext
->pRecord
->Length
- AttrContext
->pRecord
->Resident
.ValueOffset
;
856 if (MaxValueLength
< DataSize
->LowPart
)
858 // If this is the last attribute, we could move the end marker to the very end of the file record
859 MaxValueLength
+= Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
861 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
863 // convert attribute to non-resident
864 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
865 PNTFS_ATTR_RECORD NewRecord
;
866 LARGE_INTEGER AttribDataSize
;
868 ULONG NewRecordLength
;
869 ULONG EndAttributeOffset
;
872 DPRINT1("Converting attribute to non-resident.\n");
874 AttribDataSize
.QuadPart
= AttrContext
->pRecord
->Resident
.ValueLength
;
876 // Is there existing data we need to back-up?
877 if (AttribDataSize
.QuadPart
> 0)
879 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
880 if (AttribData
== NULL
)
882 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
883 return STATUS_INSUFFICIENT_RESOURCES
;
886 // read data to temp buffer
887 Status
= ReadAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
888 if (!NT_SUCCESS(Status
))
890 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
891 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
896 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
898 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
899 NewRecordLength
= ALIGN_UP_BY(0x41 + (AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
)), ATTR_RECORD_ALIGNMENT
);
901 // Create a new attribute record that will store the 0-length, non-resident attribute
902 NewRecord
= ExAllocatePoolWithTag(NonPagedPool
, NewRecordLength
, TAG_NTFS
);
904 // Zero out the NonResident structure
905 RtlZeroMemory(NewRecord
, NewRecordLength
);
907 // Copy the data that's common to both non-resident and resident attributes
908 RtlCopyMemory(NewRecord
, AttrContext
->pRecord
, FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.ValueLength
));
911 if (AttrContext
->pRecord
->NameLength
!= 0)
914 // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
915 RtlCopyMemory((PCHAR
)((ULONG_PTR
)NewRecord
+ 0x40),
916 (PCHAR
)((ULONG_PTR
)AttrContext
->pRecord
+ 0x18),
917 AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
));
920 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
921 NewRecord
->NonResident
.MappingPairsOffset
= 0x40 + (AttrContext
->pRecord
->NameLength
* sizeof(WCHAR
));
923 // update the end of the file record
924 // calculate position of end markers (1 byte for empty data run)
925 EndAttributeOffset
= AttrOffset
+ NewRecord
->NonResident
.MappingPairsOffset
+ 1;
926 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, ATTR_RECORD_ALIGNMENT
);
929 NewRecord
->Length
= EndAttributeOffset
- AttrOffset
;
931 ASSERT(NewRecord
->Length
== NewRecordLength
);
933 // Copy the new attribute record into the file record
934 RtlCopyMemory(Destination
, NewRecord
, NewRecord
->Length
);
936 // Update the file record end
937 SetFileRecordEnd(FileRecord
,
938 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
941 // Initialize the MCB, potentially catch an exception
944 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
946 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
948 DPRINT1("Unable to create LargeMcb!\n");
949 if (AttribDataSize
.QuadPart
> 0)
950 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
951 ExFreePoolWithTag(NewRecord
, TAG_NTFS
);
952 _SEH2_YIELD(return _SEH2_GetExceptionCode());
955 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
956 NewRecord
->IsNonResident
= Destination
->IsNonResident
= 1;
958 // Update file record on disk
959 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
960 if (!NT_SUCCESS(Status
))
962 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
963 if (AttribDataSize
.QuadPart
> 0)
964 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
965 ExFreePoolWithTag(NewRecord
, TAG_NTFS
);
969 // Now we need to free the old copy of the attribute record in the context and replace it with the new one
970 ExFreePoolWithTag(AttrContext
->pRecord
, TAG_NTFS
);
971 AttrContext
->pRecord
= NewRecord
;
973 // Now we can treat the attribute as non-resident and enlarge it normally
974 Status
= SetNonResidentAttributeDataLength(Vcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
975 if (!NT_SUCCESS(Status
))
977 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
978 if (AttribDataSize
.QuadPart
> 0)
979 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
983 // restore the back-up attribute, if we made one
984 if (AttribDataSize
.QuadPart
> 0)
986 Status
= WriteAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
, FileRecord
);
987 if (!NT_SUCCESS(Status
))
989 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
990 // TODO: Reverse migration so no data is lost
991 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
995 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
1001 // set the new length of the resident attribute (if we didn't migrate it)
1002 if (!AttrContext
->pRecord
->IsNonResident
)
1003 return InternalSetResidentAttributeLength(Vcb
, AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
1005 return STATUS_SUCCESS
;
1009 ReadAttribute(PDEVICE_EXTENSION Vcb
,
1010 PNTFS_ATTR_CONTEXT Context
,
1017 LONGLONG DataRunOffset
;
1018 ULONGLONG DataRunLength
;
1019 LONGLONG DataRunStartLCN
;
1020 ULONGLONG CurrentOffset
;
1028 if (!Context
->pRecord
->IsNonResident
)
1030 // We need to truncate Offset to a ULONG for pointer arithmetic
1031 // The check below should ensure that Offset is well within the range of 32 bits
1032 ULONG LittleOffset
= (ULONG
)Offset
;
1034 // Ensure that offset isn't beyond the end of the attribute
1035 if (Offset
> Context
->pRecord
->Resident
.ValueLength
)
1037 if (Offset
+ Length
> Context
->pRecord
->Resident
.ValueLength
)
1038 Length
= (ULONG
)(Context
->pRecord
->Resident
.ValueLength
- Offset
);
1040 RtlCopyMemory(Buffer
, (PVOID
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->Resident
.ValueOffset
+ LittleOffset
), Length
);
1045 * Non-resident attribute
1049 * I. Find the corresponding start data run.
1054 // FIXME: Cache seems to be non-working. Disable it for now
1055 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1058 DataRun
= Context
->CacheRun
;
1059 LastLCN
= Context
->CacheRunLastLCN
;
1060 DataRunStartLCN
= Context
->CacheRunStartLCN
;
1061 DataRunLength
= Context
->CacheRunLength
;
1062 CurrentOffset
= Context
->CacheRunCurrentOffset
;
1067 ULONG UsedBufferSize
;
1068 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1069 if (TempBuffer
== NULL
)
1071 return STATUS_INSUFFICIENT_RESOURCES
;
1077 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1078 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1080 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1083 DataRun
= TempBuffer
;
1087 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1088 if (DataRunOffset
!= -1)
1090 /* Normal data run. */
1091 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1092 LastLCN
= DataRunStartLCN
;
1096 /* Sparse data run. */
1097 DataRunStartLCN
= -1;
1100 if (Offset
>= CurrentOffset
&&
1101 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1108 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1112 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1117 * II. Go through the run list and read the data
1120 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1121 if (DataRunStartLCN
== -1)
1123 RtlZeroMemory(Buffer
, ReadLength
);
1124 Status
= STATUS_SUCCESS
;
1128 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
1129 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
1131 Vcb
->NtfsInfo
.BytesPerSector
,
1135 if (NT_SUCCESS(Status
))
1137 Length
-= ReadLength
;
1138 Buffer
+= ReadLength
;
1139 AlreadyRead
+= ReadLength
;
1141 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1143 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1144 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1145 if (DataRunOffset
!= (ULONGLONG
)-1)
1147 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1148 LastLCN
= DataRunStartLCN
;
1151 DataRunStartLCN
= -1;
1156 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1157 if (DataRunStartLCN
== -1)
1158 RtlZeroMemory(Buffer
, ReadLength
);
1161 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
1162 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1164 Vcb
->NtfsInfo
.BytesPerSector
,
1167 if (!NT_SUCCESS(Status
))
1171 Length
-= ReadLength
;
1172 Buffer
+= ReadLength
;
1173 AlreadyRead
+= ReadLength
;
1175 /* We finished this request, but there still data in this data run. */
1176 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1180 * Go to next run in the list.
1185 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1186 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1187 if (DataRunOffset
!= -1)
1189 /* Normal data run. */
1190 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1191 LastLCN
= DataRunStartLCN
;
1195 /* Sparse data run. */
1196 DataRunStartLCN
= -1;
1203 if (Context
->pRecord
->IsNonResident
)
1204 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1206 Context
->CacheRun
= DataRun
;
1207 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
1208 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1209 Context
->CacheRunLength
= DataRunLength
;
1210 Context
->CacheRunLastLCN
= LastLCN
;
1211 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1218 * @name WriteAttribute
1221 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1222 * and it still needs more documentation / cleaning up.
1225 * Volume Control Block indicating which volume to write the attribute to
1228 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1231 * Offset, in bytes, from the beginning of the attribute indicating where to start
1235 * The data that's being written to the device
1238 * How much data will be written, in bytes
1240 * @param RealLengthWritten
1241 * Pointer to a ULONG which will receive how much data was written, in bytes
1244 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
1245 * being written to. Can be NULL, in which case the file record will be read from disk.
1246 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord
1247 * will be updated with the newly-written attribute before the function returns.
1250 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1251 * writing to a sparse file.
1253 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1254 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1259 WriteAttribute(PDEVICE_EXTENSION Vcb
,
1260 PNTFS_ATTR_CONTEXT Context
,
1262 const PUCHAR Buffer
,
1264 PULONG RealLengthWritten
,
1265 PFILE_RECORD_HEADER FileRecord
)
1269 LONGLONG DataRunOffset
;
1270 ULONGLONG DataRunLength
;
1271 LONGLONG DataRunStartLCN
;
1272 ULONGLONG CurrentOffset
;
1275 PUCHAR SourceBuffer
= Buffer
;
1276 LONGLONG StartingOffset
;
1277 BOOLEAN FileRecordAllocated
= FALSE
;
1283 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
, FileRecord
);
1285 *RealLengthWritten
= 0;
1287 // is this a resident attribute?
1288 if (!Context
->pRecord
->IsNonResident
)
1290 ULONG AttributeOffset
;
1291 PNTFS_ATTR_CONTEXT FoundContext
;
1292 PNTFS_ATTR_RECORD Destination
;
1294 // Ensure requested data is within the bounds of the attribute
1295 ASSERT(Offset
+ Length
<= Context
->pRecord
->Resident
.ValueLength
);
1297 if (Offset
+ Length
> Context
->pRecord
->Resident
.ValueLength
)
1299 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1300 return STATUS_INVALID_PARAMETER
;
1303 // Do we need to read the file record?
1304 if (FileRecord
== NULL
)
1306 FileRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
1309 DPRINT1("Error: Couldn't allocate file record!\n");
1310 return STATUS_NO_MEMORY
;
1313 FileRecordAllocated
= TRUE
;
1315 // read the file record
1316 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1319 // find where to write the attribute data to
1320 Status
= FindAttribute(Vcb
, FileRecord
,
1321 Context
->pRecord
->Type
,
1322 (PCWSTR
)((ULONG_PTR
)Context
->pRecord
+ Context
->pRecord
->NameOffset
),
1323 Context
->pRecord
->NameLength
,
1327 if (!NT_SUCCESS(Status
))
1329 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1330 if(FileRecordAllocated
)
1331 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, FileRecord
);
1335 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttributeOffset
);
1337 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->pRecord
->Resident
.ValueLength
);
1339 // Will we be writing past the end of the allocated file record?
1340 if (Offset
+ Length
+ AttributeOffset
+ Context
->pRecord
->Resident
.ValueOffset
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
1342 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1343 ReleaseAttributeContext(FoundContext
);
1344 if (FileRecordAllocated
)
1345 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, FileRecord
);
1346 return STATUS_INVALID_PARAMETER
;
1349 // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
1350 RtlCopyMemory((PCHAR
)((ULONG_PTR
)Destination
+ Context
->pRecord
->Resident
.ValueOffset
+ (ULONG
)Offset
), Buffer
, Length
);
1352 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1354 // Update the context's copy of the resident attribute
1355 ASSERT(Context
->pRecord
->Length
== Destination
->Length
);
1356 RtlCopyMemory((PVOID
)Context
->pRecord
, Destination
, Context
->pRecord
->Length
);
1358 ReleaseAttributeContext(FoundContext
);
1359 if (FileRecordAllocated
)
1360 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, FileRecord
);
1362 if (NT_SUCCESS(Status
))
1363 *RealLengthWritten
= Length
;
1368 // This is a non-resident attribute.
1370 // I. Find the corresponding start data run.
1372 // FIXME: Cache seems to be non-working. Disable it for now
1373 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1376 DataRun = Context->CacheRun;
1377 LastLCN = Context->CacheRunLastLCN;
1378 DataRunStartLCN = Context->CacheRunStartLCN;
1379 DataRunLength = Context->CacheRunLength;
1380 CurrentOffset = Context->CacheRunCurrentOffset;
1384 ULONG UsedBufferSize
;
1388 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1389 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1390 if (TempBuffer
== NULL
)
1392 return STATUS_INSUFFICIENT_RESOURCES
;
1395 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1397 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1400 DataRun
= TempBuffer
;
1404 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1405 if (DataRunOffset
!= -1)
1408 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1409 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1410 LastLCN
= DataRunStartLCN
;
1414 // Sparse data run. We can't support writing to sparse files yet
1415 // (it may require increasing the allocation size).
1416 DataRunStartLCN
= -1;
1417 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1418 Status
= STATUS_NOT_IMPLEMENTED
;
1422 // Have we reached the data run we're trying to write to?
1423 if (Offset
>= CurrentOffset
&&
1424 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1431 // We reached the last assigned cluster
1432 // TODO: assign new clusters to the end of the file.
1433 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1434 // [We can reach here by creating a new file record when the MFT isn't large enough]
1435 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1436 Status
= STATUS_END_OF_FILE
;
1440 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1444 // II. Go through the run list and write the data
1446 /* REVIEWME -- As adapted from NtfsReadAttribute():
1447 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1448 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1450 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1452 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
1454 // Write the data to the disk
1455 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1458 Vcb
->NtfsInfo
.BytesPerSector
,
1459 (PVOID
)SourceBuffer
);
1461 // Did the write fail?
1462 if (!NT_SUCCESS(Status
))
1464 Context
->CacheRun
= DataRun
;
1465 Context
->CacheRunOffset
= Offset
;
1466 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1467 Context
->CacheRunLength
= DataRunLength
;
1468 Context
->CacheRunLastLCN
= LastLCN
;
1469 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1474 Length
-= WriteLength
;
1475 SourceBuffer
+= WriteLength
;
1476 *RealLengthWritten
+= WriteLength
;
1478 // Did we write to the end of the data run?
1479 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1481 // Advance to the next data run
1482 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1483 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1485 if (DataRunOffset
!= (ULONGLONG
)-1)
1487 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1488 LastLCN
= DataRunStartLCN
;
1491 DataRunStartLCN
= -1;
1494 // Do we have more data to write?
1497 // Make sure we don't write past the end of the current data run
1498 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1500 // Are we dealing with a sparse data run?
1501 if (DataRunStartLCN
== -1)
1503 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1504 Status
= STATUS_NOT_IMPLEMENTED
;
1509 // write the data to the disk
1510 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1511 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1513 Vcb
->NtfsInfo
.BytesPerSector
,
1514 (PVOID
)SourceBuffer
);
1515 if (!NT_SUCCESS(Status
))
1519 Length
-= WriteLength
;
1520 SourceBuffer
+= WriteLength
;
1521 *RealLengthWritten
+= WriteLength
;
1523 // We finished this request, but there's still data in this data run.
1524 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1527 // Go to next run in the list.
1531 // that was the last run
1534 // Failed sanity check.
1535 DPRINT1("Encountered EOF before expected!\n");
1536 Status
= STATUS_END_OF_FILE
;
1543 // Advance to the next data run
1544 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1545 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1546 if (DataRunOffset
!= -1)
1549 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1550 LastLCN
= DataRunStartLCN
;
1555 DataRunStartLCN
= -1;
1557 } // end while (Length > 0) [more data to write]
1559 Context
->CacheRun
= DataRun
;
1560 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1561 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1562 Context
->CacheRunLength
= DataRunLength
;
1563 Context
->CacheRunLastLCN
= LastLCN
;
1564 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1568 if (Context
->pRecord
->IsNonResident
)
1569 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1575 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1577 PFILE_RECORD_HEADER file
)
1579 ULONGLONG BytesRead
;
1581 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1583 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1584 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1586 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1587 return STATUS_PARTIAL_COPY
;
1590 /* Apply update sequence array fixups. */
1591 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1592 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1597 * Searches a file's parent directory (given the parent's index in the mft)
1598 * for the given file. Upon finding an index entry for that file, updates
1599 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1601 * (Most of this code was copied from NtfsFindMftRecord)
1604 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1605 ULONGLONG ParentMFTIndex
,
1606 PUNICODE_STRING FileName
,
1608 ULONGLONG NewDataSize
,
1609 ULONGLONG NewAllocationSize
,
1610 BOOLEAN CaseSensitive
)
1612 PFILE_RECORD_HEADER MftRecord
;
1613 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1614 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1616 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1618 ULONG CurrentEntry
= 0;
1620 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1624 DirSearch
? "TRUE" : "FALSE",
1627 CaseSensitive
? "TRUE" : "FALSE");
1629 MftRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
1630 if (MftRecord
== NULL
)
1632 return STATUS_INSUFFICIENT_RESOURCES
;
1635 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1636 if (!NT_SUCCESS(Status
))
1638 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
1642 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1643 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1644 if (!NT_SUCCESS(Status
))
1646 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
1650 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1651 if (IndexRecord
== NULL
)
1653 ReleaseAttributeContext(IndexRootCtx
);
1654 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
1655 return STATUS_INSUFFICIENT_RESOURCES
;
1658 Status
= ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, AttributeDataLength(IndexRootCtx
->pRecord
));
1659 if (!NT_SUCCESS(Status
))
1661 DPRINT1("ERROR: Failed to read Index Root!\n");
1662 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1663 ReleaseAttributeContext(IndexRootCtx
);
1664 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
1668 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1669 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1670 // Index root is always resident.
1671 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1673 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1675 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1678 IndexRoot
->SizeOfEntry
,
1689 if (Status
== STATUS_PENDING
)
1691 // we need to write the index root attribute back to disk
1692 ULONG LengthWritten
;
1693 Status
= WriteAttribute(Vcb
, IndexRootCtx
, 0, (PUCHAR
)IndexRecord
, AttributeDataLength(IndexRootCtx
->pRecord
), &LengthWritten
, MftRecord
);
1694 if (!NT_SUCCESS(Status
))
1696 DPRINT1("ERROR: Couldn't update Index Root!\n");
1701 ReleaseAttributeContext(IndexRootCtx
);
1702 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1703 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
1709 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1710 * proper index entry.
1711 * (Heavily based on BrowseIndexEntries)
1714 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1715 PFILE_RECORD_HEADER MftRecord
,
1717 ULONG IndexBlockSize
,
1718 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1719 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1720 PUNICODE_STRING FileName
,
1722 PULONG CurrentEntry
,
1724 ULONGLONG NewDataSize
,
1725 ULONGLONG NewAllocatedSize
,
1726 BOOLEAN CaseSensitive
)
1730 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1731 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1732 ULONGLONG IndexAllocationSize
;
1733 PINDEX_BUFFER IndexBuffer
;
1735 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1745 DirSearch
? "TRUE" : "FALSE",
1748 CaseSensitive
? "TRUE" : "FALSE");
1750 // find the index entry responsible for the file we're trying to update
1751 IndexEntry
= FirstEntry
;
1752 while (IndexEntry
< LastEntry
&&
1753 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1755 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > NTFS_FILE_FIRST_USER_FILE
&&
1756 *CurrentEntry
>= *StartEntry
&&
1757 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1758 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
1760 *StartEntry
= *CurrentEntry
;
1761 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1762 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1763 // indicate that the caller will still need to write the structure to the disk
1764 return STATUS_PENDING
;
1767 (*CurrentEntry
) += 1;
1768 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1769 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1772 /* If we're already browsing a subnode */
1773 if (IndexRecord
== NULL
)
1775 return STATUS_OBJECT_PATH_NOT_FOUND
;
1778 /* If there's no subnode */
1779 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1781 return STATUS_OBJECT_PATH_NOT_FOUND
;
1784 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1785 if (!NT_SUCCESS(Status
))
1787 DPRINT("Corrupted filesystem!\n");
1791 IndexAllocationSize
= AttributeDataLength(IndexAllocationCtx
->pRecord
);
1792 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1793 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1795 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1796 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1797 if (!NT_SUCCESS(Status
))
1802 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1803 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1804 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1805 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1806 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1807 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1809 Status
= UpdateIndexEntryFileNameSize(NULL
,
1822 if (Status
== STATUS_PENDING
)
1824 // write the index record back to disk
1827 // first we need to update the fixup values for the index block
1828 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1829 if (!NT_SUCCESS(Status
))
1831 DPRINT1("Error: Failed to update fixup sequence array!\n");
1835 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
, MftRecord
);
1836 if (!NT_SUCCESS(Status
))
1838 DPRINT1("ERROR Performing write!\n");
1842 Status
= STATUS_SUCCESS
;
1845 if (NT_SUCCESS(Status
))
1851 ReleaseAttributeContext(IndexAllocationCtx
);
1856 * @name UpdateFileRecord
1859 * Writes a file record to the master file table, at a given index.
1862 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1865 * Target index in the master file table to store the file record.
1868 * Pointer to the complete file record which will be written to the master file table.
1871 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1875 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1877 PFILE_RECORD_HEADER FileRecord
)
1880 NTSTATUS Status
= STATUS_SUCCESS
;
1882 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1884 // Add the fixup array to prepare the data for writing to disk
1885 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1887 // write the file record to the master file table
1888 Status
= WriteAttribute(Vcb
,
1890 MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
,
1891 (const PUCHAR
)FileRecord
,
1892 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1896 if (!NT_SUCCESS(Status
))
1898 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1901 // remove the fixup array (so the file record pointer can still be used)
1902 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1909 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1910 PNTFS_RECORD_HEADER Record
)
1917 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1918 USANumber
= *(USA
++);
1919 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1920 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1922 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1926 if (*Block
!= USANumber
)
1928 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1929 return STATUS_UNSUCCESSFUL
;
1932 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1936 return STATUS_SUCCESS
;
1940 * @name AddNewMftEntry
1943 * Adds a file record to the master file table of a given device.
1946 * Pointer to a complete file record which will be saved to disk.
1949 * Pointer to the DEVICE_EXTENSION of the target drive.
1951 * @param DestinationIndex
1952 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1955 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1956 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1959 * STATUS_SUCCESS on success.
1960 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1961 * to read the attribute.
1962 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1963 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1966 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1967 PDEVICE_EXTENSION DeviceExt
,
1968 PULONGLONG DestinationIndex
,
1971 NTSTATUS Status
= STATUS_SUCCESS
;
1974 ULONGLONG BitmapDataSize
;
1975 ULONGLONG AttrBytesRead
;
1977 PUCHAR BitmapBuffer
;
1978 ULONG LengthWritten
;
1979 PNTFS_ATTR_CONTEXT BitmapContext
;
1980 LARGE_INTEGER BitmapBits
;
1981 UCHAR SystemReservedBits
;
1983 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord
, DeviceExt
, DestinationIndex
, CanWait
? "TRUE" : "FALSE");
1985 // First, we have to read the mft's $Bitmap attribute
1987 // Find the attribute
1988 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1989 if (!NT_SUCCESS(Status
))
1991 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1995 // Get size of bitmap
1996 BitmapDataSize
= AttributeDataLength(BitmapContext
->pRecord
);
1998 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1999 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
2000 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
+ sizeof(ULONG
), TAG_NTFS
);
2003 ReleaseAttributeContext(BitmapContext
);
2004 return STATUS_INSUFFICIENT_RESOURCES
;
2006 RtlZeroMemory(BitmapBuffer
, BitmapDataSize
+ sizeof(ULONG
));
2008 // Get a ULONG-aligned pointer for the bitmap itself
2009 BitmapData
= (PUCHAR
)ALIGN_UP_BY((ULONG_PTR
)BitmapBuffer
, sizeof(ULONG
));
2011 // read $Bitmap attribute
2012 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, (PCHAR
)BitmapData
, BitmapDataSize
);
2014 if (AttrBytesRead
!= BitmapDataSize
)
2016 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
2017 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2018 ReleaseAttributeContext(BitmapContext
);
2019 return STATUS_OBJECT_NAME_NOT_FOUND
;
2022 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
2023 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
2024 SystemReservedBits
= BitmapData
[2];
2025 BitmapData
[2] = 0xff;
2027 // Calculate bit count
2028 BitmapBits
.QuadPart
= AttributeDataLength(DeviceExt
->MFTContext
->pRecord
) /
2029 DeviceExt
->NtfsInfo
.BytesPerFileRecord
;
2030 if (BitmapBits
.HighPart
!= 0)
2032 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
2033 NtfsGlobalData
->EnableWriteSupport
= FALSE
;
2034 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2035 ReleaseAttributeContext(BitmapContext
);
2036 return STATUS_NOT_IMPLEMENTED
;
2039 // convert buffer into bitmap
2040 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapBits
.LowPart
);
2042 // set next available bit, preferrably after 23rd bit
2043 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
2044 if ((LONG
)MftIndex
== -1)
2046 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
2048 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2049 ReleaseAttributeContext(BitmapContext
);
2051 // Couldn't find a free record in the MFT, add some blank records and try again
2052 Status
= IncreaseMftSize(DeviceExt
, CanWait
);
2053 if (!NT_SUCCESS(Status
))
2055 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
2059 return AddNewMftEntry(FileRecord
, DeviceExt
, DestinationIndex
, CanWait
);
2062 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
2064 // update file record with index
2065 FileRecord
->MFTRecordNumber
= MftIndex
;
2067 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
2069 // Restore the system reserved bits
2070 BitmapData
[2] = SystemReservedBits
;
2072 // write the bitmap back to the MFT's $Bitmap attribute
2073 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
, FileRecord
);
2074 if (!NT_SUCCESS(Status
))
2076 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
2077 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2078 ReleaseAttributeContext(BitmapContext
);
2082 // update the file record (write it to disk)
2083 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
2085 if (!NT_SUCCESS(Status
))
2087 DPRINT1("ERROR: Unable to write file record!\n");
2088 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2089 ReleaseAttributeContext(BitmapContext
);
2093 *DestinationIndex
= MftIndex
;
2095 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
2096 ReleaseAttributeContext(BitmapContext
);
2102 * @name NtfsAddFilenameToDirectory
2105 * Adds a $FILE_NAME attribute to a given directory index.
2108 * Points to the target disk's DEVICE_EXTENSION.
2110 * @param DirectoryMftIndex
2111 * Mft index of the parent directory which will receive the file.
2113 * @param FileReferenceNumber
2114 * File reference of the file to be added to the directory. This is a combination of the
2115 * Mft index and sequence number.
2117 * @param FilenameAttribute
2118 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2120 * @param CaseSensitive
2121 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2122 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2125 * STATUS_SUCCESS on success.
2126 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2127 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2130 * WIP - Can only support a few files in a directory.
2131 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2132 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2133 * get both attributes added to its parent directory.
2136 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt
,
2137 ULONGLONG DirectoryMftIndex
,
2138 ULONGLONG FileReferenceNumber
,
2139 PFILENAME_ATTRIBUTE FilenameAttribute
,
2140 BOOLEAN CaseSensitive
)
2142 NTSTATUS Status
= STATUS_SUCCESS
;
2143 PFILE_RECORD_HEADER ParentFileRecord
;
2144 PNTFS_ATTR_CONTEXT IndexRootContext
;
2145 PINDEX_ROOT_ATTRIBUTE I30IndexRoot
;
2146 ULONG IndexRootOffset
;
2147 ULONGLONG I30IndexRootLength
;
2148 ULONG LengthWritten
;
2149 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
;
2150 ULONG AttributeLength
;
2151 PNTFS_ATTR_RECORD NextAttribute
;
2153 ULONG BtreeIndexLength
;
2154 ULONG MaxIndexRootSize
;
2155 PB_TREE_KEY NewLeftKey
;
2156 PB_TREE_FILENAME_NODE NewRightHandNode
;
2157 LARGE_INTEGER MinIndexRootSize
;
2158 ULONG NewMaxIndexRootSize
;
2161 // Allocate memory for the parent directory
2162 ParentFileRecord
= ExAllocateFromNPagedLookasideList(&DeviceExt
->FileRecLookasideList
);
2163 if (!ParentFileRecord
)
2165 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2166 return STATUS_INSUFFICIENT_RESOURCES
;
2169 // Open the parent directory
2170 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2171 if (!NT_SUCCESS(Status
))
2173 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2174 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2180 DPRINT1("Dumping old parent file record:\n");
2181 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
2184 // Find the index root attribute for the directory
2185 Status
= FindAttribute(DeviceExt
,
2192 if (!NT_SUCCESS(Status
))
2194 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2196 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2200 // Find the maximum index size given what the file record can hold
2201 // First, find the max index size assuming index root is the last attribute
2202 MaxIndexRootSize
= DeviceExt
->NtfsInfo
.BytesPerFileRecord
// Start with the size of a file record
2203 - IndexRootOffset
// Subtract the length of everything that comes before index root
2204 - IndexRootContext
->pRecord
->Resident
.ValueOffset
// Subtract the length of the attribute header for index root
2205 - sizeof(INDEX_ROOT_ATTRIBUTE
) // Subtract the length of the index root header
2206 - (sizeof(ULONG
) * 2); // Subtract the length of the file record end marker and padding
2208 // Are there attributes after this one?
2209 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)ParentFileRecord
+ IndexRootOffset
+ IndexRootContext
->pRecord
->Length
);
2210 if (NextAttribute
->Type
!= AttributeEnd
)
2212 // Find the length of all attributes after this one, not counting the end marker
2213 ULONG LengthOfAttributes
= 0;
2214 PNTFS_ATTR_RECORD CurrentAttribute
= NextAttribute
;
2215 while (CurrentAttribute
->Type
!= AttributeEnd
)
2217 LengthOfAttributes
+= CurrentAttribute
->Length
;
2218 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
2221 // Leave room for the existing attributes
2222 MaxIndexRootSize
-= LengthOfAttributes
;
2225 // Allocate memory for the index root data
2226 I30IndexRootLength
= AttributeDataLength(IndexRootContext
->pRecord
);
2227 I30IndexRoot
= ExAllocatePoolWithTag(NonPagedPool
, I30IndexRootLength
, TAG_NTFS
);
2230 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2231 ReleaseAttributeContext(IndexRootContext
);
2232 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2233 return STATUS_INSUFFICIENT_RESOURCES
;
2236 // Read the Index Root
2237 Status
= ReadAttribute(DeviceExt
, IndexRootContext
, 0, (PCHAR
)I30IndexRoot
, I30IndexRootLength
);
2238 if (!NT_SUCCESS(Status
))
2240 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex
);
2241 ReleaseAttributeContext(IndexRootContext
);
2242 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2243 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2247 // Convert the index to a B*Tree
2248 Status
= CreateBTreeFromIndex(DeviceExt
,
2253 if (!NT_SUCCESS(Status
))
2255 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2256 ReleaseAttributeContext(IndexRootContext
);
2257 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2258 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2266 // Insert the key for the file we're adding
2267 Status
= NtfsInsertKey(NewTree
,
2268 FileReferenceNumber
,
2273 I30IndexRoot
->SizeOfEntry
,
2276 if (!NT_SUCCESS(Status
))
2278 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2279 DestroyBTree(NewTree
);
2280 ReleaseAttributeContext(IndexRootContext
);
2281 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2282 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2290 // The root node can't be split
2291 ASSERT(NewLeftKey
== NULL
);
2292 ASSERT(NewRightHandNode
== NULL
);
2294 // Convert B*Tree back to Index
2296 // Updating the index allocation can change the size available for the index root,
2297 // And if the index root is demoted, the index allocation will need to be updated again,
2298 // which may change the size available for index root... etc.
2299 // My solution is to decrease index root to the size it would be if it was demoted,
2300 // then UpdateIndexAllocation will have an accurate representation of the maximum space
2301 // it can use in the file record. There's still a chance that the act of allocating an
2302 // index node after demoting the index root will increase the size of the file record beyond
2303 // it's limit, but if that happens, an attribute-list will most definitely be needed.
2304 // This a bit hacky, but it seems to be functional.
2306 // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
2307 MinIndexRootSize
.QuadPart
= sizeof(INDEX_ROOT_ATTRIBUTE
) // size of the index root headers
2308 + 0x18; // Size of dummy key with a VCN for a subnode
2309 ASSERT(MinIndexRootSize
.QuadPart
% ATTR_RECORD_ALIGNMENT
== 0);
2311 // Temporarily shrink the index root to it's minimal size
2312 AttributeLength
= MinIndexRootSize
.LowPart
;
2313 AttributeLength
+= sizeof(INDEX_ROOT_ATTRIBUTE
);
2316 // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast.
2317 // The potential for catastrophic data-loss exists!!! :)
2319 // Update the length of the attribute in the file record of the parent directory
2320 Status
= InternalSetResidentAttributeLength(DeviceExt
,
2325 if (!NT_SUCCESS(Status
))
2327 DPRINT1("ERROR: Unable to set length of index root!\n");
2328 DestroyBTree(NewTree
);
2329 ReleaseAttributeContext(IndexRootContext
);
2330 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2331 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2335 // Update the index allocation
2336 Status
= UpdateIndexAllocation(DeviceExt
, NewTree
, I30IndexRoot
->SizeOfEntry
, ParentFileRecord
);
2337 if (!NT_SUCCESS(Status
))
2339 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2340 DestroyBTree(NewTree
);
2341 ReleaseAttributeContext(IndexRootContext
);
2342 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2343 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2348 DPRINT1("Index Allocation updated\n");
2352 // Find the maximum index root size given what the file record can hold
2353 // First, find the max index size assuming index root is the last attribute
2354 NewMaxIndexRootSize
=
2355 DeviceExt
->NtfsInfo
.BytesPerFileRecord
// Start with the size of a file record
2356 - IndexRootOffset
// Subtract the length of everything that comes before index root
2357 - IndexRootContext
->pRecord
->Resident
.ValueOffset
// Subtract the length of the attribute header for index root
2358 - sizeof(INDEX_ROOT_ATTRIBUTE
) // Subtract the length of the index root header
2359 - (sizeof(ULONG
) * 2); // Subtract the length of the file record end marker and padding
2361 // Are there attributes after this one?
2362 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)ParentFileRecord
+ IndexRootOffset
+ IndexRootContext
->pRecord
->Length
);
2363 if (NextAttribute
->Type
!= AttributeEnd
)
2365 // Find the length of all attributes after this one, not counting the end marker
2366 ULONG LengthOfAttributes
= 0;
2367 PNTFS_ATTR_RECORD CurrentAttribute
= NextAttribute
;
2368 while (CurrentAttribute
->Type
!= AttributeEnd
)
2370 LengthOfAttributes
+= CurrentAttribute
->Length
;
2371 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
2374 // Leave room for the existing attributes
2375 NewMaxIndexRootSize
-= LengthOfAttributes
;
2378 // The index allocation and index bitmap may have grown, leaving less room for the index root,
2379 // so now we need to double-check that index root isn't too large
2380 NodeSize
= GetSizeOfIndexEntries(NewTree
->RootNode
);
2381 if (NodeSize
> NewMaxIndexRootSize
)
2383 DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize
, NewMaxIndexRootSize
);
2385 Status
= DemoteBTreeRoot(NewTree
);
2386 if (!NT_SUCCESS(Status
))
2388 DPRINT1("ERROR: Failed to demote index root!\n");
2389 DestroyBTree(NewTree
);
2390 ReleaseAttributeContext(IndexRootContext
);
2391 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2392 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2396 // We need to update the index allocation once more
2397 Status
= UpdateIndexAllocation(DeviceExt
, NewTree
, I30IndexRoot
->SizeOfEntry
, ParentFileRecord
);
2398 if (!NT_SUCCESS(Status
))
2400 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2401 DestroyBTree(NewTree
);
2402 ReleaseAttributeContext(IndexRootContext
);
2403 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2404 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2408 // re-recalculate max size of index root
2409 NewMaxIndexRootSize
=
2410 // Find the maximum index size given what the file record can hold
2411 // First, find the max index size assuming index root is the last attribute
2412 DeviceExt
->NtfsInfo
.BytesPerFileRecord
// Start with the size of a file record
2413 - IndexRootOffset
// Subtract the length of everything that comes before index root
2414 - IndexRootContext
->pRecord
->Resident
.ValueOffset
// Subtract the length of the attribute header for index root
2415 - sizeof(INDEX_ROOT_ATTRIBUTE
) // Subtract the length of the index root header
2416 - (sizeof(ULONG
) * 2); // Subtract the length of the file record end marker and padding
2418 // Are there attributes after this one?
2419 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)ParentFileRecord
+ IndexRootOffset
+ IndexRootContext
->pRecord
->Length
);
2420 if (NextAttribute
->Type
!= AttributeEnd
)
2422 // Find the length of all attributes after this one, not counting the end marker
2423 ULONG LengthOfAttributes
= 0;
2424 PNTFS_ATTR_RECORD CurrentAttribute
= NextAttribute
;
2425 while (CurrentAttribute
->Type
!= AttributeEnd
)
2427 LengthOfAttributes
+= CurrentAttribute
->Length
;
2428 CurrentAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)CurrentAttribute
+ CurrentAttribute
->Length
);
2431 // Leave room for the existing attributes
2432 NewMaxIndexRootSize
-= LengthOfAttributes
;
2438 // Create the Index Root from the B*Tree
2439 Status
= CreateIndexRootFromBTree(DeviceExt
, NewTree
, NewMaxIndexRootSize
, &NewIndexRoot
, &BtreeIndexLength
);
2440 if (!NT_SUCCESS(Status
))
2442 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2443 DestroyBTree(NewTree
);
2444 ReleaseAttributeContext(IndexRootContext
);
2445 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2446 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2450 // We're done with the B-Tree now
2451 DestroyBTree(NewTree
);
2453 // Write back the new index root attribute to the parent directory file record
2455 // First, we need to resize the attribute.
2456 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2457 // We can't set the size as we normally would, because $INDEX_ROOT must always be resident.
2458 AttributeLength
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
2460 if (AttributeLength
!= IndexRootContext
->pRecord
->Resident
.ValueLength
)
2462 // Update the length of the attribute in the file record of the parent directory
2463 Status
= InternalSetResidentAttributeLength(DeviceExt
,
2468 if (!NT_SUCCESS(Status
))
2470 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2471 ReleaseAttributeContext(IndexRootContext
);
2472 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2473 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2474 DPRINT1("ERROR: Unable to set resident attribute length!\n");
2480 NT_ASSERT(ParentFileRecord
->BytesInUse
<= DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
2482 Status
= UpdateFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2483 if (!NT_SUCCESS(Status
))
2485 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex
);
2486 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2487 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2488 ReleaseAttributeContext(IndexRootContext
);
2489 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2493 // Write the new index root to disk
2494 Status
= WriteAttribute(DeviceExt
,
2497 (PUCHAR
)NewIndexRoot
,
2501 if (!NT_SUCCESS(Status
) || LengthWritten
!= AttributeLength
)
2503 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2504 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2505 ReleaseAttributeContext(IndexRootContext
);
2506 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2507 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2511 // re-read the parent file record, so we can dump it
2512 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2513 if (!NT_SUCCESS(Status
))
2515 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2520 DPRINT1("Dumping new B-Tree:\n");
2522 Status
= CreateBTreeFromIndex(DeviceExt
, ParentFileRecord
, IndexRootContext
, NewIndexRoot
, &NewTree
);
2523 if (!NT_SUCCESS(Status
))
2525 DPRINT1("ERROR: Couldn't re-create b-tree\n");
2531 DestroyBTree(NewTree
);
2533 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
2538 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2539 ReleaseAttributeContext(IndexRootContext
);
2540 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2541 ExFreeToNPagedLookasideList(&DeviceExt
->FileRecLookasideList
, ParentFileRecord
);
2547 AddFixupArray(PDEVICE_EXTENSION Vcb
,
2548 PNTFS_RECORD_HEADER Record
)
2550 USHORT
*pShortToFixUp
;
2551 ULONG ArrayEntryCount
= Record
->UsaCount
- 1;
2552 ULONG Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
2555 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
2557 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
2561 for (i
= 0; i
< ArrayEntryCount
; i
++)
2563 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
2565 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
2566 fixupArray
->Array
[i
] = *pShortToFixUp
;
2567 *pShortToFixUp
= fixupArray
->USN
;
2568 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
2571 return STATUS_SUCCESS
;
2575 ReadLCN(PDEVICE_EXTENSION Vcb
,
2580 LARGE_INTEGER DiskSector
;
2582 DiskSector
.QuadPart
= lcn
;
2584 return NtfsReadSectors(Vcb
->StorageDevice
,
2585 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2586 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2587 Vcb
->NtfsInfo
.BytesPerSector
,
2594 CompareFileName(PUNICODE_STRING FileName
,
2595 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
2597 BOOLEAN CaseSensitive
)
2599 BOOLEAN Ret
, Alloc
= FALSE
;
2600 UNICODE_STRING EntryName
;
2602 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
2604 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
2608 UNICODE_STRING IntFileName
;
2611 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
2616 IntFileName
= *FileName
;
2619 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, !CaseSensitive
, NULL
);
2623 RtlFreeUnicodeString(&IntFileName
);
2630 return (RtlCompareUnicodeString(FileName
, &EntryName
, !CaseSensitive
) == 0);
2635 * @name UpdateMftMirror
2638 * Backs-up the first ~4 master file table entries to the $MFTMirr file.
2641 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
2645 * STATUS_SUCCESS on success.
2646 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
2647 * STATUS_UNSUCCESSFUL if we couldn't read the master file table.
2650 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
2651 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA.
2652 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
2653 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
2654 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
2655 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
2658 UpdateMftMirror(PNTFS_VCB Vcb
)
2660 PFILE_RECORD_HEADER MirrorFileRecord
;
2661 PNTFS_ATTR_CONTEXT MirrDataContext
;
2662 PNTFS_ATTR_CONTEXT MftDataContext
;
2664 ULONGLONG DataLength
;
2667 ULONG LengthWritten
;
2669 // Allocate memory for the Mft mirror file record
2670 MirrorFileRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
2671 if (!MirrorFileRecord
)
2673 DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
2674 return STATUS_INSUFFICIENT_RESOURCES
;
2677 // Read the Mft Mirror file record
2678 Status
= ReadFileRecord(Vcb
, NTFS_FILE_MFTMIRR
, MirrorFileRecord
);
2679 if (!NT_SUCCESS(Status
))
2681 DPRINT1("ERROR: Failed to read $MFTMirr!\n");
2682 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2686 // Find the $DATA attribute of $MFTMirr
2687 Status
= FindAttribute(Vcb
, MirrorFileRecord
, AttributeData
, L
"", 0, &MirrDataContext
, NULL
);
2688 if (!NT_SUCCESS(Status
))
2690 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2691 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2695 // Find the $DATA attribute of $MFT
2696 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeData
, L
"", 0, &MftDataContext
, NULL
);
2697 if (!NT_SUCCESS(Status
))
2699 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2700 ReleaseAttributeContext(MirrDataContext
);
2701 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2705 // Get the size of the mirror's $DATA attribute
2706 DataLength
= AttributeDataLength(MirrDataContext
->pRecord
);
2708 ASSERT(DataLength
% Vcb
->NtfsInfo
.BytesPerFileRecord
== 0);
2710 // Create buffer for the mirror's $DATA attribute
2711 DataBuffer
= ExAllocatePoolWithTag(NonPagedPool
, DataLength
, TAG_NTFS
);
2714 DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
2715 ReleaseAttributeContext(MftDataContext
);
2716 ReleaseAttributeContext(MirrDataContext
);
2717 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2718 return STATUS_INSUFFICIENT_RESOURCES
;
2721 ASSERT(DataLength
< ULONG_MAX
);
2723 // Back up the first several entries of the Mft's $DATA Attribute
2724 BytesRead
= ReadAttribute(Vcb
, MftDataContext
, 0, DataBuffer
, (ULONG
)DataLength
);
2725 if (BytesRead
!= (ULONG
)DataLength
)
2727 DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
2728 ReleaseAttributeContext(MftDataContext
);
2729 ReleaseAttributeContext(MirrDataContext
);
2730 ExFreePoolWithTag(DataBuffer
, TAG_NTFS
);
2731 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2732 return STATUS_UNSUCCESSFUL
;
2735 // Write the mirror's $DATA attribute
2736 Status
= WriteAttribute(Vcb
,
2743 if (!NT_SUCCESS(Status
))
2745 DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
2749 ReleaseAttributeContext(MftDataContext
);
2750 ReleaseAttributeContext(MirrDataContext
);
2751 ExFreePoolWithTag(DataBuffer
, TAG_NTFS
);
2752 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MirrorFileRecord
);
2760 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
2762 DPRINT1("Entry: %p\n", IndexEntry
);
2763 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
2764 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
2765 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
2766 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
2767 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
2768 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
2769 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
2770 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
2771 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
2772 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
2773 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
2774 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
2775 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
2776 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
2777 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
2778 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
2783 BrowseSubNodeIndexEntries(PNTFS_VCB Vcb
,
2784 PFILE_RECORD_HEADER MftRecord
,
2785 ULONG IndexBlockSize
,
2786 PUNICODE_STRING FileName
,
2787 PNTFS_ATTR_CONTEXT IndexAllocationContext
,
2791 PULONG CurrentEntry
,
2793 BOOLEAN CaseSensitive
,
2794 ULONGLONG
*OutMFTIndex
)
2796 PINDEX_BUFFER IndexRecord
;
2799 PINDEX_ENTRY_ATTRIBUTE FirstEntry
;
2800 PINDEX_ENTRY_ATTRIBUTE LastEntry
;
2801 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
2805 DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n",
2810 IndexAllocationContext
,
2816 DirSearch
? "TRUE" : "FALSE",
2817 CaseSensitive
? "TRUE" : "FALSE",
2820 // Calculate node number as VCN / Clusters per index record
2821 NodeNumber
= VCN
/ (Vcb
->NtfsInfo
.BytesPerIndexRecord
/ Vcb
->NtfsInfo
.BytesPerCluster
);
2823 // Is the bit for this node clear in the bitmap?
2824 if (!RtlCheckBit(Bitmap
, NodeNumber
))
2826 DPRINT1("File system corruption detected, node with VCN %I64u is being reused or is marked as deleted.\n", VCN
);
2827 return STATUS_DATA_ERROR
;
2830 // Clear the bit for this node so it can't be recursively referenced
2831 RtlClearBits(Bitmap
, NodeNumber
, 1);
2833 // Allocate memory for the index record
2834 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, IndexBlockSize
, TAG_NTFS
);
2837 DPRINT1("Unable to allocate memory for index record!\n");
2838 return STATUS_INSUFFICIENT_RESOURCES
;
2841 // Calculate offset of index record
2842 Offset
= VCN
* Vcb
->NtfsInfo
.BytesPerCluster
;
2844 // Read the index record
2845 BytesRead
= ReadAttribute(Vcb
, IndexAllocationContext
, Offset
, (PCHAR
)IndexRecord
, IndexBlockSize
);
2846 if (BytesRead
!= IndexBlockSize
)
2848 DPRINT1("Unable to read index record!\n");
2849 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2850 return STATUS_UNSUCCESSFUL
;
2853 // Assert that we're dealing with an index record here
2854 ASSERT(IndexRecord
->Ntfs
.Type
== NRH_INDX_TYPE
);
2856 // Apply the fixup array to the index record
2857 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
2858 if (!NT_SUCCESS(Status
))
2860 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2861 DPRINT1("Failed to apply fixup array!\n");
2865 ASSERT(IndexRecord
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
2866 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexRecord
->Header
+ IndexRecord
->Header
.FirstEntryOffset
);
2867 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexRecord
->Header
+ IndexRecord
->Header
.TotalSizeOfEntries
);
2868 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexRecord
+ IndexBlockSize
));
2870 // Loop through all Index Entries of index, starting with FirstEntry
2871 IndexEntry
= FirstEntry
;
2872 while (IndexEntry
<= LastEntry
)
2874 // Does IndexEntry have a sub-node?
2875 if (IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
2877 if (!(IndexRecord
->Header
.Flags
& INDEX_NODE_LARGE
) || !IndexAllocationContext
)
2879 DPRINT1("Filesystem corruption detected!\n");
2883 Status
= BrowseSubNodeIndexEntries(Vcb
,
2887 IndexAllocationContext
,
2889 GetIndexEntryVCN(IndexEntry
),
2895 if (NT_SUCCESS(Status
))
2897 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2904 if (IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
)
2907 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
2908 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) >= NTFS_FILE_FIRST_USER_FILE
&&
2909 *CurrentEntry
>= *StartEntry
&&
2910 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
2911 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
2913 *StartEntry
= *CurrentEntry
;
2914 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
2915 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2916 return STATUS_SUCCESS
;
2919 // Advance to the next index entry
2920 (*CurrentEntry
) += 1;
2921 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
2922 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
2925 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2927 return STATUS_OBJECT_PATH_NOT_FOUND
;
2931 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
2932 PFILE_RECORD_HEADER MftRecord
,
2933 PINDEX_ROOT_ATTRIBUTE IndexRecord
,
2934 ULONG IndexBlockSize
,
2935 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
2936 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
2937 PUNICODE_STRING FileName
,
2939 PULONG CurrentEntry
,
2941 BOOLEAN CaseSensitive
,
2942 ULONGLONG
*OutMFTIndex
)
2945 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
2946 PNTFS_ATTR_CONTEXT IndexAllocationContext
;
2947 PNTFS_ATTR_CONTEXT BitmapContext
;
2952 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2962 DirSearch
? "TRUE" : "FALSE",
2963 CaseSensitive
? "TRUE" : "FALSE",
2966 // Find the $I30 index allocation, if there is one
2967 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationContext
, NULL
);
2968 if (NT_SUCCESS(Status
))
2970 ULONGLONG BitmapLength
;
2971 // Find the bitmap attribute for the index
2972 Status
= FindAttribute(Vcb
, MftRecord
, AttributeBitmap
, L
"$I30", 4, &BitmapContext
, NULL
);
2973 if (!NT_SUCCESS(Status
))
2975 DPRINT1("Potential file system corruption detected!\n");
2976 ReleaseAttributeContext(IndexAllocationContext
);
2980 // Get the length of the bitmap attribute
2981 BitmapLength
= AttributeDataLength(BitmapContext
->pRecord
);
2983 // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer
2984 // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
2985 BitmapMem
= ExAllocatePoolWithTag(NonPagedPool
, BitmapLength
+ sizeof(ULONG
), TAG_NTFS
);
2988 DPRINT1("Error: failed to allocate bitmap!");
2989 ReleaseAttributeContext(BitmapContext
);
2990 ReleaseAttributeContext(IndexAllocationContext
);
2991 return STATUS_INSUFFICIENT_RESOURCES
;
2994 RtlZeroMemory(BitmapMem
, BitmapLength
+ sizeof(ULONG
));
2996 // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
2997 BitmapPtr
= (PULONG
)ALIGN_UP_BY((ULONG_PTR
)BitmapMem
, sizeof(ULONG
));
2999 // Read the existing bitmap data
3000 Status
= ReadAttribute(Vcb
, BitmapContext
, 0, (PCHAR
)BitmapPtr
, BitmapLength
);
3001 if (!NT_SUCCESS(Status
))
3003 DPRINT1("ERROR: Failed to read bitmap attribute!\n");
3004 ExFreePoolWithTag(BitmapMem
, TAG_NTFS
);
3005 ReleaseAttributeContext(BitmapContext
);
3006 ReleaseAttributeContext(IndexAllocationContext
);
3010 // Initialize bitmap
3011 RtlInitializeBitMap(&Bitmap
, BitmapPtr
, BitmapLength
* 8);
3015 // Couldn't find an index allocation
3016 IndexAllocationContext
= NULL
;
3020 // Loop through all Index Entries of index, starting with FirstEntry
3021 IndexEntry
= FirstEntry
;
3022 while (IndexEntry
<= LastEntry
)
3024 // Does IndexEntry have a sub-node?
3025 if (IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
3027 if (!(IndexRecord
->Header
.Flags
& INDEX_ROOT_LARGE
) || !IndexAllocationContext
)
3029 DPRINT1("Filesystem corruption detected!\n");
3033 Status
= BrowseSubNodeIndexEntries(Vcb
,
3037 IndexAllocationContext
,
3039 GetIndexEntryVCN(IndexEntry
),
3045 if (NT_SUCCESS(Status
))
3047 ExFreePoolWithTag(BitmapMem
, TAG_NTFS
);
3048 ReleaseAttributeContext(BitmapContext
);
3049 ReleaseAttributeContext(IndexAllocationContext
);
3056 if (IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
)
3059 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
3060 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) >= NTFS_FILE_FIRST_USER_FILE
&&
3061 *CurrentEntry
>= *StartEntry
&&
3062 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
3063 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
3065 *StartEntry
= *CurrentEntry
;
3066 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
3067 if (IndexAllocationContext
)
3069 ExFreePoolWithTag(BitmapMem
, TAG_NTFS
);
3070 ReleaseAttributeContext(BitmapContext
);
3071 ReleaseAttributeContext(IndexAllocationContext
);
3073 return STATUS_SUCCESS
;
3076 // Advance to the next index entry
3077 (*CurrentEntry
) += 1;
3078 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
3079 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
3082 if (IndexAllocationContext
)
3084 ExFreePoolWithTag(BitmapMem
, TAG_NTFS
);
3085 ReleaseAttributeContext(BitmapContext
);
3086 ReleaseAttributeContext(IndexAllocationContext
);
3089 return STATUS_OBJECT_PATH_NOT_FOUND
;
3093 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
3095 PUNICODE_STRING FileName
,
3098 BOOLEAN CaseSensitive
,
3099 ULONGLONG
*OutMFTIndex
)
3101 PFILE_RECORD_HEADER MftRecord
;
3102 PNTFS_ATTR_CONTEXT IndexRootCtx
;
3103 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
3105 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
3107 ULONG CurrentEntry
= 0;
3109 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
3114 DirSearch
? "TRUE" : "FALSE",
3115 CaseSensitive
? "TRUE" : "FALSE",
3118 MftRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
3119 if (MftRecord
== NULL
)
3121 return STATUS_INSUFFICIENT_RESOURCES
;
3124 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
3125 if (!NT_SUCCESS(Status
))
3127 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
3131 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
3132 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
3133 if (!NT_SUCCESS(Status
))
3135 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
3139 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
3140 if (IndexRecord
== NULL
)
3142 ReleaseAttributeContext(IndexRootCtx
);
3143 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
3144 return STATUS_INSUFFICIENT_RESOURCES
;
3147 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
3148 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
3149 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
3150 /* Index root is always resident. */
3151 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
3152 ReleaseAttributeContext(IndexRootCtx
);
3154 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
3156 Status
= BrowseIndexEntries(Vcb
,
3158 (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
,
3159 IndexRoot
->SizeOfEntry
,
3169 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
3170 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, MftRecord
);
3176 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
3177 PUNICODE_STRING PathName
,
3178 BOOLEAN CaseSensitive
,
3179 PFILE_RECORD_HEADER
*FileRecord
,
3180 PULONGLONG MFTIndex
,
3181 ULONGLONG CurrentMFTIndex
)
3183 UNICODE_STRING Current
, Remaining
;
3185 ULONG FirstEntry
= 0;
3187 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
3190 CaseSensitive
? "TRUE" : "FALSE",
3195 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
3197 while (Current
.Length
!= 0)
3199 DPRINT("Current: %wZ\n", &Current
);
3201 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, CaseSensitive
, &CurrentMFTIndex
);
3202 if (!NT_SUCCESS(Status
))
3207 if (Remaining
.Length
== 0)
3210 FsRtlDissectName(Current
, &Current
, &Remaining
);
3213 *FileRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
3214 if (*FileRecord
== NULL
)
3216 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
3217 return STATUS_INSUFFICIENT_RESOURCES
;
3220 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
3221 if (!NT_SUCCESS(Status
))
3223 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
3224 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, *FileRecord
);
3228 *MFTIndex
= CurrentMFTIndex
;
3230 return STATUS_SUCCESS
;
3234 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
3235 PUNICODE_STRING PathName
,
3236 BOOLEAN CaseSensitive
,
3237 PFILE_RECORD_HEADER
*FileRecord
,
3238 PULONGLONG MFTIndex
)
3240 return NtfsLookupFileAt(Vcb
, PathName
, CaseSensitive
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
3244 NtfsDumpData(ULONG_PTR Buffer
, ULONG Length
)
3248 // dump binary data, 8 bytes at a time
3249 for (i
= 0; i
< Length
; i
+= 8)
3251 // display current offset, in hex
3252 DbgPrint("\t%03x\t", i
);
3254 // display hex value of each of the next 8 bytes
3255 for (j
= 0; j
< 8; j
++)
3256 DbgPrint("%02x ", *(PUCHAR
)(Buffer
+ i
+ j
));
3262 * @name NtfsDumpFileRecord
3265 * Provides diagnostic information about a file record. Prints a hex dump
3266 * of the entire record (based on the size reported by FileRecord->ByesInUse),
3267 * then prints a dump of each attribute.
3270 * Pointer to a DEVICE_EXTENSION describing the volume.
3273 * Pointer to the file record to be analyzed.
3276 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
3277 * in size, and not just the header.
3281 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
3282 PFILE_RECORD_HEADER FileRecord
)
3286 // dump binary data, 8 bytes at a time
3287 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
3289 // display current offset, in hex
3290 DbgPrint("\t%03x\t", i
);
3292 // display hex value of each of the next 8 bytes
3293 for (j
= 0; j
< 8; j
++)
3294 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
3298 NtfsDumpFileAttributes(Vcb
, FileRecord
);
3302 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
3303 PUNICODE_STRING SearchPattern
,
3305 PFILE_RECORD_HEADER
*FileRecord
,
3306 PULONGLONG MFTIndex
,
3307 ULONGLONG CurrentMFTIndex
,
3308 BOOLEAN CaseSensitive
)
3312 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
3319 (CaseSensitive
? "TRUE" : "FALSE"));
3321 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, CaseSensitive
, &CurrentMFTIndex
);
3322 if (!NT_SUCCESS(Status
))
3324 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
3328 *FileRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
3329 if (*FileRecord
== NULL
)
3331 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
3332 return STATUS_INSUFFICIENT_RESOURCES
;
3335 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
3336 if (!NT_SUCCESS(Status
))
3338 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
3339 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, *FileRecord
);
3343 *MFTIndex
= CurrentMFTIndex
;
3345 return STATUS_SUCCESS
;