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, %lu, %p, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
, Offset
);
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
* sizeof(WCHAR
)) == (NameLength
* sizeof(WCHAR
)))
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.
200 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
201 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
204 * STATUS_SUCCESS on success.
205 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
206 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
207 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
210 * Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
211 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
212 * This function will wait for exlusive access to the volume fcb.
215 IncreaseMftSize(PDEVICE_EXTENSION Vcb
, BOOLEAN CanWait
)
217 PNTFS_ATTR_CONTEXT BitmapContext
;
218 LARGE_INTEGER BitmapSize
;
219 LARGE_INTEGER DataSize
;
220 LONGLONG BitmapSizeDifference
;
221 ULONG DataSizeDifference
= Vcb
->NtfsInfo
.BytesPerFileRecord
* 8;
224 ULONGLONG BitmapBytes
;
225 ULONGLONG NewBitmapSize
;
230 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb
, CanWait
? "TRUE" : "FALSE");
232 // We need exclusive access to the mft while we change its size
233 if (!ExAcquireResourceExclusiveLite(&(Vcb
->DirResource
), CanWait
))
235 return STATUS_CANT_WAIT
;
238 // Find the bitmap attribute of master file table
239 Status
= FindAttribute(Vcb
, Vcb
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, &BitmapOffset
);
240 if (!NT_SUCCESS(Status
))
242 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
243 ExReleaseResourceLite(&(Vcb
->DirResource
));
247 // Get size of Bitmap Attribute
248 BitmapSize
.QuadPart
= AttributeDataLength(&BitmapContext
->Record
);
250 // Calculate the new mft size
251 DataSize
.QuadPart
= AttributeDataLength(&(Vcb
->MFTContext
->Record
)) + DataSizeDifference
;
253 // Determine how many bytes will make up the bitmap
254 BitmapBytes
= DataSize
.QuadPart
/ Vcb
->NtfsInfo
.BytesPerFileRecord
/ 8;
256 // Determine how much we need to adjust the bitmap size (it's possible we don't)
257 BitmapSizeDifference
= BitmapBytes
- BitmapSize
.QuadPart
;
258 NewBitmapSize
= max(BitmapSize
.QuadPart
+ BitmapSizeDifference
, BitmapSize
.QuadPart
);
260 // Allocate memory for the bitmap
261 BitmapBuffer
= ExAllocatePoolWithTag(NonPagedPool
, NewBitmapSize
, TAG_NTFS
);
264 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
265 ExReleaseResourceLite(&(Vcb
->DirResource
));
266 ReleaseAttributeContext(BitmapContext
);
267 return STATUS_INSUFFICIENT_RESOURCES
;
270 // Zero the bytes we'll be adding
271 RtlZeroMemory(BitmapBuffer
, NewBitmapSize
);
273 // Read the bitmap attribute
274 BytesRead
= ReadAttribute(Vcb
,
279 if (BytesRead
!= BitmapSize
.LowPart
)
281 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
282 ExReleaseResourceLite(&(Vcb
->DirResource
));
283 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
284 ReleaseAttributeContext(BitmapContext
);
285 return STATUS_INVALID_PARAMETER
;
288 // Increase the mft size
289 Status
= SetNonResidentAttributeDataLength(Vcb
, Vcb
->MFTContext
, Vcb
->MftDataOffset
, Vcb
->MasterFileTable
, &DataSize
);
290 if (!NT_SUCCESS(Status
))
292 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
293 ExReleaseResourceLite(&(Vcb
->DirResource
));
294 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
295 ReleaseAttributeContext(BitmapContext
);
299 // If the bitmap grew
300 if (BitmapSizeDifference
> 0)
302 // Set the new bitmap size
303 BitmapSize
.QuadPart
+= BitmapSizeDifference
;
304 if (BitmapContext
->Record
.IsNonResident
)
305 Status
= SetNonResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
307 Status
= SetResidentAttributeDataLength(Vcb
, BitmapContext
, BitmapOffset
, Vcb
->MasterFileTable
, &BitmapSize
);
309 if (!NT_SUCCESS(Status
))
311 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
312 ExReleaseResourceLite(&(Vcb
->DirResource
));
313 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
314 ReleaseAttributeContext(BitmapContext
);
319 //NtfsDumpFileAttributes(Vcb, FileRecord);
321 // Update the file record with the new attribute sizes
322 Status
= UpdateFileRecord(Vcb
, Vcb
->VolumeFcb
->MFTIndex
, Vcb
->MasterFileTable
);
323 if (!NT_SUCCESS(Status
))
325 DPRINT1("ERROR: Failed to update $MFT file record!\n");
326 ExReleaseResourceLite(&(Vcb
->DirResource
));
327 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
328 ReleaseAttributeContext(BitmapContext
);
332 // Write out the new bitmap
333 Status
= WriteAttribute(Vcb
, BitmapContext
, BitmapOffset
, BitmapBuffer
, BitmapSize
.LowPart
, &LengthWritten
);
334 if (!NT_SUCCESS(Status
))
336 ExReleaseResourceLite(&(Vcb
->DirResource
));
337 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
338 ReleaseAttributeContext(BitmapContext
);
339 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
344 ExReleaseResourceLite(&(Vcb
->DirResource
));
345 ExFreePoolWithTag(BitmapBuffer
, TAG_NTFS
);
346 ReleaseAttributeContext(BitmapContext
);
348 return STATUS_SUCCESS
;
352 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext
,
353 PFILE_RECORD_HEADER FileRecord
,
357 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
358 ULONG NextAttributeOffset
;
360 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext
, FileRecord
, AttrOffset
, DataSize
);
362 ASSERT(!AttrContext
->Record
.IsNonResident
);
364 // update ValueLength Field
365 AttrContext
->Record
.Resident
.ValueLength
=
366 Destination
->Resident
.ValueLength
= DataSize
;
368 // calculate the record length and end marker offset
369 AttrContext
->Record
.Length
=
370 Destination
->Length
= DataSize
+ AttrContext
->Record
.Resident
.ValueOffset
;
371 NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
373 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
374 if (NextAttributeOffset
% 8 != 0)
376 USHORT Padding
= ATTR_RECORD_ALIGNMENT
- (NextAttributeOffset
% ATTR_RECORD_ALIGNMENT
);
377 NextAttributeOffset
+= Padding
;
378 AttrContext
->Record
.Length
+= Padding
;
379 Destination
->Length
+= Padding
;
382 // advance Destination to the final "attribute" and set the file record end
383 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
384 SetFileRecordEnd(FileRecord
, Destination
, FILE_RECORD_END
);
388 * @parameter FileRecord
389 * Pointer to a file record. Must be a full record at least
390 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
393 SetAttributeDataLength(PFILE_OBJECT FileObject
,
395 PNTFS_ATTR_CONTEXT AttrContext
,
397 PFILE_RECORD_HEADER FileRecord
,
398 PLARGE_INTEGER DataSize
)
400 NTSTATUS Status
= STATUS_SUCCESS
;
402 DPRINT1("SetAttributeDataLenth(%p, %p, %p, %lu, %p, %I64u)\n",
410 // are we truncating the file?
411 if (DataSize
->QuadPart
< AttributeDataLength(&AttrContext
->Record
))
413 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
415 DPRINT1("Can't truncate a memory-mapped file!\n");
416 return STATUS_USER_MAPPED_FILE
;
420 if (AttrContext
->Record
.IsNonResident
)
422 Status
= SetNonResidentAttributeDataLength(Fcb
->Vcb
,
430 // resident attribute
431 Status
= SetResidentAttributeDataLength(Fcb
->Vcb
,
438 if (!NT_SUCCESS(Status
))
440 DPRINT1("ERROR: Failed to set size of attribute!\n");
444 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
446 // write the updated file record back to disk
447 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
449 if (NT_SUCCESS(Status
))
451 if (AttrContext
->Record
.IsNonResident
)
452 Fcb
->RFCB
.AllocationSize
.QuadPart
= AttrContext
->Record
.NonResident
.AllocatedSize
;
454 Fcb
->RFCB
.AllocationSize
= *DataSize
;
455 Fcb
->RFCB
.FileSize
= *DataSize
;
456 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
457 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
460 return STATUS_SUCCESS
;
464 * @name SetFileRecordEnd
467 * This small function sets a new endpoint for the file record. It set's the final
468 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
471 * Pointer to the file record whose endpoint (length) will be set.
474 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
475 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
478 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
479 * a file record, it preserves the final ULONG that previously ended the record, even though this
480 * value is (to my knowledge) never used. We emulate this behavior.
484 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
485 PNTFS_ATTR_RECORD AttrEnd
,
488 // mark the end of attributes
489 AttrEnd
->Type
= AttributeEnd
;
491 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
492 AttrEnd
->Length
= EndMarker
;
494 // recalculate bytes in use
495 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
499 * @name SetNonResidentAttributeDataLength
502 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
505 * Pointer to a DEVICE_EXTENSION describing the target disk.
508 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
511 * Offset, from the beginning of the record, of the attribute being sized.
514 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
515 * not just the header.
518 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
521 * STATUS_SUCCESS on success;
522 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
523 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
526 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
527 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
528 * any associated files. Synchronization is the callers responsibility.
531 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
532 PNTFS_ATTR_CONTEXT AttrContext
,
534 PFILE_RECORD_HEADER FileRecord
,
535 PLARGE_INTEGER DataSize
)
537 NTSTATUS Status
= STATUS_SUCCESS
;
538 ULONG BytesPerCluster
= Vcb
->NtfsInfo
.BytesPerCluster
;
539 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
540 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
541 ULONG ExistingClusters
= AttrContext
->Record
.NonResident
.AllocatedSize
/ BytesPerCluster
;
543 ASSERT(AttrContext
->Record
.IsNonResident
);
545 // do we need to increase the allocation size?
546 if (AttrContext
->Record
.NonResident
.AllocatedSize
< AllocationSize
)
548 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
549 LARGE_INTEGER LastClusterInDataRun
;
550 ULONG NextAssignedCluster
;
551 ULONG AssignedClusters
;
553 if (ExistingClusters
== 0)
555 LastClusterInDataRun
.QuadPart
= 0;
559 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
560 (LONGLONG
)AttrContext
->Record
.NonResident
.HighestVCN
,
561 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
567 DPRINT1("Error looking up final large MCB entry!\n");
569 // Most likely, HighestVCN went above the largest mapping
570 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
571 return STATUS_INVALID_PARAMETER
;
575 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
576 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
578 while (ClustersNeeded
> 0)
580 Status
= NtfsAllocateClusters(Vcb
,
581 LastClusterInDataRun
.LowPart
+ 1,
583 &NextAssignedCluster
,
586 if (!NT_SUCCESS(Status
))
588 DPRINT1("Error: Unable to allocate requested clusters!\n");
592 // now we need to add the clusters we allocated to the data run
593 Status
= AddRun(Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
594 if (!NT_SUCCESS(Status
))
596 DPRINT1("Error: Unable to add data run!\n");
600 ClustersNeeded
-= AssignedClusters
;
601 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
604 else if (AttrContext
->Record
.NonResident
.AllocatedSize
> AllocationSize
)
606 // shrink allocation size
607 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
608 Status
= FreeClusters(Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
611 // TODO: is the file compressed, encrypted, or sparse?
613 AttrContext
->Record
.NonResident
.AllocatedSize
= AllocationSize
;
614 AttrContext
->Record
.NonResident
.DataSize
= DataSize
->QuadPart
;
615 AttrContext
->Record
.NonResident
.InitializedSize
= DataSize
->QuadPart
;
617 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
618 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
619 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
621 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
627 * @name SetResidentAttributeDataLength
630 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
633 * Pointer to a DEVICE_EXTENSION describing the target disk.
636 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
639 * Offset, from the beginning of the record, of the attribute being sized.
642 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
643 * not just the header.
646 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
649 * STATUS_SUCCESS on success;
650 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
651 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
652 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
653 * last attribute listed in the file record.
656 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
657 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
658 * any associated files. Synchronization is the callers responsibility.
661 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb
,
662 PNTFS_ATTR_CONTEXT AttrContext
,
664 PFILE_RECORD_HEADER FileRecord
,
665 PLARGE_INTEGER DataSize
)
669 // find the next attribute
670 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
671 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
673 ASSERT(!AttrContext
->Record
.IsNonResident
);
675 //NtfsDumpFileAttributes(Vcb, FileRecord);
677 // Do we need to increase the data length?
678 if (DataSize
->QuadPart
> AttrContext
->Record
.Resident
.ValueLength
)
680 // There's usually padding at the end of a record. Do we need to extend past it?
681 ULONG MaxValueLength
= AttrContext
->Record
.Length
- AttrContext
->Record
.Resident
.ValueOffset
;
682 if (MaxValueLength
< DataSize
->LowPart
)
684 // If this is the last attribute, we could move the end marker to the very end of the file record
685 MaxValueLength
+= Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
687 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
689 // convert attribute to non-resident
690 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
691 LARGE_INTEGER AttribDataSize
;
693 ULONG EndAttributeOffset
;
696 DPRINT1("Converting attribute to non-resident.\n");
698 AttribDataSize
.QuadPart
= AttrContext
->Record
.Resident
.ValueLength
;
700 // Is there existing data we need to back-up?
701 if (AttribDataSize
.QuadPart
> 0)
703 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
704 if (AttribData
== NULL
)
706 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
707 return STATUS_INSUFFICIENT_RESOURCES
;
710 // read data to temp buffer
711 Status
= ReadAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
712 if (!NT_SUCCESS(Status
))
714 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
715 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
720 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
722 // Zero out the NonResident structure
723 RtlZeroMemory(&AttrContext
->Record
.NonResident
.LowestVCN
,
724 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
725 RtlZeroMemory(&Destination
->NonResident
.LowestVCN
,
726 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
728 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
729 AttrContext
->Record
.NonResident
.MappingPairsOffset
= Destination
->NonResident
.MappingPairsOffset
= 0x40 + (Destination
->NameLength
* 2);
731 // update the end of the file record
732 // calculate position of end markers (1 byte for empty data run)
733 EndAttributeOffset
= AttrOffset
+ AttrContext
->Record
.NonResident
.MappingPairsOffset
+ 1;
734 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, ATTR_RECORD_ALIGNMENT
);
737 Destination
->Length
= EndAttributeOffset
- AttrOffset
;
738 AttrContext
->Record
.Length
= Destination
->Length
;
740 // Update the file record end
741 SetFileRecordEnd(FileRecord
,
742 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
745 // Initialize the MCB, potentially catch an exception
748 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
750 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
752 DPRINT1("Unable to create LargeMcb!\n");
753 if (AttribDataSize
.QuadPart
> 0)
754 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
755 _SEH2_YIELD(return _SEH2_GetExceptionCode());
758 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
759 AttrContext
->Record
.IsNonResident
= Destination
->IsNonResident
= 1;
761 // Update file record on disk
762 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
763 if (!NT_SUCCESS(Status
))
765 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
766 if (AttribDataSize
.QuadPart
> 0)
767 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
771 // Now we can treat the attribute as non-resident and enlarge it normally
772 Status
= SetNonResidentAttributeDataLength(Vcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
773 if (!NT_SUCCESS(Status
))
775 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
776 if (AttribDataSize
.QuadPart
> 0)
777 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
781 // restore the back-up attribute, if we made one
782 if (AttribDataSize
.QuadPart
> 0)
784 Status
= WriteAttribute(Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
);
785 if (!NT_SUCCESS(Status
))
787 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
788 // TODO: Reverse migration so no data is lost
789 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
793 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
798 else if (DataSize
->LowPart
< AttrContext
->Record
.Resident
.ValueLength
)
800 // we need to decrease the length
801 if (NextAttribute
->Type
!= AttributeEnd
)
803 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
804 return STATUS_NOT_IMPLEMENTED
;
808 // set the new length of the resident attribute (if we didn't migrate it)
809 if (!AttrContext
->Record
.IsNonResident
)
810 InternalSetResidentAttributeLength(AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
812 return STATUS_SUCCESS
;
816 ReadAttribute(PDEVICE_EXTENSION Vcb
,
817 PNTFS_ATTR_CONTEXT Context
,
824 LONGLONG DataRunOffset
;
825 ULONGLONG DataRunLength
;
826 LONGLONG DataRunStartLCN
;
827 ULONGLONG CurrentOffset
;
835 if (!Context
->Record
.IsNonResident
)
837 if (Offset
> Context
->Record
.Resident
.ValueLength
)
839 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
840 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
841 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
846 * Non-resident attribute
850 * I. Find the corresponding start data run.
855 // FIXME: Cache seems to be non-working. Disable it for now
856 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
859 DataRun
= Context
->CacheRun
;
860 LastLCN
= Context
->CacheRunLastLCN
;
861 DataRunStartLCN
= Context
->CacheRunStartLCN
;
862 DataRunLength
= Context
->CacheRunLength
;
863 CurrentOffset
= Context
->CacheRunCurrentOffset
;
868 ULONG UsedBufferSize
;
869 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
874 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
875 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
877 Vcb
->NtfsInfo
.BytesPerFileRecord
,
880 DataRun
= TempBuffer
;
884 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
885 if (DataRunOffset
!= -1)
887 /* Normal data run. */
888 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
889 LastLCN
= DataRunStartLCN
;
893 /* Sparse data run. */
894 DataRunStartLCN
= -1;
897 if (Offset
>= CurrentOffset
&&
898 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
908 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
913 * II. Go through the run list and read the data
916 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
917 if (DataRunStartLCN
== -1)
919 RtlZeroMemory(Buffer
, ReadLength
);
920 Status
= STATUS_SUCCESS
;
924 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
925 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
927 Vcb
->NtfsInfo
.BytesPerSector
,
931 if (NT_SUCCESS(Status
))
933 Length
-= ReadLength
;
934 Buffer
+= ReadLength
;
935 AlreadyRead
+= ReadLength
;
937 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
939 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
940 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
941 if (DataRunOffset
!= (ULONGLONG
)-1)
943 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
944 LastLCN
= DataRunStartLCN
;
947 DataRunStartLCN
= -1;
952 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
953 if (DataRunStartLCN
== -1)
954 RtlZeroMemory(Buffer
, ReadLength
);
957 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
958 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
960 Vcb
->NtfsInfo
.BytesPerSector
,
963 if (!NT_SUCCESS(Status
))
967 Length
-= ReadLength
;
968 Buffer
+= ReadLength
;
969 AlreadyRead
+= ReadLength
;
971 /* We finished this request, but there still data in this data run. */
972 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
976 * Go to next run in the list.
981 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
982 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
983 if (DataRunOffset
!= -1)
985 /* Normal data run. */
986 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
987 LastLCN
= DataRunStartLCN
;
991 /* Sparse data run. */
992 DataRunStartLCN
= -1;
999 if (Context
->Record
.IsNonResident
)
1000 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1002 Context
->CacheRun
= DataRun
;
1003 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
1004 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1005 Context
->CacheRunLength
= DataRunLength
;
1006 Context
->CacheRunLastLCN
= LastLCN
;
1007 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1014 * @name WriteAttribute
1017 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1018 * and it still needs more documentation / cleaning up.
1021 * Volume Control Block indicating which volume to write the attribute to
1024 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1027 * Offset, in bytes, from the beginning of the attribute indicating where to start
1031 * The data that's being written to the device
1034 * How much data will be written, in bytes
1036 * @param RealLengthWritten
1037 * Pointer to a ULONG which will receive how much data was written, in bytes
1040 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1041 * writing to a sparse file.
1043 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1044 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1049 WriteAttribute(PDEVICE_EXTENSION Vcb
,
1050 PNTFS_ATTR_CONTEXT Context
,
1052 const PUCHAR Buffer
,
1054 PULONG RealLengthWritten
)
1058 LONGLONG DataRunOffset
;
1059 ULONGLONG DataRunLength
;
1060 LONGLONG DataRunStartLCN
;
1061 ULONGLONG CurrentOffset
;
1064 PUCHAR SourceBuffer
= Buffer
;
1065 LONGLONG StartingOffset
;
1071 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
);
1073 *RealLengthWritten
= 0;
1075 // is this a resident attribute?
1076 if (!Context
->Record
.IsNonResident
)
1078 ULONG AttributeOffset
;
1079 PNTFS_ATTR_CONTEXT FoundContext
;
1080 PFILE_RECORD_HEADER FileRecord
;
1082 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
1084 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1085 return STATUS_INVALID_PARAMETER
;
1088 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1092 DPRINT1("Error: Couldn't allocate file record!\n");
1093 return STATUS_NO_MEMORY
;
1096 // read the file record
1097 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1099 // find where to write the attribute data to
1100 Status
= FindAttribute(Vcb
, FileRecord
,
1101 Context
->Record
.Type
,
1102 (PCWSTR
)((PCHAR
)&Context
->Record
+ Context
->Record
.NameOffset
),
1103 Context
->Record
.NameLength
,
1107 if (!NT_SUCCESS(Status
))
1109 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1110 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1114 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->Record
.Resident
.ValueLength
);
1115 Offset
+= AttributeOffset
+ Context
->Record
.Resident
.ValueOffset
;
1117 if (Offset
+ Length
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
1119 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1120 ReleaseAttributeContext(FoundContext
);
1121 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1122 return STATUS_INVALID_PARAMETER
;
1125 // copy the data being written into the file record
1126 RtlCopyMemory((PCHAR
)FileRecord
+ Offset
, Buffer
, Length
);
1128 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
1130 ReleaseAttributeContext(FoundContext
);
1131 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
1133 if (NT_SUCCESS(Status
))
1134 *RealLengthWritten
= Length
;
1139 // This is a non-resident attribute.
1141 // I. Find the corresponding start data run.
1143 // FIXME: Cache seems to be non-working. Disable it for now
1144 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1147 DataRun = Context->CacheRun;
1148 LastLCN = Context->CacheRunLastLCN;
1149 DataRunStartLCN = Context->CacheRunStartLCN;
1150 DataRunLength = Context->CacheRunLength;
1151 CurrentOffset = Context->CacheRunCurrentOffset;
1155 ULONG UsedBufferSize
;
1159 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1160 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1162 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
1164 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1167 DataRun
= TempBuffer
;
1171 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1172 if (DataRunOffset
!= -1)
1175 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1176 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1177 LastLCN
= DataRunStartLCN
;
1181 // Sparse data run. We can't support writing to sparse files yet
1182 // (it may require increasing the allocation size).
1183 DataRunStartLCN
= -1;
1184 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1185 return STATUS_NOT_IMPLEMENTED
;
1188 // Have we reached the data run we're trying to write to?
1189 if (Offset
>= CurrentOffset
&&
1190 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
1197 // We reached the last assigned cluster
1198 // TODO: assign new clusters to the end of the file.
1199 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1200 // [We can reach here by creating a new file record when the MFT isn't large enough]
1201 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1202 return STATUS_END_OF_FILE
;
1205 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1209 // II. Go through the run list and write the data
1211 /* REVIEWME -- As adapted from NtfsReadAttribute():
1212 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1213 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1215 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
1217 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
1219 // Write the data to the disk
1220 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1223 Vcb
->NtfsInfo
.BytesPerSector
,
1224 (PVOID
)SourceBuffer
);
1226 // Did the write fail?
1227 if (!NT_SUCCESS(Status
))
1229 Context
->CacheRun
= DataRun
;
1230 Context
->CacheRunOffset
= Offset
;
1231 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1232 Context
->CacheRunLength
= DataRunLength
;
1233 Context
->CacheRunLastLCN
= LastLCN
;
1234 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1239 Length
-= WriteLength
;
1240 SourceBuffer
+= WriteLength
;
1241 *RealLengthWritten
+= WriteLength
;
1243 // Did we write to the end of the data run?
1244 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
1246 // Advance to the next data run
1247 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1248 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1250 if (DataRunOffset
!= (ULONGLONG
)-1)
1252 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1253 LastLCN
= DataRunStartLCN
;
1256 DataRunStartLCN
= -1;
1259 // Do we have more data to write?
1262 // Make sure we don't write past the end of the current data run
1263 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
1265 // Are we dealing with a sparse data run?
1266 if (DataRunStartLCN
== -1)
1268 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1269 return STATUS_NOT_IMPLEMENTED
;
1273 // write the data to the disk
1274 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
1275 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
1277 Vcb
->NtfsInfo
.BytesPerSector
,
1278 (PVOID
)SourceBuffer
);
1279 if (!NT_SUCCESS(Status
))
1283 Length
-= WriteLength
;
1284 SourceBuffer
+= WriteLength
;
1285 *RealLengthWritten
+= WriteLength
;
1287 // We finished this request, but there's still data in this data run.
1288 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1291 // Go to next run in the list.
1295 // that was the last run
1298 // Failed sanity check.
1299 DPRINT1("Encountered EOF before expected!\n");
1300 return STATUS_END_OF_FILE
;
1306 // Advance to the next data run
1307 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1308 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1309 if (DataRunOffset
!= -1)
1312 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1313 LastLCN
= DataRunStartLCN
;
1318 DataRunStartLCN
= -1;
1320 } // end while (Length > 0) [more data to write]
1323 if (Context
->Record
.IsNonResident
)
1324 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1326 Context
->CacheRun
= DataRun
;
1327 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1328 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1329 Context
->CacheRunLength
= DataRunLength
;
1330 Context
->CacheRunLastLCN
= LastLCN
;
1331 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1337 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1339 PFILE_RECORD_HEADER file
)
1341 ULONGLONG BytesRead
;
1343 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1345 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1346 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1348 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1349 return STATUS_PARTIAL_COPY
;
1352 /* Apply update sequence array fixups. */
1353 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1354 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1359 * Searches a file's parent directory (given the parent's index in the mft)
1360 * for the given file. Upon finding an index entry for that file, updates
1361 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1363 * (Most of this code was copied from NtfsFindMftRecord)
1366 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1367 ULONGLONG ParentMFTIndex
,
1368 PUNICODE_STRING FileName
,
1370 ULONGLONG NewDataSize
,
1371 ULONGLONG NewAllocationSize
,
1372 BOOLEAN CaseSensitive
)
1374 PFILE_RECORD_HEADER MftRecord
;
1375 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1376 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1378 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1380 ULONG CurrentEntry
= 0;
1382 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1386 DirSearch
? "TRUE" : "FALSE",
1389 CaseSensitive
? "TRUE" : "FALSE");
1391 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1392 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1394 if (MftRecord
== NULL
)
1396 return STATUS_INSUFFICIENT_RESOURCES
;
1399 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1400 if (!NT_SUCCESS(Status
))
1402 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1406 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1407 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1408 if (!NT_SUCCESS(Status
))
1410 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1414 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1415 if (IndexRecord
== NULL
)
1417 ReleaseAttributeContext(IndexRootCtx
);
1418 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1419 return STATUS_INSUFFICIENT_RESOURCES
;
1422 Status
= ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, AttributeDataLength(&IndexRootCtx
->Record
));
1423 if (!NT_SUCCESS(Status
))
1425 DPRINT1("ERROR: Failed to read Index Root!\n");
1426 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1427 ReleaseAttributeContext(IndexRootCtx
);
1428 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1431 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1432 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1433 // Index root is always resident.
1434 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1436 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1438 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1441 IndexRoot
->SizeOfEntry
,
1452 if (Status
== STATUS_PENDING
)
1454 // we need to write the index root attribute back to disk
1455 ULONG LengthWritten
;
1456 Status
= WriteAttribute(Vcb
, IndexRootCtx
, 0, (PUCHAR
)IndexRecord
, AttributeDataLength(&IndexRootCtx
->Record
), &LengthWritten
);
1457 if (!NT_SUCCESS(Status
))
1459 DPRINT1("ERROR: Couldn't update Index Root!\n");
1464 ReleaseAttributeContext(IndexRootCtx
);
1465 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1466 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1472 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1473 * proper index entry.
1474 * (Heavily based on BrowseIndexEntries)
1477 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1478 PFILE_RECORD_HEADER MftRecord
,
1480 ULONG IndexBlockSize
,
1481 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1482 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1483 PUNICODE_STRING FileName
,
1485 PULONG CurrentEntry
,
1487 ULONGLONG NewDataSize
,
1488 ULONGLONG NewAllocatedSize
,
1489 BOOLEAN CaseSensitive
)
1493 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1494 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1495 ULONGLONG IndexAllocationSize
;
1496 PINDEX_BUFFER IndexBuffer
;
1498 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1508 DirSearch
? "TRUE" : "FALSE",
1511 CaseSensitive
? "TRUE" : "FALSE");
1513 // find the index entry responsible for the file we're trying to update
1514 IndexEntry
= FirstEntry
;
1515 while (IndexEntry
< LastEntry
&&
1516 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1518 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > NTFS_FILE_FIRST_USER_FILE
&&
1519 *CurrentEntry
>= *StartEntry
&&
1520 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1521 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
1523 *StartEntry
= *CurrentEntry
;
1524 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1525 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1526 // indicate that the caller will still need to write the structure to the disk
1527 return STATUS_PENDING
;
1530 (*CurrentEntry
) += 1;
1531 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1532 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1535 /* If we're already browsing a subnode */
1536 if (IndexRecord
== NULL
)
1538 return STATUS_OBJECT_PATH_NOT_FOUND
;
1541 /* If there's no subnode */
1542 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1544 return STATUS_OBJECT_PATH_NOT_FOUND
;
1547 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1548 if (!NT_SUCCESS(Status
))
1550 DPRINT("Corrupted filesystem!\n");
1554 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1555 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1556 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1558 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1559 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1560 if (!NT_SUCCESS(Status
))
1565 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1566 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1567 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1568 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1569 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1570 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1572 Status
= UpdateIndexEntryFileNameSize(NULL
,
1585 if (Status
== STATUS_PENDING
)
1587 // write the index record back to disk
1590 // first we need to update the fixup values for the index block
1591 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1592 if (!NT_SUCCESS(Status
))
1594 DPRINT1("Error: Failed to update fixup sequence array!\n");
1598 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
);
1599 if (!NT_SUCCESS(Status
))
1601 DPRINT1("ERROR Performing write!\n");
1605 Status
= STATUS_SUCCESS
;
1608 if (NT_SUCCESS(Status
))
1614 ReleaseAttributeContext(IndexAllocationCtx
);
1619 * @name UpdateFileRecord
1622 * Writes a file record to the master file table, at a given index.
1625 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1628 * Target index in the master file table to store the file record.
1631 * Pointer to the complete file record which will be written to the master file table.
1634 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1638 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1640 PFILE_RECORD_HEADER FileRecord
)
1643 NTSTATUS Status
= STATUS_SUCCESS
;
1645 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1647 // Add the fixup array to prepare the data for writing to disk
1648 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1650 // write the file record to the master file table
1651 Status
= WriteAttribute(Vcb
, Vcb
->MFTContext
, MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (const PUCHAR
)FileRecord
, Vcb
->NtfsInfo
.BytesPerFileRecord
, &BytesWritten
);
1653 if (!NT_SUCCESS(Status
))
1655 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1658 // remove the fixup array (so the file record pointer can still be used)
1659 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1666 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1667 PNTFS_RECORD_HEADER Record
)
1674 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1675 USANumber
= *(USA
++);
1676 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1677 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1679 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1683 if (*Block
!= USANumber
)
1685 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1686 return STATUS_UNSUCCESSFUL
;
1689 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1693 return STATUS_SUCCESS
;
1697 * @name AddNewMftEntry
1700 * Adds a file record to the master file table of a given device.
1703 * Pointer to a complete file record which will be saved to disk.
1706 * Pointer to the DEVICE_EXTENSION of the target drive.
1708 * @param DestinationIndex
1709 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1712 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1713 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1716 * STATUS_SUCCESS on success.
1717 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1718 * to read the attribute.
1719 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1720 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1723 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1724 PDEVICE_EXTENSION DeviceExt
,
1725 PULONGLONG DestinationIndex
,
1728 NTSTATUS Status
= STATUS_SUCCESS
;
1731 ULONGLONG BitmapDataSize
;
1732 ULONGLONG AttrBytesRead
;
1734 ULONG LengthWritten
;
1735 PNTFS_ATTR_CONTEXT BitmapContext
;
1736 LARGE_INTEGER BitmapBits
;
1737 UCHAR SystemReservedBits
;
1739 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord
, DeviceExt
, DestinationIndex
, CanWait
? "TRUE" : "FALSE");
1741 // First, we have to read the mft's $Bitmap attribute
1742 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1743 if (!NT_SUCCESS(Status
))
1745 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1749 // allocate a buffer for the $Bitmap attribute
1750 BitmapDataSize
= AttributeDataLength(&BitmapContext
->Record
);
1751 BitmapData
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
, TAG_NTFS
);
1754 ReleaseAttributeContext(BitmapContext
);
1755 return STATUS_INSUFFICIENT_RESOURCES
;
1758 // read $Bitmap attribute
1759 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, (PCHAR
)BitmapData
, BitmapDataSize
);
1761 if (AttrBytesRead
== 0)
1763 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1764 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1765 ReleaseAttributeContext(BitmapContext
);
1766 return STATUS_OBJECT_NAME_NOT_FOUND
;
1769 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
1770 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
1771 SystemReservedBits
= BitmapData
[2];
1772 BitmapData
[2] = 0xff;
1774 // Calculate bit count
1775 BitmapBits
.QuadPart
= AttributeDataLength(&(DeviceExt
->MFTContext
->Record
)) /
1776 DeviceExt
->NtfsInfo
.BytesPerFileRecord
;
1777 if (BitmapBits
.HighPart
!= 0)
1779 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
1780 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1781 ReleaseAttributeContext(BitmapContext
);
1782 return STATUS_NOT_IMPLEMENTED
;
1785 // convert buffer into bitmap
1786 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapBits
.LowPart
);
1788 // set next available bit, preferrably after 23rd bit
1789 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
1790 if ((LONG
)MftIndex
== -1)
1792 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1794 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1795 ReleaseAttributeContext(BitmapContext
);
1797 // Couldn't find a free record in the MFT, add some blank records and try again
1798 Status
= IncreaseMftSize(DeviceExt
, CanWait
);
1799 if (!NT_SUCCESS(Status
))
1801 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1805 return AddNewMftEntry(FileRecord
, DeviceExt
, DestinationIndex
, CanWait
);
1808 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
1810 // update file record with index
1811 FileRecord
->MFTRecordNumber
= MftIndex
;
1813 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1815 // Restore the system reserved bits
1816 BitmapData
[2] = SystemReservedBits
;
1818 // write the bitmap back to the MFT's $Bitmap attribute
1819 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
);
1820 if (!NT_SUCCESS(Status
))
1822 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1823 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1824 ReleaseAttributeContext(BitmapContext
);
1828 // update the file record (write it to disk)
1829 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
1831 if (!NT_SUCCESS(Status
))
1833 DPRINT1("ERROR: Unable to write file record!\n");
1834 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1835 ReleaseAttributeContext(BitmapContext
);
1839 *DestinationIndex
= MftIndex
;
1841 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1842 ReleaseAttributeContext(BitmapContext
);
1848 * @name NtfsAddFilenameToDirectory
1851 * Adds a $FILE_NAME attribute to a given directory index.
1854 * Points to the target disk's DEVICE_EXTENSION.
1856 * @param DirectoryMftIndex
1857 * Mft index of the parent directory which will receive the file.
1859 * @param FileReferenceNumber
1860 * File reference of the file to be added to the directory. This is a combination of the
1861 * Mft index and sequence number.
1863 * @param FilenameAttribute
1864 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
1866 * @param CaseSensitive
1867 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1868 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1871 * STATUS_SUCCESS on success.
1872 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1873 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
1876 * WIP - Can only support a few files in a directory.
1877 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
1878 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
1879 * get both attributes added to its parent directory.
1882 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt
,
1883 ULONGLONG DirectoryMftIndex
,
1884 ULONGLONG FileReferenceNumber
,
1885 PFILENAME_ATTRIBUTE FilenameAttribute
,
1886 BOOLEAN CaseSensitive
)
1888 NTSTATUS Status
= STATUS_SUCCESS
;
1889 PFILE_RECORD_HEADER ParentFileRecord
;
1890 PNTFS_ATTR_CONTEXT IndexRootContext
;
1891 PINDEX_ROOT_ATTRIBUTE I30IndexRoot
;
1892 ULONG IndexRootOffset
;
1893 ULONGLONG I30IndexRootLength
;
1894 ULONG LengthWritten
;
1895 PNTFS_ATTR_RECORD DestinationAttribute
;
1896 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
;
1897 ULONG AttributeLength
;
1898 PNTFS_ATTR_RECORD NextAttribute
;
1900 ULONG BtreeIndexLength
;
1903 // Allocate memory for the parent directory
1904 ParentFileRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1905 DeviceExt
->NtfsInfo
.BytesPerFileRecord
,
1907 if (!ParentFileRecord
)
1909 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
1910 return STATUS_INSUFFICIENT_RESOURCES
;
1913 // Open the parent directory
1914 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
1915 if (!NT_SUCCESS(Status
))
1917 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
1918 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
1923 DPRINT1("Dumping old parent file record:\n");
1924 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
1926 // Find the index root attribute for the directory
1927 Status
= FindAttribute(DeviceExt
,
1934 if (!NT_SUCCESS(Status
))
1936 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
1938 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
1942 // Find the maximum index size given what the file record can hold
1943 MaxIndexSize
= DeviceExt
->NtfsInfo
.BytesPerFileRecord
1945 - IndexRootContext
->Record
.Resident
.ValueOffset
1946 - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
)
1947 - (sizeof(ULONG
) * 2);
1949 // Allocate memory for the index root data
1950 I30IndexRootLength
= AttributeDataLength(&IndexRootContext
->Record
);
1951 I30IndexRoot
= ExAllocatePoolWithTag(NonPagedPool
, I30IndexRootLength
, TAG_NTFS
);
1954 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
1955 ReleaseAttributeContext(IndexRootContext
);
1956 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
1957 return STATUS_INSUFFICIENT_RESOURCES
;
1960 // Read the Index Root
1961 Status
= ReadAttribute(DeviceExt
, IndexRootContext
, 0, (PCHAR
)I30IndexRoot
, I30IndexRootLength
);
1962 if (!NT_SUCCESS(Status
))
1964 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex
);
1965 ReleaseAttributeContext(IndexRootContext
);
1966 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
1967 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
1971 // Convert the index to a B*Tree
1972 Status
= CreateBTreeFromIndex(DeviceExt
,
1977 if (!NT_SUCCESS(Status
))
1979 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
1980 ReleaseAttributeContext(IndexRootContext
);
1981 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
1982 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
1988 // Insert the key for the file we're adding
1989 Status
= NtfsInsertKey(FileReferenceNumber
, FilenameAttribute
, NewTree
->RootNode
, CaseSensitive
);
1990 if (!NT_SUCCESS(Status
))
1992 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
1993 DestroyBTree(NewTree
);
1994 ReleaseAttributeContext(IndexRootContext
);
1995 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
1996 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2002 // Convert B*Tree back to Index
2003 Status
= UpdateIndexAllocation(DeviceExt
, NewTree
, I30IndexRoot
->SizeOfEntry
, ParentFileRecord
);
2004 if (!NT_SUCCESS(Status
))
2006 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2007 DestroyBTree(NewTree
);
2008 ReleaseAttributeContext(IndexRootContext
);
2009 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2010 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2014 // Create the Index Root from the B*Tree
2015 Status
= CreateIndexRootFromBTree(DeviceExt
, NewTree
, MaxIndexSize
, &NewIndexRoot
, &BtreeIndexLength
);
2016 if (!NT_SUCCESS(Status
))
2018 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2019 DestroyBTree(NewTree
);
2020 ReleaseAttributeContext(IndexRootContext
);
2021 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2022 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2026 // We're done with the B-Tree now
2027 DestroyBTree(NewTree
);
2029 // Write back the new index root attribute to the parent directory file record
2031 // First, we need to resize the attribute.
2032 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2033 // We can't set the size as we normally would, because if we extend past the file record,
2034 // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
2035 // $ATTRIBUTE_LIST's.
2036 AttributeLength
= NewIndexRoot
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE
, Header
);
2038 if (AttributeLength
!= IndexRootContext
->Record
.Resident
.ValueLength
)
2040 DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)ParentFileRecord
+ IndexRootOffset
);
2042 // Find the attribute (or attribute-end marker) after the index root
2043 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->Length
);
2044 if (NextAttribute
->Type
!= AttributeEnd
)
2046 DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n");
2047 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2048 ReleaseAttributeContext(IndexRootContext
);
2049 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2050 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2051 return STATUS_NOT_IMPLEMENTED
;
2054 // Update the length of the attribute in the file record of the parent directory
2055 InternalSetResidentAttributeLength(IndexRootContext
,
2061 NT_ASSERT(ParentFileRecord
->BytesInUse
<= DeviceExt
->NtfsInfo
.BytesPerFileRecord
);
2063 Status
= UpdateFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2064 if (!NT_SUCCESS(Status
))
2066 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex
);
2067 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2068 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2069 ReleaseAttributeContext(IndexRootContext
);
2070 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2074 // Write the new index root to disk
2075 Status
= WriteAttribute(DeviceExt
,
2078 (PUCHAR
)NewIndexRoot
,
2081 if (!NT_SUCCESS(Status
))
2083 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2084 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2085 ReleaseAttributeContext(IndexRootContext
);
2086 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2087 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2091 // re-read the parent file record, so we can dump it
2092 Status
= ReadFileRecord(DeviceExt
, DirectoryMftIndex
, ParentFileRecord
);
2093 if (!NT_SUCCESS(Status
))
2095 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2099 DPRINT1("Dumping new parent file record:\n");
2100 NtfsDumpFileRecord(DeviceExt
, ParentFileRecord
);
2104 ExFreePoolWithTag(NewIndexRoot
, TAG_NTFS
);
2105 ReleaseAttributeContext(IndexRootContext
);
2106 ExFreePoolWithTag(I30IndexRoot
, TAG_NTFS
);
2107 ExFreePoolWithTag(ParentFileRecord
, TAG_NTFS
);
2113 AddFixupArray(PDEVICE_EXTENSION Vcb
,
2114 PNTFS_RECORD_HEADER Record
)
2116 USHORT
*pShortToFixUp
;
2117 ULONG ArrayEntryCount
= Record
->UsaCount
- 1;
2118 ULONG Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
2121 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
2123 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
2127 for (i
= 0; i
< ArrayEntryCount
; i
++)
2129 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
2131 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
2132 fixupArray
->Array
[i
] = *pShortToFixUp
;
2133 *pShortToFixUp
= fixupArray
->USN
;
2134 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
2137 return STATUS_SUCCESS
;
2141 ReadLCN(PDEVICE_EXTENSION Vcb
,
2146 LARGE_INTEGER DiskSector
;
2148 DiskSector
.QuadPart
= lcn
;
2150 return NtfsReadSectors(Vcb
->StorageDevice
,
2151 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2152 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
2153 Vcb
->NtfsInfo
.BytesPerSector
,
2160 CompareFileName(PUNICODE_STRING FileName
,
2161 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
2163 BOOLEAN CaseSensitive
)
2165 BOOLEAN Ret
, Alloc
= FALSE
;
2166 UNICODE_STRING EntryName
;
2168 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
2170 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
2174 UNICODE_STRING IntFileName
;
2177 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
2182 IntFileName
= *FileName
;
2185 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, !CaseSensitive
, NULL
);
2189 RtlFreeUnicodeString(&IntFileName
);
2196 return (RtlCompareUnicodeString(FileName
, &EntryName
, !CaseSensitive
) == 0);
2203 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
2205 DPRINT1("Entry: %p\n", IndexEntry
);
2206 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
2207 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
2208 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
2209 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
2210 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
2211 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
2212 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
2213 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
2214 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
2215 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
2216 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
2217 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
2218 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
2219 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
2220 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
2221 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
2226 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
2227 PFILE_RECORD_HEADER MftRecord
,
2229 ULONG IndexBlockSize
,
2230 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
2231 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
2232 PUNICODE_STRING FileName
,
2234 PULONG CurrentEntry
,
2236 BOOLEAN CaseSensitive
,
2237 ULONGLONG
*OutMFTIndex
)
2241 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
2242 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
2243 ULONGLONG IndexAllocationSize
;
2244 PINDEX_BUFFER IndexBuffer
;
2246 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2256 DirSearch
? "TRUE" : "FALSE",
2257 CaseSensitive
? "TRUE" : "FALSE",
2260 IndexEntry
= FirstEntry
;
2261 while (IndexEntry
< LastEntry
&&
2262 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
2264 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) >= NTFS_FILE_FIRST_USER_FILE
&&
2265 *CurrentEntry
>= *StartEntry
&&
2266 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
2267 CompareFileName(FileName
, IndexEntry
, DirSearch
, CaseSensitive
))
2269 *StartEntry
= *CurrentEntry
;
2270 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
2271 return STATUS_SUCCESS
;
2274 (*CurrentEntry
) += 1;
2275 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
2276 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
2279 /* If we're already browsing a subnode */
2280 if (IndexRecord
== NULL
)
2282 return STATUS_OBJECT_PATH_NOT_FOUND
;
2285 /* If there's no subnode */
2286 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
2288 return STATUS_OBJECT_PATH_NOT_FOUND
;
2291 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
2292 if (!NT_SUCCESS(Status
))
2294 DPRINT1("Corrupted filesystem!\n");
2298 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
2299 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
2300 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
2302 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
2303 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
2304 if (!NT_SUCCESS(Status
))
2309 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
2310 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
2311 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
2312 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
2313 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
2314 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
2316 Status
= BrowseIndexEntries(NULL
,
2328 if (NT_SUCCESS(Status
))
2334 ReleaseAttributeContext(IndexAllocationCtx
);
2339 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
2341 PUNICODE_STRING FileName
,
2344 BOOLEAN CaseSensitive
,
2345 ULONGLONG
*OutMFTIndex
)
2347 PFILE_RECORD_HEADER MftRecord
;
2348 PNTFS_ATTR_CONTEXT IndexRootCtx
;
2349 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
2351 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
2353 ULONG CurrentEntry
= 0;
2355 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2360 DirSearch
? "TRUE" : "FALSE",
2361 CaseSensitive
? "TRUE" : "FALSE",
2364 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
2365 Vcb
->NtfsInfo
.BytesPerFileRecord
,
2367 if (MftRecord
== NULL
)
2369 return STATUS_INSUFFICIENT_RESOURCES
;
2372 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
2373 if (!NT_SUCCESS(Status
))
2375 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2379 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
2380 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
2381 if (!NT_SUCCESS(Status
))
2383 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2387 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
2388 if (IndexRecord
== NULL
)
2390 ReleaseAttributeContext(IndexRootCtx
);
2391 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2392 return STATUS_INSUFFICIENT_RESOURCES
;
2395 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
2396 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
2397 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
2398 /* Index root is always resident. */
2399 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
2400 ReleaseAttributeContext(IndexRootCtx
);
2402 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
2404 Status
= BrowseIndexEntries(Vcb
,
2407 IndexRoot
->SizeOfEntry
,
2417 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
2418 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
2424 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
2425 PUNICODE_STRING PathName
,
2426 BOOLEAN CaseSensitive
,
2427 PFILE_RECORD_HEADER
*FileRecord
,
2428 PULONGLONG MFTIndex
,
2429 ULONGLONG CurrentMFTIndex
)
2431 UNICODE_STRING Current
, Remaining
;
2433 ULONG FirstEntry
= 0;
2435 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2438 CaseSensitive
? "TRUE" : "FALSE",
2443 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
2445 while (Current
.Length
!= 0)
2447 DPRINT("Current: %wZ\n", &Current
);
2449 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, CaseSensitive
, &CurrentMFTIndex
);
2450 if (!NT_SUCCESS(Status
))
2455 if (Remaining
.Length
== 0)
2458 FsRtlDissectName(Current
, &Current
, &Remaining
);
2461 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2462 if (*FileRecord
== NULL
)
2464 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2465 return STATUS_INSUFFICIENT_RESOURCES
;
2468 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2469 if (!NT_SUCCESS(Status
))
2471 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2472 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2476 *MFTIndex
= CurrentMFTIndex
;
2478 return STATUS_SUCCESS
;
2482 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
2483 PUNICODE_STRING PathName
,
2484 BOOLEAN CaseSensitive
,
2485 PFILE_RECORD_HEADER
*FileRecord
,
2486 PULONGLONG MFTIndex
)
2488 return NtfsLookupFileAt(Vcb
, PathName
, CaseSensitive
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
2492 * @name NtfsDumpFileRecord
2495 * Provides diagnostic information about a file record. Prints a hex dump
2496 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2497 * then prints a dump of each attribute.
2500 * Pointer to a DEVICE_EXTENSION describing the volume.
2503 * Pointer to the file record to be analyzed.
2506 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2507 * in size, and not just the header.
2511 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
2512 PFILE_RECORD_HEADER FileRecord
)
2516 // dump binary data, 8 bytes at a time
2517 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
2519 // display current offset, in hex
2520 DbgPrint("\t%03x\t", i
);
2522 // display hex value of each of the next 8 bytes
2523 for (j
= 0; j
< 8; j
++)
2524 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
2528 NtfsDumpFileAttributes(Vcb
, FileRecord
);
2532 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
2533 PUNICODE_STRING SearchPattern
,
2535 PFILE_RECORD_HEADER
*FileRecord
,
2536 PULONGLONG MFTIndex
,
2537 ULONGLONG CurrentMFTIndex
,
2538 BOOLEAN CaseSensitive
)
2542 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2549 (CaseSensitive
? "TRUE" : "FALSE"));
2551 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, CaseSensitive
, &CurrentMFTIndex
);
2552 if (!NT_SUCCESS(Status
))
2554 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
2558 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
2559 if (*FileRecord
== NULL
)
2561 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2562 return STATUS_INSUFFICIENT_RESOURCES
;
2565 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
2566 if (!NT_SUCCESS(Status
))
2568 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2569 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
2573 *MFTIndex
= CurrentMFTIndex
;
2575 return STATUS_SUCCESS
;