3 * Copyright (C) 2002, 2014 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
30 /* INCLUDES *****************************************************************/
37 /* FUNCTIONS ****************************************************************/
40 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
42 PNTFS_ATTR_CONTEXT Context
;
44 Context
= ExAllocatePoolWithTag(NonPagedPool
,
45 FIELD_OFFSET(NTFS_ATTR_CONTEXT
, Record
) + AttrRecord
->Length
,
47 RtlCopyMemory(&Context
->Record
, AttrRecord
, AttrRecord
->Length
);
48 if (AttrRecord
->IsNonResident
)
50 LONGLONG DataRunOffset
;
51 ULONGLONG DataRunLength
;
52 ULONGLONG NextVBN
= 0;
53 PUCHAR DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
55 Context
->CacheRun
= DataRun
;
56 Context
->CacheRunOffset
= 0;
57 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
58 Context
->CacheRunLength
= DataRunLength
;
59 if (DataRunOffset
!= -1)
62 Context
->CacheRunStartLCN
=
63 Context
->CacheRunLastLCN
= DataRunOffset
;
68 Context
->CacheRunStartLCN
= -1;
69 Context
->CacheRunLastLCN
= 0;
71 Context
->CacheRunCurrentOffset
= 0;
73 // Convert the data runs to a map control block
74 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun
, &Context
->DataRunsMCB
, &NextVBN
)))
76 DPRINT1("Unable to convert data runs to MCB!\n");
77 ExFreePoolWithTag(Context
, TAG_NTFS
);
87 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
89 if (Context
->Record
.IsNonResident
)
91 FsRtlUninitializeLargeMcb(&Context
->DataRunsMCB
);
94 ExFreePoolWithTag(Context
, TAG_NTFS
);
102 * Searches a file record for an attribute matching the given type and name.
105 * Optional pointer to a ULONG that will receive the offset of the found attribute
106 * from the beginning of the record. Can be set to NULL.
109 FindAttribute(PDEVICE_EXTENSION Vcb
,
110 PFILE_RECORD_HEADER MftRecord
,
114 PNTFS_ATTR_CONTEXT
* AttrCtx
,
119 FIND_ATTR_CONTXT Context
;
120 PNTFS_ATTR_RECORD Attribute
;
122 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
);
125 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
126 while (NT_SUCCESS(Status
))
128 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
134 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
135 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
136 if (RtlCompareMemory(AttrName
, Name
, NameLength
<< 1) == (NameLength
<< 1))
148 /* Found it, fill up the context and return. */
149 DPRINT("Found context\n");
150 *AttrCtx
= PrepareAttributeContext(Attribute
);
152 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
155 *Offset
= Context
.Offset
;
157 FindCloseAttribute(&Context
);
158 return STATUS_SUCCESS
;
162 Status
= FindNextAttribute(&Context
, &Attribute
);
165 FindCloseAttribute(&Context
);
166 return STATUS_OBJECT_NAME_NOT_FOUND
;
171 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
173 if (AttrRecord
->IsNonResident
)
174 return AttrRecord
->NonResident
.AllocatedSize
;
176 return AttrRecord
->Resident
.ValueLength
;
181 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
183 if (AttrRecord
->IsNonResident
)
184 return AttrRecord
->NonResident
.DataSize
;
186 return AttrRecord
->Resident
.ValueLength
;
190 * @name IncreaseMftSize
193 * Increases the size of the master file table on a volume, increasing the space available for file records.
196 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
199 * STATUS_SUCCESS on success.
200 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
201 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
204 * Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
205 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
206 * This function will wait for exlusive access to the volume fcb.
209 IncreaseMftSize(PDEVICE_EXTENSION Vcb
)
211 PNTFS_ATTR_CONTEXT BitmapContext
;
212 LARGE_INTEGER BitmapSize
;
213 LARGE_INTEGER DataSize
;
214 LONGLONG BitmapSizeDifference
;
215 ULONG DataSizeDifference
= Vcb
->NtfsInfo
.BytesPerFileRecord
* 8;
218 ULONGLONG BitmapBytes
;
219 ULONGLONG NewBitmapSize
;
224 DPRINT1("IncreaseMftSize(%p)\n", Vcb
);
226 // We need exclusive access to the mft while we change its size
227 if (!ExAcquireResourceExclusiveLite(&(Vcb
->DirResource
), TRUE
))
229 return STATUS_CANT_WAIT
;
232 // Find the bitmap attribute of master file table
233 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, &BitmapOffset
);
234 if (!NT_SUCCESS(Status
))
236 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
237 ExReleaseResourceLite(&(Vcb
->DirResource
));
241 // Get size of Bitmap Attribute
242 BitmapSize
.QuadPart
= AttributeDataLength(&BitmapContext
->Record
);
244 // Calculate the new mft size
245 DataSize
.QuadPart
= AttributeDataLength(&(Vcb
->MFTContext
->Record
)) + DataSizeDifference
;
247 // Determine how many bytes will make up the bitmap
248 BitmapBytes
= DataSize
.QuadPart
/ Vcb
->NtfsInfo
.BytesPerFileRecord
/ 8;
250 // Determine how much we need to adjust the bitmap size (it's possible we don't)
251 BitmapSizeDifference
= BitmapBytes
- BitmapSize
.QuadPart
;
252 NewBitmapSize
= max(BitmapSize
.QuadPart
+ BitmapSizeDifference
, BitmapSize
.QuadPart
);
254 // Allocate memory for the bitmap
255 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, NewBitmapSize
, TAG_NTFS
);
258 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
259 ExReleaseResourceLite(&(Vcb
->DirResource
));
260 ReleaseAttributeContext(BitmapContext
);
261 return STATUS_INSUFFICIENT_RESOURCES
;
264 // Zero the bytes we'll be adding
265 RtlZeroMemory((PUCHAR
)((ULONG_PTR
)BitmapBuffer
), NewBitmapSize
);
267 // Read the bitmap attribute
268 BytesRead
= ReadAttribute(Vcb
,
273 if (BytesRead
!= BitmapSize
.LowPart
)
275 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
276 ExReleaseResourceLite(&(Vcb
->DirResource
));
277 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
278 ReleaseAttributeContext(BitmapContext
);
279 return STATUS_INVALID_PARAMETER
;
282 // Increase the mft size
283 Status
= SetNonResidentAttributeDataLength(Vcb
, Vcb
->MFTContext
, Vcb
->MftDataOffset
, Vcb
->MasterFileTable
, &DataSize
);
284 if (!NT_SUCCESS(Status
))
286 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
287 ExReleaseResourceLite(&(Vcb
->DirResource
));
288 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
289 ReleaseAttributeContext(BitmapContext
);
293 // If the bitmap grew
294 if (BitmapSizeDifference
> 0)
296 // Set the new bitmap size
297 BitmapSize
.QuadPart
+= BitmapSizeDifference
;
298 if (BitmapContext
->Record
.IsNonResident
)
299 Status
= SetNonResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
301 Status
= SetResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
303 if (!NT_SUCCESS(Status
))
305 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
306 ExReleaseResourceLite(&(Vcb
->DirResource
));
307 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
308 ReleaseAttributeContext(BitmapContext
);
313 //NtfsDumpFileAttributes(Vcb, FileRecord);
315 // Update the file record with the new attribute sizes
316 Status
= UpdateFileRecord(Vcb
, Vcb
->VolumeFcb
->MFTIndex
, Vcb
->MasterFileTable
);
317 if (!NT_SUCCESS(Status
))
319 DPRINT1("ERROR: Failed to update $MFT file record!\n");
320 ExReleaseResourceLite(&(Vcb
->DirResource
));
321 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
322 ReleaseAttributeContext(BitmapContext
);
326 // Write out the new bitmap
327 Status
= WriteAttribute(Vcb
, BitmapContext
, BitmapOffset
, BitmapBuffer
, BitmapSize
.LowPart
, &LengthWritten
);
328 if (!NT_SUCCESS(Status
))
330 ExReleaseResourceLite(&(Vcb
->DirResource
));
331 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
332 ReleaseAttributeContext(BitmapContext
);
333 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
337 ExReleaseResourceLite(&(Vcb
->DirResource
));
338 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
339 ReleaseAttributeContext(BitmapContext
);
341 return STATUS_SUCCESS
;
345 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext
,
346 PFILE_RECORD_HEADER FileRecord
,
350 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
351 ULONG NextAttributeOffset
;
353 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext
, FileRecord
, AttrOffset
, DataSize
);
355 // update ValueLength Field
356 AttrContext
->Record
.Resident
.ValueLength
=
357 Destination
->Resident
.ValueLength
= DataSize
;
359 // calculate the record length and end marker offset
360 AttrContext
->Record
.Length
=
361 Destination
->Length
= DataSize
+ AttrContext
->Record
.Resident
.ValueOffset
;
362 NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
364 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
365 if (NextAttributeOffset
% 8 != 0)
367 USHORT Padding
= 8 - (NextAttributeOffset
% 8);
368 NextAttributeOffset
+= Padding
;
369 AttrContext
->Record
.Length
+= Padding
;
370 Destination
->Length
+= Padding
;
373 // advance Destination to the final "attribute" and set the file record end
374 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
375 SetFileRecordEnd(FileRecord
, Destination
, FILE_RECORD_END
);
379 * @parameter FileRecord
380 * Pointer to a file record. Must be a full record at least
381 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
384 SetAttributeDataLength(PFILE_OBJECT FileObject
,
386 PNTFS_ATTR_CONTEXT AttrContext
,
388 PFILE_RECORD_HEADER FileRecord
,
389 PLARGE_INTEGER DataSize
)
391 NTSTATUS Status
= STATUS_SUCCESS
;
393 // are we truncating the file?
394 if (DataSize
->QuadPart
< AttributeDataLength(&AttrContext
->Record
))
396 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
398 DPRINT1("Can't truncate a memory-mapped file!\n");
399 return STATUS_USER_MAPPED_FILE
;
403 if (AttrContext
->Record
.IsNonResident
)
405 Status
= SetNonResidentAttributeDataLength(Fcb
->Vcb
,
413 // resident attribute
414 Status
= SetResidentAttributeDataLength(Fcb
->Vcb
,
421 if (!NT_SUCCESS(Status
))
423 DPRINT1("ERROR: Failed to set size of attribute!\n");
427 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
429 // write the updated file record back to disk
430 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
432 if (NT_SUCCESS(Status
))
434 if(AttrContext
->Record
.IsNonResident
)
435 Fcb
->RFCB
.AllocationSize
.QuadPart
= AttrContext
->Record
.NonResident
.AllocatedSize
;
437 Fcb
->RFCB
.AllocationSize
= *DataSize
;
438 Fcb
->RFCB
.FileSize
= *DataSize
;
439 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
440 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
443 return STATUS_SUCCESS
;
447 * @name SetFileRecordEnd
450 * This small function sets a new endpoint for the file record. It set's the final
451 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
454 * Pointer to the file record whose endpoint (length) will be set.
457 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
458 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
461 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
462 * a file record, it preserves the final ULONG that previously ended the record, even though this
463 * value is (to my knowledge) never used. We emulate this behavior.
467 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
468 PNTFS_ATTR_RECORD AttrEnd
,
471 // mark the end of attributes
472 AttrEnd
->Type
= AttributeEnd
;
474 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
475 AttrEnd
->Length
= EndMarker
;
477 // recalculate bytes in use
478 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
482 * @name SetNonResidentAttributeDataLength
485 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
488 * Pointer to a DEVICE_EXTENSION describing the target disk.
491 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
494 * Offset, from the beginning of the record, of the attribute being sized.
497 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
498 * not just the header.
501 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
504 * STATUS_SUCCESS on success;
505 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
506 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
509 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
510 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
511 * any associated files. Synchronization is the callers responsibility.
514 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
515 PNTFS_ATTR_CONTEXT AttrContext
,
517 PFILE_RECORD_HEADER FileRecord
,
518 PLARGE_INTEGER DataSize
)
520 NTSTATUS Status
= STATUS_SUCCESS
;
521 ULONG BytesPerCluster
= Vcb
->NtfsInfo
.BytesPerCluster
;
522 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
523 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
524 ULONG ExistingClusters
= AttrContext
->Record
.NonResident
.AllocatedSize
/ BytesPerCluster
;
526 if (!AttrContext
->Record
.IsNonResident
)
528 DPRINT1("ERROR: SetNonResidentAttributeDataLength() called for resident attribute!\n");
529 return STATUS_INVALID_PARAMETER
;
532 // do we need to increase the allocation size?
533 if (AttrContext
->Record
.NonResident
.AllocatedSize
< AllocationSize
)
535 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
536 LARGE_INTEGER LastClusterInDataRun
;
537 ULONG NextAssignedCluster
;
538 ULONG AssignedClusters
;
540 if (ExistingClusters
== 0)
542 LastClusterInDataRun
.QuadPart
= 0;
546 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
547 (LONGLONG
)AttrContext
->Record
.NonResident
.HighestVCN
,
548 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
554 DPRINT1("Error looking up final large MCB entry!\n");
556 // Most likely, HighestVCN went above the largest mapping
557 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
558 return STATUS_INVALID_PARAMETER
;
562 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
563 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
565 while (ClustersNeeded
> 0)
567 Status
= NtfsAllocateClusters(Vcb
,
568 LastClusterInDataRun
.LowPart
+ 1,
570 &NextAssignedCluster
,
573 if (!NT_SUCCESS(Status
))
575 DPRINT1("Error: Unable to allocate requested clusters!\n");
579 // now we need to add the clusters we allocated to the data run
580 Status
= AddRun(Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
581 if (!NT_SUCCESS(Status
))
583 DPRINT1("Error: Unable to add data run!\n");
587 ClustersNeeded
-= AssignedClusters
;
588 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
591 else if (AttrContext
->Record
.NonResident
.AllocatedSize
> AllocationSize
)
593 // shrink allocation size
594 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
595 Status
= FreeClusters(Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
598 // TODO: is the file compressed, encrypted, or sparse?
600 AttrContext
->Record
.NonResident
.AllocatedSize
= AllocationSize
;
601 AttrContext
->Record
.NonResident
.DataSize
= DataSize
->QuadPart
;
602 AttrContext
->Record
.NonResident
.InitializedSize
= DataSize
->QuadPart
;
604 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
605 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
606 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
608 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
614 * @name SetResidentAttributeDataLength
617 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
620 * Pointer to a DEVICE_EXTENSION describing the target disk.
623 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
626 * Offset, from the beginning of the record, of the attribute being sized.
629 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
630 * not just the header.
633 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
636 * STATUS_SUCCESS on success;
637 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
638 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
639 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
640 * last attribute listed in the file record.
643 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
644 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
645 * any associated files. Synchronization is the callers responsibility.
648 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
649 PNTFS_ATTR_CONTEXT AttrContext
,
651 PFILE_RECORD_HEADER FileRecord
,
652 PLARGE_INTEGER DataSize
)
656 // find the next attribute
657 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
658 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
660 if (AttrContext
->Record
.IsNonResident
)
662 DPRINT1("ERROR: SetResidentAttributeDataLength() called for non-resident attribute!\n");
663 return STATUS_INVALID_PARAMETER
;
666 //NtfsDumpFileAttributes(Vcb, FileRecord);
668 // Do we need to increase the data length?
669 if (DataSize
->QuadPart
> AttrContext
->Record
.Resident
.ValueLength
)
671 // There's usually padding at the end of a record. Do we need to extend past it?
672 ULONG MaxValueLength
= AttrContext
->Record
.Length
- AttrContext
->Record
.Resident
.ValueOffset
;
673 if (MaxValueLength
< DataSize
->LowPart
)
675 // If this is the last attribute, we could move the end marker to the very end of the file record
676 MaxValueLength
+= Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
678 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
680 // convert attribute to non-resident
681 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
682 LARGE_INTEGER AttribDataSize
;
684 ULONG EndAttributeOffset
;
687 DPRINT1("Converting attribute to non-resident.\n");
689 AttribDataSize
.QuadPart
= AttrContext
->Record
.Resident
.ValueLength
;
691 // Is there existing data we need to back-up?
692 if (AttribDataSize
.QuadPart
> 0)
694 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
695 if (AttribData
== NULL
)
697 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
698 return STATUS_INSUFFICIENT_RESOURCES
;
701 // read data to temp buffer
702 Status
= ReadAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
703 if (!NT_SUCCESS(Status
))
705 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
706 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
711 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
713 // Zero out the NonResident structure
714 RtlZeroMemory(&AttrContext
->Record
.NonResident
.LowestVCN
,
715 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
716 RtlZeroMemory(&Destination
->NonResident
.LowestVCN
,
717 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
719 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
720 AttrContext
->Record
.NonResident
.MappingPairsOffset
= Destination
->NonResident
.MappingPairsOffset
= 0x40 + (Destination
->NameLength
* 2);
722 // mark the attribute as non-resident
723 AttrContext
->Record
.IsNonResident
= Destination
->IsNonResident
= 1;
725 // update the end of the file record
726 // calculate position of end markers (1 byte for empty data run)
727 EndAttributeOffset
= AttrOffset
+ AttrContext
->Record
.NonResident
.MappingPairsOffset
+ 1;
728 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, 8);
731 Destination
->Length
= EndAttributeOffset
- AttrOffset
;
732 AttrContext
->Record
.Length
= Destination
->Length
;
734 // Update the file record end
735 SetFileRecordEnd(FileRecord
,
736 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
739 // update file record on disk
740 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
741 if (!NT_SUCCESS(Status
))
743 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
744 if (AttribDataSize
.QuadPart
> 0)
745 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
749 // Initialize the MCB, potentially catch an exception
751 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
752 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
753 _SEH2_YIELD(return _SEH2_GetExceptionCode());
756 // Now we can treat the attribute as non-resident and enlarge it normally
757 Status
= SetNonResidentAttributeDataLength(Vcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
758 if (!NT_SUCCESS(Status
))
760 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
761 if (AttribDataSize
.QuadPart
> 0)
762 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
766 // restore the back-up attribute, if we made one
767 if (AttribDataSize
.QuadPart
> 0)
769 Status
= WriteAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
);
770 if (!NT_SUCCESS(Status
))
772 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
773 // TODO: Reverse migration so no data is lost
774 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
778 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
783 else if (DataSize
->LowPart
< AttrContext
->Record
.Resident
.ValueLength
)
785 // we need to decrease the length
786 if (NextAttribute
->Type
!= AttributeEnd
)
788 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
789 return STATUS_NOT_IMPLEMENTED
;
793 // set the new length of the resident attribute (if we didn't migrate it)
794 if (!AttrContext
->Record
.IsNonResident
)
795 InternalSetResidentAttributeLength(AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
797 return STATUS_SUCCESS
;
801 ReadAttribute(PDEVICE_EXTENSION Vcb
,
802 PNTFS_ATTR_CONTEXT Context
,
809 LONGLONG DataRunOffset
;
810 ULONGLONG DataRunLength
;
811 LONGLONG DataRunStartLCN
;
812 ULONGLONG CurrentOffset
;
820 if (!Context
->Record
.IsNonResident
)
822 if (Offset
> Context
->Record
.Resident
.ValueLength
)
824 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
825 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
826 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
831 * Non-resident attribute
835 * I. Find the corresponding start data run.
840 // FIXME: Cache seems to be non-working. Disable it for now
841 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
844 DataRun
= Context
->CacheRun
;
845 LastLCN
= Context
->CacheRunLastLCN
;
846 DataRunStartLCN
= Context
->CacheRunStartLCN
;
847 DataRunLength
= Context
->CacheRunLength
;
848 CurrentOffset
= Context
->CacheRunCurrentOffset
;
853 ULONG UsedBufferSize
;
854 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
859 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
860 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
862 Vcb
->NtfsInfo
.BytesPerFileRecord
,
865 DataRun
= TempBuffer
;
869 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
870 if (DataRunOffset
!= -1)
872 /* Normal data run. */
873 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
874 LastLCN
= DataRunStartLCN
;
878 /* Sparse data run. */
879 DataRunStartLCN
= -1;
882 if (Offset
>= CurrentOffset
&&
883 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
893 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
898 * II. Go through the run list and read the data
901 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
902 if (DataRunStartLCN
== -1)
904 RtlZeroMemory(Buffer
, ReadLength
);
905 Status
= STATUS_SUCCESS
;
909 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
910 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
912 Vcb
->NtfsInfo
.BytesPerSector
,
916 if (NT_SUCCESS(Status
))
918 Length
-= ReadLength
;
919 Buffer
+= ReadLength
;
920 AlreadyRead
+= ReadLength
;
922 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
924 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
925 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
926 if (DataRunOffset
!= (ULONGLONG
)-1)
928 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
929 LastLCN
= DataRunStartLCN
;
932 DataRunStartLCN
= -1;
937 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
938 if (DataRunStartLCN
== -1)
939 RtlZeroMemory(Buffer
, ReadLength
);
942 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
943 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
945 Vcb
->NtfsInfo
.BytesPerSector
,
948 if (!NT_SUCCESS(Status
))
952 Length
-= ReadLength
;
953 Buffer
+= ReadLength
;
954 AlreadyRead
+= ReadLength
;
956 /* We finished this request, but there still data in this data run. */
957 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
961 * Go to next run in the list.
966 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
967 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
968 if (DataRunOffset
!= -1)
970 /* Normal data run. */
971 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
972 LastLCN
= DataRunStartLCN
;
976 /* Sparse data run. */
977 DataRunStartLCN
= -1;
984 if (Context
->Record
.IsNonResident
)
985 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
987 Context
->CacheRun
= DataRun
;
988 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
989 Context
->CacheRunStartLCN
= DataRunStartLCN
;
990 Context
->CacheRunLength
= DataRunLength
;
991 Context
->CacheRunLastLCN
= LastLCN
;
992 Context
->CacheRunCurrentOffset
= CurrentOffset
;
999 * @name WriteAttribute
1002 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1003 * and it still needs more documentation / cleaning up.
1006 * Volume Control Block indicating which volume to write the attribute to
1009 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1012 * Offset, in bytes, from the beginning of the attribute indicating where to start
1016 * The data that's being written to the device
1019 * How much data will be written, in bytes
1021 * @param RealLengthWritten
1022 * Pointer to a ULONG which will receive how much data was written, in bytes
1025 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1026 * writing to a sparse file.
1028 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1029 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1034 WriteAttribute(PDEVICE_EXTENSION Vcb
,
1035 PNTFS_ATTR_CONTEXT Context
,
1037 const PUCHAR Buffer
,
1039 PULONG RealLengthWritten
)
1043 LONGLONG DataRunOffset
;
1044 ULONGLONG DataRunLength
;
1045 LONGLONG DataRunStartLCN
;
1046 ULONGLONG CurrentOffset
;
1049 PUCHAR SourceBuffer
= Buffer
;
1050 LONGLONG StartingOffset
;
1056 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
);
1058 *RealLengthWritten
= 0;
1060 // is this a resident attribute?
1061 if (!Context
->Record
.IsNonResident
)
1063 ULONG AttributeOffset
;
1064 PNTFS_ATTR_CONTEXT FoundContext
;
1065 PFILE_RECORD_HEADER FileRecord
;
1067 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
1069 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1070 return STATUS_INVALID_PARAMETER
;
1073 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1077 DPRINT1("Error: Couldn't allocate file record!\n");
1078 return STATUS_NO_MEMORY
;
1081 // read the file record
1082 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1084 // find where to write the attribute data to
1085 Status
= FindAttribute(Vcb
, FileRecord
,
1086 Context
->Record
.Type
,
1087 (PCWSTR
)((PCHAR
)&Context
->Record
+ Context
->Record
.NameOffset
),
1088 Context
->Record
.NameLength
,
1092 if (!NT_SUCCESS(Status
))
1094 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1095 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1099 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->Record
.Resident
.ValueLength
);
1100 Offset
+= AttributeOffset
+ Context
->Record
.Resident
.ValueOffset
;
1102 if (Offset
+ Length
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
1104 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1105 ReleaseAttributeContext(FoundContext
);
1106 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1107 return STATUS_INVALID_PARAMETER
;
1110 // copy the data being written into the file record
1111 RtlCopyMemory((PCHAR
)FileRecord
+ Offset
, Buffer
, Length
);
1113 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1115 ReleaseAttributeContext(FoundContext
);
1116 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1118 if (NT_SUCCESS(Status
))
1119 *RealLengthWritten
= Length
;
1124 // This is a non-resident attribute.
1126 // I. Find the corresponding start data run.
1128 // FIXME: Cache seems to be non-working. Disable it for now
1129 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1132 DataRun = Context->CacheRun;
1133 LastLCN = Context->CacheRunLastLCN;
1134 DataRunStartLCN = Context->CacheRunStartLCN;
1135 DataRunLength = Context->CacheRunLength;
1136 CurrentOffset = Context->CacheRunCurrentOffset;
1140 ULONG UsedBufferSize
;
1144 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1145 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1147 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1149 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1152 DataRun
= TempBuffer
;
1156 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1157 if (DataRunOffset
!= -1)
1160 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1161 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1162 LastLCN
= DataRunStartLCN
;
1166 // Sparse data run. We can't support writing to sparse files yet
1167 // (it may require increasing the allocation size).
1168 DataRunStartLCN
= -1;
1169 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1170 return STATUS_NOT_IMPLEMENTED
;
1173 // Have we reached the data run we're trying to write to?
1174 if (Offset
>= CurrentOffset
&&
1175 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1182 // We reached the last assigned cluster
1183 // TODO: assign new clusters to the end of the file.
1184 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1185 // [We can reach here by creating a new file record when the MFT isn't large enough]
1186 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1187 return STATUS_END_OF_FILE
;
1190 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1194 // II. Go through the run list and write the data
1196 /* REVIEWME -- As adapted from NtfsReadAttribute():
1197 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1198 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1200 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1202 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
1204 // Write the data to the disk
1205 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1208 Vcb
->NtfsInfo
.BytesPerSector
,
1209 (PVOID
)SourceBuffer
);
1211 // Did the write fail?
1212 if (!NT_SUCCESS(Status
))
1214 Context
->CacheRun
= DataRun
;
1215 Context
->CacheRunOffset
= Offset
;
1216 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1217 Context
->CacheRunLength
= DataRunLength
;
1218 Context
->CacheRunLastLCN
= LastLCN
;
1219 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1224 Length
-= WriteLength
;
1225 SourceBuffer
+= WriteLength
;
1226 *RealLengthWritten
+= WriteLength
;
1228 // Did we write to the end of the data run?
1229 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1231 // Advance to the next data run
1232 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1233 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1235 if (DataRunOffset
!= (ULONGLONG
)-1)
1237 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1238 LastLCN
= DataRunStartLCN
;
1241 DataRunStartLCN
= -1;
1244 // Do we have more data to write?
1247 // Make sure we don't write past the end of the current data run
1248 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1250 // Are we dealing with a sparse data run?
1251 if (DataRunStartLCN
== -1)
1253 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1254 return STATUS_NOT_IMPLEMENTED
;
1258 // write the data to the disk
1259 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1260 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1262 Vcb
->NtfsInfo
.BytesPerSector
,
1263 (PVOID
)SourceBuffer
);
1264 if (!NT_SUCCESS(Status
))
1268 Length
-= WriteLength
;
1269 SourceBuffer
+= WriteLength
;
1270 *RealLengthWritten
+= WriteLength
;
1272 // We finished this request, but there's still data in this data run.
1273 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1276 // Go to next run in the list.
1280 // that was the last run
1283 // Failed sanity check.
1284 DPRINT1("Encountered EOF before expected!\n");
1285 return STATUS_END_OF_FILE
;
1291 // Advance to the next data run
1292 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1293 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1294 if (DataRunOffset
!= -1)
1297 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1298 LastLCN
= DataRunStartLCN
;
1303 DataRunStartLCN
= -1;
1305 } // end while (Length > 0) [more data to write]
1308 if(Context
->Record
.IsNonResident
)
1309 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1311 Context
->CacheRun
= DataRun
;
1312 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1313 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1314 Context
->CacheRunLength
= DataRunLength
;
1315 Context
->CacheRunLastLCN
= LastLCN
;
1316 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1322 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1324 PFILE_RECORD_HEADER file
)
1326 ULONGLONG BytesRead
;
1328 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1330 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1331 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1333 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1334 return STATUS_PARTIAL_COPY
;
1337 /* Apply update sequence array fixups. */
1338 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1339 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1344 * Searches a file's parent directory (given the parent's index in the mft)
1345 * for the given file. Upon finding an index entry for that file, updates
1346 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1348 * (Most of this code was copied from NtfsFindMftRecord)
1351 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1352 ULONGLONG ParentMFTIndex
,
1353 PUNICODE_STRING FileName
,
1355 ULONGLONG NewDataSize
,
1356 ULONGLONG NewAllocationSize
)
1358 PFILE_RECORD_HEADER MftRecord
;
1359 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1360 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1362 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1364 ULONG CurrentEntry
= 0;
1366 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb
, ParentMFTIndex
, FileName
, DirSearch
, NewDataSize
, NewAllocationSize
);
1368 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1369 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1371 if (MftRecord
== NULL
)
1373 return STATUS_INSUFFICIENT_RESOURCES
;
1376 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1377 if (!NT_SUCCESS(Status
))
1379 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1383 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1384 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1385 if (!NT_SUCCESS(Status
))
1387 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1391 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1392 if (IndexRecord
== NULL
)
1394 ReleaseAttributeContext(IndexRootCtx
);
1395 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1396 return STATUS_INSUFFICIENT_RESOURCES
;
1399 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1400 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1401 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1402 // Index root is always resident.
1403 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1405 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1407 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1410 IndexRoot
->SizeOfEntry
,
1420 ReleaseAttributeContext(IndexRootCtx
);
1421 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1422 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1428 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1429 * proper index entry.
1430 * (Heavily based on BrowseIndexEntries)
1433 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1434 PFILE_RECORD_HEADER MftRecord
,
1436 ULONG IndexBlockSize
,
1437 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1438 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1439 PUNICODE_STRING FileName
,
1441 PULONG CurrentEntry
,
1443 ULONGLONG NewDataSize
,
1444 ULONGLONG NewAllocatedSize
)
1448 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1449 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1450 ULONGLONG IndexAllocationSize
;
1451 PINDEX_BUFFER IndexBuffer
;
1453 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %I64u, %I64u)\n", Vcb
, MftRecord
, IndexRecord
, IndexBlockSize
, FirstEntry
, LastEntry
, FileName
, *StartEntry
, *CurrentEntry
, DirSearch
, NewDataSize
, NewAllocatedSize
);
1455 // find the index entry responsible for the file we're trying to update
1456 IndexEntry
= FirstEntry
;
1457 while (IndexEntry
< LastEntry
&&
1458 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1460 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1461 *CurrentEntry
>= *StartEntry
&&
1462 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1463 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1465 *StartEntry
= *CurrentEntry
;
1466 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1467 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1468 // indicate that the caller will still need to write the structure to the disk
1469 return STATUS_PENDING
;
1472 (*CurrentEntry
) += 1;
1473 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1474 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1477 /* If we're already browsing a subnode */
1478 if (IndexRecord
== NULL
)
1480 return STATUS_OBJECT_PATH_NOT_FOUND
;
1483 /* If there's no subnode */
1484 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1486 return STATUS_OBJECT_PATH_NOT_FOUND
;
1489 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1490 if (!NT_SUCCESS(Status
))
1492 DPRINT("Corrupted filesystem!\n");
1496 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1497 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1498 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1500 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1501 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1502 if (!NT_SUCCESS(Status
))
1507 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1508 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1509 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1510 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1511 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1512 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1514 Status
= UpdateIndexEntryFileNameSize(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, NewDataSize
, NewAllocatedSize
);
1515 if (Status
== STATUS_PENDING
)
1517 // write the index record back to disk
1520 // first we need to update the fixup values for the index block
1521 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1522 if (!NT_SUCCESS(Status
))
1524 DPRINT1("Error: Failed to update fixup sequence array!\n");
1528 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
);
1529 if (!NT_SUCCESS(Status
))
1531 DPRINT1("ERROR Performing write!\n");
1535 Status
= STATUS_SUCCESS
;
1538 if (NT_SUCCESS(Status
))
1544 ReleaseAttributeContext(IndexAllocationCtx
);
1549 * @name UpdateFileRecord
1552 * Writes a file record to the master file table, at a given index.
1555 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1558 * Target index in the master file table to store the file record.
1561 * Pointer to the complete file record which will be written to the master file table.
1564 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1568 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1570 PFILE_RECORD_HEADER FileRecord
)
1573 NTSTATUS Status
= STATUS_SUCCESS
;
1575 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1577 // Add the fixup array to prepare the data for writing to disk
1578 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1580 // write the file record to the master file table
1581 Status
= WriteAttribute(Vcb
, Vcb
->MFTContext
, MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (const PUCHAR
)FileRecord
, Vcb
->NtfsInfo
.BytesPerFileRecord
, &BytesWritten
);
1583 if (!NT_SUCCESS(Status
))
1585 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1588 // remove the fixup array (so the file record pointer can still be used)
1589 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1596 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1597 PNTFS_RECORD_HEADER Record
)
1604 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1605 USANumber
= *(USA
++);
1606 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1607 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1609 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1613 if (*Block
!= USANumber
)
1615 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1616 return STATUS_UNSUCCESSFUL
;
1619 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1623 return STATUS_SUCCESS
;
1627 * @name AddNewMftEntry
1630 * Adds a file record to the master file table of a given device.
1633 * Pointer to a complete file record which will be saved to disk.
1636 * Pointer to the DEVICE_EXTENSION of the target drive.
1638 * @param DestinationIndex
1639 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1642 * STATUS_SUCCESS on success.
1643 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1644 * to read the attribute.
1645 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1649 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1650 PDEVICE_EXTENSION DeviceExt
,
1651 PULONGLONG DestinationIndex
)
1653 NTSTATUS Status
= STATUS_SUCCESS
;
1656 ULONGLONG BitmapDataSize
;
1657 ULONGLONG AttrBytesRead
;
1659 ULONG LengthWritten
;
1660 PNTFS_ATTR_CONTEXT BitmapContext
;
1661 LARGE_INTEGER BitmapBits
;
1662 UCHAR SystemReservedBits
;
1664 DPRINT1("AddNewMftEntry(%p, %p, %p)\n", FileRecord
, DeviceExt
, DestinationIndex
);
1666 // First, we have to read the mft's $Bitmap attribute
1667 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1668 if (!NT_SUCCESS(Status
))
1670 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1674 // allocate a buffer for the $Bitmap attribute
1675 BitmapDataSize
= AttributeDataLength(&BitmapContext
->Record
);
1676 BitmapData
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
, TAG_NTFS
);
1679 ReleaseAttributeContext(BitmapContext
);
1680 return STATUS_INSUFFICIENT_RESOURCES
;
1683 // read $Bitmap attribute
1684 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
);
1686 if (AttrBytesRead
== 0)
1688 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1689 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1690 ReleaseAttributeContext(BitmapContext
);
1691 return STATUS_OBJECT_NAME_NOT_FOUND
;
1694 // we need to backup the bits for records 0x10 - 0x17 and leave them unassigned if they aren't assigned
1695 RtlCopyMemory(&SystemReservedBits
, (PVOID
)((ULONG_PTR
)BitmapData
+ 2), 1);
1696 RtlFillMemory((PVOID
)((ULONG_PTR
)BitmapData
+ 2), 1, (UCHAR
)0xFF);
1698 // Calculate bit count
1699 BitmapBits
.QuadPart
= AttributeDataLength(&(DeviceExt
->MFTContext
->Record
)) /
1700 DeviceExt
->NtfsInfo
.BytesPerFileRecord
;
1701 if (BitmapBits
.HighPart
!= 0)
1703 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported!\n");
1704 BitmapBits
.LowPart
= 0xFFFFFFFF;
1707 // convert buffer into bitmap
1708 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapBits
.LowPart
);
1710 // set next available bit, preferrably after 23rd bit
1711 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
1712 if ((LONG
)MftIndex
== -1)
1714 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1716 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1717 ReleaseAttributeContext(BitmapContext
);
1719 // Couldn't find a free record in the MFT, add some blank records and try again
1720 Status
= IncreaseMftSize(DeviceExt
);
1721 if (!NT_SUCCESS(Status
))
1723 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1727 return AddNewMftEntry(FileRecord
, DeviceExt
, DestinationIndex
);
1730 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
1732 // update file record with index
1733 FileRecord
->MFTRecordNumber
= MftIndex
;
1735 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1737 // Restore the system reserved bits
1738 RtlCopyMemory((PVOID
)((ULONG_PTR
)BitmapData
+ 2), &SystemReservedBits
, 1);
1740 // write the bitmap back to the MFT's $Bitmap attribute
1741 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
);
1742 if (!NT_SUCCESS(Status
))
1744 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1745 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1746 ReleaseAttributeContext(BitmapContext
);
1750 // update the file record (write it to disk)
1751 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
1753 if (!NT_SUCCESS(Status
))
1755 DPRINT1("ERROR: Unable to write file record!\n");
1756 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1757 ReleaseAttributeContext(BitmapContext
);
1761 *DestinationIndex
= MftIndex
;
1763 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1764 ReleaseAttributeContext(BitmapContext
);
1770 AddFixupArray(PDEVICE_EXTENSION Vcb
,
1771 PNTFS_RECORD_HEADER Record
)
1773 USHORT
*pShortToFixUp
;
1774 unsigned int ArrayEntryCount
= Record
->UsaCount
- 1;
1775 unsigned int Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
1778 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
1780 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
1784 for (i
= 0; i
< ArrayEntryCount
; i
++)
1786 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
1788 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
1789 fixupArray
->Array
[i
] = *pShortToFixUp
;
1790 *pShortToFixUp
= fixupArray
->USN
;
1791 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
1794 return STATUS_SUCCESS
;
1798 ReadLCN(PDEVICE_EXTENSION Vcb
,
1803 LARGE_INTEGER DiskSector
;
1805 DiskSector
.QuadPart
= lcn
;
1807 return NtfsReadSectors(Vcb
->StorageDevice
,
1808 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1809 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1810 Vcb
->NtfsInfo
.BytesPerSector
,
1817 CompareFileName(PUNICODE_STRING FileName
,
1818 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
1821 BOOLEAN Ret
, Alloc
= FALSE
;
1822 UNICODE_STRING EntryName
;
1824 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
1826 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
1830 UNICODE_STRING IntFileName
;
1831 if (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)
1833 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
1838 IntFileName
= *FileName
;
1841 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
), NULL
);
1845 RtlFreeUnicodeString(&IntFileName
);
1852 return (RtlCompareUnicodeString(FileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)) == 0);
1859 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
1861 DPRINT1("Entry: %p\n", IndexEntry
);
1862 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
1863 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
1864 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
1865 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
1866 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
1867 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
1868 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
1869 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
1870 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
1871 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
1872 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
1873 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
1874 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
1875 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
1876 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
1877 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
1882 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
1883 PFILE_RECORD_HEADER MftRecord
,
1885 ULONG IndexBlockSize
,
1886 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1887 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1888 PUNICODE_STRING FileName
,
1890 PULONG CurrentEntry
,
1892 ULONGLONG
*OutMFTIndex
)
1896 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1897 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1898 ULONGLONG IndexAllocationSize
;
1899 PINDEX_BUFFER IndexBuffer
;
1901 DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %p)\n", Vcb
, MftRecord
, IndexRecord
, IndexBlockSize
, FirstEntry
, LastEntry
, FileName
, *StartEntry
, *CurrentEntry
, DirSearch
, OutMFTIndex
);
1903 IndexEntry
= FirstEntry
;
1904 while (IndexEntry
< LastEntry
&&
1905 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1907 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) >= 0x10 &&
1908 *CurrentEntry
>= *StartEntry
&&
1909 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1910 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1912 *StartEntry
= *CurrentEntry
;
1913 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
1914 return STATUS_SUCCESS
;
1917 (*CurrentEntry
) += 1;
1918 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1919 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1922 /* If we're already browsing a subnode */
1923 if (IndexRecord
== NULL
)
1925 return STATUS_OBJECT_PATH_NOT_FOUND
;
1928 /* If there's no subnode */
1929 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1931 return STATUS_OBJECT_PATH_NOT_FOUND
;
1934 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1935 if (!NT_SUCCESS(Status
))
1937 DPRINT("Corrupted filesystem!\n");
1941 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1942 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1943 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1945 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1946 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1947 if (!NT_SUCCESS(Status
))
1952 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1953 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1954 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1955 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1956 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1957 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1959 Status
= BrowseIndexEntries(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, OutMFTIndex
);
1960 if (NT_SUCCESS(Status
))
1966 ReleaseAttributeContext(IndexAllocationCtx
);
1971 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
1973 PUNICODE_STRING FileName
,
1976 ULONGLONG
*OutMFTIndex
)
1978 PFILE_RECORD_HEADER MftRecord
;
1979 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1980 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1982 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1984 ULONG CurrentEntry
= 0;
1986 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb
, MFTIndex
, FileName
, *FirstEntry
, DirSearch
, OutMFTIndex
);
1988 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1989 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1991 if (MftRecord
== NULL
)
1993 return STATUS_INSUFFICIENT_RESOURCES
;
1996 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
1997 if (!NT_SUCCESS(Status
))
1999 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2003 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
2004 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
2005 if (!NT_SUCCESS(Status
))
2007 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2011 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
2012 if (IndexRecord
== NULL
)
2014 ReleaseAttributeContext(IndexRootCtx
);
2015 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2016 return STATUS_INSUFFICIENT_RESOURCES
;
2019 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
2020 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
2021 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
2022 /* Index root is always resident. */
2023 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
2024 ReleaseAttributeContext(IndexRootCtx
);
2026 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
2028 Status
= BrowseIndexEntries(Vcb
, MftRecord
, IndexRecord
, IndexRoot
->SizeOfEntry
, IndexEntry
, IndexEntryEnd
, FileName
, FirstEntry
, &CurrentEntry
, DirSearch
, OutMFTIndex
);
2030 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2031 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2037 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
2038 PUNICODE_STRING PathName
,
2039 PFILE_RECORD_HEADER
*FileRecord
,
2040 PULONGLONG MFTIndex
,
2041 ULONGLONG CurrentMFTIndex
)
2043 UNICODE_STRING Current
, Remaining
;
2045 ULONG FirstEntry
= 0;
2047 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb
, PathName
, FileRecord
, CurrentMFTIndex
);
2049 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
2051 while (Current
.Length
!= 0)
2053 DPRINT("Current: %wZ\n", &Current
);
2055 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, &CurrentMFTIndex
);
2056 if (!NT_SUCCESS(Status
))
2061 if (Remaining
.Length
== 0)
2064 FsRtlDissectName(Current
, &Current
, &Remaining
);
2067 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2068 if (*FileRecord
== NULL
)
2070 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2071 return STATUS_INSUFFICIENT_RESOURCES
;
2074 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2075 if (!NT_SUCCESS(Status
))
2077 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2078 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2082 *MFTIndex
= CurrentMFTIndex
;
2084 return STATUS_SUCCESS
;
2088 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
2089 PUNICODE_STRING PathName
,
2090 PFILE_RECORD_HEADER
*FileRecord
,
2091 PULONGLONG MFTIndex
)
2093 return NtfsLookupFileAt(Vcb
, PathName
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
2097 * @name NtfsDumpFileRecord
2100 * Provides diagnostic information about a file record. Prints a hex dump
2101 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2102 * then prints a dump of each attribute.
2105 * Pointer to a DEVICE_EXTENSION describing the volume.
2108 * Pointer to the file record to be analyzed.
2111 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2112 * in size, and not just the header.
2116 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
2117 PFILE_RECORD_HEADER FileRecord
)
2121 // dump binary data, 8 bytes at a time
2122 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
2124 // display current offset, in hex
2125 DbgPrint("\t%03x\t", i
);
2127 // display hex value of each of the next 8 bytes
2128 for (j
= 0; j
< 8; j
++)
2129 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
2133 NtfsDumpFileAttributes(Vcb
, FileRecord
);
2137 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
2138 PUNICODE_STRING SearchPattern
,
2140 PFILE_RECORD_HEADER
*FileRecord
,
2141 PULONGLONG MFTIndex
,
2142 ULONGLONG CurrentMFTIndex
)
2146 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb
, SearchPattern
, *FirstEntry
, FileRecord
, MFTIndex
, CurrentMFTIndex
);
2148 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, &CurrentMFTIndex
);
2149 if (!NT_SUCCESS(Status
))
2151 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
2155 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2156 if (*FileRecord
== NULL
)
2158 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2159 return STATUS_INSUFFICIENT_RESOURCES
;
2162 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2163 if (!NT_SUCCESS(Status
))
2165 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2166 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2170 *MFTIndex
= CurrentMFTIndex
;
2172 return STATUS_SUCCESS
;